Skip to main content

ghpascon_rust/devices/
device_manager.rs

1//! `DeviceManager` — carrega e gerencia múltiplos dispositivos RFID.
2//!
3//! Cada dispositivo é configurado por um arquivo `.json` em um diretório.
4//! O campo `"reader"` determina o tipo (`"X714"` ou `"R700_IOT"`).
5//! Todos os outros campos são opcionais — valores padrão são aplicados
6//! automaticamente pelo `from_map` de cada device.
7//!
8//! # Formato mínimo de configuração
9//!
10//! ```json
11//! { "reader": "X714", "connection_type": "TCP", "ip": "192.168.1.50" }
12//! ```
13//! ```json
14//! { "reader": "R700_IOT", "ip": "192.168.1.101" }
15//! ```
16
17use std::collections::HashMap;
18use std::path::{Path, PathBuf};
19use std::sync::{Arc, Mutex};
20
21use serde_json::Value;
22use tokio::task::JoinHandle;
23
24use super::rfid::r700::R700;
25use super::rfid::x714::X714;
26
27// ─── Tipos públicos ───────────────────────────────────────────────────────────
28
29/// Função de callback de eventos: `(device_name, event_type, event_data)`
30pub type EventHandler = dyn FnMut(&str, &str, Option<Value>) + Send + 'static;
31/// Handler compartilhado entre threads / devices.
32pub type SharedEventHandler = Arc<Mutex<Box<EventHandler>>>;
33
34// ─── Enum Device ─────────────────────────────────────────────────────────────
35
36/// Wrapper de enum em torno de todos os tipos de device suportados.
37/// `Clone` é barato — todos os tipos usam `Arc` internamente.
38pub enum Device {
39    X714(X714),
40    R700(R700),
41}
42
43impl Clone for Device {
44    fn clone(&self) -> Self {
45        match self {
46            Self::X714(d) => Self::X714(d.clone()),
47            Self::R700(d) => Self::R700(d.clone()),
48        }
49    }
50}
51
52impl Device {
53    pub fn name(&self) -> &str {
54        match self {
55            Self::X714(d) => &d.config.name,
56            Self::R700(d) => &d.config.name,
57        }
58    }
59
60    pub fn device_type(&self) -> &'static str {
61        match self {
62            Self::X714(_) => "X714",
63            Self::R700(_) => "R700_IOT",
64        }
65    }
66
67    pub fn is_connected(&self) -> bool {
68        match self {
69            Self::X714(d) => d.is_connected(),
70            Self::R700(d) => d.is_connected(),
71        }
72    }
73
74    pub fn is_reading(&self) -> bool {
75        match self {
76            Self::X714(d) => d.is_reading(),
77            Self::R700(d) => d.is_reading(),
78        }
79    }
80
81    pub fn serial_number(&self) -> Option<String> {
82        match self {
83            Self::X714(d) => d.serial_number(),
84            Self::R700(d) => d.serial_number(),
85        }
86    }
87
88    pub fn connect_instruction(&self) -> String {
89        match self {
90            Self::X714(d) => d.connect_instruction(),
91            Self::R700(d) => d.connect_instruction(),
92        }
93    }
94
95    pub fn set_event_handler(&mut self, handler: SharedEventHandler) {
96        match self {
97            Self::X714(d) => d.set_event_handler(handler),
98            Self::R700(d) => d.set_event_handler(handler),
99        }
100    }
101
102    pub async fn connect(&self) {
103        match self {
104            Self::X714(d) => d.connect().await,
105            Self::R700(d) => d.connect().await,
106        }
107    }
108
109    pub async fn close(&self) {
110        match self {
111            Self::X714(d) => d.close().await,
112            Self::R700(d) => d.close().await,
113        }
114    }
115
116    pub async fn start_inventory(&self) -> Result<(), String> {
117        match self {
118            Self::X714(d) => d.start_inventory().await,
119            Self::R700(d) => d.start_inventory().await,
120        }
121    }
122
123    pub async fn stop_inventory(&self) -> Result<(), String> {
124        match self {
125            Self::X714(d) => d.stop_inventory().await,
126            Self::R700(d) => d.stop_inventory().await,
127        }
128    }
129
130    pub async fn write_epc(
131        &self,
132        target_identifier: Option<&str>,
133        target_value: Option<&str>,
134        new_epc: &str,
135        password: &str,
136    ) -> Result<(), String> {
137        match self {
138            Self::X714(d) => {
139                d.write_epc(target_identifier, target_value, new_epc, password)
140                    .await
141            }
142            Self::R700(d) => {
143                d.write_epc(target_identifier, target_value, new_epc, password)
144                    .await
145            }
146        }
147    }
148
149    pub async fn write_gpo(
150        &self,
151        pin: u8,
152        state: bool,
153        control: &str,
154        time_ms: u64,
155    ) -> Result<(), String> {
156        match self {
157            Self::X714(d) => d.write_gpo(pin, state, control, time_ms).await,
158            Self::R700(d) => d.write_gpo(pin, state, control, time_ms as u32).await,
159        }
160    }
161}
162
163// ─── DeviceInfo ───────────────────────────────────────────────────────────────
164
165/// Snapshot de estado de um device (não mantém referência ao device).
166#[derive(Debug, Clone)]
167pub struct DeviceInfo {
168    pub name: String,
169    pub device_type: String,
170    pub is_connected: bool,
171    pub is_reading: bool,
172    pub serial_number: Option<String>,
173    pub connect_instruction: String,
174}
175
176// ─── DeviceManager ────────────────────────────────────────────────────────────
177
178/// Gerencia múltiplos devices: carrega configs JSON, conecta, despacha eventos.
179///
180/// # Uso básico
181///
182/// ```rust,no_run
183/// # use ghpascon_rust::devices::device_manager::{DeviceManager, SharedEventHandler};
184/// # use std::sync::{Arc, Mutex};
185/// # #[tokio::main] async fn main() {
186/// let mut manager = DeviceManager::new("examples/devices/configs");
187/// manager.load_devices();
188/// manager.connect_devices(false).await;
189/// # }
190/// ```
191pub struct DeviceManager {
192    /// Devices carregados.
193    pub devices: Vec<Device>,
194    /// Diretório com os arquivos `.json` de configuração.
195    devices_path: PathBuf,
196    /// Handler de eventos compartilhado atribuído a todos os devices.
197    event_handler: Option<SharedEventHandler>,
198    /// Handles das tasks de conexão em background.
199    connect_tasks: Vec<JoinHandle<()>>,
200}
201
202impl DeviceManager {
203    // ─── Construtores ─────────────────────────────────────────────────────────
204
205    /// Cria um manager apontando para `devices_path`.
206    pub fn new<P: AsRef<Path>>(devices_path: P) -> Self {
207        Self {
208            devices: Vec::new(),
209            devices_path: devices_path.as_ref().to_path_buf(),
210            event_handler: None,
211            connect_tasks: Vec::new(),
212        }
213    }
214
215    /// Define o handler de eventos (builder-style).
216    pub fn with_event_handler(mut self, handler: SharedEventHandler) -> Self {
217        self.event_handler = Some(handler);
218        self
219    }
220
221    /// Substitui o handler de eventos.
222    pub fn set_event_handler(&mut self, handler: SharedEventHandler) {
223        self.event_handler = Some(handler);
224    }
225
226    // ─── Carregamento ─────────────────────────────────────────────────────────
227
228    /// Lê os arquivos `.json` de `devices_path` e popula `self.devices`.
229    ///
230    /// Cada arquivo deve ter um campo `"reader"` (`"X714"` ou `"R700_IOT"`).
231    /// O nome do arquivo (sem `.json`) vira o `name` do device.
232    /// Todos os outros campos são opcionais — padrões são aplicados automaticamente.
233    /// Chama `assign_event_handler()` ao final.
234    pub fn load_devices(&mut self) {
235        self.devices.clear();
236
237        // Cria o diretório se não existir
238        if !self.devices_path.exists() {
239            match std::fs::create_dir_all(&self.devices_path) {
240                Ok(_) => eprintln!("📁 Diretório criado: {}", self.devices_path.display()),
241                Err(e) => {
242                    eprintln!(
243                        "❌ Não foi possível criar diretório '{}': {e}",
244                        self.devices_path.display()
245                    );
246                    return;
247                }
248            }
249        }
250
251        let entries = match std::fs::read_dir(&self.devices_path) {
252            Ok(e) => e,
253            Err(e) => {
254                eprintln!("❌ Erro ao listar '{}': {e}", self.devices_path.display());
255                return;
256            }
257        };
258
259        for entry in entries.flatten() {
260            let path = entry.path();
261            if path.extension().and_then(|e| e.to_str()) != Some("json") {
262                continue;
263            }
264
265            let filename = path
266                .file_name()
267                .unwrap_or_default()
268                .to_string_lossy()
269                .to_string();
270            let name = filename.trim_end_matches(".json").to_string();
271
272            eprintln!("📄 Lendo '{}'…", filename);
273
274            let content = match std::fs::read_to_string(&path) {
275                Ok(s) => s,
276                Err(e) => {
277                    eprintln!("❌ Erro ao ler '{}': {e}", filename);
278                    continue;
279                }
280            };
281
282            let raw: HashMap<String, Value> = match serde_json::from_str(&content) {
283                Ok(d) => d,
284                Err(e) => {
285                    eprintln!("❌ JSON inválido em '{}': {e}", filename);
286                    continue;
287                }
288            };
289
290            // Normaliza chaves para lowercase
291            let data: HashMap<String, Value> = raw
292                .into_iter()
293                .map(|(k, v)| (k.to_lowercase(), v))
294                .collect();
295
296            let reader_type = match data.get("reader").and_then(|v| v.as_str()) {
297                Some(t) => t.to_string(),
298                None => {
299                    eprintln!("⚠️  '{}' não tem campo 'reader' — ignorado", filename);
300                    continue;
301                }
302            };
303
304            self.add_device(&name, &reader_type, data);
305        }
306
307        self.assign_event_handler();
308        eprintln!("✅ {} device(s) carregado(s)", self.devices.len());
309    }
310
311    /// Adiciona um device a partir de parâmetros já parseados.
312    ///
313    /// O campo `"name"` é injetado automaticamente a partir do nome do arquivo.
314    /// Campos não informados usam os padrões de cada device.
315    pub fn add_device(&mut self, name: &str, device_type: &str, mut data: HashMap<String, Value>) {
316        // Nome vem sempre do arquivo; sobrescreve qualquer valor no JSON
317        data.insert("name".to_string(), Value::String(name.to_string()));
318
319        match device_type.to_uppercase().as_str() {
320            "X714" => match X714::from_map(data) {
321                Ok(d) => {
322                    eprintln!("  ✅ X714 '{}' → {}", name, d.connect_instruction());
323                    self.devices.push(Device::X714(d));
324                }
325                Err(e) => eprintln!("  ❌ X714 '{}' erro de config: {e}", name),
326            },
327            "R700_IOT" | "R700" => match R700::from_map(data) {
328                Ok(d) => {
329                    eprintln!("  ✅ R700 '{}' → {}", name, d.connect_instruction());
330                    self.devices.push(Device::R700(d));
331                }
332                Err(e) => eprintln!("  ❌ R700 '{}' erro de config: {e}", name),
333            },
334            other => {
335                eprintln!(
336                    "  ⚠️  Tipo desconhecido '{}' para '{}' — ignorado",
337                    other, name
338                );
339            }
340        }
341    }
342
343    // ─── Event handler ────────────────────────────────────────────────────────
344
345    /// Atribui o handler de eventos a todos os devices carregados.
346    pub fn assign_event_handler(&mut self) {
347        let Some(handler) = &self.event_handler else {
348            return;
349        };
350        for device in &mut self.devices {
351            device.set_event_handler(Arc::clone(handler));
352        }
353    }
354
355    // ─── Conexão ─────────────────────────────────────────────────────────────
356
357    /// Conecta todos os devices em background (tasks tokio).
358    ///
359    /// - Se já existirem tasks ativas e `force` for `false`, é um no-op.
360    /// - Se `force` for `true`, cancela as tasks anteriores, desconecta os
361    ///   devices, recarrega os JSONs e reconecta tudo.
362    pub async fn connect_devices(&mut self, force: bool) {
363        let active = self
364            .connect_tasks
365            .iter()
366            .filter(|t| !t.is_finished())
367            .count();
368
369        if active > 0 && !force {
370            eprintln!(
371                "ℹ️  {} task(s) de conexão ativas — use force=true para reiniciar",
372                active
373            );
374            return;
375        }
376
377        self.cancel_connect_tasks().await;
378        self.disconnect_devices().await;
379        self.load_devices();
380
381        let mut tasks = Vec::new();
382        for device in &self.devices {
383            let d = device.clone();
384            let name_display = d.connect_instruction();
385            eprintln!("🚀 Conectando '{}'…  ({})", d.name(), name_display);
386            let handle = tokio::spawn(async move { d.connect().await });
387            tasks.push(handle);
388        }
389
390        eprintln!("ℹ️  {} task(s) de conexão iniciadas", tasks.len());
391        self.connect_tasks = tasks;
392    }
393
394    /// Cancela todas as tasks de conexão ativas.
395    pub async fn cancel_connect_tasks(&mut self) {
396        let n = self.connect_tasks.len();
397        for task in self.connect_tasks.drain(..) {
398            task.abort();
399        }
400        if n > 0 {
401            eprintln!("🛑 {} task(s) cancelada(s)", n);
402        }
403    }
404
405    /// Fecha todos os devices e limpa a lista.
406    pub async fn disconnect_devices(&mut self) {
407        for device in &self.devices {
408            device.close().await;
409        }
410        self.devices.clear();
411    }
412
413    // ─── Introspection ────────────────────────────────────────────────────────
414
415    pub fn len(&self) -> usize {
416        self.devices.len()
417    }
418
419    pub fn is_empty(&self) -> bool {
420        self.devices.is_empty()
421    }
422
423    /// Retorna os nomes de todos os devices carregados.
424    pub fn get_device_names(&self) -> Vec<String> {
425        self.devices.iter().map(|d| d.name().to_string()).collect()
426    }
427
428    /// Retorna referência para um device pelo nome.
429    pub fn get_device(&self, name: &str) -> Option<&Device> {
430        self.devices.iter().find(|d| d.name() == name)
431    }
432
433    /// Retorna referência mutável para um device pelo nome.
434    pub fn get_device_mut(&mut self, name: &str) -> Option<&mut Device> {
435        self.devices.iter_mut().find(|d| d.name() == name)
436    }
437
438    /// Snapshot de estado de um ou todos os devices.
439    ///
440    /// Se `name` for `None`, retorna info de todos.
441    pub fn get_device_info(&self, name: Option<&str>) -> Vec<DeviceInfo> {
442        match name {
443            Some(n) => self
444                .get_device(n)
445                .map(|d| vec![Self::build_info(d)])
446                .unwrap_or_default(),
447            None => self.devices.iter().map(Self::build_info).collect(),
448        }
449    }
450
451    fn build_info(d: &Device) -> DeviceInfo {
452        DeviceInfo {
453            name: d.name().to_string(),
454            device_type: d.device_type().to_string(),
455            is_connected: d.is_connected(),
456            is_reading: d.is_reading(),
457            serial_number: d.serial_number(),
458            connect_instruction: d.connect_instruction(),
459        }
460    }
461
462    /// `true` se pelo menos um device está conectado e lendo.
463    pub fn any_device_reading(&self) -> bool {
464        self.devices
465            .iter()
466            .any(|d| d.is_connected() && d.is_reading())
467    }
468
469    /// Retorna o número de serial do device (apenas se conectado).
470    pub fn get_serial_number(&self, name: &str) -> Option<String> {
471        let d = self.get_device(name)?;
472        if !d.is_connected() {
473            return None;
474        }
475        d.serial_number()
476    }
477
478    // ─── Inventário ───────────────────────────────────────────────────────────
479
480    /// Inicia inventário em um device específico.
481    pub async fn start_inventory(&self, name: &str) -> Result<(), String> {
482        let d = self
483            .get_device(name)
484            .ok_or_else(|| format!("device '{}' não encontrado", name))?;
485        if !d.is_connected() {
486            return Err(format!("device '{}' não está conectado", name));
487        }
488        d.start_inventory().await.map_err(|e| {
489            eprintln!("❌ start_inventory '{}': {e}", name);
490            e
491        })
492    }
493
494    /// Para inventário em um device específico.
495    pub async fn stop_inventory(&self, name: &str) -> Result<(), String> {
496        let d = self
497            .get_device(name)
498            .ok_or_else(|| format!("device '{}' não encontrado", name))?;
499        if !d.is_connected() {
500            return Err(format!("device '{}' não está conectado", name));
501        }
502        d.stop_inventory().await.map_err(|e| {
503            eprintln!("❌ stop_inventory '{}': {e}", name);
504            e
505        })
506    }
507
508    /// Inicia inventário em todos os devices RFID conectados.
509    /// Retorna mapa `name → ok`.
510    pub async fn start_inventory_all(&self) -> HashMap<String, bool> {
511        let mut results = HashMap::new();
512        for d in &self.devices {
513            if d.is_connected() {
514                let ok = d.start_inventory().await.is_ok();
515                results.insert(d.name().to_string(), ok);
516            }
517        }
518        results
519    }
520
521    /// Para inventário em todos os devices RFID conectados.
522    /// Retorna mapa `name → ok`.
523    pub async fn stop_inventory_all(&self) -> HashMap<String, bool> {
524        let mut results = HashMap::new();
525        for d in &self.devices {
526            if d.is_connected() {
527                let ok = d.stop_inventory().await.is_ok();
528                results.insert(d.name().to_string(), ok);
529            }
530        }
531        results
532    }
533
534    // ─── Write operations ─────────────────────────────────────────────────────
535
536    /// Escreve um novo EPC em uma tag.
537    ///
538    /// - `target_identifier`: `Some("epc")` ou `Some("tid")` (filtra a tag alvo); `None` = qualquer tag.
539    /// - `target_value`: valor do identificador alvo (obrigatório quando `target_identifier` é `Some`).
540    pub async fn write_epc(
541        &self,
542        name: &str,
543        target_identifier: Option<&str>,
544        target_value: Option<&str>,
545        new_epc: &str,
546        password: &str,
547    ) -> Result<(), String> {
548        let d = self
549            .get_device(name)
550            .ok_or_else(|| format!("device '{}' não encontrado", name))?;
551        if !d.is_connected() {
552            return Err(format!("device '{}' não está conectado", name));
553        }
554        d.write_epc(target_identifier, target_value, new_epc, password)
555            .await
556    }
557
558    /// Controla um pino GPO.
559    ///
560    /// - `control`: `"static"` ou `"pulsed"`
561    /// - `time_ms`: duração do pulso (ignorado em modo `"static"`)
562    pub async fn write_gpo(
563        &self,
564        name: &str,
565        pin: u8,
566        state: bool,
567        control: &str,
568        time_ms: u64,
569    ) -> Result<(), String> {
570        let d = self
571            .get_device(name)
572            .ok_or_else(|| format!("device '{}' não encontrado", name))?;
573        if !d.is_connected() {
574            return Err(format!("device '{}' não está conectado", name));
575        }
576        d.write_gpo(pin, state, control, time_ms).await
577    }
578}