cu-msp-sink 0.10.0

This is the sink side of an MSP (Multiwii Serial Protocol) for Copper.
Documentation
use bincode::de::Decoder;
use bincode::enc::Encoder;
use bincode::error::{DecodeError, EncodeError};
use bincode::{Decode, Encode};
use cu29::prelude::*;
use cu_msp_lib::structs::MspRequest;
use cu_msp_lib::MspPacket;
use serde::{Deserialize, Serialize};
#[cfg(unix)]
use serialport::SerialPort;
#[cfg(unix)]
use serialport::TTYPort;
#[cfg(unix)]
use std::io::Write;

use smallvec::SmallVec;

const MAX_MSG_SIZE: usize = 8;

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct MspRequestBatch(pub SmallVec<[MspRequest; MAX_MSG_SIZE]>);

impl MspRequestBatch {
    pub fn new() -> Self {
        Self(SmallVec::new())
    }

    pub fn push(&mut self, req: MspRequest) {
        let MspRequestBatch(ref mut vec) = self;
        vec.push(req);
    }
}

impl Encode for MspRequestBatch {
    fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
        Encode::encode(&self.0.as_slice(), encoder)
    }
}

impl Decode<()> for MspRequestBatch {
    fn decode<D: Decoder<Context = ()>>(decoder: &mut D) -> Result<Self, DecodeError> {
        let v = <Vec<MspRequest> as Decode<()>>::decode(decoder)?;
        Ok(Self(v.into()))
    }
}

pub struct MSPSink {
    #[cfg(unix)]
    serial: TTYPort,
}

impl Freezable for MSPSink {}

impl CuSinkTask for MSPSink {
    type Input<'m> = input_msg!(MspRequestBatch);

    fn new(config: Option<&ComponentConfig>) -> CuResult<Self>
    where
        Self: Sized,
    {
        if config.is_none() {
            return Err("No config provided".into());
        }
        #[cfg(unix)]
        let port: String = config
            .and_then(|config| config.get::<String>("device"))
            .unwrap_or("/dev/ttyUSB0".to_string());
        #[cfg(unix)]
        let baudrate = config
            .and_then(|config| config.get::<u32>("baudrate"))
            .unwrap_or(115200);

        #[cfg(unix)]
        let builder = serialport::new(port, baudrate);

        #[cfg(unix)]
        let mut serial = TTYPort::open(&builder).unwrap();
        #[cfg(unix)]
        serial.set_exclusive(false).unwrap();
        #[cfg(unix)]
        serial
            .set_timeout(std::time::Duration::from_millis(10))
            .unwrap();

        Ok(Self {
            #[cfg(unix)]
            serial,
        })
    }

    fn process(&mut self, _clock: &RobotClock, input: &Self::Input<'_>) -> CuResult<()> {
        if let Some(batch) = input.payload() {
            let MspRequestBatch(ref batch) = batch;
            for req in batch {
                debug!("Sending request {}", req);
                let msp_packet: MspPacket = req.into();
                let size = msp_packet.packet_size_bytes();
                let mut buffer = SmallVec::<[u8; 256]>::new();
                buffer.resize(size, 0);
                msp_packet.serialize(&mut buffer).map_err(|e| {
                    debug!("Error Serializing packet {}", e.to_string());
                    CuError::new_with_cause("failed to serialize msp packet", e)
                })?;
                #[cfg(unix)]
                self.serial.write_all(buffer.as_slice()).map_err(|e| {
                    debug!("Error writing to serial port {}", e.to_string());
                    CuError::new_with_cause("failed to write to serial port", e)
                })?;
            }
        }
        Ok(())
    }
}