Skip to main content

ghpascon_rust/devices/rfid/r700/
config.rs

1use std::collections::HashMap;
2
3use serde_json::{Map, Number, Value, json};
4
5pub type ParamMap = HashMap<String, Value>;
6
7// ─── Antenna ──────────────────────────────────────────────────────────────────
8
9#[derive(Debug, Clone)]
10pub struct AntennaConfig {
11    pub antenna_port: u8,
12    pub estimated_tag_population: u32,
13    pub fast_id: bool,
14    pub inventory_search_mode: String,
15    pub inventory_session: u8,
16    pub receive_sensitivity_dbm: i32,
17    pub rf_mode: u32,
18    pub transmit_power_cdbm: i32,
19    pub protected_mode_pin_hex: Option<String>,
20}
21
22impl Default for AntennaConfig {
23    fn default() -> Self {
24        Self {
25            antenna_port: 1,
26            estimated_tag_population: 16,
27            fast_id: true,
28            inventory_search_mode: "dual-target".to_string(),
29            inventory_session: 1,
30            receive_sensitivity_dbm: -80,
31            rf_mode: 4,
32            transmit_power_cdbm: 3000,
33            protected_mode_pin_hex: None,
34        }
35    }
36}
37
38impl AntennaConfig {
39    pub fn to_json(&self) -> Value {
40        let mut obj = Map::new();
41        obj.insert("antennaPort".to_string(), json!(self.antenna_port));
42        obj.insert(
43            "estimatedTagPopulation".to_string(),
44            json!(self.estimated_tag_population),
45        );
46        obj.insert(
47            "fastId".to_string(),
48            json!(if self.fast_id { "enabled" } else { "disabled" }),
49        );
50        obj.insert(
51            "inventorySearchMode".to_string(),
52            json!(self.inventory_search_mode),
53        );
54        obj.insert(
55            "inventorySession".to_string(),
56            json!(self.inventory_session),
57        );
58        obj.insert(
59            "receiveSensitivityDbm".to_string(),
60            json!(self.receive_sensitivity_dbm),
61        );
62        obj.insert("rfMode".to_string(), json!(self.rf_mode));
63        obj.insert(
64            "transmitPowerCdbm".to_string(),
65            json!(self.transmit_power_cdbm),
66        );
67        if let Some(pin) = &self.protected_mode_pin_hex {
68            obj.insert("protectedModePinHex".to_string(), json!(pin));
69        }
70        Value::Object(obj)
71    }
72}
73
74// ─── Main config ──────────────────────────────────────────────────────────────
75
76#[derive(Debug, Clone)]
77pub struct R700Config {
78    pub name: String,
79    pub ip: String,
80    pub username: String,
81    pub password: String,
82    pub start_reading: bool,
83    pub firmware_version: Option<String>,
84    pub session: u8,
85    pub read_power: i32,
86    pub read_rssi: i32,
87    pub search_mode: String,
88    pub rf_mode: u32,
89    pub gpi_start: bool,
90    pub protected_inventory_active: bool,
91    pub protected_inventory_password: String,
92    pub reconnection_time: u64,
93    pub active_ant: Vec<u8>,
94    /// Optional full payload for POST /profiles/inventory/start.
95    /// When set, this takes precedence over the generated config.
96    pub reading_config: Option<Value>,
97}
98
99impl Default for R700Config {
100    fn default() -> Self {
101        Self {
102            name: "R700".to_string(),
103            ip: "192.168.1.101".to_string(),
104            username: "root".to_string(),
105            password: "impinj".to_string(),
106            start_reading: false,
107            firmware_version: None,
108            session: 1,
109            read_power: 3000,
110            read_rssi: -80,
111            search_mode: "single-target".to_string(),
112            rf_mode: 4,
113            gpi_start: false,
114            protected_inventory_active: false,
115            protected_inventory_password: "12345678".to_string(),
116            reconnection_time: 2,
117            active_ant: vec![1],
118            reading_config: None,
119        }
120    }
121}
122
123impl R700Config {
124    /// Build the JSON body sent to `POST /profiles/inventory/start`.
125    pub fn build_reading_config(&self) -> Value {
126        if let Some(custom) = &self.reading_config {
127            return custom.clone();
128        }
129
130        let antennas: Vec<Value> = self
131            .active_ant
132            .iter()
133            .map(|&port| {
134                let mut ant = AntennaConfig {
135                    antenna_port: port,
136                    inventory_session: self.session,
137                    receive_sensitivity_dbm: self.read_rssi,
138                    transmit_power_cdbm: self.read_power,
139                    inventory_search_mode: self.search_mode.clone(),
140                    rf_mode: self.rf_mode,
141                    ..AntennaConfig::default()
142                };
143                if self.protected_inventory_active {
144                    ant.protected_mode_pin_hex = Some(self.protected_inventory_password.clone());
145                }
146                ant.to_json()
147            })
148            .collect();
149
150        let mut cfg = Map::new();
151        cfg.insert("antennaConfigs".to_string(), Value::Array(antennas));
152
153        if self.gpi_start {
154            cfg.insert(
155                "startTriggers".to_string(),
156                json!([{"gpiTransitionEvent": {"gpi": 1, "transition": "high-to-low"}}]),
157            );
158            cfg.insert(
159                "stopTriggers".to_string(),
160                json!([{"gpiTransitionEvent": {"gpi": 1, "transition": "low-to-high"}}]),
161            );
162        }
163
164        cfg.insert(
165            "eventConfig".to_string(),
166            json!({
167                "common": {"hostname": "disabled"},
168                "tagInventory": {
169                    "epc": "disabled",
170                    "epcHex": "enabled",
171                    "xpcHex": "disabled",
172                    "tid": "disabled",
173                    "tidHex": "enabled",
174                    "antennaPort": "enabled",
175                    "transmitPowerCdbm": "disabled",
176                    "peakRssiCdbm": "enabled",
177                    "frequency": "disabled",
178                    "pc": "disabled",
179                    "lastSeenTime": "disabled",
180                    "phaseAngle": "disabled",
181                    "tagReporting": {
182                        "reportingIntervalSeconds": 1,
183                        "tagCacheSize": 2048,
184                        "antennaIdentifier": "antennaPort",
185                        "tagIdentifier": "epc"
186                    }
187                }
188            }),
189        );
190
191        Value::Object(cfg)
192    }
193
194    pub fn base_url(&self) -> String {
195        format!("https://{}/api/v1", self.ip)
196    }
197
198    // ── from_map / to_map ─────────────────────────────────────────────────────
199
200    pub fn from_map(params: ParamMap) -> Result<Self, String> {
201        let mut cfg = Self::default();
202
203        if let Some(Value::String(v)) = params.get("name") {
204            cfg.name = v.clone();
205        }
206        if let Some(Value::String(v)) = params.get("ip") {
207            cfg.ip = v.clone();
208        }
209        if let Some(Value::String(v)) = params.get("username") {
210            cfg.username = v.clone();
211        }
212        if let Some(Value::String(v)) = params.get("password") {
213            cfg.password = v.clone();
214        }
215        if let Some(Value::Bool(v)) = params.get("start_reading") {
216            cfg.start_reading = *v;
217        }
218        if let Some(Value::String(v)) = params.get("firmware_version") {
219            cfg.firmware_version = Some(v.clone());
220        }
221        if let Some(v) = params.get("session").and_then(|v| v.as_u64()) {
222            cfg.session = (v.clamp(0, 3)) as u8;
223        }
224        if let Some(v) = params.get("read_power").and_then(|v| v.as_i64()) {
225            let mut p = v as i32;
226            if p < 1000 {
227                p *= 100;
228            }
229            if !(1000..=3300).contains(&p) {
230                p = 3000;
231            }
232            cfg.read_power = p;
233        }
234        if let Some(v) = params.get("read_rssi").and_then(|v| v.as_i64()) {
235            cfg.read_rssi = v as i32;
236        }
237        if let Some(Value::String(v)) = params.get("search_mode") {
238            if matches!(v.as_str(), "single-target" | "dual-target") {
239                cfg.search_mode = v.clone();
240            } else {
241                cfg.search_mode = "single-target".to_string();
242            }
243        }
244        if let Some(v) = params.get("rf_mode").and_then(|v| v.as_u64()) {
245            cfg.rf_mode = v as u32;
246        }
247        if let Some(Value::Bool(v)) = params.get("gpi_start") {
248            cfg.gpi_start = *v;
249            if *v {
250                cfg.start_reading = false;
251            }
252        }
253        if let Some(Value::Bool(v)) = params.get("protected_inventory_active") {
254            cfg.protected_inventory_active = *v;
255        }
256        if let Some(Value::String(v)) = params.get("protected_inventory_password") {
257            cfg.protected_inventory_password = v.clone();
258        }
259        if let Some(v) = params.get("reconnection_time").and_then(|v| v.as_u64()) {
260            cfg.reconnection_time = v;
261        }
262        if let Some(Value::Array(arr)) = params.get("active_ant") {
263            cfg.active_ant = arr
264                .iter()
265                .filter_map(|v| v.as_u64().map(|n| n as u8))
266                .collect();
267            if cfg.active_ant.is_empty() {
268                cfg.active_ant = vec![1];
269            }
270        }
271
272        if let Some(Value::Object(obj)) = params.get("reading_config") {
273            cfg.reading_config = Some(Value::Object(obj.clone()));
274        }
275
276        Ok(cfg)
277    }
278
279    pub fn to_map(&self) -> ParamMap {
280        let mut map = HashMap::new();
281        map.insert("name".to_string(), Value::String(self.name.clone()));
282        map.insert("ip".to_string(), Value::String(self.ip.clone()));
283        map.insert("username".to_string(), Value::String(self.username.clone()));
284        map.insert("password".to_string(), Value::String(self.password.clone()));
285        map.insert("start_reading".to_string(), Value::Bool(self.start_reading));
286        map.insert(
287            "firmware_version".to_string(),
288            self.firmware_version
289                .clone()
290                .map(Value::String)
291                .unwrap_or(Value::Null),
292        );
293        map.insert(
294            "session".to_string(),
295            Value::Number(Number::from(self.session)),
296        );
297        map.insert(
298            "read_power".to_string(),
299            Value::Number(Number::from(self.read_power)),
300        );
301        map.insert(
302            "read_rssi".to_string(),
303            Value::Number(Number::from(self.read_rssi)),
304        );
305        map.insert(
306            "search_mode".to_string(),
307            Value::String(self.search_mode.clone()),
308        );
309        map.insert(
310            "rf_mode".to_string(),
311            Value::Number(Number::from(self.rf_mode)),
312        );
313        map.insert("gpi_start".to_string(), Value::Bool(self.gpi_start));
314        map.insert(
315            "protected_inventory_active".to_string(),
316            Value::Bool(self.protected_inventory_active),
317        );
318        map.insert(
319            "protected_inventory_password".to_string(),
320            Value::String(self.protected_inventory_password.clone()),
321        );
322        map.insert(
323            "reconnection_time".to_string(),
324            Value::Number(Number::from(self.reconnection_time)),
325        );
326        map.insert(
327            "active_ant".to_string(),
328            Value::Array(
329                self.active_ant
330                    .iter()
331                    .map(|&a| Value::Number(Number::from(a)))
332                    .collect(),
333            ),
334        );
335        map.insert(
336            "reading_config".to_string(),
337            self.reading_config.clone().unwrap_or(Value::Null),
338        );
339        map
340    }
341}