Skip to content

Commit 621a68e

Browse files
committed
src: device: manager: Add network discovery and upgrade AutoCreate method
1 parent 4a0042d commit 621a68e

File tree

2 files changed

+240
-15
lines changed

2 files changed

+240
-15
lines changed
+225
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
use std::net::Ipv4Addr;
2+
3+
use tokio_serial::available_ports;
4+
use tracing::warn;
5+
6+
use super::{SourceSelection, SourceSerialStruct, SourceUdpStruct};
7+
8+
#[derive(Debug, PartialEq)]
9+
pub struct DiscoveryResponse {
10+
pub device_name: String,
11+
pub manufacturer: String,
12+
pub mac_address: String,
13+
pub ip_address: Ipv4Addr,
14+
}
15+
16+
impl DiscoveryResponse {
17+
/// Decode Ping360 ASCI NetworkDiscovery message
18+
fn from_response(response: &str) -> Option<Self> {
19+
let lines: Vec<&str> = response
20+
.split("\r\n")
21+
.filter(|line| !line.is_empty())
22+
.collect();
23+
24+
if lines.len() != 4 {
25+
warn!(
26+
"Expected 4 lines but found {}. Response: {:?}",
27+
lines.len(),
28+
lines
29+
);
30+
return None;
31+
}
32+
33+
let device_name = lines[0].trim().to_string();
34+
let manufacturer = lines[1].trim().to_string();
35+
36+
let mac_address_line = lines[2].trim();
37+
let ip_address_line = lines[3].trim();
38+
39+
if !mac_address_line.starts_with("MAC Address:- ")
40+
|| !ip_address_line.starts_with("IP Address:- ")
41+
{
42+
warn!("auto_create: network: Unexpected format in MAC Address or IP Address lines.");
43+
return None;
44+
}
45+
46+
let mac_address = mac_address_line
47+
.trim_start_matches("MAC Address:- ")
48+
.to_string();
49+
let ip_address_str = ip_address_line.trim_start_matches("IP Address:- ").trim();
50+
51+
let cleaned_ip_address_str = ip_address_str
52+
.split('.')
53+
.map(|octet| {
54+
let trimmed = octet.trim_start_matches('0');
55+
if trimmed.is_empty() {
56+
"0"
57+
} else {
58+
trimmed
59+
}
60+
})
61+
.collect::<Vec<&str>>()
62+
.join(".");
63+
64+
let ip_address = match cleaned_ip_address_str.parse::<Ipv4Addr>() {
65+
Ok(ip) => ip,
66+
Err(err) => {
67+
warn!(
68+
"auto_create: network: Failed to parse IP address: {cleaned_ip_address_str}, details: {err}"
69+
);
70+
return None;
71+
}
72+
};
73+
74+
Some(DiscoveryResponse {
75+
device_name,
76+
manufacturer,
77+
mac_address,
78+
ip_address,
79+
})
80+
}
81+
}
82+
83+
pub fn network_discovery() -> Option<Vec<SourceSelection>> {
84+
// Create a UDP socket
85+
let socket = std::net::UdpSocket::bind("0.0.0.0:0").unwrap(); // Bind to any available port
86+
socket.set_broadcast(true).unwrap(); // Enable broadcast
87+
88+
let broadcast_addr = "255.255.255.255:30303";
89+
let discovery_message = "Discovery";
90+
91+
socket
92+
.send_to(discovery_message.as_bytes(), broadcast_addr)
93+
.unwrap();
94+
95+
socket
96+
.set_read_timeout(Some(std::time::Duration::from_secs(2)))
97+
.unwrap();
98+
99+
let mut buf = [0; 1024];
100+
let mut responses = Vec::new();
101+
102+
loop {
103+
match socket.recv_from(&mut buf) {
104+
Ok((size, src)) => {
105+
let response = std::str::from_utf8(&buf[..size]).unwrap_or("[Invalid UTF-8]");
106+
107+
if let Some(discovery_response) = DiscoveryResponse::from_response(response) {
108+
responses.push(discovery_response);
109+
} else {
110+
warn!(
111+
"auto_create: network: Failed to parse the discovery response from: {src}"
112+
);
113+
}
114+
}
115+
Err(e) => {
116+
warn!("auto_create: network: Timeout or error receiving response: {e}");
117+
break;
118+
}
119+
}
120+
}
121+
122+
if responses.is_empty() {
123+
warn!("auto_create: network: No valid discovery responses were collected.");
124+
return None;
125+
}
126+
127+
let mut available_sources = Vec::new();
128+
for device in responses {
129+
let source = SourceSelection::UdpStream(SourceUdpStruct {
130+
ip: device.ip_address,
131+
port: 12345,
132+
});
133+
134+
available_sources.push(source);
135+
}
136+
Some(available_sources)
137+
}
138+
139+
pub fn serial_discovery() -> Option<Vec<SourceSelection>> {
140+
match available_ports() {
141+
Ok(serial_ports) => {
142+
let mut available_sources = Vec::new();
143+
for port_info in serial_ports {
144+
let source = SourceSelection::SerialStream(SourceSerialStruct {
145+
path: port_info.port_name.clone(),
146+
baudrate: 115200,
147+
});
148+
available_sources.push(source);
149+
}
150+
Some(available_sources)
151+
}
152+
Err(err) => {
153+
warn!("Auto create: Unable to find available devices on serial ports, details: {err}");
154+
None
155+
}
156+
}
157+
}
158+
#[cfg(test)]
159+
mod tests {
160+
use super::*;
161+
162+
#[test]
163+
fn test_discovery_response_parsing() {
164+
let response = "SONAR PING360\r\n\
165+
Blue Robotics\r\n\
166+
MAC Address:- 54-10-EC-79-7D-D1\r\n\
167+
IP Address:- 192.168.000.197\r\n";
168+
169+
let expected = DiscoveryResponse {
170+
device_name: "SONAR PING360".to_string(),
171+
manufacturer: "Blue Robotics".to_string(),
172+
mac_address: "54-10-EC-79-7D-D1".to_string(),
173+
ip_address: Ipv4Addr::new(192, 168, 0, 197),
174+
};
175+
176+
let parsed = DiscoveryResponse::from_response(response);
177+
assert_eq!(parsed, Some(expected));
178+
}
179+
180+
#[test]
181+
fn test_invalid_response_parsing() {
182+
let invalid_response = "INVALID RESPONSE FORMAT";
183+
184+
let parsed = DiscoveryResponse::from_response(invalid_response);
185+
assert!(parsed.is_none());
186+
}
187+
188+
#[test]
189+
fn test_multiple_discovery_responses() {
190+
let response_1 = "SONAR PING360\r\n\
191+
Blue Robotics\r\n\
192+
MAC Address:- 54-10-EC-79-7D-D1\r\n\
193+
IP Address:- 192.168.000.197\r\n";
194+
195+
let response_2 = "SONAR PING360\r\n\
196+
Blue Robotics\r\n\
197+
MAC Address:- 54-10-EC-79-7D-D2\r\n\
198+
IP Address:- 192.168.000.198\r\n";
199+
200+
let expected_1 = DiscoveryResponse {
201+
device_name: "SONAR PING360".to_string(),
202+
manufacturer: "Blue Robotics".to_string(),
203+
mac_address: "54-10-EC-79-7D-D1".to_string(),
204+
ip_address: Ipv4Addr::new(192, 168, 0, 197),
205+
};
206+
207+
let expected_2 = DiscoveryResponse {
208+
device_name: "SONAR PING360".to_string(),
209+
manufacturer: "Blue Robotics".to_string(),
210+
mac_address: "54-10-EC-79-7D-D2".to_string(),
211+
ip_address: Ipv4Addr::new(192, 168, 0, 198),
212+
};
213+
214+
let responses = vec![response_1, response_2];
215+
216+
let mut parsed_responses = Vec::new();
217+
for response in responses {
218+
if let Some(parsed) = DiscoveryResponse::from_response(response) {
219+
parsed_responses.push(parsed);
220+
}
221+
}
222+
223+
assert_eq!(parsed_responses, vec![expected_1, expected_2]);
224+
}
225+
}

src/device/manager/mod.rs

+15-15
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
/// Specially for DeviceManager to retrieve checks and structures from Devices stored in it's hashmap collection
22
pub mod continuous_mode;
3+
/// Specially for auto creation methods, from UDP or serial port
4+
pub mod device_discovery;
35
/// Specially for continuous_mode methods, startup, shutdown, handle and errors routines for each device type
46
pub mod device_handle;
57

@@ -12,7 +14,6 @@ use std::{
1214
ops::Deref,
1315
};
1416
use tokio::sync::{mpsc, oneshot};
15-
use tokio_serial::available_ports;
1617

1718
use tokio_serial::{SerialPort, SerialPortBuilderExt, SerialStream};
1819
use tracing::{error, info, trace, warn};
@@ -399,20 +400,22 @@ impl DeviceManager {
399400
}
400401

401402
pub async fn auto_create(&mut self) -> Result<Answer, ManagerError> {
402-
let serial_ports =
403-
available_ports().map_err(|err| ManagerError::DeviceSourceError(err.to_string()))?;
404-
405403
let mut results = Vec::new();
406404

407-
for port_info in serial_ports {
408-
let source = SourceSelection::SerialStream(SourceSerialStruct {
409-
path: port_info.port_name.clone(),
410-
baudrate: 115200,
411-
});
405+
let mut available_source = Vec::new();
406+
407+
match device_discovery::serial_discovery() {
408+
Some(result) => available_source.extend(result),
409+
None => warn!("Auto create: Unable to find available devices on serial ports"),
410+
}
412411

413-
let device_selection = DeviceSelection::Auto;
412+
match device_discovery::network_discovery() {
413+
Some(result) => available_source.extend(result),
414+
None => warn!("Auto create: Unable to find available devices on network"),
415+
}
414416

415-
match self.create(source, device_selection).await {
417+
for source in available_source {
418+
match self.create(source.clone(), DeviceSelection::Auto).await {
416419
Ok(answer) => match answer {
417420
Answer::DeviceInfo(device_info) => {
418421
results.extend(device_info);
@@ -422,10 +425,7 @@ impl DeviceManager {
422425
}
423426
},
424427
Err(err) => {
425-
error!(
426-
"Failed to create device for port {}: {:?}",
427-
port_info.port_name, err
428-
);
428+
error!("Failed to create device for source {:?}: {:?}", source, err);
429429
}
430430
}
431431
}

0 commit comments

Comments
 (0)