parent
b0621b636d
commit
e80521af14
|
@ -0,0 +1,2 @@
|
||||||
|
[target.arm-unknown-linux-gnueabihf]
|
||||||
|
linker = "arm-linux-gnueabihf-gcc"
|
|
@ -0,0 +1,3 @@
|
||||||
|
[submodule "rpi-rgb-led-matrix"]
|
||||||
|
path = rpi-rgb-led-matrix
|
||||||
|
url = git@github.com:hzeller/rpi-rgb-led-matrix.git
|
|
@ -115,6 +115,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"config",
|
"config",
|
||||||
|
"openssl",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"rpi-led-matrix",
|
"rpi-led-matrix",
|
||||||
"serde 1.0.126",
|
"serde 1.0.126",
|
||||||
|
@ -565,6 +566,15 @@ version = "0.1.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a"
|
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]]
|
[[package]]
|
||||||
name = "openssl-sys"
|
name = "openssl-sys"
|
||||||
version = "0.9.63"
|
version = "0.9.63"
|
||||||
|
@ -574,6 +584,7 @@ dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"cc",
|
"cc",
|
||||||
"libc",
|
"libc",
|
||||||
|
"openssl-src",
|
||||||
"pkg-config",
|
"pkg-config",
|
||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|
|
@ -3,10 +3,12 @@ name = "dsn-visualizer"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Joey Hines <joey@ahines.net>"]
|
authors = ["Joey Hines <joey@ahines.net>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
build = "build.rs"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
openssl = { version = "0.10", features = ["vendored"] }
|
||||||
reqwest = {version = "0.11", features=["blocking"]}
|
reqwest = {version = "0.11", features=["blocking"]}
|
||||||
chrono = {version = "0.4.19", features=["serde"]}
|
chrono = {version = "0.4.19", features=["serde"]}
|
||||||
xml-rs = "0.8.3"
|
xml-rs = "0.8.3"
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
fn main() {
|
||||||
|
println!("cargo:rustc-link-search=rpi-rgb-led-matrix/lib/");
|
||||||
|
println!("cargo:rustc-flags=-l dylib=stdc++");
|
||||||
|
}
|
|
@ -1,3 +1,7 @@
|
||||||
dsn_address = "https://eyes.nasa.gov/dsn/data/dsn.xml"
|
dsn_address = "https://eyes.nasa.gov/dsn/data/dsn.xml"
|
||||||
polling_rate = 5
|
polling_rate = 5
|
||||||
spacecraft = []
|
|
||||||
|
[[ spacecraft ]]
|
||||||
|
name = "JNO"
|
||||||
|
id = 61
|
||||||
|
color = [235, 161, 101]
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit dfc27c15c224a92496034a39512a274744879e86
|
|
@ -1,22 +1,33 @@
|
||||||
use serde::Deserialize;
|
|
||||||
use config::{Config, ConfigError, File};
|
use config::{Config, ConfigError, File};
|
||||||
|
use rpi_led_matrix::LedColor;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
pub struct SpacecraftConfig {
|
pub struct SpacecraftConfig {
|
||||||
pub spacecraft_name: String,
|
pub spacecraft_name: String,
|
||||||
pub spacecraft_id: u64,
|
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)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
pub struct VisualizerConfig {
|
pub struct VisualizerConfig {
|
||||||
pub dsn_address: String,
|
pub dsn_address: String,
|
||||||
pub polling_rate: u64,
|
pub polling_rate: u64,
|
||||||
pub spacecraft: Vec<SpacecraftConfig>
|
pub spacecraft: Vec<SpacecraftConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VisualizerConfig {
|
impl VisualizerConfig {
|
||||||
pub fn new(config_path: &str) -> Result<Self,ConfigError> {
|
pub fn new(config_path: &str) -> Result<Self, ConfigError> {
|
||||||
let mut cfg = Config::new();
|
let mut cfg = Config::new();
|
||||||
cfg.merge(File::with_name(config_path))?;
|
cfg.merge(File::with_name(config_path))?;
|
||||||
cfg.try_into()
|
cfg.try_into()
|
||||||
|
|
|
@ -49,7 +49,7 @@ impl std::fmt::Display for ParseError {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum DSNError {
|
pub enum DSNError {
|
||||||
ParseErr(ParseError),
|
ParseErr(ParseError),
|
||||||
ReqwestErr(reqwest::Error)
|
ReqwestErr(reqwest::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ParseError> for DSNError {
|
impl From<ParseError> for DSNError {
|
||||||
|
@ -68,8 +68,7 @@ impl std::fmt::Display for DSNError {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
DSNError::ParseErr(e) => write!(f, "Req Parse Error: {}", e),
|
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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,18 +4,18 @@ use std::io::BufReader;
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
use reqwest::blocking::Client;
|
use reqwest::blocking::Client;
|
||||||
use xml::EventReader;
|
|
||||||
use xml::reader::XmlEvent;
|
use xml::reader::XmlEvent;
|
||||||
|
use xml::EventReader;
|
||||||
|
|
||||||
use error::ParseError;
|
use error::ParseError;
|
||||||
|
|
||||||
use crate::dsn::models::{Dish, Signal, Station, Target};
|
|
||||||
use crate::dsn::error::DSNError;
|
use crate::dsn::error::DSNError;
|
||||||
|
use crate::dsn::models::{Dish, Signal, Station, Target};
|
||||||
|
|
||||||
pub mod models;
|
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
pub mod models;
|
||||||
|
|
||||||
pub fn dsn_request(client: &Client, dsn_api_url: &str) -> Result<Vec<Station>, DSNError> {
|
pub fn dsn_request(client: &Client, dsn_api_url: &str) -> Result<Vec<Station>, DSNError> {
|
||||||
let mut params = HashMap::new();
|
let mut params = HashMap::new();
|
||||||
let timestamp = (SystemTime::now()
|
let timestamp = (SystemTime::now()
|
||||||
.duration_since(UNIX_EPOCH)
|
.duration_since(UNIX_EPOCH)
|
||||||
|
@ -25,11 +25,7 @@ pub fn dsn_request(client: &Client, dsn_api_url: &str) -> Result<Vec<Station>, D
|
||||||
|
|
||||||
params.insert("r", timestamp);
|
params.insert("r", timestamp);
|
||||||
|
|
||||||
let resp = client
|
let resp = client.get(dsn_api_url).query(¶ms).send()?.text()?;
|
||||||
.get(dsn_api_url)
|
|
||||||
.query(¶ms)
|
|
||||||
.send()?
|
|
||||||
.text()?;
|
|
||||||
|
|
||||||
let file = BufReader::new(resp.as_bytes());
|
let file = BufReader::new(resp.as_bytes());
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,7 @@ pub enum SignalType {
|
||||||
Data,
|
Data,
|
||||||
Carrier,
|
Carrier,
|
||||||
None,
|
None,
|
||||||
Unkown(String)
|
Unkown(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<String> for SignalType {
|
impl From<String> for SignalType {
|
||||||
|
@ -67,7 +67,7 @@ impl From<String> for SignalType {
|
||||||
"data" => SignalType::Data,
|
"data" => SignalType::Data,
|
||||||
"carrier" => SignalType::Carrier,
|
"carrier" => SignalType::Carrier,
|
||||||
"none" => SignalType::None,
|
"none" => SignalType::None,
|
||||||
_ => SignalType::Unkown(s)
|
_ => SignalType::Unkown(s),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
63
src/main.rs
63
src/main.rs
|
@ -1,12 +1,15 @@
|
||||||
use crate::dsn::dsn_request;
|
|
||||||
use reqwest::blocking::Client;
|
|
||||||
use crate::config::VisualizerConfig;
|
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::sync::mpsc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use crate::dsn::models::{Station, SignalType};
|
|
||||||
|
|
||||||
mod dsn;
|
|
||||||
mod config;
|
mod config;
|
||||||
|
mod dsn;
|
||||||
|
mod matrix;
|
||||||
|
|
||||||
fn dsn_thread(config: VisualizerConfig, tx: mpsc::Sender<Vec<Station>>) {
|
fn dsn_thread(config: VisualizerConfig, tx: mpsc::Sender<Vec<Station>>) {
|
||||||
let client = Client::new();
|
let client = Client::new();
|
||||||
|
@ -28,30 +31,54 @@ fn dsn_thread(config: VisualizerConfig, tx: mpsc::Sender<Vec<Station>>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let (tx,rx) = mpsc::channel::<Vec<Station>>();
|
let (tx, rx) = mpsc::channel::<Vec<Station>>();
|
||||||
let config = VisualizerConfig::new("config.toml").unwrap();
|
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));
|
std::thread::spawn(move || dsn_thread(config, tx));
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let stations = rx.recv().unwrap();
|
let mut blu = 0;
|
||||||
for station in stations {
|
let mut red = 255;
|
||||||
for dish in station.dishes {
|
let stations = rx.recv_timeout(sleep_duration);
|
||||||
for target in dish.target {
|
if let Ok(stations) = stations {
|
||||||
let up_signal = dish.up_signal.iter().find(|s| s.spacecraft_id == Some(target.id) && SignalType::Data == s.signal_type);
|
for station in stations {
|
||||||
let down_signal = dish.down_signal.iter().find(|s| s.spacecraft_id == Some(target.id) && SignalType::Data == s.signal_type);
|
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 {
|
if up_signal.is_some() || down_signal.is_some() {
|
||||||
println!("{}: Uplink Rate: {}, Up leg Distance: {}", target.name, up_signal.data_rate.unwrap_or(0.0), target.upleg_range)
|
|
||||||
}
|
let color = config.spacecraft.iter().find(|spacecraft| spacecraft.spacecraft_id == target.id)
|
||||||
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)
|
display.update_status(
|
||||||
|
LedColor {
|
||||||
|
red: red,
|
||||||
|
green: 0,
|
||||||
|
blue: blu,
|
||||||
|
},
|
||||||
|
target,
|
||||||
|
up_signal.cloned(),
|
||||||
|
down_signal.cloned(),
|
||||||
|
);
|
||||||
|
|
||||||
|
blu += 20;
|
||||||
|
red -= 20;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
display.refresh_display();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<Signal>,
|
||||||
|
down_signal: Option<Signal>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Display {
|
||||||
|
matrix: LedMatrix,
|
||||||
|
column_statuses: HashMap<u32, ColumnStatus>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<Signal>,
|
||||||
|
down_signal: Option<Signal>,
|
||||||
|
) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue