diff --git a/Cross.toml b/Cross.toml new file mode 100644 index 0000000..898c929 --- /dev/null +++ b/Cross.toml @@ -0,0 +1,8 @@ +[build] +default-target = "aarch64-unknown-linux-gnu" # use this target if none is explicitly provided +pre-build = [ # additional commands to run prior to building the package + "dpkg --add-architecture $CROSS_DEB_ARCH", + "apt update", + "apt --assume-yes install apt-utils:$CROSS_DEB_ARCH", + "apt --assume-yes install libsdl3-dev:$CROSS_DEB_ARCH" +] \ No newline at end of file diff --git a/src/gtfs/mod.rs b/src/gtfs/mod.rs index 29412c0..ccf0660 100644 --- a/src/gtfs/mod.rs +++ b/src/gtfs/mod.rs @@ -67,11 +67,14 @@ impl Gtfs { if trips.contains_key(&stop_time.trip_id) { let stop_timestamp = stop_time.departure_time.or(stop_time.arrival_time)?; debug!("Stop timestamp {} current timestamp {}", stop_timestamp, current_timestamp); + let trip= &self.trips.get(&stop_time.trip_id).unwrap(); if current_timestamp < stop_timestamp.into() { let arrival: Arrival = Arrival { route: self.routes.get(&self.trips.get(&stop_time.trip_id)?.route_id)?, stop: self.stops.get(&stop_time.stop_id)?, - departure_time: NaiveTime::from_num_seconds_from_midnight_opt(stop_timestamp, 0)? + stop_time: stop_time, + trip: &trip, + departure_time: NaiveTime::from_num_seconds_from_midnight_opt(stop_timestamp, 0).unwrap() }; arrivals.push(arrival); } diff --git a/src/gtfs/structs.rs b/src/gtfs/structs.rs index 0bcd116..f33ce5f 100644 --- a/src/gtfs/structs.rs +++ b/src/gtfs/structs.rs @@ -35,4 +35,6 @@ pub struct Arrival<'a> { pub departure_time: NaiveTime, pub route: &'a Route, pub stop: &'a Stop, + pub stop_time: &'a RawStopTime, + pub trip: &'a RawTrip, } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 05bbec1..ee7cc16 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,7 @@ use std::{collections::HashSet, ops::Add, os::unix::process::ExitStatusExt, proc use chrono::{DateTime, Duration, Local, NaiveTime}; use log::{Metadata, Record, debug, error, info}; use sdl3::event::Event; -use crate::{gtfs::structs::{Arrival, Gtfs}, renderer::structs::Screen}; +use crate::{gtfs::structs::{Arrival, Gtfs}, renderer::structs::{DisplayData, DisplayEntry, Screen}}; const SRC_FILE: &str = "/home/nahuel/Downloads/GTFS_Realtime.zip"; const NUM_ARRIVALS: usize = 4; @@ -15,7 +15,7 @@ struct RefreshDataEvent { } -fn refresh_schedule(gtfs: &Gtfs) -> Option>> { +fn refresh_schedule<'a>(gtfs: &'a Gtfs, screen : &mut Screen<'a>) -> Option>> { let current_timestamp = SystemTime::now(); let datetime: DateTime = current_timestamp.clone().into(); let mut next_arrivals: Vec> = gtfs.get_next_arrivals_for(&datetime)?; @@ -29,6 +29,26 @@ fn refresh_schedule(gtfs: &Gtfs) -> Option>> { } next_arrivals.sort(); + + // Create the DisplayData structure to render the information to screen + let current_time = Local::now().time(); + let mut display_data: DisplayData = DisplayData { + lines: Vec::::new(), + status: None + }; + + display_data.lines.extend(next_arrivals.iter().map(|arrival| -> DisplayEntry { + DisplayEntry { + destination: arrival.stop_time.stop_headsign.clone() + .or(arrival.trip.trip_headsign.clone() + .or(Option::Some(String::from("Unknown") + ))).unwrap(), + route: arrival.route.short_name.clone().or(arrival.route.long_name.clone()).unwrap(), + due_in: (arrival.departure_time - current_time).num_minutes().try_into().unwrap() + } + })); + + screen.update_information(&display_data); return Some(next_arrivals); } @@ -51,7 +71,11 @@ fn main() { fn flush(&self) {} } - log::set_logger(&MY_LOGGER); + let logger = log::set_logger(&MY_LOGGER); + if logger.is_err() { + print!("Error setting up the main logger:{:#?}", logger.err()); + process::exit(-1); + } log::set_max_level(log::LevelFilter::Trace); // Create preferences structures from config @@ -78,7 +102,7 @@ fn main() { // Init screen info!("Initializing screen..."); - let screen = Screen::init(&screen_prefs); + let mut screen = Screen::init(&screen_prefs); info!("Startup done."); // Register our custom event and obtain the event-related objects to interact with the event loop @@ -101,6 +125,8 @@ fn main() { }; }).unwrap(); + // Do an initial refresh + let _ = refresh_schedule(>fs, &mut screen); // Main event loop loop { @@ -117,9 +143,7 @@ fn main() { // Is the custom event a Refresh Data event? let refresh_data = event.as_user_event_type::(); if refresh_data.is_some() { - debug!("Received user event: {:#?}", refresh_data.unwrap()); - let _data: Option>> = refresh_schedule(>fs); - debug!("-------------------------------- Refresh done."); + let _data: Option>> = refresh_schedule(>fs, &mut screen); } } diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index e175998..8d1f15a 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -1,32 +1,96 @@ pub mod structs; +use std::cmp::min; use sdl3::{Sdl, pixels::Color, rect::Rect}; -use structs::{Prefs}; +use structs::{Prefs, DisplayData}; use crate::renderer::structs::Screen; +const LINE_COUNT: i32 = 6; +const COLOR_LCD_AMBER : Color = Color::RGB(0xf4, 0xcb, 0x60); +const COLOR_LCD_GREEN : Color = Color::RGB(0xb3, 0xff, 0x00); +const COLOR_LCD_RED : Color = Color::RGB(0xff, 0x3a, 0x4a); + +//const COLOR_BACKGROUND = pygame.Color(0, 0, 0) +const UPDATE_INTERVAL_SECONDS: u32 = 62; +const TEXT_SIZE: u32 = 160; // Size of the font in pixels + +// Offsets of each part within a line +const XOFFSET_ROUTE: u32 = 24; +const XOFFSET_DESTINATION: u32 = 300; +const XOFFSEET_TIME_LEFT: u32 = 1606; +const INTER_LINE_OVERLAP: u32 = 15; + + impl Screen<'_> { pub fn get_context(&self) -> &Sdl { return &self.context; } + fn color_for(&self, due_in: i32) -> Color { + if due_in > 15 { return COLOR_LCD_GREEN }; + if due_in > 10 { return COLOR_LCD_AMBER }; + return COLOR_LCD_RED; + } + + + pub fn update_information(&mut self, display_data: &DisplayData) { + self.do_clear(); + let num_arrivals: i32 = min(if display_data.status.is_some() {LINE_COUNT - 1} else {LINE_COUNT}, display_data.lines.len().try_into().unwrap()); + for line in 0..num_arrivals { + // Compose a line of text with all the information + let entry = display_data.lines.get(line as usize).unwrap(); + let line: u32 = line.try_into().unwrap(); + let due_in_mins = (entry.due_in / 60) as i32; + let arrival_color: Color = self.color_for(due_in_mins); + + self.color = COLOR_LCD_AMBER; + self.do_print_at(line, &entry.route, XOFFSET_ROUTE); + self.do_print_at(line, &entry.destination, XOFFSET_DESTINATION); + self.color = arrival_color; + self.do_print_at(line, &due_in_mins.to_string(), XOFFSEET_TIME_LEFT); + }; + + if display_data.status.is_some() { + self.do_print_at(5, display_data.status.as_ref().unwrap(), 0); + } + self.do_update(); + } + + pub fn print(&mut self, line: u32, text: &str) { - let rendered_text = self.font.render(text).solid(Color::RED).unwrap(); + self.do_print_at(line, text, 0); + self.do_update(); + } + + + fn do_print_at(&mut self, line: u32, text: &str, left: u32) -> u32 { + let rendered_text = self.font.render(text).solid(self.color).unwrap(); let texture_creator = self.canvas.texture_creator(); let texture = rendered_text.as_texture(&texture_creator).unwrap(); - let _ = self.canvas.copy(&texture, + let _= self.canvas.copy(&texture, Rect::new(0, 0, rendered_text.width(), rendered_text.height()), - Rect::new(0, (line * rendered_text.height()).try_into().unwrap(), rendered_text.width(), rendered_text.height())); - self.canvas.present(); + Rect::new(left.try_into().unwrap(), (line * (rendered_text.height() - INTER_LINE_OVERLAP)).try_into().unwrap(), rendered_text.width(), rendered_text.height())); + return left + rendered_text.width(); } pub fn clear(&mut self) { + self.do_clear(); + self.do_update(); + } + + + fn do_update (&mut self) { + self.canvas.present(); + } + + + fn do_clear(&mut self) { self.canvas.set_draw_color(Color::BLACK); self.canvas.clear(); - self.canvas.present(); } /// Initialize video, allocate buffers and load fonts @@ -50,6 +114,7 @@ impl Screen<'_> { let mut screen: Screen = Screen { canvas: Box::new(window.into_canvas()), font: Box::new(font), + color: COLOR_LCD_AMBER, context: Box::new(sdl_context), }; diff --git a/src/renderer/structs.rs b/src/renderer/structs.rs index 50cbd62..d15ebd8 100644 --- a/src/renderer/structs.rs +++ b/src/renderer/structs.rs @@ -1,9 +1,10 @@ -use sdl3::{Sdl, render::Canvas, ttf::Font, video::Window}; +use sdl3::{Sdl, pixels::Color, render::Canvas, ttf::Font, video::Window}; pub struct Screen<'a> { pub(crate) canvas: Box>, pub(crate) font: Box>, + pub(crate) color: Color, pub(crate) context: Box } @@ -12,3 +13,15 @@ pub struct Prefs { pub screen_width: u32, pub screen_height: u32, } + + +pub struct DisplayEntry { + pub route: String, + pub destination: String, + pub due_in: i32, +} + +pub struct DisplayData { + pub lines: Vec, + pub status: Option +}