Skip to main content

ghpascon_rust/devices/rfid/x714/
config.rs

1use std::collections::{BTreeMap, HashMap};
2
3use serde::{Deserialize, Serialize};
4use serde_json::{Map, Number, Value};
5
6pub type ParamMap = HashMap<String, Value>;
7
8#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
9pub enum ConnectionType {
10    Serial,
11    Tcp,
12    Ble,
13}
14
15impl ConnectionType {
16    pub fn from_str(value: &str) -> Self {
17        match value.trim().to_uppercase().as_str() {
18            "TCP" => Self::Tcp,
19            "BLE" => Self::Ble,
20            _ => Self::Serial,
21        }
22    }
23
24    pub fn as_wire_str(&self) -> &'static str {
25        match self {
26            Self::Serial => "SERIAL",
27            Self::Tcp => "TCP",
28            Self::Ble => "BLE",
29        }
30    }
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
34pub struct SerialConfig {
35    pub port: String,
36    pub baudrate: u32,
37    pub vid: u16,
38    pub pid: u16,
39}
40
41impl Default for SerialConfig {
42    fn default() -> Self {
43        Self {
44            port: "AUTO".to_string(),
45            baudrate: 115_200,
46            vid: 1,
47            pid: 1,
48        }
49    }
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
53pub struct TcpConfig {
54    pub ip: String,
55    pub port: u16,
56}
57
58impl Default for TcpConfig {
59    fn default() -> Self {
60        Self {
61            ip: "192.168.1.100".to_string(),
62            port: 23,
63        }
64    }
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
68pub struct BleConfig {
69    pub name: String,
70    pub address: Option<String>,
71    pub service_uuid: String,
72    pub rx_uuid: String,
73    pub tx_uuid: String,
74}
75
76impl Default for BleConfig {
77    fn default() -> Self {
78        Self {
79            name: "SMTX".to_string(),
80            address: None,
81            service_uuid: "6E400001-B5A3-F393-E0A9-E50E24DCCA9E".to_string(),
82            rx_uuid: "6E400002-B5A3-F393-E0A9-E50E24DCCA9E".to_string(),
83            tx_uuid: "6E400003-B5A3-F393-E0A9-E50E24DCCA9E".to_string(),
84        }
85    }
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
89pub struct AntennaConfig {
90    pub active: bool,
91    pub power: i32,
92    pub rssi: i32,
93}
94
95impl Default for AntennaConfig {
96    fn default() -> Self {
97        Self {
98            active: true,
99            power: 22,
100            rssi: -120,
101        }
102    }
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct X714Config {
107    pub name: String,
108    pub connection_type: ConnectionType,
109    pub serial: SerialConfig,
110    pub tcp: TcpConfig,
111    pub ble: BleConfig,
112    pub buzzer: bool,
113    pub session: u8,
114    pub start_reading: bool,
115    pub gpi_start: bool,
116    pub always_send: bool,
117    pub simple_send: bool,
118    pub keyboard: bool,
119    pub decode_gtin: bool,
120    pub hotspot: bool,
121    pub reconnection_time: u64,
122    pub prefix: String,
123    pub protected_inventory_active: bool,
124    pub protected_inventory_password: String,
125    pub ant_dict: BTreeMap<String, AntennaConfig>,
126}
127
128impl Default for X714Config {
129    fn default() -> Self {
130        let mut ant_dict = BTreeMap::new();
131        for (key, active) in [("1", true), ("2", false), ("3", false), ("4", false)] {
132            ant_dict.insert(
133                key.to_string(),
134                AntennaConfig {
135                    active,
136                    ..AntennaConfig::default()
137                },
138            );
139        }
140
141        Self {
142            name: "X714".to_string(),
143            connection_type: ConnectionType::Serial,
144            serial: SerialConfig::default(),
145            tcp: TcpConfig::default(),
146            ble: BleConfig::default(),
147            buzzer: false,
148            session: 1,
149            start_reading: false,
150            gpi_start: false,
151            always_send: true,
152            simple_send: false,
153            keyboard: false,
154            decode_gtin: false,
155            hotspot: true,
156            reconnection_time: 3,
157            prefix: String::new(),
158            protected_inventory_active: false,
159            protected_inventory_password: "12345678".to_string(),
160            ant_dict,
161        }
162    }
163}
164
165impl X714Config {
166    pub fn from_map(params: ParamMap) -> Result<Self, String> {
167        let mut config = Self::default();
168
169        if let Some(v) = get_string(&params, "name") {
170            config.name = v;
171        }
172        if let Some(v) = get_string(&params, "connection_type") {
173            config.connection_type = ConnectionType::from_str(&v);
174        }
175
176        if let Some(v) = get_string(&params, "port") {
177            config.serial.port = v;
178        }
179        if let Some(v) = get_u32(&params, "baudrate") {
180            config.serial.baudrate = v;
181        }
182        if let Some(v) = get_u16(&params, "vid") {
183            config.serial.vid = v;
184        }
185        if let Some(v) = get_u16(&params, "pid") {
186            config.serial.pid = v;
187        }
188
189        if let Some(v) = get_string(&params, "ip") {
190            config.tcp.ip = v;
191        }
192        if let Some(v) = get_u16(&params, "tcp_port") {
193            config.tcp.port = v;
194        }
195
196        if let Some(v) = get_string(&params, "ble_name") {
197            config.ble.name = v;
198        }
199        if let Some(v) = get_string(&params, "ble_address") {
200            config.ble.address = Some(v);
201        }
202        if let Some(v) = get_string(&params, "ble_service_uuid") {
203            config.ble.service_uuid = v;
204        }
205        if let Some(v) = get_string(&params, "ble_rx_uuid") {
206            config.ble.rx_uuid = v;
207        }
208        if let Some(v) = get_string(&params, "ble_tx_uuid") {
209            config.ble.tx_uuid = v;
210        }
211
212        set_bool(&params, "buzzer", &mut config.buzzer);
213        if let Some(v) = get_u8(&params, "session") {
214            config.session = v.clamp(0, 3);
215        }
216        set_bool(&params, "start_reading", &mut config.start_reading);
217        set_bool(&params, "gpi_start", &mut config.gpi_start);
218        set_bool(&params, "always_send", &mut config.always_send);
219        set_bool(&params, "simple_send", &mut config.simple_send);
220        set_bool(&params, "keyboard", &mut config.keyboard);
221        set_bool(&params, "decode_gtin", &mut config.decode_gtin);
222        set_bool(&params, "hotspot", &mut config.hotspot);
223
224        if let Some(v) = get_u64(&params, "reconnection_time") {
225            config.reconnection_time = v;
226        }
227        if let Some(v) = get_string(&params, "prefix") {
228            config.prefix = normalize_hex_prefix(&v);
229        }
230
231        set_bool(
232            &params,
233            "protected_inventory_active",
234            &mut config.protected_inventory_active,
235        );
236        if let Some(v) = get_string(&params, "protected_inventory_password") {
237            config.protected_inventory_password = v;
238        }
239
240        if let Some(raw_ant) = params.get("ant_dict") {
241            config.ant_dict = parse_ant_dict(raw_ant)?;
242        } else {
243            let active_ant = get_u8_vec(&params, "active_ant").unwrap_or_else(|| vec![1]);
244            let read_power = get_i32(&params, "read_power").unwrap_or(22);
245            let read_rssi = get_i32(&params, "read_rssi").unwrap_or(-120);
246            for ant in [1_u8, 2, 3, 4] {
247                config.ant_dict.insert(
248                    ant.to_string(),
249                    AntennaConfig {
250                        active: active_ant.contains(&ant),
251                        power: read_power,
252                        rssi: read_rssi,
253                    },
254                );
255            }
256        }
257
258        if config.gpi_start {
259            config.start_reading = false;
260        }
261
262        Ok(config)
263    }
264
265    pub fn to_map(&self) -> ParamMap {
266        let mut out = HashMap::new();
267        out.insert("name".to_string(), Value::String(self.name.clone()));
268        out.insert(
269            "connection_type".to_string(),
270            Value::String(self.connection_type.as_wire_str().to_string()),
271        );
272        out.insert("port".to_string(), Value::String(self.serial.port.clone()));
273        out.insert(
274            "baudrate".to_string(),
275            Value::Number(Number::from(self.serial.baudrate)),
276        );
277        out.insert(
278            "vid".to_string(),
279            Value::Number(Number::from(self.serial.vid)),
280        );
281        out.insert(
282            "pid".to_string(),
283            Value::Number(Number::from(self.serial.pid)),
284        );
285        out.insert("ip".to_string(), Value::String(self.tcp.ip.clone()));
286        out.insert(
287            "tcp_port".to_string(),
288            Value::Number(Number::from(self.tcp.port)),
289        );
290        out.insert("ble_name".to_string(), Value::String(self.ble.name.clone()));
291        out.insert(
292            "ble_address".to_string(),
293            self.ble
294                .address
295                .clone()
296                .map(Value::String)
297                .unwrap_or(Value::Null),
298        );
299        out.insert(
300            "ble_service_uuid".to_string(),
301            Value::String(self.ble.service_uuid.clone()),
302        );
303        out.insert(
304            "ble_rx_uuid".to_string(),
305            Value::String(self.ble.rx_uuid.clone()),
306        );
307        out.insert(
308            "ble_tx_uuid".to_string(),
309            Value::String(self.ble.tx_uuid.clone()),
310        );
311        out.insert("buzzer".to_string(), Value::Bool(self.buzzer));
312        out.insert(
313            "session".to_string(),
314            Value::Number(Number::from(self.session)),
315        );
316        out.insert("start_reading".to_string(), Value::Bool(self.start_reading));
317        out.insert("gpi_start".to_string(), Value::Bool(self.gpi_start));
318        out.insert("always_send".to_string(), Value::Bool(self.always_send));
319        out.insert("simple_send".to_string(), Value::Bool(self.simple_send));
320        out.insert("keyboard".to_string(), Value::Bool(self.keyboard));
321        out.insert("decode_gtin".to_string(), Value::Bool(self.decode_gtin));
322        out.insert("hotspot".to_string(), Value::Bool(self.hotspot));
323        out.insert(
324            "reconnection_time".to_string(),
325            Value::Number(Number::from(self.reconnection_time)),
326        );
327        out.insert("prefix".to_string(), Value::String(self.prefix.clone()));
328        out.insert(
329            "protected_inventory_active".to_string(),
330            Value::Bool(self.protected_inventory_active),
331        );
332        out.insert(
333            "protected_inventory_password".to_string(),
334            Value::String(self.protected_inventory_password.clone()),
335        );
336        out.insert("ant_dict".to_string(), ant_dict_to_value(&self.ant_dict));
337        out
338    }
339}
340
341pub fn ant_dict_to_value(ant_dict: &BTreeMap<String, AntennaConfig>) -> Value {
342    let mut outer = Map::new();
343    for (k, v) in ant_dict {
344        let mut ant = Map::new();
345        ant.insert("active".to_string(), Value::Bool(v.active));
346        ant.insert("power".to_string(), Value::Number(Number::from(v.power)));
347        ant.insert("rssi".to_string(), Value::Number(Number::from(v.rssi)));
348        outer.insert(k.clone(), Value::Object(ant));
349    }
350    Value::Object(outer)
351}
352
353pub fn parse_ant_dict(value: &Value) -> Result<BTreeMap<String, AntennaConfig>, String> {
354    let Some(obj) = value.as_object() else {
355        return Err("ant_dict must be an object".to_string());
356    };
357
358    let mut out = BTreeMap::new();
359    for (k, v) in obj {
360        let Some(ant) = v.as_object() else {
361            return Err(format!("ant_dict['{k}'] must be an object"));
362        };
363        let active = ant.get("active").and_then(Value::as_bool).unwrap_or(false);
364        let power = ant
365            .get("power")
366            .and_then(Value::as_i64)
367            .and_then(|v| i32::try_from(v).ok())
368            .unwrap_or(22);
369        let rssi = ant
370            .get("rssi")
371            .and_then(Value::as_i64)
372            .and_then(|v| i32::try_from(v).ok())
373            .unwrap_or(-120);
374        out.insert(
375            k.clone(),
376            AntennaConfig {
377                active,
378                power,
379                rssi,
380            },
381        );
382    }
383
384    if out.is_empty() {
385        return Err("ant_dict must not be empty".to_string());
386    }
387
388    Ok(out)
389}
390
391pub fn normalize_hex_prefix(value: &str) -> String {
392    let trimmed = value.trim().to_lowercase();
393    if trimmed.chars().all(|c| c.is_ascii_hexdigit()) {
394        trimmed
395    } else {
396        String::new()
397    }
398}
399
400fn get_string(params: &ParamMap, key: &str) -> Option<String> {
401    params.get(key).and_then(Value::as_str).map(str::to_string)
402}
403
404fn get_bool(params: &ParamMap, key: &str) -> Option<bool> {
405    params.get(key).and_then(Value::as_bool)
406}
407
408fn set_bool(params: &ParamMap, key: &str, target: &mut bool) {
409    if let Some(v) = get_bool(params, key) {
410        *target = v;
411    }
412}
413
414fn get_u8(params: &ParamMap, key: &str) -> Option<u8> {
415    params
416        .get(key)
417        .and_then(Value::as_u64)
418        .and_then(|v| u8::try_from(v).ok())
419}
420
421fn get_u16(params: &ParamMap, key: &str) -> Option<u16> {
422    params
423        .get(key)
424        .and_then(Value::as_u64)
425        .and_then(|v| u16::try_from(v).ok())
426}
427
428fn get_u32(params: &ParamMap, key: &str) -> Option<u32> {
429    params
430        .get(key)
431        .and_then(Value::as_u64)
432        .and_then(|v| u32::try_from(v).ok())
433}
434
435fn get_u64(params: &ParamMap, key: &str) -> Option<u64> {
436    params.get(key).and_then(Value::as_u64)
437}
438
439fn get_i32(params: &ParamMap, key: &str) -> Option<i32> {
440    params
441        .get(key)
442        .and_then(Value::as_i64)
443        .and_then(|v| i32::try_from(v).ok())
444}
445
446fn get_u8_vec(params: &ParamMap, key: &str) -> Option<Vec<u8>> {
447    let values = params.get(key)?.as_array()?;
448    Some(
449        values
450            .iter()
451            .filter_map(|v| v.as_u64().and_then(|n| u8::try_from(n).ok()))
452            .collect::<Vec<_>>(),
453    )
454}