platform: rewrite the platform-specific display creation traits into a new Board trait that also supports tasks

This commit is contained in:
Victoria Fischer 2024-11-30 16:06:17 +01:00
parent 57e660cbb6
commit 3af9ad408e
5 changed files with 276 additions and 80 deletions

View File

@ -54,6 +54,7 @@ smart-leds = { version = "0.4.0", optional = true }
embedded-graphics = { version = "0.8.1", optional = true, features = ["fixed_point", "defmt"] }
ansi_term = "0.12.1"
num = "0.4.3"
chrono = "0.4.38"
[build-dependencies]
embuild = "0.32.0"

View File

@ -9,31 +9,14 @@ mod animations;
mod mappings;
mod buffers;
use buffers::SurfacePool;
use esp_idf_svc::hal::prelude::Peripherals;
use crate::platform::DisplayInit;
use crate::render::Surfaces;
use crate::platform::Board;
use crate::task::{Task, Scheduler};
use crate::render::{Surfaces, Renderer};
use crate::geometry::Rectangle;
#[cfg(feature="embedded-graphics")]
#[cfg(feature="rmt")]
use crate::platform::embedded_graphics_lib::PonderjarTarget as DisplayType;
#[cfg(feature="smart-leds")]
#[cfg(feature="rmt")]
use crate::platform::smart_leds_lib::rmt::FastWs2812Esp32Rmt as DisplayType;
#[cfg(feature="smart-leds")]
#[cfg(feature="spi")]
use crate::platform::smart_leds_lib::spi::SPIDisplay as DisplayType;
#[cfg(feature="threads")]
use crate::buffers::SharedSurface as SurfaceType;
#[cfg(not(feature="threads"))]
use crate::buffers::SimpleSurface as SurfaceType;
use crate::render::Renderer;
use crate::platform::esp32::Esp32Board as BoardType;
fn main() {
// It is necessary to call this function once. Otherwise some patches to the runtime
@ -43,20 +26,27 @@ fn main() {
// Bind the log crate to the ESP Logging facilities
esp_idf_svc::log::EspLogger::initialize_default();
log::info!("Setting up display");
let peripherals = Peripherals::take().unwrap();
let mut board: BoardType = Board::new(peripherals);
log::info!("Board: {}", core::any::type_name_of_val(&board));
let display = DisplayType::new_display::<SurfaceType>();
let output = board.output();
log::info!("Output: {}", core::any::type_name_of_val(&output));
let mut surfaces: SurfacePool<SurfaceType> = SurfacePool::new();
let mut surfaces = board.surfaces();
log::info!("Surface implementation: {}", core::any::type_name_of_val(&output));
log::info!("Created new display type {}", core::any::type_name_of_val(&display));
log::info!("Creating runner");
let mut runner = task::Scheduler::new(vec![
log::info!("Creating animations");
let mut tasks: Vec<Box<dyn Task + 'static>> = vec![
Box::new(animations::IdleTask::new(&mut surfaces)),
Box::new(animations::TestPattern::new(surfaces.new_surface(&Rectangle::everything()).unwrap())),
Box::new(Renderer::new(display, surfaces)),
]);
];
tasks.append(&mut board.system_tasks());
tasks.push(Box::new(Renderer::new(output, surfaces)));
let mut runner = Scheduler::new(tasks);
log::info!("Runner ready: {:?}", runner);

238
src/platform/esp32.rs Normal file
View File

@ -0,0 +1,238 @@
use std::sync::Arc;
use std::fmt::Debug;
use std::sync::Mutex;
use chrono::DateTime;
use chrono::Utc;
use esp_idf_svc::eventloop::{EspSubscription, EspSystemEventLoop, System};
use esp_idf_svc::hal::modem::Modem;
use esp_idf_svc::hal::prelude::Peripherals;
use esp_idf_svc::netif::IpEvent;
use esp_idf_svc::nvs::{EspDefaultNvsPartition, EspNvsPartition, NvsDefault};
use esp_idf_svc::sntp::EspSntp;
use esp_idf_svc::sys::esp_efuse_mac_get_default;
use esp_idf_svc::wifi::{AuthMethod, ClientConfiguration, Configuration, EspWifi, WifiEvent};
use rgb::Rgb;
use super::Board;
use crate::task::Task;
use crate::buffers::{Pixbuf, SurfacePool};
use crate::mappings::StrideMapping;
use crate::platform::smart_leds_lib::StrideOutput;
use crate::platform::smart_leds_lib::rmt::FastWs2812Esp32Rmt;
use crate::time::Periodically;
#[cfg(feature="threads")]
use crate::buffers::SharedSurface as SurfaceType;
#[cfg(not(feature="threads"))]
use crate::buffers::SimpleSurface as SurfaceType;
pub struct Esp32Board<'a> {
output: Option<<Self as Board>::Output>,
surfaces: Option<SurfacePool<SurfaceType>>,
tasks: Option<Vec<Box<dyn Task>>>
}
impl<'a> Board for Esp32Board<'a> {
type Output = StrideOutput<[Rgb<u8>; 310], FastWs2812Esp32Rmt<'a>>;
type Surfaces = SurfacePool<SurfaceType>;
fn new(peripherals: Peripherals) -> Self {
let sys_loop = EspSystemEventLoop::take().unwrap();
let nvs = EspDefaultNvsPartition::take().unwrap();
let channel = peripherals.rmt.channel0;
let pins = peripherals.pins;
let mut chip_id: [u8; 8] = [0; 8];
unsafe {
esp_efuse_mac_get_default(&mut chip_id as *mut u8);
}
log::info!("Setting up for chip ID {:?}", chip_id);
let (pixmap, target) = match chip_id {
[72, 202, 67, 89, 145, 204, 0, 0] => {
(StrideMapping::new_panel(), FastWs2812Esp32Rmt::new(channel, pins.gpio5).unwrap())
},
[140, 170, 181, 131, 95, 116, 0, 0] => {
(StrideMapping::new_jar(), FastWs2812Esp32Rmt::new(channel, pins.gpio14).unwrap())
},
_ => {
(StrideMapping::new(), FastWs2812Esp32Rmt::new(channel, pins.gpio5).unwrap())
}
};
let tasks: Vec<Box<dyn Task>> = vec![
Box::new(WifiTask::new(peripherals.modem, sys_loop.clone(), &nvs)),
];
const POWER_VOLTS : u32 = 5;
const POWER_MA : u32 = 500;
const MAX_POWER_MW : u32 = POWER_VOLTS * POWER_MA;
let output = StrideOutput::new(
Pixbuf::new(),
pixmap,
target,
MAX_POWER_MW
);
Esp32Board {
surfaces: Some(SurfacePool::new()),
output: Some(output),
tasks: Some(tasks),
}
}
fn output(&mut self) -> Self::Output {
self.output.take().unwrap()
}
fn surfaces(&mut self) -> Self::Surfaces {
self.surfaces.take().unwrap()
}
fn system_tasks(&mut self) -> Vec<Box<dyn Task>> {
self.tasks.take().unwrap()
}
}
impl Debug for WifiTask {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("WifiTask").finish()
}
}
#[derive(Debug, PartialEq, Clone, Copy)]
enum WifiState {
Stopped,
Disconnected,
Connecting,
Connected
}
struct WifiTask {
wifi: EspWifi<'static>,
ntp: EspSntp<'static>,
connection_check: Periodically,
state: Arc<Mutex<WifiState>>,
last_state: WifiState,
sys_loop: EspSystemEventLoop,
wifi_sub: Option<EspSubscription<'static, System>>,
ip_sub: Option<EspSubscription<'static, System>>
}
impl WifiTask {
fn new(modem: Modem, sys_loop: EspSystemEventLoop, nvs: &EspNvsPartition<NvsDefault>) -> Self {
let mut wifi = EspWifi::new(
modem,
sys_loop.clone(),
Some(nvs.clone())
).unwrap();
let wifi_config = Configuration::Client(ClientConfiguration {
ssid: "The Frequency".try_into().unwrap(),
bssid: None,
auth_method: AuthMethod::WPA2Personal,
password: "thepasswordkenneth".try_into().unwrap(),
channel: None,
..Default::default()
});
wifi.set_configuration(&wifi_config).unwrap();
WifiTask {
wifi,
ntp: EspSntp::new_default().unwrap(),
connection_check: Periodically::new_every_n_seconds(1),
state: Arc::new(Mutex::new(WifiState::Stopped)),
last_state: WifiState::Stopped,
sys_loop,
wifi_sub: None,
ip_sub: None
}
}
fn connect(&mut self) {
log::info!("Connecting wifi");
self.wifi.start().unwrap();
self.wifi.connect().unwrap();
}
fn disconnect(&mut self) {
log::info!("Disconnecting wifi");
self.wifi.disconnect().unwrap();
self.wifi.stop().unwrap();
}
}
impl Task for WifiTask {
fn start(&mut self) {
log::info!("Starting wifi!");
log::info!("waiting for loop");
let wifi_state = self.state.clone();
self.wifi_sub = Some(self.sys_loop.subscribe::<WifiEvent, _>( move |evt| {
log::warn!("wifi event {:?}", evt);
let next_state = match evt {
WifiEvent::StaStopped => Some(WifiState::Disconnected),
WifiEvent::StaDisconnected => Some(WifiState::Disconnected),
WifiEvent::StaStarted => Some(WifiState::Connecting),
WifiEvent::StaConnected => Some(WifiState::Connecting),
_ => None
};
match next_state {
Some(s) => {
let mut state = wifi_state.lock().unwrap();
*state = s
},
None => ()
}
}).unwrap());
let ip_state = self.state.clone();
self.ip_sub = Some(self.sys_loop.subscribe::<IpEvent, _>(move |evt| {
log::warn!("ip event {:?}", evt);
match evt {
IpEvent::DhcpIpAssigned(_) => {
let mut state = ip_state.lock().unwrap();
*state = WifiState::Connected;
},
_ => ()
}
}).unwrap());
self.connect();
}
fn tick(&mut self ) {
if self.connection_check.tick() {
let cur_state = *self.state.lock().unwrap();
if self.last_state != cur_state {
match cur_state {
WifiState::Connected => log::info!("Wifi connected!"),
WifiState::Connecting => log::info!("Connecting!"),
WifiState::Stopped => log::info!("Stopped!"),
WifiState::Disconnected => log::info!("Disconnected!")
}
log::info!("online: {:?}", cur_state);
self.last_state = cur_state;
}
let now: DateTime<Utc> = std::time::SystemTime::now().into();
log::info!("Current time: {} status={:?}", now.format("%d/%m/%Y %T"), self.ntp.get_sync_status());
}
}
fn stop(&mut self) {
log::info!("Stopping wifi");
self.disconnect();
}
}

View File

@ -4,9 +4,19 @@ pub mod embedded_graphics_lib;
#[cfg(feature="smart-leds")]
pub mod smart_leds_lib;
use crate::render::{Surface, Output};
pub mod esp32;
pub trait DisplayInit {
use esp_idf_svc::hal::prelude::Peripherals;
use crate::render::{Output, Surfaces};
use crate::task::Task;
pub trait Board {
type Output: Output;
fn new_display<S: Surface>() -> Self::Output;
}
type Surfaces: Surfaces;
fn new(peripherals: Peripherals) -> Self;
fn output(&mut self) -> Self::Output;
fn surfaces(&mut self) -> Self::Surfaces;
fn system_tasks(&mut self) -> Vec<Box<dyn Task>>;
}

View File

@ -39,7 +39,7 @@ pub struct StrideOutput<P: Pixbuf, T: FastWrite> {
}
impl<P: Pixbuf, T: FastWrite> StrideOutput<P, T> {
fn new(pixbuf: P, stride_map: StrideMapping, target: T, max_mw: u32) -> Self {
pub fn new(pixbuf: P, stride_map: StrideMapping, target: T, max_mw: u32) -> Self {
assert!(stride_map.pixel_count <= pixbuf.pixel_count(), "map needs {} pixels, I only have PIXEL_NUM={}", stride_map.pixel_count, pixbuf.pixel_count());
StrideOutput {
pixbuf,
@ -85,18 +85,12 @@ pub trait FastWrite {
#[cfg(feature="rmt")]
pub mod rmt {
use esp_idf_svc::{hal::prelude::Peripherals, sys::esp_efuse_mac_get_default};
use ws2812_esp32_rmt_driver::driver::color::LedPixelColorGrb24;
use smart_leds::SmartLedsWrite;
use rgb::Rgb;
use ws2812_esp32_rmt_driver::LedPixelEsp32Rmt;
use crate::mappings::StrideMapping;
use crate::platform::smart_leds_lib::StrideOutput;
use crate::render::Surface;
use crate::platform::DisplayInit;
use super::{Pixbuf, FastWrite};
use super::FastWrite;
pub type FastWs2812Esp32Rmt<'a> = LedPixelEsp32Rmt<'a, Rgb<u8>, LedPixelColorGrb24>;
@ -111,43 +105,6 @@ pub mod rmt {
self.write_nocopy(iterator)
}
}
impl DisplayInit for FastWs2812Esp32Rmt<'_> {
type Output = StrideOutput<[Rgb<u8>; 310], Self>;
fn new_display<S: Surface>() -> Self::Output {
const POWER_VOLTS : u32 = 5;
const POWER_MA : u32 = 500;
const MAX_POWER_MW : u32 = POWER_VOLTS * POWER_MA;
let peripherals = Peripherals::take().unwrap();
let channel = peripherals.rmt.channel0;
let pins = peripherals.pins;
let mut chip_id: [u8; 8] = [0; 8];
unsafe {
esp_efuse_mac_get_default(&mut chip_id as *mut u8);
}
let (pixmap, target) = match chip_id {
[72, 202, 67, 89, 145, 204, 0, 0] => {
(StrideMapping::new_panel(), Self::new(channel, pins.gpio5).unwrap())
},
[140, 170, 181, 131, 95, 116, 0, 0] => {
(StrideMapping::new_jar(), Self::new(channel, pins.gpio14).unwrap())
},
_ => {
(StrideMapping::new(), Self::new(channel, pins.gpio5).unwrap())
}
};
StrideOutput::new(
Pixbuf::new(),
pixmap,
target,
MAX_POWER_MW
)
}
}
}
#[cfg(feature="spi")]