properties: rewrite properties (whoops)
This commit is contained in:
143
src/platform/esp32/board.rs
Normal file
143
src/platform/esp32/board.rs
Normal 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())
|
||||
])
|
||||
}
|
||||
}
|
3
src/platform/esp32/mod.rs
Normal file
3
src/platform/esp32/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod board;
|
||||
pub mod wifi;
|
||||
pub mod mqtt;
|
186
src/platform/esp32/mqtt.rs
Normal file
186
src/platform/esp32/mqtt.rs
Normal 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, System as SystemNS}, prop_id, properties::{PropertyID, Variant}, render::props::Output as OutputNS, scenes::props::Scenes as SceneNS, task::Task};
|
||||
use crate::platform::props::Board as BoardNS;
|
||||
use paste::paste;
|
||||
|
||||
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.set_property(props::MQTT::Online, 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!(SystemNS::NetworkOnline), 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.set_property(props::MQTT::Online, true);
|
||||
}
|
||||
},
|
||||
(prop_id!(OutputNS::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
104
src/platform/esp32/wifi.rs
Normal 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, System as SystemNS}, properties::Variant, 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.set_property(SystemNS::NetworkOnline,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.set_property(SystemNS::NetworkOnline, false);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user