Skip to main content

rill_core/queues/
signal.rs

1//! Signal and command types for queues.
2//!
3//! This module defines all command types that can be sent through queues
4//! between Rill components. Each command type represents a specific
5//! action or event in the system.
6//!
7//! ## Command hierarchy
8//!
9//! - `CommandEnum` — top-level enum wrapping all command variants
10//! - `SetParameter` — parameter change for a signal graph node
11//! - `AutomatonCommand` — automaton control
12//! - `SensorCommand` — sensor control
13//! - `ServoCommand` — servo control
14//!
15//! ## Example
16//!
17//! ```no_run
18//! use rill_core::queues::*;
19//! use rill_core::traits::*;
20//!
21//! let node = NodeId(1);
22//! let port = PortId::control_in(node, 0);
23//! let param = ParameterId::new("gain").unwrap();
24//! let cmd = SetParameter::new(port, param, ParamValue::Float(0.5), SignalOrigin::Automaton("lfo".into()));
25//! // Send via ActorRef<SetParameter> or MpscQueue<SetParameter>
26//! ```
27
28use super::command::Command;
29use super::control_event::ControlEvent;
30use crate::time::ClockTick;
31use crate::traits::{ParamValue, ParameterId, PortId};
32use std::fmt;
33use std::time::{SystemTime, UNIX_EPOCH};
34
35//==============================================================================
36// SignalOrigin — signal source
37//==============================================================================
38
39/// Origin of a signal or command.
40///
41/// Used for tracking command provenance, feedback-loop prevention,
42/// and telemetry attribution.
43#[derive(Debug, Clone, PartialEq, Eq, Hash)]
44pub enum SignalOrigin {
45    /// Command from an automaton (LFO, envelope, sequencer).
46    Automaton(String),
47    /// Command from a sensor (physical input device).
48    Sensor(String),
49    /// Command from a servo (physical output device).
50    Servo(String),
51    /// Command from an external source (OSC, etc.).
52    External(String),
53    /// Manual user interaction (UI slider, button, etc.).
54    Manual,
55    /// Command from a script.
56    Script,
57}
58
59impl SignalOrigin {
60    /// Return the human-readable name of this source.
61    pub fn name(&self) -> &str {
62        match self {
63            SignalOrigin::Automaton(name) => name,
64            SignalOrigin::Sensor(name) => name,
65            SignalOrigin::Servo(name) => name,
66            SignalOrigin::External(name) => name,
67            SignalOrigin::Manual => "manual",
68            SignalOrigin::Script => "script",
69        }
70    }
71
72    /// Return the type category of this source (e.g. "automaton", "sensor").
73    pub fn kind(&self) -> &'static str {
74        match self {
75            SignalOrigin::Automaton(_) => "automaton",
76            SignalOrigin::Sensor(_) => "sensor",
77            SignalOrigin::Servo(_) => "servo",
78            SignalOrigin::External(_) => "external",
79            SignalOrigin::Manual => "manual",
80            SignalOrigin::Script => "script",
81        }
82    }
83}
84
85impl fmt::Display for SignalOrigin {
86    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87        match self {
88            SignalOrigin::Automaton(name) => write!(f, "⚙️ {}", name),
89            SignalOrigin::Sensor(name) => write!(f, "👁️ {}", name),
90            SignalOrigin::Servo(name) => write!(f, "🦾 {}", name),
91            SignalOrigin::External(name) => write!(f, "🌍 {}", name),
92            SignalOrigin::Manual => write!(f, "👤 manual"),
93            SignalOrigin::Script => write!(f, "📜 script"),
94        }
95    }
96}
97
98// ===== SetParameter =====
99
100/// Command to change a parameter value on a signal graph node.
101#[derive(Debug, Clone)]
102pub struct SetParameter {
103    /// Target port.
104    pub port: PortId,
105    /// Target parameter identifier.
106    pub parameter: ParameterId,
107    /// New parameter value.
108    pub value: ParamValue,
109    /// Origin of this command.
110    pub source: SignalOrigin,
111    /// Unix timestamp (microseconds).
112    pub timestamp: u64,
113}
114
115impl SetParameter {
116    /// Create a new parameter-change command with the current timestamp.
117    pub fn new(
118        port: PortId,
119        parameter: ParameterId,
120        value: ParamValue,
121        source: SignalOrigin,
122    ) -> Self {
123        Self {
124            port,
125            parameter,
126            value,
127            source,
128            timestamp: Self::now(),
129        }
130    }
131
132    /// Create a new parameter-change command with an explicit timestamp.
133    pub fn with_timestamp(
134        port: PortId,
135        parameter: ParameterId,
136        value: ParamValue,
137        source: SignalOrigin,
138        timestamp: u64,
139    ) -> Self {
140        Self {
141            port,
142            parameter,
143            value,
144            source,
145            timestamp,
146        }
147    }
148
149    /// Return the current Unix time in microseconds.
150    pub fn now() -> u64 {
151        SystemTime::now()
152            .duration_since(UNIX_EPOCH)
153            .unwrap_or_default()
154            .as_micros() as u64
155    }
156}
157
158impl PartialEq for SetParameter {
159    fn eq(&self, other: &Self) -> bool {
160        self.port == other.port
161            && self.parameter == other.parameter
162            && self.value == other.value
163            && self.source == other.source
164    }
165}
166
167impl fmt::Display for SetParameter {
168    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169        write!(
170            f,
171            "[{}] {} → {}::{} = {:?}",
172            self.timestamp, self.source, self.port, self.parameter, self.value
173        )
174    }
175}
176
177// Implement the Command trait for SetParameter
178impl Command for SetParameter {}
179
180// ===== AutomatonCommand =====
181
182/// Commands for controlling automata (LFOs, envelopes, sequencers).
183#[derive(Debug, Clone)]
184pub enum AutomatonCommand {
185    /// Enable or disable an automaton by ID.
186    SetEnabled {
187        /// Automaton identifier.
188        id: String,
189        /// Whether the automaton should be enabled.
190        enabled: bool,
191    },
192    /// Set a named parameter on an automaton.
193    SetParameter {
194        /// Automaton identifier.
195        id: String,
196        /// Parameter name.
197        name: String,
198        /// Parameter value.
199        value: f32,
200    },
201    /// Reset an automaton to its initial state.
202    Reset {
203        /// Automaton identifier.
204        id: String,
205    },
206    /// Connect an automaton output to another automaton input.
207    Connect {
208        /// Source automaton identifier.
209        from: String,
210        /// Destination automaton identifier.
211        to: String,
212        /// Connection gain.
213        gain: f32,
214    },
215    /// Disconnect two automata.
216    Disconnect {
217        /// Source automaton identifier.
218        from: String,
219        /// Destination automaton identifier.
220        to: String,
221    },
222    /// Create a new automaton instance.
223    Create {
224        /// Automaton type (e.g. "lfo", "envelope").
225        kind: String,
226        /// New automaton identifier.
227        id: String,
228        /// Initial parameter values.
229        params: Vec<(String, f32)>,
230    },
231    /// Destroy an automaton by ID.
232    Destroy {
233        /// Automaton identifier to remove.
234        id: String,
235    },
236    /// Wake the automaton to process a clock tick (no payload required).
237    Wake {
238        /// Automaton identifier.
239        id: String,
240    },
241    /// Set a value from UI input for conflict resolution.
242    UiValue {
243        /// Automaton identifier.
244        id: String,
245        /// Raw value from UI.
246        value: f64,
247    },
248    /// Release UI control (unfreeze in TouchOverride mode).
249    UiRelease {
250        /// Automaton identifier.
251        id: String,
252    },
253}
254
255impl AutomatonCommand {
256    /// Return the target automaton ID, if applicable.
257    pub fn automaton_id(&self) -> Option<&str> {
258        match self {
259            AutomatonCommand::SetEnabled { id, .. } => Some(id),
260            AutomatonCommand::SetParameter { id, .. } => Some(id),
261            AutomatonCommand::Reset { id } => Some(id),
262            AutomatonCommand::Connect { from, to: _to, .. } => Some(from),
263            AutomatonCommand::Disconnect { from, to: _to } => Some(from),
264            AutomatonCommand::Create { id, .. } => Some(id),
265            AutomatonCommand::Destroy { id } => Some(id),
266            AutomatonCommand::Wake { id } => Some(id),
267            AutomatonCommand::UiValue { id, .. } => Some(id),
268            AutomatonCommand::UiRelease { id } => Some(id),
269        }
270    }
271}
272
273impl fmt::Display for AutomatonCommand {
274    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
275        match self {
276            AutomatonCommand::SetEnabled { id, enabled } => {
277                write!(f, "Automaton[{}] set_enabled({})", id, enabled)
278            }
279            AutomatonCommand::SetParameter { id, name, value } => {
280                write!(f, "Automaton[{}] set_param({}={:.2})", id, name, value)
281            }
282            AutomatonCommand::Reset { id } => {
283                write!(f, "Automaton[{}] reset()", id)
284            }
285            AutomatonCommand::Connect { from, to, gain } => {
286                write!(f, "Automaton connect {} → {} gain={:.2}", from, to, gain)
287            }
288            AutomatonCommand::Disconnect { from, to } => {
289                write!(f, "Automaton disconnect {} → {}", from, to)
290            }
291            AutomatonCommand::Create { kind, id, params } => {
292                write!(
293                    f,
294                    "Automaton create {} as {} with {} params",
295                    kind,
296                    id,
297                    params.len()
298                )
299            }
300            AutomatonCommand::Destroy { id } => {
301                write!(f, "Automaton destroy {}", id)
302            }
303            AutomatonCommand::Wake { id } => {
304                write!(f, "Automaton[{}] wake(tick)", id)
305            }
306            AutomatonCommand::UiValue { id, value } => {
307                write!(f, "Automaton[{}] ui_value({:.2})", id, value)
308            }
309            AutomatonCommand::UiRelease { id } => {
310                write!(f, "Automaton[{}] ui_release()", id)
311            }
312        }
313    }
314}
315
316impl Command for AutomatonCommand {}
317
318// ===== SensorCommand =====
319
320/// Type of sensor calibration to perform.
321#[derive(Debug, Clone)]
322pub enum CalibrationKind {
323    /// Automatically determine min/max from signal range.
324    Auto,
325    /// Set the current sensor reading as the minimum value.
326    SetCurrentAsMin,
327    /// Set the current sensor reading as the maximum value.
328    SetCurrentAsMax,
329    /// Reset calibration to factory defaults.
330    Reset,
331}
332
333impl fmt::Display for CalibrationKind {
334    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
335        match self {
336            CalibrationKind::Auto => write!(f, "auto"),
337            CalibrationKind::SetCurrentAsMin => write!(f, "set_min"),
338            CalibrationKind::SetCurrentAsMax => write!(f, "set_max"),
339            CalibrationKind::Reset => write!(f, "reset"),
340        }
341    }
342}
343
344/// Commands for controlling sensors (physical input devices).
345#[derive(Debug, Clone)]
346pub enum SensorCommand {
347    /// Start listening to a sensor data source.
348    StartListening {
349        /// Sensor identifier.
350        id: String,
351        /// Data source to listen to.
352        source: String,
353    },
354    /// Stop listening to a sensor.
355    StopListening {
356        /// Sensor identifier.
357        id: String,
358    },
359    /// Set sensor sensitivity.
360    SetSensitivity {
361        /// Sensor identifier.
362        id: String,
363        /// Sensitivity value.
364        value: f32,
365    },
366    /// Calibrate a sensor.
367    Calibrate {
368        /// Sensor identifier.
369        id: String,
370        /// Calibration type.
371        kind: CalibrationKind,
372    },
373    /// Enable or disable a sensor.
374    SetEnabled {
375        /// Sensor identifier.
376        id: String,
377        /// Whether the sensor should be enabled.
378        enabled: bool,
379    },
380}
381
382impl SensorCommand {
383    /// Return the target sensor ID.
384    pub fn sensor_id(&self) -> &str {
385        match self {
386            SensorCommand::StartListening { id, .. } => id,
387            SensorCommand::StopListening { id } => id,
388            SensorCommand::SetSensitivity { id, .. } => id,
389            SensorCommand::Calibrate { id, .. } => id,
390            SensorCommand::SetEnabled { id, .. } => id,
391        }
392    }
393}
394
395impl fmt::Display for SensorCommand {
396    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
397        match self {
398            SensorCommand::StartListening { id, source } => {
399                write!(f, "Sensor[{}] start listening to {}", id, source)
400            }
401            SensorCommand::StopListening { id } => {
402                write!(f, "Sensor[{}] stop listening", id)
403            }
404            SensorCommand::SetSensitivity { id, value } => {
405                write!(f, "Sensor[{}] set sensitivity to {:.2}", id, value)
406            }
407            SensorCommand::Calibrate { id, kind } => {
408                write!(f, "Sensor[{}] calibrate {}", id, kind)
409            }
410            SensorCommand::SetEnabled { id, enabled } => {
411                write!(f, "Sensor[{}] set enabled({})", id, enabled)
412            }
413        }
414    }
415}
416
417impl Command for SensorCommand {}
418
419// ===== ServoCommand =====
420
421/// Mapping function type for servo output value transformation.
422#[derive(Debug, Clone)]
423pub enum MappingType {
424    /// Linear mapping (identity).
425    Linear,
426    /// Exponential mapping.
427    Exponential,
428    /// Logarithmic mapping.
429    Logarithmic,
430    /// Inverted (reverse) mapping.
431    Inverted,
432    /// Custom named mapping function.
433    Custom(String),
434}
435
436impl fmt::Display for MappingType {
437    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
438        match self {
439            MappingType::Linear => write!(f, "linear"),
440            MappingType::Exponential => write!(f, "exponential"),
441            MappingType::Logarithmic => write!(f, "logarithmic"),
442            MappingType::Inverted => write!(f, "inverted"),
443            MappingType::Custom(s) => write!(f, "custom({})", s),
444        }
445    }
446}
447
448/// Commands for controlling servos (physical output devices).
449#[derive(Debug, Clone)]
450pub enum ServoCommand {
451    /// Bind a servo to follow an automaton output.
452    BindToAutomaton {
453        /// Servo identifier.
454        servo_id: String,
455        /// Automaton identifier to bind to.
456        automaton_id: String,
457    },
458    /// Bind a servo directly to a signal graph parameter.
459    BindToParameter {
460        /// Servo identifier.
461        servo_id: String,
462        /// Target port.
463        port: PortId,
464        /// Target parameter.
465        parameter: ParameterId,
466    },
467    /// Unbind a servo from all sources.
468    Unbind {
469        /// Servo identifier.
470        servo_id: String,
471    },
472    /// Set the output range of a servo.
473    SetRange {
474        /// Servo identifier.
475        servo_id: String,
476        /// Minimum output value.
477        min: f32,
478        /// Maximum output value.
479        max: f32,
480    },
481    /// Set the value mapping function for a servo.
482    SetMapping {
483        /// Servo identifier.
484        servo_id: String,
485        /// Mapping type.
486        mapping: MappingType,
487    },
488    /// Enable or disable a servo.
489    SetEnabled {
490        /// Servo identifier.
491        servo_id: String,
492        /// Whether the servo should be enabled.
493        enabled: bool,
494    },
495}
496
497impl ServoCommand {
498    /// Return the target servo ID.
499    pub fn servo_id(&self) -> &str {
500        match self {
501            ServoCommand::BindToAutomaton { servo_id, .. } => servo_id,
502            ServoCommand::BindToParameter { servo_id, .. } => servo_id,
503            ServoCommand::Unbind { servo_id } => servo_id,
504            ServoCommand::SetRange { servo_id, .. } => servo_id,
505            ServoCommand::SetMapping { servo_id, .. } => servo_id,
506            ServoCommand::SetEnabled { servo_id, .. } => servo_id,
507        }
508    }
509}
510
511impl fmt::Display for ServoCommand {
512    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
513        match self {
514            ServoCommand::BindToAutomaton {
515                servo_id,
516                automaton_id,
517            } => {
518                write!(f, "Servo[{}] bind to automaton {}", servo_id, automaton_id)
519            }
520            ServoCommand::BindToParameter {
521                servo_id,
522                port,
523                parameter,
524            } => {
525                write!(f, "Servo[{}] bind to {}::{}", servo_id, port, parameter)
526            }
527            ServoCommand::Unbind { servo_id } => {
528                write!(f, "Servo[{}] unbind", servo_id)
529            }
530            ServoCommand::SetRange { servo_id, min, max } => {
531                write!(f, "Servo[{}] set range [{}, {}]", servo_id, min, max)
532            }
533            ServoCommand::SetMapping { servo_id, mapping } => {
534                write!(f, "Servo[{}] set mapping {}", servo_id, mapping)
535            }
536            ServoCommand::SetEnabled { servo_id, enabled } => {
537                write!(f, "Servo[{}] set enabled({})", servo_id, enabled)
538            }
539        }
540    }
541}
542
543impl Command for ServoCommand {}
544
545// ===== CommandType (formerly Command) — common command type =====
546
547/// Runtime command type identifier.
548#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
549pub enum CommandType {
550    /// Parameter change command.
551    SetParameter,
552    /// Automaton control command.
553    Automaton,
554    /// Sensor control command.
555    Sensor,
556    /// Servo control command.
557    Servo,
558    /// Clock tick.
559    ClockTick,
560    /// Stop command — shuts down the actor's I/O loop.
561    Stop,
562    /// System command.
563    System,
564    /// Control event from a sensor.
565    Control,
566}
567
568impl fmt::Display for CommandType {
569    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
570        match self {
571            CommandType::SetParameter => write!(f, "SetParameter"),
572            CommandType::Automaton => write!(f, "Automaton"),
573            CommandType::Sensor => write!(f, "Sensor"),
574            CommandType::Servo => write!(f, "Servo"),
575            CommandType::ClockTick => write!(f, "ClockTick"),
576            CommandType::Stop => write!(f, "Stop"),
577            CommandType::System => write!(f, "System"),
578            CommandType::Control => write!(f, "Control"),
579        }
580    }
581}
582
583/// Universal command enum combining all possible command types.
584///
585/// Useful when a single queue must transport multiple command types,
586/// or when the command type is not known ahead of time.
587#[derive(Debug, Clone)]
588pub enum CommandEnum {
589    /// Parameter change command.
590    SetParameter(SetParameter),
591    /// Automaton control command.
592    Automaton(AutomatonCommand),
593    /// Sensor control command.
594    Sensor(SensorCommand),
595    /// Servo control command.
596    Servo(ServoCommand),
597    /// Clock tick — sent from Graph to Patchbay each processing block.
598    ClockTick(ClockTick),
599    /// Control event — decoded by a sensor, dispatched to a servo
600    /// for mapping to a graph parameter.
601    Control(ControlEvent),
602    /// Stop command — shuts down the actor's I/O loop.
603    Stop,
604    /// System-level command with opaque payload.
605    System {
606        /// System command kind.
607        kind: String,
608        /// Opaque command data.
609        data: Vec<u8>,
610    },
611}
612
613impl CommandEnum {
614    /// Return the runtime type tag of this command.
615    pub fn command_type(&self) -> CommandType {
616        match self {
617            CommandEnum::SetParameter(_) => CommandType::SetParameter,
618            CommandEnum::Automaton(_) => CommandType::Automaton,
619            CommandEnum::Sensor(_) => CommandType::Sensor,
620            CommandEnum::Servo(_) => CommandType::Servo,
621            CommandEnum::ClockTick(_) => CommandType::ClockTick,
622            CommandEnum::Stop => CommandType::Stop,
623            CommandEnum::System { .. } => CommandType::System,
624            CommandEnum::Control(_) => CommandType::Control,
625        }
626    }
627
628    /// If this is a `SetParameter` command, return the target `NodeId`.
629    pub fn target_node_id(&self) -> Option<crate::traits::NodeId> {
630        match self {
631            CommandEnum::SetParameter(cmd) => Some(cmd.port.node_id()),
632            _ => None,
633        }
634    }
635
636    /// Return the timestamp if the command carries one.
637    pub fn timestamp(&self) -> Option<u64> {
638        match self {
639            CommandEnum::SetParameter(cmd) => Some(cmd.timestamp),
640            _ => None,
641        }
642    }
643
644    /// Try to downcast to `SetParameter`.
645    pub fn as_set_parameter(&self) -> Option<&SetParameter> {
646        match self {
647            CommandEnum::SetParameter(cmd) => Some(cmd),
648            _ => None,
649        }
650    }
651
652    /// Try to downcast to `AutomatonCommand`.
653    pub fn as_automaton(&self) -> Option<&AutomatonCommand> {
654        match self {
655            CommandEnum::Automaton(cmd) => Some(cmd),
656            _ => None,
657        }
658    }
659
660    /// Try to downcast to `SensorCommand`.
661    pub fn as_sensor(&self) -> Option<&SensorCommand> {
662        match self {
663            CommandEnum::Sensor(cmd) => Some(cmd),
664            _ => None,
665        }
666    }
667
668    /// Try to downcast to `ServoCommand`.
669    pub fn as_servo(&self) -> Option<&ServoCommand> {
670        match self {
671            CommandEnum::Servo(cmd) => Some(cmd),
672            _ => None,
673        }
674    }
675
676    /// Try to downcast to `ClockTick`.
677    pub fn as_clock_tick(&self) -> Option<&ClockTick> {
678        match self {
679            CommandEnum::ClockTick(tick) => Some(tick),
680            _ => None,
681        }
682    }
683}
684
685impl fmt::Display for CommandEnum {
686    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
687        match self {
688            CommandEnum::SetParameter(cmd) => write!(f, "{}", cmd),
689            CommandEnum::Automaton(cmd) => write!(f, "{}", cmd),
690            CommandEnum::Sensor(cmd) => write!(f, "{}", cmd),
691            CommandEnum::Servo(cmd) => write!(f, "{}", cmd),
692            CommandEnum::ClockTick(tick) => write!(
693                f,
694                "ClockTick(pos={}, dt={}samp)",
695                tick.sample_pos, tick.samples_since_last,
696            ),
697            CommandEnum::Stop => write!(f, "Stop"),
698            CommandEnum::System { kind, data } => {
699                write!(f, "System({kind}, {} bytes)", data.len())
700            }
701            CommandEnum::Control(event) => {
702                write!(f, "ControlEvent({event:?})")
703            }
704        }
705    }
706}
707
708// Implement the Command trait for CommandEnum (used via actor mailboxes).
709impl Command for CommandEnum {}
710
711// ===== Conversions =====
712
713/// Marker trait for types that can be converted into a command.
714pub trait ToCommand: Send + 'static {
715    /// The command type this type converts into.
716    type Command: Into<CommandEnum>;
717
718    /// Convert self into a command.
719    fn to_command(self) -> Self::Command;
720}
721
722/// Marker trait for types that can be constructed from a command.
723pub trait FromCommand: Sized {
724    /// The command type this type is constructed from.
725    type Command: TryInto<Self> + Clone;
726
727    /// Try to construct from a command.
728    fn from_command(cmd: Self::Command) -> Option<Self>;
729}
730
731impl From<SetParameter> for CommandEnum {
732    fn from(cmd: SetParameter) -> Self {
733        CommandEnum::SetParameter(cmd)
734    }
735}
736
737impl From<AutomatonCommand> for CommandEnum {
738    fn from(cmd: AutomatonCommand) -> Self {
739        CommandEnum::Automaton(cmd)
740    }
741}
742
743impl From<SensorCommand> for CommandEnum {
744    fn from(cmd: SensorCommand) -> Self {
745        CommandEnum::Sensor(cmd)
746    }
747}
748
749impl From<ServoCommand> for CommandEnum {
750    fn from(cmd: ServoCommand) -> Self {
751        CommandEnum::Servo(cmd)
752    }
753}
754
755impl TryFrom<CommandEnum> for SetParameter {
756    type Error = ();
757
758    fn try_from(cmd: CommandEnum) -> Result<Self, Self::Error> {
759        match cmd {
760            CommandEnum::SetParameter(cmd) => Ok(cmd),
761            _ => Err(()),
762        }
763    }
764}
765
766impl TryFrom<CommandEnum> for AutomatonCommand {
767    type Error = ();
768
769    fn try_from(cmd: CommandEnum) -> Result<Self, Self::Error> {
770        match cmd {
771            CommandEnum::Automaton(cmd) => Ok(cmd),
772            _ => Err(()),
773        }
774    }
775}
776
777impl TryFrom<CommandEnum> for SensorCommand {
778    type Error = ();
779
780    fn try_from(cmd: CommandEnum) -> Result<Self, Self::Error> {
781        match cmd {
782            CommandEnum::Sensor(cmd) => Ok(cmd),
783            _ => Err(()),
784        }
785    }
786}
787
788impl TryFrom<CommandEnum> for ServoCommand {
789    type Error = ();
790
791    fn try_from(cmd: CommandEnum) -> Result<Self, Self::Error> {
792        match cmd {
793            CommandEnum::Servo(cmd) => Ok(cmd),
794            _ => Err(()),
795        }
796    }
797}