Path: blob/master/gate-controller/src-tauri/src/relay/mod.rs
1080 views
1// ==============================2// ========= MODULES ============3// ==============================4mod ch340;5mod hw348;6mod cp210x;78// ==============================9// ========== IMPORTS ===========10// ==============================11use crate::config::{self, ConfiguredRelay, RelayType};12use rand::{distributions::Alphanumeric, thread_rng, Rng};13use serialport::{self, SerialPort};14use std::{15collections::HashMap,16sync::{Arc, Mutex},17};18use tauri::{AppHandle, State};1920// ==============================21// ====== CONNECTION STATE ======22// ==============================2324// This state is for all serial-based connections (CH340, CP210x, etc.)25pub struct ConnectionManager(pub Arc<Mutex<HashMap<String, Box<dyn SerialPort + Send>>>>);2627// ==============================28// ========= DATA TYPES =========29// ==============================3031// Re-exporting for convenience in main.rs32pub use hw348::UsbRelayInfo;3334#[derive(serde::Deserialize)]35pub struct RelayActionPayload {36pub id: String,37pub action: String, // "on" or "off"38pub channel: Option<u8>,39pub toggle: Option<bool>,40pub period: Option<u64>, // in milliseconds41}4243// ==============================44// ======== TOKEN COMMANDS ======45// ==============================4647#[tauri::command]48pub fn get_webhook_token(app: AppHandle) -> Result<Option<String>, String> {49let config = config::load_config(&app)?;50Ok(config.webhook_token)51}5253#[tauri::command]54pub fn regenerate_webhook_token(app: AppHandle) -> Result<String, String> {55let mut config = config::load_config(&app)?;56let new_token: String = thread_rng()57.sample_iter(&Alphanumeric)58.take(32)59.map(char::from)60.collect();6162config.webhook_token = Some(new_token.clone());63config::save_config(&app, &config)?;64Ok(new_token)65}6667// ==============================68// ==== CONFIGURATION COMMANDS ===69// ==============================7071#[tauri::command]72pub fn get_configured_relays(app: AppHandle) -> Result<Vec<ConfiguredRelay>, String> {73let config = config::load_config(&app)?;74Ok(config.relays)75}7677#[tauri::command]78pub fn add_ch340_relay(app: AppHandle, port: String, channels: u8) -> Result<(), String> {79let mut config = config::load_config(&app)?;80if config.relays.iter().any(|r| r.id == port) {81return Err(format!("Relay with ID '{}' already exists.", port));82}8384let new_relay = ConfiguredRelay {85id: port,86relay_type: RelayType::CH340,87channels: Some(channels),88};8990config.relays.push(new_relay);91config::save_config(&app, &config)92}9394#[tauri::command]95pub fn add_hw348_relay(app: AppHandle, serial_number: String, channels: u8) -> Result<(), String> {96let mut config = config::load_config(&app)?;97if config.relays.iter().any(|r| r.id == serial_number) {98return Err(format!("Relay with ID '{}' already exists.", serial_number));99}100101let new_relay = ConfiguredRelay {102id: serial_number,103relay_type: RelayType::HW348,104channels: Some(channels),105};106107config.relays.push(new_relay);108config::save_config(&app, &config)109}110111#[tauri::command]112pub fn add_cp210x_relay(app: AppHandle, port: String, channels: u8) -> Result<(), String> {113let mut config = config::load_config(&app)?;114if config.relays.iter().any(|r| r.id == port) {115return Err(format!("Relay with ID '{}' already exists.", port));116}117118let new_relay = ConfiguredRelay {119id: port,120relay_type: RelayType::CP210x,121channels: Some(channels),122};123124config.relays.push(new_relay);125config::save_config(&app, &config)126}127128#[tauri::command]129pub fn remove_relay(app: AppHandle, id: String) -> Result<(), String> {130let mut config = config::load_config(&app)?;131config.relays.retain(|r| r.id != id);132config::save_config(&app, &config)133}134135// ==============================136// ====== RELAY ACTION LOGIC ====137// ==============================138139#[tauri::command]140pub fn trigger_relay_action(141app: AppHandle,142connections: State<'_, ConnectionManager>,143payload: RelayActionPayload,144) -> Result<(), String> {145let toggle = payload.toggle.unwrap_or(false);146let period = payload.period.unwrap_or(0);147148if toggle && period == 0 {149return Err("Period must be greater than 0 for toggle action.".to_string());150}151152let connections_arc = connections.0.clone();153154// --- Execute Initial Action ---155execute_relay_action(&app, &connections_arc, &payload.id, &payload.action, payload.channel)?;156157// --- Handle Toggle if Requested ---158if toggle {159let app_handle = app.clone();160let id_clone = payload.id.clone();161let inverted_action = if payload.action == "on" { "off" } else { "on" }.to_string();162let channel_clone = payload.channel;163164std::thread::spawn(move || {165std::thread::sleep(std::time::Duration::from_millis(period));166let _ = execute_relay_action(&app_handle, &connections_arc, &id_clone, &inverted_action, channel_clone);167});168}169170Ok(())171}172173fn execute_relay_action(174app: &AppHandle,175connections: &Arc<Mutex<HashMap<String, Box<dyn SerialPort + Send>>>>,176id: &str,177action: &str,178channel: Option<u8>,179) -> Result<(), String> {180let config = config::load_config(app)?;181let relay = config182.relays183.iter()184.find(|r| r.id == id)185.ok_or_else(|| format!("Relay with ID '{}' not found in config.", id))?;186187match relay.relay_type {188RelayType::HW348 => {189let ch = channel.ok_or_else(|| "Channel is required for HW348 relays.".to_string())?;190let max_channels = relay.channels.ok_or_else(|| "Channel configuration is missing for HW348 relay.".to_string())?;191hw348::trigger_hw348_action(&relay.id, action, ch, max_channels)192}193RelayType::CH340 => {194let ch = channel.ok_or_else(|| "Channel is required for CH340 relays.".to_string())?;195if let Some(max_channels) = relay.channels {196if ch == 0 || ch > max_channels {197return Err(format!(198"Invalid channel '{}' for relay '{}'. Must be between 1 and {}.",199ch, id, max_channels200));201}202}203ch340::trigger_ch340_action(connections, &relay.id, action, ch)204}205RelayType::CP210x => {206let ch = channel.ok_or_else(|| "Channel is required for CP210x relays.".to_string())?;207if let Some(max_channels) = relay.channels {208if ch == 0 || ch > max_channels {209return Err(format!(210"Invalid channel '{}' for relay '{}'. Must be between 1 and {}.",211ch, id, max_channels212));213}214}215cp210x::trigger_cp210x_action(connections, &relay.id, action, ch)216}217}218}219// ==============================220// ===== HELPER / DISCOVERY =====221// ==============================222223#[tauri::command]224pub fn list_serial_ports() -> Result<Vec<String>, String> {225let ports = serialport::available_ports().map_err(|e| e.to_string())?;226Ok(ports.into_iter().map(|p| p.port_name).collect())227}228229#[tauri::command]230pub fn list_hw348_relays() -> Result<Vec<UsbRelayInfo>, String> {231hw348::list_hw348_relays()232}233234235