phytrace_sdk/models/domains/
communication.rs1use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7use validator::Validate;
8
9use crate::models::enums::{CellularGeneration, ConnectionStatus, NetworkType};
10
11#[derive(Debug, Clone, Default, Serialize, Deserialize, Validate)]
13pub struct CommunicationDomain {
14 #[serde(skip_serializing_if = "Option::is_none")]
17 pub network: Option<NetworkInfo>,
18
19 #[serde(skip_serializing_if = "Option::is_none")]
22 pub cellular: Option<CellularInfo>,
23
24 #[serde(skip_serializing_if = "Option::is_none")]
27 pub fleet: Option<FleetCommunication>,
28
29 #[serde(skip_serializing_if = "Option::is_none")]
32 pub integrations: Option<Vec<IntegrationStatus>>,
33
34 #[serde(skip_serializing_if = "Option::is_none")]
37 pub phytrace: Option<PhyTraceConnection>,
38}
39
40#[derive(Debug, Clone, Default, Serialize, Deserialize, Validate)]
42pub struct NetworkInfo {
43 #[serde(skip_serializing_if = "Option::is_none")]
45 pub network_type: Option<NetworkType>,
46
47 #[serde(skip_serializing_if = "Option::is_none")]
49 pub status: Option<ConnectionStatus>,
50
51 #[serde(skip_serializing_if = "Option::is_none")]
53 pub is_connected: Option<bool>,
54
55 #[serde(skip_serializing_if = "Option::is_none")]
57 pub ssid: Option<String>,
58
59 #[serde(skip_serializing_if = "Option::is_none")]
61 pub signal_strength_dbm: Option<i32>,
62
63 #[serde(skip_serializing_if = "Option::is_none")]
65 #[validate(range(min = 0.0, max = 100.0))]
66 pub signal_quality_pct: Option<f64>,
67
68 #[serde(skip_serializing_if = "Option::is_none")]
70 pub ip_address: Option<String>,
71
72 #[serde(skip_serializing_if = "Option::is_none")]
74 pub mac_address: Option<String>,
75
76 #[serde(skip_serializing_if = "Option::is_none")]
78 pub gateway: Option<String>,
79
80 #[serde(skip_serializing_if = "Option::is_none")]
82 pub dns_servers: Option<Vec<String>>,
83
84 #[serde(skip_serializing_if = "Option::is_none")]
86 pub link_speed_mbps: Option<f64>,
87
88 #[serde(skip_serializing_if = "Option::is_none")]
90 pub bytes_tx: Option<u64>,
91
92 #[serde(skip_serializing_if = "Option::is_none")]
94 pub bytes_rx: Option<u64>,
95
96 #[serde(skip_serializing_if = "Option::is_none")]
98 #[validate(range(min = 0.0))]
99 pub latency_ms: Option<f64>,
100
101 #[serde(skip_serializing_if = "Option::is_none")]
103 #[validate(range(min = 0.0, max = 100.0))]
104 pub packet_loss_pct: Option<f64>,
105}
106
107#[derive(Debug, Clone, Default, Serialize, Deserialize, Validate)]
109pub struct CellularInfo {
110 #[serde(skip_serializing_if = "Option::is_none")]
112 pub status: Option<ConnectionStatus>,
113
114 #[serde(skip_serializing_if = "Option::is_none")]
116 pub generation: Option<CellularGeneration>,
117
118 #[serde(skip_serializing_if = "Option::is_none")]
120 pub carrier: Option<String>,
121
122 #[serde(skip_serializing_if = "Option::is_none")]
124 pub signal_strength_dbm: Option<i32>,
125
126 #[serde(skip_serializing_if = "Option::is_none")]
128 pub signal_bars: Option<u8>,
129
130 #[serde(skip_serializing_if = "Option::is_none")]
132 pub is_roaming: Option<bool>,
133
134 #[serde(skip_serializing_if = "Option::is_none")]
136 pub data_usage_bytes: Option<u64>,
137
138 #[serde(skip_serializing_if = "Option::is_none")]
140 pub sim_status: Option<String>,
141
142 #[serde(skip_serializing_if = "Option::is_none")]
144 pub imei: Option<String>,
145}
146
147#[derive(Debug, Clone, Default, Serialize, Deserialize)]
149pub struct FleetCommunication {
150 #[serde(skip_serializing_if = "Option::is_none")]
152 pub fms_connected: Option<bool>,
153
154 #[serde(skip_serializing_if = "Option::is_none")]
156 pub fms_server: Option<String>,
157
158 #[serde(skip_serializing_if = "Option::is_none")]
160 pub last_fms_heartbeat: Option<DateTime<Utc>>,
161
162 #[serde(skip_serializing_if = "Option::is_none")]
164 pub r2r_enabled: Option<bool>,
165
166 #[serde(skip_serializing_if = "Option::is_none")]
168 pub robots_in_range: Option<u32>,
169
170 #[serde(skip_serializing_if = "Option::is_none")]
172 pub peer_robot_ids: Option<Vec<String>>,
173
174 #[serde(skip_serializing_if = "Option::is_none")]
176 pub messages_sent: Option<u64>,
177
178 #[serde(skip_serializing_if = "Option::is_none")]
180 pub messages_received: Option<u64>,
181}
182
183#[derive(Debug, Clone, Default, Serialize, Deserialize)]
185pub struct IntegrationStatus {
186 #[serde(skip_serializing_if = "Option::is_none")]
188 pub name: Option<String>,
189
190 #[serde(skip_serializing_if = "Option::is_none")]
192 pub integration_type: Option<String>,
193
194 #[serde(skip_serializing_if = "Option::is_none")]
196 pub status: Option<ConnectionStatus>,
197
198 #[serde(skip_serializing_if = "Option::is_none")]
200 pub endpoint: Option<String>,
201
202 #[serde(skip_serializing_if = "Option::is_none")]
204 pub last_success: Option<DateTime<Utc>>,
205
206 #[serde(skip_serializing_if = "Option::is_none")]
208 pub last_error: Option<String>,
209
210 #[serde(skip_serializing_if = "Option::is_none")]
212 pub request_count: Option<u64>,
213
214 #[serde(skip_serializing_if = "Option::is_none")]
216 pub error_count: Option<u64>,
217
218 #[serde(skip_serializing_if = "Option::is_none")]
220 pub avg_latency_ms: Option<f64>,
221}
222
223#[derive(Debug, Clone, Default, Serialize, Deserialize, Validate)]
225pub struct PhyTraceConnection {
226 #[serde(skip_serializing_if = "Option::is_none")]
228 pub status: Option<ConnectionStatus>,
229
230 #[serde(skip_serializing_if = "Option::is_none")]
232 pub endpoint: Option<String>,
233
234 #[serde(skip_serializing_if = "Option::is_none")]
236 pub last_event_sent: Option<DateTime<Utc>>,
237
238 #[serde(skip_serializing_if = "Option::is_none")]
240 pub events_sent: Option<u64>,
241
242 #[serde(skip_serializing_if = "Option::is_none")]
244 pub events_buffered: Option<u64>,
245
246 #[serde(skip_serializing_if = "Option::is_none")]
248 pub buffer_size_bytes: Option<u64>,
249
250 #[serde(skip_serializing_if = "Option::is_none")]
252 #[validate(range(min = 0.0))]
253 pub avg_latency_ms: Option<f64>,
254
255 #[serde(skip_serializing_if = "Option::is_none")]
257 pub send_errors: Option<u64>,
258
259 #[serde(skip_serializing_if = "Option::is_none")]
261 pub agent_version: Option<String>,
262}
263
264impl CommunicationDomain {
265 pub fn with_network(network: NetworkInfo) -> Self {
267 Self {
268 network: Some(network),
269 ..Default::default()
270 }
271 }
272
273 pub fn with_cellular(mut self, cellular: CellularInfo) -> Self {
275 self.cellular = Some(cellular);
276 self
277 }
278
279 pub fn with_fleet(mut self, fleet: FleetCommunication) -> Self {
281 self.fleet = Some(fleet);
282 self
283 }
284
285 pub fn with_integration(mut self, integration: IntegrationStatus) -> Self {
287 let integrations = self.integrations.get_or_insert_with(Vec::new);
288 integrations.push(integration);
289 self
290 }
291
292 pub fn with_phytrace(mut self, phytrace: PhyTraceConnection) -> Self {
294 self.phytrace = Some(phytrace);
295 self
296 }
297}
298
299impl NetworkInfo {
300 pub fn wifi(ssid: impl Into<String>, signal_dbm: i32) -> Self {
302 Self {
303 network_type: Some(NetworkType::Wifi),
304 status: Some(ConnectionStatus::Connected),
305 is_connected: Some(true),
306 ssid: Some(ssid.into()),
307 signal_strength_dbm: Some(signal_dbm),
308 signal_quality_pct: Some(Self::dbm_to_quality(signal_dbm)),
309 ..Default::default()
310 }
311 }
312
313 pub fn ethernet(ip: impl Into<String>) -> Self {
315 Self {
316 network_type: Some(NetworkType::Ethernet),
317 status: Some(ConnectionStatus::Connected),
318 is_connected: Some(true),
319 ip_address: Some(ip.into()),
320 signal_quality_pct: Some(100.0),
321 ..Default::default()
322 }
323 }
324
325 fn dbm_to_quality(dbm: i32) -> f64 {
327 ((dbm + 90) as f64 / 60.0 * 100.0).clamp(0.0, 100.0)
329 }
330}
331
332#[cfg(test)]
333mod tests {
334 use super::*;
335
336 #[test]
337 fn test_wifi_network() {
338 let network = NetworkInfo::wifi("RobotNet", -65);
339 assert_eq!(network.network_type, Some(NetworkType::Wifi));
340 assert_eq!(network.ssid, Some("RobotNet".to_string()));
341 assert!(network.signal_quality_pct.unwrap() > 0.0);
342 }
343
344 #[test]
345 fn test_communication_domain() {
346 let comm = CommunicationDomain::with_network(NetworkInfo::ethernet("192.168.1.100"))
347 .with_phytrace(PhyTraceConnection {
348 status: Some(ConnectionStatus::Connected),
349 events_sent: Some(1000),
350 ..Default::default()
351 });
352
353 assert!(comm.network.is_some());
354 assert_eq!(comm.phytrace.as_ref().unwrap().events_sent, Some(1000));
355 }
356}