Skip to content

Commit

Permalink
add a simple example TUI based on ratatui
Browse files Browse the repository at this point in the history
 - show NAV-PVT msg as in u-center
 - show ESF-STATUS and ESF-ALG statuses similar to u-center
 - show MON-VER
 - a few other things are work in progress, such as: show a graph of the
   sensor values, properly capture the log using tracing and show it in
   the UI

Signed-off-by: Andrei Gherghescu <[email protected]>
  • Loading branch information
andrei-ng committed Jan 12, 2025
1 parent 81cc2bd commit 2fd972f
Show file tree
Hide file tree
Showing 11 changed files with 1,811 additions and 2 deletions.
4 changes: 2 additions & 2 deletions examples/adr-message-parsing/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,10 +173,10 @@ fn main() {
)
.expect("Could not write UBX-CFG-ESFALG msg due to: {e}");

// Send a packet request for the MonVer packet
// Send packet request to read the new CfgEsfAlg
device
.write_all(&UbxPacketRequest::request_for::<CfgEsfAlg>().into_packet_bytes())
.expect("Unable to write request/poll for CFG-ESFALG message");
.expect("Unable to write request/poll for UBX-CFG-ESFALG message");

// Start reading data
println!("Opened uBlox device, waiting for messages...");
Expand Down
31 changes: 31 additions & 0 deletions examples/tui/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[package]
authors = ["Andrei Gherghescu <[email protected]>"]
edition = "2021"
name = "ublox-tui"
publish = false
rust-version = "1.70"
version = "0.0.1"

[dependencies]
anyhow = "1.0"
chrono = "0.4"
clap = { version = "4.5.23", features = ["derive", "cargo"] }
crossterm = { version = "0.28", features = ["event-stream"] }
env_logger = "0.11"
indoc = "2"
log = "0.4"
ratatui = "0.29"
serialport = "4.2"
strum = { version = "0.26", features = ["derive"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
unicode-width = "0.2"
lazy_static = "1.5"
tracing-error = "0.2"
tui-logger = { version = "0.14", features = ["crossterm", "tracing-support"] }
directories = "0.10"

ublox = { path = "../../ublox" }

[features]
alloc = ["ublox/alloc"]
17 changes: 17 additions & 0 deletions examples/tui/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# TUI example

This TUI is based on the [Ratatui demo app](https://github.com/ratatui/ratatui/tree/main/examples/apps/demo)

It is implemented ony for the `crossterm` backend.

It will show the NAV-PVT and ESF-ALG, ESF-STATUS messages similar to the u-center UI from uBlox.

```shell
cargo run -p /dev/ttyACM0
```

You should see a TUI like this

![images/ublox-tui.png](images/ublox-tui.png)


Binary file added examples/tui/images/ublox-tui.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
244 changes: 244 additions & 0 deletions examples/tui/src/app.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
use core::f64;
use std::{path::PathBuf, vec};

use ublox::{
EsfAlgStatus, EsfSensorFaults, EsfSensorStatusCalibration, EsfSensorStatusTime, EsfSensorType,
EsfStatusFusionMode, EsfStatusImuInit, EsfStatusInsInit, EsfStatusMountAngle,
EsfStatusWheelTickInit, GpsFix, NavPvtFlags, NavPvtFlags2,
};

use crate::ui::LogWidget;

pub struct TabsState<'a> {
pub titles: Vec<&'a str>,
pub index: usize,
}

impl<'a> TabsState<'a> {
pub const fn new(titles: Vec<&'a str>) -> Self {
Self { titles, index: 0 }
}
pub fn next(&mut self) {
self.index = (self.index + 1) % self.titles.len();
}

pub fn previous(&mut self) {
if self.index > 0 {
self.index -= 1;
} else {
self.index = self.titles.len() - 1;
}
}
}

pub enum UbxStatus {
Pvt(Box<NavPvtWidgetState>),
MonVer(Box<MonVersionWidgetState>),
EsfAlgImu(EsfAlgImuAlignmentWidgetState),
EsfAlgSensors(EsfSensorsWidgetState),
EsfAlgStatus(EsfAlgStatusWidgetState),
}

#[derive(Debug)]
#[allow(dead_code)]
pub struct NavPvtWidgetState {
pub time_tag: f64,
pub year: u16,
pub month: u8,
pub day: u8,
pub hour: u8,
pub min: u8,
pub sec: u8,
pub valid: u8,
pub time_accuracy: u32,
pub nanosecond: i32,
pub utc_time_accuracy: u32,
pub lat: f64,
pub lon: f64,
pub height: f64,
pub msl: f64,
pub vel_ned: (f64, f64, f64),
pub speed_over_ground: f64,
pub heading_motion: f64,
pub heading_vehicle: f64,
pub magnetic_declination: f64,

pub pdop: f64,
pub satellites_used: u8,

pub position_fix_type: GpsFix,
pub fix_flags: NavPvtFlags,
pub invalid_llh: bool,
pub position_accuracy: (f64, f64),
pub velocity_accuracy: f64,
pub heading_accuracy: f64,
pub magnetic_declination_accuracy: f64,
pub flags2: NavPvtFlags2,
}

impl Default for NavPvtWidgetState {
fn default() -> Self {
Self {
time_tag: f64::NAN,
year: 0,
month: 0,
day: 0,
hour: 0,
min: 0,
sec: 0,
valid: 0,
time_accuracy: 0,
nanosecond: 0,
lat: f64::NAN,
lon: f64::NAN,
height: f64::NAN,
msl: f64::NAN,
vel_ned: (f64::NAN, f64::NAN, f64::NAN),
speed_over_ground: f64::NAN,
heading_motion: f64::NAN,
heading_vehicle: f64::NAN,
magnetic_declination: f64::NAN,
pdop: f64::NAN,
satellites_used: 0,
utc_time_accuracy: 0,
invalid_llh: true,
position_accuracy: (f64::NAN, f64::NAN),
velocity_accuracy: f64::NAN,
heading_accuracy: f64::NAN,
magnetic_declination_accuracy: f64::NAN,
position_fix_type: GpsFix::NoFix,
fix_flags: NavPvtFlags::empty(),
flags2: NavPvtFlags2::empty(),
}
}
}

#[derive(Debug, Default)]
pub struct EsfSensorsWidgetState {
pub sensors: Vec<EsfSensorWidget>,
}

#[derive(Debug, Clone)]
pub struct EsfSensorWidget {
pub sensor_type: EsfSensorType,
pub calib_status: EsfSensorStatusCalibration,
pub time_status: EsfSensorStatusTime,
pub freq: u16,
pub faults: EsfSensorFaults,
}

impl Default for EsfSensorWidget {
fn default() -> Self {
Self {
sensor_type: EsfSensorType::Unknown,
calib_status: EsfSensorStatusCalibration::NotCalibrated,
time_status: EsfSensorStatusTime::NoData,
freq: 0,
faults: EsfSensorFaults::default(),
}
}
}

pub struct EsfAlgStatusWidgetState {
pub time_tag: f64,
pub fusion_mode: EsfStatusFusionMode,
pub imu_status: EsfStatusImuInit,
pub wheel_tick_sensor_status: EsfStatusWheelTickInit,
pub ins_status: EsfStatusInsInit,
pub imu_mount_alignment_status: EsfStatusMountAngle,
}

impl Default for EsfAlgStatusWidgetState {
fn default() -> Self {
Self {
time_tag: f64::NAN,
fusion_mode: EsfStatusFusionMode::Disabled,
imu_status: EsfStatusImuInit::Off,
wheel_tick_sensor_status: EsfStatusWheelTickInit::Off,
ins_status: EsfStatusInsInit::Off,
imu_mount_alignment_status: EsfStatusMountAngle::Off,
}
}
}
#[derive(Debug)]
pub struct EsfAlgImuAlignmentWidgetState {
pub time_tag: f64,
pub auto_alignment: bool,
pub alignment_status: EsfAlgStatus,
pub angle_singularity: bool,
pub roll: f64,
pub pitch: f64,
pub yaw: f64,
}

impl Default for EsfAlgImuAlignmentWidgetState {
fn default() -> Self {
Self {
time_tag: f64::NAN,
auto_alignment: false,
alignment_status: EsfAlgStatus::UserDefinedAngles,
angle_singularity: false,
roll: f64::NAN,
pitch: f64::NAN,
yaw: f64::NAN,
}
}
}

#[derive(Debug, Default)]
pub struct MonVersionWidgetState {
pub software_version: [u8; 30],
pub hardware_version: [u8; 10],
pub extensions: String,
}

#[allow(dead_code)]
pub struct App<'a> {
pub title: &'a str,
pub log_file: PathBuf,
pub pvt_state: NavPvtWidgetState,
pub mon_ver_state: MonVersionWidgetState,
pub esf_sensors_state: EsfSensorsWidgetState,
pub esf_alg_state: EsfAlgStatusWidgetState,
pub esf_alg_imu_alignment_state: EsfAlgImuAlignmentWidgetState,
pub should_quit: bool,
pub tabs: TabsState<'a>,
pub log_widget: LogWidget,
}

impl<'a> App<'a> {
pub fn new(title: &'a str, log_file: PathBuf) -> Self {
App {
title,
log_file,
pvt_state: NavPvtWidgetState::default(),
mon_ver_state: MonVersionWidgetState::default(),
esf_sensors_state: EsfSensorsWidgetState::default(),
esf_alg_state: EsfAlgStatusWidgetState::default(),
esf_alg_imu_alignment_state: EsfAlgImuAlignmentWidgetState::default(),
should_quit: false,
log_widget: LogWidget,
tabs: TabsState::new(vec!["PVT & ESF Status", "Version Info", "World Map"]),
}
}

pub fn on_right(&mut self) {
self.tabs.next();
}

pub fn on_left(&mut self) {
self.tabs.previous();
}

pub fn on_key(&mut self, c: char) {
match c {
'q' => {
self.should_quit = true;
},
'Q' => {
self.should_quit = true;
},
_ => {},
}
}
}
Loading

0 comments on commit 2fd972f

Please sign in to comment.