From f270373a8834dd2fda45e4bf47fe60aaf3204136 Mon Sep 17 00:00:00 2001 From: Kees Verruijt Date: Sun, 6 Oct 2024 20:30:54 +0200 Subject: [PATCH] Furuno beginnings --- Cargo.lock | 24 +- Cargo.toml | 7 +- src/furuno/mod.rs | 166 ++++++++++++ src/furuno/settings.rs | 210 ++++++++++++++++ src/locator.rs | 73 ++++-- src/main.rs | 7 + src/navico/data.rs | 9 +- src/navico/mod.rs | 11 +- src/navico/report.rs | 10 +- src/navico/settings.rs | 560 ++++++++++++++++++++--------------------- src/signalk.rs | 24 +- src/util.rs | 49 +++- 12 files changed, 813 insertions(+), 337 deletions(-) create mode 100644 src/furuno/mod.rs create mode 100644 src/furuno/settings.rs diff --git a/Cargo.lock b/Cargo.lock index 4a5f6a3..d25a5f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -704,21 +704,21 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", @@ -727,21 +727,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", "futures-io", diff --git a/Cargo.toml b/Cargo.toml index 4a60326..671cef7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,11 @@ version = "0.1.0" edition = "2021" rust-version = "1.80.1" +[features] +navico = [] +furuno = [] +default = ["navico", "furuno"] + [dependencies] anyhow = "1.0.86" async-trait = "0.1.81" @@ -18,7 +23,7 @@ crossbeam = "0.8.4" directories = "5.0.1" enum-primitive-derive = "0.3.0" env_logger = "0.11.5" -futures-util = "0.3.30" +futures-util = "0.3.31" libc = "0.2.156" log = "0.4.22" mdns-sd = "0.11.4" diff --git a/src/furuno/mod.rs b/src/furuno/mod.rs new file mode 100644 index 0000000..2931501 --- /dev/null +++ b/src/furuno/mod.rs @@ -0,0 +1,166 @@ +use async_trait::async_trait; +use log::{log_enabled, trace}; +use serde::Deserialize; +use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4}; +use std::{fmt, io}; +use tokio::sync::mpsc; +use tokio_graceful_shutdown::{SubsystemBuilder, SubsystemHandle}; + +use crate::locator::{LocatorId, RadarListenAddress, RadarLocator}; +use crate::radar::{DopplerMode, Legend, RadarInfo, SharedRadars}; +use crate::util::PrintableSlice; + +mod settings; + +const FURUNO_BEACON_ADDRESS: SocketAddr = + SocketAddr::new(IpAddr::V4(Ipv4Addr::new(172, 31, 255, 255)), 10010); + +fn found(mut info: RadarInfo, radars: &SharedRadars, subsys: &SubsystemHandle) { + info.set_string(&crate::settings::ControlType::UserName, info.key()) + .unwrap(); + + if let Some(mut info) = radars.located(info) { + // It's new, start the RadarProcessor thread + + // Load the model name afresh, it may have been modified from persisted data + /* let model = match info.model_name() { + Some(s) => Model::new(&s), + None => Model::Unknown, + }; + if model != Model::Unknown { + let info2 = info.clone(); + info.controls.update_when_model_known(model, &info2); + info.set_legend(model == Model::HALO); + radars.update(&info); + } */ + + // let (tx_data, rx_data) = mpsc::channel(10); + + // Clone everything moved into future twice or more + let data_name = info.key() + " data"; + let report_name = info.key() + " reports"; + let info_clone = info.clone(); + let args = radars.cli_args(); + + if args.output { + let info_clone2 = info.clone(); + + subsys.start(SubsystemBuilder::new("stdout", move |s| { + info_clone2.forward_output(s) + })); + } + + /* + let data_receiver = data::FurunoDataReceiver::new(info, rx_data, args.replay); + let report_receiver = + report::FurunoReportReceiver::new(info_clone, radars.clone(), model, tx_data); + + subsys.start(SubsystemBuilder::new( + data_name, + move |s: SubsystemHandle| data_receiver.run(s), + )); + subsys.start(SubsystemBuilder::new(report_name, |s| { + report_receiver.run(s) + })); + */ + } +} + +fn process_locator_report( + report: &[u8], + from: &SocketAddr, + via: &Ipv4Addr, + radars: &SharedRadars, + subsys: &SubsystemHandle, +) -> io::Result<()> { + if report.len() < 2 { + return Ok(()); + } + + if log_enabled!(log::Level::Info) { + log::info!( + "{}: Furuno report: {:02X?} len {}", + from, + report, + report.len() + ); + log::info!("{}: printable: {}", from, PrintableSlice::new(report)); + } + + if report[0] == 0x1 && report[1] == 0xB2 { + // Common Furuno message + + return process_beacon_report(report, from, via, radars, subsys); + } + Ok(()) +} + +fn process_beacon_report( + report: &[u8], + from: &SocketAddr, + via: &Ipv4Addr, + radars: &SharedRadars, + subsys: &SubsystemHandle, +) -> Result<(), io::Error> { + /* + match deserialize::(report) { + Ok(data) => { + if let Some(serial_no) = c_string(&data.header.serial_no) { + let radar_addr: SocketAddrV4 = data.header.radar_addr.into(); + + let radar_data: SocketAddrV4 = data.a.data.into(); + let radar_report: SocketAddrV4 = data.a.report.into(); + let radar_send: SocketAddrV4 = data.a.send.into(); + let location_info: RadarInfo = RadarInfo::new( + LocatorId::Gen3Plus, + "Furuno", + Some(serial_no), + None, + 16, + FURUNO_SPOKES, + FURUNO_SPOKE_LEN, + radar_addr.into(), + via.clone(), + radar_data.into(), + radar_report.into(), + radar_send.into(), + FurunoControls::new(None), + ); + found(location_info, radars, subsys); + } + } + Err(e) => { + error!( + "{} via {}: Failed to decode single range data: {}", + from, via, e + ); + } + } */ + + Ok(()) +} + +struct FurunoLocator {} + +#[async_trait] +impl RadarLocator for FurunoLocator { + fn update_listen_addresses(&self, addresses: &mut Vec) { + if !addresses + .iter() + .any(|i| i.id == LocatorId::Furuno && i.brand == "Furuno Beacon") + { + addresses.push(RadarListenAddress::new( + LocatorId::Furuno, + &FURUNO_BEACON_ADDRESS, + "Furuno Beacon", + None, + &process_locator_report, + )); + } + } +} + +pub fn create_locator() -> Box { + let locator = FurunoLocator {}; + Box::new(locator) +} diff --git a/src/furuno/settings.rs b/src/furuno/settings.rs new file mode 100644 index 0000000..d795bb8 --- /dev/null +++ b/src/furuno/settings.rs @@ -0,0 +1,210 @@ +use std::collections::HashMap; + +use crate::{ + radar::RadarInfo, + settings::{AutomaticValue, Control, ControlType, Controls}, +}; + +pub fn new() -> Controls { + let mut controls = HashMap::new(); + + controls.insert( + ControlType::UserName, + Control::new_string(ControlType::UserName).read_only(false), + ); + + controls.insert( + ControlType::AntennaHeight, + Control::new_numeric(ControlType::AntennaHeight, 0, 9900) + .wire_scale_factor(99000) // we report cm but network has mm + .unit("cm"), + ); + controls.insert( + ControlType::BearingAlignment, + Control::new_numeric(ControlType::BearingAlignment, -180, 180) + .unit("Deg") + .wire_scale_factor(1800) + .wire_offset(-1), + ); + controls.insert( + ControlType::Gain, + Control::new_auto( + ControlType::Gain, + 0, + 100, + AutomaticValue { + has_auto: true, + //auto_values: 1, + //auto_descriptions: None, + has_auto_adjustable: false, + auto_adjust_min_value: 0, + auto_adjust_max_value: 0, + }, + ) + .wire_scale_factor(255), + ); + controls.insert( + ControlType::Sea, + Control::new_auto( + ControlType::Sea, + 0, + 100, + AutomaticValue { + has_auto: true, + //auto_values: 1, + //auto_descriptions: None, + has_auto_adjustable: false, + auto_adjust_min_value: 0, + auto_adjust_max_value: 0, + }, + ) + .wire_scale_factor(255), + ); + controls.insert( + ControlType::Rain, + Control::new_auto( + ControlType::Rain, + 0, + 100, + AutomaticValue { + has_auto: true, + //auto_values: 1, + //auto_descriptions: None, + has_auto_adjustable: false, + auto_adjust_min_value: 0, + auto_adjust_max_value: 0, + }, + ) + .wire_scale_factor(255), + ); + controls.insert( + ControlType::TargetBoost, + Control::new_list(ControlType::TargetBoost, &["Off", "Low", "High"]), + ); + + controls.insert( + ControlType::OperatingHours, + Control::new_numeric(ControlType::OperatingHours, 0, i32::MAX) + .read_only(true) + .unit("h"), + ); + + controls.insert( + ControlType::RotationSpeed, + Control::new_numeric(ControlType::RotationSpeed, 0, 990) + .read_only(true) + .unit("dRPM"), + ); + + controls.insert( + ControlType::FirmwareVersion, + Control::new_string(ControlType::FirmwareVersion), + ); + + let mut control = Control::new_list( + ControlType::Status, + &["Off", "Standby", "Transmit", "", "", "SpinningUp"], + ) + .send_always(); + control.set_valid_values([1, 2].to_vec()); + + controls.insert(ControlType::Status, control); + + controls.insert( + ControlType::SideLobeSuppression, + Control::new_auto( + ControlType::SideLobeSuppression, + 0, + 100, + AutomaticValue { + has_auto: true, + //auto_values: 1, + //auto_descriptions: None, + has_auto_adjustable: false, + auto_adjust_min_value: 0, + auto_adjust_max_value: 0, + }, + ) + .wire_scale_factor(255), + ); + + Controls::new_base(controls) +} + +pub fn update_when_model_known(controls: &mut Controls, radar_info: &RadarInfo) { + controls.set_model_name("Furuno".to_string()); + + let mut control = Control::new_string(ControlType::SerialNumber); + if let Some(serial_number) = radar_info.serial_no.as_ref() { + control.set_string(serial_number.to_string()); + } + controls.insert(ControlType::SerialNumber, control); + + // Update the UserName; it had to be present at start so it could be loaded from + // config. Override it if it is still the 'Furuno ... ' name. + if radar_info.user_name() == radar_info.key() { + let mut user_name = "Furuno".to_string(); + if radar_info.serial_no.is_some() { + let mut serial = radar_info.serial_no.clone().unwrap(); + + user_name.push(' '); + user_name.push_str(&serial.split_off(7)); + } + if radar_info.which.is_some() { + user_name.push(' '); + user_name.push_str(&radar_info.which.as_ref().unwrap()); + } + controls.set_user_name(user_name); + } + + let max_value = 48 * 1852; + let mut range_control = Control::new_numeric(ControlType::Range, 0, max_value) + .unit("m") + .wire_scale_factor(10 * max_value); // Radar sends and receives in decimeters + if let Some(range_detection) = &radar_info.range_detection { + if range_detection.complete { + range_control.set_valid_values(range_detection.ranges.clone()); + } + }; + controls.insert(ControlType::Range, range_control); + + controls.insert( + ControlType::NoTransmitStart1, + Control::new_numeric(ControlType::NoTransmitStart1, -180, 180) + .wire_scale_factor(1800) + .wire_offset(-1), + ); + controls.insert( + ControlType::NoTransmitEnd1, + Control::new_numeric(ControlType::NoTransmitEnd1, -180, 180) + .unit("Deg") + .wire_scale_factor(1800) + .wire_offset(-1), + ); + + controls.insert( + ControlType::ScanSpeed, + Control::new_list(ControlType::ScanSpeed, &["Normal", "Fast"]), + ); + controls.insert( + ControlType::TargetExpansion, + Control::new_list(ControlType::TargetExpansion, &["Off", "On"]), + ); + controls.insert( + ControlType::TargetSeparation, + Control::new_list( + ControlType::TargetSeparation, + &["Off", "Low", "Medium", "High"], + ), + ); + controls.insert( + ControlType::Doppler, + Control::new_list(ControlType::Doppler, &["Off", "Normal", "Approaching"]), + ); + controls.insert( + ControlType::DopplerSpeedThreshold, + Control::new_numeric(ControlType::DopplerSpeedThreshold, 0, 1594) + .wire_scale_factor(1594 * 16) + .unit("cm/s"), + ); +} diff --git a/src/locator.rs b/src/locator.rs index 087e57e..008ef7c 100644 --- a/src/locator.rs +++ b/src/locator.rs @@ -23,7 +23,7 @@ use tokio::time::sleep; use tokio_graceful_shutdown::SubsystemHandle; use crate::radar::{RadarError, SharedRadars}; -use crate::{navico, util, Cli}; +use crate::{furuno, navico, util, Cli}; const LOCATOR_PACKET_BUFFER_LEN: usize = 300; // Long enough for any location packet @@ -31,6 +31,7 @@ const LOCATOR_PACKET_BUFFER_LEN: usize = 300; // Long enough for any location pa pub enum LocatorId { GenBR24, Gen3Plus, + Furuno, } impl LocatorId { @@ -39,6 +40,7 @@ impl LocatorId { match *self { GenBR24 => "Navico BR24", Gen3Plus => "Navico 3G/4G/HALO", + Furuno => "Furuno DRSxxxx", } } } @@ -126,14 +128,7 @@ impl Locator { pub async fn run(self, subsys: SubsystemHandle) -> Result<(), RadarError> { let radars = &self.radars; - let navico_locator = navico::create_locator(); - let navico_br24_locator = navico::create_br24_locator(); - //let mut garmin_locator = garmin::create_locator(); - let mut listen_addresses: Vec = Vec::new(); - navico_locator.update_listen_addresses(&mut listen_addresses); - navico_br24_locator.update_listen_addresses(&mut listen_addresses); - // garmin_locator.update_listen_addresses(&listen_addresses); info!("Entering loop, listening for radars"); let mut interface_state = InterfaceState { @@ -144,11 +139,33 @@ impl Locator { first_loop: true, }; + #[cfg(feature = "navico")] + if interface_state + .args + .brand + .as_ref() + .unwrap_or(&"navico".to_owned()) + .eq_ignore_ascii_case("navico") + { + navico::create_locator().update_listen_addresses(&mut listen_addresses); + navico::create_br24_locator().update_listen_addresses(&mut listen_addresses); + } + #[cfg(feature = "furuno")] + if interface_state + .args + .brand + .as_ref() + .unwrap_or(&"furuno".to_owned()) + .eq_ignore_ascii_case("furuno") + { + furuno::create_locator().update_listen_addresses(&mut listen_addresses); + } + loop { let cancellation_token = subsys.create_cancellation_token(); // create a list of sockets for all listen addresses - let sockets = create_multicast_sockets(&listen_addresses, &mut interface_state); + let sockets = create_listen_sockets(&listen_addresses, &mut interface_state); let mut set = JoinSet::new(); if sockets.is_err() { if interface_state.args.interface.is_some() { @@ -172,7 +189,7 @@ impl Locator { }); // Now that we're listening to the radars, send any address request (wake) packets - { + if !interface_state.args.replay { for x in &listen_addresses { if let Some(address_request) = x.adress_request_packet { send_multicast_packet(&x.address, address_request); @@ -264,7 +281,7 @@ fn send_multicast_packet(addr: &SocketAddr, msg: &[u8]) { } } -fn create_multicast_sockets( +fn create_listen_sockets( listen_addresses: &Vec, interface_state: &mut InterfaceState, ) -> Result, RadarError> { @@ -278,7 +295,9 @@ fn create_multicast_sockets( if only_interface.is_none() || only_interface.as_ref() == Some(&itf.name) { for nic_addr in itf.addr { - if let IpAddr::V4(nic_ip) = nic_addr.ip() { + if let (IpAddr::V4(nic_ip), Some(IpAddr::V4(nic_netmask))) = + (nic_addr.ip(), nic_addr.netmask()) + { if !nic_ip.is_loopback() || only_interface.is_some() { if interface_state.lost_nic_names.contains_key(&itf.name) || !interface_state.active_nic_addresses.contains(&nic_ip) @@ -299,7 +318,7 @@ fn create_multicast_sockets( "Interface '{}' now listening on IP address {}/{} for radars", itf.name, &nic_ip, - nic_addr.netmask().unwrap() + &nic_netmask, ); } interface_state.active_nic_addresses.push(nic_ip.clone()); @@ -310,7 +329,25 @@ fn create_multicast_sockets( if let SocketAddr::V4(listen_addr) = radar_listen_address.address { - let socket = util::create_multicast(&listen_addr, &nic_ip); + if !listen_addr.ip().is_multicast() { + log::info!("{} is not multicast", listen_addr.ip()); + if !util::match_ipv4( + &nic_ip, + listen_addr.ip(), + &nic_netmask, + ) { + log::info!( + "{}/{} does not match bcast {}", + nic_ip, + nic_netmask, + listen_addr.ip() + ); + continue; + } + } + + let socket = + util::create_listen_socket(&listen_addr, &nic_ip); match socket { Ok(socket) => { sockets.push(LocatorInfo { @@ -319,13 +356,13 @@ fn create_multicast_sockets( process: radar_listen_address.process, }); debug!( - "Listening on '{}' address {} for multicast address {}", - itf.name, nic_ip, listen_addr, - ); + "Listening on '{}' address {} for address {}", + itf.name, nic_ip, listen_addr, + ); } Err(e) => { warn!( - "Cannot listen on '{}' address {} for multicast address {}: {}", + "Cannot listen on '{}' address {} for address {}: {}", itf.name, nic_ip, listen_addr, e ); } diff --git a/src/main.rs b/src/main.rs index a7b28a8..a0ba264 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,7 +12,10 @@ use web::Web; mod config; // mod garmin; +#[cfg(feature = "furuno")] +mod furuno; mod locator; +#[cfg(feature = "navico")] mod navico; mod protos; mod radar; @@ -36,6 +39,10 @@ pub struct Cli { #[arg(short, long)] interface: Option, + /// Limit radar location to a single brand + #[arg(short, long)] + brand: Option, + /// Write RadarMessage data to stdout #[arg(long, default_value_t = false)] output: bool, diff --git a/src/navico/data.rs b/src/navico/data.rs index 8b0ee58..f93548c 100644 --- a/src/navico/data.rs +++ b/src/navico/data.rs @@ -11,12 +11,12 @@ use tokio::time::sleep; use tokio_graceful_shutdown::SubsystemHandle; use trail::TrailBuffer; -use crate::locator::LocatorId; +use crate::locator::{Locator, LocatorId}; use crate::navico::NAVICO_SPOKE_LEN; use crate::protos::RadarMessage::radar_message::Spoke; use crate::protos::RadarMessage::RadarMessage; use crate::radar::*; -use crate::util::{create_multicast, PrintableSpoke}; +use crate::util::{create_listen_socket, PrintableSpoke}; use super::{ DataUpdate, NAVICO_SPOKES, NAVICO_SPOKES_RAW, RADAR_LINE_DATA_LENGTH, SPOKES_PER_FRAME, @@ -147,7 +147,7 @@ impl NavicoDataReceiver { } async fn start_socket(&mut self) -> io::Result<()> { - match create_multicast(&self.info.spoke_data_addr, &self.info.nic_addr) { + match create_listen_socket(&self.info.spoke_data_addr, &self.info.nic_addr) { Ok(sock) => { self.sock = Some(sock); debug!( @@ -360,6 +360,9 @@ impl NavicoDataReceiver { return None; } }, + LocatorId::Furuno => { + panic!("Furuno is not Navico"); + } } } diff --git a/src/navico/mod.rs b/src/navico/mod.rs index c2c0649..8ff0f33 100644 --- a/src/navico/mod.rs +++ b/src/navico/mod.rs @@ -3,7 +3,6 @@ use bincode::deserialize; use enum_primitive_derive::Primitive; use log::{debug, error, log_enabled, trace}; use serde::Deserialize; -use settings::NavicoControls; use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4}; use std::{fmt, io}; use tokio::sync::mpsc; @@ -220,7 +219,7 @@ fn found(mut info: RadarInfo, radars: &SharedRadars, subsys: &SubsystemHandle) { }; if model != Model::Unknown { let info2 = info.clone(); - info.controls.update_when_model_known(model, &info2); + settings::update_when_model_known(&mut info.controls, model, &info2); info.set_legend(model == Model::HALO); radars.update(&info); } @@ -327,7 +326,7 @@ fn process_beacon_report( radar_data.into(), radar_report.into(), radar_send.into(), - NavicoControls::new(None), + settings::new(None), ); found(location_info, radars, subsys); @@ -347,7 +346,7 @@ fn process_beacon_report( radar_data.into(), radar_report.into(), radar_send.into(), - NavicoControls::new(None), + settings::new(None), ); found(location_info, radars, subsys); } @@ -381,7 +380,7 @@ fn process_beacon_report( radar_data.into(), radar_report.into(), radar_send.into(), - NavicoControls::new(None), + settings::new(None), ); found(location_info, radars, subsys); } @@ -415,7 +414,7 @@ fn process_beacon_report( radar_data.into(), radar_report.into(), radar_send.into(), - NavicoControls::new(Some(BR24_MODEL_NAME)), + settings::new(Some(BR24_MODEL_NAME)), ); found(location_info, radars, subsys); } diff --git a/src/navico/report.rs b/src/navico/report.rs index 61daf8e..46b4da0 100644 --- a/src/navico/report.rs +++ b/src/navico/report.rs @@ -12,7 +12,7 @@ use tokio_graceful_shutdown::SubsystemHandle; use crate::radar::{DopplerMode, RadarError, RadarInfo, RangeDetection, SharedRadars}; use crate::settings::{ControlMessage, ControlState, ControlType, ControlValue}; -use crate::util::{c_string, c_wide_string, create_multicast}; +use crate::util::{c_string, c_wide_string, create_listen_socket}; use super::command::Command; use super::{DataUpdate, Model}; @@ -297,7 +297,7 @@ impl NavicoReportReceiver { } async fn start_socket(&mut self) -> io::Result<()> { - match create_multicast(&self.info.report_addr, &self.info.nic_addr) { + match create_listen_socket(&self.info.report_addr, &self.info.nic_addr) { Ok(sock) => { self.sock = Some(sock); debug!( @@ -801,7 +801,11 @@ impl NavicoReportReceiver { info!("{}: Radar is model {}", self.key, model); let info2 = self.info.clone(); self.model = model; - self.info.controls.update_when_model_known(model, &info2); + super::settings::update_when_model_known( + &mut self.info.controls, + model, + &info2, + ); self.info.set_legend(model == Model::HALO); self.radars.update(&self.info); diff --git a/src/navico/settings.rs b/src/navico/settings.rs index d45ccb4..e0bf034 100644 --- a/src/navico/settings.rs +++ b/src/navico/settings.rs @@ -7,107 +7,258 @@ use crate::{ use super::Model; -pub type NavicoControls = Controls; +pub fn new(model: Option<&str>) -> Controls { + let mut controls = HashMap::new(); -impl NavicoControls { - pub fn new(model: Option<&str>) -> Self { - let mut controls = HashMap::new(); + controls.insert( + ControlType::UserName, + Control::new_string(ControlType::UserName).read_only(false), + ); + let mut control = Control::new_string(ControlType::ModelName); + if model.is_some() { + control.set_string(model.unwrap().to_string()); + } + controls.insert(ControlType::ModelName, control); + + controls.insert( + ControlType::AntennaHeight, + Control::new_numeric(ControlType::AntennaHeight, 0, 9900) + .wire_scale_factor(99000) // we report cm but network has mm + .unit("cm"), + ); + controls.insert( + ControlType::BearingAlignment, + Control::new_numeric(ControlType::BearingAlignment, -180, 180) + .unit("Deg") + .wire_scale_factor(1800) + .wire_offset(-1), + ); + controls.insert( + ControlType::Gain, + Control::new_auto( + ControlType::Gain, + 0, + 100, + AutomaticValue { + has_auto: true, + //auto_values: 1, + //auto_descriptions: None, + has_auto_adjustable: false, + auto_adjust_min_value: 0, + auto_adjust_max_value: 0, + }, + ) + .wire_scale_factor(255), + ); + controls.insert( + ControlType::InterferenceRejection, + Control::new_list( + ControlType::InterferenceRejection, + &["Off", "Low", "Medium", "High"], + ), + ); + controls.insert( + ControlType::LocalInterferenceRejection, + Control::new_list( + ControlType::LocalInterferenceRejection, + &["Off", "Low", "Medium", "High"], + ), + ); + controls.insert( + ControlType::Rain, + Control::new_numeric(ControlType::Rain, 0, 100).wire_scale_factor(255), + ); + controls.insert( + ControlType::TargetBoost, + Control::new_list(ControlType::TargetBoost, &["Off", "Low", "High"]), + ); + + controls.insert( + ControlType::OperatingHours, + Control::new_numeric(ControlType::OperatingHours, 0, i32::MAX) + .read_only(true) + .unit("h"), + ); + + controls.insert( + ControlType::RotationSpeed, + Control::new_numeric(ControlType::RotationSpeed, 0, 990) + .read_only(true) + .unit("dRPM"), + ); + + controls.insert( + ControlType::FirmwareVersion, + Control::new_string(ControlType::FirmwareVersion), + ); + + let mut control = Control::new_list( + ControlType::Status, + &["Off", "Standby", "Transmit", "", "", "SpinningUp"], + ) + .send_always(); + control.set_valid_values([1, 2].to_vec()); + + controls.insert(ControlType::Status, control); + + controls.insert( + ControlType::SideLobeSuppression, + Control::new_auto( + ControlType::SideLobeSuppression, + 0, + 100, + AutomaticValue { + has_auto: true, + //auto_values: 1, + //auto_descriptions: None, + has_auto_adjustable: false, + auto_adjust_min_value: 0, + auto_adjust_max_value: 0, + }, + ) + .wire_scale_factor(255), + ); + + Controls::new_base(controls) +} + +pub fn update_when_model_known(controls: &mut Controls, model: Model, radar_info: &RadarInfo) { + controls.set_model_name(model.to_string()); + let mut control = Control::new_string(ControlType::SerialNumber); + if let Some(serial_number) = radar_info.serial_no.as_ref() { + control.set_string(serial_number.to_string()); + } + controls.insert(ControlType::SerialNumber, control); + + // Update the UserName; it had to be present at start so it could be loaded from + // config. Override it if it is still the 'Navico ... ' name. + if radar_info.user_name() == radar_info.key() { + let mut user_name = model.to_string(); + if radar_info.serial_no.is_some() { + let mut serial = radar_info.serial_no.clone().unwrap(); + + user_name.push(' '); + user_name.push_str(&serial.split_off(7)); + } + if radar_info.which.is_some() { + user_name.push(' '); + user_name.push_str(&radar_info.which.as_ref().unwrap()); + } + controls.set_user_name(user_name); + } + + let max_value = (match model { + Model::Unknown => 0, + Model::BR24 => 24, + Model::Gen3 => 36, + Model::Gen4 => 48, + Model::HALO => 96, + }) * 1852; + let mut range_control = Control::new_numeric(ControlType::Range, 0, max_value) + .unit("m") + .wire_scale_factor(10 * max_value); // Radar sends and receives in decimeters + if let Some(range_detection) = &radar_info.range_detection { + if range_detection.complete { + range_control.set_valid_values(range_detection.ranges.clone()); + } + }; + controls.insert(ControlType::Range, range_control); + + if model == Model::HALO { controls.insert( - ControlType::UserName, - Control::new_string(ControlType::UserName).read_only(false), + ControlType::Mode, + Control::new_list( + ControlType::Mode, + &["Custom", "Harbor", "Offshore", "Unknown", "Weather", "Bird"], + ), + ); + controls.insert( + ControlType::AccentLight, + Control::new_list(ControlType::AccentLight, &["Off", "Low", "Medium", "High"]), ); - let mut control = Control::new_string(ControlType::ModelName); - if model.is_some() { - control.set_string(model.unwrap().to_string()); - } - controls.insert(ControlType::ModelName, control); controls.insert( - ControlType::AntennaHeight, - Control::new_numeric(ControlType::AntennaHeight, 0, 9900) - .wire_scale_factor(99000) // we report cm but network has mm - .unit("cm"), + ControlType::NoTransmitStart1, + Control::new_numeric(ControlType::NoTransmitStart1, -180, 180) + .wire_scale_factor(1800) + .wire_offset(-1), ); controls.insert( - ControlType::BearingAlignment, - Control::new_numeric(ControlType::BearingAlignment, -180, 180) - .unit("Deg") + ControlType::NoTransmitStart2, + Control::new_numeric(ControlType::NoTransmitStart2, -180, 180) .wire_scale_factor(1800) .wire_offset(-1), ); controls.insert( - ControlType::Gain, - Control::new_auto( - ControlType::Gain, - 0, - 100, - AutomaticValue { - has_auto: true, - //auto_values: 1, - //auto_descriptions: None, - has_auto_adjustable: false, - auto_adjust_min_value: 0, - auto_adjust_max_value: 0, - }, - ) - .wire_scale_factor(255), + ControlType::NoTransmitStart3, + Control::new_numeric(ControlType::NoTransmitStart3, -180, 180) + .wire_scale_factor(1800) + .wire_offset(-1), ); controls.insert( - ControlType::InterferenceRejection, - Control::new_list( - ControlType::InterferenceRejection, - &["Off", "Low", "Medium", "High"], - ), + ControlType::NoTransmitStart4, + Control::new_numeric(ControlType::NoTransmitStart4, -180, 180) + .wire_scale_factor(1800) + .wire_offset(-1), ); controls.insert( - ControlType::LocalInterferenceRejection, - Control::new_list( - ControlType::LocalInterferenceRejection, - &["Off", "Low", "Medium", "High"], - ), + ControlType::NoTransmitEnd1, + Control::new_numeric(ControlType::NoTransmitEnd1, -180, 180) + .unit("Deg") + .wire_scale_factor(1800) + .wire_offset(-1), ); controls.insert( - ControlType::Rain, - Control::new_numeric(ControlType::Rain, 0, 100).wire_scale_factor(255), + ControlType::NoTransmitEnd2, + Control::new_numeric(ControlType::NoTransmitEnd2, -180, 180) + .unit("Deg") + .wire_scale_factor(1800) + .wire_offset(-1), ); controls.insert( - ControlType::TargetBoost, - Control::new_list(ControlType::TargetBoost, &["Off", "Low", "High"]), + ControlType::NoTransmitEnd3, + Control::new_numeric(ControlType::NoTransmitEnd3, -180, 180) + .unit("Deg") + .wire_scale_factor(1800) + .wire_offset(-1), ); - controls.insert( - ControlType::OperatingHours, - Control::new_numeric(ControlType::OperatingHours, 0, i32::MAX) - .read_only(true) - .unit("h"), + ControlType::NoTransmitEnd4, + Control::new_numeric(ControlType::NoTransmitEnd4, -180, 180) + .unit("Deg") + .wire_scale_factor(1800) + .wire_offset(-1), ); controls.insert( - ControlType::RotationSpeed, - Control::new_numeric(ControlType::RotationSpeed, 0, 990) - .read_only(true) - .unit("dRPM"), + // TODO: Investigate mapping on 4G + ControlType::SeaState, + Control::new_list(ControlType::SeaState, &["Calm", "Moderate", "Rough"]), ); controls.insert( - ControlType::FirmwareVersion, - Control::new_string(ControlType::FirmwareVersion), + ControlType::Sea, + Control::new_auto( + ControlType::Sea, + 0, + 100, + AutomaticValue { + has_auto: true, + //auto_values: 100, + //auto_descriptions: None, + has_auto_adjustable: true, + auto_adjust_min_value: -50, + auto_adjust_max_value: 50, + }, + ) + .wire_scale_factor(255), ); - - let mut control = Control::new_list( - ControlType::Status, - &["Off", "Standby", "Transmit", "", "", "SpinningUp"], - ) - .send_always(); - control.set_valid_values([1, 2].to_vec()); - - controls.insert(ControlType::Status, control); - + } else { controls.insert( - ControlType::SideLobeSuppression, + ControlType::Sea, Control::new_auto( - ControlType::SideLobeSuppression, + ControlType::Sea, 0, 100, AutomaticValue { @@ -121,229 +272,72 @@ impl NavicoControls { ) .wire_scale_factor(255), ); - - Controls::new_base(controls) } - pub fn update_when_model_known(&mut self, model: Model, radar_info: &RadarInfo) { - let controls = self; - - controls.set_model_name(model.to_string()); - - let mut control = Control::new_string(ControlType::SerialNumber); - if let Some(serial_number) = radar_info.serial_no.as_ref() { - control.set_string(serial_number.to_string()); - } - controls.insert(ControlType::SerialNumber, control); - - // Update the UserName; it had to be present at start so it could be loaded from - // config. Override it if it is still the 'Navico ... ' name. - if radar_info.user_name() == radar_info.key() { - let mut user_name = model.to_string(); - if radar_info.serial_no.is_some() { - let mut serial = radar_info.serial_no.clone().unwrap(); - - user_name.push(' '); - user_name.push_str(&serial.split_off(7)); - } - if radar_info.which.is_some() { - user_name.push(' '); - user_name.push_str(&radar_info.which.as_ref().unwrap()); - } - controls.set_user_name(user_name); - } - - let max_value = (match model { - Model::Unknown => 0, - Model::BR24 => 24, - Model::Gen3 => 36, - Model::Gen4 => 48, - Model::HALO => 96, - }) * 1852; - let mut range_control = Control::new_numeric(ControlType::Range, 0, max_value) - .unit("m") - .wire_scale_factor(10 * max_value); // Radar sends and receives in decimeters - if let Some(range_detection) = &radar_info.range_detection { - if range_detection.complete { - range_control.set_valid_values(range_detection.ranges.clone()); - } - }; - controls.insert(ControlType::Range, range_control); - - if model == Model::HALO { - controls.insert( - ControlType::Mode, - Control::new_list( - ControlType::Mode, - &["Custom", "Harbor", "Offshore", "Unknown", "Weather", "Bird"], - ), - ); - controls.insert( - ControlType::AccentLight, - Control::new_list(ControlType::AccentLight, &["Off", "Low", "Medium", "High"]), - ); - - controls.insert( - ControlType::NoTransmitStart1, - Control::new_numeric(ControlType::NoTransmitStart1, -180, 180) - .wire_scale_factor(1800) - .wire_offset(-1), - ); - controls.insert( - ControlType::NoTransmitStart2, - Control::new_numeric(ControlType::NoTransmitStart2, -180, 180) - .wire_scale_factor(1800) - .wire_offset(-1), - ); - controls.insert( - ControlType::NoTransmitStart3, - Control::new_numeric(ControlType::NoTransmitStart3, -180, 180) - .wire_scale_factor(1800) - .wire_offset(-1), - ); - controls.insert( - ControlType::NoTransmitStart4, - Control::new_numeric(ControlType::NoTransmitStart4, -180, 180) - .wire_scale_factor(1800) - .wire_offset(-1), - ); - controls.insert( - ControlType::NoTransmitEnd1, - Control::new_numeric(ControlType::NoTransmitEnd1, -180, 180) - .unit("Deg") - .wire_scale_factor(1800) - .wire_offset(-1), - ); - controls.insert( - ControlType::NoTransmitEnd2, - Control::new_numeric(ControlType::NoTransmitEnd2, -180, 180) - .unit("Deg") - .wire_scale_factor(1800) - .wire_offset(-1), - ); - controls.insert( - ControlType::NoTransmitEnd3, - Control::new_numeric(ControlType::NoTransmitEnd3, -180, 180) - .unit("Deg") - .wire_scale_factor(1800) - .wire_offset(-1), - ); - controls.insert( - ControlType::NoTransmitEnd4, - Control::new_numeric(ControlType::NoTransmitEnd4, -180, 180) - .unit("Deg") - .wire_scale_factor(1800) - .wire_offset(-1), - ); - - controls.insert( - // TODO: Investigate mapping on 4G - ControlType::SeaState, - Control::new_list(ControlType::SeaState, &["Calm", "Moderate", "Rough"]), - ); - - controls.insert( - ControlType::Sea, - Control::new_auto( - ControlType::Sea, - 0, - 100, - AutomaticValue { - has_auto: true, - //auto_values: 100, - //auto_descriptions: None, - has_auto_adjustable: true, - auto_adjust_min_value: -50, - auto_adjust_max_value: 50, - }, - ) - .wire_scale_factor(255), - ); - } else { - controls.insert( - ControlType::Sea, - Control::new_auto( - ControlType::Sea, - 0, - 100, - AutomaticValue { - has_auto: true, - //auto_values: 1, - //auto_descriptions: None, - has_auto_adjustable: false, - auto_adjust_min_value: 0, - auto_adjust_max_value: 0, - }, - ) - .wire_scale_factor(255), - ); - } - - controls.insert( + controls.insert( + ControlType::ScanSpeed, + Control::new_list( ControlType::ScanSpeed, - Control::new_list( - ControlType::ScanSpeed, - if model == Model::HALO { - &["Normal", "Medium", "", "Fast"] - } else { - &["Normal", "Fast"] - }, - ), - ); - controls.insert( + if model == Model::HALO { + &["Normal", "Medium", "", "Fast"] + } else { + &["Normal", "Fast"] + }, + ), + ); + controls.insert( + ControlType::TargetExpansion, + Control::new_list( ControlType::TargetExpansion, + if model == Model::HALO { + &["Off", "Low", "Medium", "High"] + } else { + &["Off", "On"] + }, + ), + ); + controls.insert( + ControlType::NoiseRejection, + Control::new_list( + ControlType::NoiseRejection, + if model == Model::HALO { + &["Off", "Low", "Medium", "High"] + } else { + &["Off", "Low", "High"] + }, + ), + ); + if model == Model::HALO || model == Model::Gen4 { + controls.insert( + ControlType::TargetSeparation, Control::new_list( - ControlType::TargetExpansion, - if model == Model::HALO { - &["Off", "Low", "Medium", "High"] - } else { - &["Off", "On"] - }, + ControlType::TargetSeparation, + &["Off", "Low", "Medium", "High"], ), ); + } + if model == Model::HALO { controls.insert( - ControlType::NoiseRejection, - Control::new_list( - ControlType::NoiseRejection, - if model == Model::HALO { - &["Off", "Low", "Medium", "High"] - } else { - &["Off", "Low", "High"] - }, - ), + ControlType::Doppler, + Control::new_list(ControlType::Doppler, &["Off", "Normal", "Approaching"]), ); - if model == Model::HALO || model == Model::Gen4 { - controls.insert( - ControlType::TargetSeparation, - Control::new_list( - ControlType::TargetSeparation, - &["Off", "Low", "Medium", "High"], - ), - ); - } - if model == Model::HALO { - controls.insert( - ControlType::Doppler, - Control::new_list(ControlType::Doppler, &["Off", "Normal", "Approaching"]), - ); - controls.insert( - ControlType::DopplerSpeedThreshold, - Control::new_numeric(ControlType::DopplerSpeedThreshold, 0, 1594) - .wire_scale_factor(1594 * 16) - .unit("cm/s"), - ); - } - controls.insert( - ControlType::NoiseRejection, - Control::new_list( - ControlType::NoiseRejection, - if model == Model::HALO { - &["Off", "Low", "Medium", "High"] - } else { - &["Off", "Low", "High"] - }, - ), + ControlType::DopplerSpeedThreshold, + Control::new_numeric(ControlType::DopplerSpeedThreshold, 0, 1594) + .wire_scale_factor(1594 * 16) + .unit("cm/s"), ); } + + controls.insert( + ControlType::NoiseRejection, + Control::new_list( + ControlType::NoiseRejection, + if model == Model::HALO { + &["Off", "Low", "Medium", "High"] + } else { + &["Off", "Low", "High"] + }, + ), + ); } diff --git a/src/signalk.rs b/src/signalk.rs index d62b33a..7a7343f 100644 --- a/src/signalk.rs +++ b/src/signalk.rs @@ -10,8 +10,8 @@ use std::{ pin::Pin, sync::atomic::{AtomicBool, Ordering}, }; -use tokio::io::AsyncBufReadExt; use tokio::io::BufReader; +use tokio::io::{AsyncBufReadExt, BufWriter}; use tokio::{io::AsyncWriteExt, net::TcpStream}; use tokio_graceful_shutdown::SubsystemHandle; @@ -117,25 +117,25 @@ impl NavigationData { // or Ok if we are to shutdown. async fn receive_loop( &self, - stream: TcpStream, + mut stream: TcpStream, subsys: &SubsystemHandle, ) -> Result<(), RadarError> { - let mut buffered = BufReader::new(stream); + let (read_half, write_half) = stream.split(); + let mut writer = BufWriter::new(write_half); + let mut lines = BufReader::new(read_half).lines(); loop { - let mut line = String::new(); - tokio::select! { biased; _ = subsys.on_shutdown_requested() => { log::info!("SK receive_loop done"); return Ok(()); }, - r = buffered.read_line(&mut line) => { + r = lines.next_line() => { match r { - Ok(_) => { + Ok(Some(line)) => { log::trace!("SK <- {}", line); if line.starts_with("{\"name\":") { - self.send_subscription(&mut buffered).await?; + self.send_subscription(&mut writer).await?; log::trace!("SK -> {}", SUBSCRIBE); } else { @@ -145,6 +145,9 @@ impl NavigationData { } } } + Ok(None) => { + return Ok(()); + } Err(ref e) if e.kind() == ErrorKind::WouldBlock => { continue; } @@ -157,7 +160,10 @@ impl NavigationData { } } - async fn send_subscription(&self, stream: &mut BufReader) -> Result<(), RadarError> { + async fn send_subscription( + &self, + stream: &mut BufWriter>, + ) -> Result<(), RadarError> { let bytes: &[u8] = SUBSCRIBE.as_bytes(); stream.write_all(bytes).await.map_err(|e| RadarError::Io(e)) diff --git a/src/util.rs b/src/util.rs index 6fe5ae7..9b05745 100644 --- a/src/util.rs +++ b/src/util.rs @@ -174,10 +174,49 @@ fn bind_to_multicast( Ok(()) } -pub fn create_multicast(addr: &SocketAddrV4, nic_addr: &Ipv4Addr) -> io::Result { +/// On Windows, unlike all Unix variants, it is improper to bind to the multicast address +/// +/// see https://msdn.microsoft.com/en-us/library/windows/desktop/ms737550(v=vs.85).aspx +#[cfg(windows)] +fn bind_to_broadcast( + socket: &socket2::Socket, + addr: &SocketAddrV4, + nic_addr: &Ipv4Addr, +) -> io::Result<()> { + let _ = socket.set_broadcast(true); + + let socketaddr = SocketAddr::new(IpAddr::V4(*nic_addr), addr.port()); + + socket.bind(&socket2::SockAddr::from(socketaddr))?; + log::info!("Binding broadcast socket to {}", socketaddr); + Ok(()) +} + +/// On unixes we bind to the multicast address, which causes multicast packets to be filtered +#[cfg(unix)] +fn bind_to_broadcast( + socket: &socket2::Socket, + addr: &SocketAddrV4, + nic_addr: &Ipv4Addr, +) -> io::Result<()> { + let _ = socket.set_broadcast(true); + let _ = nic_addr; // Not used on Linux + + let socketaddr = SocketAddr::new(IpAddr::V4(*addr.ip()), addr.port()); + + socket.bind(&socket2::SockAddr::from(socketaddr))?; + log::info!("Binding broadcast socket to {}", socketaddr); + Ok(()) +} + +pub fn create_listen_socket(addr: &SocketAddrV4, nic_addr: &Ipv4Addr) -> io::Result { let socket: socket2::Socket = new_socket()?; - bind_to_multicast(&socket, addr, nic_addr)?; + if addr.ip().is_multicast() { + bind_to_multicast(&socket, addr, nic_addr)?; + } else { + bind_to_broadcast(&socket, addr, nic_addr)?; + } let socket = UdpSocket::from_std(socket.into())?; Ok(socket) @@ -194,3 +233,9 @@ pub fn create_multicast_send(addr: &SocketAddrV4, nic_addr: &Ipv4Addr) -> io::Re let socket = UdpSocket::from_std(socket.into())?; Ok(socket) } + +pub fn match_ipv4(addr: &Ipv4Addr, bcast: &Ipv4Addr, netmask: &Ipv4Addr) -> bool { + let r = addr & netmask; + let b = bcast & netmask; + r == b +}