This commit is contained in:
2024-12-15 15:18:12 +01:00
parent 63c913a8e6
commit 1d7848119e
20 changed files with 983 additions and 696 deletions

View File

@@ -1,453 +0,0 @@
use std::fmt::Debug;
use std::thread::JoinHandle;
use chrono::DateTime;
use chrono::Timelike;
use chrono::Utc;
use esp_idf_svc::eventloop::{EspSubscription, EspSystemEventLoop, System};
use esp_idf_svc::hal::gpio::Pins;
use esp_idf_svc::hal::modem::Modem;
use esp_idf_svc::hal::prelude::Peripherals;
use esp_idf_svc::hal::rmt::RMT;
use esp_idf_svc::hal::task::thread::ThreadSpawnConfiguration;
use esp_idf_svc::mqtt::client::EspMqttClient;
use esp_idf_svc::mqtt::client::MqttClientConfiguration;
use esp_idf_svc::netif::IpEvent;
use esp_idf_svc::nvs::{EspDefaultNvsPartition, EspNvsPartition, NvsDefault};
use esp_idf_svc::sntp::EspSntp;
use esp_idf_svc::sntp::SyncStatus;
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::events::Variant;
use crate::lib8::interpolate::lerp8by8;
use crate::mappings::StrideMapping;
use crate::task::FixedSizeScheduler;
use crate::task::Task;
use crate::time::Periodically;
pub struct Esp32Board {
sys_loop: EspSystemEventLoop,
modem: Option<Modem>,
pins: Option<Pins>,
rmt: Option<RMT>,
surfaces: BufferedSurfacePool,
}
impl Board for Esp32Board {
type Output = StrideOutput<[Rgb<u8>; 310], FastWs2812Esp32Rmt<'static>>;
type Surfaces = BufferedSurfacePool;
type Scheduler = FixedSizeScheduler<4>;
fn chip_id() -> u64 {
let mut chip_id: [u8; 8] = [0; 8];
unsafe {
esp_efuse_mac_get_default(&mut chip_id as *mut u8);
}
return u64::from_be_bytes(chip_id);
}
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();
Esp32Board {
modem: Some(peripherals.modem),
sys_loop: sys_loop,
surfaces: BufferedSurfacePool::new(),
pins: Some(peripherals.pins),
rmt: Some(peripherals.rmt)
}
}
fn output(&mut self) -> Self::Output {
log::info!("Setting up output for chip ID {:x?}", Self::chip_id());
const POWER_VOLTS : u32 = 5;
const POWER_MA : u32 = 500;
const MAX_POWER_MW : u32 = POWER_VOLTS * POWER_MA;
let pins = self.pins.take().unwrap();
let rmt = self.rmt.take().unwrap();
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 Self::chip_id().to_be_bytes() { // panel test board
[72, 202, 67, 89, 145, 204, 0, 0] => {
StrideOutput::new(
Pixbuf::new(),
StrideMapping::new_panel(),
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(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(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_albus(),
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(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(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(rmt.channel0, pins.gpio5).unwrap() }).join().unwrap(),
MAX_POWER_MW
)
},
[0xfc, 0xf5, 0xc4, 0x05, 0xb8, 0x30, 0x0, 0x0] => { // cyberplague
StrideOutput::new(
Pixbuf::new(),
StrideMapping::new_cyberplague(),
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(rmt.channel0, pins.gpio13).unwrap() }).join().unwrap(),
MAX_POWER_MW
)
},
_ => {
StrideOutput::new(
Pixbuf::new(),
StrideMapping::new(),
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(rmt.channel0, pins.gpio5).unwrap() }).join().unwrap(),
MAX_POWER_MW
)
}
};
ThreadSpawnConfiguration {
..Default::default()
}.set().unwrap();
output
}
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(MqttTask::new()),
Box::new(self.surfaces.clone())
])
}
}
#[derive(Debug, Clone)]
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(60),
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 update_brightness(&self, bus: &mut EventBus) {
let now: DateTime<Utc> = 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));
}
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 as u16).wrapping_mul(60);
let end_time = (end.hour as u16).wrapping_mul(60);
let now_time = (hour as u16).wrapping_mul(60).wrapping_add(minute as u16);
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 on_ready(&mut self, bus: &mut EventBus) {
self.update_brightness(bus);
}
fn on_property_change(&mut self, key: &'static str, value: &Variant, bus: &mut EventBus) {
match (key, value) {
("system.time.synchronized", Variant::Boolean(true)) => self.update_brightness(bus),
_ => ()
}
}
fn on_tick(&mut self, bus: &mut EventBus) {
if self.time_check.tick() {
self.update_brightness(bus);
}
}
}
struct MqttTask {
client: Option<EspMqttClient<'static>>,
conn_thread: Option<JoinHandle<()>>,
}
impl MqttTask {
fn new() -> Self {
MqttTask {
conn_thread: None,
client: None,
}
}
fn start_mqtt(&mut self, bus: &EventBus) {
log::info!("Starting MQTT");
let chip_id: u64 = bus.property("system.board.chip_id").unwrap().into();
let (client, mut conn) = EspMqttClient::new(
"mqtt://10.0.0.2:1883",
&MqttClientConfiguration {
client_id: Some(&format!("{:X}", chip_id)),
..Default::default()
}
).unwrap();
log::info!("Connected!");
self.conn_thread = Some(std::thread::Builder::new()
.stack_size(6000)
.spawn(move || {
conn.next().unwrap();
}).unwrap());
self.client = Some(client);
}
fn topic_prefix(bus: &EventBus) -> String {
let chip_id: u64 = bus.property("system.board.chip_id").unwrap().into();
format!("homeassistant-test/renderbug/{:X}", chip_id)
}
}
impl Task for MqttTask {
fn start(&mut self, bus: &mut EventBus) {
bus.push(Event::new_property_change("mqtt.online", false));
}
fn on_property_change(&mut self, key: &'static str, value: &Variant, bus: &mut EventBus) {
match (key, value) {
("system.network.online", Variant::Boolean(true)) => {
log::info!("Registering with MQTT");
self.start_mqtt(bus);
if let Some(ref mut client) = self.client {
client.enqueue(
Self::topic_prefix(bus).as_str(),
esp_idf_svc::mqtt::client::QoS::AtLeastOnce,
false,
"hello, world".as_bytes()
).unwrap();
log::info!("MQTT should be online!");
bus.push(Event::new_property_change("mqtt.online", true));
}
},
(name, value) => {
if let Some(ref mut client) = self.client {
let prefix = Self::topic_prefix(bus);
client.enqueue(
format!("{}/properties/{}", prefix, name).as_str(),
esp_idf_svc::mqtt::client::QoS::AtLeastOnce,
false,
format!("{}", value).as_bytes()
).unwrap();
}
}
}
}
}
impl Debug for WifiTask {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("WifiTask").finish()
}
}
struct WifiTask {
wifi: EspWifi<'static>,
ntp: EspSntp<'static>,
connection_check: Periodically,
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 {
log::info!("Installing wifi driver");
let wifi = EspWifi::new(
modem,
sys_loop.clone(),
Some(nvs.clone())
).unwrap();
WifiTask {
wifi,
ntp: EspSntp::new_default().unwrap(),
connection_check: Periodically::new_every_n_seconds(1),
sys_loop,
wifi_sub: None,
ip_sub: None,
}
}
fn connect(&mut self) {
log::info!("Connecting wifi");
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()
});
self.wifi.set_configuration(&wifi_config).unwrap();
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, bus: &mut EventBus) {
log::info!("Starting wifi!");
let mut wifi_bus = bus.clone();
self.wifi_sub = Some(self.sys_loop.subscribe::<WifiEvent, _>( move |evt| {
log::debug!("wifi event {:?}", evt);
wifi_bus.push(Event::new_property_change("system.network.online", false));
}).unwrap());
let mut ip_bus = bus.clone();
self.ip_sub = Some(self.sys_loop.subscribe::<IpEvent, _>(move |evt| {
log::debug!("ip event {:?}", evt);
match evt {
IpEvent::DhcpIpAssigned(addr) => {
ip_bus.push(Event::new_property_change("system.network.ip", addr.ip().to_string()));
ip_bus.push(Event::new_property_change("system.network.gateway", addr.gateway().to_string()));
ip_bus.push(Event::new_property_change("system.network.online", true));
},
_ => ()
}
}).unwrap());
self.connect();
}
fn on_tick(&mut self, bus: &mut EventBus) {
if self.connection_check.tick() {
if bus.property("system.network.online").unwrap() == Variant::Boolean(true) {
match self.ntp.get_sync_status() {
SyncStatus::Completed => bus.push(Event::new_property_change("system.time.synchronized", true)),
_ => bus.push(Event::new_property_change("system.time.synchronized", false))
}
}
}
}
fn stop(&mut self, bus: &mut EventBus) {
log::info!("Stopping wifi");
self.wifi_sub.take().unwrap();
self.ip_sub.take().unwrap();
self.disconnect();
bus.push(Event::new_property_change("system.network.online", false));
}
}

143
src/platform/esp32/board.rs Normal file
View File

@@ -0,0 +1,143 @@
use esp_idf_svc::{eventloop::EspSystemEventLoop, hal::{gpio::Pins, modem::Modem, prelude::Peripherals, rmt::RMT, task::thread::ThreadSpawnConfiguration}, nvs::EspDefaultNvsPartition, sys::esp_efuse_mac_get_default};
use rgb::Rgb;
use crate::{buffers::{BufferedSurfacePool, Pixbuf}, mappings::StrideMapping, platform::{smart_leds_lib::{rmt::FastWs2812Esp32Rmt, StrideOutput}, Board}, task::FixedSizeScheduler};
use super::{mqtt::MqttTask, wifi::WifiTask};
pub struct Esp32Board {
sys_loop: EspSystemEventLoop,
modem: Option<Modem>,
pins: Option<Pins>,
rmt: Option<RMT>,
surfaces: Option<BufferedSurfacePool>,
}
impl Board for Esp32Board {
type Output = StrideOutput<[Rgb<u8>; 310], FastWs2812Esp32Rmt<'static>>;
type Surfaces = BufferedSurfacePool;
type Scheduler = FixedSizeScheduler<2>;
fn chip_id() -> u64 {
let mut chip_id: [u8; 8] = [0; 8];
unsafe {
esp_efuse_mac_get_default(&mut chip_id as *mut u8);
}
return u64::from_be_bytes(chip_id);
}
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();
Esp32Board {
modem: Some(peripherals.modem),
sys_loop: sys_loop,
surfaces: Some(BufferedSurfacePool::new()),
pins: Some(peripherals.pins),
rmt: Some(peripherals.rmt)
}
}
fn output(&mut self) -> Self::Output {
log::info!("Setting up output for chip ID {:x?}", Self::chip_id());
const POWER_VOLTS : u32 = 5;
const POWER_MA : u32 = 500;
const MAX_POWER_MW : u32 = POWER_VOLTS * POWER_MA;
let pins = self.pins.take().unwrap();
let rmt = self.rmt.take().unwrap();
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 Self::chip_id().to_be_bytes() { // panel test board
[72, 202, 67, 89, 145, 204, 0, 0] => {
StrideOutput::new(
Pixbuf::new(),
StrideMapping::new_panel(),
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(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(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_albus(),
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(rmt.channel0, pins.gpio5).unwrap() }).join().unwrap(),
POWER_VOLTS * 2_400
)
},
[0x48, 0xca, 0x43, 0x59, 0x9d, 0x48, 0x0, 0x0] => { // kitchen cabinets
StrideOutput::new(
Pixbuf::new(),
StrideMapping::new_fairylights(),
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(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(rmt.channel0, pins.gpio5).unwrap() }).join().unwrap(),
MAX_POWER_MW
)
},
[0xfc, 0xf5, 0xc4, 0x05, 0xb8, 0x30, 0x0, 0x0] => { // cyberplague
StrideOutput::new(
Pixbuf::new(),
StrideMapping::new_cyberplague(),
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(rmt.channel0, pins.gpio13).unwrap() }).join().unwrap(),
MAX_POWER_MW
)
},
_ => {
StrideOutput::new(
Pixbuf::new(),
StrideMapping::new(),
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(rmt.channel0, pins.gpio5).unwrap() }).join().unwrap(),
MAX_POWER_MW
)
}
};
ThreadSpawnConfiguration {
..Default::default()
}.set().unwrap();
output
}
fn surfaces(&mut self) -> Self::Surfaces {
self.surfaces.take().unwrap()
}
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(MqttTask::new())
])
}
}

View File

@@ -0,0 +1,3 @@
pub mod board;
pub mod wifi;
pub mod mqtt;

186
src/platform/esp32/mqtt.rs Normal file
View File

@@ -0,0 +1,186 @@
use std::{collections::LinkedList, thread::JoinHandle};
use esp_idf_svc::mqtt::client::{EspMqttClient, MqttClientConfiguration};
use serde_json::{json, Value};
use crate::{events::{Event, EventBus, Namespace, Property, System as SystemNS, Variant}, prop_id, render::props::Output as OutputNS, scenes::props::Scenes as SceneNS, task::Task, PropertyID};
use crate::platform::props::Board as BoardNS;
struct HADevice {
prefix: String,
unique_id: String
}
impl HADevice {
fn new(component: &str, chip_id: u64, name: &str) -> Self {
HADevice {
// eg: homeassistant/sensor/0BADCOFFEE/fps
unique_id: format!("{:X}-{}", chip_id, name),
prefix: format!("homeassistant/{}/renderbug-rs/{:X}-{}", component, chip_id, name)
}
}
fn topic(&self, name: &str) -> String {
format!("{}/{}", self.prefix, name)
}
fn registration(&self) -> Value {
json!({
"~": self.prefix,
"stat_t": "~/state",
"unique_id": self.unique_id,
"dev": {
"name": "Renderbug-rs ESP32",
"mdl": "Renderbug-rs ESP32",
"sw": "",
"mf": "Phong Robotics",
"ids": [self.unique_id]
}
})
}
}
struct HAScene {
prefix: String,
unique_id: String
}
impl HAScene {
fn new(chip_id: u64, name: &str) -> Self {
HAScene {
// eg: homeassistant/sensor/0BADCOFFEE/fps
unique_id: format!("{:X}-{}", chip_id, name),
prefix: format!("homeassistant/scene/renderbug-rs/{:X}-{}", chip_id, name)
}
}
fn topic(&self, name: &str) -> String {
format!("{}/{}", self.prefix, name)
}
fn registration(&self) -> Value {
json!({
"~": self.prefix,
"stat_t": "~/state",
"cmd_t": "~/command",
"unique_id": self.unique_id,
"payload_on": "on",
"dev": {
"name": "Renderbug-rs ESP32",
"mdl": "Renderbug-rs ESP32",
"sw": "",
"mf": "Phong Robotics",
"ids": [self.unique_id]
}
})
}
}
pub struct MqttTask {
client: Option<EspMqttClient<'static>>,
conn_thread: Option<JoinHandle<()>>,
fps_sensor: Option<HADevice>
}
impl MqttTask {
pub fn new() -> Self {
MqttTask {
conn_thread: None,
client: None,
fps_sensor: None
}
}
fn start_mqtt(&mut self, bus: &EventBus) {
log::info!("Starting MQTT");
let chip_id: u64 = bus.properties().get(BoardNS::ChipID).unwrap().into();
self.fps_sensor = Some(HADevice::new("sensor", chip_id, "output-fps"));
let (client, mut conn) = EspMqttClient::new(
"mqtt://10.0.0.2:1883",
&MqttClientConfiguration {
client_id: Some(&format!("{:X}", chip_id)),
..Default::default()
}
).unwrap();
log::info!("Connected!");
self.conn_thread = Some(std::thread::Builder::new()
.stack_size(6000)
.spawn(move || {
conn.next().unwrap();
}).unwrap());
self.client = Some(client);
}
}
impl Task for MqttTask {
fn start(&mut self, bus: &mut EventBus) {
bus.push(props::MQTT::Online.new_property_change(false));
let chip_id = bus.properties().get(BoardNS::ChipID).unwrap().into();
self.fps_sensor = Some(HADevice::new("sensor", chip_id, "fps"));
}
fn on_property_change(&mut self, key: PropertyID, value: &Variant, bus: &mut EventBus) {
match (key, value) {
(prop_id!(System, Online), Variant::Boolean(true)) => {
log::info!("Registering with MQTT");
let chip_id = bus.properties().get(BoardNS::ChipID).unwrap().into();
self.start_mqtt(bus);
if let Some(ref mut client) = self.client {
if let Some(ref sensor) = self.fps_sensor {
client.enqueue(
&sensor.topic("config"),
esp_idf_svc::mqtt::client::QoS::AtLeastOnce,
false,
sensor.registration().to_string().as_bytes()
).unwrap();
}
let scenes: Vec<Variant> = bus.properties().get(SceneNS::All).unwrap().into();
for scene in scenes.iter() {
let scene_name: String = scene.clone().into();
let scene_device = HAScene::new(chip_id, scene_name.as_str());
client.enqueue(
&scene_device.topic("config"),
esp_idf_svc::mqtt::client::QoS::AtLeastOnce,
false,
scene_device.registration().to_string().as_bytes()
).unwrap();
}
log::info!("MQTT should be online!");
bus.push(props::MQTT::Online.new_property_change(true));
}
},
(prop_id!(Output, FPS), Variant::UInt(fps)) => {
if let Some(ref mut client) = self.client {
if let Some(ref sensor) = self.fps_sensor {
client.enqueue(
&sensor.topic("state"),
esp_idf_svc::mqtt::client::QoS::AtLeastOnce,
false,
json!(fps).to_string().as_bytes()
).unwrap();
}
}
},
_ => ()
}
}
}
pub mod props {
use crate::property_namespace;
property_namespace!(
MQTT,
Online => true
);
}

104
src/platform/esp32/wifi.rs Normal file
View File

@@ -0,0 +1,104 @@
use esp_idf_svc::{eventloop::{EspSubscription, EspSystemEventLoop, System}, hal::modem::Modem, netif::IpEvent, nvs::{EspNvsPartition, NvsDefault}, sntp::{EspSntp, SyncStatus}, wifi::{AuthMethod, ClientConfiguration, Configuration, EspWifi, WifiEvent}};
use crate::{events::{EventBus, Variant, System as SystemNS}, task::Task, time::Periodically};
use std::fmt::Debug;
pub struct WifiTask {
wifi: EspWifi<'static>,
ntp: EspSntp<'static>,
connection_check: Periodically,
sys_loop: EspSystemEventLoop,
wifi_sub: Option<EspSubscription<'static, System>>,
ip_sub: Option<EspSubscription<'static, System>>,
}
impl Debug for WifiTask {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("WifiTask").finish()
}
}
impl WifiTask {
pub fn new(modem: Modem, sys_loop: EspSystemEventLoop, nvs: &EspNvsPartition<NvsDefault>) -> Self {
log::info!("Installing wifi driver");
let wifi = EspWifi::new(
modem,
sys_loop.clone(),
Some(nvs.clone())
).unwrap();
WifiTask {
wifi,
ntp: EspSntp::new_default().unwrap(),
connection_check: Periodically::new_every_n_seconds(1),
sys_loop,
wifi_sub: None,
ip_sub: None,
}
}
fn connect(&mut self) {
log::info!("Connecting wifi");
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()
});
self.wifi.set_configuration(&wifi_config).unwrap();
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, bus: &mut EventBus) {
log::info!("Starting wifi!");
let mut wifi_bus = bus.clone();
self.wifi_sub = Some(self.sys_loop.subscribe::<WifiEvent, _>( move |evt| {
log::debug!("wifi event {:?}", evt);
wifi_bus.push(SystemNS::NetworkOnline.new_property_change(false));
}).unwrap());
let mut ip_bus = bus.clone();
self.ip_sub = Some(self.sys_loop.subscribe::<IpEvent, _>(move |evt| {
log::debug!("ip event {:?}", evt);
match evt {
IpEvent::DhcpIpAssigned(addr) => {
ip_bus.set_property(SystemNS::IP, addr.ip().to_string());
ip_bus.set_property(SystemNS::Gateway, addr.gateway().to_string());
ip_bus.set_property(SystemNS::NetworkOnline, true);
},
_ => ()
}
}).unwrap());
self.connect();
}
fn on_tick(&mut self, bus: &mut EventBus) {
if self.connection_check.tick() {
if bus.properties().get(SystemNS::NetworkOnline).unwrap() == Variant::Boolean(true) {
match self.ntp.get_sync_status() {
SyncStatus::Completed => bus.set_property(SystemNS::TimeSync, true),
_ => bus.set_property(SystemNS::TimeSync, false)
}
}
}
}
fn stop(&mut self, bus: &mut EventBus) {
log::info!("Stopping wifi");
self.wifi_sub.take().unwrap();
self.ip_sub.take().unwrap();
self.disconnect();
bus.push(SystemNS::NetworkOnline.new_property_change(false));
}
}

View File

@@ -6,7 +6,7 @@ pub mod smart_leds_lib;
pub mod esp32;
pub type DefaultBoard = esp32::Esp32Board;
pub type DefaultBoard = esp32::board::Esp32Board;
use crate::render::{Output, Surfaces};
use crate::task::Scheduler;
@@ -21,4 +21,13 @@ pub trait Board {
fn surfaces(&mut self) -> Self::Surfaces;
fn system_tasks(&mut self) -> Self::Scheduler;
fn chip_id() -> u64;
}
pub mod props {
use crate::property_namespace;
property_namespace!(
Board,
ChipID => 0_u64
);
}

View File

@@ -2,9 +2,9 @@ use smart_leds_trait::SmartLedsWrite;
use crate::buffers::Pixbuf;
use crate::events::Variant;
use crate::render::{HardwarePixel, Output, PixelView, Sample};
use crate::render::{HardwarePixel, Output, PixelView, Sample, props::Output as OutputNS};
use crate::power::brightness_for_mw;
use crate::geometry::*;
use crate::{geometry::*, prop_id};
use crate::mappings::*;
use core::fmt::Debug;
@@ -57,7 +57,7 @@ impl<P: Pixbuf<Pixel=T::Color>, T: FastWrite> Output for StrideOutput<P, T> {
fn on_event(&mut self, event: &crate::events::Event) {
match event {
crate::events::Event::PropertyChange("output.brightness", Variant::Byte(new_brightness)) => self.brightness = *new_brightness,
crate::events::Event::PropertyChange(prop_id!(Output, Brightness), Variant::Byte(new_brightness)) => self.brightness = *new_brightness,
_ => ()
}
}