From 3565539a8fb01b02b59d96ebf469fca756d501ed Mon Sep 17 00:00:00 2001 From: Carlos Guerra Date: Sat, 28 Mar 2026 17:55:39 +0100 Subject: [PATCH 01/13] GPS feature webapp side: GPS mode selector, fixed mode lat/lon, API endpoint. Merging with Wifi client and webdav features --- daemon/src/config.rs | 52 +++++++++++++ daemon/src/gps.rs | 39 ++++++++++ daemon/src/lib.rs | 1 + daemon/src/main.rs | 17 +++++ daemon/src/server.rs | 3 + .../web/src/lib/components/ConfigForm.svelte | 73 +++++++++++++++++++ daemon/web/src/lib/utils.svelte.ts | 20 +++++ daemon/web/src/routes/+page.svelte | 51 ++++++++++++- 8 files changed, 255 insertions(+), 1 deletion(-) create mode 100644 daemon/src/gps.rs diff --git a/daemon/src/config.rs b/daemon/src/config.rs index 7f66af0f..43c93a7e 100644 --- a/daemon/src/config.rs +++ b/daemon/src/config.rs @@ -36,6 +36,12 @@ pub struct Config { pub min_space_to_start_recording_mb: u64, /// Minimum disk space required to continue a recording pub min_space_to_continue_recording_mb: u64, + /// GPS mode: 0=Disabled, 1=Fixed coordinates, 2=API endpoint + pub gps_mode: u8, + /// Fixed latitude used when gps_mode=1 + pub gps_fixed_latitude: Option, + /// Fixed longitude used when gps_mode=1 + pub gps_fixed_longitude: Option, /// Wifi client SSID pub wifi_ssid: Option, /// Wifi client password @@ -104,6 +110,9 @@ impl Default for Config { enabled_notifications: vec![NotificationType::Warning, NotificationType::LowBattery], min_space_to_start_recording_mb: 1, min_space_to_continue_recording_mb: 1, + gps_mode: 0, + gps_fixed_latitude: None, + gps_fixed_longitude: None, wifi_ssid: None, wifi_password: None, wifi_security: None, @@ -159,6 +168,49 @@ fn resolve_bin(name: &str) -> Option { None } +impl Config { + pub fn wifi_config(&self) -> wifi_station::WifiConfig { + let (wpa_bin, hostapd_conf, ctrl_interface) = match self.device { + Device::Tmobile | Device::Wingtech => ( + Some("/usr/sbin/wpa_supplicant".into()), + Some("/data/configs/hostapd.conf".into()), + None, + ), + Device::Uz801 => ( + Some("/system/bin/wpa_supplicant".into()), + Some("/data/misc/wifi/hostapd.conf".into()), + Some("/data/misc/wifi/sockets".into()), + ), + _ => (None, None, None), + }; + wifi_station::WifiConfig { + wifi_enabled: self.wifi_enabled, + dns_servers: self.dns_servers.clone(), + wifi_ssid: self.wifi_ssid.clone(), + wifi_password: self.wifi_password.clone(), + security_type: self.wifi_security, + wpa_supplicant_bin: wpa_bin.or_else(|| resolve_bin("wpa_supplicant")), + hostapd_conf, + ctrl_interface, + udhcpc_hook_path: Some("/data/rayhunter/udhcpc-hook.sh".into()), + dhcp_lease_path: Some("/data/rayhunter/dhcp_lease".into()), + wpa_conf_path: Some("/data/rayhunter/wpa_sta.conf".into()), + iw_bin: resolve_bin("iw"), + udhcpc_bin: resolve_bin("udhcpc"), + crash_log_dir: Some("/data/rayhunter/crash-logs".into()), + wakelock_name: Some("rayhunter".into()), + } + } +} + +fn resolve_bin(name: &str) -> Option { + let local = format!("/data/rayhunter/bin/{name}"); + if std::path::Path::new(&local).exists() { + return Some(local); + } + None +} + pub async fn parse_config

(path: P) -> Result where P: AsRef, diff --git a/daemon/src/gps.rs b/daemon/src/gps.rs new file mode 100644 index 00000000..c1f01c02 --- /dev/null +++ b/daemon/src/gps.rs @@ -0,0 +1,39 @@ +use axum::Json; +use axum::extract::State; +use axum::http::StatusCode; +use serde::{Deserialize, Serialize}; +use std::sync::Arc; + +use crate::server::ServerState; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct GpsData { + pub latitude: f64, + pub longitude: f64, + pub timestamp: String, +} + +pub async fn post_gps( + State(state): State>, + Json(gps_data): Json, +) -> Result { + if state.config.gps_mode != 2 { + return Err(( + StatusCode::FORBIDDEN, + "GPS API endpoint is disabled. Set gps_mode to 2 in configuration.".to_string(), + )); + } + let mut gps = state.gps_state.write().await; + *gps = Some(gps_data); + Ok(StatusCode::OK) +} + +pub async fn get_gps( + State(state): State>, +) -> Result, StatusCode> { + let gps = state.gps_state.read().await; + match gps.as_ref() { + Some(data) => Ok(Json(data.clone())), + None => Err(StatusCode::NOT_FOUND), + } +} diff --git a/daemon/src/lib.rs b/daemon/src/lib.rs index 18353792..ce0f83dd 100644 --- a/daemon/src/lib.rs +++ b/daemon/src/lib.rs @@ -6,6 +6,7 @@ pub mod diag; pub mod display; pub mod error; pub mod firewall; +pub mod gps; pub mod key_input; pub mod notifications; pub mod pcap; diff --git a/daemon/src/main.rs b/daemon/src/main.rs index 80cde339..9da023d5 100644 --- a/daemon/src/main.rs +++ b/daemon/src/main.rs @@ -6,6 +6,7 @@ mod diag; mod display; mod error; mod firewall; +mod gps; mod key_input; mod notifications; mod pcap; @@ -24,6 +25,7 @@ use crate::error::RayhunterError; use crate::notifications::{NotificationService, run_notification_worker}; use crate::pcap::get_pcap; use crate::qmdl_store::RecordingStore; +use crate::gps::{get_gps, post_gps}; use crate::server::{ ServerState, debug_set_display_state, get_config, get_qmdl, get_time, get_wifi_status, get_zip, scan_wifi, serve_static, set_config, set_time_offset, test_notification, @@ -79,6 +81,8 @@ fn get_router() -> AppRouter { .route("/api/time", get(get_time)) .route("/api/time-offset", post(set_time_offset)) .route("/api/debug/display-state", post(debug_set_display_state)) + .route("/api/gps", get(get_gps)) + .route("/api/gps", post(post_gps)) .route("/", get(|| async { Redirect::permanent("/index.html") })) .route("/{*path}", get(serve_static)) } @@ -298,6 +302,18 @@ async fn run_with_config( webdav_config.into(), ); } + let initial_gps = if config.gps_mode == 1 { + match (config.gps_fixed_latitude, config.gps_fixed_longitude) { + (Some(lat), Some(lon)) => Some(gps::GpsData { + latitude: lat, + longitude: lon, + timestamp: "fixed".to_string(), + }), + _ => None, + } + } else { + None + }; let state = Arc::new(ServerState { config_path: args.config_path.clone(), @@ -310,6 +326,7 @@ async fn run_with_config( ui_update_sender: Some(ui_update_tx), wifi_status, wifi_scan_lock: tokio::sync::Mutex::new(()), + gps_state: Arc::new(tokio::sync::RwLock::new(initial_gps)), }); run_server(&task_tracker, state, shutdown_token.clone()).await; diff --git a/daemon/src/server.rs b/daemon/src/server.rs index b4422387..541ac2cb 100644 --- a/daemon/src/server.rs +++ b/daemon/src/server.rs @@ -26,6 +26,7 @@ use crate::config::Config; use crate::diag::DiagDeviceCtrlMessage; use crate::display::DisplayState; use crate::notifications::DEFAULT_NOTIFICATION_TIMEOUT; +use crate::gps::GpsData; use crate::pcap::generate_pcap_data; use crate::qmdl_store::RecordingStore; @@ -40,6 +41,7 @@ pub struct ServerState { pub ui_update_sender: Option>, pub wifi_status: Arc>, pub wifi_scan_lock: tokio::sync::Mutex<()>, + pub gps_state: Arc>>, } #[cfg_attr(feature = "apidocs", utoipa::path( @@ -566,6 +568,7 @@ mod tests { ui_update_sender: None, wifi_status: Arc::new(RwLock::new(wifi_station::WifiStatus::default())), wifi_scan_lock: tokio::sync::Mutex::new(()), + gps_state: Arc::new(RwLock::new(None)), }) } diff --git a/daemon/web/src/lib/components/ConfigForm.svelte b/daemon/web/src/lib/components/ConfigForm.svelte index 1facbc1d..6e44080d 100644 --- a/daemon/web/src/lib/components/ConfigForm.svelte +++ b/daemon/web/src/lib/components/ConfigForm.svelte @@ -668,6 +668,79 @@ +

+

GPS Settings

+ +
+ + +

+ {#if config.gps_mode === 2} + POST latitude, longitude, and timestamp to /api/gps from + any device on the network. + {:else if config.gps_mode === 1} + GPS coordinates are fixed to the values below. + {:else} + GPS is disabled; no coordinates will be tracked. + {/if} +

+
+ + {#if config.gps_mode === 1} +
+ + +

Decimal degrees, -90 to 90

+
+ +
+ + +

Decimal degrees, -180 to 180

+
+ {/if} +
+