use core::borrow::BorrowMut; use std::sync::Arc; use std::fmt::Debug; use std::sync::Mutex; use chrono::DateTime; use chrono::Timelike; 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::hal::task::thread::ThreadSpawnConfiguration; 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::smart_leds_lib::rmt::FastWs2812Esp32Rmt; use super::smart_leds_lib::StrideOutput; use super::Board; use crate::buffers::BufferedSurfacePool; use crate::buffers::Pixbuf; use crate::events::Event; use crate::events::EventBus; use crate::lib8::interpolate::lerp8by8; use crate::mappings::StrideMapping; use crate::task::FixedSizeScheduler; use crate::task::Task; use crate::time::Periodically; pub mod i2s { use esp_idf_svc::hal::i2s::*; use rgb::ComponentBytes; use rgb::Rgb; use crate::mappings::*; use crate::buffers::Pixbuf; use crate::render::Output; use crate::render::Sample; pub struct I2SOutput<'d> { driver: I2sDriver<'d, I2sTx>, pixbuf: [Rgb; 310], pixmap: StrideMapping, } impl<'d> I2SOutput<'d> { fn new(driver: I2sDriver<'d, I2sTx>) -> Self { I2SOutput { driver, pixbuf: Pixbuf::new(), pixmap: StrideMapping::new_jar() } } } impl<'d> Output for I2SOutput<'d> { fn on_event(&mut self, event: &crate::events::Event) { } fn blank(&mut self) { self.pixbuf.blank(); } fn commit(&mut self) { let bytes = self.pixbuf.as_bytes(); let mut written = self.driver.preload_data(bytes).unwrap(); self.driver.tx_enable().unwrap(); while written < bytes.len() { let next = &bytes[written..]; written += self.driver.write(next, 0).unwrap(); } self.driver.tx_disable().unwrap(); } } impl<'d> Sample for I2SOutput<'d> { type Pixel = Rgb; fn sample(&mut self, rect: &crate::geometry::Rectangle) -> impl crate::render::PixelView { StrideSampler::new(&mut self.pixbuf, self.pixmap.select(rect)) } } } pub struct Esp32Board { sys_loop: EspSystemEventLoop, modem: Option, surfaces: BufferedSurfacePool, output: Option<::Output> } impl Board for Esp32Board { type Output = StrideOutput<[Rgb; 310], FastWs2812Esp32Rmt<'static>>; type Surfaces = BufferedSurfacePool; type Scheduler = FixedSizeScheduler<2>; fn take() -> Self { // It is necessary to call this function once. Otherwise some patches to the runtime // implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71 esp_idf_svc::sys::link_patches(); // Bind the log crate to the ESP Logging facilities esp_idf_svc::log::EspLogger::initialize_default(); let peripherals = Peripherals::take().unwrap(); let sys_loop = EspSystemEventLoop::take().unwrap(); let mut chip_id: [u8; 8] = [0; 8]; unsafe { esp_efuse_mac_get_default(&mut chip_id as *mut u8); } log::info!("Setting up output for chip ID {:x?}", chip_id); const POWER_VOLTS : u32 = 5; const POWER_MA : u32 = 500; const MAX_POWER_MW : u32 = POWER_VOLTS * POWER_MA; let pins = peripherals.pins; ThreadSpawnConfiguration { pin_to_core: Some(esp_idf_svc::hal::cpu::Core::Core1), ..Default::default() }.set().unwrap(); // Wifi driver creates too many interrupts on core0, so we need to use RMT on core1. // But the implementation spawns a thread based on the core the driver was created in, // so we create the driver in another thread briefly. // Fun stuff. let output = match chip_id { // panel test board [72, 202, 67, 89, 145, 204, 0, 0] => { StrideOutput::new( Pixbuf::new(), StrideMapping::new_panel(), std::thread::spawn(move || { FastWs2812Esp32Rmt::new(peripherals.rmt.channel0, pins.gpio5).unwrap() }).join().unwrap(), MAX_POWER_MW ) }, [0x8C, 0xAA, 0xB5, 0x83, 0x5f, 0x74, 0x0, 0x0] => { //ponderjar StrideOutput::new( Pixbuf::new(), StrideMapping::new_jar(), std::thread::spawn(move || { FastWs2812Esp32Rmt::new(peripherals.rmt.channel0, pins.gpio14).unwrap() }).join().unwrap(), MAX_POWER_MW ) }, [0x4a, 0xca, 0x43, 0x59, 0x85, 0x58, 0x0, 0x0] => { // Albus the tree StrideOutput::new( Pixbuf::new(), StrideMapping::new_jar(), std::thread::spawn(move || { FastWs2812Esp32Rmt::new(peripherals.rmt.channel0, pins.gpio5).unwrap() }).join().unwrap(), MAX_POWER_MW ) }, [0x48, 0xca, 0x43, 0x59, 0x9d, 0x48, 0x0, 0x0] => { // kitchen cabinets StrideOutput::new( Pixbuf::new(), StrideMapping::new_fairylights(), std::thread::spawn(move || { FastWs2812Esp32Rmt::new(peripherals.rmt.channel0, pins.gpio5).unwrap() }).join().unwrap(), MAX_POWER_MW ) }, [0x48, 0xca, 0x43, 0x59, 0x9e, 0xdc, 0x0, 0x0] => { // front window StrideOutput::new( Pixbuf::new(), StrideMapping::new_fairylights(), std::thread::spawn(move || { FastWs2812Esp32Rmt::new(peripherals.rmt.channel0, pins.gpio5).unwrap() }).join().unwrap(), MAX_POWER_MW ) } _ => { StrideOutput::new( Pixbuf::new(), StrideMapping::new(), std::thread::spawn(move || { FastWs2812Esp32Rmt::new(peripherals.rmt.channel0, pins.gpio5).unwrap() }).join().unwrap(), MAX_POWER_MW ) } }; ThreadSpawnConfiguration { ..Default::default() }.set().unwrap(); Esp32Board { modem: Some(peripherals.modem), sys_loop: sys_loop.clone(), surfaces: BufferedSurfacePool::new(), output: Some(output) } } fn output(&mut self) -> Self::Output { self.output.take().unwrap() } fn surfaces(&mut self) -> Self::Surfaces { self.surfaces.clone() } fn system_tasks(&mut self) -> Self::Scheduler { let nvs = EspDefaultNvsPartition::take().unwrap(); FixedSizeScheduler::new([ Box::new(WifiTask::new(self.modem.take().unwrap(), self.sys_loop.clone(), &nvs)), Box::new(CircadianRhythm::new()), Box::new(self.surfaces.clone()) ]) } } #[derive(Debug, Clone, Copy)] struct ScheduleEntry { hour: u8, brightness: u8 } struct CircadianRhythm { time_check: Periodically, schedule: [ScheduleEntry;10] } impl CircadianRhythm { fn new() -> Self { CircadianRhythm { time_check: Periodically::new_every_n_seconds(5), schedule: [ ScheduleEntry { hour: 0, brightness: 0 }, ScheduleEntry { hour: 5, brightness: 0 }, ScheduleEntry { hour: 6, brightness: 10 }, ScheduleEntry { hour: 7, brightness: 20 }, ScheduleEntry { hour: 8, brightness: 80 }, ScheduleEntry { hour: 11, brightness: 120 }, ScheduleEntry { hour: 18, brightness: 200 }, ScheduleEntry { hour: 19, brightness: 255 }, ScheduleEntry { hour: 22, brightness: 120 }, ScheduleEntry { hour: 23, brightness: 5 } ] } } fn brightness_for_time(&self, hour: u8, minute: u8) -> u8 { let mut start = self.schedule.last().unwrap(); let mut end = self.schedule.first().unwrap(); for cur in self.schedule.iter() { if (cur.hour <= hour ) { start = cur; } else { end = cur; break; } } log::info!("hour={:?} minute={:?} start={:?} end={:?}", hour, minute, start, end); let mut adjusted_end = end.clone(); if start.hour > end.hour { adjusted_end.hour += 24; } let start_time = start.hour * 60; let end_time = end.hour * 60; let now_time = hour * 60 + minute; let duration = end_time - start_time; let cur_duration = now_time - start_time; let frac = map_range(cur_duration.into(), 0, duration.into(), 0, 255) as u8; lerp8by8(start.brightness, end.brightness, frac) } } fn map_range(x: u16, in_min: u16, in_max: u16, out_min: u16, out_max: u16) -> u16 { let run = in_max - in_min; if run == 0 { return 0; } let rise = out_max - out_min; let delta = x - in_min; return (delta * rise) / run + out_min; } impl Task for CircadianRhythm { fn tick(&mut self, event: &Event, bus: &mut EventBus) { if self.time_check.tick() || event.eq(&Event::ReadyToRock) { let now: DateTime = std::time::SystemTime::now().into(); let next_brightness = self.brightness_for_time(now.hour() as u8, now.minute() as u8); bus.push(Event::new_property_change("output.brightness", next_brightness)); } } } 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>, last_state: WifiState, sys_loop: EspSystemEventLoop, wifi_sub: Option>, ip_sub: Option> } impl WifiTask { fn new(modem: Modem, sys_loop: EspSystemEventLoop, nvs: &EspNvsPartition) -> Self { log::info!("Installing wifi driver"); 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!"); let wifi_state = self.state.clone(); self.wifi_sub = Some(self.sys_loop.subscribe::( 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::(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, event: &Event, bus: &mut EventBus) { 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; match cur_state { WifiState::Connected => bus.push(Event::new_input_event(crate::events::InputEvent::NetworkOnline)), _ => bus.push(Event::new_input_event(crate::events::InputEvent::NetworkOffline)) } } } } fn stop(&mut self) { log::info!("Stopping wifi"); self.disconnect(); } }