serial-arbiter 0.2.0

Serial Port Arbiter - Manages serial port access and ensures it recovers from failures
Documentation
use std::{io, thread};
use serial_arbiter::*;
use std::time::*;

fn main() -> io::Result<()> {
    // let cfg = serialport::new("/dev/ttyACM0", 115200)
    //     .data_bits(DataBits::Eight)
    //     .parity(Parity::None)
    //     .stop_bits(StopBits::One)
    //     .flow_control(FlowControl::None)
    //     .timeout(Duration::from_millis(0));

    // let port = Arbiter::new(cfg);
    let port = Arbiter::new();

    // {"jsonrpc":"2.0","id":777,"method":"set_timings","params":{"T1":2,"T2":5,"T3":3}}
    // let request = format!("{}\r\n", r#"{"jsonrpc":"2.0","id":777,"method":"set_timings","params":{"T1":2,"T2":5,"T3":3}}"#);

    let rpm = 5.0;
    let pwr = 0.1;
    let mut id = 1;
    let mut angle;
    let mut now = Instant::now() - Duration::from_secs(1);
    let mut print_time = Instant::now();
    let start = Instant::now();

    loop {
        port.close();
        if let Err(err) = port.open("/dev/tty-usb-to-led-1") {
            println!("Cannot connecto: {err}");
            thread::sleep(Duration::from_secs(1));
            continue;
        }

        loop {
            // print!("\n[TX]");
            // let deadline = Instant::now() + Duration::from_millis(10);
            // if let Some(garbage) = port.receive_string(deadline)? {
            //     print!("-[OOB:{}]", garbage.trim_ascii());
            // }

            let elapsed = now.elapsed();
            now = Instant::now();
            if now >= print_time {
                eprintln!("Frequency: {} Hz", (1.0 / elapsed.as_secs_f32()).floor());
                print_time = now + Duration::from_secs(1);
            }

            id += 1;
            angle = start.elapsed().as_secs_f32().rem_euclid(1.0) * rpm;
            let request = make_request(id, angle, pwr);

            let deadline = Instant::now() + Duration::from_millis(100);
            if let Err(err) = port.transmit_str(&request, deadline) {
                println!("Cannot transmitto: {err}");
                thread::sleep(Duration::from_secs(1));
                break;
            }
            // match port.transmit_str(&request, deadline) {
            //     Ok(None) => print!("-[OK]"),
            //     Ok(Some(data)) => print!("-[OOB:{}]-[OK]", String::from_utf8_lossy(&data).trim_ascii()),
            //     Err(err) => print!("-[ERR:{err}]"),
            // }

            let deadline = Instant::now() + Duration::from_millis(1);
            if let Err(err) = port.receive_string(deadline) {
                println!("Cannot recivio: {err}");
                thread::sleep(Duration::from_secs(1));
                break;
            }

            // print!("     [RX]");
            // let response = port.receive_string(deadline)?.unwrap_or("<EMPTY>".to_string());
            // print!("-[{}]", response.trim_ascii());

            // thread::sleep(Duration::from_millis(1));
        }
    }

    // Ok(())
}


fn make_request(id: usize, angle: f32, power: f32) -> String {
    let mut pixels = [0; 12];
    let sprite = get_pixel_index(angle);
    pixels[sprite.i0] = (power * 255.0 * (1.0 - sprite.ratio)).round() as u8;
    pixels[sprite.i1] = (power * 255.0 * sprite.ratio).round() as u8;
    let mut channels = [0; 36];
    for i in 0..pixels.len() {
        channels[3 * i + 1] = pixels[i];
    }
    let request = format!("{}\n", r#"{"jsonrpc":"2.0","id":{id},"method":"set_pixels","params":{channels}}"#);
    let request = request.replace("{id}", &id.to_string());
    let request = request.replace("{channels}", &format!("{channels:?}"));
    // println!("REQUEST: {request}");
    request
}

#[derive(Debug, PartialEq, PartialOrd)]
struct PixelIndex {
    i0: usize,
    i1: usize,
    ratio: f32,
}

fn get_pixel_index(angle: f32) -> PixelIndex {
    let a = 12.0 * angle.rem_euclid(1.0);
    let i0 = a.floor() as usize;
    let i1 = if i0 < 11 { i0 + 1 } else { 0 };
    let ratio = a.rem_euclid(1.0);
    PixelIndex { i0, i1, ratio }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_getting_pixel_index() {
        let p = 1.0 / 12.0;
        assert_eq!(get_pixel_index(p * 0.00), PixelIndex { i0:  0, i1: 1, ratio: 0.00 });
        assert_eq!(get_pixel_index(p * 1.00), PixelIndex { i0:  1, i1: 2, ratio: 0.00 });
        assert_eq!(get_pixel_index(p * 2.00), PixelIndex { i0:  2, i1: 3, ratio: 0.00 });
        assert_eq!(get_pixel_index(p * 3.00), PixelIndex { i0:  3, i1: 4, ratio: 0.00 });
        assert_eq!(get_pixel_index(p * 3.50), PixelIndex { i0:  3, i1: 4, ratio: 0.50000024 });
        assert_eq!(get_pixel_index(p * 3.75), PixelIndex { i0:  3, i1: 4, ratio: 0.75 });
        assert_eq!(get_pixel_index(p * 6.00), PixelIndex { i0:  6, i1: 7, ratio: 0.00 });
        assert_eq!(get_pixel_index(p * 8.11), PixelIndex { i0:  8, i1: 9, ratio: 0.11000061 });
        assert_eq!(get_pixel_index(p * 8.44), PixelIndex { i0:  8, i1: 9, ratio: 0.43999958 });
        assert_eq!(get_pixel_index(p * 8.99), PixelIndex { i0:  8, i1: 9, ratio: 0.9899998 });
        assert_eq!(get_pixel_index(p * 11.0), PixelIndex { i0: 11, i1: 0, ratio: 0.00 });
        assert_eq!(get_pixel_index(p * 11.9), PixelIndex { i0: 11, i1: 0, ratio: 0.8999996 });
        assert_eq!(get_pixel_index(p * 12.0), PixelIndex { i0:  0, i1: 1, ratio: 0.00 });
    }
}