diff --git a/.cargo/config b/.cargo/config new file mode 100644 index 0000000..19e7705 --- /dev/null +++ b/.cargo/config @@ -0,0 +1,2 @@ +[target.arm-unknown-linux-gnueabihf] +linker = "arm-linux-gnueabihf-gcc" \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..4297ad6 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "rpi-rgb-led-matrix"] + path = rpi-rgb-led-matrix + url = git@github.com:hzeller/rpi-rgb-led-matrix.git diff --git a/Cargo.lock b/Cargo.lock index cd00cc1..5911e40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -115,6 +115,7 @@ version = "0.1.0" dependencies = [ "chrono", "config", + "openssl", "reqwest", "rpi-led-matrix", "serde 1.0.126", @@ -565,6 +566,15 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a" +[[package]] +name = "openssl-src" +version = "111.15.0+1.1.1k" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a5f6ae2ac04393b217ea9f700cd04fa9bf3d93fae2872069f3d15d908af70a" +dependencies = [ + "cc", +] + [[package]] name = "openssl-sys" version = "0.9.63" @@ -574,6 +584,7 @@ dependencies = [ "autocfg", "cc", "libc", + "openssl-src", "pkg-config", "vcpkg", ] diff --git a/Cargo.toml b/Cargo.toml index 4f328ca..6fe8f4a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,10 +3,12 @@ name = "dsn-visualizer" version = "0.1.0" authors = ["Joey Hines "] edition = "2018" +build = "build.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +openssl = { version = "0.10", features = ["vendored"] } reqwest = {version = "0.11", features=["blocking"]} chrono = {version = "0.4.19", features=["serde"]} xml-rs = "0.8.3" diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..dc2fbdb --- /dev/null +++ b/build.rs @@ -0,0 +1,4 @@ +fn main() { + println!("cargo:rustc-link-search=rpi-rgb-led-matrix/lib/"); + println!("cargo:rustc-flags=-l dylib=stdc++"); +} diff --git a/config.toml b/config.toml index f4c0784..33d20ed 100644 --- a/config.toml +++ b/config.toml @@ -1,3 +1,7 @@ dsn_address = "https://eyes.nasa.gov/dsn/data/dsn.xml" polling_rate = 5 -spacecraft = [] \ No newline at end of file + +[[ spacecraft ]] +name = "JNO" +id = 61 +color = [235, 161, 101] \ No newline at end of file diff --git a/rpi-rgb-led-matrix b/rpi-rgb-led-matrix new file mode 160000 index 0000000..dfc27c1 --- /dev/null +++ b/rpi-rgb-led-matrix @@ -0,0 +1 @@ +Subproject commit dfc27c15c224a92496034a39512a274744879e86 diff --git a/src/config/mod.rs b/src/config/mod.rs index 8b678c3..5136b1f 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,22 +1,33 @@ -use serde::Deserialize; use config::{Config, ConfigError, File}; +use rpi_led_matrix::LedColor; +use serde::Deserialize; #[derive(Debug, Deserialize, Clone)] pub struct SpacecraftConfig { pub spacecraft_name: String, pub spacecraft_id: u64, - pub color: (u8, u8, u8) + pub color: (u8, u8, u8), +} + +impl SpacecraftConfig { + fn color(&self) -> LedColor { + LedColor { + red: self.color.0, + green: self.color.1, + blue: self.color.2, + } + } } #[derive(Debug, Deserialize, Clone)] pub struct VisualizerConfig { pub dsn_address: String, pub polling_rate: u64, - pub spacecraft: Vec + pub spacecraft: Vec, } impl VisualizerConfig { - pub fn new(config_path: &str) -> Result { + pub fn new(config_path: &str) -> Result { let mut cfg = Config::new(); cfg.merge(File::with_name(config_path))?; cfg.try_into() diff --git a/src/dsn/error.rs b/src/dsn/error.rs index b1fdf9a..0e30335 100644 --- a/src/dsn/error.rs +++ b/src/dsn/error.rs @@ -49,7 +49,7 @@ impl std::fmt::Display for ParseError { #[derive(Debug)] pub enum DSNError { ParseErr(ParseError), - ReqwestErr(reqwest::Error) + ReqwestErr(reqwest::Error), } impl From for DSNError { @@ -68,8 +68,7 @@ impl std::fmt::Display for DSNError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { DSNError::ParseErr(e) => write!(f, "Req Parse Error: {}", e), - DSNError::ReqwestErr(e) => write!(f, "Reqwest Error: {}", e) + DSNError::ReqwestErr(e) => write!(f, "Reqwest Error: {}", e), } } } - diff --git a/src/dsn/mod.rs b/src/dsn/mod.rs index 4c52928..79f0fba 100644 --- a/src/dsn/mod.rs +++ b/src/dsn/mod.rs @@ -4,18 +4,18 @@ use std::io::BufReader; use std::time::{SystemTime, UNIX_EPOCH}; use reqwest::blocking::Client; -use xml::EventReader; use xml::reader::XmlEvent; +use xml::EventReader; use error::ParseError; -use crate::dsn::models::{Dish, Signal, Station, Target}; use crate::dsn::error::DSNError; +use crate::dsn::models::{Dish, Signal, Station, Target}; -pub mod models; pub mod error; +pub mod models; -pub fn dsn_request(client: &Client, dsn_api_url: &str) -> Result, DSNError> { +pub fn dsn_request(client: &Client, dsn_api_url: &str) -> Result, DSNError> { let mut params = HashMap::new(); let timestamp = (SystemTime::now() .duration_since(UNIX_EPOCH) @@ -25,11 +25,7 @@ pub fn dsn_request(client: &Client, dsn_api_url: &str) -> Result, D params.insert("r", timestamp); - let resp = client - .get(dsn_api_url) - .query(¶ms) - .send()? - .text()?; + let resp = client.get(dsn_api_url).query(¶ms).send()?.text()?; let file = BufReader::new(resp.as_bytes()); diff --git a/src/dsn/models.rs b/src/dsn/models.rs index 9933ad7..218eb06 100644 --- a/src/dsn/models.rs +++ b/src/dsn/models.rs @@ -58,7 +58,7 @@ pub enum SignalType { Data, Carrier, None, - Unkown(String) + Unkown(String), } impl From for SignalType { @@ -67,7 +67,7 @@ impl From for SignalType { "data" => SignalType::Data, "carrier" => SignalType::Carrier, "none" => SignalType::None, - _ => SignalType::Unkown(s) + _ => SignalType::Unkown(s), } } } diff --git a/src/main.rs b/src/main.rs index 21a5fa2..ef15440 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,15 @@ -use crate::dsn::dsn_request; -use reqwest::blocking::Client; use crate::config::VisualizerConfig; +use crate::dsn::dsn_request; +use crate::dsn::models::{Signal, SignalType, Station, Target}; +use matrix::Display; +use reqwest::blocking::Client; +use rpi_led_matrix::{LedColor, LedMatrix, LedMatrixOptions, LedRuntimeOptions}; use std::sync::mpsc; use std::time::Duration; -use crate::dsn::models::{Station, SignalType}; -mod dsn; mod config; +mod dsn; +mod matrix; fn dsn_thread(config: VisualizerConfig, tx: mpsc::Sender>) { let client = Client::new(); @@ -28,30 +31,54 @@ fn dsn_thread(config: VisualizerConfig, tx: mpsc::Sender>) { } } - fn main() { - let (tx,rx) = mpsc::channel::>(); + let (tx, rx) = mpsc::channel::>(); let config = VisualizerConfig::new("config.toml").unwrap(); + let sleep_duration = Duration::from_millis(1); + let mut x = 0; + + let mut display = Display::new(64, 64); std::thread::spawn(move || dsn_thread(config, tx)); loop { - let stations = rx.recv().unwrap(); - for station in stations { - for dish in station.dishes { - for target in dish.target { - let up_signal = dish.up_signal.iter().find(|s| s.spacecraft_id == Some(target.id) && SignalType::Data == s.signal_type); - let down_signal = dish.down_signal.iter().find(|s| s.spacecraft_id == Some(target.id) && SignalType::Data == s.signal_type); + let mut blu = 0; + let mut red = 255; + let stations = rx.recv_timeout(sleep_duration); + if let Ok(stations) = stations { + for station in stations { + for dish in station.dishes { + for target in dish.target { + let up_signal = dish.up_signal.iter().find(|s| { + s.spacecraft_id == Some(target.id) && SignalType::Data == s.signal_type + }); + let down_signal = dish.down_signal.iter().find(|s| { + s.spacecraft_id == Some(target.id) && SignalType::Data == s.signal_type + }); - if let Some(up_signal) = up_signal { - println!("{}: Uplink Rate: {}, Up leg Distance: {}", target.name, up_signal.data_rate.unwrap_or(0.0), target.upleg_range) - } - else if let Some(down_signal) = down_signal { - println!("{}: Downlink Rate: {}, Down leg Distance: {}", target.name, down_signal.data_rate.unwrap_or(0.0), target.downleg_range) + if up_signal.is_some() || down_signal.is_some() { + + let color = config.spacecraft.iter().find(|spacecraft| spacecraft.spacecraft_id == target.id) + + display.update_status( + LedColor { + red: red, + green: 0, + blue: blu, + }, + target, + up_signal.cloned(), + down_signal.cloned(), + ); + + blu += 20; + red -= 20; + } } } } } - } + display.refresh_display(); + } } diff --git a/src/matrix/mod.rs b/src/matrix/mod.rs new file mode 100644 index 0000000..b0c94e9 --- /dev/null +++ b/src/matrix/mod.rs @@ -0,0 +1,107 @@ +use crate::dsn::models::{Signal, Target}; +use rpi_led_matrix::{LedCanvas, LedColor, LedMatrix, LedMatrixOptions, LedRuntimeOptions}; +use std::collections::HashMap; + +struct ColumnStatus { + color: LedColor, + target: Target, + state: i32, + up_signal: Option, + down_signal: Option, +} + +pub struct Display { + matrix: LedMatrix, + column_statuses: HashMap, +} + +impl Display { + pub fn new(width: u32, height: u32) -> Self { + let mut matrix_config = LedMatrixOptions::new(); + + matrix_config.set_cols(width); + matrix_config.set_rows(height); + matrix_config.set_hardware_mapping("adafruit-hat-pwm"); + matrix_config.set_pwm_bits(7); + + let mut rt_config = LedRuntimeOptions::new(); + rt_config.set_gpio_slowdown(0); + + let matrix = LedMatrix::new(Some(matrix_config), Some(rt_config)).unwrap(); + + Self { + matrix, + column_statuses: HashMap::new(), + } + } + + pub fn update_status( + &mut self, + color: LedColor, + target: Target, + up_signal: Option, + down_signal: Option, + ) { + self.column_statuses.insert( + target.id, + ColumnStatus { + color, + target, + state: 0, + up_signal, + down_signal, + }, + ); + } + + fn uplink_animation(canvas: &mut LedCanvas, col_status: &mut ColumnStatus, position: i32) { + canvas.draw_line( + col_status.state, + (position) * 2, + col_status.state - 5, + (position) * 2, + &col_status.color, + ); + canvas.draw_line( + col_status.state, + (position) * 2 + 1, + col_status.state - 5, + (position) * 2 + 1, + &col_status.color, + ); + col_status.state = (col_status.state + 1) % canvas.canvas_size().0; + } + + fn downlink_animation(canvas: &mut LedCanvas, col_status: &mut ColumnStatus, position: i32) { + canvas.draw_line( + (canvas.canvas_size().0 - col_status.state) as i32, + (position) * 2, + (canvas.canvas_size().0 - col_status.state + 5) as i32, + (position) * 2, + &col_status.color, + ); + canvas.draw_line( + (canvas.canvas_size().0 - col_status.state) as i32, + (position) * 2 + 1, + (canvas.canvas_size().0 - col_status.state + 5) as i32, + (position) * 2 + 1, + &col_status.color, + ); + col_status.state = (col_status.state + 1) % canvas.canvas_size().0; + } + + pub fn refresh_display(&mut self) { + let mut canvas = self.matrix.offscreen_canvas(); + canvas.clear(); + let mut pos = 0; + for (_, col_status) in self.column_statuses.iter_mut() { + if let Some(up_signal) = &col_status.up_signal { + Display::uplink_animation(&mut canvas, col_status, pos) + } else if let Some(down_signal) = &col_status.down_signal { + Display::downlink_animation(&mut canvas, col_status, pos) + } + pos += 1 + } + self.matrix.swap(canvas); + } +}