Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
6de1704da2 |
@ -47,6 +47,7 @@ num = "0.4.3"
|
||||
chrono = "0.4.38"
|
||||
serde_json = "1.0.133"
|
||||
paste = "1.0.15"
|
||||
serde = { version = "1.0.216", features = ["derive"] }
|
||||
|
||||
[build-dependencies]
|
||||
embuild = "0.32.0"
|
||||
|
@ -6,7 +6,7 @@ use crate::lib8::Hsv;
|
||||
use crate::events::EventBus;
|
||||
use crate::geometry::*;
|
||||
use crate::render::{Shader, Surface, Surfaces};
|
||||
use crate::task::Task;
|
||||
use crate::task::{Environment, Task};
|
||||
use crate::lib8::{trig::{sin8, cos8}, noise::inoise8, IntoRgb8};
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -68,7 +68,7 @@ impl<T: Surface> IdleTask<T> {
|
||||
impl<T: Surface> Task for IdleTask<T> {
|
||||
fn name(&self) -> &'static str { "Idle" }
|
||||
|
||||
fn start(&mut self, _bus: &mut EventBus) {
|
||||
fn start(&mut self, _env: &mut Environment) {
|
||||
self.solid.set_shader(SolidShader {});
|
||||
self.surface.set_shader(ThinkingShader { });
|
||||
self.shimmer.set_shader(ShimmerShader { });
|
||||
@ -78,7 +78,7 @@ impl<T: Surface> Task for IdleTask<T> {
|
||||
self.shimmer.set_opacity(64);
|
||||
}
|
||||
|
||||
fn stop(&mut self, _bus: &mut EventBus) {
|
||||
fn stop(&mut self, _env: &mut Environment) {
|
||||
self.solid.clear_shader();
|
||||
self.surface.clear_shader();
|
||||
self.shimmer.clear_shader();
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::lib8::Hsv;
|
||||
use crate::{lib8::Hsv, task::Environment};
|
||||
use rgb::RGB8;
|
||||
|
||||
use crate::{events::{Event, EventBus}, lib8::{interpolate::scale8, trig::{cos8, sin8}, IntoRgb8}, render::{Shader, Surface}, task::Task, time::Periodically};
|
||||
use crate::{events::EventBus, lib8::{interpolate::scale8, trig::{cos8, sin8}, IntoRgb8}, render::{Shader, Surface}, task::Task, time::Periodically};
|
||||
|
||||
use super::{Coordinates, Rectangle, VirtualCoordinates};
|
||||
|
||||
@ -93,11 +93,11 @@ impl<T: Surface> TestPattern<T> {
|
||||
impl<T: Surface> Task for TestPattern<T> {
|
||||
fn name(&self) -> &'static str { "TestPattern" }
|
||||
|
||||
fn start(&mut self, _bus: &mut EventBus) {
|
||||
fn start(&mut self, _env: &mut Environment) {
|
||||
self.surface.set_shader(self.pattern);
|
||||
}
|
||||
|
||||
fn on_tick(&mut self, bus: &mut EventBus) {
|
||||
fn on_tick(&mut self, _env: &mut Environment) {
|
||||
self.updater.run(|| {
|
||||
self.pattern = self.pattern.next();
|
||||
log::info!("Test pattern: {:?}", self.pattern);
|
||||
@ -122,7 +122,7 @@ impl<T: Surface> Task for TestPattern<T> {
|
||||
});
|
||||
}
|
||||
|
||||
fn stop(&mut self, _bus: &mut EventBus) {
|
||||
fn stop(&mut self, _env: &mut Environment) {
|
||||
self.surface.clear_shader();
|
||||
}
|
||||
}
|
@ -46,6 +46,15 @@ impl Event {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Evented {
|
||||
fn on_event(&mut self, event: &Event, bus: &mut EventBus);
|
||||
}
|
||||
|
||||
pub trait EventSource {
|
||||
fn next(&mut self) -> Event;
|
||||
fn push(&mut self, event: Event);
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct EventBus {
|
||||
pending: Arc<Mutex<VecDeque<Event>>>,
|
||||
@ -59,8 +68,10 @@ impl EventBus {
|
||||
props: Arc::new(Mutex::new(Properties::new()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next(&mut self) -> Event {
|
||||
impl EventSource for EventBus {
|
||||
fn next(&mut self) -> Event {
|
||||
let next = self.pending.lock().unwrap().pop_front().unwrap_or(Event::new_tick());
|
||||
|
||||
match next {
|
||||
@ -76,17 +87,9 @@ impl EventBus {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push(&mut self, event: Event) {
|
||||
fn push(&mut self, event: Event) {
|
||||
self.pending.lock().unwrap().push_back(event);
|
||||
}
|
||||
|
||||
pub fn properties(&self) -> std::sync::MutexGuard<'_, Properties> {
|
||||
self.props.lock().unwrap()
|
||||
}
|
||||
|
||||
pub fn set_property<K: Into<PropertyID>, V: Into<Variant>>(&mut self, key: K, value: V) {
|
||||
self.push(Event::new_property_change(key.into(), value.into()));
|
||||
}
|
||||
}
|
||||
|
||||
property_namespace!(
|
||||
@ -94,5 +97,6 @@ property_namespace!(
|
||||
NetworkOnline => false,
|
||||
IP => "",
|
||||
Gateway => "",
|
||||
TimeSync => false
|
||||
TimeSync => false,
|
||||
Idle => false
|
||||
);
|
@ -1,8 +1,7 @@
|
||||
use chrono::{DateTime, Timelike, Utc};
|
||||
|
||||
use crate::{events::{Event, EventBus}, lib8::interpolate::lerp8by8, prop_id, properties::{PropertyID, Variant}, render::props::Output as OutputNS, task::Task, time::Periodically};
|
||||
use crate::{events::EventBus, lib8::interpolate::lerp8by8, properties::{PropertyID, Variant}, render::props::Output as OutputNS, task::{Environment, Task}, time::Periodically};
|
||||
use crate::events::System as SystemNS;
|
||||
use paste::paste;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct ScheduleEntry {
|
||||
@ -34,10 +33,10 @@ impl CircadianRhythm {
|
||||
}
|
||||
}
|
||||
|
||||
fn update_brightness(&self, bus: &mut EventBus) {
|
||||
fn update_brightness(&self, env: &mut Environment) {
|
||||
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.set_property(OutputNS::Brightness, next_brightness);
|
||||
env.set_property(OutputNS::Brightness, next_brightness);
|
||||
}
|
||||
|
||||
fn brightness_for_time(&self, hour: u8, minute: u8) -> u8 {
|
||||
@ -84,20 +83,20 @@ fn map_range(x: u16, in_min: u16, in_max: u16, out_min: u16, out_max: u16) -> u1
|
||||
|
||||
|
||||
impl Task for CircadianRhythm {
|
||||
fn on_ready(&mut self, bus: &mut EventBus) {
|
||||
self.update_brightness(bus);
|
||||
fn on_ready(&mut self, env: &mut Environment) {
|
||||
self.update_brightness(env);
|
||||
}
|
||||
|
||||
fn on_property_change(&mut self, key: PropertyID, value: &Variant, bus: &mut EventBus) {
|
||||
fn on_property_change(&mut self, key: PropertyID, value: &Variant, env: &mut Environment) {
|
||||
match (key, value) {
|
||||
(prop_id!(SystemNS::TimeSync), Variant::Boolean(true)) => self.update_brightness(bus),
|
||||
(SystemNS::TimeSync, Variant::Boolean(true)) => self.update_brightness(env),
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
|
||||
fn on_tick(&mut self, bus: &mut EventBus) {
|
||||
fn on_tick(&mut self, env: &mut Environment) {
|
||||
if self.time_check.tick() {
|
||||
self.update_brightness(bus);
|
||||
self.update_brightness(env);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
38
src/inputs/idle.rs
Normal file
38
src/inputs/idle.rs
Normal file
@ -0,0 +1,38 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::task::Environment;
|
||||
use crate::{task::Task, time::Periodically};
|
||||
use crate::events::System;
|
||||
|
||||
pub struct IdleTimer {
|
||||
timeout_start: Instant,
|
||||
timeout_duration: Duration,
|
||||
timeout_check: Periodically
|
||||
}
|
||||
|
||||
impl IdleTimer {
|
||||
pub fn new() -> Self {
|
||||
IdleTimer {
|
||||
timeout_start: Instant::now(),
|
||||
timeout_duration: Duration::from_secs(60),
|
||||
timeout_check: Periodically::new_every_n_seconds(5)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Task for IdleTimer {
|
||||
fn start(&mut self, env: &mut Environment) {
|
||||
env.set_property(System::Idle, true);
|
||||
}
|
||||
|
||||
fn on_tick(&mut self, env: &mut Environment) {
|
||||
if self.timeout_check.tick() {
|
||||
let is_idle: bool = env.get_property(System::Idle).unwrap();
|
||||
if !is_idle && self.timeout_start.elapsed() >= self.timeout_duration {
|
||||
env.set_property(System::Idle, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1 +1,2 @@
|
||||
pub mod circadian;
|
||||
pub mod circadian;
|
||||
pub mod idle;
|
24
src/main.rs
24
src/main.rs
@ -15,13 +15,14 @@ mod properties;
|
||||
|
||||
use events::*;
|
||||
use inputs::circadian::CircadianRhythm;
|
||||
use inputs::idle::IdleTimer;
|
||||
use platform::esp32::mqtt::props::MQTT;
|
||||
use render::props::Output;
|
||||
use scenes::Sequencer;
|
||||
|
||||
use crate::events::EventBus;
|
||||
use crate::events::{Evented, EventBus};
|
||||
use crate::platform::{DefaultBoard, Board, props::Board as BoardNS};
|
||||
use crate::task::{FixedSizeScheduler, Scheduler};
|
||||
use crate::task::FixedSizeScheduler;
|
||||
use crate::render::{Surfaces, Renderer};
|
||||
use crate::geometry::Rectangle;
|
||||
use crate::scenes::props::Scenes as SceneNS;
|
||||
@ -54,6 +55,7 @@ fn main() {
|
||||
let mut inputs = FixedSizeScheduler::new([
|
||||
Box::new(CircadianRhythm::new()),
|
||||
Box::new(Sequencer::new()),
|
||||
Box::new(IdleTimer::new())
|
||||
]);
|
||||
|
||||
let mut renderer = FixedSizeScheduler::new([Box::new(Renderer::new(output, surfaces))]);
|
||||
@ -81,27 +83,23 @@ fn main() {
|
||||
"renderbug::scenes::Sequencer",
|
||||
"renderbug::platform::esp32::wifi::WifiTask",
|
||||
"renderbug::platform::esp32::mqtt::MqttTask",
|
||||
"renderbug::inputs::circadian::CircadianRhythm"
|
||||
"renderbug::inputs::circadian::CircadianRhythm",
|
||||
"renderbug::inputs::idle::IdleTimer"
|
||||
];
|
||||
for task_name in initial_tasks {
|
||||
bus.push(Event::new_start_thing(task_name));
|
||||
log::info!("+ {}", task_name);
|
||||
}
|
||||
bus.push(Event::new_ready_to_rock());
|
||||
|
||||
log::info!("🚀 Launching...");
|
||||
log::info!("🚀 Ready to rock and roll");
|
||||
loop {
|
||||
let next_event = bus.next();
|
||||
match next_event {
|
||||
events::Event::Tick => (),
|
||||
Event::ReadyToRock => {
|
||||
log::info!("🚀 Ready to rock and roll");
|
||||
}
|
||||
_ => log::info!("⚡ Event: {}", next_event)
|
||||
}
|
||||
inputs.tick(&next_event, &mut bus);
|
||||
animations.tick(&next_event, &mut bus);
|
||||
system.tick(&next_event, &mut bus);
|
||||
renderer.tick(&next_event, &mut bus);
|
||||
inputs.on_event(&next_event, &mut bus);
|
||||
animations.on_event(&next_event, &mut bus);
|
||||
system.on_event(&next_event, &mut bus);
|
||||
renderer.on_event(&next_event, &mut bus);
|
||||
}
|
||||
}
|
||||
|
@ -1,167 +1,164 @@
|
||||
use std::{collections::LinkedList, thread::JoinHandle};
|
||||
use core::str;
|
||||
use std::{borrow::BorrowMut, collections::{HashMap, LinkedList}, thread::JoinHandle};
|
||||
|
||||
use esp_idf_svc::mqtt::client::{EspMqttClient, MqttClientConfiguration};
|
||||
use esp_idf_svc::mqtt::client::{EspMqttClient, EventPayload, MqttClientConfiguration};
|
||||
use log::Log;
|
||||
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::{events::{Event, EventBus, System as SystemNS}, properties::{NamespaceKey, PropertyID, PropertyKey, Variant}, render::props::Output as OutputNS, scenes::props::Scenes as SceneNS, task::{Environment, Task}};
|
||||
use crate::platform::props::Board as BoardNS;
|
||||
use paste::paste;
|
||||
|
||||
struct HADevice {
|
||||
prefix: String,
|
||||
unique_id: String
|
||||
device_id: String,
|
||||
scenes: Vec<String>
|
||||
}
|
||||
|
||||
impl HADevice {
|
||||
fn new(component: &str, chip_id: u64, name: &str) -> Self {
|
||||
fn new(device_id: String, scenes: Vec<String>) -> Self {
|
||||
HADevice {
|
||||
// eg: homeassistant/sensor/0BADCOFFEE/fps
|
||||
unique_id: format!("{:X}-{}", chip_id, name),
|
||||
prefix: format!("homeassistant/{}/renderbug-rs/{:X}-{}", component, chip_id, name)
|
||||
device_id,
|
||||
scenes
|
||||
}
|
||||
}
|
||||
|
||||
fn topic(&self, name: &str) -> String {
|
||||
format!("{}/{}", self.prefix, name)
|
||||
fn topic(&self, key: &str) -> String {
|
||||
format!("renderbug/{}/{}", self.device_id, key)
|
||||
}
|
||||
|
||||
fn command_topic(&self) -> String {
|
||||
self.topic("command")
|
||||
}
|
||||
|
||||
fn state_topic(&self) -> String {
|
||||
self.topic("state")
|
||||
}
|
||||
|
||||
fn device_config_topic(&self) -> String {
|
||||
format!("homeassistant/device/renderbug-rs-{}/config", self.device_id)
|
||||
}
|
||||
|
||||
fn registration(&self) -> Value {
|
||||
let components: HashMap<String, Value> = self.scenes.iter().map(|f| {
|
||||
let scene_id = format!("renderbug_{}_scene_{}", self.device_id, f);
|
||||
(scene_id.clone(), json!({
|
||||
"p": "scene",
|
||||
"unique_id": scene_id,
|
||||
"payload_on": json!({"namespace": SceneNS::Namespace, "key": SceneNS::Current.key, "value": f}).to_string(),
|
||||
"name": f
|
||||
}))
|
||||
}).collect();
|
||||
json!({
|
||||
"~": self.prefix,
|
||||
"stat_t": "~/state",
|
||||
"unique_id": self.unique_id,
|
||||
"stat_t": self.state_topic(),
|
||||
"cmd_t": self.command_topic(),
|
||||
"dev": {
|
||||
"name": "Renderbug-rs ESP32",
|
||||
"name": format!("Renderbug-rs ESP32 {}", self.device_id),
|
||||
"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]
|
||||
}
|
||||
"identifiers": [self.device_id]
|
||||
},
|
||||
"origin": {
|
||||
"name": "renderbug-rs"
|
||||
},
|
||||
"cmps": components
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MqttTask {
|
||||
client: Option<EspMqttClient<'static>>,
|
||||
conn_thread: Option<JoinHandle<()>>,
|
||||
fps_sensor: Option<HADevice>
|
||||
ha_device: Option<HADevice>
|
||||
}
|
||||
|
||||
impl MqttTask {
|
||||
pub fn new() -> Self {
|
||||
MqttTask {
|
||||
conn_thread: None,
|
||||
client: None,
|
||||
fps_sensor: None
|
||||
ha_device: None
|
||||
}
|
||||
}
|
||||
|
||||
fn start_mqtt(&mut self, bus: &EventBus) {
|
||||
fn start_mqtt(&mut self, env: &mut Environment) {
|
||||
log::info!("Starting MQTT");
|
||||
let chip_id: u64 = bus.properties().get(BoardNS::ChipID).unwrap().into();
|
||||
let chip_id: u64 = env.get_property(BoardNS::ChipID).unwrap();
|
||||
|
||||
self.fps_sensor = Some(HADevice::new("sensor", chip_id, "output-fps"));
|
||||
|
||||
let (client, mut conn) = EspMqttClient::new(
|
||||
let mut client_bus = env.bus.clone();
|
||||
self.client = Some(EspMqttClient::new_cb(
|
||||
"mqtt://10.0.0.2:1883",
|
||||
&MqttClientConfiguration {
|
||||
client_id: Some(&format!("{:X}", chip_id)),
|
||||
..Default::default()
|
||||
}
|
||||
).unwrap();
|
||||
log::info!("Connected!");
|
||||
},
|
||||
move |evt| {
|
||||
match evt.payload() {
|
||||
EventPayload::Received { id, topic, data, details } => {
|
||||
log::info!("got event id={} topic={:?} data={:?} details={:?}", id, topic, str::from_utf8(data), details);
|
||||
let props: HashMap<String, Variant> = serde_json::from_slice(data).unwrap();
|
||||
let ns: String = props.get("namespace").unwrap().to_owned().into();
|
||||
let key: String = props.get("key").unwrap().to_owned().into();
|
||||
let value: Variant = props.get("value").unwrap().clone();
|
||||
let all = client_bus.properties().all_props();
|
||||
let prop_id = all.iter().find(|f| {
|
||||
f.key.0 == key && f.namespace.0 == ns
|
||||
});
|
||||
|
||||
self.conn_thread = Some(std::thread::Builder::new()
|
||||
.stack_size(6000)
|
||||
.spawn(move || {
|
||||
conn.next().unwrap();
|
||||
}).unwrap());
|
||||
self.client = Some(client);
|
||||
client_bus.set_property(SystemNS::Idle, false);
|
||||
|
||||
match prop_id {
|
||||
None => log::warn!("Could not find property {} in {}!", key, ns),
|
||||
Some(id) => client_bus.set_property(id.clone(), value),
|
||||
}
|
||||
},
|
||||
EventPayload::Connected(_) => {
|
||||
log::info!("MQTT is online!");
|
||||
client_bus.set_property(props::MQTT::Online, true);
|
||||
},
|
||||
EventPayload::Disconnected => {
|
||||
log::info!("MQTT is offline!");
|
||||
client_bus.set_property(props::MQTT::Online, false);
|
||||
},
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
).unwrap());
|
||||
log::info!("Connected!");
|
||||
}
|
||||
}
|
||||
|
||||
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_ready(&mut self, bus: &mut EventBus) {
|
||||
let chip_id: u64 = bus.get_property(BoardNS::ChipID).unwrap();
|
||||
let scenes: Vec<String> = bus.get_property(SceneNS::All).unwrap();
|
||||
log::info!("Setting up scenes: {:?}", scenes);
|
||||
self.ha_device = Some(HADevice::new(format!("{:X}", chip_id), scenes));
|
||||
}
|
||||
|
||||
fn on_property_change(&mut self, key: PropertyID, value: &Variant, bus: &mut EventBus) {
|
||||
match (key, value) {
|
||||
(prop_id!(SystemNS::NetworkOnline), Variant::Boolean(true)) => {
|
||||
(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)) => {
|
||||
(props::MQTT::Online, Variant::Boolean(true)) => {
|
||||
if let Some(ref mut client) = self.client {
|
||||
if let Some(ref sensor) = self.fps_sensor {
|
||||
if let Some(ref device) = self.ha_device {
|
||||
client.enqueue(
|
||||
&device.device_config_topic(),
|
||||
esp_idf_svc::mqtt::client::QoS::AtLeastOnce,
|
||||
false,
|
||||
device.registration().to_string().as_bytes()
|
||||
).unwrap();
|
||||
log::info!("Subscribing to {}", device.command_topic());
|
||||
client.subscribe(&device.command_topic(), esp_idf_svc::mqtt::client::QoS::AtLeastOnce).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
/*(prop_id!(OutputNS::FPS), Variant::UInt(fps)) => {
|
||||
if let Some(ref mut client) = self.client {
|
||||
if let Some(ref sensor) = self.ha_device {
|
||||
client.enqueue(
|
||||
&sensor.topic("state"),
|
||||
esp_idf_svc::mqtt::client::QoS::AtLeastOnce,
|
||||
@ -170,7 +167,7 @@ impl Task for MqttTask {
|
||||
).unwrap();
|
||||
}
|
||||
}
|
||||
},
|
||||
},*/
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
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 crate::{events::{EventBus, System as SystemNS}, properties::Variant, task::{Environment, Task}, time::Periodically};
|
||||
|
||||
use std::fmt::Debug;
|
||||
|
||||
@ -83,22 +83,22 @@ impl Task for WifiTask {
|
||||
self.connect();
|
||||
}
|
||||
|
||||
fn on_tick(&mut self, bus: &mut EventBus) {
|
||||
fn on_tick(&mut self, env: &mut Environment) {
|
||||
if self.connection_check.tick() {
|
||||
if bus.properties().get(SystemNS::NetworkOnline).unwrap() == Variant::Boolean(true) {
|
||||
if env.get_property(SystemNS::NetworkOnline).unwrap() {
|
||||
match self.ntp.get_sync_status() {
|
||||
SyncStatus::Completed => bus.set_property(SystemNS::TimeSync, true),
|
||||
_ => bus.set_property(SystemNS::TimeSync, false)
|
||||
SyncStatus::Completed => env.set_property(SystemNS::TimeSync, true),
|
||||
_ => env.set_property(SystemNS::TimeSync, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn stop(&mut self, bus: &mut EventBus) {
|
||||
fn stop(&mut self, env: &mut Environment) {
|
||||
log::info!("Stopping wifi");
|
||||
self.wifi_sub.take().unwrap();
|
||||
self.ip_sub.take().unwrap();
|
||||
self.disconnect();
|
||||
bus.set_property(SystemNS::NetworkOnline, false);
|
||||
env.set_property(SystemNS::NetworkOnline, false);
|
||||
}
|
||||
}
|
||||
|
@ -9,12 +9,12 @@ pub mod esp32;
|
||||
pub type DefaultBoard = esp32::board::Esp32Board;
|
||||
|
||||
use crate::render::{Output, Surfaces};
|
||||
use crate::task::Scheduler;
|
||||
use crate::events::Evented;
|
||||
|
||||
pub trait Board {
|
||||
type Output: Output;
|
||||
type Surfaces: Surfaces;
|
||||
type Scheduler: Scheduler;
|
||||
type Scheduler: Evented;
|
||||
|
||||
fn take() -> Self;
|
||||
fn output(&mut self) -> Self::Output;
|
||||
|
@ -4,9 +4,8 @@ use crate::buffers::Pixbuf;
|
||||
use crate::properties::Variant;
|
||||
use crate::render::{HardwarePixel, Output, PixelView, Sample, props::Output as OutputNS};
|
||||
use crate::power::brightness_for_mw;
|
||||
use crate::{geometry::*, prop_id};
|
||||
use crate::geometry::*;
|
||||
use crate::mappings::*;
|
||||
use paste::paste;
|
||||
|
||||
use core::fmt::Debug;
|
||||
|
||||
@ -58,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(prop_id!(OutputNS::Brightness), Variant::Byte(new_brightness)) => self.brightness = *new_brightness,
|
||||
crate::events::Event::PropertyChange(OutputNS::Brightness, Variant::Byte(new_brightness)) => self.brightness = *new_brightness,
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use rgb::Rgb;
|
||||
pub use paste::paste;
|
||||
use serde::{de::Visitor, ser::{SerializeSeq, SerializeTuple}, Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Variant {
|
||||
@ -17,6 +17,73 @@ pub enum Variant {
|
||||
Vec(Vec<Variant>)
|
||||
}
|
||||
|
||||
impl Serialize for Variant {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer {
|
||||
match self {
|
||||
Variant::SignedByte(b) => serializer.serialize_i8(*b),
|
||||
Variant::Byte(b) => serializer.serialize_u8(*b),
|
||||
Variant::UInt(b) => serializer.serialize_u32(*b),
|
||||
Variant::Int(b) => serializer.serialize_i32(*b),
|
||||
Variant::BigUInt(b) => serializer.serialize_u64(*b),
|
||||
Variant::BigInt(b) => serializer.serialize_i64(*b),
|
||||
Variant::Boolean(b) => serializer.serialize_bool(*b),
|
||||
Variant::String(b) => serializer.serialize_str(&b),
|
||||
Variant::RGB(rgb) => {
|
||||
let mut seq = serializer.serialize_tuple(3).unwrap();
|
||||
seq.serialize_element(&rgb.r).unwrap();
|
||||
seq.serialize_element(&rgb.g).unwrap();
|
||||
seq.serialize_element(&rgb.b).unwrap();
|
||||
seq.end()
|
||||
},
|
||||
Variant::Vec(v) => {
|
||||
let mut seq = serializer.serialize_seq(Some(v.len())).unwrap();
|
||||
for i in v {
|
||||
seq.serialize_element(i).unwrap();
|
||||
}
|
||||
seq.end()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct VariantVisitor {}
|
||||
|
||||
impl<'de> Visitor<'de> for VariantVisitor {
|
||||
type Value = Variant;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
formatter.write_str("variant")
|
||||
}
|
||||
|
||||
fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error, {
|
||||
Ok(Variant::Boolean(v))
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error, {
|
||||
Ok(Variant::String(v.into()))
|
||||
}
|
||||
|
||||
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error, {
|
||||
Ok(Variant::String(v))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Variant {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de> {
|
||||
deserializer.deserialize_any(VariantVisitor {})
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_variant_type {
|
||||
($type:ty, $var_type:tt) => {
|
||||
impl From<$type> for Variant {
|
||||
@ -33,6 +100,19 @@ macro_rules! impl_variant_type {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Vec<$type>> for Variant {
|
||||
fn into(self) -> Vec<$type> {
|
||||
match self {
|
||||
Variant::Vec(v) => {
|
||||
v.into_iter().map(|f| {
|
||||
f.into()
|
||||
}).collect()
|
||||
},
|
||||
_ => panic!(concat!("Expected Variant::Vec(", stringify!($var_type), "but got {:?}"), self)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -53,7 +133,7 @@ impl<'a> From<&'a str> for Variant {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct Property {
|
||||
key: PropertyKey,
|
||||
value: Variant
|
||||
@ -68,19 +148,19 @@ impl Property {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct NamespaceKey(pub &'static str);
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct PropertyKey(pub &'static str);
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize)]
|
||||
pub struct PropertyID {
|
||||
pub namespace: NamespaceKey,
|
||||
pub key: PropertyKey
|
||||
}
|
||||
|
||||
impl Display for PropertyID {
|
||||
impl<'a> Display for PropertyID {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}::{}", self.namespace.0, self.key.0)
|
||||
}
|
||||
@ -91,64 +171,34 @@ pub trait Namespace {
|
||||
fn properties() -> Vec<Property>;
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! prop_id {
|
||||
($head:ident $(:: $tail:tt)+) => {
|
||||
prop_id!( $head :: ; $($tail),* )
|
||||
};
|
||||
($($namespace:ident ::)+ ; $key:ident ) => {
|
||||
$crate::properties::PropertyID {
|
||||
namespace: $($namespace ::)+ NAMESPACE_KEY,
|
||||
key: paste! { $($namespace ::)+ [<KEY_ $key>] }
|
||||
}
|
||||
};
|
||||
($($namespace:ident ::)+ ; $head:ident , $($tail:ident),+) => {
|
||||
prop_id!( $($namespace ::)* $head :: ; $($tail),* )
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! property_namespace {
|
||||
($name:ident, $($prop_name:ident => $value:expr),*) => {
|
||||
pub enum $name {
|
||||
$($prop_name),*
|
||||
($name:ident, $($prop_name:ident => $default_value:expr),*) => {
|
||||
pub struct $name {}
|
||||
|
||||
impl $name {
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const Namespace: $crate::properties::NamespaceKey = $crate::properties::NamespaceKey(stringify!($name));
|
||||
$(
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const $prop_name: $crate::properties::PropertyID = $crate::properties::PropertyID {
|
||||
namespace: Self::Namespace,
|
||||
key: $crate::properties::PropertyKey(stringify!($prop_name))
|
||||
};
|
||||
)*
|
||||
}
|
||||
|
||||
use paste::paste;
|
||||
|
||||
|
||||
impl crate::properties::Namespace for $name {
|
||||
fn nskey() -> $crate::properties::NamespaceKey {
|
||||
Self::NAMESPACE_KEY
|
||||
Self::Namespace
|
||||
}
|
||||
|
||||
fn properties() -> Vec<$crate::properties::Property> {
|
||||
vec![
|
||||
$($crate::properties::Property::new(paste! {Self::[<KEY_ $prop_name>]}, $crate::properties::Variant::from($value)),)*
|
||||
$($crate::properties::Property::new(Self::$prop_name.key, $crate::properties::Variant::from($default_value)),)*
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl $name {
|
||||
pub const NAMESPACE_KEY: $crate::properties::NamespaceKey = $crate::properties::NamespaceKey(stringify!($name));
|
||||
|
||||
$(paste! { pub const [<KEY_ $prop_name>]: crate::properties::PropertyKey = $crate::properties::PropertyKey(stringify!($prop_name)); })*
|
||||
|
||||
pub const fn key(&self) -> $crate::properties::PropertyKey {
|
||||
match self {
|
||||
$(Self::$prop_name => $crate::properties::PropertyKey(stringify!($prop_name))),*
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn id(&self) -> $crate::properties::PropertyID {
|
||||
$crate::properties::PropertyID { namespace: Self::NAMESPACE_KEY, key: self.key() }
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<$crate::properties::PropertyID> for $name {
|
||||
fn into(self) -> $crate::properties::PropertyID {
|
||||
self.id()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -267,4 +317,12 @@ impl Properties {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
pub fn all_props(&self) -> Vec<PropertyID> {
|
||||
self.contents.iter().map(|f| {
|
||||
f.props.iter().map(|k| {
|
||||
PropertyID { namespace: f.key, key: k.key }
|
||||
})
|
||||
}).flatten().collect()
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
use crate::task::Task;
|
||||
use crate::events::*;
|
||||
use crate::properties::*;
|
||||
use paste::paste;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
struct Scene {
|
||||
@ -12,6 +11,7 @@ struct Scene {
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
enum Trigger {
|
||||
Never,
|
||||
Startup,
|
||||
PropertyEquals(PropertyID, Variant)
|
||||
}
|
||||
@ -28,13 +28,28 @@ impl Sequencer {
|
||||
scenes: vec![
|
||||
Scene {
|
||||
name: "Start",
|
||||
patterns: vec!["Idle"],
|
||||
patterns: vec!["Test"],
|
||||
trigger: Trigger::Startup
|
||||
},
|
||||
Scene {
|
||||
name: "Online",
|
||||
name: "Test",
|
||||
patterns: vec!["Test"],
|
||||
trigger: Trigger::Never
|
||||
},
|
||||
Scene {
|
||||
name: "Offline",
|
||||
patterns: vec!["Offline"],
|
||||
trigger: Trigger::PropertyEquals(System::NetworkOnline.into(), Variant::Boolean(false))
|
||||
},
|
||||
Scene {
|
||||
name: "Active",
|
||||
patterns: vec!["Active"],
|
||||
trigger: Trigger::PropertyEquals(System::Idle.into(), Variant::Boolean(false))
|
||||
},
|
||||
Scene {
|
||||
name: "Idle",
|
||||
patterns: vec!["Idle"],
|
||||
trigger: Trigger::PropertyEquals(prop_id!(System::NetworkOnline), Variant::Boolean(true))
|
||||
trigger: Trigger::PropertyEquals(System::Idle.into(), Variant::Boolean(true))
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -94,37 +109,36 @@ impl Task for Sequencer {
|
||||
fn start(&mut self, bus: &mut EventBus) {
|
||||
log::info!("Starting sequencer!!!");
|
||||
|
||||
let startup_scene = self.scenes.iter().filter(|i| { i.trigger == Trigger::Startup }).next().unwrap();
|
||||
bus.set_property(Scenes::Current, startup_scene.name);
|
||||
let mut scene_list = Vec::new();
|
||||
for scene in self.scenes.iter() {
|
||||
scene_list.push(Variant::String(scene.name.to_string()));
|
||||
}
|
||||
|
||||
bus.set_property(Scenes::All, scene_list);
|
||||
|
||||
let startup_scene = self.scenes.iter().filter(|i| { i.trigger == Trigger::Startup }).next().unwrap();
|
||||
bus.set_property(Scenes::Current, startup_scene.name);
|
||||
}
|
||||
|
||||
fn on_property_change(&mut self, key: PropertyID, value: &Variant, bus: &mut EventBus) {
|
||||
match (key, value) {
|
||||
(prop_id!(Scenes::Current), Variant::String(ref scene_name)) => {
|
||||
(Scenes::Current, Variant::String(ref scene_name)) => {
|
||||
log::info!("Applying scene");
|
||||
self.apply_scene(scene_name, bus);
|
||||
},
|
||||
(key, value) => {
|
||||
/*for scene in self.scenes.iter() {
|
||||
for scene in self.scenes.iter() {
|
||||
match scene.trigger {
|
||||
Trigger::PropertyEquals(trigger_key, ref trigger_value) => {
|
||||
if trigger_key == key && trigger_value == value {
|
||||
log::info!("Triggering scene {}", scene.name);
|
||||
bus.push(Scenes::Current.new_property_change(scene.name))
|
||||
bus.set_property(Scenes::Current, scene.name);
|
||||
}
|
||||
},
|
||||
_ => ()
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use crate::prop_id;
|
||||
}
|
58
src/task.rs
58
src/task.rs
@ -1,14 +1,33 @@
|
||||
use core::fmt;
|
||||
|
||||
use crate::{events::{Event, EventBus}, properties::{Variant, PropertyID}};
|
||||
use crate::{events::{Event, EventBus}, properties::{Properties, PropertyID, Variant}, EventSource, Evented};
|
||||
|
||||
pub struct Environment<'a> {
|
||||
pub props: &'a mut Properties,
|
||||
pub bus: &'a mut EventBus
|
||||
}
|
||||
|
||||
impl Environment {
|
||||
|
||||
}
|
||||
|
||||
impl<'a> Environment<'a> {
|
||||
pub fn get_property<K: Into<PropertyID>, V>(&mut self, key: K) -> Option<V> where Variant: Into<V> {
|
||||
self.props.get(key.into()).and_then(|f| { Some(f.into()) })
|
||||
}
|
||||
|
||||
pub fn set_property<K: Into<PropertyID>, V: Into<Variant>>(&mut self, key: K, value: V) {
|
||||
self.bus.push(Event::new_property_change(key.into(), value.into()));
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Task: Send {
|
||||
fn on_ready(&mut self, bus: &mut EventBus) {}
|
||||
fn on_tick(&mut self, bus: &mut EventBus) {}
|
||||
fn on_property_change(&mut self, key: PropertyID, value: &Variant, bus: &mut EventBus) {}
|
||||
fn on_ready(&mut self, env: &mut Environment) {}
|
||||
fn on_tick(&mut self, env: &mut Environment) {}
|
||||
fn on_property_change(&mut self, key: PropertyID, value: &Variant, env: &mut Environment) {}
|
||||
|
||||
fn start(&mut self, bus: &mut EventBus) {}
|
||||
fn stop(&mut self, bus: &mut EventBus) {}
|
||||
fn start(&mut self, env: &mut Environment) {}
|
||||
fn stop(&mut self, env: &mut Environment) {}
|
||||
fn name(&self) -> &'static str {
|
||||
core::any::type_name::<Self>()
|
||||
}
|
||||
@ -60,16 +79,16 @@ impl ScheduledTask {
|
||||
}
|
||||
}
|
||||
|
||||
fn tick(&mut self, event: &Event, bus: &mut EventBus) {
|
||||
fn on_event(&mut self, event: &Event, env: &mut Environment) {
|
||||
match self.state {
|
||||
ScheduledState::Start => {
|
||||
log::info!("Starting task {}", self.task.name());
|
||||
self.task.start(bus);
|
||||
self.task.start(env);
|
||||
self.state = ScheduledState::Running
|
||||
},
|
||||
ScheduledState::Stop => {
|
||||
log::info!("Stopping task {}", self.task.name());
|
||||
self.task.stop(bus);
|
||||
self.task.stop(env);
|
||||
self.state = ScheduledState::Stopped
|
||||
},
|
||||
_ => ()
|
||||
@ -78,9 +97,9 @@ impl ScheduledTask {
|
||||
match self.state {
|
||||
ScheduledState::Running => {
|
||||
match event {
|
||||
Event::Tick => self.task.on_tick(bus),
|
||||
Event::ReadyToRock => self.task.on_ready(bus),
|
||||
Event::PropertyChange(key, value) => self.task.on_property_change(key.clone(), value, bus),
|
||||
Event::Tick => self.task.on_tick(env),
|
||||
Event::ReadyToRock => self.task.on_ready(env),
|
||||
Event::PropertyChange(key, value) => self.task.on_property_change(key.clone(), value, env),
|
||||
_ => ()
|
||||
}
|
||||
},
|
||||
@ -118,33 +137,26 @@ pub fn new(tasks: [Box<dyn Task>; TASK_COUNT]) -> Self {
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<const TASK_COUNT: usize> Scheduler for FixedSizeScheduler<TASK_COUNT> {
|
||||
fn tick(&mut self, event: &Event, bus: &mut EventBus) {
|
||||
fn on_event(&mut self, event: &Event, env: &mut Environment) {
|
||||
match event {
|
||||
Event::StartThing(task_name) => {
|
||||
if let Some(slot) = self.find_task(task_name) {
|
||||
log::debug!("Starting {}", task_name);
|
||||
log::info!("Queuing start for {}", task_name);
|
||||
slot.start();
|
||||
}
|
||||
},
|
||||
Event::StopThing(task_name) => {
|
||||
if let Some(slot) = self.find_task(task_name) {
|
||||
log::debug!("Stopping {}", task_name);
|
||||
log::info!("Queuing stop for {}", task_name);
|
||||
slot.stop();
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
for slot in &mut self.tasks {
|
||||
slot.tick(event, bus);
|
||||
slot.on_event(event, env);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Scheduler {
|
||||
fn tick(&mut self, event: &Event, bus: &mut EventBus);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user