Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
parkpow
GitHub Repository: parkpow/deep-license-plate-recognition
Path: blob/master/gate-controller/src-tauri/src/relay/mod.rs
1080 views
1
2
// ==============================
3
// ========= MODULES ============
4
// ==============================
5
mod ch340;
6
mod hw348;
7
mod cp210x;
8
9
// ==============================
10
// ========== IMPORTS ===========
11
// ==============================
12
use crate::config::{self, ConfiguredRelay, RelayType};
13
use rand::{distributions::Alphanumeric, thread_rng, Rng};
14
use serialport::{self, SerialPort};
15
use std::{
16
collections::HashMap,
17
sync::{Arc, Mutex},
18
};
19
use tauri::{AppHandle, State};
20
21
// ==============================
22
// ====== CONNECTION STATE ======
23
// ==============================
24
25
// This state is for all serial-based connections (CH340, CP210x, etc.)
26
pub struct ConnectionManager(pub Arc<Mutex<HashMap<String, Box<dyn SerialPort + Send>>>>);
27
28
// ==============================
29
// ========= DATA TYPES =========
30
// ==============================
31
32
// Re-exporting for convenience in main.rs
33
pub use hw348::UsbRelayInfo;
34
35
#[derive(serde::Deserialize)]
36
pub struct RelayActionPayload {
37
pub id: String,
38
pub action: String, // "on" or "off"
39
pub channel: Option<u8>,
40
pub toggle: Option<bool>,
41
pub period: Option<u64>, // in milliseconds
42
}
43
44
// ==============================
45
// ======== TOKEN COMMANDS ======
46
// ==============================
47
48
#[tauri::command]
49
pub fn get_webhook_token(app: AppHandle) -> Result<Option<String>, String> {
50
let config = config::load_config(&app)?;
51
Ok(config.webhook_token)
52
}
53
54
#[tauri::command]
55
pub fn regenerate_webhook_token(app: AppHandle) -> Result<String, String> {
56
let mut config = config::load_config(&app)?;
57
let new_token: String = thread_rng()
58
.sample_iter(&Alphanumeric)
59
.take(32)
60
.map(char::from)
61
.collect();
62
63
config.webhook_token = Some(new_token.clone());
64
config::save_config(&app, &config)?;
65
Ok(new_token)
66
}
67
68
// ==============================
69
// ==== CONFIGURATION COMMANDS ===
70
// ==============================
71
72
#[tauri::command]
73
pub fn get_configured_relays(app: AppHandle) -> Result<Vec<ConfiguredRelay>, String> {
74
let config = config::load_config(&app)?;
75
Ok(config.relays)
76
}
77
78
#[tauri::command]
79
pub fn add_ch340_relay(app: AppHandle, port: String, channels: u8) -> Result<(), String> {
80
let mut config = config::load_config(&app)?;
81
if config.relays.iter().any(|r| r.id == port) {
82
return Err(format!("Relay with ID '{}' already exists.", port));
83
}
84
85
let new_relay = ConfiguredRelay {
86
id: port,
87
relay_type: RelayType::CH340,
88
channels: Some(channels),
89
};
90
91
config.relays.push(new_relay);
92
config::save_config(&app, &config)
93
}
94
95
#[tauri::command]
96
pub fn add_hw348_relay(app: AppHandle, serial_number: String, channels: u8) -> Result<(), String> {
97
let mut config = config::load_config(&app)?;
98
if config.relays.iter().any(|r| r.id == serial_number) {
99
return Err(format!("Relay with ID '{}' already exists.", serial_number));
100
}
101
102
let new_relay = ConfiguredRelay {
103
id: serial_number,
104
relay_type: RelayType::HW348,
105
channels: Some(channels),
106
};
107
108
config.relays.push(new_relay);
109
config::save_config(&app, &config)
110
}
111
112
#[tauri::command]
113
pub fn add_cp210x_relay(app: AppHandle, port: String, channels: u8) -> Result<(), String> {
114
let mut config = config::load_config(&app)?;
115
if config.relays.iter().any(|r| r.id == port) {
116
return Err(format!("Relay with ID '{}' already exists.", port));
117
}
118
119
let new_relay = ConfiguredRelay {
120
id: port,
121
relay_type: RelayType::CP210x,
122
channels: Some(channels),
123
};
124
125
config.relays.push(new_relay);
126
config::save_config(&app, &config)
127
}
128
129
#[tauri::command]
130
pub fn remove_relay(app: AppHandle, id: String) -> Result<(), String> {
131
let mut config = config::load_config(&app)?;
132
config.relays.retain(|r| r.id != id);
133
config::save_config(&app, &config)
134
}
135
136
// ==============================
137
// ====== RELAY ACTION LOGIC ====
138
// ==============================
139
140
#[tauri::command]
141
pub fn trigger_relay_action(
142
app: AppHandle,
143
connections: State<'_, ConnectionManager>,
144
payload: RelayActionPayload,
145
) -> Result<(), String> {
146
let toggle = payload.toggle.unwrap_or(false);
147
let period = payload.period.unwrap_or(0);
148
149
if toggle && period == 0 {
150
return Err("Period must be greater than 0 for toggle action.".to_string());
151
}
152
153
let connections_arc = connections.0.clone();
154
155
// --- Execute Initial Action ---
156
execute_relay_action(&app, &connections_arc, &payload.id, &payload.action, payload.channel)?;
157
158
// --- Handle Toggle if Requested ---
159
if toggle {
160
let app_handle = app.clone();
161
let id_clone = payload.id.clone();
162
let inverted_action = if payload.action == "on" { "off" } else { "on" }.to_string();
163
let channel_clone = payload.channel;
164
165
std::thread::spawn(move || {
166
std::thread::sleep(std::time::Duration::from_millis(period));
167
let _ = execute_relay_action(&app_handle, &connections_arc, &id_clone, &inverted_action, channel_clone);
168
});
169
}
170
171
Ok(())
172
}
173
174
fn execute_relay_action(
175
app: &AppHandle,
176
connections: &Arc<Mutex<HashMap<String, Box<dyn SerialPort + Send>>>>,
177
id: &str,
178
action: &str,
179
channel: Option<u8>,
180
) -> Result<(), String> {
181
let config = config::load_config(app)?;
182
let relay = config
183
.relays
184
.iter()
185
.find(|r| r.id == id)
186
.ok_or_else(|| format!("Relay with ID '{}' not found in config.", id))?;
187
188
match relay.relay_type {
189
RelayType::HW348 => {
190
let ch = channel.ok_or_else(|| "Channel is required for HW348 relays.".to_string())?;
191
let max_channels = relay.channels.ok_or_else(|| "Channel configuration is missing for HW348 relay.".to_string())?;
192
hw348::trigger_hw348_action(&relay.id, action, ch, max_channels)
193
}
194
RelayType::CH340 => {
195
let ch = channel.ok_or_else(|| "Channel is required for CH340 relays.".to_string())?;
196
if let Some(max_channels) = relay.channels {
197
if ch == 0 || ch > max_channels {
198
return Err(format!(
199
"Invalid channel '{}' for relay '{}'. Must be between 1 and {}.",
200
ch, id, max_channels
201
));
202
}
203
}
204
ch340::trigger_ch340_action(connections, &relay.id, action, ch)
205
}
206
RelayType::CP210x => {
207
let ch = channel.ok_or_else(|| "Channel is required for CP210x relays.".to_string())?;
208
if let Some(max_channels) = relay.channels {
209
if ch == 0 || ch > max_channels {
210
return Err(format!(
211
"Invalid channel '{}' for relay '{}'. Must be between 1 and {}.",
212
ch, id, max_channels
213
));
214
}
215
}
216
cp210x::trigger_cp210x_action(connections, &relay.id, action, ch)
217
}
218
}
219
}
220
// ==============================
221
// ===== HELPER / DISCOVERY =====
222
// ==============================
223
224
#[tauri::command]
225
pub fn list_serial_ports() -> Result<Vec<String>, String> {
226
let ports = serialport::available_ports().map_err(|e| e.to_string())?;
227
Ok(ports.into_iter().map(|p| p.port_name).collect())
228
}
229
230
#[tauri::command]
231
pub fn list_hw348_relays() -> Result<Vec<UsbRelayInfo>, String> {
232
hw348::list_hw348_relays()
233
}
234
235