spat: Add screen
This commit is contained in:
parent
a7f6440e75
commit
b2ec98da2e
7 changed files with 463 additions and 30 deletions
78
Cargo.lock
generated
78
Cargo.lock
generated
|
|
@ -91,6 +91,12 @@ version = "1.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "az"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.13.1"
|
version = "0.13.1"
|
||||||
|
|
@ -604,6 +610,29 @@ dependencies = [
|
||||||
"nb 1.1.0",
|
"nb 1.1.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "embedded-graphics"
|
||||||
|
version = "0.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4e8da660bb0c829b34a56a965490597f82a55e767b91f9543be80ce8ccb416fe"
|
||||||
|
dependencies = [
|
||||||
|
"az",
|
||||||
|
"byteorder",
|
||||||
|
"embedded-graphics-core",
|
||||||
|
"float-cmp",
|
||||||
|
"micromath",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "embedded-graphics-core"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "95743bef3ff70fcba3930246c4e6872882bbea0dcc6da2ca860112e0cd4bd09f"
|
||||||
|
dependencies = [
|
||||||
|
"az",
|
||||||
|
"byteorder",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "embedded-hal"
|
name = "embedded-hal"
|
||||||
version = "0.2.7"
|
version = "0.2.7"
|
||||||
|
|
@ -629,6 +658,16 @@ dependencies = [
|
||||||
"embedded-hal 1.0.0",
|
"embedded-hal 1.0.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "embedded-hal-bus"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "513e0b3a8fb7d3013a8ae17a834283f170deaf7d0eeab0a7c1a36ad4dd356d22"
|
||||||
|
dependencies = [
|
||||||
|
"critical-section",
|
||||||
|
"embedded-hal 1.0.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "embedded-io"
|
name = "embedded-io"
|
||||||
version = "0.6.1"
|
version = "0.6.1"
|
||||||
|
|
@ -1104,6 +1143,8 @@ dependencies = [
|
||||||
"embassy-executor",
|
"embassy-executor",
|
||||||
"embassy-time",
|
"embassy-time",
|
||||||
"embassy_gps",
|
"embassy_gps",
|
||||||
|
"embedded-graphics",
|
||||||
|
"embedded-hal-bus",
|
||||||
"embedded-io 0.7.1",
|
"embedded-io 0.7.1",
|
||||||
"embedded-io-async 0.7.0",
|
"embedded-io-async 0.7.0",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
|
|
@ -1116,7 +1157,9 @@ dependencies = [
|
||||||
"esp-rtos",
|
"esp-rtos",
|
||||||
"geo-types",
|
"geo-types",
|
||||||
"log",
|
"log",
|
||||||
|
"mipidsi",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
|
"profont",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1197,6 +1240,15 @@ version = "0.1.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "float-cmp"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fnv"
|
name = "fnv"
|
||||||
version = "1.0.7"
|
version = "1.0.7"
|
||||||
|
|
@ -1537,12 +1589,29 @@ version = "2.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "micromath"
|
||||||
|
version = "2.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c3c8dda44ff03a2f238717214da50f65d5a53b45cd213a7370424ffdb6fae815"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "minimal-lexical"
|
name = "minimal-lexical"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mipidsi"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "790ebd28bd67addbccf41b1c0c188c26bb9f5bdcd91d4d6da9bd558e20d97a1d"
|
||||||
|
dependencies = [
|
||||||
|
"embedded-graphics-core",
|
||||||
|
"embedded-hal 1.0.0",
|
||||||
|
"heapless 0.8.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nb"
|
name = "nb"
|
||||||
version = "0.1.3"
|
version = "0.1.3"
|
||||||
|
|
@ -1771,6 +1840,15 @@ dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "profont"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "016681370a9dd6e7ddb4c1a959922fd59dc45e5ebaa5ff5b13090267898ced34"
|
||||||
|
dependencies = [
|
||||||
|
"embedded-graphics",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.45"
|
version = "1.0.45"
|
||||||
|
|
|
||||||
|
|
@ -40,9 +40,11 @@ cam = ["c-its-parser/cam"]
|
||||||
# echo DENM events on serial and BLE
|
# echo DENM events on serial and BLE
|
||||||
denm = ["c-its-parser/denm"]
|
denm = ["c-its-parser/denm"]
|
||||||
# evaluate SPAT information
|
# evaluate SPAT information
|
||||||
spat = ["gnss", "c-its-parser/spatem", "c-its-parser/mapem"]
|
spat = ["gnss", "screen", "c-its-parser/spatem", "c-its-parser/mapem"]
|
||||||
# enable some debug printing (without enabling debug logging of the ESP RTOS)
|
# enable some debug printing (without enabling debug logging of the ESP RTOS)
|
||||||
spat_debug = []
|
spat_debug = []
|
||||||
|
# enable ST7789 based display
|
||||||
|
screen = ["dep:mipidsi", "dep:embedded-hal-bus", "dep:profont"]
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
@ -81,6 +83,8 @@ esp-radio = { version = "0.18.0", optional = true, features = [
|
||||||
] }
|
] }
|
||||||
|
|
||||||
embassy_gps = { version = "0.1.0", optional = true, default-features = false, features = ["esp", "log-04"], git = "https://github.com/jbeyerstedt/embassy_gps.git", branch = "feat/rework-gpsfix" }
|
embassy_gps = { version = "0.1.0", optional = true, default-features = false, features = ["esp", "log-04"], git = "https://github.com/jbeyerstedt/embassy_gps.git", branch = "feat/rework-gpsfix" }
|
||||||
|
embedded-hal-bus = { version = "0.3.0", optional = true }
|
||||||
|
embedded-graphics = "0.8.2"
|
||||||
c-its-parser = { version = "2.2.2", default-features = false, features = [
|
c-its-parser = { version = "2.2.2", default-features = false, features = [
|
||||||
"time",
|
"time",
|
||||||
"geo",
|
"geo",
|
||||||
|
|
@ -90,7 +94,9 @@ c-its-parser = { version = "2.2.2", default-features = false, features = [
|
||||||
chrono = { version = "0.4.44", default-features = false, features = ["alloc"] }
|
chrono = { version = "0.4.44", default-features = false, features = ["alloc"] }
|
||||||
crossbeam-queue = { version = "0.3.12", default-features = false, features = ["alloc"] }
|
crossbeam-queue = { version = "0.3.12", default-features = false, features = ["alloc"] }
|
||||||
geo-types = { version = "0.7.19", default-features = false }
|
geo-types = { version = "0.7.19", default-features = false }
|
||||||
|
mipidsi = { version = "0.10.0", optional = true }
|
||||||
num-traits = { version = "0.2.19", default-features = false, features = ["libm"] }
|
num-traits = { version = "0.2.19", default-features = false, features = ["libm"] }
|
||||||
|
profont = { version = "0.7.0", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
assert_float_eq = "1.2.0"
|
assert_float_eq = "1.2.0"
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,8 @@ The code is currently tailored to:
|
||||||
- Seeed XIAO ESP32-C5 (8MB PSRAM)
|
- Seeed XIAO ESP32-C5 (8MB PSRAM)
|
||||||
- Seeed XIAO L67K (connected on GPIO12 (RX), GPIO11 (TX), GPIO1 (wakeup) and GPIO25 (reset))
|
- Seeed XIAO L67K (connected on GPIO12 (RX), GPIO11 (TX), GPIO1 (wakeup) and GPIO25 (reset))
|
||||||
only available with `gnss` feature (which is included in `spat` feature)
|
only available with `gnss` feature (which is included in `spat` feature)
|
||||||
- TODO: display (when using `spat` feature)
|
- Some ST7789 172*320px screen (connected on GPIO8 (SCL/ SCK), GPIO10 (SDA/ MOSI), GPIO7 (reset), GPIO23 (DC), GPIO24 (CS), TODO GPIO0 (BL))
|
||||||
|
only available with `screen` feature (which is included in `spat` feature)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,15 +5,13 @@ use alloc::rc::Rc;
|
||||||
#[cfg(feature = "spat")]
|
#[cfg(feature = "spat")]
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
#[cfg(all(target_arch = "riscv32", feature = "spat"))]
|
|
||||||
use esp_println::println;
|
|
||||||
#[cfg(feature = "spat")]
|
#[cfg(feature = "spat")]
|
||||||
use log::{debug, error};
|
use log::{debug, error};
|
||||||
#[cfg(feature = "gnss")]
|
#[cfg(feature = "gnss")]
|
||||||
use log::{info, warn};
|
use log::{info, warn};
|
||||||
|
|
||||||
#[cfg(feature = "spat")]
|
#[cfg(feature = "spat")]
|
||||||
use crate::geo_alg;
|
use crate::{applogic::v2x::map, geo_alg};
|
||||||
|
|
||||||
pub mod v2x;
|
pub mod v2x;
|
||||||
|
|
||||||
|
|
@ -63,6 +61,54 @@ pub struct State {
|
||||||
glosa_state: GlosaState,
|
glosa_state: GlosaState,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "spat")]
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct SignalGroupData {
|
||||||
|
pub phase: SignalPhase,
|
||||||
|
pub maneuver: map::Maneuvers,
|
||||||
|
pub min_end_sec: i16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "spat")]
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub enum SignalPhase {
|
||||||
|
#[default]
|
||||||
|
Unknown,
|
||||||
|
Red,
|
||||||
|
RedYellow,
|
||||||
|
Green,
|
||||||
|
Yellow,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::fmt::Display for SignalPhase {
|
||||||
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
|
match self {
|
||||||
|
SignalPhase::Unknown => write!(f, "__"),
|
||||||
|
SignalPhase::Red => write!(f, "RE"),
|
||||||
|
SignalPhase::RedYellow => write!(f, "RY"),
|
||||||
|
SignalPhase::Green => write!(f, "GN"),
|
||||||
|
SignalPhase::Yellow => write!(f, "YE"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<c_its_parser::standards::dsrc_2_2_1::etsi_its_dsrc::MovementPhaseState> for SignalPhase {
|
||||||
|
fn from(value: c_its_parser::standards::dsrc_2_2_1::etsi_its_dsrc::MovementPhaseState) -> Self {
|
||||||
|
use c_its_parser::standards::dsrc_2_2_1::etsi_its_dsrc::MovementPhaseState;
|
||||||
|
|
||||||
|
match value {
|
||||||
|
MovementPhaseState::stop_And_Remain => SignalPhase::Red,
|
||||||
|
MovementPhaseState::pre_Movement => SignalPhase::RedYellow,
|
||||||
|
MovementPhaseState::permissive_Movement_Allowed
|
||||||
|
| MovementPhaseState::protected_Movement_Allowed => SignalPhase::Green,
|
||||||
|
MovementPhaseState::permissive_clearance | MovementPhaseState::protected_clearance => {
|
||||||
|
SignalPhase::Yellow
|
||||||
|
}
|
||||||
|
_ => SignalPhase::Unknown,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "spat")]
|
#[cfg(feature = "spat")]
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
enum GlosaState {
|
enum GlosaState {
|
||||||
|
|
@ -172,12 +218,12 @@ impl State {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "spat")]
|
#[cfg(feature = "spat")]
|
||||||
pub fn run_glosa(&mut self) {
|
pub fn run_glosa(&mut self) -> Vec<SignalGroupData> {
|
||||||
let Some(own_heading) = self.heading_deg else {
|
let Some(own_heading) = self.heading_deg else {
|
||||||
return;
|
return alloc::vec![];
|
||||||
};
|
};
|
||||||
|
|
||||||
let state_update = match &self.glosa_state {
|
let (state_update, sig_grps) = match &self.glosa_state {
|
||||||
// search for upcoming intersection
|
// search for upcoming intersection
|
||||||
GlosaState::Idle => {
|
GlosaState::Idle => {
|
||||||
debug!(
|
debug!(
|
||||||
|
|
@ -193,9 +239,9 @@ impl State {
|
||||||
search_result.signal_groups
|
search_result.signal_groups
|
||||||
);
|
);
|
||||||
|
|
||||||
Some(GlosaState::Locked(search_result))
|
(Some(GlosaState::Locked(search_result)), None)
|
||||||
} else {
|
} else {
|
||||||
None
|
(None, None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GlosaState::Locked(info) => {
|
GlosaState::Locked(info) => {
|
||||||
|
|
@ -213,7 +259,7 @@ impl State {
|
||||||
&self.position,
|
&self.position,
|
||||||
) {
|
) {
|
||||||
info!("GLOSA: Intersection was left -> back to idle");
|
info!("GLOSA: Intersection was left -> back to idle");
|
||||||
Some(GlosaState::Idle)
|
(Some(GlosaState::Idle), None)
|
||||||
} else {
|
} else {
|
||||||
// get signal groups which are in our approach
|
// get signal groups which are in our approach
|
||||||
let mut approach_sig_grps = intersection
|
let mut approach_sig_grps = intersection
|
||||||
|
|
@ -231,22 +277,36 @@ impl State {
|
||||||
approach_sig_grps.sort_by_key(|v| v.maneuvers());
|
approach_sig_grps.sort_by_key(|v| v.maneuvers());
|
||||||
|
|
||||||
// display signal phases
|
// display signal phases
|
||||||
for sig in approach_sig_grps {
|
let sig_grps = approach_sig_grps
|
||||||
if let Some(phase) = sig.phase_to_str()
|
.iter()
|
||||||
&& let Some(manv) = sig.maneuvers()
|
.filter_map(|sig| {
|
||||||
{
|
if let Some(manv) = sig.maneuvers() {
|
||||||
use core::ops::Sub;
|
use core::ops::Sub;
|
||||||
|
|
||||||
|
use num_traits::Float;
|
||||||
|
|
||||||
let min_end = sig.min_end_time();
|
let min_end = sig.min_end_time();
|
||||||
let dur = min_end.sub(self.time.and_utc()).as_seconds_f32();
|
let dur = min_end.sub(self.time.and_utc()).as_seconds_f32();
|
||||||
println!(
|
|
||||||
"GLOSA: {manv} {phase} for {dur:.1} s (until {})",
|
|
||||||
sig.min_end_time()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None // keep state
|
let phase = sig
|
||||||
|
.phase()
|
||||||
|
.map(core::convert::Into::into)
|
||||||
|
.unwrap_or_default();
|
||||||
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
|
let min_end_sec = dur.round() as i16;
|
||||||
|
|
||||||
|
Some(SignalGroupData {
|
||||||
|
phase,
|
||||||
|
maneuver: manv,
|
||||||
|
min_end_sec,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
(None, Some(sig_grps)) // keep state, return signal groups
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -255,6 +315,8 @@ impl State {
|
||||||
if let Some(new) = state_update {
|
if let Some(new) = state_update {
|
||||||
self.glosa_state = new;
|
self.glosa_state = new;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sig_grps.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "spat")]
|
#[cfg(feature = "spat")]
|
||||||
|
|
|
||||||
|
|
@ -472,9 +472,9 @@ pub enum UsageType {
|
||||||
|
|
||||||
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
|
||||||
pub struct Maneuvers {
|
pub struct Maneuvers {
|
||||||
left_allowed: bool,
|
pub left_allowed: bool,
|
||||||
staight_allowed: bool,
|
pub staight_allowed: bool,
|
||||||
right_allowed: bool,
|
pub right_allowed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl core::fmt::Display for Maneuvers {
|
impl core::fmt::Display for Maneuvers {
|
||||||
|
|
|
||||||
102
src/main.rs
102
src/main.rs
|
|
@ -18,6 +18,10 @@ use embassy_gps::gps::l76k;
|
||||||
use esp_backtrace as _;
|
use esp_backtrace as _;
|
||||||
use esp_hal::clock::CpuClock;
|
use esp_hal::clock::CpuClock;
|
||||||
use esp_hal::timer::timg::TimerGroup;
|
use esp_hal::timer::timg::TimerGroup;
|
||||||
|
#[cfg(feature = "screen")]
|
||||||
|
use esp_hal::{delay, gpio, spi, time};
|
||||||
|
#[cfg(all(target_arch = "riscv32", feature = "spat"))]
|
||||||
|
use esp_println::println;
|
||||||
use esp_radio::wifi;
|
use esp_radio::wifi;
|
||||||
use log::{debug, error, info, warn};
|
use log::{debug, error, info, warn};
|
||||||
|
|
||||||
|
|
@ -28,6 +32,8 @@ mod applogic;
|
||||||
mod cache;
|
mod cache;
|
||||||
mod geo_alg;
|
mod geo_alg;
|
||||||
mod radio;
|
mod radio;
|
||||||
|
#[cfg(feature = "screen")]
|
||||||
|
mod screen;
|
||||||
|
|
||||||
const WIFI_CHANNEL: radio::Channel = 180;
|
const WIFI_CHANNEL: radio::Channel = 180;
|
||||||
|
|
||||||
|
|
@ -106,8 +112,53 @@ async fn main(spawner: Spawner) -> ! {
|
||||||
)
|
)
|
||||||
.expect("Failed to spawn GNSS task"),
|
.expect("Failed to spawn GNSS task"),
|
||||||
);
|
);
|
||||||
|
#[cfg(feature = "gnss")]
|
||||||
info!("GNSS task started, waiting for GNSS fix...");
|
info!("GNSS task started, waiting for GNSS fix...");
|
||||||
|
|
||||||
|
// ST7789 172*320px screen
|
||||||
|
// SCL: D8/ GPIO8 (SPI_SCK)
|
||||||
|
// SDA: D10/ GPIO10 (SPI_MOSI)
|
||||||
|
// RES: D3/ GPIO7
|
||||||
|
// DC: D4/ GPIO23
|
||||||
|
// CS: D5/ GPIO24
|
||||||
|
// BL: TODO (hard-wire to high? or D1/ GPIO0)
|
||||||
|
#[cfg(feature = "screen")]
|
||||||
|
let mut spi_buffer = [0_u8; 512];
|
||||||
|
#[cfg(feature = "screen")]
|
||||||
|
let mut display = {
|
||||||
|
let (spi, cs) = setup_st7789_spi(
|
||||||
|
peripherals.GPIO8,
|
||||||
|
peripherals.GPIO10,
|
||||||
|
peripherals.GPIO24,
|
||||||
|
peripherals.GPIO9,
|
||||||
|
peripherals.SPI2,
|
||||||
|
)
|
||||||
|
.expect("Failed to build SPI config");
|
||||||
|
let output_config = gpio::OutputConfig::default();
|
||||||
|
let dc_output = gpio::Output::new(peripherals.GPIO23, gpio::Level::Low, output_config);
|
||||||
|
let rst_output = gpio::Output::new(peripherals.GPIO7, gpio::Level::High, output_config);
|
||||||
|
let _ = gpio::Output::new(peripherals.GPIO0, gpio::Level::High, output_config); // backlight
|
||||||
|
|
||||||
|
let mut display_delay = delay::Delay::new();
|
||||||
|
|
||||||
|
let spi_device = embedded_hal_bus::spi::ExclusiveDevice::new_no_delay(spi, cs)
|
||||||
|
.expect("Failed to initialize SPI device");
|
||||||
|
|
||||||
|
mipidsi::Builder::new(
|
||||||
|
mipidsi::models::ST7789,
|
||||||
|
mipidsi::interface::SpiInterface::new(spi_device, dc_output, &mut spi_buffer),
|
||||||
|
)
|
||||||
|
.display_size(screen::HEIGHT + 34, screen::WIDTH) // somehow this display has 34 px of invisible space on the left (in native orientation)
|
||||||
|
.reset_pin(rst_output)
|
||||||
|
.invert_colors(mipidsi::options::ColorInversion::Inverted)
|
||||||
|
.orientation(mipidsi::options::Orientation::new().rotate(mipidsi::options::Rotation::Deg90))
|
||||||
|
.init(&mut display_delay)
|
||||||
|
.expect("Failed to initialize display")
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "screen")]
|
||||||
|
let _ = screen::draw_slash_screen(&mut display).inspect_err(log_screen_error);
|
||||||
|
|
||||||
let mut state = applogic::State::new();
|
let mut state = applogic::State::new();
|
||||||
loop {
|
loop {
|
||||||
embassy_time::Timer::after(embassy_time::Duration::from_millis(5)).await;
|
embassy_time::Timer::after(embassy_time::Duration::from_millis(5)).await;
|
||||||
|
|
@ -124,7 +175,18 @@ async fn main(spawner: Spawner) -> ! {
|
||||||
|
|
||||||
// run GLOSA algorithm
|
// run GLOSA algorithm
|
||||||
#[cfg(feature = "spat")]
|
#[cfg(feature = "spat")]
|
||||||
state.run_glosa();
|
let signal_groups = state.run_glosa();
|
||||||
|
|
||||||
|
for sig in &signal_groups {
|
||||||
|
println!(
|
||||||
|
"GLOSA: {} {} for {} s",
|
||||||
|
sig.maneuver, sig.phase, sig.min_end_sec
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "screen")]
|
||||||
|
let _ = screen::draw_glosa(&mut display, &signal_groups)
|
||||||
|
.inspect_err(log_screen_error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -284,3 +346,41 @@ async fn gnss_task(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Builds the SPI interface for the ST7789 display
|
||||||
|
///
|
||||||
|
/// Returns (spi master, chip-select) tuple or SPI config error
|
||||||
|
#[cfg(feature = "screen")]
|
||||||
|
fn setup_st7789_spi(
|
||||||
|
scl: esp_hal::peripherals::GPIO8<'static>,
|
||||||
|
sda: esp_hal::peripherals::GPIO10<'static>,
|
||||||
|
cs: esp_hal::peripherals::GPIO24<'static>,
|
||||||
|
miso: esp_hal::peripherals::GPIO9<'static>,
|
||||||
|
spi: esp_hal::peripherals::SPI2<'static>,
|
||||||
|
) -> Result<
|
||||||
|
(
|
||||||
|
spi::master::Spi<'static, esp_hal::Blocking>,
|
||||||
|
gpio::Output<'static>,
|
||||||
|
),
|
||||||
|
spi::master::ConfigError,
|
||||||
|
> {
|
||||||
|
let spi_config = spi::master::Config::default()
|
||||||
|
.with_frequency(time::Rate::from_mhz(10))
|
||||||
|
.with_mode(spi::Mode::_0);
|
||||||
|
let spi: spi::master::Spi<'_, esp_hal::Blocking> = spi::master::Spi::new(spi, spi_config)?
|
||||||
|
.with_sck(scl)
|
||||||
|
.with_mosi(sda)
|
||||||
|
.with_miso(miso);
|
||||||
|
|
||||||
|
let cs_output = gpio::Output::new(cs, gpio::Level::High, gpio::OutputConfig::default());
|
||||||
|
|
||||||
|
Ok((spi, cs_output))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "screen")]
|
||||||
|
fn log_screen_error<E>(error: &E)
|
||||||
|
where
|
||||||
|
E: core::fmt::Debug,
|
||||||
|
{
|
||||||
|
error!("TODO: {error:?}");
|
||||||
|
}
|
||||||
|
|
|
||||||
186
src/screen.rs
Normal file
186
src/screen.rs
Normal file
|
|
@ -0,0 +1,186 @@
|
||||||
|
//! Screen Output
|
||||||
|
|
||||||
|
use alloc::vec;
|
||||||
|
|
||||||
|
use embedded_graphics::prelude::*;
|
||||||
|
use embedded_graphics::{geometry, mono_font, pixelcolor, primitives, text};
|
||||||
|
|
||||||
|
use crate::applogic;
|
||||||
|
|
||||||
|
pub const WIDTH: u16 = 320;
|
||||||
|
pub const HEIGHT: u16 = 172;
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
pub fn clear<D>(target: &mut D) -> Result<(), D::Error>
|
||||||
|
where
|
||||||
|
D: DrawTarget<Color = pixelcolor::Rgb565>,
|
||||||
|
{
|
||||||
|
let style = primitives::PrimitiveStyleBuilder::new()
|
||||||
|
.fill_color(pixelcolor::Rgb565::BLACK)
|
||||||
|
.build();
|
||||||
|
primitives::Rectangle::new(
|
||||||
|
geometry::Point::new(0, 0),
|
||||||
|
geometry::Size::new(WIDTH.into(), HEIGHT.into()),
|
||||||
|
)
|
||||||
|
.into_styled(style)
|
||||||
|
.draw(target)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
pub fn test_img<C, D>(target: &mut D) -> Result<(), D::Error>
|
||||||
|
where
|
||||||
|
D: DrawTarget<Color = C>,
|
||||||
|
C: RgbColor,
|
||||||
|
{
|
||||||
|
mipidsi::TestImage::new().draw(target)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
pub fn draw_glosa<D>(target: &mut D, data: &[applogic::SignalGroupData]) -> Result<(), D::Error>
|
||||||
|
where
|
||||||
|
D: DrawTarget<Color = pixelcolor::Rgb565>,
|
||||||
|
{
|
||||||
|
let num_signals = data.len();
|
||||||
|
|
||||||
|
if num_signals == 0 {
|
||||||
|
return clear(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
|
||||||
|
let sig_width = (WIDTH as i32) / (num_signals as i32);
|
||||||
|
|
||||||
|
let text_style =
|
||||||
|
mono_font::MonoTextStyle::new(&profont::PROFONT_24_POINT, pixelcolor::Rgb565::WHITE);
|
||||||
|
let blank_style = primitives::PrimitiveStyleBuilder::new()
|
||||||
|
.fill_color(pixelcolor::Rgb565::CSS_GRAY)
|
||||||
|
.build();
|
||||||
|
let rd_style = primitives::PrimitiveStyleBuilder::new()
|
||||||
|
.fill_color(pixelcolor::Rgb565::CSS_RED)
|
||||||
|
.build();
|
||||||
|
let ye_style = primitives::PrimitiveStyleBuilder::new()
|
||||||
|
.fill_color(pixelcolor::Rgb565::CSS_YELLOW) // TODO: select nicer color
|
||||||
|
.build();
|
||||||
|
let gn_style = primitives::PrimitiveStyleBuilder::new()
|
||||||
|
.fill_color(pixelcolor::Rgb565::CSS_GREEN)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// clear maneuver and timing area
|
||||||
|
let style = primitives::PrimitiveStyleBuilder::new()
|
||||||
|
.fill_color(pixelcolor::Rgb565::BLACK)
|
||||||
|
.build();
|
||||||
|
primitives::Rectangle::new(
|
||||||
|
geometry::Point::new(0, 105),
|
||||||
|
geometry::Size::new(WIDTH.into(), 67),
|
||||||
|
)
|
||||||
|
.into_styled(style)
|
||||||
|
.draw(target)?;
|
||||||
|
|
||||||
|
for (idx, sig) in data.iter().enumerate() {
|
||||||
|
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
|
||||||
|
let offset = (idx as i32) * sig_width;
|
||||||
|
|
||||||
|
let circle_dia = 50;
|
||||||
|
let circle_margin = (sig_width - circle_dia) / 2;
|
||||||
|
let centerline = offset + (sig_width / 2);
|
||||||
|
|
||||||
|
// draw phase color
|
||||||
|
let style = match sig.phase {
|
||||||
|
applogic::SignalPhase::Unknown => blank_style,
|
||||||
|
applogic::SignalPhase::Red => rd_style,
|
||||||
|
applogic::SignalPhase::Green => gn_style,
|
||||||
|
applogic::SignalPhase::RedYellow | applogic::SignalPhase::Yellow => ye_style,
|
||||||
|
};
|
||||||
|
primitives::Circle::new(
|
||||||
|
geometry::Point::new(offset + circle_margin, 10),
|
||||||
|
circle_dia as u32,
|
||||||
|
)
|
||||||
|
.into_styled(style)
|
||||||
|
.draw(target)?;
|
||||||
|
|
||||||
|
// TODO: draw lane type?
|
||||||
|
|
||||||
|
// draw maneuver
|
||||||
|
text::Text::with_alignment(
|
||||||
|
&alloc::format!("{}", maneuver_to_str(&sig.maneuver)),
|
||||||
|
geometry::Point::new(centerline, 130),
|
||||||
|
text_style,
|
||||||
|
text::Alignment::Center,
|
||||||
|
)
|
||||||
|
.draw(target)?;
|
||||||
|
|
||||||
|
// draw timing indicator
|
||||||
|
text::Text::with_alignment(
|
||||||
|
&alloc::format!("{}s", sig.min_end_sec),
|
||||||
|
geometry::Point::new(centerline, 160),
|
||||||
|
text_style,
|
||||||
|
text::Alignment::Center,
|
||||||
|
)
|
||||||
|
.draw(target)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
pub fn draw_slash_screen<D>(target: &mut D) -> Result<(), D::Error>
|
||||||
|
where
|
||||||
|
D: DrawTarget<Color = pixelcolor::Rgb565>,
|
||||||
|
{
|
||||||
|
let text_style =
|
||||||
|
mono_font::MonoTextStyle::new(&profont::PROFONT_18_POINT, pixelcolor::Rgb565::WHITE);
|
||||||
|
let blank_style = primitives::PrimitiveStyleBuilder::new()
|
||||||
|
.fill_color(pixelcolor::Rgb565::CSS_GRAY)
|
||||||
|
.build();
|
||||||
|
let rd_style = primitives::PrimitiveStyleBuilder::new()
|
||||||
|
.fill_color(pixelcolor::Rgb565::CSS_RED)
|
||||||
|
.build();
|
||||||
|
let ye_style = primitives::PrimitiveStyleBuilder::new()
|
||||||
|
.fill_color(pixelcolor::Rgb565::CSS_YELLOW) // TODO: select nicer color
|
||||||
|
.build();
|
||||||
|
let gn_style = primitives::PrimitiveStyleBuilder::new()
|
||||||
|
.fill_color(pixelcolor::Rgb565::CSS_GREEN)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
clear(target)?;
|
||||||
|
|
||||||
|
let sig_width = (WIDTH as i32) / 3;
|
||||||
|
let circle_dia = 50;
|
||||||
|
let circle_margin = (sig_width - circle_dia) / 2;
|
||||||
|
|
||||||
|
for (idx, style) in vec![rd_style, ye_style, gn_style].iter().enumerate() {
|
||||||
|
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
|
||||||
|
let offset = (idx as i32) * sig_width;
|
||||||
|
|
||||||
|
primitives::Circle::new(
|
||||||
|
geometry::Point::new(offset + circle_margin, 10),
|
||||||
|
circle_dia as u32,
|
||||||
|
)
|
||||||
|
.into_styled(*style)
|
||||||
|
.draw(target)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw welcome text
|
||||||
|
let centerline = (WIDTH as i32) / 2;
|
||||||
|
text::Text::with_alignment(
|
||||||
|
"C-ITS Signal Phase Display\nWaiting for GNSS fix...",
|
||||||
|
geometry::Point::new(centerline, 130),
|
||||||
|
text_style,
|
||||||
|
text::Alignment::Center,
|
||||||
|
)
|
||||||
|
.draw(target)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn maneuver_to_str(value: &applogic::v2x::map::Maneuvers) -> alloc::string::String {
|
||||||
|
alloc::format!(
|
||||||
|
"{}{}{}",
|
||||||
|
if value.left_allowed { "<" } else { " " },
|
||||||
|
if value.staight_allowed { "^" } else { " " },
|
||||||
|
if value.right_allowed { ">" } else { " " },
|
||||||
|
)
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue