mirror of
https://github.com/EFForg/rayhunter.git
synced 2026-06-04 12:11:54 -07:00
PR chage requests, revision to GPS logging feature, code cleanup
This commit is contained in:
committed by
Will Greenberg
parent
dbe102e366
commit
0b91a6e5d3
+28
-1
@@ -12,7 +12,9 @@ use futures::{StreamExt, TryStreamExt, future};
|
|||||||
use log::{debug, error, info, warn};
|
use log::{debug, error, info, warn};
|
||||||
use rayhunter::Device;
|
use rayhunter::Device;
|
||||||
use tokio::fs::File;
|
use tokio::fs::File;
|
||||||
use tokio::io::{AsyncBufReadExt, BufReader};
|
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
||||||
|
|
||||||
|
use crate::gps::GpsRecord;
|
||||||
use tokio::sync::mpsc::{Receiver, Sender};
|
use tokio::sync::mpsc::{Receiver, Sender};
|
||||||
use tokio::sync::{RwLock, oneshot};
|
use tokio::sync::{RwLock, oneshot};
|
||||||
use tokio_stream::wrappers::LinesStream;
|
use tokio_stream::wrappers::LinesStream;
|
||||||
@@ -57,6 +59,7 @@ pub struct DiagTask {
|
|||||||
min_space_to_start_mb: u64,
|
min_space_to_start_mb: u64,
|
||||||
min_space_to_continue_mb: u64,
|
min_space_to_continue_mb: u64,
|
||||||
gps_mode: u8,
|
gps_mode: u8,
|
||||||
|
gps_fixed_coords: Option<(f64, f64)>,
|
||||||
state: DiagState,
|
state: DiagState,
|
||||||
max_type_seen: EventType,
|
max_type_seen: EventType,
|
||||||
bytes_since_space_check: usize,
|
bytes_since_space_check: usize,
|
||||||
@@ -106,6 +109,7 @@ impl DiagTask {
|
|||||||
min_space_to_start_mb: u64,
|
min_space_to_start_mb: u64,
|
||||||
min_space_to_continue_mb: u64,
|
min_space_to_continue_mb: u64,
|
||||||
gps_mode: u8,
|
gps_mode: u8,
|
||||||
|
gps_fixed_coords: Option<(f64, f64)>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
ui_update_sender,
|
ui_update_sender,
|
||||||
@@ -115,6 +119,7 @@ impl DiagTask {
|
|||||||
min_space_to_start_mb,
|
min_space_to_start_mb,
|
||||||
min_space_to_continue_mb,
|
min_space_to_continue_mb,
|
||||||
gps_mode,
|
gps_mode,
|
||||||
|
gps_fixed_coords,
|
||||||
state: DiagState::Stopped,
|
state: DiagState::Stopped,
|
||||||
max_type_seen: EventType::Informational,
|
max_type_seen: EventType::Informational,
|
||||||
bytes_since_space_check: 0,
|
bytes_since_space_check: 0,
|
||||||
@@ -155,6 +160,26 @@ impl DiagTask {
|
|||||||
return Err(msg);
|
return Err(msg);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
// For fixed-mode sessions, write the configured coordinates to the sidecar
|
||||||
|
// immediately so the per-session GPS is stored durably and isn't affected
|
||||||
|
// by future config changes or GPS API calls.
|
||||||
|
if self.gps_mode == 1 {
|
||||||
|
if let Some((lat, lon)) = self.gps_fixed_coords {
|
||||||
|
if let Some((entry_idx, _)) = qmdl_store.get_current_entry() {
|
||||||
|
if let Ok(mut gps_file) = qmdl_store.open_entry_gps_for_append(entry_idx).await
|
||||||
|
{
|
||||||
|
let record = GpsRecord {
|
||||||
|
unix_ts: 0,
|
||||||
|
lat,
|
||||||
|
lon,
|
||||||
|
};
|
||||||
|
if let Ok(json) = serde_json::to_string(&record) {
|
||||||
|
let _ = gps_file.write_all(format!("{json}\n").as_bytes()).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
self.stop_current_recording().await;
|
self.stop_current_recording().await;
|
||||||
let qmdl_writer = QmdlWriter::new(qmdl_file);
|
let qmdl_writer = QmdlWriter::new(qmdl_file);
|
||||||
let analysis_writer = match AnalysisWriter::new(analysis_file, &self.analyzer_config).await
|
let analysis_writer = match AnalysisWriter::new(analysis_file, &self.analyzer_config).await
|
||||||
@@ -385,6 +410,7 @@ pub fn run_diag_read_thread(
|
|||||||
min_space_to_start_mb: u64,
|
min_space_to_start_mb: u64,
|
||||||
min_space_to_continue_mb: u64,
|
min_space_to_continue_mb: u64,
|
||||||
gps_mode: u8,
|
gps_mode: u8,
|
||||||
|
gps_fixed_coords: Option<(f64, f64)>,
|
||||||
) {
|
) {
|
||||||
task_tracker.spawn(async move {
|
task_tracker.spawn(async move {
|
||||||
info!("Using configuration for device: {0:?}", device);
|
info!("Using configuration for device: {0:?}", device);
|
||||||
@@ -402,6 +428,7 @@ pub fn run_diag_read_thread(
|
|||||||
min_space_to_start_mb,
|
min_space_to_start_mb,
|
||||||
min_space_to_continue_mb,
|
min_space_to_continue_mb,
|
||||||
gps_mode,
|
gps_mode,
|
||||||
|
gps_fixed_coords,
|
||||||
);
|
);
|
||||||
qmdl_file_tx
|
qmdl_file_tx
|
||||||
.send(DiagDeviceCtrlMessage::StartRecording { response_tx: None })
|
.send(DiagDeviceCtrlMessage::StartRecording { response_tx: None })
|
||||||
|
|||||||
+41
-11
@@ -1,6 +1,8 @@
|
|||||||
use axum::Json;
|
use axum::Json;
|
||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use axum::http::StatusCode;
|
use axum::http::StatusCode;
|
||||||
|
use chrono::{DateTime, FixedOffset, Utc};
|
||||||
|
use log::error;
|
||||||
use serde::{Deserialize, Deserializer, Serialize};
|
use serde::{Deserialize, Deserializer, Serialize};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
||||||
@@ -14,13 +16,18 @@ where
|
|||||||
use serde::de;
|
use serde::de;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
match Value::deserialize(deserializer)? {
|
match Value::deserialize(deserializer)? {
|
||||||
Value::Number(n) => n.as_i64()
|
Value::Number(n) => n
|
||||||
|
.as_i64()
|
||||||
.or_else(|| n.as_f64().map(|f| f as i64))
|
.or_else(|| n.as_f64().map(|f| f as i64))
|
||||||
.ok_or_else(|| de::Error::custom("timestamp out of range")),
|
.ok_or_else(|| de::Error::custom("timestamp out of range")),
|
||||||
Value::String(s) => s.trim().parse::<f64>()
|
Value::String(s) => s
|
||||||
|
.trim()
|
||||||
|
.parse::<f64>()
|
||||||
.map(|f| f as i64)
|
.map(|f| f as i64)
|
||||||
.map_err(|_| de::Error::custom("timestamp must be a numeric value")),
|
.map_err(|_| de::Error::custom("timestamp must be a numeric value")),
|
||||||
_ => Err(de::Error::custom("timestamp must be a number or numeric string")),
|
_ => Err(de::Error::custom(
|
||||||
|
"timestamp must be a number or numeric string",
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,14 +39,30 @@ pub struct GpsData {
|
|||||||
pub timestamp: i64,
|
pub timestamp: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl GpsData {
|
||||||
|
pub fn to_datetime(&self) -> DateTime<FixedOffset> {
|
||||||
|
DateTime::from_timestamp(self.timestamp, 0)
|
||||||
|
.unwrap_or_default()
|
||||||
|
.fixed_offset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct GpsRecord {
|
pub struct GpsRecord {
|
||||||
pub unix_ts: u32,
|
pub unix_ts: i64,
|
||||||
pub lat: f64,
|
pub lat: f64,
|
||||||
pub lon: f64,
|
pub lon: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reads all GPS records from a sidecar file, skipping malformed lines.
|
impl GpsRecord {
|
||||||
|
pub fn to_datetime(&self) -> DateTime<FixedOffset> {
|
||||||
|
DateTime::from_timestamp(self.unix_ts, 0)
|
||||||
|
.unwrap_or_default()
|
||||||
|
.fixed_offset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads all GPS records from a sidecar NDJSON file, skipping malformed lines.
|
||||||
pub async fn load_gps_records(file: tokio::fs::File) -> Vec<GpsRecord> {
|
pub async fn load_gps_records(file: tokio::fs::File) -> Vec<GpsRecord> {
|
||||||
let reader = BufReader::new(file);
|
let reader = BufReader::new(file);
|
||||||
let mut lines = reader.lines();
|
let mut lines = reader.lines();
|
||||||
@@ -69,9 +92,18 @@ pub async fn post_gps(
|
|||||||
let qmdl_store = state.qmdl_store_lock.read().await;
|
let qmdl_store = state.qmdl_store_lock.read().await;
|
||||||
if let Some((entry_idx, _)) = qmdl_store.get_current_entry() {
|
if let Some((entry_idx, _)) = qmdl_store.get_current_entry() {
|
||||||
if let Ok(mut file) = qmdl_store.open_entry_gps_for_append(entry_idx).await {
|
if let Ok(mut file) = qmdl_store.open_entry_gps_for_append(entry_idx).await {
|
||||||
let record = GpsRecord { unix_ts: chrono::Utc::now().timestamp() as u32, lat: gps_data.latitude, lon: gps_data.longitude };
|
let record = GpsRecord {
|
||||||
if let Ok(json) = serde_json::to_string(&record) {
|
unix_ts: Utc::now().timestamp(),
|
||||||
let _ = file.write_all(format!("{json}\n").as_bytes()).await;
|
lat: gps_data.latitude,
|
||||||
|
lon: gps_data.longitude,
|
||||||
|
};
|
||||||
|
match serde_json::to_string(&record) {
|
||||||
|
Ok(json) => {
|
||||||
|
if let Err(e) = file.write_all(format!("{json}\n").as_bytes()).await {
|
||||||
|
error!("failed to write GPS record to sidecar: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => error!("failed to serialize GPS record: {e}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -79,9 +111,7 @@ pub async fn post_gps(
|
|||||||
Ok(StatusCode::OK)
|
Ok(StatusCode::OK)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_gps(
|
pub async fn get_gps(State(state): State<Arc<ServerState>>) -> Result<Json<GpsData>, StatusCode> {
|
||||||
State(state): State<Arc<ServerState>>,
|
|
||||||
) -> Result<Json<GpsData>, StatusCode> {
|
|
||||||
let gps = state.gps_state.read().await;
|
let gps = state.gps_state.read().await;
|
||||||
match gps.as_ref() {
|
match gps.as_ref() {
|
||||||
Some(data) => Ok(Json(data.clone())),
|
Some(data) => Ok(Json(data.clone())),
|
||||||
|
|||||||
+6
-1
@@ -21,10 +21,10 @@ use crate::battery::run_battery_notification_worker;
|
|||||||
use crate::config::{parse_args, parse_config};
|
use crate::config::{parse_args, parse_config};
|
||||||
use crate::diag::run_diag_read_thread;
|
use crate::diag::run_diag_read_thread;
|
||||||
use crate::error::RayhunterError;
|
use crate::error::RayhunterError;
|
||||||
|
use crate::gps::{get_gps, post_gps};
|
||||||
use crate::notifications::{NotificationService, run_notification_worker};
|
use crate::notifications::{NotificationService, run_notification_worker};
|
||||||
use crate::pcap::get_pcap;
|
use crate::pcap::get_pcap;
|
||||||
use crate::qmdl_store::RecordingStore;
|
use crate::qmdl_store::RecordingStore;
|
||||||
use crate::gps::{get_gps, post_gps};
|
|
||||||
use crate::server::{
|
use crate::server::{
|
||||||
ServerState, debug_set_display_state, get_config, get_qmdl, get_time, get_wifi_status, get_zip,
|
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,
|
scan_wifi, serve_static, set_config, set_time_offset, test_notification,
|
||||||
@@ -220,6 +220,10 @@ async fn run_with_config(
|
|||||||
|
|
||||||
if !config.debug_mode {
|
if !config.debug_mode {
|
||||||
info!("Starting Diag Thread");
|
info!("Starting Diag Thread");
|
||||||
|
let gps_fixed_coords = match (config.gps_fixed_latitude, config.gps_fixed_longitude) {
|
||||||
|
(Some(lat), Some(lon)) => Some((lat, lon)),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
run_diag_read_thread(
|
run_diag_read_thread(
|
||||||
&task_tracker,
|
&task_tracker,
|
||||||
config.device.clone(),
|
config.device.clone(),
|
||||||
@@ -233,6 +237,7 @@ async fn run_with_config(
|
|||||||
config.min_space_to_start_recording_mb,
|
config.min_space_to_start_recording_mb,
|
||||||
config.min_space_to_continue_recording_mb,
|
config.min_space_to_continue_recording_mb,
|
||||||
config.gps_mode,
|
config.gps_mode,
|
||||||
|
gps_fixed_coords,
|
||||||
);
|
);
|
||||||
info!("Starting UI");
|
info!("Starting UI");
|
||||||
|
|
||||||
|
|||||||
+96
-12
@@ -75,6 +75,7 @@ pub(crate) async fn load_gps_records_for_entry(
|
|||||||
) -> Vec<GpsRecord> {
|
) -> Vec<GpsRecord> {
|
||||||
// Always try the per-session sidecar first — it reflects what was actually
|
// Always try the per-session sidecar first — it reflects what was actually
|
||||||
// recorded regardless of what the current gps_mode config is.
|
// recorded regardless of what the current gps_mode config is.
|
||||||
|
let entry_gps_mode;
|
||||||
{
|
{
|
||||||
let qmdl_store = state.qmdl_store_lock.read().await;
|
let qmdl_store = state.qmdl_store_lock.read().await;
|
||||||
if let Ok(file) = qmdl_store.open_entry_gps(entry_index).await {
|
if let Ok(file) = qmdl_store.open_entry_gps(entry_index).await {
|
||||||
@@ -83,19 +84,33 @@ pub(crate) async fn load_gps_records_for_entry(
|
|||||||
return records;
|
return records;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Capture the entry's recorded GPS mode before releasing the lock.
|
||||||
|
entry_gps_mode = qmdl_store
|
||||||
|
.manifest
|
||||||
|
.entries
|
||||||
|
.get(entry_index)
|
||||||
|
.and_then(|e| e.gps_mode);
|
||||||
}
|
}
|
||||||
// Sidecar missing or empty — fall back to current config.
|
// Sidecar missing or empty — fall back using the entry's own recorded GPS mode,
|
||||||
if state.config.gps_mode == 1 {
|
// not the current config, so old fixed-mode sessions still get coordinates even
|
||||||
let guard = state.gps_state.read().await;
|
// if the mode has since been changed. Use the configured fixed coords directly
|
||||||
return guard
|
// rather than gps_state, which can be overwritten by API calls or be None.
|
||||||
.as_ref()
|
if entry_gps_mode == Some(1) {
|
||||||
.map(|g| vec![GpsRecord { unix_ts: 0, lat: g.latitude, lon: g.longitude }])
|
if let (Some(lat), Some(lon)) = (
|
||||||
.unwrap_or_default();
|
state.config.gps_fixed_latitude,
|
||||||
|
state.config.gps_fixed_longitude,
|
||||||
|
) {
|
||||||
|
return vec![GpsRecord {
|
||||||
|
unix_ts: 0,
|
||||||
|
lat,
|
||||||
|
lon,
|
||||||
|
}];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
vec![]
|
vec![]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_nearest_gps(records: &[GpsRecord], packet_unix_ts: u32) -> Option<GpsPoint> {
|
fn find_nearest_gps(records: &[GpsRecord], packet_unix_ts: i64) -> Option<GpsPoint> {
|
||||||
if records.is_empty() {
|
if records.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@@ -106,9 +121,79 @@ fn find_nearest_gps(records: &[GpsRecord], packet_unix_ts: u32) -> Option<GpsPoi
|
|||||||
&records[records.len() - 1]
|
&records[records.len() - 1]
|
||||||
} else {
|
} else {
|
||||||
let (before, after) = (&records[idx - 1], &records[idx]);
|
let (before, after) = (&records[idx - 1], &records[idx]);
|
||||||
if packet_unix_ts - before.unix_ts <= after.unix_ts - packet_unix_ts { before } else { after }
|
let before_delta = packet_unix_ts - before.unix_ts;
|
||||||
|
let after_delta = after.unix_ts - packet_unix_ts;
|
||||||
|
if before_delta <= after_delta {
|
||||||
|
before
|
||||||
|
} else {
|
||||||
|
after
|
||||||
|
}
|
||||||
};
|
};
|
||||||
Some(GpsPoint { latitude: record.lat, longitude: record.lon, unix_ts: record.unix_ts })
|
Some(GpsPoint {
|
||||||
|
latitude: record.lat,
|
||||||
|
longitude: record.lon,
|
||||||
|
unix_ts: record.unix_ts,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn rec(unix_ts: i64, lat: f64, lon: f64) -> GpsRecord {
|
||||||
|
GpsRecord { unix_ts, lat, lon }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_empty_returns_none() {
|
||||||
|
assert!(find_nearest_gps(&[], 100).is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_single_record_always_returned() {
|
||||||
|
let records = vec![rec(100, 1.0, 2.0)];
|
||||||
|
assert_eq!(find_nearest_gps(&records, 0).unwrap().unix_ts, 100);
|
||||||
|
assert_eq!(find_nearest_gps(&records, 200).unwrap().unix_ts, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_before_all_records_returns_first() {
|
||||||
|
let records = vec![rec(100, 1.0, 2.0), rec(200, 3.0, 4.0)];
|
||||||
|
assert_eq!(find_nearest_gps(&records, 50).unwrap().unix_ts, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_after_all_records_returns_last() {
|
||||||
|
let records = vec![rec(100, 1.0, 2.0), rec(200, 3.0, 4.0)];
|
||||||
|
assert_eq!(find_nearest_gps(&records, 300).unwrap().unix_ts, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_exact_match() {
|
||||||
|
let records = vec![rec(100, 1.0, 2.0), rec(200, 3.0, 4.0), rec(300, 5.0, 6.0)];
|
||||||
|
assert_eq!(find_nearest_gps(&records, 200).unwrap().unix_ts, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_closer_to_before() {
|
||||||
|
// packet at 130: delta to before(100)=30, delta to after(200)=70 → picks before
|
||||||
|
let records = vec![rec(100, 1.0, 2.0), rec(200, 3.0, 4.0)];
|
||||||
|
assert_eq!(find_nearest_gps(&records, 130).unwrap().unix_ts, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_closer_to_after() {
|
||||||
|
// packet at 170: delta to before(100)=70, delta to after(200)=30 → picks after
|
||||||
|
let records = vec![rec(100, 1.0, 2.0), rec(200, 3.0, 4.0)];
|
||||||
|
assert_eq!(find_nearest_gps(&records, 170).unwrap().unix_ts, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_equidistant_prefers_before() {
|
||||||
|
// packet at 150: delta to before(100)=50, delta to after(200)=50 → tie, picks before
|
||||||
|
let records = vec![rec(100, 1.0, 2.0), rec(200, 3.0, 4.0)];
|
||||||
|
assert_eq!(find_nearest_gps(&records, 150).unwrap().unix_ts, 100);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn generate_pcap_data<R, W>(
|
pub async fn generate_pcap_data<R, W>(
|
||||||
@@ -135,8 +220,7 @@ where
|
|||||||
Ok(msg) => {
|
Ok(msg) => {
|
||||||
let maybe_gsmtap_msg = gsmtap_parser::parse(msg)?;
|
let maybe_gsmtap_msg = gsmtap_parser::parse(msg)?;
|
||||||
if let Some((timestamp, gsmtap_msg)) = maybe_gsmtap_msg {
|
if let Some((timestamp, gsmtap_msg)) = maybe_gsmtap_msg {
|
||||||
let packet_unix_ts =
|
let packet_unix_ts = timestamp.to_datetime().timestamp();
|
||||||
timestamp.to_datetime().timestamp().max(0) as u32;
|
|
||||||
let gps = find_nearest_gps(&gps_records, packet_unix_ts);
|
let gps = find_nearest_gps(&gps_records, packet_unix_ts);
|
||||||
pcap_writer
|
pcap_writer
|
||||||
.write_gsmtap_message(gsmtap_msg, timestamp, gps.as_ref())
|
.write_gsmtap_message(gsmtap_msg, timestamp, gps.as_ref())
|
||||||
|
|||||||
@@ -105,9 +105,7 @@ impl ManifestEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_gps_filepath<P: AsRef<Path>>(&self, path: P) -> PathBuf {
|
pub fn get_gps_filepath<P: AsRef<Path>>(&self, path: P) -> PathBuf {
|
||||||
let mut filepath = path.as_ref().join(&self.name);
|
path.as_ref().join(format!("{}-gps.ndjson", self.name))
|
||||||
filepath.set_extension("gps.ndjson");
|
|
||||||
filepath
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -388,8 +388,13 @@ pub async fn get_zip(
|
|||||||
.take(qmdl_size_bytes as u64)
|
.take(qmdl_size_bytes as u64)
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(e) =
|
if let Err(e) = generate_pcap_data(
|
||||||
generate_pcap_data(&mut entry_writer, qmdl_file_for_pcap, qmdl_size_bytes, gps_records).await
|
&mut entry_writer,
|
||||||
|
qmdl_file_for_pcap,
|
||||||
|
qmdl_size_bytes,
|
||||||
|
gps_records,
|
||||||
|
)
|
||||||
|
.await
|
||||||
{
|
{
|
||||||
// if we fail to generate the PCAP file, we should still continue and give the
|
// if we fail to generate the PCAP file, we should still continue and give the
|
||||||
// user the QMDL.
|
// user the QMDL.
|
||||||
|
|||||||
@@ -70,12 +70,10 @@
|
|||||||
>
|
>
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
{#if entry.gps_mode !== undefined}
|
<p>
|
||||||
<p>
|
<b>GPS Mode:</b>
|
||||||
<b>GPS Mode:</b>
|
{(entry.gps_mode ?? 0) === 0 ? 'Disabled' : entry.gps_mode === 1 ? 'Fixed coordinates' : 'API endpoint'}
|
||||||
{entry.gps_mode === 0 ? 'Disabled' : entry.gps_mode === 1 ? 'Fixed coordinates' : 'API endpoint'}
|
</p>
|
||||||
</p>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
{#if metadata && metadata.analyzers}
|
{#if metadata && metadata.analyzers}
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
+9
-3
@@ -30,7 +30,7 @@ pub struct GpsPoint {
|
|||||||
pub latitude: f64,
|
pub latitude: f64,
|
||||||
pub longitude: f64,
|
pub longitude: f64,
|
||||||
/// Unix timestamp of the GPS fix. 0 means fixed/synthetic (no real GPS time).
|
/// Unix timestamp of the GPS fix. 0 means fixed/synthetic (no real GPS time).
|
||||||
pub unix_ts: u32,
|
pub unix_ts: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct GsmtapPcapWriter<T>
|
pub struct GsmtapPcapWriter<T>
|
||||||
@@ -149,9 +149,15 @@ where
|
|||||||
let mut options = vec![];
|
let mut options = vec![];
|
||||||
if let Some(p) = gps {
|
if let Some(p) = gps {
|
||||||
let comment = if p.unix_ts == 0 {
|
let comment = if p.unix_ts == 0 {
|
||||||
format!("GPS fixed lat={:.7} lon={:.7}", p.latitude, p.longitude)
|
format!(
|
||||||
|
r#"{{"latitude":{:.7},"longitude":{:.7}}}"#,
|
||||||
|
p.latitude, p.longitude
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
format!("GPS lat={:.7} lon={:.7} ts={}", p.latitude, p.longitude, p.unix_ts)
|
format!(
|
||||||
|
r#"{{"latitude":{:.7},"longitude":{:.7},"timestamp":{}}}"#,
|
||||||
|
p.latitude, p.longitude, p.unix_ts
|
||||||
|
)
|
||||||
};
|
};
|
||||||
options.push(EnhancedPacketOption::Comment(Cow::Owned(comment)));
|
options.push(EnhancedPacketOption::Comment(Cow::Owned(comment)));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user