mlua-periphery 1.2.4

A Rust-native implementation of lua-periphery for mlua.
#[cfg(test)]
pub mod integration_tests;

mod close;
mod configure;
mod constructor;
mod poll;
mod read;
mod read_event;
mod write;

use gpio_cdev::{Chip, EventType, Line, LineEventHandle};
use mlua::Error::RuntimeError;
use mlua::{Error, Lua, MultiValue, Table, UserData, UserDataFields, UserDataMethods};
use std::os::raw;
use std::sync::{Arc, Mutex};
use std::time::{SystemTime, UNIX_EPOCH};

#[derive(Clone)]
struct Gpio {
    // Immutable
    line_num: u32,

    // Mutable
    bias: Arc<Mutex<String>>,
    chip: Arc<Mutex<Chip>>,
    direction: Arc<Mutex<String>>,
    drive: Arc<Mutex<String>>,
    edge: Arc<Mutex<String>>,
    inverted: Arc<Mutex<bool>>,
    label: Arc<Mutex<String>>,
    line: Arc<Mutex<Line>>,
    line_events: Arc<Mutex<Option<LineEventHandle>>>,
}

impl UserData for Gpio {
    fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
        methods.add_method("close", close::handle);
        methods.add_method("poll", poll::handle);
        methods.add_method("read", read::handle);
        methods.add_method(
            "read_event",
            |lua: &Lua, gpio: &Gpio, args: MultiValue| match read_event::handle(lua, gpio, args) {
                Err(e) => Err(e),
                Ok(line_event) => {
                    let table = lua.create_table()?;
                    match line_event.event_type() {
                        EventType::RisingEdge => table.raw_set("edge", "rising")?,
                        EventType::FallingEdge => table.raw_set("edge", "falling")?,
                    };
                    let now = SystemTime::now()
                        .duration_since(UNIX_EPOCH)
                        .map_err(|err| RuntimeError(err.to_string()))?;
                    table.raw_set("timestamp", mlua::Value::Number(now.as_nanos() as f64 / 1e9_f64))?;
                    Ok(table)
                }
            },
        );
        methods.add_method("write", write::handle);
    }
    fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
        // Getters
        fields.add_field_method_get("bias", |_, gpio| -> Result<String, mlua::Error> {
            Ok(gpio.bias.lock().map_err(|err| RuntimeError(err.to_string()))?.clone())
        });
        fields.add_field_method_get("chip_fd", |_, _gpio| -> Result<raw::c_int, mlua::Error> {
            Err(RuntimeError("NIY".to_string())) // TODO
        });
        fields.add_field_method_get("chip_label", |_, gpio| -> Result<String, mlua::Error> {
            let locked_chip = gpio.chip.lock().map_err(|err| RuntimeError(err.to_string()))?;
            Ok(locked_chip.label().to_string())
        });
        fields.add_field_method_get("chip_name", |_, gpio| -> Result<String, mlua::Error> {
            let locked_chip = gpio.chip.lock().map_err(|err| RuntimeError(err.to_string()))?;
            Ok(locked_chip.name().to_string())
        });
        fields.add_field_method_get("direction", |_, gpio| -> Result<String, mlua::Error> {
            Ok(gpio
                .direction
                .lock()
                .map_err(|err| RuntimeError(err.to_string()))?
                .clone())
        });
        fields.add_field_method_get("drive", |_, gpio| -> Result<String, mlua::Error> {
            Ok(gpio.drive.lock().map_err(|err| RuntimeError(err.to_string()))?.clone())
        });
        fields.add_field_method_get("edge", |_, gpio| -> Result<String, mlua::Error> {
            Ok(gpio.edge.lock().map_err(|err| RuntimeError(err.to_string()))?.clone())
        });
        fields.add_field_method_get("fd", |_, _gpio| -> Result<raw::c_int, mlua::Error> {
            Err(RuntimeError("NIY".to_string())) // TODO
        });
        fields.add_field_method_get("inverted", |_, gpio| -> Result<bool, mlua::Error> {
            Ok(*gpio.inverted.lock().map_err(|err| RuntimeError(err.to_string()))?)
        });
        fields.add_field_method_get("label", |_, gpio| -> Result<String, mlua::Error> {
            Ok(gpio.label.lock().map_err(|err| RuntimeError(err.to_string()))?.clone())
        });
        fields.add_field_method_get("line", |_, gpio| -> Result<u32, mlua::Error> { Ok(gpio.line_num) });
        fields.add_field_method_get("name", |_, _gpio| -> Result<String, mlua::Error> {
            Err(RuntimeError("NIY".to_string())) // TODO
        });

        // Setters
        fields.add_field_method_set("bias", |_, gpio, value: String| {
            let mut locked_bias = gpio.bias.lock().map_err(|err| RuntimeError(err.to_string()))?;
            *locked_bias = value;
            Ok(())
        });
        fields.add_field_method_set("direction", |_, gpio, value: String| {
            let mut locked_direction = gpio.direction.lock().map_err(|err| RuntimeError(err.to_string()))?;
            *locked_direction = value;
            Ok(())
        });
        fields.add_field_method_set("drive", |_, gpio, value: String| {
            let mut locked_drive = gpio.drive.lock().map_err(|err| RuntimeError(err.to_string()))?;
            *locked_drive = value;
            Ok(())
        });
        fields.add_field_method_set("edge", |_, gpio, value: String| {
            let mut locked_edge = gpio.edge.lock().map_err(|err| RuntimeError(err.to_string()))?;
            *locked_edge = value;
            Ok(())
        });
        fields.add_field_method_set("inverted", |_, gpio, value: bool| {
            let mut locked_inverted = gpio.inverted.lock().map_err(|err| RuntimeError(err.to_string()))?;
            *locked_inverted = value;
            Ok(())
        });
    }
}

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.GPIO", 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 GPIO = require('periphery.GPIO')").exec()?;
        Ok(())
    }
}