Basic Matrix Display

+ Matrix driver working
+ Basic animations in place
main
Joey Hines 2021-07-10 19:56:29 -06:00
parent b0621b636d
commit e80521af14
No known key found for this signature in database
GPG Key ID: 80F567B5C968F91B
13 changed files with 204 additions and 37 deletions

2
.cargo/config 100644
View File

@ -0,0 +1,2 @@
[target.arm-unknown-linux-gnueabihf]
linker = "arm-linux-gnueabihf-gcc"

3
.gitmodules vendored 100644
View File

@ -0,0 +1,3 @@
[submodule "rpi-rgb-led-matrix"]
path = rpi-rgb-led-matrix
url = git@github.com:hzeller/rpi-rgb-led-matrix.git

11
Cargo.lock generated
View File

@ -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",
]

View File

@ -3,10 +3,12 @@ name = "dsn-visualizer"
version = "0.1.0"
authors = ["Joey Hines <joey@ahines.net>"]
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"

4
build.rs 100644
View File

@ -0,0 +1,4 @@
fn main() {
println!("cargo:rustc-link-search=rpi-rgb-led-matrix/lib/");
println!("cargo:rustc-flags=-l dylib=stdc++");
}

View File

@ -1,3 +1,7 @@
dsn_address = "https://eyes.nasa.gov/dsn/data/dsn.xml"
polling_rate = 5
spacecraft = []
[[ spacecraft ]]
name = "JNO"
id = 61
color = [235, 161, 101]

@ -0,0 +1 @@
Subproject commit dfc27c15c224a92496034a39512a274744879e86

View File

@ -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<SpacecraftConfig>
pub spacecraft: Vec<SpacecraftConfig>,
}
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();
cfg.merge(File::with_name(config_path))?;
cfg.try_into()

View File

@ -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<ParseError> 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),
}
}
}

View File

@ -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<Vec<Station>, DSNError> {
pub fn dsn_request(client: &Client, dsn_api_url: &str) -> Result<Vec<Station>, 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<Vec<Station>, D
params.insert("r", timestamp);
let resp = client
.get(dsn_api_url)
.query(&params)
.send()?
.text()?;
let resp = client.get(dsn_api_url).query(&params).send()?.text()?;
let file = BufReader::new(resp.as_bytes());

View File

@ -58,7 +58,7 @@ pub enum SignalType {
Data,
Carrier,
None,
Unkown(String)
Unkown(String),
}
impl From<String> for SignalType {
@ -67,7 +67,7 @@ impl From<String> for SignalType {
"data" => SignalType::Data,
"carrier" => SignalType::Carrier,
"none" => SignalType::None,
_ => SignalType::Unkown(s)
_ => SignalType::Unkown(s),
}
}
}

View File

@ -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<Vec<Station>>) {
let client = Client::new();
@ -28,30 +31,54 @@ fn dsn_thread(config: VisualizerConfig, tx: mpsc::Sender<Vec<Station>>) {
}
}
fn main() {
let (tx,rx) = mpsc::channel::<Vec<Station>>();
let (tx, rx) = mpsc::channel::<Vec<Station>>();
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();
}
}

107
src/matrix/mod.rs 100644
View File

@ -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);
}
}