1use serde_json::Value;
8use std::collections::HashMap;
9use std::path::{Path, PathBuf};
10use std::sync::{Arc, Mutex};
11use tokio::task::JoinHandle;
12
13use super::generic::serial::SerialDevice;
14use super::generic::tcp::TcpDevice;
15use super::printer::sato::{SatoPrinter, SatoWs4Printer};
16use super::rfid::r700::R700;
17use super::rfid::x714::X714;
18
19pub type EventHandler = dyn FnMut(&str, &str, Option<Value>) + Send + 'static;
20pub type SharedEventHandler = Arc<Mutex<Box<EventHandler>>>;
21
22static CONFIG_EXAMPLES: &[(&str, fn() -> HashMap<String, Value>)] = &[
23 ("X714_XPAD", || {
24 super::rfid::x714::config_example::x714_default_map()
25 }),
26 ("X714_SERIAL", || {
27 super::rfid::x714::config_example::x714_map()
28 }),
29 ("X714_TCP", || {
30 super::rfid::x714::config_example::x714_tcp_map()
31 }),
32 ("X714_BLE", || {
33 super::rfid::x714::config_example::x714_ble_map()
34 }),
35 ("X714_ALL", || {
36 super::rfid::x714::config_example::x714_all_map()
37 }),
38 ("R700_IOT", || {
39 super::rfid::r700::config_example::r700_iot_map()
40 }),
41 ("R700_IOT_DICT", || {
42 super::rfid::r700::config_example::r700_iot_dict_map()
43 }),
44 ("R700_IOT_GPI", || {
45 super::rfid::r700::config_example::r700_iot_gpi_map()
46 }),
47 ("R700_IOT_FULL", || {
48 super::rfid::r700::config_example::r700_iot_full_map()
49 }),
50 ("R700_PROTECTED_INVENTORY", || {
51 super::rfid::r700::config_example::r700_protected_inventory_map()
52 }),
53 ("SERIAL", || {
54 super::generic::serial::config_example::serial_default_map()
55 }),
56 ("SERIAL_CUSTOM", || {
57 super::generic::serial::config_example::serial_custom_map()
58 }),
59 ("TCP", || {
60 super::generic::tcp::config_example::tcp_default_map()
61 }),
62 ("TCP_CUSTOM", || {
63 super::generic::tcp::config_example::tcp_custom_map()
64 }),
65 ("SATO", || {
66 super::printer::sato::config_example::sato_default_map()
67 }),
68 ("SATO_WS4", || {
69 super::printer::sato::config_example::sato_ws4_map()
70 }),
71];
72
73pub enum Device {
74 X714(X714),
75 R700(R700),
76 Serial(SerialDevice),
77 Tcp(TcpDevice),
78 Sato(SatoPrinter),
79 SatoWs4(SatoWs4Printer),
80}
81
82impl Clone for Device {
83 fn clone(&self) -> Self {
84 match self {
85 Self::X714(d) => Self::X714(d.clone()),
86 Self::R700(d) => Self::R700(d.clone()),
87 Self::Serial(d) => Self::Serial(d.clone()),
88 Self::Tcp(d) => Self::Tcp(d.clone()),
89 Self::Sato(d) => Self::Sato(d.clone()),
90 Self::SatoWs4(d) => Self::SatoWs4(d.clone()),
91 }
92 }
93}
94
95impl Device {
96 pub fn name(&self) -> &str {
97 match self {
98 Self::X714(d) => &d.config.name,
99 Self::R700(d) => &d.config.name,
100 Self::Serial(d) => &d.config.name,
101 Self::Tcp(d) => &d.config.name,
102 Self::Sato(d) => &d.config.name,
103 Self::SatoWs4(d) => &d.config.name,
104 }
105 }
106
107 pub fn device_type(&self) -> &'static str {
108 match self {
109 Self::X714(_) => "X714",
110 Self::R700(_) => "R700_IOT",
111 Self::Serial(_) => "SERIAL",
112 Self::Tcp(_) => "TCP",
113 Self::Sato(_) => "SATO",
114 Self::SatoWs4(_) => "SATO_WS4",
115 }
116 }
117
118 pub fn device_class(&self) -> &'static str {
119 match self {
120 Self::X714(_) => "X714",
121 Self::R700(_) => "R700",
122 Self::Serial(_) => "SerialDevice",
123 Self::Tcp(_) => "TcpDevice",
124 Self::Sato(_) => "SatoPrinter",
125 Self::SatoWs4(_) => "SatoWs4Printer",
126 }
127 }
128
129 pub fn is_connected(&self) -> bool {
130 match self {
131 Self::X714(d) => d.is_connected(),
132 Self::R700(d) => d.is_connected(),
133 Self::Serial(d) => d.is_connected(),
134 Self::Tcp(d) => d.is_connected(),
135 Self::Sato(d) => d.is_connected(),
136 Self::SatoWs4(d) => d.is_connected(),
137 }
138 }
139
140 pub fn is_reading(&self) -> bool {
141 match self {
142 Self::X714(d) => d.is_reading(),
143 Self::R700(d) => d.is_reading(),
144 Self::Serial(_) => false,
145 Self::Tcp(_) => false,
146 Self::Sato(_) => false,
147 Self::SatoWs4(_) => false,
148 }
149 }
150
151 pub fn is_gpi_trigger_on(&self) -> bool {
152 match self {
153 Self::X714(d) => d.config.gpi_start,
154 Self::R700(d) => d.config.gpi_start,
155 Self::Serial(_) | Self::Tcp(_) | Self::Sato(_) | Self::SatoWs4(_) => false,
156 }
157 }
158
159 pub fn serial_number(&self) -> Option<String> {
160 match self {
161 Self::X714(d) => d.serial_number(),
162 Self::R700(d) => d.serial_number(),
163 Self::Serial(_) => None,
164 Self::Tcp(_) => None,
165 Self::Sato(_) => None,
166 Self::SatoWs4(_) => None,
167 }
168 }
169
170 pub fn can_print(&self) -> bool {
171 match self {
172 Self::Sato(d) => d.can_print(),
173 Self::SatoWs4(d) => d.can_print(),
174 _ => false,
175 }
176 }
177
178 pub fn pending_print_jobs(&self) -> usize {
179 match self {
180 Self::Sato(d) => d.pending_print_jobs(),
181 Self::SatoWs4(d) => d.pending_print_jobs(),
182 _ => 0,
183 }
184 }
185
186 pub fn to_map(&self) -> HashMap<String, Value> {
187 match self {
188 Self::X714(d) => d.to_map(),
189 Self::R700(d) => d.to_map(),
190 Self::Serial(d) => d.to_map(),
191 Self::Tcp(d) => d.to_map(),
192 Self::Sato(d) => d.to_map(),
193 Self::SatoWs4(d) => d.to_map(),
194 }
195 }
196
197 pub fn connect_instruction(&self) -> String {
198 match self {
199 Self::X714(d) => d.connect_instruction(),
200 Self::R700(d) => d.connect_instruction(),
201 Self::Serial(d) => d.connect_instruction(),
202 Self::Tcp(d) => d.connect_instruction(),
203 Self::Sato(d) => d.connect_instruction(),
204 Self::SatoWs4(d) => d.connect_instruction(),
205 }
206 }
207
208 pub fn set_event_handler(&mut self, handler: SharedEventHandler) {
209 match self {
210 Self::X714(d) => d.set_event_handler(handler),
211 Self::R700(d) => d.set_event_handler(handler),
212 Self::Serial(d) => d.set_event_handler(handler),
213 Self::Tcp(d) => d.set_event_handler(handler),
214 Self::Sato(d) => d.set_event_handler(handler),
215 Self::SatoWs4(d) => d.set_event_handler(handler),
216 }
217 }
218
219 pub async fn connect(&self) {
220 match self {
221 Self::X714(d) => d.connect().await,
222 Self::R700(d) => d.connect().await,
223 Self::Serial(d) => d.connect().await,
224 Self::Tcp(d) => d.connect().await,
225 Self::Sato(d) => d.connect().await,
226 Self::SatoWs4(d) => d.0.connect().await,
227 }
228 }
229
230 pub async fn close(&self) {
231 match self {
232 Self::X714(d) => d.close().await,
233 Self::R700(d) => d.close().await,
234 Self::Serial(d) => d.close().await,
235 Self::Tcp(d) => d.close().await,
236 Self::Sato(d) => d.close().await,
237 Self::SatoWs4(d) => d.0.close().await,
238 }
239 }
240
241 pub async fn start_inventory(&self) -> Result<(), String> {
242 match self {
243 Self::X714(d) => d.start_inventory().await,
244 Self::R700(d) => d.start_inventory().await,
245 _ => Err("device type does not support this operation".to_string()),
246 }
247 }
248
249 pub async fn stop_inventory(&self) -> Result<(), String> {
250 match self {
251 Self::X714(d) => d.stop_inventory().await,
252 Self::R700(d) => d.stop_inventory().await,
253 _ => Err("device type does not support this operation".to_string()),
254 }
255 }
256
257 pub async fn write_epc(
258 &self,
259 target_identifier: Option<&str>,
260 target_value: Option<&str>,
261 new_epc: &str,
262 password: &str,
263 ) -> Result<(), String> {
264 match self {
265 Self::X714(d) => {
266 d.write_epc(target_identifier, target_value, new_epc, password)
267 .await
268 }
269 Self::R700(d) => {
270 d.write_epc(target_identifier, target_value, new_epc, password)
271 .await
272 }
273 _ => Err("device type does not support this operation".to_string()),
274 }
275 }
276
277 pub async fn write_gpo(
278 &self,
279 pin: u8,
280 state: bool,
281 control: &str,
282 time_ms: u64,
283 ) -> Result<(), String> {
284 match self {
285 Self::X714(d) => d.write_gpo(pin, state, control, time_ms).await,
286 Self::R700(d) => d.write_gpo(pin, state, control, time_ms as u32).await,
287 _ => Err("device type does not support this operation".to_string()),
288 }
289 }
290}
291
292#[derive(Debug, Clone, serde::Serialize)]
294pub struct DeviceInfo {
295 pub name: String,
296 pub device_type: String,
297 pub device_class: String,
298 pub is_connected: bool,
299 pub is_reading: bool,
300 pub is_gpi_trigger_on: bool,
301 pub can_print: bool,
302 pub to_print: usize,
303 pub has_serial_number: bool,
304 pub serial_number: String,
305 pub connect_instruction: String,
306 pub current_config: HashMap<String, Value>,
307}
308
309pub struct DeviceManager {
311 pub devices: Vec<Device>,
312 devices_path: PathBuf,
313 event_handler: Option<SharedEventHandler>,
314 connect_tasks: Vec<JoinHandle<()>>,
315}
316
317impl DeviceManager {
318 pub fn new<P: AsRef<Path>>(devices_path: P) -> Self {
319 Self {
320 devices: Vec::new(),
321 devices_path: devices_path.as_ref().to_path_buf(),
322 event_handler: None,
323 connect_tasks: Vec::new(),
324 }
325 }
326
327 pub fn with_event_handler(mut self, handler: SharedEventHandler) -> Self {
328 self.event_handler = Some(handler);
329 self
330 }
331
332 pub fn set_event_handler(&mut self, handler: SharedEventHandler) {
333 self.event_handler = Some(handler);
334 }
335
336 pub fn load_devices(&mut self) {
337 self.devices.clear();
338
339 if !self.devices_path.exists() {
340 match std::fs::create_dir_all(&self.devices_path) {
341 Ok(_) => eprintln!("đ Directory created: {}", self.devices_path.display()),
342 Err(e) => {
343 eprintln!(
344 "â Could not create directory '{}': {e}",
345 self.devices_path.display()
346 );
347 return;
348 }
349 }
350 }
351
352 let entries = match std::fs::read_dir(&self.devices_path) {
353 Ok(e) => e,
354 Err(e) => {
355 eprintln!("â Error listing '{}': {e}", self.devices_path.display());
356 return;
357 }
358 };
359
360 for entry in entries.flatten() {
361 let path = entry.path();
362 if path.extension().and_then(|e| e.to_str()) != Some("json") {
363 continue;
364 }
365
366 let filename = path
367 .file_name()
368 .unwrap_or_default()
369 .to_string_lossy()
370 .to_string();
371 let name = filename.trim_end_matches(".json").to_string();
372
373 eprintln!("đ Reading '{}'âĻ", filename);
374
375 let content = match std::fs::read_to_string(&path) {
376 Ok(s) => s,
377 Err(e) => {
378 eprintln!("â Error reading '{}': {e}", filename);
379 continue;
380 }
381 };
382
383 let raw: HashMap<String, Value> = match serde_json::from_str(&content) {
384 Ok(d) => d,
385 Err(e) => {
386 eprintln!("â Invalid JSON in '{}': {e}", filename);
387 continue;
388 }
389 };
390
391 let data: HashMap<String, Value> = raw
392 .into_iter()
393 .map(|(k, v)| (k.to_lowercase(), v))
394 .collect();
395
396 let reader_type = match data.get("reader").and_then(|v| v.as_str()) {
397 Some(t) => t.to_string(),
398 None => {
399 eprintln!("â ī¸ '{}' has no 'reader' field â skipped", filename);
400 continue;
401 }
402 };
403
404 self.add_device(&name, &reader_type, data);
405 }
406
407 self.assign_event_handler();
408 eprintln!("â
{} device(s) loaded", self.devices.len());
409 }
410
411 pub fn add_device(&mut self, name: &str, device_type: &str, mut data: HashMap<String, Value>) {
412 data.insert("name".to_string(), Value::String(name.to_string()));
413
414 match device_type.to_uppercase().as_str() {
415 "X714" => match X714::from_map(data) {
416 Ok(d) => {
417 eprintln!(" â
X714 '{}' â {}", name, d.connect_instruction());
418 self.devices.push(Device::X714(d));
419 }
420 Err(e) => eprintln!(" â X714 '{}' config error: {e}", name),
421 },
422 "R700_IOT" | "R700" => match R700::from_map(data) {
423 Ok(d) => {
424 eprintln!(" â
R700 '{}' â {}", name, d.connect_instruction());
425 self.devices.push(Device::R700(d));
426 }
427 Err(e) => eprintln!(" â R700 '{}' config error: {e}", name),
428 },
429 "SERIAL" => {
430 let d = SerialDevice::from_map(data);
431 eprintln!(" â
SERIAL '{}' â {}", name, d.connect_instruction());
432 self.devices.push(Device::Serial(d));
433 }
434 "TCP" => {
435 let d = TcpDevice::from_map(data);
436 eprintln!(" â
TCP '{}' â {}", name, d.connect_instruction());
437 self.devices.push(Device::Tcp(d));
438 }
439 "SATO" => {
440 let d = SatoPrinter::from_map(data);
441 eprintln!(" â
SATO '{}' â {}", name, d.connect_instruction());
442 self.devices.push(Device::Sato(d));
443 }
444 "SATO_WS4" => {
445 let d = SatoWs4Printer::from_map(data);
446 eprintln!(" â
SATO_WS4 '{}' â {}", name, d.connect_instruction());
447 self.devices.push(Device::SatoWs4(d));
448 }
449 other => eprintln!(" â ī¸ Unknown type '{}' for '{}' â skipped", other, name),
450 }
451 }
452
453 pub fn assign_event_handler(&mut self) {
454 let Some(handler) = &self.event_handler else {
455 return;
456 };
457 for device in &mut self.devices {
458 device.set_event_handler(Arc::clone(handler));
459 }
460 }
461
462 pub async fn connect_devices(&mut self, force: bool) {
463 let active = self
464 .connect_tasks
465 .iter()
466 .filter(|t| !t.is_finished())
467 .count();
468
469 if active > 0 && !force {
470 eprintln!(
471 "âšī¸ {} active connection task(s) â use force=true to restart",
472 active
473 );
474 return;
475 }
476
477 self.cancel_connect_tasks().await;
478 self.disconnect_devices().await;
479 self.load_devices();
480
481 let mut tasks = Vec::new();
482 for device in &self.devices {
483 let d = device.clone();
484 let name_display = d.connect_instruction();
485 eprintln!("đ Connecting '{}'âĻ ({})", d.name(), name_display);
486 let handle = tokio::spawn(async move { d.connect().await });
487 tasks.push(handle);
488 }
489
490 eprintln!("âšī¸ {} connection task(s) started", tasks.len());
491 self.connect_tasks = tasks;
492 }
493
494 pub async fn cancel_connect_tasks(&mut self) {
495 let n = self.connect_tasks.len();
496 for task in self.connect_tasks.drain(..) {
497 task.abort();
498 }
499 if n > 0 {
500 eprintln!("đ {} task(s) cancelled", n);
501 }
502 }
503
504 pub async fn disconnect_devices(&mut self) {
505 for device in &self.devices {
506 device.close().await;
507 }
508 self.devices.clear();
509 }
510
511 pub fn len(&self) -> usize {
512 self.devices.len()
513 }
514 pub fn is_empty(&self) -> bool {
515 self.devices.is_empty()
516 }
517
518 pub fn get_device_names(&self) -> Vec<String> {
519 self.devices.iter().map(|d| d.name().to_string()).collect()
520 }
521
522 pub fn get_device(&self, name: &str) -> Option<&Device> {
523 self.devices.iter().find(|d| d.name() == name)
524 }
525
526 pub fn get_device_mut(&mut self, name: &str) -> Option<&mut Device> {
527 self.devices.iter_mut().find(|d| d.name() == name)
528 }
529
530 pub fn get_device_info(&self, name: Option<&str>) -> Vec<DeviceInfo> {
531 match name {
532 Some(n) => self
533 .get_device(n)
534 .map(|d| vec![Self::build_info(d)])
535 .unwrap_or_default(),
536 None => self.devices.iter().map(Self::build_info).collect(),
537 }
538 }
539
540 fn build_info(d: &Device) -> DeviceInfo {
541 let serial_number = d.serial_number();
542 let has_serial_number = d.is_connected() && serial_number.is_some();
543 DeviceInfo {
544 name: d.name().to_string(),
545 device_type: d.device_type().to_string(),
546 device_class: d.device_class().to_string(),
547 is_connected: d.is_connected(),
548 is_reading: d.is_connected() && d.is_reading(),
549 is_gpi_trigger_on: d.is_gpi_trigger_on(),
550 can_print: d.can_print(),
551 to_print: d.pending_print_jobs(),
552 has_serial_number,
553 serial_number: serial_number.unwrap_or_else(|| "Unknown".to_string()),
554 connect_instruction: d.connect_instruction(),
555 current_config: d.to_map(),
556 }
557 }
558
559 pub fn any_device_reading(&self) -> bool {
560 self.devices
561 .iter()
562 .any(|d| d.is_connected() && d.is_reading())
563 }
564
565 pub fn get_serial_number(&self, name: &str) -> Option<String> {
566 let d = self.get_device(name)?;
567 if !d.is_connected() {
568 return None;
569 }
570 d.serial_number()
571 }
572
573 pub fn get_device_config(&self, name: &str) -> Option<HashMap<String, Value>> {
574 self.get_device(name).map(Device::to_map)
575 }
576
577 pub fn get_device_configs(&self) -> HashMap<String, HashMap<String, Value>> {
578 self.devices
579 .iter()
580 .map(|d| (d.name().to_string(), d.to_map()))
581 .collect()
582 }
583
584 pub async fn start_inventory(&self, name: &str) -> Result<(), String> {
585 let d = self
586 .get_device(name)
587 .ok_or_else(|| format!("device '{}' not found", name))?;
588 if !d.is_connected() {
589 return Err(format!("device '{}' is not connected", name));
590 }
591 d.start_inventory().await.map_err(|e| {
592 eprintln!("â start_inventory '{}': {e}", name);
593 e
594 })
595 }
596
597 pub async fn stop_inventory(&self, name: &str) -> Result<(), String> {
598 let d = self
599 .get_device(name)
600 .ok_or_else(|| format!("device '{}' not found", name))?;
601 if !d.is_connected() {
602 return Err(format!("device '{}' is not connected", name));
603 }
604 d.stop_inventory().await.map_err(|e| {
605 eprintln!("â stop_inventory '{}': {e}", name);
606 e
607 })
608 }
609
610 pub async fn start_inventory_all(&self) -> HashMap<String, bool> {
611 let mut results = HashMap::new();
612 for d in &self.devices {
613 if d.is_connected() {
614 let ok = d.start_inventory().await.is_ok();
615 results.insert(d.name().to_string(), ok);
616 }
617 }
618 results
619 }
620
621 pub async fn stop_inventory_all(&self) -> HashMap<String, bool> {
622 let mut results = HashMap::new();
623 for d in &self.devices {
624 if d.is_connected() {
625 let ok = d.stop_inventory().await.is_ok();
626 results.insert(d.name().to_string(), ok);
627 }
628 }
629 results
630 }
631
632 pub async fn write_epc(
633 &self,
634 name: &str,
635 target_identifier: Option<&str>,
636 target_value: Option<&str>,
637 new_epc: &str,
638 password: &str,
639 ) -> Result<(), String> {
640 let d = self
641 .get_device(name)
642 .ok_or_else(|| format!("device '{}' not found", name))?;
643 if !d.is_connected() {
644 return Err(format!("device '{}' is not connected", name));
645 }
646 d.write_epc(target_identifier, target_value, new_epc, password)
647 .await
648 }
649
650 pub async fn write_gpo(
651 &self,
652 name: &str,
653 pin: u8,
654 state: bool,
655 control: &str,
656 time_ms: u64,
657 ) -> Result<(), String> {
658 let d = self
659 .get_device(name)
660 .ok_or_else(|| format!("device '{}' not found", name))?;
661 if !d.is_connected() {
662 return Err(format!("device '{}' is not connected", name));
663 }
664 d.write_gpo(pin, state, control, time_ms).await
665 }
666
667 pub fn get_config_examples() -> Vec<&'static str> {
668 CONFIG_EXAMPLES.iter().map(|(name, _)| *name).collect()
669 }
670
671 pub fn get_config_example(name: &str) -> Option<HashMap<String, Value>> {
672 CONFIG_EXAMPLES
673 .iter()
674 .find(|(n, _)| n.eq_ignore_ascii_case(name))
675 .map(|(_, f)| f())
676 }
677}
678
679#[cfg(test)]
680mod tests {
681 use super::*;
682 use serde_json::json;
683
684 #[test]
685 fn device_info_includes_runtime_and_config_fields() {
686 let mut manager = DeviceManager::new("/tmp/unused");
687 manager.add_device(
688 "dock-reader",
689 "X714",
690 HashMap::from([
691 ("reader".to_string(), Value::String("X714".to_string())),
692 (
693 "connection_type".to_string(),
694 Value::String("TCP".to_string()),
695 ),
696 ("ip".to_string(), Value::String("192.168.1.50".to_string())),
697 ("tcp_port".to_string(), json!(23)),
698 ("gpi_start".to_string(), Value::Bool(true)),
699 ]),
700 );
701
702 let info = manager
703 .get_device_info(Some("dock-reader"))
704 .into_iter()
705 .next()
706 .expect("device info");
707
708 assert_eq!(info.name, "dock-reader");
709 assert_eq!(info.device_type, "X714");
710 assert_eq!(info.device_class, "X714");
711 assert!(!info.is_connected);
712 assert!(!info.is_reading);
713 assert!(info.is_gpi_trigger_on);
714 assert!(!info.can_print);
715 assert_eq!(info.to_print, 0);
716 assert!(!info.has_serial_number);
717 assert_eq!(info.serial_number, "Unknown");
718 assert_eq!(
719 info.current_config
720 .get("connection_type")
721 .and_then(Value::as_str),
722 Some("TCP")
723 );
724 }
725}