Skip to main content

radar_server/plugins/
radar_packet.rs

1use std::cmp::Ordering;
2use std::fs;
3use std::string::ToString;
4use bytemuck::{cast_slice, pod_collect_to_vec};
5use serde::{Deserialize, Serialize};
6use serde_with::{skip_serializing_none};
7use hdf5_metno::{Extent, File, H5Type};
8use num_complex::Complex;
9use crate::consts::{ARCHIVE_FILE_CHUNK_SIZE, DATA_ARCHIVE_DIR, DATA_CHUNK_SIZE};
10use crate::plugins::radar_packet::NetType::Archiver;
11
12/// Server binary version sourced from cargo at compile time.
13pub static VERSION:&str = env!("CARGO_PKG_VERSION");
14
15/// Network type for the packet, currently unused.
16#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
17pub enum NetType {
18    Server,
19    Client,
20    Archiver,
21}
22
23/// An enum to denote when we are sending SettingsInfo or receiving SettingData
24#[derive(Serialize,Deserialize,Debug, PartialEq,Clone)]
25#[serde(untagged)]
26pub enum SettingType{
27    SettingData(SettingData),
28    SettingInfo(Vec<SettingInfo>),
29}
30/// Identity info contains the network type and server version.
31#[derive(Serialize,Deserialize,Debug,Clone)]
32pub struct Identity {
33    pub(crate) net_type:NetType,
34    pub(crate) version:String,
35}
36
37/// The state of the radar at the time of recording.
38#[derive(H5Type,Clone,Copy,Serialize,Deserialize,Debug)]
39#[repr(C)]
40pub struct State {
41    pub(crate) angle:f64,
42    pub(crate) antenna:u8,
43    pub(crate) enabled:bool,
44    pub(crate) samples:u64,
45    pub(crate) rotation_rate:f64,
46}
47
48/// The current settings understood by the Archiver and Server ToDo: these could probably be split.
49#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
50pub struct SettingData {
51    pub samples:Option<u16>,
52    pub sample_rate:Option<f64>,
53    pub playback_delay:Option<u16>,
54}
55
56
57/// The packet expected from any settings channel communication
58#[derive(Serialize,Deserialize,Debug,Clone)]
59pub struct SettingsPacket {
60    pub(crate) identity:Identity,
61    pub(crate) controls:SettingType,
62}
63/// The radar data packet for use with dummy complex i16 data.
64///
65/// Contains an identity, time of recording, state, and the data vector.
66#[derive(Serialize, Deserialize, Debug, Clone)]
67pub struct ComPacket {
68    pub(crate) identity:Identity,
69    pub(crate) timestamp:f64,
70    pub(crate) state:State,
71    #[serde(with = "serde_bytes")]
72    pub(crate) data:Vec<u8>,
73}
74
75/// The struct to store, ComPacket's Identity field doesn't need to be stored, and data has its own dataset
76#[derive(H5Type,Serialize,Clone,Copy)]
77#[repr(C)]
78pub struct HDF5Packet {
79    timestamp:f64,
80    state:State,
81}
82
83/// Convert ComPacket to HDF5Packet
84impl From<&ComPacket> for HDF5Packet {
85    fn from(packet: &ComPacket) -> Self {
86        Self {
87            timestamp: packet.timestamp,
88            state: packet.state,
89        }
90    }
91}
92
93/// Helper functions for finding a specific time in a file or the data directory. 
94/// Once you have an ArchivedDataSource struct, this should be moved there
95impl HDF5Packet {
96    #[expect(dead_code)] // I didn't get the chance to test this, remove once this code is tested
97    pub fn find_index_in_file(timestamp:f64, file: File) -> Option<usize> {
98        let timestamps_ds = match file.dataset("timestamps"){
99            Ok(ds) => ds,
100            Err(_) => return None,
101        };
102        let timestamps = match timestamps_ds.read_raw::<f64>(){
103            Ok(ts) => ts,
104            Err(_) => return None,
105        };
106
107        match timestamps.binary_search_by(|&ts| ts.partial_cmp(&timestamp).unwrap_or(Ordering::Equal)) {
108            Ok(idx) => Some(idx), // exact match
109            Err(idx) => { // need to return nearest neighbor
110                if idx == 0 {
111                    Some(idx)
112                } else if idx == timestamps.len(){
113                    Some(idx-1)
114                } else {
115                    let previous_diff = (timestamp - timestamps[idx-1]).abs();
116                    let next_diff = (timestamps[idx] - timestamp).abs();
117                    if previous_diff <= next_diff {
118                        Some(idx-1)
119                    } else {
120                        Some(idx)
121                    }
122                }
123            }
124        }
125    }
126
127    #[expect(dead_code)] // I didn't get the chance to test this, remove once this code is tested
128    pub fn find_file_in_data_dir(timestamp:f64) -> File {
129        let mut data_path = dirs::home_dir().expect("Failed to get home directory");
130        data_path.push(DATA_ARCHIVE_DIR);
131        let epoch_secs = timestamp as i64;
132        let name = [
133            // dir
134            data_path.to_str().unwrap(),
135            // filename
136            (epoch_secs - (epoch_secs % ARCHIVE_FILE_CHUNK_SIZE)).to_string().as_str()
137            // extension
138            ,".h5"
139        ].concat();
140        File::open(&name).unwrap_or_else(|_| {
141            fs::create_dir_all(data_path.to_str().unwrap()).expect("Failed to create data dir"); // creates the data dir or does nothing
142            File::create(&name).expect("Couldn't create radar-data.h5 file")
143        })
144    }
145}
146
147
148/// A struct representing a piece of SettingInfo
149#[skip_serializing_none]
150#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
151pub struct SettingInfo {
152    id: String,
153    name:String,
154    min:Option<String>,
155    max:Option<String>,
156    step:Option<String>,
157    unit:Option<String>,
158    values:Option<Vec<SettingInfo>>,
159}
160
161/// Generate setting info. This should probably be changed to be done at compile time.
162impl SettingInfo {
163    pub fn get_archiver_settings() -> Vec<SettingInfo> {
164        let mut vals = Vec::<SettingInfo>::new();
165        vals.push(SettingInfo {
166            id: "playback_delay".to_string(),
167            name: "Playback Delay".to_string(),
168            min: Some("0".to_string()),
169            max: Some(u16::MAX.to_string()),
170            step: Some("1".to_string()),
171            unit: Some("Ms".to_string()),
172            values: None,
173        });
174        vals
175    }
176    pub fn get_server_settings() -> Vec<SettingInfo> {
177        let mut vals = Vec::<SettingInfo>::new();
178        vals.push(SettingInfo {
179            id: "samples".to_string(),
180            name: "Samples".to_string(),
181            min: Some("0".to_string()),
182            max: Some(u16::MAX.to_string()),
183            step: Some("1".to_string()),
184            unit: None,
185            values: None,
186        });
187        vals.push(SettingInfo {
188            id: "sample_rate".to_string(),
189            name: "Sample Rate".to_string(),
190            min: Some("0".to_string()),
191            max: Some(u16::MAX.to_string()),
192            step: None,
193            unit: Some("Samples/Sec".to_string()),
194            values: None,
195        });
196        vals
197    }
198}
199
200/// An Hdf5Object can be stored and retrieved from an HDF5 file.
201pub trait Hdf5Object{
202    /// Stores the object in the specified file.
203    fn to_hdf5(&self, file:&mut File) -> hdf5_metno::Result<()>;
204
205    /// Retrieves an object from the specified file and index.
206    fn from_hdf5(idx:usize, file: &File) -> hdf5_metno::Result<Self> where Self: Sized;
207}
208
209/// Implementation of HDF5Object for ComPacket.
210impl Hdf5Object for ComPacket {
211    fn to_hdf5(&self, file: &mut File) -> hdf5_metno::Result<()> {
212        let timestamps = match file.dataset("timestamps") {
213            Ok(ds) => ds,
214            Err(_) => {
215                println!("creating new timestamps dataset");
216                file.new_dataset::<f64>()
217                    .chunk((1,))
218                    .shape(Extent::resizable(0))
219                    .create("timestamps").expect("failed to create new dataset for timestamps")
220            }
221        };
222
223        let metadata = match file.dataset("metadata") {
224            Ok(ds) => ds,
225            Err(_) => {
226                println!("creating new metadata dataset");
227                file.new_dataset::<HDF5Packet>()
228                    .chunk((1,))
229                    .shape(Extent::resizable(0))
230                    .create("metadata").expect("failed to create new dataset for timestamps")
231            }
232        };
233
234        let data = match file.dataset("data") {
235            Ok(ds) => ds,
236            Err(_) => {
237                println!("creating new data dataset");
238                file.new_dataset::<Complex<i32>>()
239                    .chunk((1,DATA_CHUNK_SIZE))
240                    .shape((1..,DATA_CHUNK_SIZE))
241                    .create("data").expect("failed to create new dataset for timestamps")
242            }
243        };
244
245        let ds:&[Complex<i32>] = cast_slice(self.data.as_slice());
246
247        let mut idx = timestamps.size();
248        let mut written = 0usize;
249        let mut writes = 0;
250
251        while written < ds.len() {
252            let slice = &ds[written..DATA_CHUNK_SIZE + (DATA_CHUNK_SIZE * writes)];
253            timestamps.resize(idx + 1)?;
254            metadata.resize(idx + 1)?;
255            data.resize((idx+1,DATA_CHUNK_SIZE))?;
256            timestamps.write_slice(&[self.timestamp],idx..idx+1)?;
257            metadata.write_slice(&[HDF5Packet::from(self)],idx..idx+1)?;
258            data.write_slice(slice, (idx,..))?;
259            written += DATA_CHUNK_SIZE;
260            writes += 1;
261            idx += 1;
262        }
263
264        file.flush()?;
265        Ok(())
266    }
267
268    fn from_hdf5(idx:usize,file: &File) -> hdf5_metno::Result<Self> {
269        let metadata = match file.dataset("metadata") {
270            Ok(ds) => ds,
271            Err(e) => return Err(e),
272        };
273
274        let data = match file.dataset("data") {
275            Ok(ds) => ds,
276            Err(e) => return Err(e)
277        };
278        let meta:HDF5Packet = metadata.read_slice(idx..).expect("Failed to read metadata").to_vec()[0];
279        let data:Vec<Complex<i32>> = data.read_slice((idx,..)).expect("failed to read data").to_vec();
280
281        Ok(ComPacket{
282            identity:Identity{
283                net_type:Archiver,
284                version: VERSION.to_string()
285            },
286            timestamp: meta.timestamp,
287            state:meta.state,
288            data:pod_collect_to_vec(data.as_slice())  // ToDo: i think this involves a clone, there has to be a better way
289        })
290    }
291}