usbrelay-rs 0.1.0

USB relay support
Documentation
use anyhow::{anyhow, Result};
use libftdi1_sys::*;
use std::ffi::CString;
use std::os::raw::c_uchar;

pub struct SainSmartFourChannelRelay {
    context: *mut ftdi_context,
}

impl SainSmartFourChannelRelay {
    pub const N_CHANNELS: u8 = 4;
    pub const MASK: u8 = (1 << Self::N_CHANNELS) - 1;

    pub fn new(ftdi_device_string: &str) -> Result<Self> {
        unsafe {
            let context = ftdi_new();
            if context.is_null() {
                return Err(anyhow!("Failed to cons up new context"));
            }

            let ftdi_device_cstring = CString::new(ftdi_device_string)?.into_raw();
            let rc = ftdi_usb_open_string(context, ftdi_device_cstring);
            drop(CString::from_raw(ftdi_device_cstring));
            if rc != 0 {
                ftdi_free(context);
                return Err(anyhow!("No device found using '{ftdi_device_string}'"));
            }

            if ftdi_set_bitmode(
                context,
                Self::MASK,
                ftdi_mpsse_mode::BITMODE_BITBANG.0 as c_uchar,
            ) != 0
            {
                ftdi_free(context);
                return Err(anyhow!("Failed to set bitmode"));
            }
            if (*context).type_ != ftdi_chip_type::TYPE_R {
                ftdi_free(context);
                return Err(anyhow!("Chip type is not type R as expected"));
            }
            Ok(Self { context })
        }
    }

    pub fn read(&self) -> Result<u8> {
        let mut pins: u8 = 0;
        match unsafe { ftdi_read_pins(self.context, &mut pins) } {
            0 => Ok(pins & Self::MASK),
            _ => Err(anyhow!("Failed to read pins")),
        }
    }

    pub fn set(&self, pins: u8) -> Result<()> {
        let pins = pins & Self::MASK;
        match unsafe { ftdi_write_data(self.context, &pins, 1) } {
            1 => Ok(()),
            _ => Err(anyhow!("Failed to set pins")),
        }
    }
}

impl Drop for SainSmartFourChannelRelay {
    fn drop(&mut self) {
        unsafe {
            ftdi_free(self.context);
        }
    }
}