tloop 0.0.1

Tauri plugin for Arduino integration — flash firmware, stream serial data, detect boards from your desktop app
use tloop_arduino as arduino;
pub mod commands;
mod state;

use tauri::Manager;

pub use state::LoopState;

// Re-exports — a Tauri project only needs `tloop` in its Cargo.toml.
pub use tloop_config::{Board, FirmwareConfig, LoopConfig, SerialConfig};
pub use commands::boards::BoardInfo;
pub use commands::serial::SerialLine;

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum LoopFeature {
    Flash,
    SerialRead,
    SerialPorts,
    ConnectedBoards,
    FileUpload,
}

pub struct Builder {
    pub features: std::collections::HashSet<LoopFeature>,
    pub boards: Vec<tloop_config::Board>,
    pub firmware_sketch: Option<String>,
    pub serial_baudrate: u32,
    pub libraries: Vec<String>,
    pub env_defaults: std::collections::HashMap<String, serde_json::Value>,
}

impl Builder {
    pub fn new() -> Self {
        Self {
            features: std::collections::HashSet::new(),
            boards: vec![],
            firmware_sketch: None,
            serial_baudrate: 9600,
            libraries: vec![],
            env_defaults: std::collections::HashMap::new(),
        }
    }

    pub fn with_flash(mut self) -> Self {
        self.features.insert(LoopFeature::Flash);
        self
    }

    pub fn with_serial_read(mut self) -> Self {
        self.features.insert(LoopFeature::SerialRead);
        self
    }

    pub fn with_serial_ports(mut self) -> Self {
        self.features.insert(LoopFeature::SerialPorts);
        self
    }

    pub fn with_connected_boards(mut self) -> Self {
        self.features.insert(LoopFeature::ConnectedBoards);
        self
    }

    pub fn with_file_upload(mut self) -> Self {
        self.features.insert(LoopFeature::FileUpload);
        self
    }

    pub fn with_boards(mut self, boards: Vec<tloop_config::Board>) -> Self {
        self.boards = boards;
        self
    }

    pub fn with_firmware_sketch(mut self, path: String) -> Self {
        self.firmware_sketch = Some(path);
        self
    }

    pub fn with_libraries(mut self, libs: Vec<String>) -> Self {
        self.libraries = libs;
        self
    }

    pub fn with_serial_baudrate(mut self, baud: u32) -> Self {
        self.serial_baudrate = baud;
        self
    }

    pub fn with_env_defaults(mut self, defaults: std::collections::HashMap<String, serde_json::Value>) -> Self {
        self.env_defaults = defaults;
        self
    }

    pub fn build(self) -> tauri::plugin::TauriPlugin<tauri::Wry> {
        let features = self.features.clone();
        let boards = self.boards.clone();
        let firmware_sketch = self.firmware_sketch.clone();
        let serial_baudrate = self.serial_baudrate;
        let libraries = self.libraries.clone();
        let env_defaults = self.env_defaults.clone();
        tauri::plugin::Builder::new("loop")
            .invoke_handler(tauri::generate_handler![
                commands::env_vars::loop_set_env,
                commands::env_vars::loop_get_env,
                commands::serial::loop_serial_ports,
                commands::serial::loop_serial_start,
                commands::serial::loop_serial_stop,
                commands::boards::loop_connected_boards,
                commands::flash::loop_flash,
            ])
            .setup(move |app, _| {
                let arduino_cli = arduino::extract_arduino_cli(app.path().app_cache_dir()?)?;
                let mut state = LoopState::new(arduino_cli, features.clone(), boards.clone());
                state.firmware_sketch = firmware_sketch.clone();
                state.serial_baudrate = serial_baudrate;
                state.libraries = libraries.clone();
                state.env_defaults = env_defaults.clone();
                app.manage(state);
                Ok(())
            })
            .build()
    }
}

impl Default for Builder {
    fn default() -> Self {
        Self::new()
    }
}

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

    #[test]
    fn builder_tracks_features() {
        let b = Builder::new().with_flash().with_serial_ports();
        assert!(b.features.contains(&LoopFeature::Flash));
        assert!(b.features.contains(&LoopFeature::SerialPorts));
        assert!(!b.features.contains(&LoopFeature::SerialRead));
    }

    #[test]
    fn builder_stores_boards() {
        let board = tloop_config::Board {
            name: "uno".into(),
            fqbn: "arduino:avr:uno".into(),
            url: None,
            upload_speed: None,
        };
        let b = Builder::new().with_boards(vec![board]);
        assert_eq!(b.boards.len(), 1);
        assert_eq!(b.boards[0].name, "uno");
    }

    #[test]
    fn builder_stores_env_defaults() {
        let mut defaults = std::collections::HashMap::new();
        defaults.insert("speed".into(), serde_json::json!(100));
        let b = Builder::new().with_env_defaults(defaults);
        assert_eq!(b.env_defaults.get("speed"), Some(&serde_json::json!(100)));
    }
}