mlua-periphery 1.2.4

A Rust-native implementation of lua-periphery for mlua.
mod close;
mod constructor;
mod flush;
mod input_waiting;
mod output_waiting;
mod poll;
mod read;
mod write;

#[cfg(test)]
pub mod integration_tests;

use mlua::Error::RuntimeError;
use mlua::{Error, Lua, Number, Table, UserData, UserDataFields, UserDataMethods};
use serial2::{CharSize, FlowControl, IntoSettings, Parity, SerialPort, Settings, StopBits};
use std::sync::{Arc, Mutex};

#[derive(Copy, Clone)]
#[allow(dead_code)]
enum ErrorCode {
    Arg = -1,
    Open = -2,
    Query = -3,
    Configure = -4,
    Io = -5,
    Close = -6,
}

#[derive(Clone)]
struct Config {
    baudrate: u32,
    char_size: CharSize,
    flow_control: FlowControl,
    parity: Parity,
    stopbits: StopBits,
}

#[derive(Clone)]
struct Serial {
    #[allow(dead_code)]
    device: String,
    port: Arc<Mutex<SerialPort>>,
}

impl IntoSettings for Config {
    fn apply_to_settings(self, settings: &mut Settings) -> std::io::Result<()> {
        let _ = settings.set_baud_rate(self.baudrate);
        settings.set_parity(self.parity);
        settings.set_char_size(self.char_size);
        settings.set_flow_control(self.flow_control);
        settings.set_stop_bits(self.stopbits);
        Ok(())
    }
}

impl UserData for Serial {
    fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
        methods.add_method("close", close::handle);
        methods.add_method("flush", flush::handle);
        methods.add_method("input_waiting", input_waiting::handle);
        methods.add_method("output_waiting", output_waiting::handle);
        methods.add_method("poll", poll::handle);
        methods.add_method("read", read::handle);
        methods.add_method("write", write::handle);
    }
    fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
        fields.add_field_method_get("baudrate", |_, serial| {
            let baud_rate = {
                let locked_port = serial.port.lock().map_err(|err| RuntimeError(err.to_string()))?;
                locked_port.get_configuration()?.get_baud_rate()?
            };
            Ok(baud_rate)
        });
        fields.add_field_method_get("databits", |_, serial| {
            let char_size = {
                let locked_port = serial.port.lock().map_err(|err| RuntimeError(err.to_string()))?;
                locked_port.get_configuration()?.get_char_size()?
            };
            Ok(match char_size {
                CharSize::Bits5 => 5,
                CharSize::Bits6 => 6,
                CharSize::Bits7 => 7,
                CharSize::Bits8 => 8,
            })
        });
        #[cfg(target_family = "unix")]
        fields.add_field_method_get("fd", |_, serial| {
            use std::os::fd::AsRawFd;
            let locked_port = serial.port.lock().map_err(|err| RuntimeError(err.to_string()))?;
            Ok(locked_port.as_raw_fd())
        });
        #[cfg(target_family = "windows")]
        fields.add_field_method_get("fd", |_, _serial| {
            Err::<u64, mlua::Error>(RuntimeError("Unavailable on this platform".to_string()))
        });
        fields.add_field_method_get("parity", |_, serial| {
            let parity = {
                let locked_port = serial.port.lock().map_err(|err| RuntimeError(err.to_string()))?;
                locked_port.get_configuration()?.get_parity()?
            };
            Ok(match parity {
                Parity::Even => "even",
                Parity::None => "none",
                Parity::Odd => "odd",
            })
        });
        fields.add_field_method_get("rtscts", |_, serial| {
            let flow_control = {
                let locked_port = serial.port.lock().map_err(|err| RuntimeError(err.to_string()))?;
                locked_port.get_configuration()?.get_flow_control()?
            };
            Ok(flow_control == FlowControl::RtsCts)
        });
        fields.add_field_method_get("stopbits", |_, serial| {
            let stop_bits = {
                let locked_port = serial.port.lock().map_err(|err| RuntimeError(err.to_string()))?;
                locked_port.get_configuration()?.get_stop_bits()?
            };
            Ok(match stop_bits {
                StopBits::One => Number::from(1),
                StopBits::Two => Number::from(2),
            })
        });
        fields.add_field_method_get("xonxoff", |_, serial| {
            let flow_control = {
                let locked_port = serial.port.lock().map_err(|err| RuntimeError(err.to_string()))?;
                locked_port.get_configuration()?.get_flow_control()?
            };
            Ok(flow_control == FlowControl::XonXoff)
        });

        fields.add_field_method_set("vmin", |_, _serial, _value: Number| {
            Err(RuntimeError("NIY".to_string())) // TODO
        });
        fields.add_field_method_set("vtime", |_, _serial, _value: Number| {
            Err(RuntimeError("NIY".to_string())) // TODO
        });
    }
}

pub fn preload(lua: &Lua) -> Result<(), Error> {
    // Configure module table
    let module = lua.create_table()?;

    // Configure module metatable
    let metatable = lua.create_table()?;
    metatable.raw_set("__call", lua.create_function(constructor::handle)?)?;
    module.set_metatable(Some(metatable))?;

    // Preload module
    let globals = lua.globals();
    let package: Table = globals.get("package")?;
    let loaded: Table = package.get("loaded")?;
    loaded.set("periphery.Serial", module)?;

    Ok(())
}

#[cfg(test)]
mod tests {
    use mlua::Lua;
    use std::error::Error;

    #[test]
    fn preload() -> Result<(), Box<dyn Error>> {
        let lua = Lua::new();
        super::preload(&lua)?;
        lua.load("local Serial = require('periphery.Serial')").exec()?;
        Ok(())
    }

    // #[test]
    // fn constructor_for_non_existent_device() -> Result<(), Box<dyn Error>> {
    //     let lua = Lua::new();
    //     super::preload(&lua)?;
    //     let result = lua
    //         .load(
    //             r#"
    //             local I2C = require('periphery.I2C')
    //             local i2c = I2C('Foo-9999')
    //         "#,
    //         )
    //         .exec();
    //     assert!(result.is_err());
    //     Ok(())
    // }
}