Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
7a3ea61030 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,4 +1,3 @@
|
|||||||
/.vscode
|
|
||||||
/.embuild
|
/.embuild
|
||||||
/target
|
/target
|
||||||
/Cargo.lock
|
/Cargo.lock
|
26
.vscode/launch.json
vendored
Normal file
26
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "probe-rs-debug",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "probe-rs Test",
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
"connectUnderReset": true,
|
||||||
|
"chip": "ESP32S3",
|
||||||
|
"flashingConfig": {
|
||||||
|
"flashingEnabled": true,
|
||||||
|
"haltAfterReset": true
|
||||||
|
},
|
||||||
|
"coreConfigs": [
|
||||||
|
{
|
||||||
|
"coreIndex": 0,
|
||||||
|
"programBinary": "./target/xtensa-esp32s3-espidf/debug/${workspaceFolderBasename}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
10
Cargo.toml
10
Cargo.toml
@ -11,11 +11,13 @@ 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 = "s"
|
opt-level = 3
|
||||||
|
lto = true
|
||||||
|
|
||||||
[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 = "z"
|
opt-level = "z"
|
||||||
|
lto = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["std", "esp-idf-svc/native", "rmt", "smart-leds"]
|
default = ["std", "esp-idf-svc/native", "rmt", "smart-leds"]
|
||||||
@ -32,6 +34,8 @@ 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"
|
||||||
|
|
||||||
@ -43,10 +47,10 @@ 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"
|
fugit = "0.3.7"
|
||||||
paste = "1.0.15"
|
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
embuild = "0.32.0"
|
embuild = "0.32.0"
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
partition_table = "partitions.csv"
|
|
||||||
baudrate = 460800
|
|
@ -1,5 +0,0 @@
|
|||||||
# Name, Type, SubType, Offset, Size, Flags
|
|
||||||
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
|
|
||||||
nvs, data, nvs, , 0x6000,
|
|
||||||
phy_init, data, phy, , 0x1000,
|
|
||||||
factory, app, factory, , 3M,
|
|
|
13
src/TODO.md
13
src/TODO.md
@ -1,18 +1,15 @@
|
|||||||
[x] cfg macros
|
[x] cfg macros
|
||||||
[ ] warnings
|
[ ] warnings
|
||||||
[x] rgb crate
|
[x] rgb crate
|
||||||
[x] Layer blending
|
[ ] Layer blending
|
||||||
[x] Refactor idle pattern into test pattern
|
[ ] Refactor idle pattern into test pattern
|
||||||
[x] Wifi
|
[ ] Wifi
|
||||||
[ ] JSON surface map loading
|
[ ] JSON surface map loading
|
||||||
[ ] Weather
|
[ ] Weather
|
||||||
[x] Circadian Rhythm
|
[ ] Circadian Rhythm
|
||||||
[x] NTP
|
[ ] NTP
|
||||||
[ ] Config to only start a subset of tasks on startup
|
[ ] Config to only start a subset of tasks on startup
|
||||||
[ ] Serial CLI
|
[ ] Serial CLI
|
||||||
[ ] Surface blending API
|
[ ] Surface blending API
|
||||||
[ ] Layer blending equations
|
[ ] Layer blending equations
|
||||||
[ ] Surface rotation
|
[ ] Surface rotation
|
||||||
[ ] esp8266 port
|
|
||||||
[ ] event system
|
|
||||||
[ ] threaded schedulers
|
|
@ -1,9 +1,89 @@
|
|||||||
use crate::lib8::Hsv;
|
use palette::Hsv;
|
||||||
|
|
||||||
use rgb::RGB8;
|
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::{Event, EventBus};
|
||||||
|
use crate::time::Periodically;
|
||||||
|
use crate::geometry::*;
|
||||||
|
use crate::render::{Shader, Surface, Surfaces};
|
||||||
|
use crate::task::Task;
|
||||||
|
use crate::lib8::{trig::{sin8, cos8}, interpolate::scale8, noise::inoise8, IntoRgb8};
|
||||||
|
|
||||||
use super::{Coordinates, Rectangle, VirtualCoordinates};
|
#[derive(Debug)]
|
||||||
|
pub struct IdleTask<T: Surface> {
|
||||||
|
solid: T,
|
||||||
|
surface: T,
|
||||||
|
shimmer: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct SolidShader {}
|
||||||
|
|
||||||
|
impl Shader for SolidShader {
|
||||||
|
fn draw(&self, _coords: &VirtualCoordinates, frame: usize) -> RGB8 {
|
||||||
|
Hsv::new_srgb((frame % 255) as u8, 255, sin8((frame % 64) as u8)).into_rgb8()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct ShimmerShader {}
|
||||||
|
|
||||||
|
impl Shader for ShimmerShader {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct ThinkingShader {}
|
||||||
|
|
||||||
|
impl Shader for ThinkingShader {
|
||||||
|
fn draw(&self, coords: &VirtualCoordinates, frame: usize) -> RGB8 {
|
||||||
|
//let noise_x = sin8(sin8((frame % 255) as u8).wrapping_add(coords.x));
|
||||||
|
//let noise_y = cos8(cos8((frame % 255) as u8).wrapping_add(coords.y));
|
||||||
|
let offset_x = sin8(frame.wrapping_add(coords.x as usize));
|
||||||
|
let offset_y = cos8(frame.wrapping_add(coords.y as usize));
|
||||||
|
let noise_x = offset_x / 2;
|
||||||
|
let noise_y = offset_y / 2;
|
||||||
|
//let noise_x = coords.x.wrapping_add(offset_x);
|
||||||
|
//let noise_y = coords.y.wrapping_add(offset_y);
|
||||||
|
Hsv::new_srgb(
|
||||||
|
inoise8(offset_x as i16, offset_y as i16),
|
||||||
|
128_u8.saturating_add(inoise8(noise_y as i16, noise_x as i16)),
|
||||||
|
255
|
||||||
|
).into_rgb8()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Surface> IdleTask<T> {
|
||||||
|
pub fn new<S: Surfaces<Surface = T>>(surfaces: &mut S) -> Self {
|
||||||
|
IdleTask {
|
||||||
|
solid: surfaces.new_surface(&Rectangle::everything()).unwrap(),
|
||||||
|
surface: surfaces.new_surface(&Rectangle::everything()).unwrap(),
|
||||||
|
shimmer: surfaces.new_surface(&Rectangle::everything()).unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Surface> Task for IdleTask<T> {
|
||||||
|
fn start(&mut self) {
|
||||||
|
self.solid.set_shader(Box::new(SolidShader { }));
|
||||||
|
self.surface.set_shader(Box::new(ThinkingShader { }));
|
||||||
|
self.shimmer.set_shader(Box::new(ShimmerShader { }));
|
||||||
|
|
||||||
|
self.solid.set_opacity(64);
|
||||||
|
self.surface.set_opacity(128);
|
||||||
|
self.shimmer.set_opacity(64);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tick(&mut self, event: &Event, bus: &mut EventBus) {}
|
||||||
|
|
||||||
|
fn stop(&mut self) {
|
||||||
|
self.solid.clear_shader();
|
||||||
|
self.surface.clear_shader();
|
||||||
|
self.shimmer.clear_shader();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
enum TestShader {
|
enum TestShader {
|
||||||
@ -48,7 +128,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(coords.x, coords.y, 255).into_rgb8(),
|
Self::HSV => Hsv::new_srgb(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),
|
||||||
@ -88,27 +168,23 @@ impl<T: Surface> TestPattern<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//const Animations: Namespace = Namespace("animations");
|
|
||||||
|
|
||||||
impl<T: Surface> Task for TestPattern<T> {
|
impl<T: Surface> Task for TestPattern<T> {
|
||||||
fn name(&self) -> &'static str { "TestPattern" }
|
fn start(&mut self) {
|
||||||
|
self.surface.set_shader(Box::new(self.pattern.clone()));
|
||||||
fn start(&mut self, _bus: &mut EventBus) {
|
|
||||||
self.surface.set_shader(self.pattern);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_tick(&mut self, bus: &mut EventBus) {
|
fn tick(&mut self, event: &Event, bus: &mut EventBus) {
|
||||||
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);
|
||||||
self.frame = 0;
|
self.frame = 0;
|
||||||
self.surface.set_shader(self.pattern);
|
self.surface.set_shader(Box::new(self.pattern.clone()));
|
||||||
//bus.push(Animations.new_property_change( "test.pattern", format!("{:?}", self.pattern)));
|
|
||||||
});
|
});
|
||||||
self.stepper.run(|| {
|
self.stepper.run(|| {
|
||||||
self.frame = self.frame.wrapping_add(1);
|
self.frame = self.frame.wrapping_add(1);
|
||||||
self.surface.set_opacity(sin8(self.frame));
|
self.surface.set_opacity(sin8(self.frame));
|
||||||
self.surface.set_rect( match self.pattern {
|
//log::info!("Step {}", self.frame);
|
||||||
|
self.surface.set_rect( &match self.pattern {
|
||||||
TestShader::SweepX => Rectangle::new(
|
TestShader::SweepX => Rectangle::new(
|
||||||
Coordinates::new(self.frame, 0),
|
Coordinates::new(self.frame, 0),
|
||||||
Coordinates::new(self.frame, 255)
|
Coordinates::new(self.frame, 255)
|
||||||
@ -122,7 +198,7 @@ impl<T: Surface> Task for TestPattern<T> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stop(&mut self, _bus: &mut EventBus) {
|
fn stop(&mut self) {
|
||||||
self.surface.clear_shader();
|
self.surface.clear_shader();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,86 +0,0 @@
|
|||||||
pub mod test;
|
|
||||||
|
|
||||||
use rgb::RGB8;
|
|
||||||
use crate::lib8::Hsv;
|
|
||||||
|
|
||||||
use crate::events::EventBus;
|
|
||||||
use crate::geometry::*;
|
|
||||||
use crate::render::{Shader, Surface, Surfaces};
|
|
||||||
use crate::task::Task;
|
|
||||||
use crate::lib8::{trig::{sin8, cos8}, noise::inoise8, IntoRgb8};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct IdleTask<T: Surface> {
|
|
||||||
solid: T,
|
|
||||||
surface: T,
|
|
||||||
shimmer: T,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct SolidShader {}
|
|
||||||
|
|
||||||
impl Shader for SolidShader {
|
|
||||||
fn draw(&self, _coords: &VirtualCoordinates, frame: usize) -> RGB8 {
|
|
||||||
Hsv::new((frame % 255) as u8, 255, sin8((frame % 64) as u8)).into_rgb8()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct ShimmerShader {}
|
|
||||||
|
|
||||||
impl Shader for ShimmerShader {
|
|
||||||
fn draw(&self, coords: &VirtualCoordinates, frame: usize) -> RGB8 {
|
|
||||||
Hsv::new(((coords.x as usize).wrapping_add(frame / 3) % 255) as u8, coords.y, sin8(frame).max(75).min(64)).into_rgb8()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct ThinkingShader {}
|
|
||||||
|
|
||||||
impl Shader for ThinkingShader {
|
|
||||||
fn draw(&self, coords: &VirtualCoordinates, frame: usize) -> RGB8 {
|
|
||||||
//let noise_x = sin8(sin8((frame % 255) as u8).wrapping_add(coords.x));
|
|
||||||
//let noise_y = cos8(cos8((frame % 255) as u8).wrapping_add(coords.y));
|
|
||||||
let offset_x = sin8(frame.wrapping_add(coords.x as usize));
|
|
||||||
let offset_y = cos8(frame.wrapping_add(coords.y as usize));
|
|
||||||
let noise_x = offset_x / 2;
|
|
||||||
let noise_y = offset_y / 2;
|
|
||||||
//let noise_x = coords.x.wrapping_add(offset_x);
|
|
||||||
//let noise_y = coords.y.wrapping_add(offset_y);
|
|
||||||
Hsv::new(
|
|
||||||
inoise8(offset_x as i16, offset_y as i16),
|
|
||||||
128_u8.saturating_add(inoise8(noise_y as i16, noise_x as i16)),
|
|
||||||
255
|
|
||||||
).into_rgb8()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Surface> IdleTask<T> {
|
|
||||||
pub fn new<S: Surfaces<Surface = T>>(surfaces: &mut S) -> Self {
|
|
||||||
IdleTask {
|
|
||||||
solid: surfaces.new_surface(Rectangle::everything()).unwrap(),
|
|
||||||
surface: surfaces.new_surface(Rectangle::everything()).unwrap(),
|
|
||||||
shimmer: surfaces.new_surface(Rectangle::everything()).unwrap(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Surface> Task for IdleTask<T> {
|
|
||||||
fn name(&self) -> &'static str { "Idle" }
|
|
||||||
|
|
||||||
fn start(&mut self, _bus: &mut EventBus) {
|
|
||||||
self.solid.set_shader(SolidShader {});
|
|
||||||
self.surface.set_shader(ThinkingShader { });
|
|
||||||
self.shimmer.set_shader(ShimmerShader { });
|
|
||||||
|
|
||||||
self.solid.set_opacity(64);
|
|
||||||
self.surface.set_opacity(128);
|
|
||||||
self.shimmer.set_opacity(64);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stop(&mut self, _bus: &mut EventBus) {
|
|
||||||
self.solid.clear_shader();
|
|
||||||
self.surface.clear_shader();
|
|
||||||
self.shimmer.clear_shader();
|
|
||||||
}
|
|
||||||
}
|
|
174
src/buffers.rs
174
src/buffers.rs
@ -1,20 +1,26 @@
|
|||||||
|
use crate::events::{Event, EventBus};
|
||||||
use crate::geometry::*;
|
use crate::geometry::*;
|
||||||
use crate::lib8::interpolate::Fract8Ops;
|
use crate::lib8::interpolate::Fract8Ops;
|
||||||
use crate::power::AsMilliwatts;
|
use crate::power::AsMilliwatts;
|
||||||
use crate::render::{PixelView, Sample, Shader, Surface, Surfaces, HardwarePixel};
|
use crate::render::{PixelView, Sample, Shader, Surface, Surfaces, HardwarePixel};
|
||||||
|
use crate::task::Task;
|
||||||
|
|
||||||
use std::cell::RefCell;
|
use std::fmt::Debug;
|
||||||
|
use std::io;
|
||||||
use std::ops::IndexMut;
|
use std::ops::IndexMut;
|
||||||
|
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
|
use std::sync::RwLock;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
struct ShaderBinding {
|
struct ShaderBinding {
|
||||||
shader: Option<Box<dyn Shader>>,
|
shader: Option<Box<dyn Shader>>,
|
||||||
rect: Rectangle<Virtual>,
|
rect: Rectangle<Virtual>,
|
||||||
opacity: u8
|
opacity: u8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
struct SurfaceUpdate {
|
struct SurfaceUpdate {
|
||||||
shader: Option<Option<Box<dyn Shader>>>,
|
shader: Option<Option<Box<dyn Shader>>>,
|
||||||
rect: Option<Rectangle<Virtual>>,
|
rect: Option<Rectangle<Virtual>>,
|
||||||
@ -36,6 +42,12 @@ impl SurfaceUpdate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Debug for Box<dyn Shader> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("Shader").finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for SurfaceUpdate {
|
impl Default for SurfaceUpdate {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
SurfaceUpdate {
|
SurfaceUpdate {
|
||||||
@ -47,6 +59,7 @@ impl Default for SurfaceUpdate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct BufferedSurface {
|
pub struct BufferedSurface {
|
||||||
updater: Arc<UpdateQueue>,
|
updater: Arc<UpdateQueue>,
|
||||||
slot: usize
|
slot: usize
|
||||||
@ -69,23 +82,24 @@ impl Surface for BufferedSurface {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_rect(&mut self, rect: Rectangle<Virtual>) {
|
fn set_rect(&mut self, rect: &Rectangle<Virtual>) {
|
||||||
self.updater.push(SurfaceUpdate {
|
self.updater.push(SurfaceUpdate {
|
||||||
rect: Some(rect),
|
rect: Some(rect.clone()),
|
||||||
slot: self.slot,
|
slot: self.slot,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_shader<T: Shader + 'static>(&mut self, shader: T) {
|
fn set_shader(&mut self, shader: Box<dyn Shader>) {
|
||||||
self.updater.push(SurfaceUpdate {
|
self.updater.push(SurfaceUpdate {
|
||||||
shader: Some(Some(Box::new(shader))),
|
shader: Some(Some(shader)),
|
||||||
slot: self.slot,
|
slot: self.slot,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
struct UpdateQueue {
|
struct UpdateQueue {
|
||||||
pending: Mutex<Vec<SurfaceUpdate>>,
|
pending: Mutex<Vec<SurfaceUpdate>>,
|
||||||
damaged: AtomicBool
|
damaged: AtomicBool
|
||||||
@ -122,6 +136,7 @@ impl UpdateQueue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct ShaderChain {
|
pub struct ShaderChain {
|
||||||
bindings: Vec<ShaderBinding>,
|
bindings: Vec<ShaderBinding>,
|
||||||
updates: Arc<UpdateQueue>
|
updates: Arc<UpdateQueue>
|
||||||
@ -140,33 +155,36 @@ impl ShaderChain {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn commit(&mut self) {
|
pub fn commit(&mut self) {
|
||||||
if self.is_dirty() {
|
let mut queue: Vec<SurfaceUpdate> = {
|
||||||
let mut queue: Vec<SurfaceUpdate> = {
|
let mut updates = self.updates.pending.lock().unwrap();
|
||||||
let mut updates = self.updates.pending.lock().unwrap();
|
std::mem::take(updates.as_mut())
|
||||||
std::mem::take(updates.as_mut())
|
};
|
||||||
};
|
for update in queue.iter_mut() {
|
||||||
for update in queue.iter_mut() {
|
let target_slot = &mut self.bindings[update.slot];
|
||||||
let target_slot = &mut self.bindings[update.slot];
|
if let Some(shader) = update.shader.take() {
|
||||||
if let Some(shader) = update.shader.take() {
|
target_slot.shader = shader;
|
||||||
target_slot.shader = shader;
|
}
|
||||||
}
|
if let Some(opacity) = update.opacity.take() {
|
||||||
if let Some(opacity) = update.opacity.take() {
|
target_slot.opacity = opacity;
|
||||||
target_slot.opacity = opacity;
|
}
|
||||||
}
|
if let Some(rect) = update.rect.take() {
|
||||||
if let Some(rect) = update.rect.take() {
|
target_slot.rect = rect;
|
||||||
target_slot.rect = rect;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
self.updates.damaged.store(false, std::sync::atomic::Ordering::Relaxed);
|
|
||||||
}
|
}
|
||||||
|
self.updates.damaged.store(false, std::sync::atomic::Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn new_surface(&mut self, area: Rectangle<Virtual>) -> Result<BufferedSurface, ()> {
|
impl Surfaces for ShaderChain {
|
||||||
|
type Error = ();
|
||||||
|
type Surface = BufferedSurface;
|
||||||
|
|
||||||
|
fn new_surface(&mut self, area: &Rectangle<Virtual>) -> Result<Self::Surface, Self::Error> {
|
||||||
let next_slot = self.bindings.len();
|
let next_slot = self.bindings.len();
|
||||||
self.bindings.push(ShaderBinding {
|
self.bindings.push(ShaderBinding {
|
||||||
opacity: 255,
|
opacity: 255,
|
||||||
shader: None,
|
shader: None,
|
||||||
rect: area
|
rect: area.clone()
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(BufferedSurface {
|
Ok(BufferedSurface {
|
||||||
@ -176,12 +194,12 @@ impl ShaderChain {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn render_to<S: Sample>(&self, output: &mut S, frame: usize) {
|
fn render_to<S: Sample>(&self, output: &mut S, frame: usize) {
|
||||||
for surface in &self.bindings {
|
for surface in self.bindings.iter() {
|
||||||
let opacity = surface.opacity;
|
let opacity = surface.opacity;
|
||||||
if opacity > 0 {
|
if opacity > 0 {
|
||||||
|
let rect = surface.rect;
|
||||||
|
let mut sample = output.sample(&rect);
|
||||||
if let Some(ref shader) = surface.shader {
|
if let Some(ref shader) = surface.shader {
|
||||||
let rect = surface.rect;
|
|
||||||
let mut sample = output.sample(&rect);
|
|
||||||
while let Some((virt_coords, pixel)) = sample.next() {
|
while let Some((virt_coords, pixel)) = sample.next() {
|
||||||
*pixel = pixel.blend8(shader.draw(&virt_coords, frame).into(), opacity);
|
*pixel = pixel.blend8(shader.draw(&virt_coords, frame).into(), opacity);
|
||||||
}
|
}
|
||||||
@ -191,29 +209,115 @@ impl ShaderChain {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct BufferedSurfacePool {
|
pub struct BufferedSurfacePool {
|
||||||
pool: RefCell<ShaderChain>
|
pool: Arc<RwLock<ShaderChain>>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BufferedSurfacePool {
|
impl BufferedSurfacePool {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
BufferedSurfacePool {
|
BufferedSurfacePool {
|
||||||
pool: RefCell::new(ShaderChain::new())
|
pool: Arc::new(RwLock::new(ShaderChain::new()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Surfaces for BufferedSurfacePool {
|
impl Surfaces for BufferedSurfacePool {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
type Surface = BufferedSurface;
|
type Surface = <ShaderChain as Surfaces>::Surface;
|
||||||
fn new_surface(&mut self, area: crate::geometry::Rectangle<crate::geometry::Virtual>) -> Result<Self::Surface, Self::Error> {
|
fn new_surface(&mut self, area: &crate::geometry::Rectangle<crate::geometry::Virtual>) -> Result<Self::Surface, Self::Error> {
|
||||||
self.pool.borrow_mut().new_surface(area)
|
self.pool.write().unwrap().new_surface(area)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_to<S: crate::render::Sample>(&self, output: &mut S, frame: usize) {
|
fn render_to<S: crate::render::Sample>(&self, output: &mut S, frame: usize) {
|
||||||
let mut b = self.pool.borrow_mut();
|
self.pool.read().unwrap().render_to(output, frame);
|
||||||
b.commit();
|
}
|
||||||
b.render_to(output, frame);
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl Task for BufferedSurfacePool {
|
||||||
|
fn tick(&mut self, event: &Event, bus: &mut EventBus) {
|
||||||
|
if self.pool.read().unwrap().is_dirty() {
|
||||||
|
self.pool.write().unwrap().commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SharedSurface {
|
||||||
|
binding: Arc<Mutex<ShaderBinding>>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl Default for SharedSurface {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
binding: Arc::new(Mutex::new(ShaderBinding {
|
||||||
|
shader: None,
|
||||||
|
rect: Rectangle::everything(),
|
||||||
|
opacity: 255
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Surface for SharedSurface {
|
||||||
|
fn set_shader(&mut self, shader: Box<dyn Shader>) {
|
||||||
|
self.binding.lock().unwrap().shader = Some(shader);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_shader(&mut self) {
|
||||||
|
self.binding.lock().unwrap().shader = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_rect(&mut self, rect: &Rectangle<Virtual>) {
|
||||||
|
self.binding.lock().unwrap().rect = rect.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_opacity(&mut self, opacity: u8) {
|
||||||
|
self.binding.lock().unwrap().opacity = opacity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SurfacePool {
|
||||||
|
surfaces: Vec<SharedSurface>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SurfacePool {
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
surfaces: Vec::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Surfaces for SurfacePool {
|
||||||
|
type Surface = SharedSurface;
|
||||||
|
type Error = io::Error;
|
||||||
|
fn new_surface(&mut self, area: &Rectangle<Virtual>) -> Result<Self::Surface, Self::Error> {
|
||||||
|
let mut surface = SharedSurface::default();
|
||||||
|
surface.set_rect(area);
|
||||||
|
self.surfaces.push(surface.clone());
|
||||||
|
return Ok(surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_to<Sampler: Sample>(&self, output: &mut Sampler, frame: usize) {
|
||||||
|
for surface in self.surfaces.iter() {
|
||||||
|
let binding = surface.binding.lock().unwrap();
|
||||||
|
let opacity = binding.opacity;
|
||||||
|
if opacity > 0 {
|
||||||
|
let rect = binding.rect;
|
||||||
|
let mut sample = output.sample(&rect);
|
||||||
|
|
||||||
|
if let Some(ref shader) = binding.shader {
|
||||||
|
while let Some((virt_coords, pixel)) = sample.next() {
|
||||||
|
*pixel = pixel.blend8(shader.draw(&virt_coords, frame).into(), opacity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
177
src/events.rs
177
src/events.rs
@ -1,98 +1,163 @@
|
|||||||
use core::fmt::Debug;
|
use core::fmt::Debug;
|
||||||
use std::{collections::VecDeque, fmt::{Display, Result}, sync::{Arc, Mutex}};
|
|
||||||
|
|
||||||
use crate::{properties::{Properties, PropertyID, Variant}, property_namespace};
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum InputEvent {
|
||||||
|
PowerOn,
|
||||||
|
PowerOff,
|
||||||
|
NetworkActivity,
|
||||||
|
NetworkOnline,
|
||||||
|
NetworkOffline
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub enum Variant {
|
||||||
|
Byte(u8),
|
||||||
|
UInt(u32),
|
||||||
|
Int(i32),
|
||||||
|
BigUInt(u64),
|
||||||
|
BigInt(i64),
|
||||||
|
Boolean(bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
ReadyToRock,
|
ReadyToRock,
|
||||||
Tick,
|
Tick,
|
||||||
StartThing(&'static str),
|
StartThing(&'static str),
|
||||||
StopThing(&'static str),
|
StopThing(&'static str),
|
||||||
PropertyChange(PropertyID, Variant)
|
Input(InputEvent),
|
||||||
|
PropertyChange(&'static str, Variant)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Event {
|
pub struct SystemState {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result {
|
key: &'static str,
|
||||||
match self {
|
value: Variant,
|
||||||
Self::ReadyToRock => f.write_str("ReadyToRock"),
|
values: Vec::<Box<SystemState>>
|
||||||
Self::Tick => f.write_str("Tick"),
|
}
|
||||||
Self::StartThing(name) => write!(f, "Start {}", *name),
|
|
||||||
Self::StopThing(name) => write!(f, "Stop {}", *name),
|
impl SystemState {
|
||||||
Self::PropertyChange(id, value) => write!(f, "{} -> {:?}", id, value)
|
pub fn new() -> Self {
|
||||||
|
SystemState {
|
||||||
|
key: "",
|
||||||
|
value: Variant::Byte(0),
|
||||||
|
values: Vec::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_key(&self, key: &'static str) -> Option<&Self> {
|
||||||
|
if key == self.key {
|
||||||
|
Some(self)
|
||||||
|
} else {
|
||||||
|
for next in self.values.iter() {
|
||||||
|
match next.get_key(key) {
|
||||||
|
None => (),
|
||||||
|
Some(next_val) => return Some(next_val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_key_mut(&mut self, key: &'static str) -> Option<&mut Self> {
|
||||||
|
if key == self.key {
|
||||||
|
Some(self)
|
||||||
|
} else {
|
||||||
|
for next in self.values.iter_mut() {
|
||||||
|
match next.get_key_mut(key) {
|
||||||
|
None => (),
|
||||||
|
Some(next_val) => return Some(next_val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, key: &'static str) -> Option<Variant> {
|
||||||
|
match self.get_key(key) {
|
||||||
|
None => None,
|
||||||
|
Some(v) => Some(v.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set<V>(&mut self, key: &'static str, value: V) where Variant: From<V> {
|
||||||
|
match self.get_key_mut(key) {
|
||||||
|
None => self.values.push(Box::new(SystemState {
|
||||||
|
value: value.into(),
|
||||||
|
key: key,
|
||||||
|
values: Vec::new()
|
||||||
|
})),
|
||||||
|
Some(found_key) => {
|
||||||
|
found_key.value = value.into()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Event {
|
impl Event {
|
||||||
pub const fn new_tick() -> Self {
|
pub fn new_tick() -> Self {
|
||||||
Event::Tick
|
Event::Tick
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_property_change<T>(key: PropertyID, data: T) -> Self where Variant: From<T> {
|
pub fn new_property_change<T>(key: &'static str, data: T) -> Self where Variant: From<T> {
|
||||||
Event::PropertyChange(key, Variant::from(data))
|
Event::PropertyChange(key, Variant::from(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn new_ready_to_rock() -> Self {
|
pub fn new_ready_to_rock() -> Self {
|
||||||
Event::ReadyToRock
|
Event::ReadyToRock
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn new_start_thing(name: &'static str) -> Self {
|
pub fn new_input_event(event: InputEvent) -> Self {
|
||||||
Event::StartThing(name)
|
Event::Input(event)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
pub const fn new_stop_thing(name: &'static str) -> Self {
|
|
||||||
Event::StopThing(name)
|
impl Into<u8> for Variant {
|
||||||
|
fn into(self) -> u8 {
|
||||||
|
match self {
|
||||||
|
Variant::Byte(b) => b,
|
||||||
|
_ => 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<bool> for Variant {
|
||||||
|
fn from(value: bool) -> Self {
|
||||||
|
Variant::Boolean(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<i64> for Variant {
|
||||||
|
fn from(value: i64) -> Self {
|
||||||
|
Variant::BigInt(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u8> for Variant {
|
||||||
|
fn from(value: u8) -> Self {
|
||||||
|
Variant::Byte(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct EventBus {
|
pub struct EventBus {
|
||||||
pending: Arc<Mutex<VecDeque<Event>>>,
|
pending: Vec<Event>
|
||||||
props: Arc<Mutex<Properties>>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventBus {
|
impl EventBus {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
EventBus {
|
EventBus {
|
||||||
pending: Arc::new(Mutex::new(VecDeque::with_capacity(32))),
|
pending: Vec::new()
|
||||||
props: Arc::new(Mutex::new(Properties::new()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn next(&mut self) -> Event {
|
pub fn next(&mut self) -> Event {
|
||||||
let next = self.pending.lock().unwrap().pop_front().unwrap_or(Event::new_tick());
|
if self.pending.len() == 0 {
|
||||||
|
Event::new_tick()
|
||||||
match next {
|
} else {
|
||||||
Event::PropertyChange(key, value) => {
|
self.pending.pop().unwrap()
|
||||||
if self.props.lock().unwrap().set(key, value.clone()) {
|
|
||||||
log::trace!("prop-update key={:?} value={:?}", key, value);
|
|
||||||
Event::PropertyChange(key, value)
|
|
||||||
} else {
|
|
||||||
Event::new_tick()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => next
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push(&mut self, event: Event) {
|
pub fn push(&mut self, event: Event) {
|
||||||
self.pending.lock().unwrap().push_back(event);
|
self.pending.push(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!(
|
|
||||||
System,
|
|
||||||
NetworkOnline => false,
|
|
||||||
IP => "",
|
|
||||||
Gateway => "",
|
|
||||||
TimeSync => false
|
|
||||||
);
|
|
@ -64,19 +64,19 @@ impl<S: CoordinateSpace> Coordinates<S> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const fn top_left() -> Self {
|
fn top_left() -> Self {
|
||||||
Self::new(S::Data::MIN, S::Data::MIN)
|
Self::new(S::Data::MIN, S::Data::MIN)
|
||||||
}
|
}
|
||||||
|
|
||||||
const fn top_right() -> Self {
|
fn top_right() -> Self {
|
||||||
Self::new(S::Data::MAX, S::Data::MIN)
|
Self::new(S::Data::MAX, S::Data::MIN)
|
||||||
}
|
}
|
||||||
|
|
||||||
const fn bottom_left() -> Self {
|
fn bottom_left() -> Self {
|
||||||
Self::new(S::Data::MIN, S::Data::MAX)
|
Self::new(S::Data::MIN, S::Data::MAX)
|
||||||
}
|
}
|
||||||
|
|
||||||
const fn bottom_right() -> Self {
|
fn bottom_right() -> Self {
|
||||||
Self::new(S::Data::MAX, S::Data::MAX)
|
Self::new(S::Data::MAX, S::Data::MAX)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,13 +101,15 @@ pub struct Rectangle<Space: CoordinateSpace> {
|
|||||||
|
|
||||||
impl<Space: CoordinateSpace> Rectangle<Space> {
|
impl<Space: CoordinateSpace> Rectangle<Space> {
|
||||||
pub const fn new(top_left: Coordinates<Space>, bottom_right: Coordinates<Space>) -> Self {
|
pub const fn new(top_left: Coordinates<Space>, bottom_right: Coordinates<Space>) -> Self {
|
||||||
|
//debug_assert!(top_left.x <= bottom_right.x);
|
||||||
|
//debug_assert!(top_left.y <= bottom_right.y);
|
||||||
Self {
|
Self {
|
||||||
top_left,
|
top_left,
|
||||||
bottom_right
|
bottom_right
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn everything() -> Self {
|
pub fn everything() -> Self {
|
||||||
Self {
|
Self {
|
||||||
top_left: Coordinates::<Space>::top_left(),
|
top_left: Coordinates::<Space>::top_left(),
|
||||||
bottom_right: Coordinates::<Space>::bottom_right()
|
bottom_right: Coordinates::<Space>::bottom_right()
|
||||||
|
@ -1,103 +0,0 @@
|
|||||||
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::System as SystemNS;
|
|
||||||
use paste::paste;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
struct ScheduleEntry {
|
|
||||||
hour: u8,
|
|
||||||
brightness: u8
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct CircadianRhythm {
|
|
||||||
time_check: Periodically,
|
|
||||||
schedule: [ScheduleEntry;10]
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CircadianRhythm {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
CircadianRhythm {
|
|
||||||
time_check: Periodically::new_every_n_seconds(60),
|
|
||||||
schedule: [
|
|
||||||
ScheduleEntry { hour: 0, brightness: 0 },
|
|
||||||
ScheduleEntry { hour: 5, brightness: 0 },
|
|
||||||
ScheduleEntry { hour: 6, brightness: 10 },
|
|
||||||
ScheduleEntry { hour: 7, brightness: 20 },
|
|
||||||
ScheduleEntry { hour: 8, brightness: 80 },
|
|
||||||
ScheduleEntry { hour: 11, brightness: 120 },
|
|
||||||
ScheduleEntry { hour: 18, brightness: 200 },
|
|
||||||
ScheduleEntry { hour: 19, brightness: 255 },
|
|
||||||
ScheduleEntry { hour: 22, brightness: 120 },
|
|
||||||
ScheduleEntry { hour: 23, brightness: 5 }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_brightness(&self, bus: &mut EventBus) {
|
|
||||||
let now: DateTime<Utc> = std::time::SystemTime::now().into();
|
|
||||||
let next_brightness = self.brightness_for_time(now.hour() as u8, now.minute() as u8);
|
|
||||||
bus.set_property(OutputNS::Brightness, next_brightness);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn brightness_for_time(&self, hour: u8, minute: u8) -> u8 {
|
|
||||||
let mut start = self.schedule.last().unwrap();
|
|
||||||
let mut end = self.schedule.first().unwrap();
|
|
||||||
for cur in self.schedule.iter() {
|
|
||||||
if cur.hour <= hour {
|
|
||||||
start = cur;
|
|
||||||
} else {
|
|
||||||
end = cur;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log::info!("hour={:?} minute={:?} start={:?} end={:?}", hour, minute, start, end);
|
|
||||||
|
|
||||||
let mut adjusted_end = end.clone();
|
|
||||||
if start.hour > end.hour {
|
|
||||||
adjusted_end.hour += 24;
|
|
||||||
}
|
|
||||||
|
|
||||||
let start_time = (start.hour as u16).wrapping_mul(60);
|
|
||||||
let end_time = (end.hour as u16).wrapping_mul(60);
|
|
||||||
let now_time = (hour as u16).wrapping_mul(60).wrapping_add(minute as u16);
|
|
||||||
|
|
||||||
let duration = end_time - start_time;
|
|
||||||
let cur_duration = now_time - start_time;
|
|
||||||
|
|
||||||
let frac = map_range(cur_duration.into(), 0, duration.into(), 0, 255) as u8;
|
|
||||||
|
|
||||||
lerp8by8(start.brightness, end.brightness, frac)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn map_range(x: u16, in_min: u16, in_max: u16, out_min: u16, out_max: u16) -> u16 {
|
|
||||||
let run = in_max - in_min;
|
|
||||||
if run == 0 {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
let rise = out_max - out_min;
|
|
||||||
let delta = x - in_min;
|
|
||||||
return (delta.wrapping_mul(rise)) / run + out_min;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl Task for CircadianRhythm {
|
|
||||||
fn on_ready(&mut self, bus: &mut EventBus) {
|
|
||||||
self.update_brightness(bus);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_property_change(&mut self, key: PropertyID, value: &Variant, bus: &mut EventBus) {
|
|
||||||
match (key, value) {
|
|
||||||
(prop_id!(SystemNS::TimeSync), Variant::Boolean(true)) => self.update_brightness(bus),
|
|
||||||
_ => ()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_tick(&mut self, bus: &mut EventBus) {
|
|
||||||
if self.time_check.tick() {
|
|
||||||
self.update_brightness(bus);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
pub mod circadian;
|
|
@ -12,21 +12,12 @@ pub trait Fract8Ops {
|
|||||||
|
|
||||||
impl Fract8Ops for u8 {
|
impl Fract8Ops for u8 {
|
||||||
fn scale8(self, scale: Fract8) -> Self {
|
fn scale8(self, scale: Fract8) -> Self {
|
||||||
match scale {
|
// borrowed from FastLED
|
||||||
0 => 0,
|
(self as u16 * (1 + scale as u16)).unsigned_shr(8) as u8
|
||||||
255 => self,
|
|
||||||
_ =>
|
|
||||||
// borrowed from FastLED
|
|
||||||
(self as u16 * (1 + scale as u16)).unsigned_shr(8) as u8
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn blend8(self, other: Self, scale: Fract8) -> Self {
|
fn blend8(self, other: Self, scale: Fract8) -> Self {
|
||||||
match scale {
|
((((self as u16).unsigned_shl(8).bitor(other as u16)) as u16).wrapping_add(other as u16 * scale as u16).wrapping_sub(self as u16 * scale as u16)).unsigned_shr(8) as u8
|
||||||
0 => self,
|
|
||||||
255 => other,
|
|
||||||
_ => ((((self as u16).unsigned_shl(8).bitor(other as u16)) as u16).wrapping_add(other as u16 * scale as u16).wrapping_sub(self as u16 * scale as u16)).unsigned_shr(8) as u8
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,17 +31,13 @@ impl Fract8Ops for Rgb<u8> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn blend8(self, other: Self, scale: Fract8) -> Self {
|
fn blend8(self, other: Self, scale: Fract8) -> Self {
|
||||||
match scale {
|
match (other.r, other.g, other.b) {
|
||||||
0 => self,
|
(0, 0, 0) => self,
|
||||||
255 => other,
|
_ => Rgb::new(
|
||||||
_ => match (other.r, other.g, other.b) {
|
self.r.blend8(other.r, scale),
|
||||||
(0, 0, 0) => self,
|
self.g.blend8(other.g, scale),
|
||||||
_ => Rgb::new(
|
self.b.blend8(other.b, scale)
|
||||||
self.r.blend8(other.r, scale),
|
)
|
||||||
self.g.blend8(other.g, scale),
|
|
||||||
self.b.blend8(other.b, scale)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@ 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;
|
||||||
@ -16,23 +18,7 @@ impl IntoRgb8 for Rgb<u8> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Hsv {
|
impl IntoRgb8 for palette::Hsv<Srgb, u8> {
|
||||||
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;
|
||||||
@ -41,7 +27,7 @@ impl IntoRgb8 for Hsv {
|
|||||||
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);
|
let mock_hue = scale8(191, self.hue.into_inner());
|
||||||
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;
|
||||||
@ -66,4 +52,4 @@ impl IntoRgb8 for Hsv {
|
|||||||
_ => Rgb::new(rampup_adj_with_floor, brightness_floor, rampdown_adj_with_floor)
|
_ => Rgb::new(rampup_adj_with_floor, brightness_floor, rampdown_adj_with_floor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
70
src/main.rs
70
src/main.rs
@ -8,98 +8,52 @@ mod platform;
|
|||||||
mod animations;
|
mod animations;
|
||||||
mod mappings;
|
mod mappings;
|
||||||
mod buffers;
|
mod buffers;
|
||||||
mod scenes;
|
|
||||||
mod inputs;
|
|
||||||
mod events;
|
mod events;
|
||||||
mod properties;
|
|
||||||
|
|
||||||
use events::*;
|
use events::Event;
|
||||||
use inputs::circadian::CircadianRhythm;
|
|
||||||
use platform::esp32::mqtt::props::MQTT;
|
|
||||||
use render::props::Output;
|
|
||||||
use scenes::Sequencer;
|
|
||||||
|
|
||||||
use crate::events::EventBus;
|
use crate::events::EventBus;
|
||||||
use crate::platform::{DefaultBoard, Board, props::Board as BoardNS};
|
use crate::platform::{DefaultBoard, Board};
|
||||||
use crate::task::{FixedSizeScheduler, Scheduler};
|
use crate::task::{FixedSizeScheduler, Scheduler};
|
||||||
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;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut board: DefaultBoard = Board::take();
|
let mut board: DefaultBoard = Board::take();
|
||||||
|
|
||||||
log::info!("🐛 Booting Renderbug!");
|
log::info!("Board: {}", core::any::type_name_of_val(&board));
|
||||||
|
|
||||||
log::info!("📡 Board {}", core::any::type_name_of_val(&board));
|
log::info!("Creating tasks");
|
||||||
|
|
||||||
log::info!("⚙️ Creating tasks");
|
|
||||||
let mut system = board.system_tasks();
|
let mut system = board.system_tasks();
|
||||||
log::info!("⏰ System scheduler: {}", core::any::type_name_of_val(&system));
|
log::info!("System scheduler: {}", core::any::type_name_of_val(&system));
|
||||||
|
|
||||||
log::info!("💡 Creating output");
|
log::info!("Creating output");
|
||||||
let output = board.output();
|
let output = board.output();
|
||||||
log::info!("Output: {}", core::any::type_name_of_val(&output));
|
log::info!("Output: {}", core::any::type_name_of_val(&output));
|
||||||
|
|
||||||
log::info!("🎨 Preparing surfaces");
|
log::info!("Preparing surfaces");
|
||||||
let mut surfaces = board.surfaces();
|
let mut surfaces = board.surfaces();
|
||||||
log::info!("Surface implementation: {}", core::any::type_name_of_val(&output));
|
log::info!("Surface implementation: {}", core::any::type_name_of_val(&output));
|
||||||
|
|
||||||
log::info!("🌌 Creating animations");
|
log::info!("Creating animations");
|
||||||
let mut animations = FixedSizeScheduler::new([
|
let mut animations = FixedSizeScheduler::new([
|
||||||
Box::new(animations::IdleTask::new(&mut surfaces)),
|
Box::new(animations::IdleTask::new(&mut surfaces)),
|
||||||
Box::new(animations::test::TestPattern::new(surfaces.new_surface(Rectangle::everything()).unwrap())),
|
//Box::new(animations::TestPattern::new(surfaces.new_surface(&Rectangle::everything()).unwrap())),
|
||||||
]);
|
|
||||||
|
|
||||||
let mut inputs = FixedSizeScheduler::new([
|
|
||||||
Box::new(CircadianRhythm::new()),
|
|
||||||
Box::new(Sequencer::new()),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let mut renderer = FixedSizeScheduler::new([Box::new(Renderer::new(output, surfaces))]);
|
let mut renderer = FixedSizeScheduler::new([Box::new(Renderer::new(output, surfaces))]);
|
||||||
|
|
||||||
log::info!("🚌 Starting event bus");
|
log::info!("Starting event bus");
|
||||||
let mut bus = EventBus::new();
|
let mut bus = EventBus::new();
|
||||||
|
|
||||||
{
|
log::info!("Ready to rock and roll");
|
||||||
let mut props = bus.properties();
|
|
||||||
props.add_namespace::<System>();
|
|
||||||
props.add_namespace::<BoardNS>();
|
|
||||||
props.add_namespace::<Output>();
|
|
||||||
props.add_namespace::<SceneNS>();
|
|
||||||
props.add_namespace::<MQTT>();
|
|
||||||
|
|
||||||
props.set(BoardNS::ChipID, DefaultBoard::chip_id());
|
|
||||||
|
|
||||||
log::info!("System properties:");
|
|
||||||
log::info!("{}", props);
|
|
||||||
}
|
|
||||||
|
|
||||||
log::info!("Priming events...");
|
|
||||||
let initial_tasks = [
|
|
||||||
"Renderer",
|
|
||||||
"renderbug::scenes::Sequencer",
|
|
||||||
"renderbug::platform::esp32::wifi::WifiTask",
|
|
||||||
"renderbug::platform::esp32::mqtt::MqttTask",
|
|
||||||
"renderbug::inputs::circadian::CircadianRhythm"
|
|
||||||
];
|
|
||||||
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());
|
bus.push(Event::new_ready_to_rock());
|
||||||
|
|
||||||
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!("Event: {:?}", next_event)
|
||||||
log::info!("🚀 Ready to rock and roll");
|
|
||||||
}
|
|
||||||
_ => log::info!("⚡ Event: {}", next_event)
|
|
||||||
}
|
}
|
||||||
inputs.tick(&next_event, &mut bus);
|
|
||||||
animations.tick(&next_event, &mut bus);
|
animations.tick(&next_event, &mut bus);
|
||||||
system.tick(&next_event, &mut bus);
|
system.tick(&next_event, &mut bus);
|
||||||
renderer.tick(&next_event, &mut bus);
|
renderer.tick(&next_event, &mut bus);
|
||||||
|
@ -20,7 +20,7 @@ pub trait Select<'a> {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct LinearCoordView {
|
pub struct LinearCoordView {
|
||||||
max_x: u8,
|
rect: Rectangle<Virtual>,
|
||||||
idx: usize,
|
idx: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,7 +34,7 @@ pub type LinearCoords = Coordinates<LinearSpace>;
|
|||||||
impl<'a> CoordinateView<'a> for LinearCoordView {
|
impl<'a> CoordinateView<'a> for LinearCoordView {
|
||||||
type Space = LinearSpace;
|
type Space = LinearSpace;
|
||||||
fn next(&mut self) -> Option<(VirtualCoordinates, LinearCoords)> {
|
fn next(&mut self) -> Option<(VirtualCoordinates, LinearCoords)> {
|
||||||
if self.idx as u8 == self.max_x {
|
if self.idx as u8 == self.rect.bottom_right.x {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
let virt = VirtualCoordinates::new(self.idx as u8, 0); // FIXME: scale8
|
let virt = VirtualCoordinates::new(self.idx as u8, 0); // FIXME: scale8
|
||||||
@ -62,7 +62,7 @@ impl<'a> Select<'a> for LinearPixelMapping {
|
|||||||
type View = LinearCoordView;
|
type View = LinearCoordView;
|
||||||
fn select(&'a self, rect: &Rectangle<Virtual>) -> Self::View {
|
fn select(&'a self, rect: &Rectangle<Virtual>) -> Self::View {
|
||||||
LinearCoordView {
|
LinearCoordView {
|
||||||
max_x: rect.bottom_right.x,
|
rect: rect.clone(),
|
||||||
idx: 0,
|
idx: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -107,24 +107,6 @@ impl<const STRIDE_NUM: usize> StrideMapping<STRIDE_NUM> {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_cyberplague() -> Self {
|
|
||||||
Self::from_json(&[
|
|
||||||
(0, 6, 6, false),
|
|
||||||
(1, 6, 6, true),
|
|
||||||
(2, 6, 6, false),
|
|
||||||
(3, 4, 9, true),
|
|
||||||
(4, 4, 14, false),
|
|
||||||
(5, 0, 17, true),
|
|
||||||
(6, 2, 12, false),
|
|
||||||
(7, 0, 18, true),
|
|
||||||
(8, 4, 14, false),
|
|
||||||
(9, 5, 9, true),
|
|
||||||
(10, 4, 7, false),
|
|
||||||
(11, 5, 6, true),
|
|
||||||
(12, 5, 6, false)
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_jar() -> Self {
|
pub fn new_jar() -> Self {
|
||||||
Self::from_json(&[
|
Self::from_json(&[
|
||||||
(0, 0, 17, false),
|
(0, 0, 17, false),
|
||||||
@ -169,19 +151,6 @@ impl<const STRIDE_NUM: usize> StrideMapping<STRIDE_NUM> {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_albus() -> Self {
|
|
||||||
Self::from_json(&[
|
|
||||||
(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),
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_json(stride_json: &[(u8, u8, u8, bool)]) -> Self {
|
pub fn from_json(stride_json: &[(u8, u8, u8, bool)]) -> Self {
|
||||||
let mut strides = [Stride::default(); STRIDE_NUM];
|
let mut strides = [Stride::default(); STRIDE_NUM];
|
||||||
let stride_count = stride_json.len();
|
let stride_count = stride_json.len();
|
||||||
@ -222,6 +191,8 @@ impl<const STRIDE_NUM: usize> StrideMapping<STRIDE_NUM> {
|
|||||||
let s = size.take().unwrap();
|
let s = size.take().unwrap();
|
||||||
log::info!("size={:?}", s);
|
log::info!("size={:?}", s);
|
||||||
|
|
||||||
|
log::info!("strides={:?}", strides);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
strides,
|
strides,
|
||||||
pixel_count: physical_idx,
|
pixel_count: physical_idx,
|
||||||
|
@ -98,7 +98,7 @@ impl<T: LedPixelShape, S: Surface> Display<S> for EmbeddedDisplay<Ws2812DrawTarg
|
|||||||
let mut pixel = RGB8::new(0, 0, 0);
|
let mut pixel = RGB8::new(0, 0, 0);
|
||||||
for surface in self.surfaces.iter() {
|
for surface in self.surfaces.iter() {
|
||||||
surface.with_shader(|shader| {
|
surface.with_shader(|shader| {
|
||||||
pixel = pixel.saturating_add(shader.draw(virtCoords));
|
pixel = pixel.saturating_add(shader.draw(virtCoords.clone()));
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
self.total_mw += pixel.as_milliwatts();
|
self.total_mw += pixel.as_milliwatts();
|
||||||
|
504
src/platform/esp32.rs
Normal file
504
src/platform/esp32.rs
Normal file
@ -0,0 +1,504 @@
|
|||||||
|
use core::borrow::BorrowMut;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
use std::thread::JoinHandle;
|
||||||
|
use std::thread::ScopedJoinHandle;
|
||||||
|
|
||||||
|
use chrono::DateTime;
|
||||||
|
use chrono::Timelike;
|
||||||
|
use chrono::Utc;
|
||||||
|
|
||||||
|
use esp_idf_svc::eventloop::{EspSubscription, EspSystemEventLoop, System};
|
||||||
|
use esp_idf_svc::hal::modem::Modem;
|
||||||
|
use esp_idf_svc::hal::prelude::Peripherals;
|
||||||
|
use esp_idf_svc::hal::task::thread::ThreadSpawnConfiguration;
|
||||||
|
use esp_idf_svc::mqtt::client::EspMqttClient;
|
||||||
|
use esp_idf_svc::mqtt::client::EspMqttConnection;
|
||||||
|
use esp_idf_svc::mqtt::client::MqttClientConfiguration;
|
||||||
|
use esp_idf_svc::netif::IpEvent;
|
||||||
|
use esp_idf_svc::nvs::{EspDefaultNvsPartition, EspNvsPartition, NvsDefault};
|
||||||
|
use esp_idf_svc::sntp::EspSntp;
|
||||||
|
use esp_idf_svc::sys::esp_efuse_mac_get_default;
|
||||||
|
use esp_idf_svc::wifi::{AuthMethod, ClientConfiguration, Configuration, EspWifi, WifiEvent};
|
||||||
|
use rgb::Rgb;
|
||||||
|
|
||||||
|
use super::smart_leds_lib::rmt::FastWs2812Esp32Rmt;
|
||||||
|
use super::smart_leds_lib::StrideOutput;
|
||||||
|
use super::Board;
|
||||||
|
|
||||||
|
use crate::buffers::BufferedSurfacePool;
|
||||||
|
use crate::buffers::Pixbuf;
|
||||||
|
use crate::events::Event;
|
||||||
|
use crate::events::EventBus;
|
||||||
|
use crate::lib8::interpolate::lerp8by8;
|
||||||
|
use crate::mappings::StrideMapping;
|
||||||
|
use crate::task::FixedSizeScheduler;
|
||||||
|
use crate::task::Task;
|
||||||
|
use crate::time::Periodically;
|
||||||
|
|
||||||
|
pub mod i2s {
|
||||||
|
use esp_idf_svc::hal::i2s::*;
|
||||||
|
use rgb::ComponentBytes;
|
||||||
|
use rgb::Rgb;
|
||||||
|
|
||||||
|
use crate::mappings::*;
|
||||||
|
use crate::buffers::Pixbuf;
|
||||||
|
use crate::render::Output;
|
||||||
|
use crate::render::Sample;
|
||||||
|
|
||||||
|
pub struct I2SOutput<'d> {
|
||||||
|
driver: I2sDriver<'d, I2sTx>,
|
||||||
|
pixbuf: [Rgb<u8>; 310],
|
||||||
|
pixmap: StrideMapping,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'d> I2SOutput<'d> {
|
||||||
|
fn new(driver: I2sDriver<'d, I2sTx>) -> Self {
|
||||||
|
I2SOutput {
|
||||||
|
driver,
|
||||||
|
pixbuf: Pixbuf::new(),
|
||||||
|
pixmap: StrideMapping::new_jar()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'d> Output for I2SOutput<'d> {
|
||||||
|
fn on_event(&mut self, event: &crate::events::Event) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fn blank(&mut self) {
|
||||||
|
self.pixbuf.blank();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn commit(&mut self) {
|
||||||
|
let bytes = self.pixbuf.as_bytes();
|
||||||
|
let mut written = self.driver.preload_data(bytes).unwrap();
|
||||||
|
self.driver.tx_enable().unwrap();
|
||||||
|
while written < bytes.len() {
|
||||||
|
let next = &bytes[written..];
|
||||||
|
written += self.driver.write(next, 0).unwrap();
|
||||||
|
}
|
||||||
|
self.driver.tx_disable().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'d> Sample for I2SOutput<'d> {
|
||||||
|
type Pixel = Rgb<u8>;
|
||||||
|
fn sample(&mut self, rect: &crate::geometry::Rectangle<crate::geometry::Virtual>) -> impl crate::render::PixelView<Pixel = Self::Pixel> {
|
||||||
|
StrideSampler::new(&mut self.pixbuf, self.pixmap.select(rect))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Esp32Board {
|
||||||
|
sys_loop: EspSystemEventLoop,
|
||||||
|
modem: Option<Modem>,
|
||||||
|
surfaces: BufferedSurfacePool,
|
||||||
|
output: Option<<Self as Board>::Output>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Board for Esp32Board {
|
||||||
|
type Output = StrideOutput<[Rgb<u8>; 310], FastWs2812Esp32Rmt<'static>>;
|
||||||
|
type Surfaces = BufferedSurfacePool;
|
||||||
|
type Scheduler = FixedSizeScheduler<4>;
|
||||||
|
|
||||||
|
fn take() -> Self {
|
||||||
|
// It is necessary to call this function once. Otherwise some patches to the runtime
|
||||||
|
// implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71
|
||||||
|
esp_idf_svc::sys::link_patches();
|
||||||
|
|
||||||
|
// Bind the log crate to the ESP Logging facilities
|
||||||
|
esp_idf_svc::log::EspLogger::initialize_default();
|
||||||
|
|
||||||
|
let peripherals = Peripherals::take().unwrap();
|
||||||
|
let sys_loop = EspSystemEventLoop::take().unwrap();
|
||||||
|
|
||||||
|
let mut chip_id: [u8; 8] = [0; 8];
|
||||||
|
unsafe {
|
||||||
|
esp_efuse_mac_get_default(&mut chip_id as *mut u8);
|
||||||
|
}
|
||||||
|
|
||||||
|
log::info!("Setting up output for chip ID {:x?}", chip_id);
|
||||||
|
|
||||||
|
const POWER_VOLTS : u32 = 5;
|
||||||
|
const POWER_MA : u32 = 500;
|
||||||
|
const MAX_POWER_MW : u32 = POWER_VOLTS * POWER_MA;
|
||||||
|
let pins = peripherals.pins;
|
||||||
|
|
||||||
|
ThreadSpawnConfiguration {
|
||||||
|
pin_to_core: Some(esp_idf_svc::hal::cpu::Core::Core1),
|
||||||
|
..Default::default()
|
||||||
|
}.set().unwrap();
|
||||||
|
// Wifi driver creates too many interrupts on core0, so we need to use RMT on core1.
|
||||||
|
// But the implementation spawns a thread based on the core the driver was created in,
|
||||||
|
// so we create the driver in another thread briefly.
|
||||||
|
// Fun stuff.
|
||||||
|
let output = match chip_id { // panel test board
|
||||||
|
[72, 202, 67, 89, 145, 204, 0, 0] => {
|
||||||
|
StrideOutput::new(
|
||||||
|
Pixbuf::new(),
|
||||||
|
StrideMapping::new_panel(),
|
||||||
|
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(peripherals.rmt.channel0, pins.gpio5).unwrap() }).join().unwrap(),
|
||||||
|
MAX_POWER_MW
|
||||||
|
)
|
||||||
|
},
|
||||||
|
[0x8C, 0xAA, 0xB5, 0x83, 0x5f, 0x74, 0x0, 0x0] => { //ponderjar
|
||||||
|
StrideOutput::new(
|
||||||
|
Pixbuf::new(),
|
||||||
|
StrideMapping::new_jar(),
|
||||||
|
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(peripherals.rmt.channel0, pins.gpio14).unwrap() }).join().unwrap(),
|
||||||
|
MAX_POWER_MW
|
||||||
|
)
|
||||||
|
},
|
||||||
|
[0x4a, 0xca, 0x43, 0x59, 0x85, 0x58, 0x0, 0x0] => { // Albus the tree
|
||||||
|
StrideOutput::new(
|
||||||
|
Pixbuf::new(),
|
||||||
|
StrideMapping::new_jar(),
|
||||||
|
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(peripherals.rmt.channel0, pins.gpio5).unwrap() }).join().unwrap(),
|
||||||
|
MAX_POWER_MW
|
||||||
|
)
|
||||||
|
},
|
||||||
|
[0x48, 0xca, 0x43, 0x59, 0x9d, 0x48, 0x0, 0x0] => { // kitchen cabinets
|
||||||
|
StrideOutput::new(
|
||||||
|
Pixbuf::new(),
|
||||||
|
StrideMapping::new_fairylights(),
|
||||||
|
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(peripherals.rmt.channel0, pins.gpio5).unwrap() }).join().unwrap(),
|
||||||
|
MAX_POWER_MW
|
||||||
|
)
|
||||||
|
},
|
||||||
|
[0x48, 0xca, 0x43, 0x59, 0x9e, 0xdc, 0x0, 0x0] => { // front window
|
||||||
|
StrideOutput::new(
|
||||||
|
Pixbuf::new(),
|
||||||
|
StrideMapping::new_fairylights(),
|
||||||
|
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(peripherals.rmt.channel0, pins.gpio5).unwrap() }).join().unwrap(),
|
||||||
|
MAX_POWER_MW
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
StrideOutput::new(
|
||||||
|
Pixbuf::new(),
|
||||||
|
StrideMapping::new(),
|
||||||
|
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(peripherals.rmt.channel0, pins.gpio5).unwrap() }).join().unwrap(),
|
||||||
|
MAX_POWER_MW
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ThreadSpawnConfiguration {
|
||||||
|
..Default::default()
|
||||||
|
}.set().unwrap();
|
||||||
|
|
||||||
|
Esp32Board {
|
||||||
|
modem: Some(peripherals.modem),
|
||||||
|
sys_loop: sys_loop.clone(),
|
||||||
|
surfaces: BufferedSurfacePool::new(),
|
||||||
|
output: Some(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output(&mut self) -> Self::Output {
|
||||||
|
self.output.take().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn surfaces(&mut self) -> Self::Surfaces {
|
||||||
|
self.surfaces.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn system_tasks(&mut self) -> Self::Scheduler {
|
||||||
|
let nvs = EspDefaultNvsPartition::take().unwrap();
|
||||||
|
FixedSizeScheduler::new([
|
||||||
|
Box::new(WifiTask::new(self.modem.take().unwrap(), self.sys_loop.clone(), &nvs)),
|
||||||
|
Box::new(CircadianRhythm::new()),
|
||||||
|
Box::new(MqttTask::new()),
|
||||||
|
Box::new(self.surfaces.clone())
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
struct ScheduleEntry {
|
||||||
|
hour: u8,
|
||||||
|
brightness: u8
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CircadianRhythm {
|
||||||
|
time_check: Periodically,
|
||||||
|
schedule: [ScheduleEntry;10]
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CircadianRhythm {
|
||||||
|
fn new() -> Self {
|
||||||
|
CircadianRhythm {
|
||||||
|
time_check: Periodically::new_every_n_seconds(5),
|
||||||
|
schedule: [
|
||||||
|
ScheduleEntry { hour: 0, brightness: 0 },
|
||||||
|
ScheduleEntry { hour: 5, brightness: 0 },
|
||||||
|
ScheduleEntry { hour: 6, brightness: 10 },
|
||||||
|
ScheduleEntry { hour: 7, brightness: 20 },
|
||||||
|
ScheduleEntry { hour: 8, brightness: 80 },
|
||||||
|
ScheduleEntry { hour: 11, brightness: 120 },
|
||||||
|
ScheduleEntry { hour: 18, brightness: 200 },
|
||||||
|
ScheduleEntry { hour: 19, brightness: 255 },
|
||||||
|
ScheduleEntry { hour: 22, brightness: 120 },
|
||||||
|
ScheduleEntry { hour: 23, brightness: 5 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn brightness_for_time(&self, hour: u8, minute: u8) -> u8 {
|
||||||
|
let mut start = self.schedule.last().unwrap();
|
||||||
|
let mut end = self.schedule.first().unwrap();
|
||||||
|
for cur in self.schedule.iter() {
|
||||||
|
if (cur.hour <= hour ) {
|
||||||
|
start = cur;
|
||||||
|
} else {
|
||||||
|
end = cur;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log::info!("hour={:?} minute={:?} start={:?} end={:?}", hour, minute, start, end);
|
||||||
|
|
||||||
|
let mut adjusted_end = end.clone();
|
||||||
|
if start.hour > end.hour {
|
||||||
|
adjusted_end.hour += 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
let start_time = start.hour * 60;
|
||||||
|
let end_time = end.hour * 60;
|
||||||
|
let now_time = hour * 60 + minute;
|
||||||
|
|
||||||
|
let duration = end_time - start_time;
|
||||||
|
let cur_duration = now_time - start_time;
|
||||||
|
|
||||||
|
let frac = map_range(cur_duration.into(), 0, duration.into(), 0, 255) as u8;
|
||||||
|
|
||||||
|
lerp8by8(start.brightness, end.brightness, frac)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_range(x: u16, in_min: u16, in_max: u16, out_min: u16, out_max: u16) -> u16 {
|
||||||
|
let run = in_max - in_min;
|
||||||
|
if run == 0 {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
let rise = out_max - out_min;
|
||||||
|
let delta = x - in_min;
|
||||||
|
return (delta * rise) / run + out_min;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl Task for CircadianRhythm {
|
||||||
|
fn tick(&mut self, event: &Event, bus: &mut EventBus) {
|
||||||
|
if self.time_check.tick() || event.eq(&Event::ReadyToRock) {
|
||||||
|
let now: DateTime<Utc> = std::time::SystemTime::now().into();
|
||||||
|
let next_brightness = self.brightness_for_time(now.hour() as u8, now.minute() as u8);
|
||||||
|
bus.push(Event::new_property_change("output.brightness", next_brightness));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MqttTask {
|
||||||
|
client: Option<EspMqttClient<'static>>,
|
||||||
|
conn_thread: Option<JoinHandle<()>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MqttTask {
|
||||||
|
fn new() -> Self {
|
||||||
|
MqttTask {
|
||||||
|
conn_thread: None,
|
||||||
|
client: None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_mqtt(&mut self) {
|
||||||
|
log::info!("Starting MQTT");
|
||||||
|
let (client, mut conn) = EspMqttClient::new(
|
||||||
|
"mqtt://10.0.0.2:1883",
|
||||||
|
&MqttClientConfiguration {
|
||||||
|
client_id: Some("renderbug-rs"),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
).unwrap();
|
||||||
|
log::info!("Connected!");
|
||||||
|
|
||||||
|
self.conn_thread = Some(std::thread::Builder::new()
|
||||||
|
.stack_size(6000)
|
||||||
|
.spawn(move || {
|
||||||
|
conn.next().unwrap();
|
||||||
|
}).unwrap());
|
||||||
|
self.client = Some(client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Task for MqttTask {
|
||||||
|
fn tick(&mut self, event: &Event, bus: &mut EventBus) {
|
||||||
|
match event {
|
||||||
|
Event::Input(crate::events::InputEvent::NetworkOnline) => {
|
||||||
|
log::info!("Registering with MQTT");
|
||||||
|
|
||||||
|
self.start_mqtt();
|
||||||
|
|
||||||
|
if let Some(ref mut client) = self.client {
|
||||||
|
client.enqueue(
|
||||||
|
"homeassistant-test/renderbug/rust",
|
||||||
|
esp_idf_svc::mqtt::client::QoS::AtLeastOnce,
|
||||||
|
false,
|
||||||
|
"hello, world".as_bytes()
|
||||||
|
).unwrap();
|
||||||
|
log::info!("MQTT should be online!");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Event::PropertyChange(name, value) => {
|
||||||
|
if let Some(ref mut client) = self.client {
|
||||||
|
let payload = format!("name={} value={:?}", name, value);
|
||||||
|
client.enqueue(
|
||||||
|
"homeassistant-test/renderbug/rust/property-change",
|
||||||
|
esp_idf_svc::mqtt::client::QoS::AtLeastOnce,
|
||||||
|
false,
|
||||||
|
payload.as_bytes()
|
||||||
|
).unwrap();
|
||||||
|
log::info!("property change bump: {}", payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for WifiTask {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("WifiTask").finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||||
|
enum WifiState {
|
||||||
|
Stopped,
|
||||||
|
Disconnected,
|
||||||
|
Connecting,
|
||||||
|
Connected
|
||||||
|
}
|
||||||
|
|
||||||
|
struct WifiTask {
|
||||||
|
wifi: EspWifi<'static>,
|
||||||
|
ntp: EspSntp<'static>,
|
||||||
|
connection_check: Periodically,
|
||||||
|
state: Arc<Mutex<WifiState>>,
|
||||||
|
last_state: WifiState,
|
||||||
|
sys_loop: EspSystemEventLoop,
|
||||||
|
wifi_sub: Option<EspSubscription<'static, System>>,
|
||||||
|
ip_sub: Option<EspSubscription<'static, System>>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WifiTask {
|
||||||
|
fn new(modem: Modem, sys_loop: EspSystemEventLoop, nvs: &EspNvsPartition<NvsDefault>) -> Self {
|
||||||
|
log::info!("Installing wifi driver");
|
||||||
|
let mut wifi = EspWifi::new(
|
||||||
|
modem,
|
||||||
|
sys_loop.clone(),
|
||||||
|
Some(nvs.clone())
|
||||||
|
).unwrap();
|
||||||
|
let wifi_config = Configuration::Client(ClientConfiguration {
|
||||||
|
ssid: "The Frequency".try_into().unwrap(),
|
||||||
|
bssid: None,
|
||||||
|
auth_method: AuthMethod::WPA2Personal,
|
||||||
|
password: "thepasswordkenneth".try_into().unwrap(),
|
||||||
|
channel: None,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
wifi.set_configuration(&wifi_config).unwrap();
|
||||||
|
|
||||||
|
WifiTask {
|
||||||
|
wifi,
|
||||||
|
ntp: EspSntp::new_default().unwrap(),
|
||||||
|
connection_check: Periodically::new_every_n_seconds(1),
|
||||||
|
state: Arc::new(Mutex::new(WifiState::Stopped)),
|
||||||
|
last_state: WifiState::Stopped,
|
||||||
|
sys_loop,
|
||||||
|
wifi_sub: None,
|
||||||
|
ip_sub: None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn connect(&mut self) {
|
||||||
|
log::info!("Connecting wifi");
|
||||||
|
self.wifi.start().unwrap();
|
||||||
|
self.wifi.connect().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn disconnect(&mut self) {
|
||||||
|
log::info!("Disconnecting wifi");
|
||||||
|
self.wifi.disconnect().unwrap();
|
||||||
|
self.wifi.stop().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Task for WifiTask {
|
||||||
|
fn start(&mut self) {
|
||||||
|
log::info!("Starting wifi!");
|
||||||
|
|
||||||
|
let wifi_state = self.state.clone();
|
||||||
|
self.wifi_sub = Some(self.sys_loop.subscribe::<WifiEvent, _>( move |evt| {
|
||||||
|
log::warn!("wifi event {:?}", evt);
|
||||||
|
let next_state = match evt {
|
||||||
|
WifiEvent::StaStopped => Some(WifiState::Disconnected),
|
||||||
|
WifiEvent::StaDisconnected => Some(WifiState::Disconnected),
|
||||||
|
WifiEvent::StaStarted => Some(WifiState::Connecting),
|
||||||
|
WifiEvent::StaConnected => Some(WifiState::Connecting),
|
||||||
|
_ => None
|
||||||
|
};
|
||||||
|
|
||||||
|
match next_state {
|
||||||
|
Some(s) => {
|
||||||
|
let mut state = wifi_state.lock().unwrap();
|
||||||
|
*state = s
|
||||||
|
},
|
||||||
|
None => ()
|
||||||
|
}
|
||||||
|
}).unwrap());
|
||||||
|
let ip_state = self.state.clone();
|
||||||
|
self.ip_sub = Some(self.sys_loop.subscribe::<IpEvent, _>(move |evt| {
|
||||||
|
log::warn!("ip event {:?}", evt);
|
||||||
|
match evt {
|
||||||
|
IpEvent::DhcpIpAssigned(_) => {
|
||||||
|
let mut state = ip_state.lock().unwrap();
|
||||||
|
*state = WifiState::Connected;
|
||||||
|
},
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
}).unwrap());
|
||||||
|
|
||||||
|
self.connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tick(&mut self, event: &Event, bus: &mut EventBus) {
|
||||||
|
if self.connection_check.tick() {
|
||||||
|
let cur_state = *self.state.lock().unwrap();
|
||||||
|
|
||||||
|
if self.last_state != cur_state {
|
||||||
|
match cur_state {
|
||||||
|
WifiState::Connected => log::info!("Wifi connected!"),
|
||||||
|
WifiState::Connecting => log::info!("Connecting!"),
|
||||||
|
WifiState::Stopped => log::info!("Stopped!"),
|
||||||
|
WifiState::Disconnected => log::info!("Disconnected!")
|
||||||
|
}
|
||||||
|
|
||||||
|
log::info!("online: {:?}", cur_state);
|
||||||
|
|
||||||
|
self.last_state = cur_state;
|
||||||
|
|
||||||
|
match cur_state {
|
||||||
|
WifiState::Connected => bus.push(Event::new_input_event(crate::events::InputEvent::NetworkOnline)),
|
||||||
|
_ => bus.push(Event::new_input_event(crate::events::InputEvent::NetworkOffline))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stop(&mut self) {
|
||||||
|
log::info!("Stopping wifi");
|
||||||
|
self.disconnect();
|
||||||
|
}
|
||||||
|
}
|
@ -1,143 +0,0 @@
|
|||||||
use esp_idf_svc::{eventloop::EspSystemEventLoop, hal::{gpio::Pins, modem::Modem, prelude::Peripherals, rmt::RMT, task::thread::ThreadSpawnConfiguration}, nvs::EspDefaultNvsPartition, sys::esp_efuse_mac_get_default};
|
|
||||||
use rgb::Rgb;
|
|
||||||
|
|
||||||
use crate::{buffers::{BufferedSurfacePool, Pixbuf}, mappings::StrideMapping, platform::{smart_leds_lib::{rmt::FastWs2812Esp32Rmt, StrideOutput}, Board}, task::FixedSizeScheduler};
|
|
||||||
|
|
||||||
use super::{mqtt::MqttTask, wifi::WifiTask};
|
|
||||||
|
|
||||||
pub struct Esp32Board {
|
|
||||||
sys_loop: EspSystemEventLoop,
|
|
||||||
modem: Option<Modem>,
|
|
||||||
pins: Option<Pins>,
|
|
||||||
rmt: Option<RMT>,
|
|
||||||
surfaces: Option<BufferedSurfacePool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Board for Esp32Board {
|
|
||||||
type Output = StrideOutput<[Rgb<u8>; 310], FastWs2812Esp32Rmt<'static>>;
|
|
||||||
type Surfaces = BufferedSurfacePool;
|
|
||||||
type Scheduler = FixedSizeScheduler<2>;
|
|
||||||
|
|
||||||
fn chip_id() -> u64 {
|
|
||||||
let mut chip_id: [u8; 8] = [0; 8];
|
|
||||||
unsafe {
|
|
||||||
esp_efuse_mac_get_default(&mut chip_id as *mut u8);
|
|
||||||
}
|
|
||||||
return u64::from_be_bytes(chip_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn take() -> Self {
|
|
||||||
// It is necessary to call this function once. Otherwise some patches to the runtime
|
|
||||||
// implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71
|
|
||||||
esp_idf_svc::sys::link_patches();
|
|
||||||
|
|
||||||
// Bind the log crate to the ESP Logging facilities
|
|
||||||
esp_idf_svc::log::EspLogger::initialize_default();
|
|
||||||
|
|
||||||
let peripherals = Peripherals::take().unwrap();
|
|
||||||
let sys_loop = EspSystemEventLoop::take().unwrap();
|
|
||||||
|
|
||||||
Esp32Board {
|
|
||||||
modem: Some(peripherals.modem),
|
|
||||||
sys_loop: sys_loop,
|
|
||||||
surfaces: Some(BufferedSurfacePool::new()),
|
|
||||||
pins: Some(peripherals.pins),
|
|
||||||
rmt: Some(peripherals.rmt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn output(&mut self) -> Self::Output {
|
|
||||||
|
|
||||||
log::info!("Setting up output for chip ID {:x?}", Self::chip_id());
|
|
||||||
|
|
||||||
const POWER_VOLTS : u32 = 5;
|
|
||||||
const POWER_MA : u32 = 500;
|
|
||||||
const MAX_POWER_MW : u32 = POWER_VOLTS * POWER_MA;
|
|
||||||
|
|
||||||
let pins = self.pins.take().unwrap();
|
|
||||||
let rmt = self.rmt.take().unwrap();
|
|
||||||
ThreadSpawnConfiguration {
|
|
||||||
pin_to_core: Some(esp_idf_svc::hal::cpu::Core::Core1),
|
|
||||||
..Default::default()
|
|
||||||
}.set().unwrap();
|
|
||||||
// Wifi driver creates too many interrupts on core0, so we need to use RMT on core1.
|
|
||||||
// But the implementation spawns a thread based on the core the driver was created in,
|
|
||||||
// so we create the driver in another thread briefly.
|
|
||||||
// Fun stuff.
|
|
||||||
let output = match Self::chip_id().to_be_bytes() { // panel test board
|
|
||||||
[72, 202, 67, 89, 145, 204, 0, 0] => {
|
|
||||||
StrideOutput::new(
|
|
||||||
Pixbuf::new(),
|
|
||||||
StrideMapping::new_panel(),
|
|
||||||
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(rmt.channel0, pins.gpio5).unwrap() }).join().unwrap(),
|
|
||||||
MAX_POWER_MW
|
|
||||||
)
|
|
||||||
},
|
|
||||||
[0x8C, 0xAA, 0xB5, 0x83, 0x5f, 0x74, 0x0, 0x0] => { //ponderjar
|
|
||||||
StrideOutput::new(
|
|
||||||
Pixbuf::new(),
|
|
||||||
StrideMapping::new_jar(),
|
|
||||||
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(rmt.channel0, pins.gpio14).unwrap() }).join().unwrap(),
|
|
||||||
MAX_POWER_MW
|
|
||||||
)
|
|
||||||
},
|
|
||||||
[0x4a, 0xca, 0x43, 0x59, 0x85, 0x58, 0x0, 0x0] => { // Albus the tree
|
|
||||||
StrideOutput::new(
|
|
||||||
Pixbuf::new(),
|
|
||||||
StrideMapping::new_albus(),
|
|
||||||
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(rmt.channel0, pins.gpio5).unwrap() }).join().unwrap(),
|
|
||||||
POWER_VOLTS * 2_400
|
|
||||||
)
|
|
||||||
},
|
|
||||||
[0x48, 0xca, 0x43, 0x59, 0x9d, 0x48, 0x0, 0x0] => { // kitchen cabinets
|
|
||||||
StrideOutput::new(
|
|
||||||
Pixbuf::new(),
|
|
||||||
StrideMapping::new_fairylights(),
|
|
||||||
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(rmt.channel0, pins.gpio5).unwrap() }).join().unwrap(),
|
|
||||||
MAX_POWER_MW
|
|
||||||
)
|
|
||||||
},
|
|
||||||
[0x48, 0xca, 0x43, 0x59, 0x9e, 0xdc, 0x0, 0x0] => { // front window
|
|
||||||
StrideOutput::new(
|
|
||||||
Pixbuf::new(),
|
|
||||||
StrideMapping::new_fairylights(),
|
|
||||||
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(rmt.channel0, pins.gpio5).unwrap() }).join().unwrap(),
|
|
||||||
MAX_POWER_MW
|
|
||||||
)
|
|
||||||
},
|
|
||||||
[0xfc, 0xf5, 0xc4, 0x05, 0xb8, 0x30, 0x0, 0x0] => { // cyberplague
|
|
||||||
StrideOutput::new(
|
|
||||||
Pixbuf::new(),
|
|
||||||
StrideMapping::new_cyberplague(),
|
|
||||||
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(rmt.channel0, pins.gpio13).unwrap() }).join().unwrap(),
|
|
||||||
MAX_POWER_MW
|
|
||||||
)
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
StrideOutput::new(
|
|
||||||
Pixbuf::new(),
|
|
||||||
StrideMapping::new(),
|
|
||||||
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(rmt.channel0, pins.gpio5).unwrap() }).join().unwrap(),
|
|
||||||
MAX_POWER_MW
|
|
||||||
)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
ThreadSpawnConfiguration {
|
|
||||||
..Default::default()
|
|
||||||
}.set().unwrap();
|
|
||||||
|
|
||||||
output
|
|
||||||
}
|
|
||||||
|
|
||||||
fn surfaces(&mut self) -> Self::Surfaces {
|
|
||||||
self.surfaces.take().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn system_tasks(&mut self) -> Self::Scheduler {
|
|
||||||
let nvs = EspDefaultNvsPartition::take().unwrap();
|
|
||||||
FixedSizeScheduler::new([
|
|
||||||
Box::new(WifiTask::new(self.modem.take().unwrap(), self.sys_loop.clone(), &nvs)),
|
|
||||||
Box::new(MqttTask::new())
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
pub mod board;
|
|
||||||
pub mod wifi;
|
|
||||||
pub mod mqtt;
|
|
@ -1,186 +0,0 @@
|
|||||||
use std::{collections::LinkedList, thread::JoinHandle};
|
|
||||||
|
|
||||||
use esp_idf_svc::mqtt::client::{EspMqttClient, MqttClientConfiguration};
|
|
||||||
use serde_json::{json, Value};
|
|
||||||
|
|
||||||
use crate::{events::{Event, EventBus, System as SystemNS}, prop_id, properties::{PropertyID, Variant}, render::props::Output as OutputNS, scenes::props::Scenes as SceneNS, task::Task};
|
|
||||||
use crate::platform::props::Board as BoardNS;
|
|
||||||
use paste::paste;
|
|
||||||
|
|
||||||
struct HADevice {
|
|
||||||
prefix: String,
|
|
||||||
unique_id: String
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HADevice {
|
|
||||||
fn new(component: &str, chip_id: u64, name: &str) -> Self {
|
|
||||||
HADevice {
|
|
||||||
// eg: homeassistant/sensor/0BADCOFFEE/fps
|
|
||||||
unique_id: format!("{:X}-{}", chip_id, name),
|
|
||||||
prefix: format!("homeassistant/{}/renderbug-rs/{:X}-{}", component, chip_id, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn topic(&self, name: &str) -> String {
|
|
||||||
format!("{}/{}", self.prefix, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn registration(&self) -> Value {
|
|
||||||
json!({
|
|
||||||
"~": self.prefix,
|
|
||||||
"stat_t": "~/state",
|
|
||||||
"unique_id": self.unique_id,
|
|
||||||
"dev": {
|
|
||||||
"name": "Renderbug-rs ESP32",
|
|
||||||
"mdl": "Renderbug-rs ESP32",
|
|
||||||
"sw": "",
|
|
||||||
"mf": "Phong Robotics",
|
|
||||||
"ids": [self.unique_id]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct HAScene {
|
|
||||||
prefix: String,
|
|
||||||
unique_id: String
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HAScene {
|
|
||||||
fn new(chip_id: u64, name: &str) -> Self {
|
|
||||||
HAScene {
|
|
||||||
// eg: homeassistant/sensor/0BADCOFFEE/fps
|
|
||||||
unique_id: format!("{:X}-{}", chip_id, name),
|
|
||||||
prefix: format!("homeassistant/scene/renderbug-rs/{:X}-{}", chip_id, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn topic(&self, name: &str) -> String {
|
|
||||||
format!("{}/{}", self.prefix, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn registration(&self) -> Value {
|
|
||||||
json!({
|
|
||||||
"~": self.prefix,
|
|
||||||
"stat_t": "~/state",
|
|
||||||
"cmd_t": "~/command",
|
|
||||||
"unique_id": self.unique_id,
|
|
||||||
"payload_on": "on",
|
|
||||||
"dev": {
|
|
||||||
"name": "Renderbug-rs ESP32",
|
|
||||||
"mdl": "Renderbug-rs ESP32",
|
|
||||||
"sw": "",
|
|
||||||
"mf": "Phong Robotics",
|
|
||||||
"ids": [self.unique_id]
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct MqttTask {
|
|
||||||
client: Option<EspMqttClient<'static>>,
|
|
||||||
conn_thread: Option<JoinHandle<()>>,
|
|
||||||
fps_sensor: Option<HADevice>
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MqttTask {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
MqttTask {
|
|
||||||
conn_thread: None,
|
|
||||||
client: None,
|
|
||||||
fps_sensor: None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn start_mqtt(&mut self, bus: &EventBus) {
|
|
||||||
log::info!("Starting MQTT");
|
|
||||||
let chip_id: u64 = bus.properties().get(BoardNS::ChipID).unwrap().into();
|
|
||||||
|
|
||||||
self.fps_sensor = Some(HADevice::new("sensor", chip_id, "output-fps"));
|
|
||||||
|
|
||||||
let (client, mut conn) = EspMqttClient::new(
|
|
||||||
"mqtt://10.0.0.2:1883",
|
|
||||||
&MqttClientConfiguration {
|
|
||||||
client_id: Some(&format!("{:X}", chip_id)),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
).unwrap();
|
|
||||||
log::info!("Connected!");
|
|
||||||
|
|
||||||
self.conn_thread = Some(std::thread::Builder::new()
|
|
||||||
.stack_size(6000)
|
|
||||||
.spawn(move || {
|
|
||||||
conn.next().unwrap();
|
|
||||||
}).unwrap());
|
|
||||||
self.client = Some(client);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Task for MqttTask {
|
|
||||||
fn start(&mut self, bus: &mut EventBus) {
|
|
||||||
bus.set_property(props::MQTT::Online, false);
|
|
||||||
|
|
||||||
let chip_id = bus.properties().get(BoardNS::ChipID).unwrap().into();
|
|
||||||
self.fps_sensor = Some(HADevice::new("sensor", chip_id, "fps"));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_property_change(&mut self, key: PropertyID, value: &Variant, bus: &mut EventBus) {
|
|
||||||
match (key, value) {
|
|
||||||
(prop_id!(SystemNS::NetworkOnline), Variant::Boolean(true)) => {
|
|
||||||
log::info!("Registering with MQTT");
|
|
||||||
|
|
||||||
let chip_id = bus.properties().get(BoardNS::ChipID).unwrap().into();
|
|
||||||
|
|
||||||
self.start_mqtt(bus);
|
|
||||||
|
|
||||||
if let Some(ref mut client) = self.client {
|
|
||||||
if let Some(ref sensor) = self.fps_sensor {
|
|
||||||
client.enqueue(
|
|
||||||
&sensor.topic("config"),
|
|
||||||
esp_idf_svc::mqtt::client::QoS::AtLeastOnce,
|
|
||||||
false,
|
|
||||||
sensor.registration().to_string().as_bytes()
|
|
||||||
).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let scenes: Vec<Variant> = bus.properties().get(SceneNS::All).unwrap().into();
|
|
||||||
for scene in scenes.iter() {
|
|
||||||
let scene_name: String = scene.clone().into();
|
|
||||||
let scene_device = HAScene::new(chip_id, scene_name.as_str());
|
|
||||||
client.enqueue(
|
|
||||||
&scene_device.topic("config"),
|
|
||||||
esp_idf_svc::mqtt::client::QoS::AtLeastOnce,
|
|
||||||
false,
|
|
||||||
scene_device.registration().to_string().as_bytes()
|
|
||||||
).unwrap();
|
|
||||||
}
|
|
||||||
log::info!("MQTT should be online!");
|
|
||||||
|
|
||||||
bus.set_property(props::MQTT::Online, true);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(prop_id!(OutputNS::FPS), Variant::UInt(fps)) => {
|
|
||||||
if let Some(ref mut client) = self.client {
|
|
||||||
if let Some(ref sensor) = self.fps_sensor {
|
|
||||||
client.enqueue(
|
|
||||||
&sensor.topic("state"),
|
|
||||||
esp_idf_svc::mqtt::client::QoS::AtLeastOnce,
|
|
||||||
false,
|
|
||||||
json!(fps).to_string().as_bytes()
|
|
||||||
).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => ()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod props {
|
|
||||||
use crate::property_namespace;
|
|
||||||
|
|
||||||
property_namespace!(
|
|
||||||
MQTT,
|
|
||||||
Online => true
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,104 +0,0 @@
|
|||||||
use esp_idf_svc::{eventloop::{EspSubscription, EspSystemEventLoop, System}, hal::modem::Modem, netif::IpEvent, nvs::{EspNvsPartition, NvsDefault}, sntp::{EspSntp, SyncStatus}, wifi::{AuthMethod, ClientConfiguration, Configuration, EspWifi, WifiEvent}};
|
|
||||||
|
|
||||||
use crate::{events::{EventBus, System as SystemNS}, properties::Variant, task::Task, time::Periodically};
|
|
||||||
|
|
||||||
use std::fmt::Debug;
|
|
||||||
|
|
||||||
pub struct WifiTask {
|
|
||||||
wifi: EspWifi<'static>,
|
|
||||||
ntp: EspSntp<'static>,
|
|
||||||
connection_check: Periodically,
|
|
||||||
sys_loop: EspSystemEventLoop,
|
|
||||||
wifi_sub: Option<EspSubscription<'static, System>>,
|
|
||||||
ip_sub: Option<EspSubscription<'static, System>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for WifiTask {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.debug_struct("WifiTask").finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WifiTask {
|
|
||||||
pub fn new(modem: Modem, sys_loop: EspSystemEventLoop, nvs: &EspNvsPartition<NvsDefault>) -> Self {
|
|
||||||
log::info!("Installing wifi driver");
|
|
||||||
let wifi = EspWifi::new(
|
|
||||||
modem,
|
|
||||||
sys_loop.clone(),
|
|
||||||
Some(nvs.clone())
|
|
||||||
).unwrap();
|
|
||||||
|
|
||||||
WifiTask {
|
|
||||||
wifi,
|
|
||||||
ntp: EspSntp::new_default().unwrap(),
|
|
||||||
connection_check: Periodically::new_every_n_seconds(1),
|
|
||||||
sys_loop,
|
|
||||||
wifi_sub: None,
|
|
||||||
ip_sub: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn connect(&mut self) {
|
|
||||||
log::info!("Connecting wifi");
|
|
||||||
let wifi_config = Configuration::Client(ClientConfiguration {
|
|
||||||
ssid: "The Frequency".try_into().unwrap(),
|
|
||||||
bssid: None,
|
|
||||||
auth_method: AuthMethod::WPA2Personal,
|
|
||||||
password: "thepasswordkenneth".try_into().unwrap(),
|
|
||||||
channel: None,
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
self.wifi.set_configuration(&wifi_config).unwrap();
|
|
||||||
self.wifi.start().unwrap();
|
|
||||||
self.wifi.connect().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn disconnect(&mut self) {
|
|
||||||
log::info!("Disconnecting wifi");
|
|
||||||
self.wifi.disconnect().unwrap();
|
|
||||||
self.wifi.stop().unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Task for WifiTask {
|
|
||||||
fn start(&mut self, bus: &mut EventBus) {
|
|
||||||
log::info!("Starting wifi!");
|
|
||||||
let mut wifi_bus = bus.clone();
|
|
||||||
self.wifi_sub = Some(self.sys_loop.subscribe::<WifiEvent, _>( move |evt| {
|
|
||||||
log::debug!("wifi event {:?}", evt);
|
|
||||||
wifi_bus.set_property(SystemNS::NetworkOnline,false);
|
|
||||||
}).unwrap());
|
|
||||||
let mut ip_bus = bus.clone();
|
|
||||||
self.ip_sub = Some(self.sys_loop.subscribe::<IpEvent, _>(move |evt| {
|
|
||||||
log::debug!("ip event {:?}", evt);
|
|
||||||
match evt {
|
|
||||||
IpEvent::DhcpIpAssigned(addr) => {
|
|
||||||
ip_bus.set_property(SystemNS::IP, addr.ip().to_string());
|
|
||||||
ip_bus.set_property(SystemNS::Gateway, addr.gateway().to_string());
|
|
||||||
ip_bus.set_property(SystemNS::NetworkOnline, true);
|
|
||||||
},
|
|
||||||
_ => ()
|
|
||||||
}
|
|
||||||
}).unwrap());
|
|
||||||
self.connect();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_tick(&mut self, bus: &mut EventBus) {
|
|
||||||
if self.connection_check.tick() {
|
|
||||||
if bus.properties().get(SystemNS::NetworkOnline).unwrap() == Variant::Boolean(true) {
|
|
||||||
match self.ntp.get_sync_status() {
|
|
||||||
SyncStatus::Completed => bus.set_property(SystemNS::TimeSync, true),
|
|
||||||
_ => bus.set_property(SystemNS::TimeSync, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stop(&mut self, bus: &mut EventBus) {
|
|
||||||
log::info!("Stopping wifi");
|
|
||||||
self.wifi_sub.take().unwrap();
|
|
||||||
self.ip_sub.take().unwrap();
|
|
||||||
self.disconnect();
|
|
||||||
bus.set_property(SystemNS::NetworkOnline, false);
|
|
||||||
}
|
|
||||||
}
|
|
44
src/platform/esp32_nostd.rs
Normal file
44
src/platform/esp32_nostd.rs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
use rgb::Rgb;
|
||||||
|
use super::smart_leds_lib::rmt::FastWs2812Esp32Rmt;
|
||||||
|
use super::smart_leds_lib::StrideOutput;
|
||||||
|
|
||||||
|
use crate::task::{FixedSizeScheduler, Task};
|
||||||
|
use crate::buffers::StaticSurfacePool;
|
||||||
|
|
||||||
|
use super::Board;
|
||||||
|
|
||||||
|
pub struct Esp32Board<'a> {
|
||||||
|
output: Option<<Self as Board>::Output>,
|
||||||
|
surfaces: Option<StaticSurfacePool>,
|
||||||
|
tasks: Option<[&'a mut dyn Task; 1]>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Board for Esp32Board<'a> {
|
||||||
|
type Output = StrideOutput<[Rgb<u8>; 310], FastWs2812Esp32Rmt<'a>>;
|
||||||
|
type Surfaces = StaticSurfacePool;
|
||||||
|
type Scheduler = FixedSizeScheduler<0>;
|
||||||
|
fn take() -> Self {
|
||||||
|
let peripherals = esp_hal::init(esp_hal::Config::default());
|
||||||
|
//esp_alloc::heap_allocator!(72 * 1024);
|
||||||
|
|
||||||
|
const POWER_VOLTS : u32 = 5;
|
||||||
|
const POWER_MA : u32 = 500;
|
||||||
|
const MAX_POWER_MW : u32 = POWER_VOLTS * POWER_MA;
|
||||||
|
let pins = peripherals.pins;
|
||||||
|
|
||||||
|
|
||||||
|
Esp32Board { output: None, surfaces: None, tasks: None }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn output(&mut self) -> Self::Output {
|
||||||
|
self.output.take().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn surfaces(&mut self) -> Self::Surfaces {
|
||||||
|
self.surfaces.take().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn system_tasks(&mut self) -> Self::Scheduler {
|
||||||
|
FixedSizeScheduler::new([])
|
||||||
|
}
|
||||||
|
}
|
@ -6,7 +6,7 @@ pub mod smart_leds_lib;
|
|||||||
|
|
||||||
pub mod esp32;
|
pub mod esp32;
|
||||||
|
|
||||||
pub type DefaultBoard = esp32::board::Esp32Board;
|
pub type DefaultBoard = esp32::Esp32Board;
|
||||||
|
|
||||||
use crate::render::{Output, Surfaces};
|
use crate::render::{Output, Surfaces};
|
||||||
use crate::task::Scheduler;
|
use crate::task::Scheduler;
|
||||||
@ -20,14 +20,4 @@ pub trait Board {
|
|||||||
fn output(&mut self) -> Self::Output;
|
fn output(&mut self) -> Self::Output;
|
||||||
fn surfaces(&mut self) -> Self::Surfaces;
|
fn surfaces(&mut self) -> Self::Surfaces;
|
||||||
fn system_tasks(&mut self) -> Self::Scheduler;
|
fn system_tasks(&mut self) -> Self::Scheduler;
|
||||||
fn chip_id() -> u64;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod props {
|
|
||||||
use crate::property_namespace;
|
|
||||||
|
|
||||||
property_namespace!(
|
|
||||||
Board,
|
|
||||||
ChipID => 0_u64
|
|
||||||
);
|
|
||||||
}
|
}
|
@ -1,12 +1,11 @@
|
|||||||
use smart_leds_trait::SmartLedsWrite;
|
use smart_leds_trait::SmartLedsWrite;
|
||||||
|
|
||||||
use crate::buffers::Pixbuf;
|
use crate::buffers::Pixbuf;
|
||||||
use crate::properties::Variant;
|
use crate::events::Variant;
|
||||||
use crate::render::{HardwarePixel, Output, PixelView, Sample, props::Output as OutputNS};
|
use crate::render::{HardwarePixel, Output, PixelView, Sample};
|
||||||
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 paste::paste;
|
|
||||||
|
|
||||||
use core::fmt::Debug;
|
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) {
|
fn on_event(&mut self, event: &crate::events::Event) {
|
||||||
match event {
|
match event {
|
||||||
crate::events::Event::PropertyChange(prop_id!(OutputNS::Brightness), Variant::Byte(new_brightness)) => self.brightness = *new_brightness,
|
crate::events::Event::PropertyChange("output.brightness", new_brightness) => self.brightness = new_brightness.clone().into(),
|
||||||
_ => ()
|
_ => ()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,270 +0,0 @@
|
|||||||
use std::fmt::Display;
|
|
||||||
|
|
||||||
use rgb::Rgb;
|
|
||||||
pub use paste::paste;
|
|
||||||
|
|
||||||
#[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)]
|
|
||||||
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)]
|
|
||||||
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<'_>) -> 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! 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),*
|
|
||||||
}
|
|
||||||
|
|
||||||
use paste::paste;
|
|
||||||
|
|
||||||
impl crate::properties::Namespace for $name {
|
|
||||||
fn nskey() -> $crate::properties::NamespaceKey {
|
|
||||||
Self::NAMESPACE_KEY
|
|
||||||
}
|
|
||||||
|
|
||||||
fn properties() -> Vec<$crate::properties::Property> {
|
|
||||||
vec![
|
|
||||||
$($crate::properties::Property::new(paste! {Self::[<KEY_ $prop_name>]}, $crate::properties::Variant::from($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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,6 @@
|
|||||||
use rgb::Rgb;
|
use rgb::Rgb;
|
||||||
use crate::events::*;
|
|
||||||
use crate::properties::*;
|
use crate::events::{Event, EventBus};
|
||||||
use crate::geometry::*;
|
use crate::geometry::*;
|
||||||
use crate::lib8::interpolate::Fract8Ops;
|
use crate::lib8::interpolate::Fract8Ops;
|
||||||
use crate::power::AsMilliwatts;
|
use crate::power::AsMilliwatts;
|
||||||
@ -23,22 +23,22 @@ pub trait Sample {
|
|||||||
fn sample(&mut self, rect: &Rectangle<Virtual>) -> impl PixelView<Pixel = Self::Pixel>;
|
fn sample(&mut self, rect: &Rectangle<Virtual>) -> impl PixelView<Pixel = Self::Pixel>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Shader: Send {
|
pub trait Shader: Send + Sync {
|
||||||
fn draw(&self, surface_coords: &VirtualCoordinates, frame: usize) -> Rgb<u8>;
|
fn draw(&self, surface_coords: &VirtualCoordinates, frame: usize) -> Rgb<u8>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Surfaces: Send {
|
pub trait Surfaces: Send + Sync {
|
||||||
type Surface: Surface;
|
type Surface: Surface;
|
||||||
type Error: Debug;
|
type Error: Debug;
|
||||||
fn new_surface(&mut self, area: Rectangle<Virtual>) -> Result<Self::Surface, Self::Error>;
|
fn new_surface(&mut self, area: &Rectangle<Virtual>) -> Result<Self::Surface, Self::Error>;
|
||||||
fn render_to<S: Sample>(&self, output: &mut S, frame: usize);
|
fn render_to<S: Sample>(&self, output: &mut S, frame: usize);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Surface: Send {
|
pub trait Surface: Send + Sync {
|
||||||
fn set_shader<T: Shader + 'static>(&mut self, shader: T);
|
fn set_shader(&mut self, shader: Box<dyn Shader>);
|
||||||
fn clear_shader(&mut self);
|
fn clear_shader(&mut self);
|
||||||
|
|
||||||
fn set_rect(&mut self, rect: Rectangle<Virtual>);
|
fn set_rect(&mut self, rect: &Rectangle<Virtual>);
|
||||||
fn set_opacity(&mut self, opacity: u8);
|
fn set_opacity(&mut self, opacity: u8);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,8 +54,7 @@ pub struct Renderer<T: Output, S: Surfaces> {
|
|||||||
surfaces: S,
|
surfaces: S,
|
||||||
fps: RealTimeRunningAverage<u32>,
|
fps: RealTimeRunningAverage<u32>,
|
||||||
fps_display: Periodically,
|
fps_display: Periodically,
|
||||||
frame: usize,
|
frame: usize
|
||||||
frame_count: usize
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Output, S: Surfaces> Renderer<T, S> {
|
impl<T: Output, S: Surfaces> Renderer<T, S> {
|
||||||
@ -65,8 +64,7 @@ impl<T: Output, S: Surfaces> Renderer<T, S> {
|
|||||||
surfaces: surfaces,
|
surfaces: surfaces,
|
||||||
fps: RealTimeRunningAverage::default(),
|
fps: RealTimeRunningAverage::default(),
|
||||||
fps_display: Periodically::new_every_n_seconds(5),
|
fps_display: Periodically::new_every_n_seconds(5),
|
||||||
frame: 0,
|
frame: 0
|
||||||
frame_count: 0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -74,33 +72,22 @@ 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: &Variant, _bus: &mut EventBus) {
|
fn tick(&mut self, event: &Event, _bus: &mut EventBus) {
|
||||||
self.output.on_event(&Event::new_property_change(key, value.clone()));
|
match event {
|
||||||
}
|
crate::events::Event::Tick => {
|
||||||
|
self.output.blank();
|
||||||
|
|
||||||
fn on_tick(&mut self, bus: &mut EventBus) {
|
self.surfaces.render_to(&mut self.output, self.frame);
|
||||||
self.output.blank();
|
|
||||||
|
self.output.commit();
|
||||||
self.surfaces.render_to(&mut self.output, self.frame);
|
|
||||||
|
self.fps.insert(1);
|
||||||
self.output.commit();
|
self.frame += 1;
|
||||||
|
self.fps_display.run(|| {
|
||||||
self.frame += 1;
|
log::info!("FPS: {}", self.fps.measurement());
|
||||||
self.fps_display.run(|| {
|
});
|
||||||
self.fps.insert((self.frame - self.frame_count) as u32);
|
},
|
||||||
self.frame_count = self.frame;
|
_ => self.output.on_event(event)
|
||||||
let fps = self.fps.measurement();
|
}
|
||||||
bus.set_property(crate::render::props::Output::FPS, fps.rate() as u32);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod props {
|
|
||||||
use crate::property_namespace;
|
|
||||||
|
|
||||||
property_namespace!(
|
|
||||||
Output,
|
|
||||||
FPS => 0,
|
|
||||||
Brightness => 0
|
|
||||||
);
|
|
||||||
}
|
|
130
src/scenes.rs
130
src/scenes.rs
@ -1,130 +0,0 @@
|
|||||||
use crate::task::Task;
|
|
||||||
use crate::events::*;
|
|
||||||
use crate::properties::*;
|
|
||||||
use paste::paste;
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
|
||||||
struct Scene {
|
|
||||||
name: &'static str,
|
|
||||||
patterns: Vec<&'static str>,
|
|
||||||
trigger: Trigger
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
|
||||||
enum Trigger {
|
|
||||||
Startup,
|
|
||||||
PropertyEquals(PropertyID, Variant)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Sequencer {
|
|
||||||
scenes: Vec<Scene>,
|
|
||||||
cur_scene: String
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Sequencer {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Sequencer {
|
|
||||||
cur_scene: String::new(),
|
|
||||||
scenes: vec![
|
|
||||||
Scene {
|
|
||||||
name: "Start",
|
|
||||||
patterns: vec!["Idle"],
|
|
||||||
trigger: Trigger::Startup
|
|
||||||
},
|
|
||||||
Scene {
|
|
||||||
name: "Online",
|
|
||||||
patterns: vec!["Idle"],
|
|
||||||
trigger: Trigger::PropertyEquals(prop_id!(System::NetworkOnline), Variant::Boolean(true))
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_scene(&self, name: &String) -> Option<&Scene> {
|
|
||||||
for scene in self.scenes.iter() {
|
|
||||||
if scene.name == name {
|
|
||||||
return Some(scene);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn apply_scene(&mut self, name: &String, bus: &mut EventBus) {
|
|
||||||
if let Some(dst_tasks) = self.get_scene(name) {
|
|
||||||
if let Some(src_tasks) = self.get_scene(&self.cur_scene) {
|
|
||||||
let stop_queue = src_tasks.patterns.iter().filter(|i| !dst_tasks.patterns.contains(i));
|
|
||||||
let start_queue = dst_tasks.patterns.iter().filter(|i| !src_tasks.patterns.contains(i));
|
|
||||||
|
|
||||||
log::info!("Switching scene from {} to {}", self.cur_scene, name);
|
|
||||||
|
|
||||||
for stop in stop_queue {
|
|
||||||
bus.push(Event::new_stop_thing(stop));
|
|
||||||
}
|
|
||||||
for start in start_queue {
|
|
||||||
bus.push(Event::new_start_thing(start));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log::info!("Starting new scene {}", name);
|
|
||||||
log::info!("start={:?}", dst_tasks.patterns);
|
|
||||||
for start in dst_tasks.patterns.iter() {
|
|
||||||
bus.push(Event::new_start_thing(start));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.cur_scene = name.clone();
|
|
||||||
} else {
|
|
||||||
panic!("Could not apply scene {:?} scenes={:?}", name, self.scenes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod props {
|
|
||||||
use crate::property_namespace;
|
|
||||||
|
|
||||||
property_namespace!(
|
|
||||||
Scenes,
|
|
||||||
Current => "",
|
|
||||||
All => Vec::new()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
use crate::scenes::props::Scenes;
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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)) => {
|
|
||||||
log::info!("Applying scene");
|
|
||||||
self.apply_scene(scene_name, bus);
|
|
||||||
},
|
|
||||||
(key, value) => {
|
|
||||||
/*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))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => ()
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
use crate::prop_id;
|
|
101
src/task.rs
101
src/task.rs
@ -1,20 +1,17 @@
|
|||||||
use core::fmt;
|
use core::fmt;
|
||||||
|
|
||||||
use crate::{events::{Event, EventBus}, properties::{Variant, PropertyID}};
|
use crate::events::{Event, EventBus};
|
||||||
|
|
||||||
pub trait Task: Send {
|
pub trait Task: Send {
|
||||||
fn on_ready(&mut self, bus: &mut EventBus) {}
|
fn tick(&mut self, event: &Event, bus: &mut EventBus) {}
|
||||||
fn on_tick(&mut self, bus: &mut EventBus) {}
|
fn start(&mut self) {}
|
||||||
fn on_property_change(&mut self, key: PropertyID, value: &Variant, bus: &mut EventBus) {}
|
fn stop(&mut self) {}
|
||||||
|
|
||||||
fn start(&mut self, bus: &mut EventBus) {}
|
|
||||||
fn stop(&mut self, bus: &mut EventBus) {}
|
|
||||||
fn name(&self) -> &'static str {
|
fn name(&self) -> &'static str {
|
||||||
core::any::type_name::<Self>()
|
core::any::type_name::<Self>()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
enum ScheduledState {
|
enum ScheduledState {
|
||||||
Stopped,
|
Stopped,
|
||||||
Start,
|
Start,
|
||||||
@ -39,109 +36,75 @@ impl core::fmt::Debug for ScheduledTask {
|
|||||||
impl ScheduledTask {
|
impl ScheduledTask {
|
||||||
fn new(task: Box<dyn Task>) -> Self {
|
fn new(task: Box<dyn Task>) -> Self {
|
||||||
ScheduledTask {
|
ScheduledTask {
|
||||||
state: ScheduledState::Stopped,
|
state: ScheduledState::Start,
|
||||||
task: task,
|
task: task,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start(&mut self) {
|
fn start(&mut self) {
|
||||||
match self.state {
|
self.state = match self.state {
|
||||||
ScheduledState::Stopped => self.state = ScheduledState::Start,
|
ScheduledState::Stopped => ScheduledState::Start,
|
||||||
ScheduledState::Stop => self.state = ScheduledState::Running,
|
ScheduledState::Stop => ScheduledState::Running,
|
||||||
_ => ()
|
_ => self.state
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stop(&mut self) {
|
fn stop(&mut self) {
|
||||||
match self.state {
|
self.state = match self.state {
|
||||||
ScheduledState::Running => self.state = ScheduledState::Stop,
|
ScheduledState::Running => ScheduledState::Stop,
|
||||||
ScheduledState::Start => self.state = ScheduledState::Stopped,
|
ScheduledState::Start => ScheduledState::Stopped,
|
||||||
_ => ()
|
_ => self.state
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tick(&mut self, event: &Event, bus: &mut EventBus) {
|
fn tick(&mut self, event: &Event, bus: &mut EventBus) {
|
||||||
match self.state {
|
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();
|
||||||
self.state = ScheduledState::Running
|
ScheduledState::Running
|
||||||
|
},
|
||||||
|
ScheduledState::Running => {
|
||||||
|
self.task.tick(event, bus);
|
||||||
|
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();
|
||||||
self.state = ScheduledState::Stopped
|
ScheduledState::Stopped
|
||||||
},
|
},
|
||||||
_ => ()
|
ScheduledState::Stopped => ScheduledState::Stopped
|
||||||
};
|
|
||||||
|
|
||||||
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),
|
|
||||||
_ => ()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => ()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct FixedSizeScheduler<const TASK_COUNT: usize> {
|
pub struct FixedSizeScheduler<const TASK_COUNT: usize> {
|
||||||
tasks: [ScheduledTask; TASK_COUNT],
|
tasks: [Option<ScheduledTask>; TASK_COUNT],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const TASK_COUNT: usize> FixedSizeScheduler<TASK_COUNT> {
|
impl<const TASK_COUNT: usize> FixedSizeScheduler<TASK_COUNT> {
|
||||||
pub fn new(tasks: [Box<dyn Task>; TASK_COUNT]) -> Self {
|
pub fn new(tasks: [Box<dyn Task>; TASK_COUNT]) -> Self {
|
||||||
let mut scheduled: [ScheduledTask; TASK_COUNT] = unsafe { std::mem::MaybeUninit::zeroed().assume_init() };
|
let mut scheduled = [const { None }; TASK_COUNT];
|
||||||
let mut idx = 0;
|
let mut idx = 0;
|
||||||
for task in tasks {
|
for task in tasks {
|
||||||
log::info!("Scheduling task {}", task.name());
|
scheduled[idx] = Some(ScheduledTask::new(task));
|
||||||
let slot = &mut scheduled[idx];
|
|
||||||
unsafe { std::ptr::write(slot, ScheduledTask::new(task)) };
|
|
||||||
idx += 1;
|
idx += 1;
|
||||||
}
|
}
|
||||||
FixedSizeScheduler {
|
FixedSizeScheduler {
|
||||||
tasks: scheduled
|
tasks: scheduled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_task(&mut self, name: &str) -> Option<&mut ScheduledTask> {
|
|
||||||
for slot in &mut self.tasks {
|
|
||||||
if slot.task.name() == name {
|
|
||||||
return Some(slot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const TASK_COUNT: usize> Scheduler for FixedSizeScheduler<TASK_COUNT> {
|
impl<const TASK_COUNT: usize> Scheduler for FixedSizeScheduler<TASK_COUNT> {
|
||||||
fn tick(&mut self, event: &Event, bus: &mut EventBus) {
|
fn tick(&mut self, event: &Event, bus: &mut EventBus) {
|
||||||
match event {
|
for slot in &mut self.tasks {
|
||||||
Event::StartThing(task_name) => {
|
match slot {
|
||||||
if let Some(slot) = self.find_task(task_name) {
|
Some(task) => task.tick(event, bus),
|
||||||
log::debug!("Starting {}", task_name);
|
_ => ()
|
||||||
slot.start();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Event::StopThing(task_name) => {
|
|
||||||
if let Some(slot) = self.find_task(task_name) {
|
|
||||||
log::debug!("Stopping {}", task_name);
|
|
||||||
slot.stop();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
for slot in &mut self.tasks {
|
|
||||||
slot.tick(event, bus);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use core::time::Duration;
|
use core::time::Duration;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct Periodically {
|
pub struct Periodically {
|
||||||
last_run: Instant,
|
last_run: Instant,
|
||||||
duration: Duration
|
duration: Duration
|
||||||
|
Loading…
x
Reference in New Issue
Block a user