Compare commits
22 Commits
e57ceeb149
...
63c913a8e6
Author | SHA1 | Date | |
---|---|---|---|
63c913a8e6 | |||
cdc82cdbf9 | |||
01fdc11552 | |||
a0d524b825 | |||
f9a8b32d3e | |||
473aff9aa3 | |||
5c2fa005c7 | |||
e9bbdd13c2 | |||
f180171ee7 | |||
da2a8f5bfc | |||
d09c82c3fc | |||
c9496e3dc3 | |||
bbdb3d7404 | |||
a237bb7dc8 | |||
3a850105a8 | |||
7d44c11c78 | |||
457ae73c50 | |||
8bb46b6f78 | |||
4b256bc0d9 | |||
2af9918239 | |||
bfdf7c3230 | |||
b6e34111ff |
1
espflash.toml
Normal file
1
espflash.toml
Normal file
@ -0,0 +1 @@
|
|||||||
|
partition_table = "partitions.csv"
|
5
partitions.csv
Normal file
5
partitions.csv
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# 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,
|
|
@ -58,15 +58,17 @@ impl Shader for ThinkingShader {
|
|||||||
impl<T: Surface> IdleTask<T> {
|
impl<T: Surface> IdleTask<T> {
|
||||||
pub fn new<S: Surfaces<Surface = T>>(surfaces: &mut S) -> Self {
|
pub fn new<S: Surfaces<Surface = T>>(surfaces: &mut S) -> Self {
|
||||||
IdleTask {
|
IdleTask {
|
||||||
solid: surfaces.new_surface(&Rectangle::everything()).unwrap(),
|
solid: surfaces.new_surface(Rectangle::everything()).unwrap(),
|
||||||
surface: surfaces.new_surface(&Rectangle::everything()).unwrap(),
|
surface: surfaces.new_surface(Rectangle::everything()).unwrap(),
|
||||||
shimmer: surfaces.new_surface(&Rectangle::everything()).unwrap(),
|
shimmer: surfaces.new_surface(Rectangle::everything()).unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Surface> Task for IdleTask<T> {
|
impl<T: Surface> Task for IdleTask<T> {
|
||||||
fn start(&mut self) {
|
fn name(&self) -> &'static str { "Idle" }
|
||||||
|
|
||||||
|
fn start(&mut self, _bus: &mut EventBus) {
|
||||||
self.solid.set_shader(Box::new(SolidShader { }));
|
self.solid.set_shader(Box::new(SolidShader { }));
|
||||||
self.surface.set_shader(Box::new(ThinkingShader { }));
|
self.surface.set_shader(Box::new(ThinkingShader { }));
|
||||||
self.shimmer.set_shader(Box::new(ShimmerShader { }));
|
self.shimmer.set_shader(Box::new(ShimmerShader { }));
|
||||||
@ -76,9 +78,7 @@ impl<T: Surface> Task for IdleTask<T> {
|
|||||||
self.shimmer.set_opacity(64);
|
self.shimmer.set_opacity(64);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tick(&mut self, event: &Event, bus: &mut EventBus) {}
|
fn stop(&mut self, _bus: &mut EventBus) {
|
||||||
|
|
||||||
fn stop(&mut self) {
|
|
||||||
self.solid.clear_shader();
|
self.solid.clear_shader();
|
||||||
self.surface.clear_shader();
|
self.surface.clear_shader();
|
||||||
self.shimmer.clear_shader();
|
self.shimmer.clear_shader();
|
||||||
@ -169,22 +169,24 @@ impl<T: Surface> TestPattern<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Surface> Task for TestPattern<T> {
|
impl<T: Surface> Task for TestPattern<T> {
|
||||||
fn start(&mut self) {
|
fn name(&self) -> &'static str { "TestPattern" }
|
||||||
self.surface.set_shader(Box::new(self.pattern.clone()));
|
|
||||||
|
fn start(&mut self, _bus: &mut EventBus) {
|
||||||
|
self.surface.set_shader(Box::new(self.pattern));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tick(&mut self, event: &Event, bus: &mut EventBus) {
|
fn on_tick(&mut self, 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(Box::new(self.pattern.clone()));
|
self.surface.set_shader(Box::new(self.pattern));
|
||||||
|
bus.push(Event::new_property_change("animations.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));
|
||||||
//log::info!("Step {}", self.frame);
|
self.surface.set_rect( match self.pattern {
|
||||||
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)
|
||||||
@ -198,7 +200,7 @@ impl<T: Surface> Task for TestPattern<T> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stop(&mut self) {
|
fn stop(&mut self, _bus: &mut EventBus) {
|
||||||
self.surface.clear_shader();
|
self.surface.clear_shader();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use crate::events::{Event, EventBus};
|
use crate::events::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;
|
||||||
@ -6,7 +6,6 @@ use crate::render::{PixelView, Sample, Shader, Surface, Surfaces, HardwarePixel}
|
|||||||
use crate::task::Task;
|
use crate::task::Task;
|
||||||
|
|
||||||
use std::fmt::Debug;
|
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;
|
||||||
@ -82,9 +81,9 @@ 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.clone()),
|
rect: Some(rect),
|
||||||
slot: self.slot,
|
slot: self.slot,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
@ -179,12 +178,12 @@ impl Surfaces for ShaderChain {
|
|||||||
type Error = ();
|
type Error = ();
|
||||||
type Surface = BufferedSurface;
|
type Surface = BufferedSurface;
|
||||||
|
|
||||||
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> {
|
||||||
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.clone()
|
rect: area
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(BufferedSurface {
|
Ok(BufferedSurface {
|
||||||
@ -225,7 +224,7 @@ impl BufferedSurfacePool {
|
|||||||
impl Surfaces for BufferedSurfacePool {
|
impl Surfaces for BufferedSurfacePool {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
type Surface = <ShaderChain as Surfaces>::Surface;
|
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.write().unwrap().new_surface(area)
|
self.pool.write().unwrap().new_surface(area)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,91 +235,13 @@ impl Surfaces for BufferedSurfacePool {
|
|||||||
|
|
||||||
|
|
||||||
impl Task for BufferedSurfacePool {
|
impl Task for BufferedSurfacePool {
|
||||||
fn tick(&mut self, event: &Event, bus: &mut EventBus) {
|
fn on_tick(&mut self, _bus: &mut EventBus) {
|
||||||
if self.pool.read().unwrap().is_dirty() {
|
if self.pool.read().unwrap().is_dirty() {
|
||||||
self.pool.write().unwrap().commit();
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Pixbuf: AsMilliwatts + IndexMut<usize, Output=Self::Pixel> + Send {
|
pub trait Pixbuf: AsMilliwatts + IndexMut<usize, Output=Self::Pixel> + Send {
|
||||||
type Pixel: HardwarePixel;
|
type Pixel: HardwarePixel;
|
||||||
fn new() -> Self;
|
fn new() -> Self;
|
||||||
|
264
src/events.rs
264
src/events.rs
@ -1,100 +1,83 @@
|
|||||||
use core::fmt::Debug;
|
use core::fmt::{Debug, Display};
|
||||||
|
use std::{collections::{LinkedList, VecDeque}, sync::{Arc, Mutex}};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
use rgb::Rgb;
|
||||||
pub enum InputEvent {
|
|
||||||
PowerOn,
|
|
||||||
PowerOff,
|
|
||||||
NetworkActivity,
|
|
||||||
NetworkOnline,
|
|
||||||
NetworkOffline
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum Variant {
|
pub enum Variant {
|
||||||
|
SignedByte(i8),
|
||||||
Byte(u8),
|
Byte(u8),
|
||||||
UInt(u32),
|
UInt(u32),
|
||||||
Int(i32),
|
Int(i32),
|
||||||
BigUInt(u64),
|
BigUInt(u64),
|
||||||
BigInt(i64),
|
BigInt(i64),
|
||||||
Boolean(bool)
|
Boolean(bool),
|
||||||
|
String(String),
|
||||||
|
RGB(Rgb<u8>)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
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!("Expected Variant::$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<'a> From<&'a str> for Variant {
|
||||||
|
fn from(value: &'a str) -> Self {
|
||||||
|
Variant::String(value.to_owned())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Variant {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Variant::BigInt(i) => (i as &dyn Display).fmt(f),
|
||||||
|
Variant::BigUInt(i) => (i as &dyn Display).fmt(f),
|
||||||
|
Variant::Boolean(i) => (i as &dyn Display).fmt(f),
|
||||||
|
Variant::Byte(i) => (i as &dyn Display).fmt(f),
|
||||||
|
Variant::Int(i) => (i as &dyn Display).fmt(f),
|
||||||
|
Variant::String(i) => (i as &dyn Display).fmt(f),
|
||||||
|
Variant::UInt(i) => (i as &dyn Display).fmt(f),
|
||||||
|
Variant::SignedByte(b) => (b as &dyn Display).fmt(f),
|
||||||
|
Variant::RGB(rgb) => f.write_fmt(format_args!("[{}, {}, {}]", rgb.r, rgb.g, rgb.b)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
ReadyToRock,
|
ReadyToRock,
|
||||||
Tick,
|
Tick,
|
||||||
StartThing(&'static str),
|
StartThing(&'static str),
|
||||||
StopThing(&'static str),
|
StopThing(&'static str),
|
||||||
Input(InputEvent),
|
|
||||||
PropertyChange(&'static str, Variant)
|
PropertyChange(&'static str, Variant)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SystemState {
|
|
||||||
key: &'static str,
|
|
||||||
value: Variant,
|
|
||||||
values: Vec::<Box<SystemState>>
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SystemState {
|
|
||||||
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 fn new_tick() -> Self {
|
pub const fn new_tick() -> Self {
|
||||||
Event::Tick
|
Event::Tick
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,62 +85,127 @@ impl Event {
|
|||||||
Event::PropertyChange(key, Variant::from(data))
|
Event::PropertyChange(key, Variant::from(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_ready_to_rock() -> Self {
|
pub const fn new_ready_to_rock() -> Self {
|
||||||
Event::ReadyToRock
|
Event::ReadyToRock
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_input_event(event: InputEvent) -> Self {
|
pub const fn new_start_thing(name: &'static str) -> Self {
|
||||||
Event::Input(event)
|
Event::StartThing(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn new_stop_thing(name: &'static str) -> Self {
|
||||||
|
Event::StopThing(name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<u8> for Variant {
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
fn into(self) -> u8 {
|
pub struct Property {
|
||||||
match self {
|
key: &'static str,
|
||||||
Variant::Byte(b) => b,
|
value: Variant
|
||||||
_ => 0
|
}
|
||||||
|
|
||||||
|
impl Property {
|
||||||
|
pub const fn new(key: &'static str, value: Variant) -> Self {
|
||||||
|
Property {
|
||||||
|
key,
|
||||||
|
value: value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<bool> for Variant {
|
#[derive(Debug, Clone)]
|
||||||
fn from(value: bool) -> Self {
|
pub struct Properties {
|
||||||
Variant::Boolean(value)
|
contents: LinkedList<Property>
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
impl Properties {
|
||||||
impl From<i64> for Variant {
|
fn new() -> Self {
|
||||||
fn from(value: i64) -> Self {
|
Properties {
|
||||||
Variant::BigInt(value)
|
contents: LinkedList::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<u8> for Variant {
|
fn get_key(&self, key: &'static str) -> Option<&Property> {
|
||||||
fn from(value: u8) -> Self {
|
for next in self.contents.iter() {
|
||||||
Variant::Byte(value)
|
if next.key == key {
|
||||||
|
return Some(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_key_mut(&mut self, key: &'static str) -> Option<&mut Property> {
|
||||||
|
for next in self.contents.iter_mut() {
|
||||||
|
if next.key == key {
|
||||||
|
return Some(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, key: &'static str) -> Option<Variant> {
|
||||||
|
match self.get_key(key) {
|
||||||
|
None => None,
|
||||||
|
Some(v) => Some(v.value.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set<V>(&mut self, key: &'static str, value: V) -> bool where Variant: From<V> {
|
||||||
|
let as_variant: Variant = value.into();
|
||||||
|
match self.get_key_mut(key) {
|
||||||
|
None => {
|
||||||
|
self.contents.push_front(Property::new(key, as_variant));
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
Some(found_key) => {
|
||||||
|
if found_key.value != as_variant {
|
||||||
|
found_key.value = as_variant;
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct EventBus {
|
pub struct EventBus {
|
||||||
pending: Vec<Event>
|
pending: Arc<Mutex<VecDeque<Event>>>,
|
||||||
|
props: Arc<Mutex<Properties>>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventBus {
|
impl EventBus {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
EventBus {
|
EventBus {
|
||||||
pending: Vec::new()
|
pending: Arc::new(Mutex::new(VecDeque::with_capacity(32))),
|
||||||
|
props: Arc::new(Mutex::new(Properties::new()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn next(&mut self) -> Event {
|
pub fn next(&mut self) -> Event {
|
||||||
if self.pending.len() == 0 {
|
let next = self.pending.lock().unwrap().pop_front().unwrap_or(Event::new_tick());
|
||||||
Event::new_tick()
|
|
||||||
} else {
|
match next {
|
||||||
self.pending.pop().unwrap()
|
Event::PropertyChange(key, value) => {
|
||||||
|
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.push(event);
|
self.pending.lock().unwrap().push_back(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn property(&self, key: &'static str) -> Option<Variant> {
|
||||||
|
self.props.lock().unwrap().get(key)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -64,19 +64,19 @@ impl<S: CoordinateSpace> Coordinates<S> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn top_left() -> Self {
|
const fn top_left() -> Self {
|
||||||
Self::new(S::Data::MIN, S::Data::MIN)
|
Self::new(S::Data::MIN, S::Data::MIN)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn top_right() -> Self {
|
const fn top_right() -> Self {
|
||||||
Self::new(S::Data::MAX, S::Data::MIN)
|
Self::new(S::Data::MAX, S::Data::MIN)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bottom_left() -> Self {
|
const fn bottom_left() -> Self {
|
||||||
Self::new(S::Data::MIN, S::Data::MAX)
|
Self::new(S::Data::MIN, S::Data::MAX)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bottom_right() -> Self {
|
const fn bottom_right() -> Self {
|
||||||
Self::new(S::Data::MAX, S::Data::MAX)
|
Self::new(S::Data::MAX, S::Data::MAX)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,15 +101,13 @@ 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 fn everything() -> Self {
|
pub const 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()
|
||||||
|
51
src/main.rs
51
src/main.rs
@ -9,8 +9,11 @@ mod animations;
|
|||||||
mod mappings;
|
mod mappings;
|
||||||
mod buffers;
|
mod buffers;
|
||||||
mod events;
|
mod events;
|
||||||
|
mod scenes;
|
||||||
|
|
||||||
use events::Event;
|
use events::Event;
|
||||||
|
use rgb::Rgb;
|
||||||
|
use scenes::Sequencer;
|
||||||
|
|
||||||
use crate::events::EventBus;
|
use crate::events::EventBus;
|
||||||
use crate::platform::{DefaultBoard, Board};
|
use crate::platform::{DefaultBoard, Board};
|
||||||
@ -21,39 +24,67 @@ use crate::geometry::Rectangle;
|
|||||||
fn main() {
|
fn main() {
|
||||||
let mut board: DefaultBoard = Board::take();
|
let mut board: DefaultBoard = Board::take();
|
||||||
|
|
||||||
log::info!("Board: {}", core::any::type_name_of_val(&board));
|
log::info!("🐛 Booting Renderbug!");
|
||||||
|
|
||||||
log::info!("Creating tasks");
|
log::info!("📡 Board {}", core::any::type_name_of_val(&board));
|
||||||
|
|
||||||
|
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::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(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");
|
bus.push(Event::new_property_change("colors.primary", Rgb::new(255, 128, 128)));
|
||||||
|
bus.push(Event::new_property_change("system.board.chip_id", DefaultBoard::chip_id()));
|
||||||
|
bus.push(Event::new_property_change("system.network.online", false));
|
||||||
|
|
||||||
|
log::info!("Priming events...");
|
||||||
|
let initial_tasks = [
|
||||||
|
"Renderer",
|
||||||
|
"renderbug::scenes::Sequencer",
|
||||||
|
"renderbug::buffers::BufferedSurfacePool",
|
||||||
|
"renderbug::platform::esp32::WifiTask",
|
||||||
|
"renderbug::platform::esp32::MqttTask",
|
||||||
|
"renderbug::platform::esp32::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 => (),
|
||||||
_ => log::info!("Event: {:?}", next_event)
|
Event::ReadyToRock => {
|
||||||
|
log::info!("🚀 Ready to rock and roll");
|
||||||
|
}
|
||||||
|
_ => log::info!("⚡ Event: {:?}", next_event)
|
||||||
}
|
}
|
||||||
|
inputs.tick(&next_event, &mut bus);
|
||||||
animations.tick(&next_event, &mut bus);
|
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 {
|
||||||
rect: Rectangle<Virtual>,
|
max_x: u8,
|
||||||
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.rect.bottom_right.x {
|
if self.idx as u8 == self.max_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 {
|
||||||
rect: rect.clone(),
|
max_x: rect.bottom_right.x,
|
||||||
idx: 0,
|
idx: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -169,6 +169,12 @@ impl<const STRIDE_NUM: usize> StrideMapping<STRIDE_NUM> {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new_albus() -> Self {
|
||||||
|
Self::from_json(&[
|
||||||
|
(0, 0, 50 * 3, 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();
|
||||||
@ -209,8 +215,6 @@ 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.clone()));
|
pixel = pixel.saturating_add(shader.draw(virtCoords));
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
self.total_mw += pixel.as_milliwatts();
|
self.total_mw += pixel.as_milliwatts();
|
||||||
|
@ -1,24 +1,22 @@
|
|||||||
use core::borrow::BorrowMut;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::sync::Mutex;
|
|
||||||
use std::thread::JoinHandle;
|
use std::thread::JoinHandle;
|
||||||
use std::thread::ScopedJoinHandle;
|
|
||||||
|
|
||||||
use chrono::DateTime;
|
use chrono::DateTime;
|
||||||
use chrono::Timelike;
|
use chrono::Timelike;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
|
|
||||||
use esp_idf_svc::eventloop::{EspSubscription, EspSystemEventLoop, System};
|
use esp_idf_svc::eventloop::{EspSubscription, EspSystemEventLoop, System};
|
||||||
|
use esp_idf_svc::hal::gpio::Pins;
|
||||||
use esp_idf_svc::hal::modem::Modem;
|
use esp_idf_svc::hal::modem::Modem;
|
||||||
use esp_idf_svc::hal::prelude::Peripherals;
|
use esp_idf_svc::hal::prelude::Peripherals;
|
||||||
|
use esp_idf_svc::hal::rmt::RMT;
|
||||||
use esp_idf_svc::hal::task::thread::ThreadSpawnConfiguration;
|
use esp_idf_svc::hal::task::thread::ThreadSpawnConfiguration;
|
||||||
use esp_idf_svc::mqtt::client::EspMqttClient;
|
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::mqtt::client::MqttClientConfiguration;
|
||||||
use esp_idf_svc::netif::IpEvent;
|
use esp_idf_svc::netif::IpEvent;
|
||||||
use esp_idf_svc::nvs::{EspDefaultNvsPartition, EspNvsPartition, NvsDefault};
|
use esp_idf_svc::nvs::{EspDefaultNvsPartition, EspNvsPartition, NvsDefault};
|
||||||
use esp_idf_svc::sntp::EspSntp;
|
use esp_idf_svc::sntp::EspSntp;
|
||||||
|
use esp_idf_svc::sntp::SyncStatus;
|
||||||
use esp_idf_svc::sys::esp_efuse_mac_get_default;
|
use esp_idf_svc::sys::esp_efuse_mac_get_default;
|
||||||
use esp_idf_svc::wifi::{AuthMethod, ClientConfiguration, Configuration, EspWifi, WifiEvent};
|
use esp_idf_svc::wifi::{AuthMethod, ClientConfiguration, Configuration, EspWifi, WifiEvent};
|
||||||
use rgb::Rgb;
|
use rgb::Rgb;
|
||||||
@ -31,79 +29,34 @@ use crate::buffers::BufferedSurfacePool;
|
|||||||
use crate::buffers::Pixbuf;
|
use crate::buffers::Pixbuf;
|
||||||
use crate::events::Event;
|
use crate::events::Event;
|
||||||
use crate::events::EventBus;
|
use crate::events::EventBus;
|
||||||
|
use crate::events::Variant;
|
||||||
use crate::lib8::interpolate::lerp8by8;
|
use crate::lib8::interpolate::lerp8by8;
|
||||||
use crate::mappings::StrideMapping;
|
use crate::mappings::StrideMapping;
|
||||||
use crate::task::FixedSizeScheduler;
|
use crate::task::FixedSizeScheduler;
|
||||||
use crate::task::Task;
|
use crate::task::Task;
|
||||||
use crate::time::Periodically;
|
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 {
|
pub struct Esp32Board {
|
||||||
sys_loop: EspSystemEventLoop,
|
sys_loop: EspSystemEventLoop,
|
||||||
modem: Option<Modem>,
|
modem: Option<Modem>,
|
||||||
|
pins: Option<Pins>,
|
||||||
|
rmt: Option<RMT>,
|
||||||
surfaces: BufferedSurfacePool,
|
surfaces: BufferedSurfacePool,
|
||||||
output: Option<<Self as Board>::Output>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Board for Esp32Board {
|
impl Board for Esp32Board {
|
||||||
type Output = StrideOutput<[Rgb<u8>; 310], FastWs2812Esp32Rmt<'static>>;
|
type Output = StrideOutput<[Rgb<u8>; 310], FastWs2812Esp32Rmt<'static>>;
|
||||||
type Surfaces = BufferedSurfacePool;
|
type Surfaces = BufferedSurfacePool;
|
||||||
type Scheduler = FixedSizeScheduler<4>;
|
type Scheduler = FixedSizeScheduler<4>;
|
||||||
|
|
||||||
|
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 {
|
fn take() -> Self {
|
||||||
// It is necessary to call this function once. Otherwise some patches to the runtime
|
// 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
|
// implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71
|
||||||
@ -115,18 +68,25 @@ impl Board for Esp32Board {
|
|||||||
let peripherals = Peripherals::take().unwrap();
|
let peripherals = Peripherals::take().unwrap();
|
||||||
let sys_loop = EspSystemEventLoop::take().unwrap();
|
let sys_loop = EspSystemEventLoop::take().unwrap();
|
||||||
|
|
||||||
let mut chip_id: [u8; 8] = [0; 8];
|
Esp32Board {
|
||||||
unsafe {
|
modem: Some(peripherals.modem),
|
||||||
esp_efuse_mac_get_default(&mut chip_id as *mut u8);
|
sys_loop: sys_loop,
|
||||||
|
surfaces: BufferedSurfacePool::new(),
|
||||||
|
pins: Some(peripherals.pins),
|
||||||
|
rmt: Some(peripherals.rmt)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
log::info!("Setting up output for chip ID {:x?}", chip_id);
|
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_VOLTS : u32 = 5;
|
||||||
const POWER_MA : u32 = 500;
|
const POWER_MA : u32 = 500;
|
||||||
const MAX_POWER_MW : u32 = POWER_VOLTS * POWER_MA;
|
const MAX_POWER_MW : u32 = POWER_VOLTS * POWER_MA;
|
||||||
let pins = peripherals.pins;
|
|
||||||
|
|
||||||
|
let pins = self.pins.take().unwrap();
|
||||||
|
let rmt = self.rmt.take().unwrap();
|
||||||
ThreadSpawnConfiguration {
|
ThreadSpawnConfiguration {
|
||||||
pin_to_core: Some(esp_idf_svc::hal::cpu::Core::Core1),
|
pin_to_core: Some(esp_idf_svc::hal::cpu::Core::Core1),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@ -135,12 +95,12 @@ impl Board for Esp32Board {
|
|||||||
// But the implementation spawns a thread based on the core the driver was created in,
|
// But the implementation spawns a thread based on the core the driver was created in,
|
||||||
// so we create the driver in another thread briefly.
|
// so we create the driver in another thread briefly.
|
||||||
// Fun stuff.
|
// Fun stuff.
|
||||||
let output = match chip_id { // panel test board
|
let output = match Self::chip_id().to_be_bytes() { // panel test board
|
||||||
[72, 202, 67, 89, 145, 204, 0, 0] => {
|
[72, 202, 67, 89, 145, 204, 0, 0] => {
|
||||||
StrideOutput::new(
|
StrideOutput::new(
|
||||||
Pixbuf::new(),
|
Pixbuf::new(),
|
||||||
StrideMapping::new_panel(),
|
StrideMapping::new_panel(),
|
||||||
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(peripherals.rmt.channel0, pins.gpio5).unwrap() }).join().unwrap(),
|
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(rmt.channel0, pins.gpio5).unwrap() }).join().unwrap(),
|
||||||
MAX_POWER_MW
|
MAX_POWER_MW
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -148,15 +108,15 @@ impl Board for Esp32Board {
|
|||||||
StrideOutput::new(
|
StrideOutput::new(
|
||||||
Pixbuf::new(),
|
Pixbuf::new(),
|
||||||
StrideMapping::new_jar(),
|
StrideMapping::new_jar(),
|
||||||
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(peripherals.rmt.channel0, pins.gpio14).unwrap() }).join().unwrap(),
|
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(rmt.channel0, pins.gpio14).unwrap() }).join().unwrap(),
|
||||||
MAX_POWER_MW
|
MAX_POWER_MW
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
[0x4a, 0xca, 0x43, 0x59, 0x85, 0x58, 0x0, 0x0] => { // Albus the tree
|
[0x4a, 0xca, 0x43, 0x59, 0x85, 0x58, 0x0, 0x0] => { // Albus the tree
|
||||||
StrideOutput::new(
|
StrideOutput::new(
|
||||||
Pixbuf::new(),
|
Pixbuf::new(),
|
||||||
StrideMapping::new_jar(),
|
StrideMapping::new_albus(),
|
||||||
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(peripherals.rmt.channel0, pins.gpio5).unwrap() }).join().unwrap(),
|
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(rmt.channel0, pins.gpio5).unwrap() }).join().unwrap(),
|
||||||
MAX_POWER_MW
|
MAX_POWER_MW
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -164,7 +124,7 @@ impl Board for Esp32Board {
|
|||||||
StrideOutput::new(
|
StrideOutput::new(
|
||||||
Pixbuf::new(),
|
Pixbuf::new(),
|
||||||
StrideMapping::new_fairylights(),
|
StrideMapping::new_fairylights(),
|
||||||
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(peripherals.rmt.channel0, pins.gpio5).unwrap() }).join().unwrap(),
|
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(rmt.channel0, pins.gpio5).unwrap() }).join().unwrap(),
|
||||||
MAX_POWER_MW
|
MAX_POWER_MW
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -172,7 +132,7 @@ impl Board for Esp32Board {
|
|||||||
StrideOutput::new(
|
StrideOutput::new(
|
||||||
Pixbuf::new(),
|
Pixbuf::new(),
|
||||||
StrideMapping::new_fairylights(),
|
StrideMapping::new_fairylights(),
|
||||||
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(peripherals.rmt.channel0, pins.gpio5).unwrap() }).join().unwrap(),
|
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(rmt.channel0, pins.gpio5).unwrap() }).join().unwrap(),
|
||||||
MAX_POWER_MW
|
MAX_POWER_MW
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -180,7 +140,7 @@ impl Board for Esp32Board {
|
|||||||
StrideOutput::new(
|
StrideOutput::new(
|
||||||
Pixbuf::new(),
|
Pixbuf::new(),
|
||||||
StrideMapping::new_cyberplague(),
|
StrideMapping::new_cyberplague(),
|
||||||
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(peripherals.rmt.channel0, pins.gpio13).unwrap() }).join().unwrap(),
|
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(rmt.channel0, pins.gpio13).unwrap() }).join().unwrap(),
|
||||||
MAX_POWER_MW
|
MAX_POWER_MW
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -188,7 +148,7 @@ impl Board for Esp32Board {
|
|||||||
StrideOutput::new(
|
StrideOutput::new(
|
||||||
Pixbuf::new(),
|
Pixbuf::new(),
|
||||||
StrideMapping::new(),
|
StrideMapping::new(),
|
||||||
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(peripherals.rmt.channel0, pins.gpio5).unwrap() }).join().unwrap(),
|
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(rmt.channel0, pins.gpio5).unwrap() }).join().unwrap(),
|
||||||
MAX_POWER_MW
|
MAX_POWER_MW
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -197,16 +157,7 @@ impl Board for Esp32Board {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
}.set().unwrap();
|
}.set().unwrap();
|
||||||
|
|
||||||
Esp32Board {
|
output
|
||||||
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 {
|
fn surfaces(&mut self) -> Self::Surfaces {
|
||||||
@ -224,7 +175,7 @@ impl Board for Esp32Board {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone)]
|
||||||
struct ScheduleEntry {
|
struct ScheduleEntry {
|
||||||
hour: u8,
|
hour: u8,
|
||||||
brightness: u8
|
brightness: u8
|
||||||
@ -238,7 +189,7 @@ struct CircadianRhythm {
|
|||||||
impl CircadianRhythm {
|
impl CircadianRhythm {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
CircadianRhythm {
|
CircadianRhythm {
|
||||||
time_check: Periodically::new_every_n_seconds(5),
|
time_check: Periodically::new_every_n_seconds(60),
|
||||||
schedule: [
|
schedule: [
|
||||||
ScheduleEntry { hour: 0, brightness: 0 },
|
ScheduleEntry { hour: 0, brightness: 0 },
|
||||||
ScheduleEntry { hour: 5, brightness: 0 },
|
ScheduleEntry { hour: 5, brightness: 0 },
|
||||||
@ -254,11 +205,17 @@ impl CircadianRhythm {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.push(Event::new_property_change("output.brightness", next_brightness));
|
||||||
|
}
|
||||||
|
|
||||||
fn brightness_for_time(&self, hour: u8, minute: u8) -> u8 {
|
fn brightness_for_time(&self, hour: u8, minute: u8) -> u8 {
|
||||||
let mut start = self.schedule.last().unwrap();
|
let mut start = self.schedule.last().unwrap();
|
||||||
let mut end = self.schedule.first().unwrap();
|
let mut end = self.schedule.first().unwrap();
|
||||||
for cur in self.schedule.iter() {
|
for cur in self.schedule.iter() {
|
||||||
if (cur.hour <= hour ) {
|
if cur.hour <= hour {
|
||||||
start = cur;
|
start = cur;
|
||||||
} else {
|
} else {
|
||||||
end = cur;
|
end = cur;
|
||||||
@ -298,11 +255,20 @@ fn map_range(x: u16, in_min: u16, in_max: u16, out_min: u16, out_max: u16) -> u1
|
|||||||
|
|
||||||
|
|
||||||
impl Task for CircadianRhythm {
|
impl Task for CircadianRhythm {
|
||||||
fn tick(&mut self, event: &Event, bus: &mut EventBus) {
|
fn on_ready(&mut self, bus: &mut EventBus) {
|
||||||
if self.time_check.tick() || event.eq(&Event::ReadyToRock) {
|
self.update_brightness(bus);
|
||||||
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));
|
fn on_property_change(&mut self, key: &'static str, value: &Variant, bus: &mut EventBus) {
|
||||||
|
match (key, value) {
|
||||||
|
("system.time.synchronized", Variant::Boolean(true)) => self.update_brightness(bus),
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_tick(&mut self, bus: &mut EventBus) {
|
||||||
|
if self.time_check.tick() {
|
||||||
|
self.update_brightness(bus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -316,16 +282,17 @@ impl MqttTask {
|
|||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
MqttTask {
|
MqttTask {
|
||||||
conn_thread: None,
|
conn_thread: None,
|
||||||
client: None
|
client: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_mqtt(&mut self) {
|
fn start_mqtt(&mut self, bus: &EventBus) {
|
||||||
log::info!("Starting MQTT");
|
log::info!("Starting MQTT");
|
||||||
|
let chip_id: u64 = bus.property("system.board.chip_id").unwrap().into();
|
||||||
let (client, mut conn) = EspMqttClient::new(
|
let (client, mut conn) = EspMqttClient::new(
|
||||||
"mqtt://10.0.0.2:1883",
|
"mqtt://10.0.0.2:1883",
|
||||||
&MqttClientConfiguration {
|
&MqttClientConfiguration {
|
||||||
client_id: Some("renderbug-rs"),
|
client_id: Some(&format!("{:X}", chip_id)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
).unwrap();
|
).unwrap();
|
||||||
@ -338,39 +305,50 @@ impl MqttTask {
|
|||||||
}).unwrap());
|
}).unwrap());
|
||||||
self.client = Some(client);
|
self.client = Some(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn topic_prefix(bus: &EventBus) -> String {
|
||||||
|
let chip_id: u64 = bus.property("system.board.chip_id").unwrap().into();
|
||||||
|
|
||||||
|
format!("homeassistant-test/renderbug/{:X}", chip_id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Task for MqttTask {
|
impl Task for MqttTask {
|
||||||
fn tick(&mut self, event: &Event, bus: &mut EventBus) {
|
fn start(&mut self, bus: &mut EventBus) {
|
||||||
match event {
|
bus.push(Event::new_property_change("mqtt.online", false));
|
||||||
Event::Input(crate::events::InputEvent::NetworkOnline) => {
|
}
|
||||||
|
|
||||||
|
fn on_property_change(&mut self, key: &'static str, value: &Variant, bus: &mut EventBus) {
|
||||||
|
match (key, value) {
|
||||||
|
("system.network.online", Variant::Boolean(true)) => {
|
||||||
log::info!("Registering with MQTT");
|
log::info!("Registering with MQTT");
|
||||||
|
|
||||||
self.start_mqtt();
|
self.start_mqtt(bus);
|
||||||
|
|
||||||
if let Some(ref mut client) = self.client {
|
if let Some(ref mut client) = self.client {
|
||||||
client.enqueue(
|
client.enqueue(
|
||||||
"homeassistant-test/renderbug/rust",
|
Self::topic_prefix(bus).as_str(),
|
||||||
esp_idf_svc::mqtt::client::QoS::AtLeastOnce,
|
esp_idf_svc::mqtt::client::QoS::AtLeastOnce,
|
||||||
false,
|
false,
|
||||||
"hello, world".as_bytes()
|
"hello, world".as_bytes()
|
||||||
).unwrap();
|
).unwrap();
|
||||||
log::info!("MQTT should be online!");
|
log::info!("MQTT should be online!");
|
||||||
|
|
||||||
|
bus.push(Event::new_property_change("mqtt.online", true));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Event::PropertyChange(name, value) => {
|
(name, value) => {
|
||||||
if let Some(ref mut client) = self.client {
|
if let Some(ref mut client) = self.client {
|
||||||
let payload = format!("name={} value={:?}", name, value);
|
let prefix = Self::topic_prefix(bus);
|
||||||
|
|
||||||
client.enqueue(
|
client.enqueue(
|
||||||
"homeassistant-test/renderbug/rust/property-change",
|
format!("{}/properties/{}", prefix, name).as_str(),
|
||||||
esp_idf_svc::mqtt::client::QoS::AtLeastOnce,
|
esp_idf_svc::mqtt::client::QoS::AtLeastOnce,
|
||||||
false,
|
false,
|
||||||
payload.as_bytes()
|
format!("{}", value).as_bytes()
|
||||||
).unwrap();
|
).unwrap();
|
||||||
log::info!("property change bump: {}", payload);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => ()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -381,33 +359,36 @@ impl Debug for WifiTask {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
|
||||||
enum WifiState {
|
|
||||||
Stopped,
|
|
||||||
Disconnected,
|
|
||||||
Connecting,
|
|
||||||
Connected
|
|
||||||
}
|
|
||||||
|
|
||||||
struct WifiTask {
|
struct WifiTask {
|
||||||
wifi: EspWifi<'static>,
|
wifi: EspWifi<'static>,
|
||||||
ntp: EspSntp<'static>,
|
ntp: EspSntp<'static>,
|
||||||
connection_check: Periodically,
|
connection_check: Periodically,
|
||||||
state: Arc<Mutex<WifiState>>,
|
|
||||||
last_state: WifiState,
|
|
||||||
sys_loop: EspSystemEventLoop,
|
sys_loop: EspSystemEventLoop,
|
||||||
wifi_sub: Option<EspSubscription<'static, System>>,
|
wifi_sub: Option<EspSubscription<'static, System>>,
|
||||||
ip_sub: Option<EspSubscription<'static, System>>
|
ip_sub: Option<EspSubscription<'static, System>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WifiTask {
|
impl WifiTask {
|
||||||
fn new(modem: Modem, sys_loop: EspSystemEventLoop, nvs: &EspNvsPartition<NvsDefault>) -> Self {
|
fn new(modem: Modem, sys_loop: EspSystemEventLoop, nvs: &EspNvsPartition<NvsDefault>) -> Self {
|
||||||
log::info!("Installing wifi driver");
|
log::info!("Installing wifi driver");
|
||||||
let mut wifi = EspWifi::new(
|
let wifi = EspWifi::new(
|
||||||
modem,
|
modem,
|
||||||
sys_loop.clone(),
|
sys_loop.clone(),
|
||||||
Some(nvs.clone())
|
Some(nvs.clone())
|
||||||
).unwrap();
|
).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 {
|
let wifi_config = Configuration::Client(ClientConfiguration {
|
||||||
ssid: "The Frequency".try_into().unwrap(),
|
ssid: "The Frequency".try_into().unwrap(),
|
||||||
bssid: None,
|
bssid: None,
|
||||||
@ -416,22 +397,7 @@ impl WifiTask {
|
|||||||
channel: None,
|
channel: None,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
wifi.set_configuration(&wifi_config).unwrap();
|
self.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.start().unwrap();
|
||||||
self.wifi.connect().unwrap();
|
self.wifi.connect().unwrap();
|
||||||
}
|
}
|
||||||
@ -444,69 +410,44 @@ impl WifiTask {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Task for WifiTask {
|
impl Task for WifiTask {
|
||||||
fn start(&mut self) {
|
fn start(&mut self, bus: &mut EventBus) {
|
||||||
log::info!("Starting wifi!");
|
log::info!("Starting wifi!");
|
||||||
|
let mut wifi_bus = bus.clone();
|
||||||
let wifi_state = self.state.clone();
|
|
||||||
self.wifi_sub = Some(self.sys_loop.subscribe::<WifiEvent, _>( move |evt| {
|
self.wifi_sub = Some(self.sys_loop.subscribe::<WifiEvent, _>( move |evt| {
|
||||||
log::warn!("wifi event {:?}", evt);
|
log::debug!("wifi event {:?}", evt);
|
||||||
let next_state = match evt {
|
wifi_bus.push(Event::new_property_change("system.network.online", false));
|
||||||
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());
|
}).unwrap());
|
||||||
let ip_state = self.state.clone();
|
let mut ip_bus = bus.clone();
|
||||||
self.ip_sub = Some(self.sys_loop.subscribe::<IpEvent, _>(move |evt| {
|
self.ip_sub = Some(self.sys_loop.subscribe::<IpEvent, _>(move |evt| {
|
||||||
log::warn!("ip event {:?}", evt);
|
log::debug!("ip event {:?}", evt);
|
||||||
match evt {
|
match evt {
|
||||||
IpEvent::DhcpIpAssigned(_) => {
|
IpEvent::DhcpIpAssigned(addr) => {
|
||||||
let mut state = ip_state.lock().unwrap();
|
ip_bus.push(Event::new_property_change("system.network.ip", addr.ip().to_string()));
|
||||||
*state = WifiState::Connected;
|
ip_bus.push(Event::new_property_change("system.network.gateway", addr.gateway().to_string()));
|
||||||
|
ip_bus.push(Event::new_property_change("system.network.online", true));
|
||||||
},
|
},
|
||||||
_ => ()
|
_ => ()
|
||||||
}
|
}
|
||||||
}).unwrap());
|
}).unwrap());
|
||||||
|
|
||||||
self.connect();
|
self.connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tick(&mut self, event: &Event, bus: &mut EventBus) {
|
fn on_tick(&mut self, bus: &mut EventBus) {
|
||||||
if self.connection_check.tick() {
|
if self.connection_check.tick() {
|
||||||
let cur_state = *self.state.lock().unwrap();
|
if bus.property("system.network.online").unwrap() == Variant::Boolean(true) {
|
||||||
|
match self.ntp.get_sync_status() {
|
||||||
if self.last_state != cur_state {
|
SyncStatus::Completed => bus.push(Event::new_property_change("system.time.synchronized", true)),
|
||||||
match cur_state {
|
_ => bus.push(Event::new_property_change("system.time.synchronized", false))
|
||||||
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) {
|
fn stop(&mut self, bus: &mut EventBus) {
|
||||||
log::info!("Stopping wifi");
|
log::info!("Stopping wifi");
|
||||||
|
self.wifi_sub.take().unwrap();
|
||||||
|
self.ip_sub.take().unwrap();
|
||||||
self.disconnect();
|
self.disconnect();
|
||||||
|
bus.push(Event::new_property_change("system.network.online", false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,4 +20,5 @@ 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;
|
||||||
}
|
}
|
@ -57,7 +57,7 @@ impl<P: Pixbuf<Pixel=T::Color>, T: FastWrite> Output for StrideOutput<P, T> {
|
|||||||
|
|
||||||
fn on_event(&mut self, event: &crate::events::Event) {
|
fn on_event(&mut self, event: &crate::events::Event) {
|
||||||
match event {
|
match event {
|
||||||
crate::events::Event::PropertyChange("output.brightness", new_brightness) => self.brightness = new_brightness.clone().into(),
|
crate::events::Event::PropertyChange("output.brightness", Variant::Byte(new_brightness)) => self.brightness = *new_brightness,
|
||||||
_ => ()
|
_ => ()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ pub trait Shader: Send + Sync {
|
|||||||
pub trait Surfaces: Send + Sync {
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,7 +38,7 @@ pub trait Surface: Send + Sync {
|
|||||||
fn set_shader(&mut self, shader: Box<dyn Shader>);
|
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,7 +54,8 @@ 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> {
|
||||||
@ -64,7 +65,8 @@ 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -72,22 +74,23 @@ 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 tick(&mut self, event: &Event, _bus: &mut EventBus) {
|
fn on_property_change(&mut self, key: &'static str, value: &crate::events::Variant, _bus: &mut EventBus) {
|
||||||
match event {
|
self.output.on_event(&Event::new_property_change(key, value.clone()));
|
||||||
crate::events::Event::Tick => {
|
|
||||||
self.output.blank();
|
|
||||||
|
|
||||||
self.surfaces.render_to(&mut self.output, self.frame);
|
|
||||||
|
|
||||||
self.output.commit();
|
|
||||||
|
|
||||||
self.fps.insert(1);
|
|
||||||
self.frame += 1;
|
|
||||||
self.fps_display.run(|| {
|
|
||||||
log::info!("FPS: {}", self.fps.measurement());
|
|
||||||
});
|
|
||||||
},
|
|
||||||
_ => self.output.on_event(event)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
fn on_tick(&mut self, bus: &mut EventBus) {
|
||||||
|
self.output.blank();
|
||||||
|
|
||||||
|
self.surfaces.render_to(&mut self.output, self.frame);
|
||||||
|
|
||||||
|
self.output.commit();
|
||||||
|
|
||||||
|
self.frame += 1;
|
||||||
|
self.fps_display.run(|| {
|
||||||
|
self.fps.insert((self.frame - self.frame_count) as u32);
|
||||||
|
self.frame_count = self.frame;
|
||||||
|
let fps = self.fps.measurement();
|
||||||
|
bus.push(Event::new_property_change("output.fps", fps.rate() as u32));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
111
src/scenes.rs
Normal file
111
src/scenes.rs
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use crate::task::Task;
|
||||||
|
use crate::events::*;
|
||||||
|
|
||||||
|
#[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(&'static str, 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("system.network.online", 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.push(Event::new_property_change("scenes.current", startup_scene.name));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_property_change(&mut self, key: &'static str, value: &Variant, bus: &mut EventBus) {
|
||||||
|
match (key, value) {
|
||||||
|
("scenes.current", Variant::String(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(Event::new_property_change("scenes.current", scene.name))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
101
src/task.rs
101
src/task.rs
@ -1,17 +1,20 @@
|
|||||||
use core::fmt;
|
use core::fmt;
|
||||||
|
|
||||||
use crate::events::{Event, EventBus};
|
use crate::events::{Event, EventBus, Variant};
|
||||||
|
|
||||||
pub trait Task: Send {
|
pub trait Task: Send {
|
||||||
fn tick(&mut self, event: &Event, bus: &mut EventBus) {}
|
fn on_ready(&mut self, bus: &mut EventBus) {}
|
||||||
fn start(&mut self) {}
|
fn on_tick(&mut self, bus: &mut EventBus) {}
|
||||||
fn stop(&mut self) {}
|
fn on_property_change(&mut self, key: &'static str, value: &Variant, bus: &mut EventBus) {}
|
||||||
|
|
||||||
|
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, Copy, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
enum ScheduledState {
|
enum ScheduledState {
|
||||||
Stopped,
|
Stopped,
|
||||||
Start,
|
Start,
|
||||||
@ -36,75 +39,109 @@ 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::Start,
|
state: ScheduledState::Stopped,
|
||||||
task: task,
|
task: task,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start(&mut self) {
|
fn start(&mut self) {
|
||||||
self.state = match self.state {
|
match self.state {
|
||||||
ScheduledState::Stopped => ScheduledState::Start,
|
ScheduledState::Stopped => self.state = ScheduledState::Start,
|
||||||
ScheduledState::Stop => ScheduledState::Running,
|
ScheduledState::Stop => self.state = ScheduledState::Running,
|
||||||
_ => self.state
|
_ => ()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stop(&mut self) {
|
fn stop(&mut self) {
|
||||||
self.state = match self.state {
|
match self.state {
|
||||||
ScheduledState::Running => ScheduledState::Stop,
|
ScheduledState::Running => self.state = ScheduledState::Stop,
|
||||||
ScheduledState::Start => ScheduledState::Stopped,
|
ScheduledState::Start => self.state = ScheduledState::Stopped,
|
||||||
_ => self.state
|
_ => ()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tick(&mut self, event: &Event, bus: &mut EventBus) {
|
fn tick(&mut self, event: &Event, bus: &mut EventBus) {
|
||||||
self.state = match self.state {
|
match self.state {
|
||||||
ScheduledState::Start => {
|
ScheduledState::Start => {
|
||||||
log::info!("Starting task {}", self.task.name());
|
log::info!("Starting task {}", self.task.name());
|
||||||
self.task.start();
|
self.task.start(bus);
|
||||||
ScheduledState::Running
|
self.state = 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();
|
self.task.stop(bus);
|
||||||
ScheduledState::Stopped
|
self.state = 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, value, bus),
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => ()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct FixedSizeScheduler<const TASK_COUNT: usize> {
|
pub struct FixedSizeScheduler<const TASK_COUNT: usize> {
|
||||||
tasks: [Option<ScheduledTask>; TASK_COUNT],
|
tasks: [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 = [const { None }; TASK_COUNT];
|
let mut scheduled: [ScheduledTask; TASK_COUNT] = unsafe { std::mem::MaybeUninit::zeroed().assume_init() };
|
||||||
let mut idx = 0;
|
let mut idx = 0;
|
||||||
for task in tasks {
|
for task in tasks {
|
||||||
scheduled[idx] = Some(ScheduledTask::new(task));
|
log::info!("Scheduling task {}", task.name());
|
||||||
|
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) {
|
||||||
for slot in &mut self.tasks {
|
match event {
|
||||||
match slot {
|
Event::StartThing(task_name) => {
|
||||||
Some(task) => task.tick(event, bus),
|
if let Some(slot) = self.find_task(task_name) {
|
||||||
_ => ()
|
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, Clone, Copy)]
|
#[derive(Debug)]
|
||||||
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