Major rewrites, signalk support almost there
Font and forms refactored, but not working yet, will explore Rusttype lifetime issues in seperate project
This commit is contained in:
634
Cargo.lock
generated
634
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -14,9 +14,12 @@ arrayref = "*"
|
||||
chrono = "*"
|
||||
chrono-tz = "*"
|
||||
euclid = "*"
|
||||
log = "*"
|
||||
log4rs = { version = "*", features = ["file", "yaml_format", "console_appender"] }
|
||||
xml-rs = "0.8"
|
||||
text_io = "0.1.8"
|
||||
serde = { version = "*", features = ["derive"] }
|
||||
serde_json = "*"
|
||||
rusttype = "0.8.3"
|
||||
rusttype = "0.9.2"
|
||||
tungstenite = "*"
|
||||
url = "*"
|
||||
|
||||
@@ -12,7 +12,7 @@ The idea is to find a cheap, small, GPIO connected monitor and have it display u
|
||||
* Very cheap cockpit display software for Openplotter on the Raspberry PI.
|
||||
* Bought on eBay, for size, and use of less GPIO pins. Thus ended up buying an ili9488 based 3.5 inch TFT LCD. I would love to get something transflective in the future. It turned out the ili9488 is very poorly supported in the linux fb world; I've ordered a 2.8 inch ili9341 based display to try that one out too.
|
||||
* Reference platform is the Raspberry PI 3 Model B; reasons being:
|
||||
* It seems to be powerful enough, it's a 1.2 GHz 64 bit qtad-core ARM Cortex-A53 processor and 1 GiB of memory. Furthermore is has 4 USB ports, making it less likely that I'll need a USB hub; also, it has WiFi should I want to play around with that. Newer, more powerful models are very neat, but they also have a significantly higher power consumption, model B idles at about 230 mA (1.2 W) where the B+, for example, idles at 350 mA (1.7 W)
|
||||
* It seems to be powerful enough, it's a 1.2 GHz 64 bit quad-core ARM Cortex-A53 processor and 1 GiB of memory. Furthermore is has 4 USB ports, making it less likely that I'll need a USB hub; also, it has WiFi should I want to play around with that. Newer, more powerful models are very neat, but they also have a significantly higher power consumption, model B idles at about 230 mA (1.2 W) where the B+, for example, idles at 350 mA (1.7 W)
|
||||
|
||||
|
||||
# Software considerations
|
||||
|
||||
8
log4rs.yml
Normal file
8
log4rs.yml
Normal file
@@ -0,0 +1,8 @@
|
||||
refresh_rate: 30 seconds
|
||||
appenders:
|
||||
stdout:
|
||||
kind: console
|
||||
root:
|
||||
level: info
|
||||
appenders:
|
||||
- stdout
|
||||
22
src/forms.rs
22
src/forms.rs
@@ -11,6 +11,7 @@ use std::error::Error;
|
||||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
use std::io::Read;
|
||||
use std::rc::Rc;
|
||||
use std::fs;
|
||||
use xml::reader::{EventReader, XmlEvent};
|
||||
use euclid::Point2D;
|
||||
@@ -72,20 +73,13 @@ impl Loader {
|
||||
|
||||
|
||||
pub fn load_font (&mut self, path: &str) -> Option<Font> {
|
||||
let mut file = match self.make_file(path) {
|
||||
Some(f) => f,
|
||||
None => return None,
|
||||
let fullpath = match self.make_path(path) {
|
||||
Some(p) => p,
|
||||
None => return None,
|
||||
};
|
||||
let mut data = Vec::new();
|
||||
if file.read_to_end(&mut data).is_err() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let font = match Font::from_bytes(data) {
|
||||
Ok(data) => data,
|
||||
Err(_) => return None,
|
||||
};
|
||||
Some(font)
|
||||
let data = fs::read(fullpath).expect("File read error");
|
||||
let font = move || Font::try_from_bytes(&data).unwrap();
|
||||
Some(font())
|
||||
}
|
||||
|
||||
pub fn load_form(&mut self, path: &str) -> Option<Form> {
|
||||
@@ -155,7 +149,7 @@ impl Screen {
|
||||
|
||||
|
||||
pub fn render (&mut self, form: &Form, angle: f32, x: u16, y: u16) {
|
||||
let transform : Transform2D<f32, ModelSpace, ModelSpace> = Transform2D::create_rotation(Angle::radians(angle)).post_translate(Vector2D::new(500.0, 500.0));
|
||||
let transform : Transform2D<f32, ModelSpace, ModelSpace> = Transform2D::rotation(Angle::radians(angle)).then_translate(Vector2D::new(500.0, 500.0));
|
||||
let tx_points = form.points.iter().map(|p| transform.transform_point(*p)).collect::<Vec<_>>();
|
||||
let screen_points = tx_points.iter().map(|p| self.model_scale.transform_point(*p)).collect::<Vec<_>>();
|
||||
for (indices, weight, (r, g, b)) in &form.lines {
|
||||
|
||||
175
src/main.rs
175
src/main.rs
@@ -6,7 +6,11 @@ use std::time::Duration;
|
||||
use std::f32::consts::PI;
|
||||
use chrono::{DateTime, Utc};
|
||||
use chrono_tz::Tz;
|
||||
use rusttype::Font;
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
use log4rs;
|
||||
|
||||
use image::{
|
||||
Rgb,
|
||||
@@ -18,12 +22,134 @@ use imageproc::drawing::draw_filled_rect_mut;
|
||||
mod ilidisplay;
|
||||
mod forms;
|
||||
mod signalk;
|
||||
mod vesseldata;
|
||||
use vesseldata::VesselDataEventSource;
|
||||
|
||||
pub struct HelmsDisplay {
|
||||
font: Box<Font<'static>>,
|
||||
boat: forms::Form,
|
||||
compassrose: forms::Form,
|
||||
cog: forms::Form,
|
||||
wind: forms::Form,
|
||||
gps_screen: forms::Screen,
|
||||
time_screen: forms::Screen,
|
||||
course_screen: forms::Screen,
|
||||
}
|
||||
|
||||
impl HelmsDisplay {
|
||||
|
||||
pub fn new() -> HelmsDisplay {
|
||||
// in the future, this method should probably take some sort of
|
||||
// configuration object.
|
||||
let mut loader = forms::Loader::new("/root/helms-display".to_string());
|
||||
let boat = loader.load_form("boat.svg").unwrap();
|
||||
let compassrose = loader.load_form("compass-rose.svg").unwrap();
|
||||
let cog = loader.load_form("cog.svg").unwrap();
|
||||
let wind = loader.load_form("wind.svg").unwrap();
|
||||
let f = loader.load_font("font.ttf").unwrap();
|
||||
let font = Box::new(f);
|
||||
|
||||
HelmsDisplay {
|
||||
font: font,
|
||||
boat: boat,
|
||||
compassrose: compassrose,
|
||||
cog: cog,
|
||||
wind: wind,
|
||||
gps_screen: forms::Screen::new(400, 40),
|
||||
time_screen: forms::Screen::new(200, 60),
|
||||
course_screen: forms::Screen::new(200, 200),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_gps (&mut self, loc: Option<vesseldata::VesselDataEvent>) {
|
||||
self.gps_screen.clear();
|
||||
match loc {
|
||||
None => self.gps_screen.text_c(&self.font, " --\u{00B0}--\u{2032}--\u{2033} --\u{00B0}--\u{2032}--\u{2033}", 32.0, 200, 5),
|
||||
Some(vesseldata::VesselDataEvent::Location(lat,lon)) => {
|
||||
|
||||
// format with unicodes for degrees, minutes and seconds.
|
||||
let (latitude, lat_ew) = {
|
||||
if lat < 0.0 {
|
||||
(-lat, 'W')
|
||||
} else if lat > 0.0 {
|
||||
(lat, 'E')
|
||||
} else {
|
||||
(0.0, '-')
|
||||
}
|
||||
};
|
||||
|
||||
let (longitude, long_ns) = {
|
||||
if lon < 0.0 {
|
||||
(-lon, 'S')
|
||||
} else if lon > 0.0 {
|
||||
(lon, 'N')
|
||||
} else {
|
||||
(0.0, '-')
|
||||
}
|
||||
};
|
||||
|
||||
let long_d: u8 = longitude.trunc() as u8;
|
||||
let longitude = longitude.fract() * 60.0;
|
||||
let long_m: u8 = longitude.trunc() as u8;
|
||||
let longitude = longitude.fract() * 60.0;
|
||||
let long_s: u8 = longitude.round() as u8;
|
||||
|
||||
let lat_d: u8 = latitude.trunc() as u8;
|
||||
let latitude = latitude.fract() * 60.0;
|
||||
let lat_m: u8 = latitude.trunc() as u8;
|
||||
let latitude = latitude.fract() * 60.0;
|
||||
let lat_s: u8 = latitude.round() as u8;
|
||||
self.gps_screen.text_c(&self.font,
|
||||
format!("{}{:02}\u{00B0}{:02}\u{2032}{:02}\u{2033} {}{:02}\u{00B0}{:02}\u{2032}{:02}\u{2033}",
|
||||
long_ns, long_d, long_m, long_s, lat_ew, lat_d, lat_m, lat_s).as_str(), 32.0, 200, 5);
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_time (&mut self) {
|
||||
let tz: Tz = "Europe/Copenhagen".parse().unwrap();
|
||||
let now = Utc::now().with_timezone(&tz);
|
||||
self.time_screen.text_c(&self.font, now.format("%H:%M:%S %Z").to_string().as_str(), 24.0, 100, 1);
|
||||
self.time_screen.text_c(&self.font, "Europe/Copenhagen", 16.0, 100, 25);
|
||||
self.time_screen.text_c(&self.font, now.format("%Y-%m-%d").to_string().as_str(), 16.0, 100, 43);
|
||||
}
|
||||
|
||||
pub fn render_course (&mut self, cog: Option<vesseldata::VesselDataEvent>, magnetic: Option<vesseldata::VesselDataEvent>, wind: Option<vesseldata::VesselDataEvent>) {
|
||||
let rad = (6 as f32) * 2.0*PI / 100.0;
|
||||
self.course_screen.clear();
|
||||
|
||||
// render course over ground if present
|
||||
match cog {
|
||||
Some(vesseldata::VesselDataEvent::CourseOverGround(c)) => {
|
||||
self.course_screen.render(&self.cog, c, 500, 500);
|
||||
},
|
||||
_ => (),
|
||||
};
|
||||
|
||||
// render wind if present
|
||||
match wind {
|
||||
Some(vesseldata::VesselDataEvent::WindOrigin(o)) => {
|
||||
self.course_screen.render(&self.wind, o, 500, 500);
|
||||
},
|
||||
_ => (),
|
||||
};
|
||||
|
||||
// render compass rose
|
||||
|
||||
self.course_screen.render(&self.compassrose, rad, 500, 500);
|
||||
self.course_screen.render(&self.boat, 0.0, 500, 500);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
|
||||
// init log library
|
||||
log4rs::init_file("log4rs.yml", Default::default()).unwrap();
|
||||
|
||||
let mut course_screen = forms::Screen::new(200, 200);
|
||||
let mut sog_screen = forms::Screen::new(160,60);
|
||||
let mut gps_screen = forms::Screen::new(400, 40);
|
||||
let mut time_screen = forms::Screen::new(200, 60);
|
||||
|
||||
let mut loader = forms::Loader::new("/root/helms-display".to_string());
|
||||
@@ -32,8 +158,10 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
let b = loader.load_form("boat.svg").unwrap();
|
||||
let cog = loader.load_form("cog.svg").unwrap();
|
||||
let wind = loader.load_form("wind.svg").unwrap();
|
||||
let font = loader.load_font("font.ttf").unwrap();
|
||||
//let font = loader.load_font("font.ttf").unwrap();
|
||||
let f2 = loader.load_font("font.ttf").unwrap();
|
||||
|
||||
let mut helms = HelmsDisplay::new();
|
||||
/*
|
||||
let mut img = RgbImage::new(480, 320);
|
||||
let thickness = 10;
|
||||
@@ -54,8 +182,9 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
course_screen.render(&wind, rad*2.0, 500, 500);
|
||||
course_screen.render(&c, rad, 500, 500);
|
||||
course_screen.render(&b, 0.0, 500, 500);
|
||||
e.put_image(&(course_screen.image), (160, (160-100)));
|
||||
e.put_image(&(course_screen.image), (140, (160-110)));
|
||||
|
||||
/*
|
||||
sog_screen.clear();
|
||||
sog_screen.text(&font, "SOG", 32.0, 5, 5);
|
||||
sog_screen.text(&font, "speed over ground", 12.0, 5, 38);
|
||||
@@ -63,41 +192,21 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
let speed_over_ground = speed_over_ground * 1.9438612860586; // now in nautic miles per hour
|
||||
sog_screen.text_rj(&font, format!("{:.1}", speed_over_ground).as_str(), 32.0, 138, 5);
|
||||
sog_screen.fraction(&font, "nm", "h", 14.0, 140, 6);
|
||||
e.put_image(&(sog_screen.image), (0, 0));
|
||||
e.put_image(&(sog_screen.image), (0, 50));
|
||||
*/
|
||||
|
||||
gps_screen.clear();
|
||||
// format with unicodes for degrees, minutes and seconds.
|
||||
let longitude: f32 = 55.658863;
|
||||
let latitude: f32 = 12.480960;
|
||||
helms.render_gps(None);
|
||||
e.put_image(&(helms.gps_screen.image), (40,0));
|
||||
|
||||
let long_d: u8 = longitude.trunc() as u8;
|
||||
let longitude = longitude.fract() * 60.0;
|
||||
let long_m: u8 = longitude.trunc() as u8;
|
||||
let longitude = longitude.fract() * 60.0;
|
||||
let long_s: u8 = longitude.round() as u8;
|
||||
let long_ns: char = 'N';
|
||||
|
||||
let lat_d: u8 = latitude.trunc() as u8;
|
||||
let latitude = latitude.fract() * 60.0;
|
||||
let lat_m: u8 = latitude.trunc() as u8;
|
||||
let latitude = latitude.fract() * 60.0;
|
||||
let lat_s: u8 = latitude.round() as u8;
|
||||
let lat_ew: char = 'E';
|
||||
gps_screen.text_c(&font, format!("{}{:02}\u{00B0}{:02}\u{2032}{:02}\u{2033} {}{:02}\u{00B0}{:02}\u{2032}{:02}\u{2033}", long_ns, long_d, long_m, long_s, lat_ew, lat_d, lat_m, lat_s).as_str(), 32.0, 200, 5);
|
||||
e.put_image(&(gps_screen.image), (240,370));
|
||||
|
||||
time_screen.clear();
|
||||
let tz: Tz = "Europe/Copenhagen".parse().unwrap();
|
||||
let now = Utc::now().with_timezone(&tz);
|
||||
time_screen.text_c(&font, now.format("%H:%M:%S %Z").to_string().as_str(), 24.0, 100, 1);
|
||||
time_screen.text_c(&font, "Europe/Copenhagen", 16.0, 100, 25);
|
||||
time_screen.text_c(&font, now.format("%Y-%m-%d").to_string().as_str(), 16.0, 100, 43);
|
||||
time_screen.save();
|
||||
helms.render_time();
|
||||
e.put_image(&(helms.time_screen.image), (140,260));
|
||||
|
||||
|
||||
println!("Display has been rendered now, sleeping for 5s");
|
||||
signalk::SignalKData::connect();
|
||||
thread::sleep(Duration::from_millis(5000));
|
||||
let vd = signalk::SignalK::connect();
|
||||
|
||||
thread::sleep(Duration::from_millis(15000));
|
||||
e.turn_off();
|
||||
Ok(())
|
||||
|
||||
}
|
||||
|
||||
116
src/signalk.rs
116
src/signalk.rs
@@ -1,32 +1,65 @@
|
||||
use std::thread;
|
||||
use std::io::{BufReader, Read};
|
||||
use tungstenite::connect;
|
||||
use url::Url;
|
||||
use serde_json::{Result, Value};
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value as JValue;
|
||||
use std::sync::mpsc::{Sender, Receiver};
|
||||
use std::sync::mpsc;
|
||||
use crate::vesseldata::{VesselDataEvent, VesselDataEventSource};
|
||||
|
||||
// Model the data sent over SignalK Websocket
|
||||
// that I'm interested in
|
||||
pub enum SignalKEvent {
|
||||
SpeedOverGround(f32),
|
||||
SpeedThroughWater(f32),
|
||||
Location(f32, f32),
|
||||
CourseOverGround(f32),
|
||||
TrueCompassCourse(f32),
|
||||
AISVessel(String, f32, f32, f32), // Name, lat, long, speed
|
||||
BatteryLevel(u8, u8), // Bank#, percentage
|
||||
FuelLevel(u8), // percentage
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde()]
|
||||
struct Source {
|
||||
sentence: String,
|
||||
talker: String,
|
||||
#[serde(rename = "type")]
|
||||
k_type: String,
|
||||
label: String,
|
||||
}
|
||||
|
||||
|
||||
pub struct SignalKData {
|
||||
latitude: f32,
|
||||
longitude: f32,
|
||||
position_timestamp: u64,
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde()]
|
||||
struct Value {
|
||||
path: String,
|
||||
value: JValue,
|
||||
}
|
||||
|
||||
impl SignalKData {
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde()]
|
||||
struct Update {
|
||||
#[serde(rename = "$source")]
|
||||
dsource: String,
|
||||
timestamp: String,
|
||||
values: Vec<Value>,
|
||||
}
|
||||
|
||||
pub fn parseJson (json: String) -> Vec<SignalKEvent> {
|
||||
let res = Vec::<SignalKEvent>::new();
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde()]
|
||||
struct SignalKEventData {
|
||||
context: String,
|
||||
updates: Vec<Update>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde()]
|
||||
struct SignalKHeader {
|
||||
name: String,
|
||||
version: String,
|
||||
#[serde(rename="self")]
|
||||
k_self: String,
|
||||
roles: Vec<String>,
|
||||
timestamp: String,
|
||||
}
|
||||
|
||||
pub struct SignalK {
|
||||
}
|
||||
|
||||
impl SignalK {
|
||||
|
||||
fn parse_json (json: String) -> Vec<SignalKEventData> {
|
||||
let res = Vec::<SignalKEventData>::new();
|
||||
let v: Value = match serde_json::from_str(&json) {
|
||||
Ok(value) => value,
|
||||
Err(_) => return res,
|
||||
@@ -37,22 +70,43 @@ impl SignalKData {
|
||||
res
|
||||
}
|
||||
|
||||
pub fn connect() -> SignalKData {
|
||||
let mut data = SignalKData {
|
||||
latitude: f32::NAN,
|
||||
longitude: f32::NAN,
|
||||
position_timestamp: 0,
|
||||
};
|
||||
let handle = thread::spawn(|| {
|
||||
fn value_to_vesseldata (val: Value) -> Option<VesselDataEvent> {
|
||||
match val.path.as_str() {
|
||||
"navigation.position" => return Some(VesselDataEvent::Location(val.value["latitude"].as_f64().unwrap() as f32, val.value["longitude"].as_f64().unwrap() as f32)),
|
||||
"navigation.headingMagnetic" => return Some(VesselDataEvent::TrueCompassCourse(val.value.as_f64().unwrap() as f32)),
|
||||
"navigation.courseOverGroundTrue" => return Some(VesselDataEvent::CourseOverGround(val.value.as_f64().unwrap() as f32)),
|
||||
"navigation.speedOverGround" => return Some(VesselDataEvent::SpeedOverGround(val.value.as_f64().unwrap() as f32)),
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VesselDataEventSource for SignalK {
|
||||
|
||||
fn connect() -> Receiver<VesselDataEvent> {
|
||||
let (tx, rx): (Sender<VesselDataEvent>, Receiver<VesselDataEvent>) = mpsc::channel();
|
||||
let handle = thread::spawn(move || {
|
||||
let (mut socket, response) = connect(Url::parse("ws://localhost:3000/signalk/v1/stream?subscribe=self").unwrap()).expect("Can't connect");
|
||||
let header: SignalKHeader = serde_json::from_str(socket.read_message().unwrap().to_text().unwrap()).unwrap();
|
||||
if header.version != "1.33.0" {
|
||||
warn!("SignalK parser has only been tested with Signal K Server version 1.33.0");
|
||||
}
|
||||
loop {
|
||||
let msg = socket.read_message().expect("Error reading message");
|
||||
if msg.is_text() {
|
||||
println!("{}", msg.into_text().unwrap());
|
||||
let message = socket.read_message().unwrap();
|
||||
if message.is_text() {
|
||||
let data: SignalKEventData = serde_json::from_str(message.to_text().unwrap()).unwrap();
|
||||
for u in data.updates {
|
||||
for v in u.values {
|
||||
match SignalK::value_to_vesseldata(v) {
|
||||
Some(vd) => tx.send(vd).unwrap(),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
data
|
||||
rx
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
83
src/vesseldata.rs
Normal file
83
src/vesseldata.rs
Normal file
@@ -0,0 +1,83 @@
|
||||
use std::fmt;
|
||||
use std::thread;
|
||||
use tungstenite::connect;
|
||||
use url::Url;
|
||||
use serde_json::{Result, Value};
|
||||
use std::sync::mpsc::Receiver;
|
||||
|
||||
// Model the data sent over SignalK Websocket
|
||||
// that I'm interested in
|
||||
pub enum VesselDataEvent {
|
||||
SpeedOverGround(f32),
|
||||
SpeedThroughWater(f32),
|
||||
Location(f32, f32),
|
||||
CourseOverGround(f32),
|
||||
WindOrigin(f32),
|
||||
TrueCompassCourse(f32),
|
||||
AISVessel(String, f32, f32, f32), // Name, lat, long, speed
|
||||
BatteryLevel(u8, u8), // Bank#, percentage
|
||||
FuelLevel(u8), // percentage
|
||||
}
|
||||
|
||||
pub trait VesselDataEventSource {
|
||||
|
||||
fn connect() -> Receiver<VesselDataEvent>;
|
||||
}
|
||||
|
||||
impl fmt::Debug for VesselDataEvent {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
VesselDataEvent::SpeedOverGround(s) => f.debug_tuple("SpeedOverGround").field(s).finish(),
|
||||
VesselDataEvent::SpeedThroughWater(s) => f.debug_tuple("SpeedThroughWater").field(s).finish(),
|
||||
VesselDataEvent::Location(lat, lon) => f.debug_tuple("Location").field(lat).field(lon).finish(),
|
||||
VesselDataEvent::CourseOverGround(c) => f.debug_tuple("CourseOverGround").field(c).finish(),
|
||||
VesselDataEvent::TrueCompassCourse(c) => f.debug_tuple("TrueCompassCourse").field(c).finish(),
|
||||
VesselDataEvent::AISVessel(name, lat, lon, speed) => f.debug_tuple("AISVessel").field(name).field(lat).field(lon).field(speed).finish(),
|
||||
VesselDataEvent::BatteryLevel(bank, level) => f.debug_tuple("BatteryLevel").field(bank).field(level).finish(),
|
||||
VesselDataEvent::FuelLevel(level) => f.debug_tuple("FuelLevel").field(level).finish(),
|
||||
VesselDataEvent::WindOrigin(c) => f.debug_tuple("WindOrigin").field(c).finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
pub struct VesselDataState {
|
||||
source: Receiver<VesselDataEvent>,
|
||||
pub latitude: f32,
|
||||
pub longitude: f32,
|
||||
pub speed_over_ground: f32,
|
||||
pub speed_through_water: f32,
|
||||
pub true_compass_course: f32,
|
||||
pub course_over_ground: f32,
|
||||
pub position_timestamp: u64,
|
||||
}
|
||||
|
||||
impl VesselDataState {
|
||||
pub fn init (source: Receiver<VesselDataEvent>) -> VesselDataState {
|
||||
VesselDataState {
|
||||
source: source,
|
||||
latitude: 0.0,
|
||||
longitude: 0.0,
|
||||
speed_over_ground: 0.0,
|
||||
speed_through_water: 0.0,
|
||||
true_compass_course: 0.0,
|
||||
course_over_ground: 0.0,
|
||||
position_timestamp: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_events(&mut self) -> () {
|
||||
for vde in self.source.try_iter() {
|
||||
match vde {
|
||||
VesselDataEvent::SpeedOverGround(s) => self.speed_over_ground = s,
|
||||
VesselDataEvent::SpeedThroughWater(s) => self.speed_through_water = s,
|
||||
VesselDataEvent::Location(lat, lon) => { self.latitude = lat; self.longitude = lon; },
|
||||
VesselDataEvent::CourseOverGround(c) => self.course_over_ground = c,
|
||||
VesselDataEvent::TrueCompassCourse(c) => self.true_compass_course = c,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user