diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0a3072c12..1b2ed5f42 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -252,7 +252,7 @@ jobs: bench.filename frontend_bench: name: Run Frontend Benchmarks - timeout-minutes: 5 + timeout-minutes: 30 needs: - build strategy: diff --git a/Cargo.lock b/Cargo.lock index d370396f5..4159afefc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -31,9 +31,9 @@ checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "bitvec" -version = "0.19.4" +version = "0.19.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7ba35e9565969edb811639dbebfe34edc0368e472c5018474c8eb2543397f81" +checksum = "8942c8d352ae1838c9dda0b0ca2ab657696ef2232a20147cf1b30ae1a9cb4321" dependencies = [ "funty", "radium", @@ -479,11 +479,12 @@ dependencies = [ [[package]] name = "nom" -version = "6.1.0" +version = "6.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab6f70b46d6325aa300f1c7bb3d470127dfc27806d8ea6bf294ee0ce643ce2b1" +checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2" dependencies = [ "bitvec", + "funty", "lexical-core", "memchr", "version_check", @@ -767,7 +768,7 @@ dependencies = [ [[package]] name = "sbp" version = "3.4.5" -source = "git+https://github.com/swift-nav/libsbp.git?rev=986af3f35033a720f21872794d0bd6579c02866c#986af3f35033a720f21872794d0bd6579c02866c" +source = "git+https://github.com/swift-nav/libsbp.git?rev=0563350bb387e4a966a2b558adb7b05b2baf005f#0563350bb387e4a966a2b558adb7b05b2baf005f" dependencies = [ "byteorder", "bytes", @@ -878,9 +879,9 @@ dependencies = [ [[package]] name = "tap" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36474e732d1affd3a6ed582781b3683df3d0563714c59c39591e8ff707cf078e" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "textwrap" @@ -893,18 +894,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.23" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76cc616c6abf8c8928e2fdcc0dbfab37175edd8fb49a4641066ad1364fdab146" +checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.23" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9be73a2caec27583d0046ef3796c3794f868a5bc813db689eed00c7631275cd1" +checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0" dependencies = [ "proc-macro2", "quote", diff --git a/Makefile.toml b/Makefile.toml index 187c5b3f8..6b8d2a5d7 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -10,32 +10,32 @@ default_to_workspace = false [tasks.poetry-export-dev] script_runner = "@shell" script = ''' -conda run -n console_pp poetry export --dev -f $REQUIREMENTS_FILE --output $REQUIREMENTS_DEV_FILE --without-hashes +conda run -n $CONDA_ENV poetry export --dev -f $REQUIREMENTS_FILE --output $REQUIREMENTS_DEV_FILE --without-hashes ''' [tasks.poetry-export] dependencies = ["poetry-export-dev"] script_runner = "@shell" script = ''' -conda run -n console_pp poetry export -f $REQUIREMENTS_FILE --output $REQUIREMENTS_FILE --without-hashes +conda run -n $CONDA_ENV poetry export -f $REQUIREMENTS_FILE --output $REQUIREMENTS_FILE --without-hashes ''' [tasks.pip-install-dev] script_runner = "@shell" script = ''' -conda run -n console_pp pip install -r $REQUIREMENTS_DEV_FILE +conda run -n $CONDA_ENV pip install -r $REQUIREMENTS_DEV_FILE ''' [tasks.pip-install] script_runner = "@shell" script = ''' -conda run -n console_pp pip install -r $REQUIREMENTS_FILE +conda run -n $CONDA_ENV pip install -r $REQUIREMENTS_FILE ''' [tasks.generate-resources] script_runner = "@shell" script = ''' -conda run -n $CONDA_ENV pyside2-rcc resources/console_resources.qrc -o src/main/python/console_resources.py +conda run -n $CONDA_ENV --no-capture-output --live-stream pyside2-rcc resources/console_resources.qrc -o src/main/python/console_resources.py ''' [tasks.copy-capnp] @@ -62,14 +62,14 @@ Get-ChildItem .\console_backend -Recurse -Include @("*.egg-info", "*.dist-info") dependencies = ["copy-capnp", "generate-resources", "remove-egg-dist", "install-backend"] script_runner = "@shell" script = ''' -conda run -n $CONDA_ENV fbs run +conda run -n $CONDA_ENV --no-capture-output --live-stream fbs run ''' [tasks.prod-run] dependencies = ["copy-capnp", "generate-resources", "remove-egg-dist", "prod-install-backend"] script_runner = "@shell" script = ''' -conda run -n $CONDA_ENV fbs run +conda run -n $CONDA_ENV --no-capture-output --live-stream fbs run ''' [tasks.setuptools-rust] @@ -87,7 +87,7 @@ conda run -n $CONDA_ENV pip install -e ./console_backend [tasks.prod-install-backend] script_runner = "@shell" script = ''' -conda run -n $CONDA_ENV python -m pip install -t ./console_backend --upgrade --force ./console_backend -v +conda run -n $CONDA_ENV --no-capture-output --live-stream python -m pip install ./console_backend --upgrade --force ./console_backend -v ''' [tasks.prod-freeze-no-copy] diff --git a/README.md b/README.md index 434c304c1..787a1cc54 100644 --- a/README.md +++ b/README.md @@ -33,10 +33,11 @@ brew install capnp apt-get install capnproto ``` -Setup the poetry environment +Install development dependencies (On Windows make sure you're using Adminstrator shell). ``` -poetry install +cargo make pip-install-dev +git pull lfs ``` ## Running diff --git a/console_backend/Cargo.toml b/console_backend/Cargo.toml index 7e757e1d5..53d17bc7f 100644 --- a/console_backend/Cargo.toml +++ b/console_backend/Cargo.toml @@ -8,7 +8,7 @@ edition = "2018" [dependencies] capnp = "0.14" pyo3 = { version = "0.13", features = ["extension-module"] } -sbp = { git = "https://github.com/swift-nav/libsbp.git", rev = "986af3f35033a720f21872794d0bd6579c02866c" } +sbp = { git = "https://github.com/swift-nav/libsbp.git", rev = "0563350bb387e4a966a2b558adb7b05b2baf005f" } ordered-float = "2.0" ndarray = "0.14.0" glob = "0.3.0" diff --git a/console_backend/benches/cpu_benches.rs b/console_backend/benches/cpu_benches.rs index 4bf0c90e4..7142bca61 100644 --- a/console_backend/benches/cpu_benches.rs +++ b/console_backend/benches/cpu_benches.rs @@ -1,10 +1,15 @@ #![allow(unused_imports)] use criterion::{criterion_group, criterion_main, Criterion}; use glob::glob; -use std::{fs, path::Path, sync::mpsc, thread, time}; +use std::{ + fs, + path::Path, + sync::{mpsc, Arc, Mutex}, + thread, time, +}; extern crate console_backend; -use console_backend::process_messages; +use console_backend::{process_messages, types::SharedState}; const BENCH_FILEPATH: &str = "./tests/data/piksi-relay.sbp"; const BENCHMARK_TIME_LIMIT: u64 = 10000; @@ -48,7 +53,9 @@ fn run_process_messages(file_in_name: &str, failure: bool) { thread::sleep(time::Duration::from_millis(FAILURE_CASE_SLEEP_MILLIS)); } let messages = sbp::iter_messages(Box::new(fs::File::open(file_in_name).unwrap())); - process_messages::process_messages(messages, client_send); + let shared_state = SharedState::new(); + let shared_state = Arc::new(Mutex::new(shared_state)); + process_messages::process_messages(messages, &shared_state, client_send); } recv_thread.join().expect("join should succeed"); } diff --git a/console_backend/setup.py b/console_backend/setup.py index 1c1a4a1a7..4ac584dca 100644 --- a/console_backend/setup.py +++ b/console_backend/setup.py @@ -20,7 +20,7 @@ def get_py_version_cfgs(): def make_rust_extension(module_name): - return RustExtension(module_name, "Cargo.toml", rustc_flags=get_py_version_cfgs(), debug=True) + return RustExtension(module_name, "Cargo.toml", rustc_flags=get_py_version_cfgs()) setup( diff --git a/console_backend/src/constants.rs b/console_backend/src/constants.rs new file mode 100644 index 000000000..55c0880ce --- /dev/null +++ b/console_backend/src/constants.rs @@ -0,0 +1,671 @@ +use std::{ + cmp::{Eq, PartialEq}, + collections::HashMap, + hash::Hash, +}; + +use crate::types::Error; + +// Tracking Tab constants. +pub const NUM_POINTS: usize = 200; +pub const NUM_SATELLITES: usize = 60; +pub const TRK_RATE: f64 = 2.0; +pub const GLO_SLOT_SAT_MAX: u8 = 90; +pub const GLO_FCN_OFFSET: i16 = 8; +pub const SBAS_NEG_OFFSET: i16 = 120; +pub const QZSS_NEG_OFFSET: i16 = 193; +pub const SNR_THRESHOLD: f64 = 15.0; +pub const TRACKING_SIGNALS_PLOT_MAX: f64 = 60.0; +pub const GUI_UPDATE_PERIOD: f64 = 0.2; +pub const GPS_L1CA: &str = "GPS L1CA"; +pub const GPS_L2C_M: &str = "GPS L2C M"; +pub const GLO_L10F: &str = "GLO L1OF"; +pub const GLO_L20F: &str = "GLO L2OF"; +pub const BDS2_B1_I: &str = "BDS2 B1 I"; +pub const BDS2_B2_I: &str = "BDS2 B2 I"; +pub const GAL_E1_B: &str = "GAL E1 B"; +pub const GAL_E5B_I: &str = "GAL E5b I"; +pub const QZS_L1CA: &str = "QZS L1CA"; +pub const QZS_L2C_M: &str = "QZS L2C M"; +pub const SBAS_L1: &str = "SBAS L1"; +pub const SHOW_LEGEND: &str = "Show Legend"; + +#[repr(u8)] +#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)] +pub enum SignalCodes { + CodeGpsL1Ca = 0, + CodeGpsL2Cm = 1, + CodeGpsL2Cl = 7, + CodeGpsL2Cx = 8, + CodeGpsL1P = 5, + CodeGpsL2P = 6, + CodeGpsL5I = 9, + CodeGpsL5Q = 10, + CodeGpsL5X = 11, + CodeGpsL1Ci = 56, + CodeGpsL1Cq = 57, + CodeGpsL1Cx = 58, + CodeAuxGps = 59, + + CodeGloL1Of = 3, + CodeGloL2Of = 4, + CodeGloL1P = 29, + CodeGloL2P = 30, + + CodeSbasL1Ca = 2, + CodeSbasL5I = 41, + CodeSbasL5Q = 42, + CodeSbasL5X = 43, + CodeAuxSbas = 60, + + CodeBds2B1 = 12, + CodeBds2B2 = 13, + CodeBds3B1Ci = 44, + CodeBds3B1Cq = 45, + CodeBds3B1Cx = 46, + CodeBds3B5I = 47, + CodeBds3B5Q = 48, + CodeBds3B5X = 49, + CodeBds3B7I = 50, + CodeBds3B7Q = 51, + CodeBds3B7X = 52, + CodeBds3B3I = 53, + CodeBds3B3Q = 54, + CodeBds3B3X = 55, + + CodeGalE1B = 14, + CodeGalE1C = 15, + CodeGalE1X = 16, + CodeGalE6B = 17, + CodeGalE6C = 18, + CodeGalE6X = 19, + CodeGalE7I = 20, + CodeGalE7Q = 21, + CodeGalE7X = 22, + CodeGalE8I = 23, + CodeGalE8Q = 24, + CodeGalE8X = 25, + CodeGalE5I = 26, + CodeGalE5Q = 27, + CodeGalE5X = 28, + CodeAuxGal = 61, + + CodeQzsL1Ca = 31, + CodeQzsL1Ci = 32, + CodeQzsL1Cq = 33, + CodeQzsL1Cx = 34, + CodeQzsL2Cm = 35, + CodeQzsL2Cl = 36, + CodeQzsL2Cx = 37, + CodeQzsL5I = 38, + CodeQzsL5Q = 39, + CodeQzsL5X = 40, + CodeAuxQzs = 62, + NotAvailable = u8::MAX, +} + +impl SignalCodes { + pub fn code_is_gps(&self) -> bool { + matches!( + self, + SignalCodes::CodeGpsL1Ca + | SignalCodes::CodeGpsL2Cm + | SignalCodes::CodeGpsL2Cl + | SignalCodes::CodeGpsL2Cx + | SignalCodes::CodeGpsL1P + | SignalCodes::CodeGpsL2P + | SignalCodes::CodeGpsL5I + | SignalCodes::CodeGpsL5Q + | SignalCodes::CodeGpsL5X + | SignalCodes::CodeAuxGps + ) + } + pub fn code_is_glo(&self) -> bool { + matches!( + self, + SignalCodes::CodeGloL1Of + | SignalCodes::CodeGloL2Of + | SignalCodes::CodeGloL1P + | SignalCodes::CodeGloL2P + ) + } + pub fn code_is_sbas(&self) -> bool { + matches!( + self, + SignalCodes::CodeSbasL1Ca + | SignalCodes::CodeSbasL5I + | SignalCodes::CodeSbasL5Q + | SignalCodes::CodeSbasL5X + | SignalCodes::CodeAuxSbas + ) + } + pub fn code_is_bds(&self) -> bool { + matches!( + self, + SignalCodes::CodeBds2B1 + | SignalCodes::CodeBds2B2 + | SignalCodes::CodeBds3B1Ci + | SignalCodes::CodeBds3B1Cq + | SignalCodes::CodeBds3B1Cx + | SignalCodes::CodeBds3B5I + | SignalCodes::CodeBds3B5Q + | SignalCodes::CodeBds3B5X + | SignalCodes::CodeBds3B3I + | SignalCodes::CodeBds3B3Q + | SignalCodes::CodeBds3B3X + | SignalCodes::CodeBds3B7I + | SignalCodes::CodeBds3B7Q + | SignalCodes::CodeBds3B7X + ) + } + + pub fn code_is_galileo(&self) -> bool { + matches!( + self, + SignalCodes::CodeGalE1B + | SignalCodes::CodeGalE1C + | SignalCodes::CodeGalE1X + | SignalCodes::CodeGalE6B + | SignalCodes::CodeGalE6C + | SignalCodes::CodeGalE6X + | SignalCodes::CodeGalE7I + | SignalCodes::CodeGalE7Q + | SignalCodes::CodeGalE7X + | SignalCodes::CodeGalE8I + | SignalCodes::CodeGalE8Q + | SignalCodes::CodeGalE8X + | SignalCodes::CodeGalE5I + | SignalCodes::CodeGalE5Q + | SignalCodes::CodeGalE5X + | SignalCodes::CodeAuxGal + ) + } + + pub fn code_is_qzss(&self) -> bool { + matches!( + self, + SignalCodes::CodeQzsL1Ca + | SignalCodes::CodeQzsL2Cm + | SignalCodes::CodeQzsL2Cl + | SignalCodes::CodeQzsL2Cx + | SignalCodes::CodeQzsL5I + | SignalCodes::CodeQzsL5Q + | SignalCodes::CodeQzsL5X + | SignalCodes::CodeAuxQzs + ) + } + + pub fn filters(&self) -> Option { + match self { + SignalCodes::CodeGpsL1Ca => Some(GPS_L1CA.to_string()), + SignalCodes::CodeGpsL2Cm => Some(GPS_L2C_M.to_string()), + SignalCodes::CodeGloL1Of => Some(GLO_L10F.to_string()), + SignalCodes::CodeGloL2Of => Some(GLO_L20F.to_string()), + SignalCodes::CodeBds2B1 => Some(BDS2_B1_I.to_string()), + SignalCodes::CodeBds2B2 => Some(BDS2_B2_I.to_string()), + SignalCodes::CodeGalE1B => Some(GAL_E1_B.to_string()), + SignalCodes::CodeGalE7I => Some(GAL_E5B_I.to_string()), + SignalCodes::CodeQzsL1Ca => Some(QZS_L1CA.to_string()), + SignalCodes::CodeQzsL2Cm => Some(QZS_L2C_M.to_string()), + SignalCodes::CodeSbasL1Ca => Some(SBAS_L1.to_string()), + _ => None, + } + } +} + +impl From for SignalCodes { + fn from(s: u8) -> Self { + match s { + 0 => SignalCodes::CodeGpsL1Ca, + 1 => SignalCodes::CodeGpsL2Cm, + 7 => SignalCodes::CodeGpsL2Cl, + 8 => SignalCodes::CodeGpsL2Cx, + 5 => SignalCodes::CodeGpsL1P, + 6 => SignalCodes::CodeGpsL2P, + 9 => SignalCodes::CodeGpsL5I, + 10 => SignalCodes::CodeGpsL5Q, + 11 => SignalCodes::CodeGpsL5X, + 56 => SignalCodes::CodeGpsL1Ci, + 57 => SignalCodes::CodeGpsL1Cq, + 58 => SignalCodes::CodeGpsL1Cx, + 59 => SignalCodes::CodeAuxGps, + + 3 => SignalCodes::CodeGloL1Of, + 4 => SignalCodes::CodeGloL2Of, + 29 => SignalCodes::CodeGloL1P, + 30 => SignalCodes::CodeGloL2P, + + 2 => SignalCodes::CodeSbasL1Ca, + 41 => SignalCodes::CodeSbasL5I, + 42 => SignalCodes::CodeSbasL5Q, + 43 => SignalCodes::CodeSbasL5X, + 60 => SignalCodes::CodeAuxSbas, + + 12 => SignalCodes::CodeBds2B1, + 13 => SignalCodes::CodeBds2B2, + 44 => SignalCodes::CodeBds3B1Ci, + 45 => SignalCodes::CodeBds3B1Cq, + 46 => SignalCodes::CodeBds3B1Cx, + 47 => SignalCodes::CodeBds3B5I, + 48 => SignalCodes::CodeBds3B5Q, + 49 => SignalCodes::CodeBds3B5X, + 50 => SignalCodes::CodeBds3B7I, + 51 => SignalCodes::CodeBds3B7Q, + 52 => SignalCodes::CodeBds3B7X, + 53 => SignalCodes::CodeBds3B3I, + 54 => SignalCodes::CodeBds3B3Q, + 55 => SignalCodes::CodeBds3B3X, + + 14 => SignalCodes::CodeGalE1B, + 15 => SignalCodes::CodeGalE1C, + 16 => SignalCodes::CodeGalE1X, + 17 => SignalCodes::CodeGalE6B, + 18 => SignalCodes::CodeGalE6C, + 19 => SignalCodes::CodeGalE6X, + 20 => SignalCodes::CodeGalE7I, + 21 => SignalCodes::CodeGalE7Q, + 22 => SignalCodes::CodeGalE7X, + 23 => SignalCodes::CodeGalE8I, + 24 => SignalCodes::CodeGalE8Q, + 25 => SignalCodes::CodeGalE8X, + 26 => SignalCodes::CodeGalE5I, + 27 => SignalCodes::CodeGalE5Q, + 28 => SignalCodes::CodeGalE5X, + 61 => SignalCodes::CodeAuxGal, + + 31 => SignalCodes::CodeQzsL1Ca, + 32 => SignalCodes::CodeQzsL1Ci, + 33 => SignalCodes::CodeQzsL1Cq, + 34 => SignalCodes::CodeQzsL1Cx, + 35 => SignalCodes::CodeQzsL2Cm, + 36 => SignalCodes::CodeQzsL2Cl, + 37 => SignalCodes::CodeQzsL2Cx, + 38 => SignalCodes::CodeQzsL5I, + 39 => SignalCodes::CodeQzsL5Q, + 40 => SignalCodes::CodeQzsL5X, + 62 => SignalCodes::CodeAuxQzs, + _ => SignalCodes::NotAvailable, + } + } +} + +impl std::str::FromStr for SignalCodes { + type Err = Error; + + /// Retrieve the signal constellation, band and code based off provided string. + /// + /// # Parameters + /// + /// - `sat_str`: The signal code. + fn from_str(s: &str) -> Result { + match s { + GPS_L1CA_STR => Ok(SignalCodes::CodeGpsL1Ca), + GPS_L2CM_STR => Ok(SignalCodes::CodeGpsL2Cm), + GPS_L2CL_STR => Ok(SignalCodes::CodeGpsL2Cl), + GPS_L2CX_STR => Ok(SignalCodes::CodeGpsL2Cx), + GPS_L5I_STR => Ok(SignalCodes::CodeGpsL5I), + GPS_L5Q_STR => Ok(SignalCodes::CodeGpsL5Q), + GPS_L5X_STR => Ok(SignalCodes::CodeGpsL5X), + GPS_L1P_STR => Ok(SignalCodes::CodeGpsL1P), + GPS_L2P_STR => Ok(SignalCodes::CodeGpsL2P), + GPS_AUX_STR => Ok(SignalCodes::CodeAuxGps), + + SBAS_L1_STR => Ok(SignalCodes::CodeSbasL1Ca), + SBAS_L5I_STR => Ok(SignalCodes::CodeSbasL5I), + SBAS_L5Q_STR => Ok(SignalCodes::CodeSbasL5Q), + SBAS_L5X_STR => Ok(SignalCodes::CodeSbasL5X), + SBAS_AUX_STR => Ok(SignalCodes::CodeAuxSbas), + + GLO_L1OF_STR => Ok(SignalCodes::CodeGloL1Of), + GLO_L2OF_STR => Ok(SignalCodes::CodeGloL2Of), + GLO_L1P_STR => Ok(SignalCodes::CodeGloL1P), + GLO_L2P_STR => Ok(SignalCodes::CodeGloL2P), + + BDS2_B1_STR => Ok(SignalCodes::CodeBds2B1), + BDS2_B2_STR => Ok(SignalCodes::CodeBds2B2), + BDS3_B1CI_STR => Ok(SignalCodes::CodeBds3B1Ci), + BDS3_B1CQ_STR => Ok(SignalCodes::CodeBds3B1Cq), + BDS3_B1CX_STR => Ok(SignalCodes::CodeBds3B1Cx), + BDS3_B5I_STR => Ok(SignalCodes::CodeBds3B5I), + BDS3_B5Q_STR => Ok(SignalCodes::CodeBds3B5Q), + BDS3_B5X_STR => Ok(SignalCodes::CodeBds3B5X), + BDS3_B7I_STR => Ok(SignalCodes::CodeBds3B7I), + BDS3_B7Q_STR => Ok(SignalCodes::CodeBds3B7Q), + BDS3_B7X_STR => Ok(SignalCodes::CodeBds3B7X), + BDS3_B3I_STR => Ok(SignalCodes::CodeBds3B3I), + BDS3_B3Q_STR => Ok(SignalCodes::CodeBds3B3Q), + BDS3_B3X_STR => Ok(SignalCodes::CodeBds3B3X), + + GAL_E1B_STR => Ok(SignalCodes::CodeGalE1B), + GAL_E1C_STR => Ok(SignalCodes::CodeGalE1C), + GAL_E1X_STR => Ok(SignalCodes::CodeGalE1X), + GAL_E5I_STR => Ok(SignalCodes::CodeGalE5I), + GAL_E5Q_STR => Ok(SignalCodes::CodeGalE5Q), + GAL_E5X_STR => Ok(SignalCodes::CodeGalE5X), + GAL_E6B_STR => Ok(SignalCodes::CodeGalE6B), + GAL_E6C_STR => Ok(SignalCodes::CodeGalE6C), + GAL_E6X_STR => Ok(SignalCodes::CodeGalE6X), + GAL_E7I_STR => Ok(SignalCodes::CodeGalE7I), + GAL_E7Q_STR => Ok(SignalCodes::CodeGalE7Q), + GAL_E7X_STR => Ok(SignalCodes::CodeGalE7X), + GAL_E8I_STR => Ok(SignalCodes::CodeGalE8I), + // GAL_E8Q_STR => SignalCodes::CodeGalE8Q, // Unreachable + // GAL_E8X_STR => SignalCodes::CodeGalE8X, // Unreachable + GAL_AUX_STR => Ok(SignalCodes::CodeAuxGal), + + QZS_L1CA_STR => Ok(SignalCodes::CodeQzsL1Ca), + QZS_L2CM_STR => Ok(SignalCodes::CodeQzsL2Cm), + QZS_L2CL_STR => Ok(SignalCodes::CodeQzsL2Cl), + QZS_L2CX_STR => Ok(SignalCodes::CodeQzsL2Cx), + QZS_L5I_STR => Ok(SignalCodes::CodeQzsL5I), + QZS_L5Q_STR => Ok(SignalCodes::CodeQzsL5Q), + QZS_L5X_STR => Ok(SignalCodes::CodeQzsL5X), + QZS_AUX_STR => Ok(SignalCodes::CodeAuxQzs), + _ => Ok(SignalCodes::NotAvailable), + } + } +} + +impl ToString for SignalCodes { + /// Retrieve associated string with the provided signal code. + /// + /// # Parameters + /// + /// - `key`: The code, which is signal code, and satellite constellation-specific satellite identifier. + fn to_string(&self) -> String { + let sat_code_str = match self { + SignalCodes::CodeGpsL1Ca => GPS_L1CA_STR, + SignalCodes::CodeGpsL2Cm => GPS_L2CM_STR, + SignalCodes::CodeGpsL2Cl => GPS_L2CL_STR, + SignalCodes::CodeGpsL2Cx => GPS_L2CX_STR, + SignalCodes::CodeGpsL1P => GPS_L1P_STR, + SignalCodes::CodeGpsL2P => GPS_L2P_STR, + SignalCodes::CodeGpsL5I => GPS_L5I_STR, + SignalCodes::CodeGpsL5Q => GPS_L5Q_STR, + SignalCodes::CodeGpsL5X => GPS_L5X_STR, + SignalCodes::CodeAuxGps => GPS_AUX_STR, + + SignalCodes::CodeGloL1Of => GLO_L1OF_STR, + SignalCodes::CodeGloL2Of => GLO_L2OF_STR, + SignalCodes::CodeGloL1P => GLO_L1P_STR, + SignalCodes::CodeGloL2P => GLO_L2P_STR, + + SignalCodes::CodeSbasL1Ca => SBAS_L1_STR, + SignalCodes::CodeSbasL5I => SBAS_L5I_STR, + SignalCodes::CodeSbasL5Q => SBAS_L5Q_STR, + SignalCodes::CodeSbasL5X => SBAS_L5X_STR, + SignalCodes::CodeAuxSbas => SBAS_AUX_STR, + + SignalCodes::CodeBds2B1 => BDS2_B1_STR, + SignalCodes::CodeBds2B2 => BDS2_B2_STR, + SignalCodes::CodeBds3B1Ci => BDS3_B1CI_STR, + SignalCodes::CodeBds3B1Cq => BDS3_B1CQ_STR, + SignalCodes::CodeBds3B1Cx => BDS3_B1CX_STR, + SignalCodes::CodeBds3B5I => BDS3_B5I_STR, + SignalCodes::CodeBds3B5Q => BDS3_B5Q_STR, + SignalCodes::CodeBds3B5X => BDS3_B5X_STR, + SignalCodes::CodeBds3B7I => BDS3_B7I_STR, + SignalCodes::CodeBds3B7Q => BDS3_B7Q_STR, + SignalCodes::CodeBds3B7X => BDS3_B7X_STR, + SignalCodes::CodeBds3B3I => BDS3_B3I_STR, + SignalCodes::CodeBds3B3Q => BDS3_B3Q_STR, + SignalCodes::CodeBds3B3X => BDS3_B3X_STR, + + SignalCodes::CodeGalE1B => GAL_E1B_STR, + SignalCodes::CodeGalE1C => GAL_E1C_STR, + SignalCodes::CodeGalE1X => GAL_E1X_STR, + SignalCodes::CodeGalE6B => GAL_E6B_STR, + SignalCodes::CodeGalE6C => GAL_E6C_STR, + SignalCodes::CodeGalE6X => GAL_E6X_STR, + SignalCodes::CodeGalE7I => GAL_E7I_STR, + SignalCodes::CodeGalE7Q => GAL_E7Q_STR, + SignalCodes::CodeGalE7X => GAL_E7X_STR, + SignalCodes::CodeGalE8I => GAL_E8I_STR, + SignalCodes::CodeGalE8Q => GAL_E8Q_STR, + SignalCodes::CodeGalE8X => GAL_E8X_STR, + SignalCodes::CodeGalE5I => GAL_E5I_STR, + SignalCodes::CodeGalE5Q => GAL_E5Q_STR, + SignalCodes::CodeGalE5X => GAL_E5X_STR, + SignalCodes::CodeAuxGal => GAL_AUX_STR, + + SignalCodes::CodeQzsL1Ca => QZS_L1CA_STR, + SignalCodes::CodeQzsL2Cm => QZS_L2CM_STR, + SignalCodes::CodeQzsL2Cl => QZS_L2CL_STR, + SignalCodes::CodeQzsL2Cx => QZS_L2CX_STR, + SignalCodes::CodeQzsL5I => QZS_L5I_STR, + SignalCodes::CodeQzsL5Q => QZS_L5Q_STR, + SignalCodes::CodeQzsL5X => QZS_L5X_STR, + _ => CODE_NOT_AVAILABLE, + }; + String::from(sat_code_str) + } +} + +pub fn get_label( + key: (SignalCodes, i16), + extra: &HashMap, +) -> (Option, Option, Option) { + let (code, sat) = key; + let code_lbl = Some(code.to_string()); + let mut freq_lbl = None; + let id_lbl; + + if code.code_is_glo() { + let freq_lbl_ = format!("F+{:02}", sat); + freq_lbl = Some(freq_lbl_); + if extra.contains_key(&sat) { + id_lbl = Some(format!("R{:<02}", extra[&sat])); + } else { + id_lbl = Some(format!("R{:<02}", sat)); + } + } else if code.code_is_sbas() { + id_lbl = Some(format!("S{: >3}", sat)); + } else if code.code_is_bds() { + id_lbl = Some(format!("C{:0>2}", sat)); + } else if code.code_is_qzss() { + id_lbl = Some(format!("J{: >3}", sat)); + } else if code.code_is_galileo() { + id_lbl = Some(format!("E{:0>2}", sat)); + } else { + id_lbl = Some(format!("G{:0>2}", sat)); + } + (code_lbl, freq_lbl, id_lbl) +} + +pub const GPS_L1CA_STR: &str = "GPS L1CA"; +pub const GPS_L2CM_STR: &str = "GPS L2C M"; +pub const GPS_L2CL_STR: &str = "GPS L2C L"; +pub const GPS_L2CX_STR: &str = "GPS L2C M+L"; +pub const GPS_L1P_STR: &str = "GPS L1P"; +pub const GPS_L2P_STR: &str = "GPS L2P"; +pub const GPS_L5I_STR: &str = "GPS L5 I"; +pub const GPS_L5Q_STR: &str = "GPS L5 Q"; +pub const GPS_L5X_STR: &str = "GPS L5 I+Q"; +pub const GPS_AUX_STR: &str = "AUX GPS L1"; + +pub const SBAS_L1_STR: &str = "SBAS L1"; +pub const SBAS_L5I_STR: &str = "SBAS L5 I"; +pub const SBAS_L5Q_STR: &str = "SBAS L5 Q"; +pub const SBAS_L5X_STR: &str = "SBAS L5 I+Q"; +pub const SBAS_AUX_STR: &str = "AUX SBAS L1"; + +pub const GLO_L1OF_STR: &str = "GLO L1OF"; +pub const GLO_L2OF_STR: &str = "GLO L2OF"; +pub const GLO_L1P_STR: &str = "GLO L1P"; +pub const GLO_L2P_STR: &str = "GLO L2P"; + +pub const BDS2_B1_STR: &str = "BDS2 B1 I"; +pub const BDS2_B2_STR: &str = "BDS2 B2 I"; +pub const BDS3_B1CI_STR: &str = "BDS3 B1C I"; +pub const BDS3_B1CQ_STR: &str = "BDS3 B1C Q"; +pub const BDS3_B1CX_STR: &str = "BDS3 B1C I+Q"; +pub const BDS3_B5I_STR: &str = "BDS3 B2a I"; +pub const BDS3_B5Q_STR: &str = "BDS3 B2a Q"; +pub const BDS3_B5X_STR: &str = "BDS3 B2a X"; +pub const BDS3_B7I_STR: &str = "BDS3 B2b I"; +pub const BDS3_B7Q_STR: &str = "BDS3 B2b Q"; +pub const BDS3_B7X_STR: &str = "BDS3 B2b X"; +pub const BDS3_B3I_STR: &str = "BDS3 B3I"; +pub const BDS3_B3Q_STR: &str = "BDS3 B3Q"; +pub const BDS3_B3X_STR: &str = "BDS3 B3X"; +pub const BDS3_AUX_STR: &str = "AUX BDS B1"; + +pub const GAL_E1B_STR: &str = "GAL E1 B"; +pub const GAL_E1C_STR: &str = "GAL E1 C"; +pub const GAL_E1X_STR: &str = "GAL E1 B+C"; +pub const GAL_E5I_STR: &str = "GAL E5a I"; +pub const GAL_E5Q_STR: &str = "GAL E5a Q"; +pub const GAL_E5X_STR: &str = "GAL E5a I+Q"; +pub const GAL_E6B_STR: &str = "GAL E6 B"; +pub const GAL_E6C_STR: &str = "GAL E6 C"; +pub const GAL_E6X_STR: &str = "GAL E6 B+C"; +pub const GAL_E8I_STR: &str = "GAL AltBOC"; +pub const GAL_E8Q_STR: &str = "GAL AltBOC"; +pub const GAL_E8X_STR: &str = "GAL AltBOC"; +pub const GAL_E7I_STR: &str = "GAL E5b I"; +pub const GAL_E7Q_STR: &str = "GAL E5b Q"; +pub const GAL_E7X_STR: &str = "GAL E5b I+Q"; +pub const GAL_AUX_STR: &str = "AUX GAL E1"; + +pub const QZS_L1CA_STR: &str = "QZS L1CA"; +pub const QZS_L2CM_STR: &str = "QZS L2C M"; +pub const QZS_L2CL_STR: &str = "QZS L2C L"; +pub const QZS_L2CX_STR: &str = "QZS L2C M+L"; +pub const QZS_L5I_STR: &str = "QZS L5 I"; +pub const QZS_L5Q_STR: &str = "QZS L5 Q"; +pub const QZS_L5X_STR: &str = "QZS L5 I+Q"; +pub const QZS_AUX_STR: &str = "AUX QZS L1"; + +pub const CODE_NOT_AVAILABLE: &str = "N/A"; + +/// These colors are distinguishable from one another based on expected codes. +/// +/// # Parameters +/// +/// - `code`: The signal code. +pub fn color_map(code: i16) -> &'static str { + match code { + 1 => "#e58a8a", + 2 => "#664949", + 3 => "#590c00", + 4 => "#cc4631", + 5 => "#e56c1c", + 6 => "#4c2a12", + 7 => "#996325", + 8 => "#f2b774", + 9 => "#ffaa00", + 10 => "#ccb993", + 11 => "#997a00", + 12 => "#4c4700", + 13 => "#d0d94e", + 14 => "#aaff00", + 15 => "#4ea614", + 16 => "#123306", + 17 => "#18660c", + 18 => "#6e9974", + 19 => "#8ae6a2", + 20 => "#00ff66", + 21 => "#57f2e8", + 22 => "#1f7980", + 23 => "#263e40", + 24 => "#004d73", + 25 => "#37abe6", + 26 => "#7790a6", + 27 => "#144ea6", + 28 => "#263040", + 29 => "#152859", + 30 => "#1d39f2", + 31 => "#828ed9", + 32 => "#000073", + 33 => "#000066", + 34 => "#8c7aff", + 35 => "#1b0033", + 36 => "#d900ca", + 37 => "#730e6c", + _ => "#ff0000", + } +} + +const NUM_COLORS: u8 = 37; + +/// Retreive the associated color based on provided key. +/// +/// # Parameters +/// +/// - `key`: The code, which is signal code and satellite constellation-specific satellite identifier. +pub fn get_color(key: (SignalCodes, i16)) -> &'static str { + let (code, mut sat) = key; + + if code.code_is_glo() { + sat += GLO_FCN_OFFSET; + } else if code.code_is_sbas() { + sat -= SBAS_NEG_OFFSET; + } else if code.code_is_qzss() { + sat -= QZSS_NEG_OFFSET; + } + if sat > NUM_COLORS as i16 { + sat %= NUM_COLORS as i16; + } + color_map(sat) +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn get_label_test() { + let mut extra: HashMap = HashMap::new(); + extra.insert( + SignalCodes::CodeGloL2P as i16, + SignalCodes::CodeGloL2P as i16, + ); + + let (code_lbl, freq_lbl, id_lbl) = get_label( + (SignalCodes::CodeGloL2P, SignalCodes::CodeGloL2P as i16), + &extra, + ); + assert_eq!(code_lbl.unwrap(), GLO_L2P_STR); + assert_eq!(freq_lbl.unwrap(), "F+30"); + assert_eq!(id_lbl.unwrap(), "R30"); + + let (code_lbl, freq_lbl, id_lbl) = get_label( + (SignalCodes::CodeGloL2Of, SignalCodes::CodeGloL2Of as i16), + &extra, + ); + assert_eq!(code_lbl.unwrap(), GLO_L2OF_STR); + assert_eq!(freq_lbl.unwrap(), "F+04"); + assert_eq!(id_lbl.unwrap(), "R04"); + + let (code_lbl, freq_lbl, id_lbl) = get_label( + (SignalCodes::CodeSbasL5Q, SignalCodes::CodeSbasL5Q as i16), + &extra, + ); + assert_eq!(code_lbl.unwrap(), SBAS_L5Q_STR); + assert_eq!(freq_lbl, None); + assert_eq!(id_lbl.unwrap(), "S 42"); + + let (code_lbl, freq_lbl, id_lbl) = get_label( + (SignalCodes::CodeBds3B5Q, SignalCodes::CodeBds3B5Q as i16), + &extra, + ); + assert_eq!(code_lbl.unwrap(), BDS3_B5Q_STR); + assert_eq!(freq_lbl, None); + assert_eq!(id_lbl.unwrap(), "C48"); + + let (code_lbl, freq_lbl, id_lbl) = get_label( + (SignalCodes::CodeQzsL2Cx, SignalCodes::CodeQzsL2Cx as i16), + &extra, + ); + assert_eq!(code_lbl.unwrap(), QZS_L2CX_STR); + assert_eq!(freq_lbl, None); + assert_eq!(id_lbl.unwrap(), "J 37"); + + let (code_lbl, freq_lbl, id_lbl) = get_label( + (SignalCodes::CodeGalE8X, SignalCodes::CodeGalE8X as i16), + &extra, + ); + assert_eq!(code_lbl.unwrap(), GAL_E8X_STR); + assert_eq!(freq_lbl, None); + assert_eq!(id_lbl.unwrap(), "E25"); + } +} diff --git a/console_backend/src/lib.rs b/console_backend/src/lib.rs index 8a164eda7..21b713e86 100644 --- a/console_backend/src/lib.rs +++ b/console_backend/src/lib.rs @@ -1,5 +1,8 @@ -pub mod process_messages; -pub mod server; pub mod console_backend_capnp { include!(concat!(env!("OUT_DIR"), "/console_backend_capnp.rs")); } +pub mod constants; +pub mod process_messages; +pub mod server; +pub mod tracking_tab; +pub mod types; diff --git a/console_backend/src/process_messages.rs b/console_backend/src/process_messages.rs index 62b13f9bf..f4dac7361 100644 --- a/console_backend/src/process_messages.rs +++ b/console_backend/src/process_messages.rs @@ -1,77 +1,57 @@ use capnp::message::Builder; use capnp::serialize; -use ordered_float::*; +use ordered_float::OrderedFloat; use sbp::messages::SBP; -use std::sync::mpsc; +use std::sync::{mpsc, Arc, Mutex}; use crate::console_backend_capnp as m; +use crate::tracking_tab::*; +use crate::types::SharedState; + pub fn process_messages( messages: impl Iterator>, + shared_state: &Arc>, client_send_clone: mpsc::Sender>, ) { let mut hpoints: Vec<(f64, OrderedFloat)> = vec![]; let mut vpoints: Vec<(f64, OrderedFloat)> = vec![]; - let mut sat_headers: Vec = vec![]; - let mut sats: Vec)>> = vec![]; - let mut tow: f64 = 0.0; + let mut tow: f64; + let shared_state_clone = Arc::clone(&shared_state); + let mut tracking_signals = TrackingSignalsTab::new(&shared_state_clone); + for message in messages { match message { + Ok(SBP::MsgTrackingState(msg)) => { + tracking_signals + .handle_msg_tracking_state(msg.states.clone(), client_send_clone.clone()); + } + Ok(SBP::MsgObs(msg)) => { + tracking_signals.handle_obs( + ObservationMsg::MsgObs(msg.clone()), + client_send_clone.clone(), + ); + } Ok(SBP::MsgMeasurementState(msg)) => { - for state in msg.states { - if state.cn0 != 0 { - let points = - match sat_headers.iter().position(|&ele| ele == state.mesid.sat) { - Some(idx) => sats.get_mut(idx).unwrap(), - _ => { - sat_headers.push(state.mesid.sat); - sats.push(Vec::new()); - sats.last_mut().unwrap() - } - }; - if points.len() >= 200 { - points.remove(0); - } - points.push((tow, OrderedFloat(state.cn0 as f64 / 4.0))); - } - } - let mut builder = Builder::new_default(); - let msg = builder.init_root::(); - - let mut tracking_status = msg.init_tracking_status(); - tracking_status.set_min(0_f64); - tracking_status.set_max(60_f64); - let mut tracking_headers = tracking_status - .reborrow() - .init_headers(sat_headers.len() as u32); - - for (i, header) in sat_headers.iter().enumerate() { - tracking_headers.set(i as u32, *header); - } - - let mut tracking_points = tracking_status - .reborrow() - .init_data(sat_headers.len() as u32); - { - for idx in 0..sat_headers.len() { - let points = sats.get_mut(idx).unwrap(); - let mut point_val_idx = tracking_points - .reborrow() - .init(idx as u32, points.len() as u32); - for (i, (x, OrderedFloat(y))) in points.iter().enumerate() { - let mut point_val = point_val_idx.reborrow().get(i as u32); - point_val.set_x(*x); - point_val.set_y(*y); - } - } - } - let mut msg_bytes: Vec = vec![]; - serialize::write_message(&mut msg_bytes, &builder).unwrap(); - - client_send_clone.send(msg_bytes).unwrap(); + tracking_signals + .handle_msg_measurement_state(msg.states.clone(), client_send_clone.clone()); + } + Ok(SBP::MsgObsDepA(_msg)) => { + //CPP-85 Unhandled for tracking signals plot tab. + println!("The message type, MsgObsDepA, is not handled in the Tracking->SignalsPlot tab."); + } + Ok(SBP::MsgObsDepB(msg)) => { + tracking_signals.handle_obs( + ObservationMsg::MsgObsDepB(msg.clone()), + client_send_clone.clone(), + ); + } + Ok(SBP::MsgObsDepC(msg)) => { + tracking_signals.handle_obs( + ObservationMsg::MsgObsDepC(msg.clone()), + client_send_clone.clone(), + ); } - Ok(SBP::MsgTrackingState(_msg)) => {} - Ok(SBP::MsgObs(_msg)) => {} Ok(SBP::MsgVelNED(velocity_ned)) => { let n = velocity_ned.n as f64; diff --git a/console_backend/src/server.rs b/console_backend/src/server.rs index 3c790a888..f7e352414 100644 --- a/console_backend/src/server.rs +++ b/console_backend/src/server.rs @@ -11,11 +11,12 @@ use pyo3::types::PyBytes; use std::fs; use std::io::{BufReader, Cursor}; use std::net::TcpStream; -use std::sync::mpsc; +use std::sync::{mpsc, Arc, Mutex}; use std::thread; use crate::console_backend_capnp as m; use crate::process_messages::process_messages; +use crate::types::SharedState; const CLOSE: &str = "CLOSE"; @@ -94,6 +95,8 @@ impl Server { server_send: Some(server_send), }; let client_send_clone = client_send; + let shared_state = SharedState::new(); + let shared_state = Arc::new(Mutex::new(shared_state)); thread::spawn(move || loop { let buf = server_recv.recv(); let client_send_clone = client_send_clone.clone(); @@ -111,11 +114,13 @@ impl Server { let port = conn_req.get_port(); println!("connect request, host: {}, port: {}", host, port); let host_port = format!("{}:{}", host, port); + let shared_state_clone = Arc::clone(&shared_state); thread::spawn(move || { if let Ok(stream) = TcpStream::connect(host_port) { println!("Connected to the server!"); let messages = sbp::iter_messages(stream); - process_messages(messages, client_send_clone); + // let shared_state_clone = Arc::clone(&shared_state_clone); + process_messages(messages, &shared_state_clone, client_send_clone); } else { println!("Couldn't connect to server..."); } @@ -125,12 +130,19 @@ impl Server { let filename = file_in.get_filename().unwrap(); let filename = filename.to_string(); println!("{}", filename); + let shared_state_clone = Arc::clone(&shared_state); thread::spawn(move || { if let Ok(stream) = fs::File::open(filename) { println!("Opened file successfully!"); let messages = sbp::iter_messages(stream); - process_messages(messages, client_send_clone.clone()); + // process_messages(messages, &Arc::clone(&shared_state), client_send_clone.clone()); + + process_messages( + messages, + &shared_state_clone, + client_send_clone.clone(), + ); let mut builder = Builder::new_default(); let msg = builder.init_root::(); let mut status = msg.init_status(); @@ -143,6 +155,18 @@ impl Server { } }); } + Ok(m::message::Which::TrackingStatusFront(Ok(cv_in))) => { + let check_visibility = cv_in.get_check_visibility().unwrap(); + let check_visibility: Vec = check_visibility + .iter() + .map(|x| String::from(x.unwrap())) + .collect(); + let shared_state_clone = Arc::clone(&shared_state); + { + let mut shared_data = shared_state_clone.lock().unwrap(); + (*shared_data).tracking_tab.check_visibility = check_visibility; + } + } Ok(_) => { println!("something else"); } diff --git a/console_backend/src/tracking_tab.rs b/console_backend/src/tracking_tab.rs new file mode 100644 index 000000000..ff805ea94 --- /dev/null +++ b/console_backend/src/tracking_tab.rs @@ -0,0 +1,511 @@ +use ordered_float::OrderedFloat; +use std::{ + collections::{HashMap, VecDeque}, + sync::{mpsc::Sender, Arc, Mutex}, + time::Instant, +}; + +use capnp::message::Builder; +use capnp::serialize; + +use crate::console_backend_capnp as m; +use crate::constants::*; +use crate::types::*; +use sbp::messages::{ + observation::{ + MsgObs, MsgObsDepB, MsgObsDepC, PackedObsContent, PackedObsContentDepB, + PackedObsContentDepC, + }, + tracking::{MeasurementState, TrackingChannelState}, +}; + +pub type Cn0Dict = HashMap<(SignalCodes, i16), Deque<(OrderedFloat, f64)>>; +pub type Cn0Age = HashMap<(SignalCodes, i16), f64>; + +/// TrackingSignalsTab struct. +/// +/// # Fields: +/// +/// - `at_least_one_track_received`: Whether a MsgTrackingState has been received. If so block Obs Msgs from being processed. +/// - `cn0_age`: Main storage of (code, sat) keys corresponding to cn0 age. +/// - `cn0_dict`: Main storage of (code, sat) keys corresponding to cn0 values. +/// - `colors`: Stored rgb codes for frontend correspond to index of sv_labels. +/// - `glo_fcn_dict`: Storage of glonass sat codes if 100 +[-6, 7] case. +/// - `glo_slot_dict`: Storage of glonass sat codes if [1,28] slot. +/// - `gps_tow`: The GPS Time of Week. +/// - `gps_week`: The GPS week. +/// - `incoming_obs_cn0`: Map used for accumulating (key, cn0) pairs before performing update_from_obs. +/// - `last_update_time`: Instant monotonic time checking if enough time has passed before performing update_from_obs. +/// - `max`: Stored maximum dB-Hz used for frontend plot. +/// - `min`: Stored minimum dB-Hz used for frontend plot. +/// - `prev_obs_count`: Previous observation count of total expected messages. +/// - `prev_obs_total`: Previous total expected observation messages. +/// - `sats`: Stored satellite data (NUM_SATS, NUM_POINTS) to be sent to frontend. +/// - `sv_labels`: Vector used to store sorted labels before sending to frontend. +/// - `t_init`: Instant monotonic time used as starting reference time. +/// - `time`: Vector of Monotic times stored. +#[derive(Debug)] +pub struct TrackingSignalsTab<'a> { + pub at_least_one_track_received: bool, + pub check_labels: [&'static str; 12], + pub cn0_age: Cn0Age, + pub cn0_dict: Cn0Dict, + pub colors: Vec, + pub glo_fcn_dict: HashMap, + pub glo_slot_dict: HashMap, + pub gps_tow: f64, + pub gps_week: u16, + pub incoming_obs_cn0: HashMap<(SignalCodes, i16), f64>, + pub last_update_time: Instant, + pub max: f64, + pub min: f64, + pub prev_obs_count: u8, + pub prev_obs_total: u8, + pub received_codes: Vec, + pub sats: Vec, f64)>>, + pub shared_state: &'a Arc>, + pub sv_labels: Vec, + pub t_init: Instant, + pub time: VecDeque, +} + +impl<'a> TrackingSignalsTab<'a> { + pub fn new(shared_state: &'a Arc>) -> TrackingSignalsTab { + TrackingSignalsTab { + at_least_one_track_received: false, + check_labels: [ + SHOW_LEGEND, + GPS_L1CA, + GPS_L2C_M, + GLO_L10F, + GLO_L20F, + BDS2_B1_I, + BDS2_B2_I, + GAL_E1_B, + GAL_E5B_I, + QZS_L1CA, + QZS_L2C_M, + SBAS_L1, + ], + cn0_dict: Cn0Dict::new(), + cn0_age: Cn0Age::new(), + colors: Vec::new(), + glo_fcn_dict: HashMap::new(), + glo_slot_dict: HashMap::new(), + gps_tow: 0.0, + gps_week: 0, + incoming_obs_cn0: HashMap::new(), + last_update_time: Instant::now(), + max: TRACKING_SIGNALS_PLOT_MAX, + min: SNR_THRESHOLD, + prev_obs_count: 0, + prev_obs_total: 0, + received_codes: Vec::new(), + sats: Vec::new(), + shared_state, + sv_labels: Vec::new(), + t_init: Instant::now(), + time: { + let mut time: Deque = Deque::with_capacity(NUM_POINTS); + for x in (0..(NUM_POINTS as i32)).rev() { + time.push_back((-x as f64) * (1.0 / TRK_RATE)); + } + time + }, + } + } + /// Push to existing entry in cn0 dict or create Deque and store. + /// + /// # Parameters: + /// + /// - `key`: The (code, sat) to store. + /// - `cn0`: The carrier-to-noise density. + fn push_to_cn0_dict(&mut self, key: (SignalCodes, i16), t: f64, cn0: f64) { + let cn0_deque = self + .cn0_dict + .entry(key) + .or_insert_with(|| Deque::with_capacity(NUM_POINTS)); + cn0_deque.add((OrderedFloat(t), cn0)); + } + /// Push carrier-to-noise density age to cn0_age with key. + /// + /// # Parameters: + /// + /// - `key`: The (code, sat) to store. + /// - `age`: The time elapsed since last start. + fn push_to_cn0_age(&mut self, key: (SignalCodes, i16), age: f64) { + let cn0_age = self.cn0_age.entry(key).or_insert(-1.0); + *cn0_age = age; + } + + /// Remove cn0 data if age is too old. + pub fn clean_cn0(&mut self) { + let mut remove_vec: Vec<(SignalCodes, i16)> = Vec::new(); + for (key, _) in self.cn0_dict.iter_mut() { + if self.cn0_age[key] < self.time[0] { + remove_vec.push(*key); + } + } + for key in remove_vec { + self.cn0_dict.remove(&key); + self.cn0_age.remove(&key); + } + } + + /// Clear and prepare sv_labels, colors, and sats to send data to frontend. + pub fn update_plot(&mut self) { + self.sv_labels.clear(); + self.colors.clear(); + self.sats.clear(); + let mut temp_labels = Vec::new(); + let filters; + { + let shared_data = self.shared_state.lock().unwrap(); + filters = (*shared_data).tracking_tab.check_visibility.clone(); + } + for (key, _) in self.cn0_dict.iter_mut() { + let (signal_code, _) = key; + if let Some(filter) = signal_code.filters() { + if filters.contains(&filter) { + continue; + } + } + let (code_lbl, freq_lbl, id_lbl) = get_label(*key, &self.glo_slot_dict); + let mut label = String::from(""); + if let Some(lbl) = code_lbl { + label = format!("{} {}", label, lbl); + } + if let Some(lbl) = freq_lbl { + label = format!("{} {}", label, lbl); + } + if let Some(lbl) = id_lbl { + label = format!("{} {}", label, lbl); + } + + temp_labels.push((label, *key)); + } + temp_labels.sort_by(|x, y| (x.0).cmp(&(y.0))); + + for (label, key) in temp_labels.iter() { + self.sv_labels.push(label.clone()); + self.colors.push(String::from(get_color(*key))); + self.sats.push(self.cn0_dict[key].clone()); + } + } + + /// Handle MsgMeasurementState message states. + /// + /// # Parameters: + /// + /// - `states`: All states contained within the measurementstate message. + /// - `client_send`: The Sender channel to be used to send data to frontend. + pub fn handle_msg_measurement_state( + &mut self, + states: Vec, + client_send: Sender>, + ) { + self.at_least_one_track_received = true; + let mut codes_that_came: Vec<(SignalCodes, i16)> = Vec::new(); + let t = (Instant::now()).duration_since(self.t_init).as_secs_f64(); + self.time.add(t); + for (idx, state) in states.iter().enumerate() { + let mut sat = state.mesid.sat as i16; + let signal_code = SignalCodes::from(state.mesid.code); + if signal_code.code_is_glo() { + if state.mesid.sat > GLO_SLOT_SAT_MAX { + self.glo_fcn_dict + .insert(idx as u8, state.mesid.sat as i16 - 100); + } + sat = *self.glo_fcn_dict.get(&(idx as u8)).unwrap_or(&(0_i16)); + + if state.mesid.sat <= GLO_SLOT_SAT_MAX { + self.glo_slot_dict.insert(sat, state.mesid.sat as i16); + } + } + let key = (signal_code, sat); + codes_that_came.push(key); + if state.cn0 != 0 { + self.push_to_cn0_dict(key, t, state.cn0 as f64 / 4.0); + self.push_to_cn0_age(key, t); + } + if !self.received_codes.contains(&signal_code) { + self.received_codes.push(signal_code); + } + } + for (key, cn0_deque) in self.cn0_dict.iter_mut() { + if !codes_that_came.contains(key) { + cn0_deque.add((OrderedFloat(t), 0.0)); + } + } + self.clean_cn0(); + self.update_plot(); + self.send_data(client_send); + } + /// Handle MsgTrackingState message states. + /// + /// # Parameters: + /// + /// - `states`: All states contained within the trackingstate message. + /// - `client_send`: The Sender channel to be used to send data to frontend. + pub fn handle_msg_tracking_state( + &mut self, + states: Vec, + client_send: Sender>, + ) { + self.at_least_one_track_received = true; + let mut codes_that_came: Vec<(SignalCodes, i16)> = Vec::new(); + let t = (Instant::now()).duration_since(self.t_init).as_secs_f64(); + self.time.add(t); + for state in states.iter() { + let mut sat = state.sid.sat as i16; + let signal_code = SignalCodes::from(state.sid.code); + if signal_code.code_is_glo() { + if state.sid.sat > GLO_SLOT_SAT_MAX { + sat -= 100.0 as i16; + } else { + sat -= state.fcn as i16 - GLO_FCN_OFFSET; + } + self.glo_slot_dict.insert(sat, state.sid.sat as i16); + } + let key = (signal_code, sat); + codes_that_came.push(key); + if state.cn0 != 0 { + self.push_to_cn0_dict(key, t, state.cn0 as f64 / 4.0); + self.push_to_cn0_age(key, t); + } + } + self.clean_cn0(); + self.update_plot(); + self.send_data(client_send); + } + /// Handle MsgObs, MsgObsDepB, and MsgObsDepC full messages. + /// + /// # Parameters: + /// + /// - `msg`: The full SBP message cast as an ObservationMsg variant. + /// - `client_send`: The Sender channel to be used to send data to frontend. + pub fn handle_obs(&mut self, msg: ObservationMsg, client_send: Sender>) { + let (seq, tow, wn, states) = match &msg { + ObservationMsg::MsgObs(obs) => { + let states: Vec = obs + .obs + .clone() + .into_iter() + .map(Observations::PackedObsContent) + .collect(); + ( + obs.header.n_obs, + obs.header.t.tow as f64 / 1000.0_f64, + obs.header.t.wn, + states, + ) + } + // ObservationMsg::MsgObsDepA(obs) + ObservationMsg::MsgObsDepB(obs) => { + let states: Vec = obs + .obs + .clone() + .into_iter() + .map(Observations::PackedObsContentDepB) + .collect(); + ( + obs.header.n_obs, + obs.header.t.tow as f64 / 1000.0_f64, + obs.header.t.wn, + states, + ) + } + ObservationMsg::MsgObsDepC(obs) => { + let states: Vec = obs + .obs + .clone() + .into_iter() + .map(Observations::PackedObsContentDepC) + .collect(); + ( + obs.header.n_obs, + obs.header.t.tow as f64 / 1000.0_f64, + obs.header.t.wn, + states, + ) + } + }; + + let total = seq >> 4; + let count = seq & ((1 << 4) - 1); + + if count == 0 { + self.obs_reset(tow, wn, total); + } else if (self.gps_tow - tow) > f64::EPSILON + || self.gps_week != wn + || self.prev_obs_count + 1 != count + || self.prev_obs_total != total + { + println!("We dropped a packet. Skipping this ObservationMsg sequence"); + self.obs_reset(tow, wn, total); + self.prev_obs_count = count; + return; + } else { + self.prev_obs_count = count; + } + + for state in states.iter() { + let (code, sat, cn0) = match state { + Observations::PackedObsContentDepB(obs) => { + let mut sat_ = obs.sid.sat as i16; + let signal_code = SignalCodes::from(obs.sid.code); + if signal_code.code_is_gps() { + sat_ += 1; + } + (signal_code, sat_, obs.cn0 as f64) + } + Observations::PackedObsContentDepC(obs) => { + let mut sat_ = obs.sid.sat as i16; + let signal_code = SignalCodes::from(obs.sid.code); + if signal_code.code_is_gps() { + sat_ += 1; + } + (signal_code, sat_, obs.cn0 as f64) + } + Observations::PackedObsContent(obs) => ( + SignalCodes::from(obs.sid.code), + obs.sid.sat as i16, + obs.cn0 as f64, + ), + }; + self.incoming_obs_cn0.insert((code, sat), cn0 / 4.0); + } + + if count == (total - 1) { + self.last_update_time = Instant::now(); + self.update_from_obs(self.incoming_obs_cn0.clone(), client_send); + } + } + + /// Update cn0 dict using the observation data accumulated by handle_obs. + /// + /// # Parameters: + /// + /// - `msg`: The full SBP message cast as an ObservationMsg variant. + /// - `client_send`: The Sender channel to be used to send data to frontend. + pub fn update_from_obs( + &mut self, + obs_dict: HashMap<(SignalCodes, i16), f64>, + client_send: Sender>, + ) { + if self.at_least_one_track_received { + return; + } + let mut codes_that_came: Vec<(SignalCodes, i16)> = Vec::new(); + let t = (Instant::now()).duration_since(self.t_init).as_secs_f64(); + self.time.add(t); + for (key, cn0) in obs_dict.iter() { + let (signal_code, _) = *key; + codes_that_came.push(*key); + if *cn0 > 0.0_f64 { + self.push_to_cn0_dict(*key, t, *cn0); + self.push_to_cn0_age(*key, t); + } + if !self.received_codes.contains(&signal_code) { + self.received_codes.push(signal_code); + } + } + for (key, cn0_deque) in self.cn0_dict.iter_mut() { + if !codes_that_came.contains(key) { + cn0_deque.add((OrderedFloat(t), 0.0)); + } + } + self.clean_cn0(); + self.update_plot(); + self.send_data(client_send); + } + + /// Reset observation cn0 data in the event of empty observation packet drop. + /// + /// # Parameters: + /// + /// - `tow`: The GPS time of week. + /// - `wn`: The current GPS week number. + /// - `obs_total`: The current observation message total to store. + pub fn obs_reset(&mut self, tow: f64, wn: u16, obs_total: u8) { + self.gps_tow = tow; + self.gps_week = wn; + self.prev_obs_total = obs_total; + self.prev_obs_count = 0; + self.incoming_obs_cn0.clear() + } +} + +/// Enum wrapping around various Observation Message types. +pub enum ObservationMsg { + MsgObs(MsgObs), + // MsgObsDepA(MsgObsDepA), + MsgObsDepB(MsgObsDepB), + MsgObsDepC(MsgObsDepC), +} +/// Enum wrapping around various Observation Message observation types. +pub enum Observations { + PackedObsContent(PackedObsContent), + // PackedObsContentDepA(PackedObsContentDepA), + PackedObsContentDepB(PackedObsContentDepB), + PackedObsContentDepC(PackedObsContentDepC), +} + +impl TabBackend for TrackingSignalsTab<'_> { + /// Package data into a message buffer and send to frontend. + /// + /// # Parameters: + /// + /// - `client_send`: The Sender channel to be used to send data to frontend. + fn send_data(&mut self, client_send: Sender>) { + let mut builder = Builder::new_default(); + let msg = builder.init_root::(); + + let mut tracking_status = msg.init_tracking_status(); + tracking_status.set_min(self.min); + tracking_status.set_max(self.max); + let mut labels = tracking_status + .reborrow() + .init_labels(self.sv_labels.len() as u32); + + for (i, header) in self.sv_labels.iter().enumerate() { + labels.set(i as u32, header); + } + + let mut colors = tracking_status + .reborrow() + .init_colors(self.colors.len() as u32); + + for (i, color) in self.colors.iter().enumerate() { + colors.set(i as u32, color); + } + + let mut tracking_points = tracking_status + .reborrow() + .init_data(self.sv_labels.len() as u32); + { + for idx in 0..self.sv_labels.len() { + let points = self.sats.get_mut(idx).unwrap(); + let mut point_val_idx = tracking_points + .reborrow() + .init(idx as u32, points.len() as u32); + for (i, (OrderedFloat(x), y)) in points.iter().enumerate() { + let mut point_val = point_val_idx.reborrow().get(i as u32); + point_val.set_x(*x); + point_val.set_y(*y); + } + } + } + let mut tracking_checkbox_labels = tracking_status + .reborrow() + .init_check_labels(self.check_labels.len() as u32); + for (i, label) in self.check_labels.iter().enumerate() { + tracking_checkbox_labels.set(i as u32, label); + } + + let mut msg_bytes: Vec = vec![]; + serialize::write_message(&mut msg_bytes, &builder).unwrap(); + + client_send.send(msg_bytes).unwrap(); + } +} diff --git a/console_backend/src/types.rs b/console_backend/src/types.rs new file mode 100644 index 000000000..5be0527e9 --- /dev/null +++ b/console_backend/src/types.rs @@ -0,0 +1,51 @@ +use std::{collections::VecDeque, sync::mpsc::Sender}; + +pub type Error = std::boxed::Box; +pub type Result = std::result::Result; + +pub type Deque = VecDeque; +pub trait DequeExt { + fn add(&mut self, ele: T); +} +impl DequeExt for Deque { + fn add(&mut self, ele: T) { + if self.len() == self.capacity() { + self.pop_front(); + } + self.push_back(ele); + } +} + +pub trait TabBackend { + fn send_data(&mut self, client_send: Sender>); +} + +#[derive(Debug)] +pub struct SharedState { + pub tracking_tab: TrackingTabState, +} +impl SharedState { + pub fn new() -> SharedState { + SharedState { + tracking_tab: TrackingTabState::new(), + } + } +} +impl Default for SharedState { + fn default() -> Self { + SharedState::new() + } +} + +#[derive(Debug)] +pub struct TrackingTabState { + pub check_visibility: Vec, +} + +impl TrackingTabState { + fn new() -> TrackingTabState { + TrackingTabState { + check_visibility: vec![], + } + } +} diff --git a/console_backend/tests/mem_benches.rs b/console_backend/tests/mem_benches.rs index 49d1f5a71..fdd88dc39 100644 --- a/console_backend/tests/mem_benches.rs +++ b/console_backend/tests/mem_benches.rs @@ -10,7 +10,7 @@ use std::{ }; use sysinfo::{get_current_pid, Process, ProcessExt, System, SystemExt}; extern crate console_backend; -use console_backend::process_messages; +use console_backend::{process_messages, types::SharedState}; const BENCH_FILEPATH: &str = "./tests/data/piksi-relay-1min.sbp"; const MINIMUM_MEM_READINGS: usize = 20; @@ -91,7 +91,9 @@ fn test_run_process_messages() { .expect("sending client recv handle should succeed"); let messages = sbp::iter_messages(Box::new(fs::File::open(BENCH_FILEPATH).unwrap())); - process_messages::process_messages(messages, client_send); + let shared_state = SharedState::new(); + let shared_state = Arc::new(Mutex::new(shared_state)); + process_messages::process_messages(messages, &shared_state, client_send); } recv_thread.join().expect("join should succeed"); mem_read_thread.join().expect("join should succeed"); diff --git a/resources/TrackingTab.qml b/resources/TrackingTab.qml new file mode 100644 index 000000000..9cc130cfb --- /dev/null +++ b/resources/TrackingTab.qml @@ -0,0 +1,44 @@ +import QtQuick 2.5 +import QtQuick.Controls 2.12 +import QtCharts 2.2 +import QtQuick.Layouts 1.15 + +import "TrackingTabComponents" as TrackingTabComponents + +Item{ + id: trackingTab + width: parent.width + height: parent.height + TabBar { + id: trackingBar + z: 100 + Repeater { + model: ["Signals", "Sky Plot"] + TabButton { + text: modelData + width: implicitWidth + } + } + } + Rectangle { + id: trackingTabBackground + width: parent.width + height: parent.height + anchors.top: trackingBar.bottom + anchors.bottom: trackingTab.bottom + StackLayout { + id: trackingBarLayout + width: parent.width + height: parent.height + currentIndex: trackingBar.currentIndex + TrackingTabComponents.TrackingSignalsTab{} + Item { + id: trackingSkyplotTab + } + + } + Component.onCompleted: { + } + } + +} diff --git a/resources/TrackingTabComponents/TrackingSignalsTab.qml b/resources/TrackingTabComponents/TrackingSignalsTab.qml new file mode 100644 index 000000000..e6ce78b28 --- /dev/null +++ b/resources/TrackingTabComponents/TrackingSignalsTab.qml @@ -0,0 +1,202 @@ + +import QtQuick 2.5 +import QtQuick.Controls 2.12 +import QtCharts 2.2 +import QtQuick.Layouts 1.15 + +import SwiftConsole 1.0 + +Item { + + TrackingSignalsPoints { + id: trackingSignalsPoints + } + + id: trackingSignalsTab + width: parent.width + height: parent.height + property variant lines: [] + property variant labels: [] + property variant colors: [] + property variant check_labels: [] + property variant check_visibility: [] + Rectangle { + id: trackingSignalsArea + width: parent.width + height: parent.height + + ChartView { + id: trackingSignalsChart + title: "Tracking C/N0" + titleFont { pointSize: 14; bold: true } + titleColor: "#00006E" + width: parent.width + height: parent.height - trackingSignalsCheckboxes.height + anchors.top: parent.top + backgroundColor: "#CDC9C9" + plotAreaColor: "#FFFFFF" + legend.visible: false + + Rectangle { + id: lineLegend + border.color: "#000000" + border.width: 1 + + anchors.bottom: trackingSignalsChart.bottom + anchors.left: trackingSignalsChart.left + anchors.bottomMargin: 85 + anchors.leftMargin: 60 + implicitHeight: lineLegendRepeater.height + width: lineLegendRepeater.width + + + Column { + id: lineLegendRepeater + spacing: -1 + anchors.bottom: lineLegend.bottom + Repeater { + model: labels + id: lineLegendRepeaterRows + Row { + Rectangle { + id: marker + width: 20 + height: 3 + color: "#000000" + anchors.verticalCenter: parent.verticalCenter + } + Text { + id: label + text: modelData + font.pointSize: 6 + anchors.verticalCenter: parent.verticalCenter + anchors.verticalCenterOffset: -1 + } + Component.onCompleted: { + for (var idx in colors) { + if (lineLegendRepeaterRows.itemAt(idx)){ + lineLegendRepeaterRows.itemAt(idx).children[0].color = colors[idx]; + } + } + } + } + } + + } + } + ValueAxis { + id: trackingSignalsXAxis + labelsFont { pointSize: 10; bold: true } + titleText: "seconds" + gridVisible: true + lineVisible: true + minorGridVisible: true + minorGridLineColor: "#CDC9C9" + visible: true + } + ValueAxis { + id: trackingSignalsYAxis + titleText: "dB-Hz" + min: 0 + max: 1 + labelsFont { pointSize: 10; bold: true } + gridVisible: true + lineVisible: true + minorGridVisible: true + minorGridLineColor: "#CDC9C9" + visible: true + } + Timer { + interval: 1000/5 // 5 Hz refresh + running: true + repeat: true + onTriggered: { + if (!trackingTab.visible) { + return; + } + tracking_signals_model.fill_console_points(trackingSignalsPoints); + if (!trackingSignalsPoints.points.length) { + return; + } + var points = trackingSignalsPoints.points; + colors = trackingSignalsPoints.colors; + labels = trackingSignalsPoints.labels; + if (check_labels != trackingSignalsPoints.check_labels) { + check_labels = trackingSignalsPoints.check_labels; + } + for (var idx in labels) { + if (idx < lines.length) { + if (labels[idx]!=lines[idx][1]){ + trackingSignalsChart.removeSeries(lines[idx][0]) + var line = trackingSignalsChart.createSeries(ChartView.SeriesTypeLine, labels[idx], trackingSignalsXAxis); + line.color = colors[idx]; + line.width = 1.5; + line.axisYRight = trackingSignalsYAxis; + lines[idx] = [line, labels[idx]]; + } + } else { + var line = trackingSignalsChart.createSeries(ChartView.SeriesTypeLine, labels[idx], trackingSignalsXAxis); + line.color = colors[idx]; + line.width = 1.5; + line.axisYRight = trackingSignalsYAxis; + lines.push([line, labels[idx]]); + } + } + trackingSignalsPoints.fill_series(lines); + + var last = points[0][points[0].length - 1]; + trackingSignalsXAxis.min = last.x - 100; + trackingSignalsXAxis.max = last.x; + + if (trackingSignalsYAxis.min!=trackingSignalsPoints.min_){ + trackingSignalsYAxis.min = trackingSignalsPoints.min_; + trackingSignalsYAxis.max = trackingSignalsPoints.max_; + } + + + } + } + Component.onCompleted: { + } + } + GridLayout { + id: trackingSignalsCheckboxes + columns: 6 + anchors.horizontalCenter: trackingSignalsChart.horizontalCenter + anchors.top: trackingSignalsChart.bottom + + + Repeater { + model: check_labels + id: trackingSignalsCheckbox + Column { + CheckBox { + checked: true + text: modelData + verticalPadding: 0 + onClicked: { + check_visibility[index] = checked; + if (index == 0){ + lineLegend.visible = !lineLegend.visible; + return; + } + var labels_not_visible = []; + for (var idx in check_visibility){ + if (!check_visibility[idx]){ + labels_not_visible.push(check_labels[idx]); + } + } + data_model.check_visibility(labels_not_visible); + } + Component.onCompleted: { + check_visibility.push(checked); + } + } + + } + } + } + } + Component.onCompleted: { + } +} diff --git a/resources/console_resources.qrc b/resources/console_resources.qrc index de76a5ebe..8201b3bd3 100644 --- a/resources/console_resources.qrc +++ b/resources/console_resources.qrc @@ -3,5 +3,7 @@ qtquickcontrols2.conf view.qml ContactModel.qml + TrackingTab.qml + TrackingTabComponents/TrackingSignalsTab.qml diff --git a/resources/view.qml b/resources/view.qml index ec15013c3..f7ee27afc 100644 --- a/resources/view.qml +++ b/resources/view.qml @@ -13,247 +13,168 @@ ApplicationWindow { font.pointSize: 8 ConsolePoints { - id: console_points + id: consolePoints } - - TrackingSignalsPoints { - id: tracking_signals_points - } - property variant lines: [] ColumnLayout { - anchors.fill: parent - anchors.margins: 4 spacing: 2 - - TabBar { - id: bar + width: parent.width + height: parent.height + Rectangle { + id: mainTabs + height: parent.height - consoleLog.height width: parent.width - - Repeater { - model: ["Tracking", "Solution", "Baseline", "Observations", "Settings", "Update", "Advanced"] - - TabButton { - text: modelData - width: implicitWidth - } - } - } - StackLayout { - width: parent.width - currentIndex: bar.currentIndex - Item { + TabBar { + id: tab + z: 100 + width: parent.width - id: trackingTab - TabBar { - id: trackingbar - Repeater { - model: ["Signals", "Sky Plot"] - TabButton { - text: modelData - width: implicitWidth - } + Repeater { + model: ["Tracking", "Solution", "Baseline", "Observations", "Settings", "Update", "Advanced"] + + TabButton { + text: modelData + width: implicitWidth } } - StackLayout { - anchors.top: trackingbar.bottom - anchors.bottom: trackingTab.bottom - id: trackingbarlayout - width: parent.width - currentIndex: trackingbar.currentIndex - Item { - id: trackingsignalsTab + } + + StackLayout { + width: parent.width + height: parent.height - tab.height + anchors.top: tab.bottom + currentIndex: tab.currentIndex + TrackingTab {} + Item { + id: solutionTab + RowLayout{ + anchors.fill: parent + spacing: 2 + ListView { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.alignment: Qt.AlignLeft + - ChartView { - id: tracking_signals_chart + Component { + id: contactsDelegate + Rectangle { + id: wrapper + width: 180 + height: contactInfo.height + color: ListView.isCurrentItem ? "black" : "red" + Text { + id: contactInfo + text: name + ": " + number + color: wrapper.ListView.isCurrentItem ? "red" : "black" + } + } + } + model: ContactModel {} + delegate: contactsDelegate + focus: true + } + ChartView { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.alignment: Qt.AlignLeft titleFont.pointSize: 8 antialiasing: true - width: trackingTab.width - height: trackingTab.height - + width: parent.width/2 legend.font.pointSize: 7 legend.alignment: Qt.AlignTop legend.showToolTips: true ValueAxis { - id: tracking_signals_x_axis + id: x_axis labelsFont.pointSize: 7 titleText: "GPS Time of Week" } ValueAxis { - id: tracking_signals_y_axis + id: y_axis min: -1.0 - max: 60.0 + max: 1.0 labelsFont.pointSize: 7 } - + + LineSeries { + id: hseries + name: "Horizontal [m/s]" + axisX: x_axis + axisY: y_axis + //useOpenGL: true + } + LineSeries { + id: vseries + name: "Vertical [m/s]" + axisX: x_axis + axisY: y_axis + //useOpenGL: true + } Timer { interval: 1000/5 // 5 Hz refresh running: true repeat: true onTriggered: { - - if (!trackingTab.visible) { + if (!solutionTab.visible) { return; } - - tracking_signals_model.fill_console_points(tracking_signals_points); - if (!tracking_signals_points.points.length) { + data_model.fill_console_points(consolePoints); + if (!consolePoints.valid) { return; } - var points = tracking_signals_points.points; - if (lines.length < points.length) { - for (var idx=0; idx< (points.length-lines.length); idx++){ - var lineTypeSeries = tracking_signals_chart.createSeries(ChartView.SeriesTypeLine, lines.length, tracking_signals_x_axis, tracking_signals_y_axis); - lines.push(lineTypeSeries); - } - } - tracking_signals_points.fill_series(lines); - var last = points[0][points[0].length - 1]; - tracking_signals_x_axis.min = last.x - 10; - tracking_signals_x_axis.max = last.x; + var hpoints = consolePoints.hpoints; + var last = hpoints[hpoints.length - 1]; + x_axis.min = last.x - 10; + x_axis.max = last.x; + y_axis.min = consolePoints.min_; + y_axis.max = consolePoints.max_; + consolePoints.fill_hseries(hseries); + consolePoints.fill_vseries(vseries); } } - Component.onCompleted: { - } } - } - Item { - id: trackingskyplotTab - } - + + } + Item { + id: baselineTab + } + Item { + id: observationsTab + } + Item { + id: settingsTab + } + Item { + id: updateTab + } + Item { + id: advancedTab } - } - Item { - id: solutionTab - RowLayout{ - anchors.fill: parent - spacing: 2 - ListView { - Layout.fillWidth: true - Layout.fillHeight: true - Layout.alignment: Qt.AlignLeft - - Component { - id: contactsDelegate - Rectangle { - id: wrapper - width: 180 - height: contactInfo.height - color: ListView.isCurrentItem ? "black" : "red" - Text { - id: contactInfo - text: name + ": " + number - color: wrapper.ListView.isCurrentItem ? "red" : "black" - } - } - } - - model: ContactModel {} - delegate: contactsDelegate - focus: true - } - - ChartView { - - - Layout.fillWidth: true - Layout.fillHeight: true - Layout.alignment: Qt.AlignLeft - titleFont.pointSize: 8 - antialiasing: true - width: parent.width/2 - legend.font.pointSize: 7 - legend.alignment: Qt.AlignTop - legend.showToolTips: true - - ValueAxis { - id: x_axis - labelsFont.pointSize: 7 - titleText: "GPS Time of Week" - } - ValueAxis { - id: y_axis - min: -1.0 - max: 1.0 - labelsFont.pointSize: 7 - } - - LineSeries { - id: hseries - name: "Horizontal [m/s]" - axisX: x_axis - axisY: y_axis - //useOpenGL: true - } - LineSeries { - id: vseries - name: "Vertical [m/s]" - axisX: x_axis - axisY: y_axis - //useOpenGL: true - } + } + Rectangle { + id: consoleLog + width: parent.width + height: 100 + Layout.alignment: Qt.AlignBottom - Timer { - interval: 1000/5 // 5 Hz refresh - running: true - repeat: true - onTriggered: { - if (!solutionTab.visible) { - return; - } - data_model.fill_console_points(console_points); - if (!console_points.valid) { - return; - } - var hpoints = console_points.hpoints; - var last = hpoints[hpoints.length - 1]; - x_axis.min = last.x - 10; - x_axis.max = last.x; - y_axis.min = console_points.min_; - y_axis.max = console_points.max_; - console_points.fill_hseries(hseries); - console_points.fill_vseries(vseries); - } - } - } + RowLayout { + Button { + text: "Connect" + onClicked: data_model.connect() + } + Button { + text: "File In" + onClicked: data_model.readfile() } - - } - Item { - id: baselineTab - } - Item { - id: observationsTab - } - Item { - id: settingsTab - } - Item { - id: updateTab - } - Item { - id: advancedTab - } - } - - RowLayout { - Button { - text: "Connect" - onClicked: data_model.connect() - } - Button { - text: "File In" - onClicked: data_model.readfile() } } - } Component.onCompleted: { diff --git a/src/main/python/constants.py b/src/main/python/constants.py new file mode 100644 index 000000000..abfa60b54 --- /dev/null +++ b/src/main/python/constants.py @@ -0,0 +1,20 @@ +from enum import Enum + +class Keys(str, Enum): + POINTS = "POINTS" + LABELS = "LABELS" + CHECK_LABELS = "CHECK_LABELS" + COLORS = "COLORS" + MAX = "MAX" + MIN = "MIN" + +class ApplicationStates(str, Enum): + CLOSE = "CLOSE" + +class MessageKeys(str, Enum): + STATUS = "status" + VELOCITY_STATUS = "velocityStatus" + TRACKING_STATUS = "trackingStatus" + +class QTKeys(str, Enum): + QVARIANTLIST = "QVariantList" diff --git a/src/main/python/main.py b/src/main/python/main.py index ce23fd179..f699ba1f9 100644 --- a/src/main/python/main.py +++ b/src/main/python/main.py @@ -5,7 +5,7 @@ import sys import threading -from typing import List, Optional, Tuple, Any +from typing import Dict, List, Optional, Tuple, Any import capnp # type: ignore @@ -21,13 +21,12 @@ from PySide2.QtQml import qmlRegisterType from PySide2.QtCore import Property +from constants import ApplicationStates, MessageKeys, Keys, QTKeys + import console_resources # type: ignore # pylint: disable=unused-import,import-error import console_backend.server # type: ignore # pylint: disable=import-error,no-name-in-module -CLOSE = "CLOSE" -DARWIN = "darwin" - CONSOLE_BACKEND_CAPNP_PATH = "console_backend.capnp" PIKSI_HOST = "piksi-relay-bb9f2b10e53143f4a816a11884e679cf.ce.swiftnav.com" @@ -39,6 +38,16 @@ TRACKING_POINTS: List[List[QPointF]] = [] TRACKING_HEADERS: List[int] = [] + +TRACKING_SIGNALS_TAB: Dict[str, Any] = { + Keys.POINTS: [], + Keys.CHECK_LABELS: [], + Keys.LABELS: [], + Keys.COLORS: [], + Keys.MAX: 0, + Keys.MIN: 0, +} + capnp.remove_import_hook() # pylint: disable=no-member @@ -46,29 +55,33 @@ def receive_messages(app_, backend, messages): while True: buffer = backend.fetch_message() m = messages.Message.from_bytes(buffer) - if m.which == "status": - if m.status.text == CLOSE: + if m.which == MessageKeys.STATUS: + if m.status.text == ApplicationStates.CLOSE: return app_.quit() - elif m.which == "velocityStatus": + elif m.which == MessageKeys.VELOCITY_STATUS: POINTS_H_MINMAX[0] = (m.velocityStatus.min, m.velocityStatus.max) POINTS_H[:] = [QPointF(point.x, point.y) for point in m.velocityStatus.hpoints] POINTS_V[:] = [QPointF(point.x, point.y) for point in m.velocityStatus.vpoints] - elif m.which == "trackingStatus": - TRACKING_HEADERS[:] = m.trackingStatus.headers - TRACKING_POINTS[:] = [ + elif m.which == MessageKeys.TRACKING_STATUS: + TRACKING_SIGNALS_TAB[Keys.CHECK_LABELS][:] = m.trackingStatus.checkLabels + TRACKING_SIGNALS_TAB[Keys.LABELS][:] = m.trackingStatus.labels + TRACKING_SIGNALS_TAB[Keys.COLORS][:] = m.trackingStatus.colors + TRACKING_SIGNALS_TAB[Keys.POINTS][:] = [ [QPointF(point.x, point.y) for point in m.trackingStatus.data[idx]] for idx in range(len(m.trackingStatus.data)) ] + TRACKING_SIGNALS_TAB[Keys.MAX] = m.trackingStatus.max + TRACKING_SIGNALS_TAB[Keys.MIN] = m.trackingStatus.min else: pass - # print(f"other message: {m}") + class ConsolePoints(QObject): - _valid: bool = False _hpoints: List[QPointF] = [] _vpoints: List[QPointF] = [] + _valid: bool = False _min: float = 0.0 _max: float = 0.0 @@ -87,21 +100,6 @@ def set_valid(self, valid: bool) -> None: valid = Property(bool, get_valid, set_valid) - def get_hpoints(self) -> List[QPointF]: - return self._hpoints - - def set_hpoints(self, hpoints) -> None: - self._hpoints = hpoints - - def get_vpoints(self) -> List[QPointF]: - return self._vpoints - - def set_vpoints(self, vpoints) -> None: - self._vpoints = vpoints - - hpoints = Property("QVariantList", get_hpoints, set_hpoints) # type: ignore - vpoints = Property("QVariantList", get_vpoints, set_vpoints) # type: ignore - def get_min(self) -> float: """Getter for _min. """ @@ -126,6 +124,21 @@ def set_max(self, max_: float) -> None: max_ = Property(float, get_max, set_max) + def get_hpoints(self) -> List[QPointF]: + return self._hpoints + + def set_hpoints(self, hpoints) -> None: + self._hpoints = hpoints + + def get_vpoints(self) -> List[QPointF]: + return self._vpoints + + def set_vpoints(self, vpoints) -> None: + self._vpoints = vpoints + + hpoints = Property(QTKeys.QVARIANTLIST, get_hpoints, set_hpoints) # type: ignore + vpoints = Property(QTKeys.QVARIANTLIST, get_vpoints, set_vpoints) # type: ignore + @Slot(QtCharts.QXYSeries) # type: ignore def fill_hseries(self, hseries): hseries.replace(self._hpoints) @@ -186,41 +199,112 @@ def readfile(self) -> None: buffer = m.to_bytes() self.endpoint.send_message(buffer) + @Slot(list) # type: ignore + def check_visibility(self, checks: List[str]) -> None: + m = self.messages.Message() + m.trackingStatusFront = m.init("trackingStatusFront") + m.trackingStatusFront.checkVisibility = checks + buffer = m.to_bytes() + self.endpoint.send_message(buffer) + class TrackingSignalsPoints(QObject): - _valid: bool = False + _colors: List[str] = [] + _check_labels: List[str] = [] + _labels: List[str] = [] _points: List[List[QPointF]] = [[]] + _valid: bool = False _min: float = 0.0 _max: float = 0.0 def get_valid(self) -> bool: + """Getter for _valid. + + Returns: + bool: Whether it is valid or not. + """ return self._valid - def set_valid(self, valid) -> None: + def set_valid(self, valid: bool) -> None: + """Setter for _valid. + """ self._valid = valid valid = Property(bool, get_valid, set_valid) + def get_min(self) -> float: + """Getter for _min. + """ + return self._min + + def set_min(self, min_: float) -> None: + """Setter for _min. + """ + self._min = min_ + + min_ = Property(float, get_min, set_min) + + def get_max(self) -> float: + """Getter for _max. + """ + return self._max + + def set_max(self, max_: float) -> None: + """Setter for _max. + """ + self._max = max_ + + max_ = Property(float, get_max, set_max) + + def get_check_labels(self) -> List[str]: + return self._check_labels + + def set_check_labels(self, check_labels) -> None: + self._check_labels = check_labels + + check_labels = Property(QTKeys.QVARIANTLIST, get_check_labels, set_check_labels) # type: ignore + + def get_labels(self) -> List[str]: + return self._labels + + def set_labels(self, labels) -> None: + self._labels = labels + + labels = Property(QTKeys.QVARIANTLIST, get_labels, set_labels) # type: ignore + + def get_colors(self) -> List[str]: + return self._colors + + def set_colors(self, colors) -> None: + self._colors = colors + + colors = Property(QTKeys.QVARIANTLIST, get_colors, set_colors) # type: ignore + def get_points(self) -> List[List[QPointF]]: return self._points def set_points(self, points) -> None: self._points = points - points = Property("QVariantList", get_points, set_points) # type: ignore + points = Property(QTKeys.QVARIANTLIST, get_points, set_points) # type: ignore @Slot(list) # type: ignore def fill_series(self, series_list): - for idx, series in enumerate(series_list): - series.replace(self._points[idx]) - + for idx, series_and_key in enumerate(series_list): + series, _ = series_and_key + if idx < len(self._points): + series.replace(self._points[idx]) class TrackingSignalsModel(QObject): # pylint: disable=too-few-public-methods @Slot(TrackingSignalsPoints) # type: ignore def fill_console_points(self, cp: TrackingSignalsPoints) -> TrackingSignalsPoints: # pylint:disable=no-self-use - - cp.set_points(TRACKING_POINTS) + cp.set_points(TRACKING_SIGNALS_TAB[Keys.POINTS]) + cp.set_labels(TRACKING_SIGNALS_TAB[Keys.LABELS]) + cp.set_check_labels(TRACKING_SIGNALS_TAB[Keys.CHECK_LABELS]) + cp.set_colors(TRACKING_SIGNALS_TAB[Keys.COLORS]) + cp.set_max(TRACKING_SIGNALS_TAB[Keys.MAX]) + cp.set_min(TRACKING_SIGNALS_TAB[Keys.MIN]) return cp def is_frozen() -> bool: @@ -281,5 +365,4 @@ def get_capnp_path() -> str: root_context.setContextProperty("data_model", data_model) threading.Thread(target=receive_messages, args=(app, backend_main, messages_main,), daemon=True).start() - sys.exit(app.exec_()) diff --git a/src/main/resources/base/console_backend.capnp b/src/main/resources/base/console_backend.capnp index 4b748144f..1797b6906 100644 --- a/src/main/resources/base/console_backend.capnp +++ b/src/main/resources/base/console_backend.capnp @@ -24,8 +24,14 @@ struct VelocityStatus { struct TrackingStatus { min @0 :Float64; max @1 :Float64; - headers @2 :List(UInt8); + labels @2 :List(Text); data @3 :List(List(Point)); + colors @4 :List(Text); + checkLabels @5 :List(Text); +} + +struct TrackingStatusFront { + checkVisibility @0 :List(Text); } struct Status { @@ -39,5 +45,6 @@ struct Message { status @2 :Status; fileinRequest @3 :FileinRequest; trackingStatus @4 :TrackingStatus; + trackingStatusFront @5 :TrackingStatusFront; } } diff --git a/utils/bench_runner.py b/utils/bench_runner.py index be87960ea..d4e8cf482 100644 --- a/utils/bench_runner.py +++ b/utils/bench_runner.py @@ -102,7 +102,7 @@ NAME: "202010224_192043", FILE_PATH: "data/202010224_192043.sbp", KEY_LOCATION: "mean", - EXPECTED: 5.0, + EXPECTED: 50.0, ERROR_MARGIN_FRAC: 0.05, }, ], @@ -111,7 +111,7 @@ NAME: "202010224_192043", FILE_PATH: "data/202010224_192043.sbp", KEY_LOCATION: "mean", - EXPECTED: 1.75, + EXPECTED: 12.0, ERROR_MARGIN_FRAC: 0.05, }, ], @@ -120,7 +120,7 @@ NAME: "202010224_192043", FILE_PATH: "data/202010224_192043.sbp", KEY_LOCATION: "mean", - EXPECTED: 12.0, + EXPECTED: 80.0, ERROR_MARGIN_FRAC: 0.05, }, ], @@ -134,7 +134,7 @@ BYTES_TO_MB = lambda x: float(x) / (1 << 20) ABSOLUTE_MINIMUM_MEMORY_MB = 1 ABSOLUTE_MINIMUM_READINGS = 200 -THREAD_TIMEOUT_SEC = 30 +THREAD_TIMEOUT_SEC = 180 RUN_COUNT = 5 FRONTEND_MEM_BENCHMARKS: Dict[str, List[Dict[str, Any]]] = { @@ -142,7 +142,7 @@ { NAME: "piksi-relay-1min", FILE_PATH: "data/piksi-relay-1min.sbp", - MAXIMUM_MEAN_MB: 200, + MAXIMUM_MEAN_MB: 300, MAXIMUM_RATE_OF_MAX_MEAN: 0.05, MAXIMUM_RATE_OF_MAX_STD: 0.3, }, @@ -151,7 +151,7 @@ { NAME: "piksi-relay-1min", FILE_PATH: "data/piksi-relay-1min.sbp", - MAXIMUM_MEAN_MB: 200, + MAXIMUM_MEAN_MB: 300, MAXIMUM_RATE_OF_MAX_MEAN: 0.05, MAXIMUM_RATE_OF_MAX_STD: 0.3, }, @@ -160,7 +160,7 @@ { NAME: "piksi-relay-1min", FILE_PATH: "data/piksi-relay-1min.sbp", - MAXIMUM_MEAN_MB: 200, + MAXIMUM_MEAN_MB: 400, MAXIMUM_RATE_OF_MAX_MEAN: 0.05, MAXIMUM_RATE_OF_MAX_STD: 0.4, },