11 Commits
wip-2 ... wip-3

18 changed files with 635 additions and 486 deletions

View File

@ -11,13 +11,11 @@ name = "renderbug"
harness = false # do not use the built in cargo test harness -> resolve rust-analyzer errors harness = false # do not use the built in cargo test harness -> resolve rust-analyzer errors
[profile.release] [profile.release]
opt-level = 3 opt-level = "s"
lto = "on"
[profile.dev] [profile.dev]
debug = true # Symbols are nice and they don't increase the size on Flash debug = true # Symbols are nice and they don't increase the size on Flash
opt-level = "s" opt-level = "z"
lto = "on"
[features] [features]
default = ["std", "esp-idf-svc/native", "rmt", "smart-leds"] default = ["std", "esp-idf-svc/native", "rmt", "smart-leds"]
@ -34,8 +32,6 @@ alloc = ["esp-idf-svc/alloc"]
[dependencies] [dependencies]
log = { version = "0.4", default-features = false } log = { version = "0.4", default-features = false }
esp-idf-svc = { version = "0.49", default-features = false } esp-idf-svc = { version = "0.49", default-features = false }
hsv = "0.1.1"
palette = { version = "0.7.6" }
running-average = "0.1.0" running-average = "0.1.0"
rgb = "0.8.50" rgb = "0.8.50"
@ -47,12 +43,11 @@ smart-leds-trait = { version = "0.3.0", optional = true }
smart-leds = { version = "0.4.0", optional = true } smart-leds = { version = "0.4.0", optional = true }
embedded-graphics = { version = "0.8.1", optional = true, features = ["fixed_point", "defmt"] } embedded-graphics = { version = "0.8.1", optional = true, features = ["fixed_point", "defmt"] }
ansi_term = "0.12.1"
num = "0.4.3" num = "0.4.3"
chrono = "0.4.38" chrono = "0.4.38"
serde_json = "1.0.133" serde_json = "1.0.133"
ssd1306 = "0.9.0" paste = "1.0.15"
embedded-hal = "1.0.0" serde = { version = "1.0.216", features = ["derive"] }
[build-dependencies] [build-dependencies]
embuild = "0.32.0" embuild = "0.32.0"

View File

@ -1,13 +1,12 @@
pub mod test; pub mod test;
use palette::Hsv;
use rgb::RGB8; use rgb::RGB8;
use crate::lib8::Hsv;
use crate::events::EventBus; use crate::events::EventBus;
use crate::geometry::*; use crate::geometry::*;
use crate::render::{Shader, Surface, Surfaces}; use crate::render::{Shader, Surface, Surfaces};
use crate::task::Task; use crate::task::{Environment, Task};
use crate::lib8::{trig::{sin8, cos8}, noise::inoise8, IntoRgb8}; use crate::lib8::{trig::{sin8, cos8}, noise::inoise8, IntoRgb8};
#[derive(Debug)] #[derive(Debug)]
@ -22,7 +21,7 @@ struct SolidShader {}
impl Shader for SolidShader { impl Shader for SolidShader {
fn draw(&self, _coords: &VirtualCoordinates, frame: usize) -> RGB8 { fn draw(&self, _coords: &VirtualCoordinates, frame: usize) -> RGB8 {
Hsv::new_srgb((frame % 255) as u8, 255, sin8((frame % 64) as u8)).into_rgb8() Hsv::new((frame % 255) as u8, 255, sin8((frame % 64) as u8)).into_rgb8()
} }
} }
@ -31,7 +30,7 @@ struct ShimmerShader {}
impl Shader for ShimmerShader { impl Shader for ShimmerShader {
fn draw(&self, coords: &VirtualCoordinates, frame: usize) -> RGB8 { fn draw(&self, coords: &VirtualCoordinates, frame: usize) -> RGB8 {
Hsv::new_srgb(((coords.x as usize).wrapping_add(frame / 3) % 255) as u8, coords.y, sin8(frame).max(75).min(64)).into_rgb8() Hsv::new(((coords.x as usize).wrapping_add(frame / 3) % 255) as u8, coords.y, sin8(frame).max(75).min(64)).into_rgb8()
} }
} }
@ -48,7 +47,7 @@ impl Shader for ThinkingShader {
let noise_y = offset_y / 2; let noise_y = offset_y / 2;
//let noise_x = coords.x.wrapping_add(offset_x); //let noise_x = coords.x.wrapping_add(offset_x);
//let noise_y = coords.y.wrapping_add(offset_y); //let noise_y = coords.y.wrapping_add(offset_y);
Hsv::new_srgb( Hsv::new(
inoise8(offset_x as i16, offset_y as i16), inoise8(offset_x as i16, offset_y as i16),
128_u8.saturating_add(inoise8(noise_y as i16, noise_x as i16)), 128_u8.saturating_add(inoise8(noise_y as i16, noise_x as i16)),
255 255
@ -69,7 +68,7 @@ impl<T: Surface> IdleTask<T> {
impl<T: Surface> Task for IdleTask<T> { impl<T: Surface> Task for IdleTask<T> {
fn name(&self) -> &'static str { "Idle" } 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.solid.set_shader(SolidShader {});
self.surface.set_shader(ThinkingShader { }); self.surface.set_shader(ThinkingShader { });
self.shimmer.set_shader(ShimmerShader { }); self.shimmer.set_shader(ShimmerShader { });
@ -79,7 +78,7 @@ impl<T: Surface> Task for IdleTask<T> {
self.shimmer.set_opacity(64); self.shimmer.set_opacity(64);
} }
fn stop(&mut self, _bus: &mut EventBus) { fn stop(&mut self, _env: &mut Environment) {
self.solid.clear_shader(); self.solid.clear_shader();
self.surface.clear_shader(); self.surface.clear_shader();
self.shimmer.clear_shader(); self.shimmer.clear_shader();

View File

@ -1,7 +1,7 @@
use palette::Hsv; use crate::{lib8::Hsv, task::Environment};
use rgb::RGB8; use rgb::RGB8;
use crate::{events::{Event, EventBus, Namespace}, 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}; use super::{Coordinates, Rectangle, VirtualCoordinates};
@ -48,7 +48,7 @@ impl Shader for TestShader {
Self::Blue => RGB8::new(0, 0, 255), Self::Blue => RGB8::new(0, 0, 255),
Self::White => RGB8::new(255, 255, 255), Self::White => RGB8::new(255, 255, 255),
Self::RGB => RGB8::new(coords.x, coords.y, 255 - (coords.x / 2 + coords.y / 2)), Self::RGB => RGB8::new(coords.x, coords.y, 255 - (coords.x / 2 + coords.y / 2)),
Self::HSV => Hsv::new_srgb(coords.x, coords.y, 255).into_rgb8(), Self::HSV => Hsv::new(coords.x, coords.y, 255).into_rgb8(),
Self::Outline => match (coords.x, coords.y) { Self::Outline => match (coords.x, coords.y) {
(0, 0) => RGB8::new(255, 255, 255), (0, 0) => RGB8::new(255, 255, 255),
(0, _) => RGB8::new(255, 0, 0), (0, _) => RGB8::new(255, 0, 0),
@ -93,11 +93,11 @@ impl<T: Surface> TestPattern<T> {
impl<T: Surface> Task for TestPattern<T> { impl<T: Surface> Task for TestPattern<T> {
fn name(&self) -> &'static str { "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); 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.updater.run(|| {
self.pattern = self.pattern.next(); self.pattern = self.pattern.next();
log::info!("Test pattern: {:?}", self.pattern); 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(); self.surface.clear_shader();
} }
} }

View File

@ -1,57 +1,7 @@
use core::fmt::Debug; use core::fmt::Debug;
use std::{collections::VecDeque, fmt::{Display, Result}, sync::{Arc, Mutex}}; use std::{collections::VecDeque, fmt::{Display, Result}, sync::{Arc, Mutex}};
use rgb::Rgb; use crate::{properties::{Properties, PropertyID, Variant}, property_namespace};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Variant {
SignedByte(i8),
Byte(u8),
UInt(u32),
Int(i32),
BigUInt(u64),
BigInt(i64),
Boolean(bool),
String(String),
RGB(Rgb<u8>),
Vec(Vec<Variant>)
}
macro_rules! impl_variant_type {
($type:ty, $var_type:tt) => {
impl From<$type> for Variant {
fn from(value: $type) -> Self {
Variant::$var_type(value)
}
}
impl Into<$type> for Variant {
fn into(self) -> $type {
match self {
Variant::$var_type(value) => value,
_ => panic!(concat!("Expected Variant::", stringify!($var_type), "but got {:?}"), self)
}
}
}
};
}
impl_variant_type!(u8, Byte);
impl_variant_type!(i8, SignedByte);
impl_variant_type!(u32, UInt);
impl_variant_type!(i32, Int);
impl_variant_type!(i64, BigInt);
impl_variant_type!(u64, BigUInt);
impl_variant_type!(bool, Boolean);
impl_variant_type!(String, String);
impl_variant_type!(Rgb<u8>, RGB);
impl_variant_type!(Vec<Variant>, Vec);
impl<'a> From<&'a str> for Variant {
fn from(value: &'a str) -> Self {
Variant::String(value.to_owned())
}
}
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum Event { pub enum Event {
@ -96,212 +46,13 @@ impl Event {
} }
} }
#[derive(Debug, Clone, PartialEq, Eq)] pub trait Evented {
pub struct Property { fn on_event(&mut self, event: &Event, bus: &mut EventBus);
key: PropertyKey,
value: Variant
} }
impl Property { pub trait EventSource {
pub const fn new(key: PropertyKey, value: Variant) -> Self { fn next(&mut self) -> Event;
Property { fn push(&mut self, event: Event);
key,
value,
}
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct NamespaceKey(pub &'static str);
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct PropertyKey(pub &'static str);
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct PropertyID {
pub namespace: NamespaceKey,
pub key: PropertyKey
}
impl Display for PropertyID {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result {
write!(f, "{}::{}", self.namespace.0, self.key.0)
}
}
pub trait Namespace {
fn nskey() -> NamespaceKey;
fn properties() -> Vec<Property>;
}
#[macro_export]
macro_rules! prop_id {
($name:ident, $prop_name:ident) => {
$crate::PropertyID {
namespace: $crate::NamespaceKey(stringify!($name)),
key: $crate::PropertyKey(stringify!($prop_name))
}
};
}
#[macro_export]
macro_rules! property_namespace {
($name:ident, $($prop_name:ident => $value:expr),*) => {
pub enum $name {
$($prop_name),*
}
impl $crate::Namespace for $name {
fn nskey() -> $crate::NamespaceKey {
$crate::NamespaceKey(stringify!($name))
}
fn properties() -> Vec<$crate::Property> {
vec![
$($crate::Property::new(Self::$prop_name.key(), $crate::Variant::from($value))),*
]
}
}
impl $name {
pub const fn key(&self) -> $crate::PropertyKey {
match self {
$(Self::$prop_name => $crate::PropertyKey(stringify!($prop_name))),*
}
}
pub const fn id(&self) -> $crate::PropertyID {
$crate::PropertyID { namespace: $crate::NamespaceKey(stringify!($name)), key: self.key() }
}
pub fn new_property_change<V>(&self, value: V) -> $crate::Event where $crate::Variant: From<V> {
$crate::Event::PropertyChange(self.id(), $crate::Variant::from(value))
}
}
impl Into<$crate::PropertyID> for $name {
fn into(self) -> $crate::PropertyID {
self.id()
}
}
};
}
#[derive(Debug, Clone)]
pub struct NamespaceProps {
props: Vec<Property>,
key: NamespaceKey
}
impl NamespaceProps {
fn new<NS: Namespace>() -> Self {
NamespaceProps {
props: NS::properties(),
key: NS::nskey()
}
}
fn get_key(&self, key: &PropertyKey) -> Option<&Property> {
for next in self.props.iter() {
if next.key == *key {
return Some(next);
}
}
log::warn!("Unknown key {:?}", key);
return None;
}
fn get_key_mut(&mut self, key: &PropertyKey) -> Option<&mut Property> {
for next in self.props.iter_mut() {
if next.key == *key {
return Some(next);
}
}
log::warn!("Unknown key {:?}", key);
return None;
}
}
#[derive(Debug, Clone)]
pub struct Properties {
contents: Vec<NamespaceProps>
}
impl Display for Properties {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for ns in self.contents.iter() {
write!(f, "{}\n", ns.key.0).unwrap();
for prop in ns.props.iter() {
write!(f, "\t{} = {:?}\n", prop.key.0, prop.value).unwrap();
}
}
Result::Ok(())
}
}
impl Properties {
fn new() -> Self {
Properties {
contents: Vec::new(),
}
}
pub fn add_namespace<T: Namespace>(&mut self) {
self.contents.push(NamespaceProps::new::<T>());
}
fn get_namespace(&self, ns: &NamespaceKey) -> Option<&NamespaceProps> {
for nsprops in self.contents.iter() {
if nsprops.key == *ns {
return Some(nsprops)
}
}
log::warn!("Unknown namespace {:?}", ns);
None
}
fn get_namespace_mut(&mut self, ns: &NamespaceKey) -> Option<&mut NamespaceProps> {
for nsprops in self.contents.iter_mut() {
if nsprops.key == *ns {
return Some(nsprops)
}
}
log::warn!("Unknown namespace {:?}", ns);
None
}
pub fn get<T: Into<PropertyID>>(&self, key: T) -> Option<Variant> {
let as_id = key.into();
match self.get_namespace(&as_id.namespace).unwrap().get_key(&as_id.key) {
None => None,
Some(v) => Some(v.value.clone())
}
}
pub fn set<V, T: Into<PropertyID>>(&mut self, key: T, value: V) -> bool where Variant: From<V> {
let as_id = key.into();
let as_variant: Variant = value.into();
match self.get_namespace_mut(&as_id.namespace).unwrap().get_key_mut(&as_id.key) {
None => (),
Some(found_key) => {
if found_key.value != as_variant {
found_key.value = as_variant;
return true
}
}
}
return false
}
} }
#[derive(Clone)] #[derive(Clone)]
@ -317,8 +68,10 @@ impl EventBus {
props: Arc::new(Mutex::new(Properties::new())) 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()); let next = self.pending.lock().unwrap().pop_front().unwrap_or(Event::new_tick());
match next { match next {
@ -334,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); 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!( property_namespace!(
@ -352,5 +97,6 @@ property_namespace!(
NetworkOnline => false, NetworkOnline => false,
IP => "", IP => "",
Gateway => "", Gateway => "",
TimeSync => false TimeSync => false,
Idle => false
); );

View File

@ -1,6 +1,7 @@
use chrono::{DateTime, Timelike, Utc}; use chrono::{DateTime, Timelike, Utc};
use crate::{events::{Event, EventBus, Namespace, Variant}, lib8::interpolate::lerp8by8, prop_id, render::props::Output as OutputNS, task::Task, time::Periodically, PropertyID}; 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;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct ScheduleEntry { struct ScheduleEntry {
@ -32,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 now: DateTime<Utc> = std::time::SystemTime::now().into();
let next_brightness = self.brightness_for_time(now.hour() as u8, now.minute() as u8); let next_brightness = self.brightness_for_time(now.hour() as u8, now.minute() as u8);
bus.push(OutputNS::Brightness.new_property_change(next_brightness)); env.set_property(OutputNS::Brightness, next_brightness);
} }
fn brightness_for_time(&self, hour: u8, minute: u8) -> u8 { fn brightness_for_time(&self, hour: u8, minute: u8) -> u8 {
@ -77,25 +78,25 @@ fn map_range(x: u16, in_min: u16, in_max: u16, out_min: u16, out_max: u16) -> u1
} }
let rise = out_max - out_min; let rise = out_max - out_min;
let delta = x - in_min; let delta = x - in_min;
return (delta * rise) / run + out_min; return (delta.wrapping_mul(rise)) / run + out_min;
} }
impl Task for CircadianRhythm { impl Task for CircadianRhythm {
fn on_ready(&mut self, bus: &mut EventBus) { fn on_ready(&mut self, env: &mut Environment) {
self.update_brightness(bus); 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) { match (key, value) {
(prop_id!(System, 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() { if self.time_check.tick() {
self.update_brightness(bus); self.update_brightness(env);
} }
} }
} }

38
src/inputs/idle.rs Normal file
View 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);
}
}
}
}

View File

@ -1 +1,2 @@
pub mod circadian; pub mod circadian;
pub mod idle;

View File

@ -2,8 +2,6 @@ pub mod interpolate;
pub mod noise; pub mod noise;
pub mod trig; pub mod trig;
use palette::encoding::srgb::Srgb;
use rgb::Rgb; use rgb::Rgb;
use crate::lib8::interpolate::scale8; use crate::lib8::interpolate::scale8;
@ -18,7 +16,23 @@ impl IntoRgb8 for Rgb<u8> {
} }
} }
impl IntoRgb8 for palette::Hsv<Srgb, u8> { pub struct Hsv {
pub hue: u8,
pub saturation: u8,
pub value: u8
}
impl Hsv {
pub fn new(hue: u8, saturation: u8, value: u8) -> Self {
Hsv {
hue,
saturation,
value
}
}
}
impl IntoRgb8 for Hsv {
//TODO: Borrowed from FastLED //TODO: Borrowed from FastLED
fn into_rgb8(self) -> Rgb<u8> { fn into_rgb8(self) -> Rgb<u8> {
const HSV_SECTION_3: u8 = 0x40; const HSV_SECTION_3: u8 = 0x40;
@ -27,7 +41,7 @@ impl IntoRgb8 for palette::Hsv<Srgb, u8> {
return Rgb::new(self.value, self.value, self.value) return Rgb::new(self.value, self.value, self.value)
} }
let mock_hue = scale8(191, self.hue.into_inner()); let mock_hue = scale8(191, self.hue);
let value: u8 = self.value; let value: u8 = self.value;
let saturation: u8 = self.saturation; let saturation: u8 = self.saturation;
let invsat: u8 = 255 - saturation; let invsat: u8 = 255 - saturation;
@ -52,4 +66,4 @@ impl IntoRgb8 for palette::Hsv<Srgb, u8> {
_ => Rgb::new(rampup_adj_with_floor, brightness_floor, rampdown_adj_with_floor) _ => Rgb::new(rampup_adj_with_floor, brightness_floor, rampdown_adj_with_floor)
} }
} }
} }

View File

@ -10,19 +10,19 @@ mod mappings;
mod buffers; mod buffers;
mod scenes; mod scenes;
mod inputs; mod inputs;
#[macro_use]
mod events; mod events;
mod properties;
use events::*; use events::*;
use inputs::circadian::CircadianRhythm; use inputs::circadian::CircadianRhythm;
use inputs::idle::IdleTimer;
use platform::esp32::mqtt::props::MQTT; use platform::esp32::mqtt::props::MQTT;
use render::props::Output; use render::props::Output;
use scenes::Sequencer; use scenes::Sequencer;
use crate::events::EventBus; use crate::events::{Evented, EventBus};
use crate::platform::{DefaultBoard, Board, props::Board as BoardNS}; 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::render::{Surfaces, Renderer};
use crate::geometry::Rectangle; use crate::geometry::Rectangle;
use crate::scenes::props::Scenes as SceneNS; use crate::scenes::props::Scenes as SceneNS;
@ -55,6 +55,7 @@ fn main() {
let mut inputs = FixedSizeScheduler::new([ let mut inputs = FixedSizeScheduler::new([
Box::new(CircadianRhythm::new()), Box::new(CircadianRhythm::new()),
Box::new(Sequencer::new()), Box::new(Sequencer::new()),
Box::new(IdleTimer::new())
]); ]);
let mut renderer = FixedSizeScheduler::new([Box::new(Renderer::new(output, surfaces))]); let mut renderer = FixedSizeScheduler::new([Box::new(Renderer::new(output, surfaces))]);
@ -82,27 +83,23 @@ fn main() {
"renderbug::scenes::Sequencer", "renderbug::scenes::Sequencer",
"renderbug::platform::esp32::wifi::WifiTask", "renderbug::platform::esp32::wifi::WifiTask",
"renderbug::platform::esp32::mqtt::MqttTask", "renderbug::platform::esp32::mqtt::MqttTask",
"renderbug::inputs::circadian::CircadianRhythm" "renderbug::inputs::circadian::CircadianRhythm",
"renderbug::inputs::idle::IdleTimer"
]; ];
for task_name in initial_tasks { for task_name in initial_tasks {
bus.push(Event::new_start_thing(task_name)); bus.push(Event::new_start_thing(task_name));
log::info!("+ {}", task_name);
} }
bus.push(Event::new_ready_to_rock()); bus.push(Event::new_ready_to_rock());
log::info!("🚀 Ready to rock and roll");
log::info!("🚀 Launching...");
loop { loop {
let next_event = bus.next(); let next_event = bus.next();
match next_event { match next_event {
events::Event::Tick => (), events::Event::Tick => (),
Event::ReadyToRock => {
log::info!("🚀 Ready to rock and roll");
}
_ => log::info!("⚡ Event: {}", next_event) _ => log::info!("⚡ Event: {}", next_event)
} }
inputs.tick(&next_event, &mut bus); inputs.on_event(&next_event, &mut bus);
animations.tick(&next_event, &mut bus); animations.on_event(&next_event, &mut bus);
system.tick(&next_event, &mut bus); system.on_event(&next_event, &mut bus);
renderer.tick(&next_event, &mut bus); renderer.on_event(&next_event, &mut bus);
} }
} }

View File

@ -171,7 +171,14 @@ impl<const STRIDE_NUM: usize> StrideMapping<STRIDE_NUM> {
pub fn new_albus() -> Self { pub fn new_albus() -> Self {
Self::from_json(&[ Self::from_json(&[
(0, 0, 50 * 3, false) (0, 0, 29, false),
(1, 0, 20, false),
(2, 0, 22, false),
(3, 0, 19, false),
(4, 0, 12, false),
(5, 0, 14, false),
(6, 0, 16, false),
(7, 0, 19, false),
]) ])
} }

View File

@ -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 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::{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 crate::platform::props::Board as BoardNS;
struct HADevice { struct HADevice {
prefix: String, device_id: String,
unique_id: String scenes: Vec<String>
} }
impl HADevice { impl HADevice {
fn new(component: &str, chip_id: u64, name: &str) -> Self { fn new(device_id: String, scenes: Vec<String>) -> Self {
HADevice { HADevice {
// eg: homeassistant/sensor/0BADCOFFEE/fps device_id,
unique_id: format!("{:X}-{}", chip_id, name), scenes
prefix: format!("homeassistant/{}/renderbug-rs/{:X}-{}", component, chip_id, name)
} }
} }
fn topic(&self, name: &str) -> String { fn topic(&self, key: &str) -> String {
format!("{}/{}", self.prefix, name) 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 { 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!({ json!({
"~": self.prefix, "stat_t": self.state_topic(),
"stat_t": "~/state", "cmd_t": self.command_topic(),
"unique_id": self.unique_id,
"dev": { "dev": {
"name": "Renderbug-rs ESP32", "name": format!("Renderbug-rs ESP32 {}", self.device_id),
"mdl": "Renderbug-rs ESP32", "mdl": "Renderbug-rs ESP32",
"sw": "",
"mf": "Phong Robotics", "mf": "Phong Robotics",
"ids": [self.unique_id] "identifiers": [self.device_id]
} },
}) "origin": {
} "name": "renderbug-rs"
} },
"cmps": components
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 { pub struct MqttTask {
client: Option<EspMqttClient<'static>>, client: Option<EspMqttClient<'static>>,
conn_thread: Option<JoinHandle<()>>, ha_device: Option<HADevice>
fps_sensor: Option<HADevice>
} }
impl MqttTask { impl MqttTask {
pub fn new() -> Self { pub fn new() -> Self {
MqttTask { MqttTask {
conn_thread: None,
client: 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"); 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 mut client_bus = env.bus.clone();
self.client = Some(EspMqttClient::new_cb(
let (client, mut conn) = EspMqttClient::new(
"mqtt://10.0.0.2:1883", "mqtt://10.0.0.2:1883",
&MqttClientConfiguration { &MqttClientConfiguration {
client_id: Some(&format!("{:X}", chip_id)), client_id: Some(&format!("{:X}", chip_id)),
..Default::default() ..Default::default()
} },
).unwrap(); move |evt| {
log::info!("Connected!"); 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() client_bus.set_property(SystemNS::Idle, false);
.stack_size(6000)
.spawn(move || { match prop_id {
conn.next().unwrap(); None => log::warn!("Could not find property {} in {}!", key, ns),
}).unwrap()); Some(id) => client_bus.set_property(id.clone(), value),
self.client = Some(client); }
},
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 { impl Task for MqttTask {
fn start(&mut self, bus: &mut EventBus) { fn start(&mut self, bus: &mut EventBus) {
bus.push(props::MQTT::Online.new_property_change(false)); bus.set_property(props::MQTT::Online, false);
}
fn on_ready(&mut self, bus: &mut EventBus) {
let chip_id = bus.properties().get(BoardNS::ChipID).unwrap().into(); let chip_id: u64 = bus.get_property(BoardNS::ChipID).unwrap();
self.fps_sensor = Some(HADevice::new("sensor", chip_id, "fps")); 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) { fn on_property_change(&mut self, key: PropertyID, value: &Variant, bus: &mut EventBus) {
match (key, value) { match (key, value) {
(prop_id!(System, Online), Variant::Boolean(true)) => { (SystemNS::NetworkOnline, Variant::Boolean(true)) => {
log::info!("Registering with MQTT"); log::info!("Registering with MQTT");
let chip_id = bus.properties().get(BoardNS::ChipID).unwrap().into();
self.start_mqtt(bus); 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)) => { (props::MQTT::Online, Variant::Boolean(true)) => {
if let Some(ref mut client) = self.client { 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( client.enqueue(
&sensor.topic("state"), &sensor.topic("state"),
esp_idf_svc::mqtt::client::QoS::AtLeastOnce, esp_idf_svc::mqtt::client::QoS::AtLeastOnce,
@ -170,7 +167,7 @@ impl Task for MqttTask {
).unwrap(); ).unwrap();
} }
} }
}, },*/
_ => () _ => ()
} }
} }

View File

@ -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 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 crate::{events::{EventBus, System as SystemNS}, properties::Variant, task::{Environment, Task}, time::Periodically};
use std::fmt::Debug; use std::fmt::Debug;
@ -66,7 +66,7 @@ impl Task for WifiTask {
let mut wifi_bus = bus.clone(); let mut wifi_bus = bus.clone();
self.wifi_sub = Some(self.sys_loop.subscribe::<WifiEvent, _>( move |evt| { self.wifi_sub = Some(self.sys_loop.subscribe::<WifiEvent, _>( move |evt| {
log::debug!("wifi event {:?}", evt); log::debug!("wifi event {:?}", evt);
wifi_bus.push(SystemNS::NetworkOnline.new_property_change(false)); wifi_bus.set_property(SystemNS::NetworkOnline,false);
}).unwrap()); }).unwrap());
let mut ip_bus = bus.clone(); let mut ip_bus = bus.clone();
self.ip_sub = Some(self.sys_loop.subscribe::<IpEvent, _>(move |evt| { self.ip_sub = Some(self.sys_loop.subscribe::<IpEvent, _>(move |evt| {
@ -83,22 +83,22 @@ impl Task for WifiTask {
self.connect(); self.connect();
} }
fn on_tick(&mut self, bus: &mut EventBus) { fn on_tick(&mut self, env: &mut Environment) {
if self.connection_check.tick() { 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() { match self.ntp.get_sync_status() {
SyncStatus::Completed => bus.set_property(SystemNS::TimeSync, true), SyncStatus::Completed => env.set_property(SystemNS::TimeSync, true),
_ => bus.set_property(SystemNS::TimeSync, false) _ => env.set_property(SystemNS::TimeSync, false)
} }
} }
} }
} }
fn stop(&mut self, bus: &mut EventBus) { fn stop(&mut self, env: &mut Environment) {
log::info!("Stopping wifi"); log::info!("Stopping wifi");
self.wifi_sub.take().unwrap(); self.wifi_sub.take().unwrap();
self.ip_sub.take().unwrap(); self.ip_sub.take().unwrap();
self.disconnect(); self.disconnect();
bus.push(SystemNS::NetworkOnline.new_property_change(false)); env.set_property(SystemNS::NetworkOnline, false);
} }
} }

View File

@ -9,12 +9,12 @@ pub mod esp32;
pub type DefaultBoard = esp32::board::Esp32Board; pub type DefaultBoard = esp32::board::Esp32Board;
use crate::render::{Output, Surfaces}; use crate::render::{Output, Surfaces};
use crate::task::Scheduler; use crate::events::Evented;
pub trait Board { pub trait Board {
type Output: Output; type Output: Output;
type Surfaces: Surfaces; type Surfaces: Surfaces;
type Scheduler: Scheduler; type Scheduler: Evented;
fn take() -> Self; fn take() -> Self;
fn output(&mut self) -> Self::Output; fn output(&mut self) -> Self::Output;

View File

@ -1,10 +1,10 @@
use smart_leds_trait::SmartLedsWrite; use smart_leds_trait::SmartLedsWrite;
use crate::buffers::Pixbuf; use crate::buffers::Pixbuf;
use crate::events::Variant; use crate::properties::Variant;
use crate::render::{HardwarePixel, Output, PixelView, Sample, props::Output as OutputNS}; use crate::render::{HardwarePixel, Output, PixelView, Sample, props::Output as OutputNS};
use crate::power::brightness_for_mw; use crate::power::brightness_for_mw;
use crate::{geometry::*, prop_id}; use crate::geometry::*;
use crate::mappings::*; use crate::mappings::*;
use core::fmt::Debug; 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) { fn on_event(&mut self, event: &crate::events::Event) {
match event { match event {
crate::events::Event::PropertyChange(prop_id!(Output, Brightness), Variant::Byte(new_brightness)) => self.brightness = *new_brightness, crate::events::Event::PropertyChange(OutputNS::Brightness, Variant::Byte(new_brightness)) => self.brightness = *new_brightness,
_ => () _ => ()
} }
} }

328
src/properties.rs Normal file
View File

@ -0,0 +1,328 @@
use std::fmt::Display;
use rgb::Rgb;
use serde::{de::Visitor, ser::{SerializeSeq, SerializeTuple}, Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Variant {
SignedByte(i8),
Byte(u8),
UInt(u32),
Int(i32),
BigUInt(u64),
BigInt(i64),
Boolean(bool),
String(String),
RGB(Rgb<u8>),
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 {
fn from(value: $type) -> Self {
Variant::$var_type(value)
}
}
impl Into<$type> for Variant {
fn into(self) -> $type {
match self {
Variant::$var_type(value) => value,
_ => panic!(concat!("Expected Variant::", stringify!($var_type), "but got {:?}"), self)
}
}
}
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)
}
}
}
};
}
impl_variant_type!(u8, Byte);
impl_variant_type!(i8, SignedByte);
impl_variant_type!(u32, UInt);
impl_variant_type!(i32, Int);
impl_variant_type!(i64, BigInt);
impl_variant_type!(u64, BigUInt);
impl_variant_type!(bool, Boolean);
impl_variant_type!(String, String);
impl_variant_type!(Rgb<u8>, RGB);
impl_variant_type!(Vec<Variant>, Vec);
impl<'a> From<&'a str> for Variant {
fn from(value: &'a str) -> Self {
Variant::String(value.to_owned())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct Property {
key: PropertyKey,
value: Variant
}
impl Property {
pub const fn new(key: PropertyKey, value: Variant) -> Self {
Property {
key,
value,
}
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
pub struct NamespaceKey(pub &'static str);
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)]
pub struct PropertyKey(pub &'static str);
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize)]
pub struct PropertyID {
pub namespace: NamespaceKey,
pub key: PropertyKey
}
impl<'a> Display for PropertyID {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}::{}", self.namespace.0, self.key.0)
}
}
pub trait Namespace {
fn nskey() -> NamespaceKey;
fn properties() -> Vec<Property>;
}
#[macro_export]
macro_rules! property_namespace {
($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))
};
)*
}
impl crate::properties::Namespace for $name {
fn nskey() -> $crate::properties::NamespaceKey {
Self::Namespace
}
fn properties() -> Vec<$crate::properties::Property> {
vec![
$($crate::properties::Property::new(Self::$prop_name.key, $crate::properties::Variant::from($default_value)),)*
]
}
}
};
}
#[derive(Debug, Clone)]
pub struct NamespaceProps {
props: Vec<Property>,
key: NamespaceKey
}
impl NamespaceProps {
fn new<NS: Namespace>() -> Self {
NamespaceProps {
props: NS::properties(),
key: NS::nskey()
}
}
fn get_key(&self, key: &PropertyKey) -> Option<&Property> {
for next in self.props.iter() {
if next.key == *key {
return Some(next);
}
}
log::warn!("Unknown key {:?}", key);
return None;
}
fn get_key_mut(&mut self, key: &PropertyKey) -> Option<&mut Property> {
for next in self.props.iter_mut() {
if next.key == *key {
return Some(next);
}
}
log::warn!("Unknown key {:?}", key);
return None;
}
}
#[derive(Debug, Clone)]
pub struct Properties {
contents: Vec<NamespaceProps>
}
impl Display for Properties {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for ns in self.contents.iter() {
write!(f, "{}\n", ns.key.0).unwrap();
for prop in ns.props.iter() {
write!(f, "\t{} = {:?}\n", prop.key.0, prop.value).unwrap();
}
}
Result::Ok(())
}
}
impl Properties {
pub fn new() -> Self {
Properties {
contents: Vec::new(),
}
}
pub fn add_namespace<T: Namespace>(&mut self) {
self.contents.push(NamespaceProps::new::<T>());
}
fn get_namespace(&self, ns: &NamespaceKey) -> Option<&NamespaceProps> {
for nsprops in self.contents.iter() {
if nsprops.key == *ns {
return Some(nsprops)
}
}
log::warn!("Unknown namespace {:?}", ns);
None
}
fn get_namespace_mut(&mut self, ns: &NamespaceKey) -> Option<&mut NamespaceProps> {
for nsprops in self.contents.iter_mut() {
if nsprops.key == *ns {
return Some(nsprops)
}
}
log::warn!("Unknown namespace {:?}", ns);
None
}
pub fn get<T: Into<PropertyID>>(&self, key: T) -> Option<Variant> {
let as_id = key.into();
match self.get_namespace(&as_id.namespace).unwrap().get_key(&as_id.key) {
None => None,
Some(v) => Some(v.value.clone())
}
}
pub fn set<V, T: Into<PropertyID>>(&mut self, key: T, value: V) -> bool where Variant: From<V> {
let as_id = key.into();
let as_variant: Variant = value.into();
match self.get_namespace_mut(&as_id.namespace).unwrap().get_key_mut(&as_id.key) {
None => (),
Some(found_key) => {
if found_key.value != as_variant {
found_key.value = as_variant;
return true
}
}
}
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()
}
}

View File

@ -1,13 +1,13 @@
use rgb::Rgb; use rgb::Rgb;
use crate::events::*; use crate::events::*;
use crate::{geometry::*, PropertyID}; use crate::properties::*;
use crate::geometry::*;
use crate::lib8::interpolate::Fract8Ops; use crate::lib8::interpolate::Fract8Ops;
use crate::power::AsMilliwatts; use crate::power::AsMilliwatts;
use crate::task::Task; use crate::task::Task;
use crate::time::Periodically; use crate::time::Periodically;
use running_average::RealTimeRunningAverage; use running_average::RealTimeRunningAverage;
use core::fmt::Debug; use core::fmt::Debug;
use std::collections::LinkedList;
pub trait HardwarePixel: Send + Sync + Copy + AsMilliwatts + Default + From<Rgb<u8>> + Fract8Ops {} pub trait HardwarePixel: Send + Sync + Copy + AsMilliwatts + Default + From<Rgb<u8>> + Fract8Ops {}
impl<T> HardwarePixel for T where T: Send + Sync + Copy + AsMilliwatts + Default + From<Rgb<u8>> + Fract8Ops {} impl<T> HardwarePixel for T where T: Send + Sync + Copy + AsMilliwatts + Default + From<Rgb<u8>> + Fract8Ops {}
@ -74,7 +74,7 @@ impl<T: Output, S: Surfaces> Renderer<T, S> {
impl<T: Output, S: Surfaces> Task for Renderer<T, S> { impl<T: Output, S: Surfaces> Task for Renderer<T, S> {
fn name(&self) -> &'static str { "Renderer" } fn name(&self) -> &'static str { "Renderer" }
fn on_property_change(&mut self, key: PropertyID, value: &crate::events::Variant, _bus: &mut EventBus) { fn on_property_change(&mut self, key: PropertyID, value: &Variant, _bus: &mut EventBus) {
self.output.on_event(&Event::new_property_change(key, value.clone())); self.output.on_event(&Event::new_property_change(key, value.clone()));
} }
@ -90,7 +90,7 @@ impl<T: Output, S: Surfaces> Task for Renderer<T, S> {
self.fps.insert((self.frame - self.frame_count) as u32); self.fps.insert((self.frame - self.frame_count) as u32);
self.frame_count = self.frame; self.frame_count = self.frame;
let fps = self.fps.measurement(); let fps = self.fps.measurement();
bus.push(crate::render::props::Output::FPS.new_property_change(fps.rate() as u32)); bus.set_property(crate::render::props::Output::FPS, fps.rate() as u32);
}); });
} }
} }

View File

@ -1,5 +1,6 @@
use crate::task::Task; use crate::task::Task;
use crate::events::*; use crate::events::*;
use crate::properties::*;
#[derive(Debug, PartialEq, Eq, Clone)] #[derive(Debug, PartialEq, Eq, Clone)]
struct Scene { struct Scene {
@ -10,8 +11,9 @@ struct Scene {
#[derive(Debug, PartialEq, Eq, Clone)] #[derive(Debug, PartialEq, Eq, Clone)]
enum Trigger { enum Trigger {
Never,
Startup, Startup,
PropertyEquals(&'static str, Variant) PropertyEquals(PropertyID, Variant)
} }
pub struct Sequencer { pub struct Sequencer {
@ -26,13 +28,28 @@ impl Sequencer {
scenes: vec![ scenes: vec![
Scene { Scene {
name: "Start", name: "Start",
patterns: vec!["Idle"], patterns: vec!["Test"],
trigger: Trigger::Startup trigger: Trigger::Startup
}, },
Scene { 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"], patterns: vec!["Idle"],
trigger: Trigger::PropertyEquals("system.network.online", Variant::Boolean(true)) trigger: Trigger::PropertyEquals(System::Idle.into(), Variant::Boolean(true))
} }
] ]
} }
@ -92,37 +109,36 @@ impl Task for Sequencer {
fn start(&mut self, bus: &mut EventBus) { fn start(&mut self, bus: &mut EventBus) {
log::info!("Starting sequencer!!!"); log::info!("Starting sequencer!!!");
let startup_scene = self.scenes.iter().filter(|i| { i.trigger == Trigger::Startup }).next().unwrap();
bus.push(Scenes::Current.new_property_change(startup_scene.name));
let mut scene_list = Vec::new(); let mut scene_list = Vec::new();
for scene in self.scenes.iter() { for scene in self.scenes.iter() {
scene_list.push(Variant::String(scene.name.to_string())); scene_list.push(Variant::String(scene.name.to_string()));
} }
bus.push(Scenes::All.new_property_change(scene_list)); 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) { fn on_property_change(&mut self, key: PropertyID, value: &Variant, bus: &mut EventBus) {
match (key, value) { match (key, value) {
(prop_id!(Scenes, Current), Variant::String(ref scene_name)) => { (Scenes::Current, Variant::String(ref scene_name)) => {
log::info!("Applying scene"); log::info!("Applying scene");
self.apply_scene(scene_name, bus); self.apply_scene(scene_name, bus);
}, },
(key, value) => { (key, value) => {
/*for scene in self.scenes.iter() { for scene in self.scenes.iter() {
match scene.trigger { match scene.trigger {
Trigger::PropertyEquals(trigger_key, ref trigger_value) => { Trigger::PropertyEquals(trigger_key, ref trigger_value) => {
if trigger_key == key && trigger_value == value { if trigger_key == key && trigger_value == value {
log::info!("Triggering scene {}", scene.name); 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;

View File

@ -1,20 +1,36 @@
use core::fmt; use core::fmt;
use std::collections::LinkedList;
use crate::{events::{Event, EventBus, Namespace, Property, 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 { pub trait Task: Send {
fn on_ready(&mut self, bus: &mut EventBus) {} fn on_ready(&mut self, env: &mut Environment) {}
fn on_tick(&mut self, bus: &mut EventBus) {} fn on_tick(&mut self, env: &mut Environment) {}
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) {}
fn start(&mut self, bus: &mut EventBus) {} fn start(&mut self, env: &mut Environment) {}
fn stop(&mut self, bus: &mut EventBus) {} fn stop(&mut self, env: &mut Environment) {}
fn name(&self) -> &'static str { fn name(&self) -> &'static str {
core::any::type_name::<Self>() core::any::type_name::<Self>()
} }
fn properties(&self) -> LinkedList<Property> { LinkedList::new() }
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -63,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 { match self.state {
ScheduledState::Start => { ScheduledState::Start => {
log::info!("Starting task {}", self.task.name()); log::info!("Starting task {}", self.task.name());
self.task.start(bus); self.task.start(env);
self.state = ScheduledState::Running self.state = ScheduledState::Running
}, },
ScheduledState::Stop => { ScheduledState::Stop => {
log::info!("Stopping task {}", self.task.name()); log::info!("Stopping task {}", self.task.name());
self.task.stop(bus); self.task.stop(env);
self.state = ScheduledState::Stopped self.state = ScheduledState::Stopped
}, },
_ => () _ => ()
@ -81,9 +97,9 @@ impl ScheduledTask {
match self.state { match self.state {
ScheduledState::Running => { ScheduledState::Running => {
match event { match event {
Event::Tick => self.task.on_tick(bus), Event::Tick => self.task.on_tick(env),
Event::ReadyToRock => self.task.on_ready(bus), Event::ReadyToRock => self.task.on_ready(env),
Event::PropertyChange(key, value) => self.task.on_property_change(key.clone(), value, bus), Event::PropertyChange(key, value) => self.task.on_property_change(key.clone(), value, env),
_ => () _ => ()
} }
}, },
@ -121,32 +137,26 @@ pub fn new(tasks: [Box<dyn Task>; TASK_COUNT]) -> Self {
None None
} }
}
impl<const TASK_COUNT: usize> Scheduler for FixedSizeScheduler<TASK_COUNT> { fn on_event(&mut self, event: &Event, env: &mut Environment) {
fn tick(&mut self, event: &Event, bus: &mut EventBus) {
match event { match event {
Event::StartThing(task_name) => { Event::StartThing(task_name) => {
if let Some(slot) = self.find_task(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(); slot.start();
} }
}, },
Event::StopThing(task_name) => { Event::StopThing(task_name) => {
if let Some(slot) = self.find_task(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(); slot.stop();
} }
}, },
_ => { _ => {
for slot in &mut self.tasks { 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);
} }