diff --git a/Cargo.toml b/Cargo.toml index 0e7fef6..f7113e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/animations/mod.rs b/src/animations/mod.rs index 1da9970..30b376f 100644 --- a/src/animations/mod.rs +++ b/src/animations/mod.rs @@ -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 IdleTask { impl Task for IdleTask { 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 Task for IdleTask { 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(); diff --git a/src/animations/test.rs b/src/animations/test.rs index 9e7466d..f58a428 100644 --- a/src/animations/test.rs +++ b/src/animations/test.rs @@ -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 TestPattern { impl Task for TestPattern { 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 Task for TestPattern { }); } - fn stop(&mut self, _bus: &mut EventBus) { + fn stop(&mut self, _env: &mut Environment) { self.surface.clear_shader(); } } \ No newline at end of file diff --git a/src/events.rs b/src/events.rs index c6e2c08..5ec07be 100644 --- a/src/events.rs +++ b/src/events.rs @@ -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>>, @@ -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, V: Into>(&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 ); \ No newline at end of file diff --git a/src/inputs/circadian.rs b/src/inputs/circadian.rs index 9ba2bf6..d24d4cc 100644 --- a/src/inputs/circadian.rs +++ b/src/inputs/circadian.rs @@ -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 = 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); } } } diff --git a/src/inputs/idle.rs b/src/inputs/idle.rs new file mode 100644 index 0000000..9b2f073 --- /dev/null +++ b/src/inputs/idle.rs @@ -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); + } + } + } +} \ No newline at end of file diff --git a/src/inputs/mod.rs b/src/inputs/mod.rs index 87fdfc2..bffe0d1 100644 --- a/src/inputs/mod.rs +++ b/src/inputs/mod.rs @@ -1 +1,2 @@ -pub mod circadian; \ No newline at end of file +pub mod circadian; +pub mod idle; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 5d1882e..9e7b8ea 100644 --- a/src/main.rs +++ b/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); } } diff --git a/src/platform/esp32/mqtt.rs b/src/platform/esp32/mqtt.rs index 800dc0c..e43a788 100644 --- a/src/platform/esp32/mqtt.rs +++ b/src/platform/esp32/mqtt.rs @@ -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 } impl HADevice { - fn new(component: &str, chip_id: u64, name: &str) -> Self { + fn new(device_id: String, scenes: Vec) -> 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 = 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>, - conn_thread: Option>, - fps_sensor: Option + ha_device: Option } 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 = 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 = 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 = 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(); } } - }, + },*/ _ => () } } diff --git a/src/platform/esp32/wifi.rs b/src/platform/esp32/wifi.rs index 64ae1f3..023bc70 100644 --- a/src/platform/esp32/wifi.rs +++ b/src/platform/esp32/wifi.rs @@ -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); } } diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 162be19..8f55d02 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -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; diff --git a/src/platform/smart_leds_lib.rs b/src/platform/smart_leds_lib.rs index d1602f6..fc00301 100644 --- a/src/platform/smart_leds_lib.rs +++ b/src/platform/smart_leds_lib.rs @@ -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, T: FastWrite> Output for StrideOutput { 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, _ => () } } diff --git a/src/properties.rs b/src/properties.rs index e27f16e..30c9c15 100644 --- a/src/properties.rs +++ b/src/properties.rs @@ -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) } +impl Serialize for Variant { + fn serialize(&self, serializer: S) -> Result + 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(self, v: bool) -> Result + where + E: serde::de::Error, { + Ok(Variant::Boolean(v)) + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, { + Ok(Variant::String(v.into())) + } + + fn visit_string(self, v: String) -> Result + where + E: serde::de::Error, { + Ok(Variant::String(v)) + } +} + +impl<'de> Deserialize<'de> for Variant { + fn deserialize(deserializer: D) -> Result + 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> 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; } -#[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 ::)+ [] } - } - }; - ($($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::[]}, $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 []: 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 { + self.contents.iter().map(|f| { + f.props.iter().map(|k| { + PropertyID { namespace: f.key, key: k.key } + }) + }).flatten().collect() + } } \ No newline at end of file diff --git a/src/scenes.rs b/src/scenes.rs index 51544bc..4c119ed 100644 --- a/src/scenes.rs +++ b/src/scenes.rs @@ -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; \ No newline at end of file +} \ No newline at end of file diff --git a/src/task.rs b/src/task.rs index 40f5aea..a5aaf44 100644 --- a/src/task.rs +++ b/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, V>(&mut self, key: K) -> Option where Variant: Into { + self.props.get(key.into()).and_then(|f| { Some(f.into()) }) + } + + pub fn set_property, V: Into>(&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::() } @@ -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; TASK_COUNT]) -> Self { None } -} -impl Scheduler for FixedSizeScheduler { - 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); } \ No newline at end of file