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