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