diff --git a/README.md b/README.md index 8f8b82d2..fc3f2ef4 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ The program is designed to be run during a flight and display information in a t Run your executable from the terminal with the path to your configuration file: ```shell -./packetraven_Windows.exe examples/example_1.yaml +./packetraven_Windows.exe run examples/example_1.yaml ``` [Instructions for creating a configuration file can be found in the documentation](https://packetraven.readthedocs.io/en/latest/configuration.html). @@ -61,3 +61,13 @@ and the up and down arrow keys change the current plot (or scroll through log me To quit, press `q` or `Esc`. +### Prediction + +You can run the `predict` subcommand to retrieve a prediction: + +```shell +./packetraven_Windows.exe predict "2023-08-16T10:00:00" -- -79 39 5 30000 9 +``` + +> **Note**\ +> Negative values must be prepended with `-- `, e.g. `-- -79`. \ No newline at end of file diff --git a/src/configuration/mod.rs b/src/configuration/mod.rs index fd3eb5f8..f23399d6 100644 --- a/src/configuration/mod.rs +++ b/src/configuration/mod.rs @@ -4,7 +4,7 @@ fn default_name() -> String { String::from("unnamed_flight") } -#[derive(serde::Deserialize, Clone)] +#[derive(serde::Deserialize, Clone, Default, serde::Serialize)] pub struct RunConfiguration { #[serde(default = "default_name")] pub name: String, @@ -23,7 +23,7 @@ fn default_interval() -> chrono::Duration { } #[serde_with::serde_as] -#[derive(PartialEq, Debug, serde::Deserialize, Clone)] +#[derive(PartialEq, Debug, serde::Deserialize, Clone, serde::Serialize)] pub struct TimeConfiguration { #[serde(default)] #[serde(with = "crate::utilities::optional_local_datetime_string")] @@ -46,7 +46,7 @@ impl Default for TimeConfiguration { } } -#[derive(Default, serde::Deserialize, PartialEq, Debug, Clone)] +#[derive(Default, serde::Deserialize, PartialEq, Debug, Clone, serde::Serialize)] pub struct ConnectionConfiguration { pub text: Option>, #[cfg(feature = "sondehub")] diff --git a/src/configuration/prediction.rs b/src/configuration/prediction.rs index 9c153c94..7e52b8ba 100644 --- a/src/configuration/prediction.rs +++ b/src/configuration/prediction.rs @@ -1,6 +1,6 @@ use serde_with::serde_as; -#[derive(serde::Deserialize, Clone)] +#[derive(serde::Deserialize, Clone, serde::Serialize)] #[serde(untagged)] pub enum PredictionConfiguration { Single(Prediction), @@ -14,7 +14,7 @@ fn default_name() -> String { String::from("prediction") } -#[derive(serde::Deserialize, PartialEq, Debug, Clone)] +#[derive(serde::Deserialize, PartialEq, Debug, Clone, serde::Serialize)] pub struct Prediction { pub start: crate::location::Location, pub profile: StandardProfile, @@ -63,7 +63,7 @@ fn default_descent_only() -> bool { false } -#[derive(serde::Deserialize, PartialEq, Debug, Clone)] +#[derive(serde::Deserialize, PartialEq, Debug, Clone, serde::Serialize)] pub struct StandardProfile { pub ascent_rate: f64, pub burst_altitude: f64, @@ -74,7 +74,7 @@ pub struct StandardProfile { } #[serde_as] -#[derive(serde::Deserialize, PartialEq, Debug, Clone)] +#[derive(serde::Deserialize, PartialEq, Debug, Clone, serde::Serialize)] pub struct FloatProfile { pub altitude: f64, pub uncertainty: Option, diff --git a/src/connection/aprs_fi.rs b/src/connection/aprs_fi.rs index 523622c5..060fe4e2 100644 --- a/src/connection/aprs_fi.rs +++ b/src/connection/aprs_fi.rs @@ -4,7 +4,7 @@ lazy_static::lazy_static! { static ref MINIMUM_ACCESS_INTERVAL: chrono::Duration = chrono::Duration::seconds(10); } -#[derive(serde::Deserialize, Debug, PartialEq, Clone)] +#[derive(serde::Deserialize, Debug, PartialEq, Clone, serde::Serialize)] pub struct AprsFiQuery { pub api_key: String, pub callsigns: Option>, diff --git a/src/connection/postgres.rs b/src/connection/postgres.rs index ae7835a8..ef2b1fae 100644 --- a/src/connection/postgres.rs +++ b/src/connection/postgres.rs @@ -1,6 +1,6 @@ use chrono::TimeZone; -#[derive(serde::Deserialize, Debug, PartialEq, Clone)] +#[derive(serde::Deserialize, Debug, PartialEq, Clone, serde::Serialize)] pub struct DatabaseCredentials { pub hostname: String, pub port: u32, diff --git a/src/connection/sondehub.rs b/src/connection/sondehub.rs index b958cde8..238df321 100644 --- a/src/connection/sondehub.rs +++ b/src/connection/sondehub.rs @@ -2,7 +2,7 @@ lazy_static::lazy_static! { static ref MINIMUM_ACCESS_INTERVAL: chrono::Duration = chrono::Duration::seconds(10); } -#[derive(serde::Deserialize, Debug, PartialEq, Clone, Default)] +#[derive(serde::Deserialize, Debug, PartialEq, Clone, Default, serde::Serialize)] pub struct SondeHubQuery { pub start: Option>, pub end: Option>, diff --git a/src/connection/text/file.rs b/src/connection/text/file.rs index b2218a30..be76193b 100644 --- a/src/connection/text/file.rs +++ b/src/connection/text/file.rs @@ -2,7 +2,7 @@ use std::io::prelude::BufRead; use chrono::{TimeZone, Timelike}; -#[derive(serde::Deserialize, Debug, PartialEq, Clone)] +#[derive(serde::Deserialize, Debug, PartialEq, Clone, serde::Serialize)] pub struct AprsTextFile { pub path: String, pub callsigns: Option>, @@ -134,7 +134,7 @@ impl AprsTextFile { } } -#[derive(serde::Deserialize, Debug, PartialEq, Clone)] +#[derive(serde::Deserialize, Debug, PartialEq, Clone, serde::Serialize)] pub struct GeoJsonFile { pub path: String, } diff --git a/src/connection/text/mod.rs b/src/connection/text/mod.rs index ef0c5e3d..ea502249 100644 --- a/src/connection/text/mod.rs +++ b/src/connection/text/mod.rs @@ -2,7 +2,7 @@ pub mod file; #[cfg(feature = "serial")] pub mod serial; -#[derive(serde::Deserialize, Debug, PartialEq, Clone)] +#[derive(serde::Deserialize, Debug, PartialEq, Clone, serde::Serialize)] #[serde(untagged)] pub enum TextStream { AprsTextFile(file::AprsTextFile), diff --git a/src/connection/text/serial.rs b/src/connection/text/serial.rs index 4623e18f..6631158d 100644 --- a/src/connection/text/serial.rs +++ b/src/connection/text/serial.rs @@ -2,7 +2,7 @@ lazy_static::lazy_static! { static ref DEFAULT_BAUD_RATE: u32 = 9600; } -#[derive(serde::Deserialize, Debug, PartialEq, Clone)] +#[derive(serde::Deserialize, Debug, PartialEq, Clone, serde::Serialize)] pub struct AprsSerial { #[serde(default = "first_available_port")] pub port: String, diff --git a/src/main.rs b/src/main.rs index ce21a9ba..310b1534 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,17 +21,115 @@ lazy_static::lazy_static! { #[derive(Parser)] #[command(author, version, about, long_about = None)] struct PacketravenCommand { - // configuration file to read - config_file: std::path::PathBuf, + #[command(subcommand)] + command: Command, +} + +#[derive(clap::Subcommand)] +enum Command { + /// run program from configuration + Run { + /// file path to configuration + config_file: std::path::PathBuf, + }, + /// retrieve a balloon prediction from the given API + Predict { + /// start time + time: chrono::NaiveDateTime, + /// start longitude + longitude: f64, + /// start latitude + latitude: f64, + /// start altitude + #[arg(short, long)] + altitude: Option, + /// expected average ascent rate + ascent_rate: f64, + /// expected burst altitude + burst_altitude: f64, + /// descent rate at sea level + sea_level_descent_rate: f64, + /// desired float altitude + #[arg(long)] + float_altitude: Option, + /// desired float duration in seconds + #[arg(long)] + float_duration: Option, + }, + /// write an empty configuration file + Write { + /// file path to configuration + filename: std::path::PathBuf, + }, } fn main() -> Result<(), Box> { let arguments = PacketravenCommand::parse(); - let configuration_file = std::fs::File::open(arguments.config_file).unwrap(); - let configuration: crate::configuration::RunConfiguration = - serde_yaml::from_reader(configuration_file).expect("error reading configuration"); + match arguments.command { + Command::Run { config_file } => { + let file = std::fs::File::open(config_file).unwrap(); + let configuration: crate::configuration::RunConfiguration = + serde_yaml::from_reader(file).expect("error reading configuration"); + + tui::run(configuration, *LOG_LEVEL)?; + Ok(()) + } + Command::Predict { + time, + longitude, + latitude, + altitude, + ascent_rate, + burst_altitude, + sea_level_descent_rate, + float_altitude, + float_duration, + } => { + let start = location::Location { + time: time.and_local_timezone(chrono::Local).unwrap(), + coord: geo::coord! {x:longitude,y:latitude}, + altitude, + }; + let profile = prediction::FlightProfile::new( + ascent_rate, + float_altitude, + match float_duration { + Some(seconds) => Some(chrono::Duration::seconds(seconds as i64)), + None => None, + }, + None, + burst_altitude, + sea_level_descent_rate, + ); + + let query = prediction::tawhiri::TawhiriQuery::new( + &start, &profile, None, None, None, false, None, + ); + + match query.retrieve_prediction() { + Ok(prediction) => { + for location in prediction { + println!( + "{:}, {:.1}, {:.1}, {:.1}", + location.location.time.format("%Y-%m-%d %H:%M:%S"), + location.location.coord.x, + location.location.coord.y, + location.location.altitude.unwrap_or(0.0) + ); + } + } + Err(error) => return Err(Box::new(error)), + } + + Ok(()) + } + Command::Write { filename } => { + let configuration = configuration::RunConfiguration::default(); + let file = std::fs::File::create(filename).unwrap(); - tui::run(configuration, *LOG_LEVEL)?; - Ok(()) + serde_yaml::to_writer(file, &configuration).unwrap(); + Ok(()) + } + } }