-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
src: device: manager: Add network discovery and upgrade AutoCreate me…
…thod
- Loading branch information
1 parent
4a0042d
commit e5c8b5d
Showing
2 changed files
with
240 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,225 @@ | ||
use std::net::Ipv4Addr; | ||
|
||
use tokio_serial::available_ports; | ||
use tracing::warn; | ||
|
||
use super::{SourceSelection, SourceSerialStruct, SourceUdpStruct}; | ||
|
||
#[derive(Debug, PartialEq)] | ||
pub struct DiscoveryResponse { | ||
pub device_name: String, | ||
pub manufacturer: String, | ||
pub mac_address: String, | ||
pub ip_address: Ipv4Addr, | ||
} | ||
|
||
impl DiscoveryResponse { | ||
/// Decode Ping360 ASCI NetworkDiscovery message | ||
fn from_response(response: &str) -> Option<Self> { | ||
let lines: Vec<&str> = response | ||
.split("\r\n") | ||
.filter(|line| !line.is_empty()) | ||
.collect(); | ||
|
||
if lines.len() != 4 { | ||
warn!( | ||
"Expected 4 lines but found {}. Response: {:?}", | ||
lines.len(), | ||
lines | ||
); | ||
return None; | ||
} | ||
|
||
let device_name = lines[0].trim().to_string(); | ||
let manufacturer = lines[1].trim().to_string(); | ||
|
||
let mac_address_line = lines[2].trim(); | ||
let ip_address_line = lines[3].trim(); | ||
|
||
if !mac_address_line.starts_with("MAC Address:- ") | ||
|| !ip_address_line.starts_with("IP Address:- ") | ||
{ | ||
warn!("auto_create: network: Unexpected format in MAC Address or IP Address lines."); | ||
return None; | ||
} | ||
|
||
let mac_address = mac_address_line | ||
.trim_start_matches("MAC Address:- ") | ||
.to_string(); | ||
let ip_address_str = ip_address_line.trim_start_matches("IP Address:- ").trim(); | ||
|
||
let cleaned_ip_address_str = ip_address_str | ||
.split('.') | ||
.map(|octet| { | ||
let trimmed = octet.trim_start_matches('0'); | ||
if trimmed.is_empty() { | ||
"0" | ||
} else { | ||
trimmed | ||
} | ||
}) | ||
.collect::<Vec<&str>>() | ||
.join("."); | ||
|
||
let ip_address = match cleaned_ip_address_str.parse::<Ipv4Addr>() { | ||
Ok(ip) => ip, | ||
Err(err) => { | ||
warn!( | ||
"auto_create: network: Failed to parse IP address: {cleaned_ip_address_str}, details: {err}" | ||
); | ||
return None; | ||
} | ||
}; | ||
|
||
Some(DiscoveryResponse { | ||
device_name, | ||
manufacturer, | ||
mac_address, | ||
ip_address, | ||
}) | ||
} | ||
} | ||
|
||
pub fn network_discovery() -> Option<Vec<SourceSelection>> { | ||
// Create a UDP socket | ||
let socket = std::net::UdpSocket::bind("0.0.0.0:0").unwrap(); // Bind to any available port | ||
socket.set_broadcast(true).unwrap(); // Enable broadcast | ||
|
||
let broadcast_addr = "255.255.255.255:30303"; | ||
let discovery_message = "Discovery"; | ||
|
||
socket | ||
.send_to(discovery_message.as_bytes(), broadcast_addr) | ||
.unwrap(); | ||
|
||
socket | ||
.set_read_timeout(Some(std::time::Duration::from_secs(2))) | ||
.unwrap(); | ||
|
||
let mut buf = [0; 1024]; | ||
let mut responses = Vec::new(); | ||
|
||
loop { | ||
match socket.recv_from(&mut buf) { | ||
Ok((size, src)) => { | ||
let response = std::str::from_utf8(&buf[..size]).unwrap_or("[Invalid UTF-8]"); | ||
|
||
if let Some(discovery_response) = DiscoveryResponse::from_response(response) { | ||
responses.push(discovery_response); | ||
} else { | ||
warn!( | ||
"auto_create: network: Failed to parse the discovery response from: {src}" | ||
); | ||
} | ||
} | ||
Err(e) => { | ||
warn!("auto_create: network: Timeout or error receiving response: {e}"); | ||
break; | ||
} | ||
} | ||
} | ||
|
||
if !responses.is_empty() { | ||
let mut available_sources = Vec::new(); | ||
for device in responses { | ||
let source = SourceSelection::UdpStream(SourceUdpStruct { | ||
ip: device.ip_address, | ||
port: 12345, | ||
}); | ||
|
||
available_sources.push(source); | ||
} | ||
Some(available_sources) | ||
} else { | ||
warn!("auto_create: network: No valid discovery responses were collected."); | ||
None | ||
} | ||
} | ||
|
||
pub fn serial_discovery() -> Option<Vec<SourceSelection>> { | ||
match available_ports() { | ||
Ok(serial_ports) => { | ||
let mut available_sources = Vec::new(); | ||
for port_info in serial_ports { | ||
let source = SourceSelection::SerialStream(SourceSerialStruct { | ||
path: port_info.port_name.clone(), | ||
baudrate: 115200, | ||
}); | ||
available_sources.push(source); | ||
} | ||
Some(available_sources) | ||
} | ||
Err(err) => { | ||
warn!("Auto create: Unable to find available devices on serial ports, details: {err}"); | ||
None | ||
} | ||
} | ||
} | ||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn test_discovery_response_parsing() { | ||
let response = "SONAR PING360\r\n\ | ||
Blue Robotics\r\n\ | ||
MAC Address:- 54-10-EC-79-7D-D1\r\n\ | ||
IP Address:- 192.168.000.197\r\n"; | ||
|
||
let expected = DiscoveryResponse { | ||
device_name: "SONAR PING360".to_string(), | ||
manufacturer: "Blue Robotics".to_string(), | ||
mac_address: "54-10-EC-79-7D-D1".to_string(), | ||
ip_address: Ipv4Addr::new(192, 168, 0, 197), | ||
}; | ||
|
||
let parsed = DiscoveryResponse::from_response(response); | ||
assert_eq!(parsed, Some(expected)); | ||
} | ||
|
||
#[test] | ||
fn test_invalid_response_parsing() { | ||
let invalid_response = "INVALID RESPONSE FORMAT"; | ||
|
||
let parsed = DiscoveryResponse::from_response(invalid_response); | ||
assert!(parsed.is_none()); | ||
} | ||
|
||
#[test] | ||
fn test_multiple_discovery_responses() { | ||
let response_1 = "SONAR PING360\r\n\ | ||
Blue Robotics\r\n\ | ||
MAC Address:- 54-10-EC-79-7D-D1\r\n\ | ||
IP Address:- 192.168.000.197\r\n"; | ||
|
||
let response_2 = "SONAR PING360\r\n\ | ||
Blue Robotics\r\n\ | ||
MAC Address:- 54-10-EC-79-7D-D2\r\n\ | ||
IP Address:- 192.168.000.198\r\n"; | ||
|
||
let expected_1 = DiscoveryResponse { | ||
device_name: "SONAR PING360".to_string(), | ||
manufacturer: "Blue Robotics".to_string(), | ||
mac_address: "54-10-EC-79-7D-D1".to_string(), | ||
ip_address: Ipv4Addr::new(192, 168, 0, 197), | ||
}; | ||
|
||
let expected_2 = DiscoveryResponse { | ||
device_name: "SONAR PING360".to_string(), | ||
manufacturer: "Blue Robotics".to_string(), | ||
mac_address: "54-10-EC-79-7D-D2".to_string(), | ||
ip_address: Ipv4Addr::new(192, 168, 0, 198), | ||
}; | ||
|
||
let responses = vec![response_1, response_2]; | ||
|
||
let mut parsed_responses = Vec::new(); | ||
for response in responses { | ||
if let Some(parsed) = DiscoveryResponse::from_response(response) { | ||
parsed_responses.push(parsed); | ||
} | ||
} | ||
|
||
assert_eq!(parsed_responses, vec![expected_1, expected_2]); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters