valentine

Terminal control panel for the Focusrite Scarlett 18i20 — a from-scratch replacement for Focusrite Control.
Log | Files | Refs | README | LICENSE

protocol.rs (7814B)


      1 //! High-level scarlett2 operations built on a [`Transport`].
      2 //!
      3 //! These are the primitives the whole feature set rides on. Most controls
      4 //! (phantom power, air, pad, inst/line, mutes, monitor level, …) are just
      5 //! [`Scarlett::get_data`] / [`Scarlett::set_data`] at a per-feature offset, then
      6 //! an [`Scarlett::activate`]. The matrix mixer / routing / metering use their own
      7 //! opcodes and are layered on top in `matrix.rs`.
      8 
      9 use crate::packet::Response;
     10 use crate::transport::{Transport, TransportError};
     11 
     12 /// scarlett2 command opcodes (from the kernel driver, confirmed by the spike).
     13 pub mod op {
     14     pub const INIT_1: u32 = 0x0000_0000;
     15     pub const INIT_2: u32 = 0x0000_0002;
     16     pub const REBOOT: u32 = 0x0000_0003;
     17     pub const GET_METER: u32 = 0x0000_1001;
     18     pub const GET_MIX: u32 = 0x0000_2001;
     19     pub const SET_MIX: u32 = 0x0000_2002;
     20     pub const GET_MUX: u32 = 0x0000_3001;
     21     pub const SET_MUX: u32 = 0x0000_3002;
     22     pub const GET_SYNC: u32 = 0x0000_6004;
     23     pub const GET_DATA: u32 = 0x0080_0000;
     24     pub const SET_DATA: u32 = 0x0080_0001;
     25     pub const DATA_CMD: u32 = 0x0080_0002;
     26 }
     27 
     28 /// `activate` argument that persists the current settings to the device's flash
     29 /// (standalone mode).
     30 pub const CONFIG_SAVE: u32 = 6;
     31 
     32 /// A connected (or mocked) Scarlett, speaking the scarlett2 protocol.
     33 pub struct Scarlett<T: Transport> {
     34     transport: T,
     35     firmware: u32,
     36 }
     37 
     38 impl<T: Transport> Scarlett<T> {
     39     pub fn new(transport: T) -> Self {
     40         Self { transport, firmware: 0 }
     41     }
     42 
     43     /// Firmware version reported by [`Scarlett::init`] (0 until init runs).
     44     pub fn firmware(&self) -> u32 {
     45         self.firmware
     46     }
     47 
     48     /// Issue one raw scarlett2 command (used by the matrix/mux/meter ops in
     49     /// `matrix.rs`). Crate-internal so the wire stays encapsulated.
     50     pub(crate) fn command(
     51         &mut self,
     52         cmd: u32,
     53         payload: &[u8],
     54         resp_cap: usize,
     55     ) -> Result<Response, TransportError> {
     56         self.transport.command(cmd, payload, resp_cap)
     57     }
     58 
     59     /// Run the INIT handshake: a raw priming read, then INIT_1 and INIT_2 each
     60     /// with the sequence counter reset to 1.
     61     ///
     62     /// INIT_2 (which carries the firmware version at byte 8) is **best-effort**:
     63     /// on macOS its response is normally signalled on the interrupt endpoint,
     64     /// which we can't claim while FocusriteControlServer holds it, so the IN read
     65     /// times out. The Phase-0 spike confirmed every other command (GET_SYNC,
     66     /// GET_DATA, …) works regardless, so a timed-out INIT_2 must not abort init —
     67     /// we just report firmware 0. INIT_1 must still succeed.
     68     pub fn init(&mut self) -> Result<u32, TransportError> {
     69         let _ = self.transport.raw_in(0, 24); // step 0 (best-effort)
     70 
     71         self.transport.set_seq(1);
     72         self.transport.command(op::INIT_1, &[], 0)?;
     73 
     74         self.transport.set_seq(1);
     75         self.firmware = match self.transport.command(op::INIT_2, &[], 32) {
     76             Ok(resp) => resp
     77                 .payload
     78                 .get(8..12)
     79                 .map(|s| u32::from_le_bytes(s.try_into().unwrap()))
     80                 .unwrap_or(0),
     81             Err(_) => 0, // firmware unknown; not fatal
     82         };
     83         Ok(self.firmware)
     84     }
     85 
     86     /// Read `size` bytes from the device data space at `offset`.
     87     pub fn get_data(&mut self, offset: u32, size: u32) -> Result<Vec<u8>, TransportError> {
     88         let mut payload = Vec::with_capacity(8);
     89         payload.extend_from_slice(&offset.to_le_bytes());
     90         payload.extend_from_slice(&size.to_le_bytes());
     91         Ok(self
     92             .transport
     93             .command(op::GET_DATA, &payload, size as usize)?
     94             .payload)
     95     }
     96 
     97     /// Write a `size`-byte (1, 2, or 4) little-endian `value` at `offset`.
     98     /// Remember to [`Scarlett::activate`] afterwards to apply it.
     99     pub fn set_data(&mut self, offset: u32, size: u32, value: u32) -> Result<(), TransportError> {
    100         let size = size.clamp(1, 4);
    101         let mut payload = Vec::with_capacity(8 + size as usize);
    102         payload.extend_from_slice(&offset.to_le_bytes());
    103         payload.extend_from_slice(&size.to_le_bytes());
    104         payload.extend_from_slice(&value.to_le_bytes()[..size as usize]);
    105         self.transport.command(op::SET_DATA, &payload, 0)?;
    106         Ok(())
    107     }
    108 
    109     /// Apply previously uploaded `set_data` changes. `activate` is the per-item
    110     /// activation value (or [`CONFIG_SAVE`] to persist to flash).
    111     pub fn activate(&mut self, activate: u32) -> Result<(), TransportError> {
    112         self.transport
    113             .command(op::DATA_CMD, &activate.to_le_bytes(), 0)?;
    114         Ok(())
    115     }
    116 
    117     /// Persist the current configuration to the device's flash (standalone mode).
    118     pub fn save_to_flash(&mut self) -> Result<(), TransportError> {
    119         self.activate(CONFIG_SAVE)
    120     }
    121 
    122     /// Clock-sync lock status. The device returns a 4-byte value; non-zero = locked.
    123     pub fn get_sync(&mut self) -> Result<bool, TransportError> {
    124         let resp = self.transport.command(op::GET_SYNC, &[], 4)?;
    125         Ok(resp.payload.iter().any(|&b| b != 0))
    126     }
    127 
    128     /// Consume the wrapper and get the transport back (e.g. to re-issue init).
    129     pub fn into_transport(self) -> T {
    130         self.transport
    131     }
    132 }
    133 
    134 #[cfg(test)]
    135 mod tests {
    136     use super::*;
    137     use crate::transport::mock::MockTransport;
    138 
    139     #[test]
    140     fn init_resets_seq_and_extracts_firmware() {
    141         let mut m = MockTransport::new();
    142         m.push_response(op::INIT_1, &[]);
    143         let mut p = vec![0u8; 12];
    144         p[8..12].copy_from_slice(&0x1234u32.to_le_bytes());
    145         m.push_response(op::INIT_2, &p);
    146 
    147         let mut dev = Scarlett::new(m);
    148         let fw = dev.init().unwrap();
    149         assert_eq!(fw, 0x1234);
    150         assert_eq!(dev.firmware(), 0x1234);
    151 
    152         let m = dev.into_transport();
    153         assert_eq!(m.sent[0].0, op::INIT_1);
    154         assert_eq!(m.sent[1].0, op::INIT_2);
    155     }
    156 
    157     #[test]
    158     fn get_data_builds_offset_size_payload() {
    159         let mut m = MockTransport::new();
    160         m.push_response(op::GET_DATA, &[0xaa, 0xbb]);
    161         let mut dev = Scarlett::new(m);
    162 
    163         let data = dev.get_data(0x40, 2).unwrap();
    164         assert_eq!(data, vec![0xaa, 0xbb]);
    165 
    166         let m = dev.into_transport();
    167         let (cmd, payload) = &m.sent[0];
    168         assert_eq!(*cmd, op::GET_DATA);
    169         assert_eq!(payload, &vec![0x40, 0, 0, 0, 0x02, 0, 0, 0]);
    170     }
    171 
    172     #[test]
    173     fn set_data_packs_value_to_declared_width() {
    174         let mut m = MockTransport::new();
    175         m.push_response(op::SET_DATA, &[]);
    176         let mut dev = Scarlett::new(m);
    177 
    178         dev.set_data(0x9c, 1, 1).unwrap();
    179 
    180         let m = dev.into_transport();
    181         let (cmd, payload) = &m.sent[0];
    182         assert_eq!(*cmd, op::SET_DATA);
    183         assert_eq!(payload, &vec![0x9c, 0, 0, 0, 0x01, 0, 0, 0, 0x01]);
    184     }
    185 
    186     #[test]
    187     fn activate_and_save_send_data_cmd() {
    188         let mut m = MockTransport::new();
    189         m.push_response(op::DATA_CMD, &[]);
    190         m.push_response(op::DATA_CMD, &[]);
    191         let mut dev = Scarlett::new(m);
    192 
    193         dev.activate(0x05).unwrap();
    194         dev.save_to_flash().unwrap();
    195 
    196         let m = dev.into_transport();
    197         assert_eq!(m.sent[0], (op::DATA_CMD, 0x05u32.to_le_bytes().to_vec()));
    198         assert_eq!(m.sent[1], (op::DATA_CMD, CONFIG_SAVE.to_le_bytes().to_vec()));
    199     }
    200 
    201     #[test]
    202     fn get_sync_reads_lock_state() {
    203         let mut m = MockTransport::new();
    204         m.push_response(op::GET_SYNC, &[0x01, 0, 0, 0]); // locked (real spike bytes)
    205         let mut dev = Scarlett::new(m);
    206         assert!(dev.get_sync().unwrap());
    207 
    208         let mut m2 = MockTransport::new();
    209         m2.push_response(op::GET_SYNC, &[0, 0, 0, 0]); // unlocked
    210         let mut dev2 = Scarlett::new(m2);
    211         assert!(!dev2.get_sync().unwrap());
    212     }
    213 }