Compare commits
10 Commits
b20c562b27
...
e57ceeb149
Author | SHA1 | Date | |
---|---|---|---|
|
e57ceeb149 | ||
|
e0491fafe8 | ||
|
bf4ef46699 | ||
|
42fc0b0c62 | ||
|
3c3952a8a9 | ||
|
2f8b94ae61 | ||
|
d7f312ffe4 | ||
|
9a749c40a1 | ||
|
272bc49eaa | ||
|
b468eb8533 |
29
.vscode/tasks.json
vendored
Normal file
29
.vscode/tasks.json
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"type": "cargo",
|
||||||
|
"command": "build",
|
||||||
|
"problemMatcher": [
|
||||||
|
"$rustc"
|
||||||
|
],
|
||||||
|
"group": {
|
||||||
|
"kind": "build",
|
||||||
|
"isDefault": true
|
||||||
|
},
|
||||||
|
"label": "rust: cargo build"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "cargo",
|
||||||
|
"command": "espflash flash --no-stub --monitor",
|
||||||
|
"problemMatcher": [
|
||||||
|
"$rustc"
|
||||||
|
],
|
||||||
|
"group": {
|
||||||
|
"kind": "build",
|
||||||
|
"isDefault": false
|
||||||
|
},
|
||||||
|
"label": "rust: cargo espflash"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
13
src/TODO.md
13
src/TODO.md
@ -1,15 +1,18 @@
|
|||||||
[x] cfg macros
|
[x] cfg macros
|
||||||
[ ] warnings
|
[ ] warnings
|
||||||
[x] rgb crate
|
[x] rgb crate
|
||||||
[ ] Layer blending
|
[x] Layer blending
|
||||||
[ ] Refactor idle pattern into test pattern
|
[x] Refactor idle pattern into test pattern
|
||||||
[ ] Wifi
|
[x] Wifi
|
||||||
[ ] JSON surface map loading
|
[ ] JSON surface map loading
|
||||||
[ ] Weather
|
[ ] Weather
|
||||||
[ ] Circadian Rhythm
|
[x] Circadian Rhythm
|
||||||
[ ] NTP
|
[x] 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
|
@ -2,6 +2,7 @@ use palette::Hsv;
|
|||||||
|
|
||||||
use rgb::RGB8;
|
use rgb::RGB8;
|
||||||
|
|
||||||
|
use crate::events::{Event, EventBus};
|
||||||
use crate::time::Periodically;
|
use crate::time::Periodically;
|
||||||
use crate::geometry::*;
|
use crate::geometry::*;
|
||||||
use crate::render::{Shader, Surface, Surfaces};
|
use crate::render::{Shader, Surface, Surfaces};
|
||||||
@ -75,7 +76,7 @@ impl<T: Surface> Task for IdleTask<T> {
|
|||||||
self.shimmer.set_opacity(64);
|
self.shimmer.set_opacity(64);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tick(&mut self) {}
|
fn tick(&mut self, event: &Event, bus: &mut EventBus) {}
|
||||||
|
|
||||||
fn stop(&mut self) {
|
fn stop(&mut self) {
|
||||||
self.solid.clear_shader();
|
self.solid.clear_shader();
|
||||||
@ -172,7 +173,7 @@ impl<T: Surface> Task for TestPattern<T> {
|
|||||||
self.surface.set_shader(Box::new(self.pattern.clone()));
|
self.surface.set_shader(Box::new(self.pattern.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tick(&mut self) {
|
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);
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
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;
|
||||||
@ -235,7 +236,7 @@ impl Surfaces for BufferedSurfacePool {
|
|||||||
|
|
||||||
|
|
||||||
impl Task for BufferedSurfacePool {
|
impl Task for BufferedSurfacePool {
|
||||||
fn tick(&mut self) {
|
fn tick(&mut self, event: &Event, 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();
|
||||||
}
|
}
|
||||||
|
163
src/events.rs
Normal file
163
src/events.rs
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
use core::fmt::Debug;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum InputEvent {
|
||||||
|
PowerOn,
|
||||||
|
PowerOff,
|
||||||
|
NetworkActivity,
|
||||||
|
NetworkOnline,
|
||||||
|
NetworkOffline
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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 {
|
||||||
|
ReadyToRock,
|
||||||
|
Tick,
|
||||||
|
StartThing(&'static str),
|
||||||
|
StopThing(&'static str),
|
||||||
|
Input(InputEvent),
|
||||||
|
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 {
|
||||||
|
pub fn new_tick() -> Self {
|
||||||
|
Event::Tick
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_property_change<T>(key: &'static str, data: T) -> Self where Variant: From<T> {
|
||||||
|
Event::PropertyChange(key, Variant::from(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_ready_to_rock() -> Self {
|
||||||
|
Event::ReadyToRock
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_input_event(event: InputEvent) -> Self {
|
||||||
|
Event::Input(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct EventBus {
|
||||||
|
pending: Vec<Event>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventBus {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
EventBus {
|
||||||
|
pending: Vec::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&mut self) -> Event {
|
||||||
|
if self.pending.len() == 0 {
|
||||||
|
Event::new_tick()
|
||||||
|
} else {
|
||||||
|
self.pending.pop().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push(&mut self, event: Event) {
|
||||||
|
self.pending.push(event);
|
||||||
|
}
|
||||||
|
}
|
@ -82,6 +82,25 @@ pub fn lerp7by8(a: i8, b: i8, frac: u8) -> i8 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn lerp8by8(a: u8, b: u8, frac: u8) -> u8 {
|
||||||
|
if b > a {
|
||||||
|
let delta = b - a;
|
||||||
|
let scaled = scale8(delta, frac);
|
||||||
|
return a + scaled;
|
||||||
|
} else {
|
||||||
|
let delta = a - b;
|
||||||
|
let scaled = scale8(delta, frac);
|
||||||
|
return a - scaled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn map8(x: u8, range_start: u8, range_end: u8) -> u8 {
|
||||||
|
let range_width = range_end - range_start;
|
||||||
|
let mut out = scale8(x, range_width);
|
||||||
|
out += range_start;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn ease8InOutQuad(i: u8) -> u8 {
|
pub fn ease8InOutQuad(i: u8) -> u8 {
|
||||||
let j = if i & 0x80 != 0 {
|
let j = if i & 0x80 != 0 {
|
||||||
255 - i
|
255 - i
|
||||||
|
19
src/main.rs
19
src/main.rs
@ -8,7 +8,11 @@ mod platform;
|
|||||||
mod animations;
|
mod animations;
|
||||||
mod mappings;
|
mod mappings;
|
||||||
mod buffers;
|
mod buffers;
|
||||||
|
mod events;
|
||||||
|
|
||||||
|
use events::Event;
|
||||||
|
|
||||||
|
use crate::events::EventBus;
|
||||||
use crate::platform::{DefaultBoard, Board};
|
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};
|
||||||
@ -39,10 +43,19 @@ fn main() {
|
|||||||
|
|
||||||
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");
|
||||||
|
let mut bus = EventBus::new();
|
||||||
|
|
||||||
log::info!("Ready to rock and roll");
|
log::info!("Ready to rock and roll");
|
||||||
|
bus.push(Event::new_ready_to_rock());
|
||||||
loop {
|
loop {
|
||||||
animations.tick();
|
let next_event = bus.next();
|
||||||
system.tick();
|
match next_event {
|
||||||
renderer.tick();
|
events::Event::Tick => (),
|
||||||
|
_ => log::info!("Event: {:?}", next_event)
|
||||||
|
}
|
||||||
|
animations.tick(&next_event, &mut bus);
|
||||||
|
system.tick(&next_event, &mut bus);
|
||||||
|
renderer.tick(&next_event, &mut bus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -101,6 +101,30 @@ impl<const STRIDE_NUM: usize> StrideMapping<STRIDE_NUM> {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new_fairylights() -> Self {
|
||||||
|
Self::from_json(&[
|
||||||
|
(0, 0, 50, false)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
@ -2,14 +2,20 @@ use core::borrow::BorrowMut;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
use std::thread::JoinHandle;
|
||||||
|
use std::thread::ScopedJoinHandle;
|
||||||
|
|
||||||
use chrono::DateTime;
|
use chrono::DateTime;
|
||||||
|
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::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::task::thread::ThreadSpawnConfiguration;
|
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::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;
|
||||||
@ -23,6 +29,9 @@ use super::Board;
|
|||||||
|
|
||||||
use crate::buffers::BufferedSurfacePool;
|
use crate::buffers::BufferedSurfacePool;
|
||||||
use crate::buffers::Pixbuf;
|
use crate::buffers::Pixbuf;
|
||||||
|
use crate::events::Event;
|
||||||
|
use crate::events::EventBus;
|
||||||
|
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;
|
||||||
@ -55,6 +64,10 @@ pub mod i2s {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'d> Output for I2SOutput<'d> {
|
impl<'d> Output for I2SOutput<'d> {
|
||||||
|
fn on_event(&mut self, event: &crate::events::Event) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
fn blank(&mut self) {
|
fn blank(&mut self) {
|
||||||
self.pixbuf.blank();
|
self.pixbuf.blank();
|
||||||
}
|
}
|
||||||
@ -89,7 +102,7 @@ pub struct Esp32Board {
|
|||||||
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<2>;
|
type Scheduler = FixedSizeScheduler<4>;
|
||||||
|
|
||||||
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
|
||||||
@ -122,7 +135,7 @@ 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 {
|
let output = match chip_id { // 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(),
|
||||||
@ -131,7 +144,7 @@ impl Board for Esp32Board {
|
|||||||
MAX_POWER_MW
|
MAX_POWER_MW
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
[0x8C, 0xAA, 0xB5, 0x83, 0x5f, 0x74, 0x0, 0x0] => {
|
[0x8C, 0xAA, 0xB5, 0x83, 0x5f, 0x74, 0x0, 0x0] => { //ponderjar
|
||||||
StrideOutput::new(
|
StrideOutput::new(
|
||||||
Pixbuf::new(),
|
Pixbuf::new(),
|
||||||
StrideMapping::new_jar(),
|
StrideMapping::new_jar(),
|
||||||
@ -139,6 +152,38 @@ impl Board for Esp32Board {
|
|||||||
MAX_POWER_MW
|
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
|
||||||
|
)
|
||||||
|
},
|
||||||
|
[0xfc, 0xf5, 0xc4, 0x05, 0xb8, 0x30, 0x0, 0x0] => { // cyberplague
|
||||||
|
StrideOutput::new(
|
||||||
|
Pixbuf::new(),
|
||||||
|
StrideMapping::new_cyberplague(),
|
||||||
|
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(peripherals.rmt.channel0, pins.gpio13).unwrap() }).join().unwrap(),
|
||||||
|
MAX_POWER_MW
|
||||||
|
)
|
||||||
|
},
|
||||||
_ => {
|
_ => {
|
||||||
StrideOutput::new(
|
StrideOutput::new(
|
||||||
Pixbuf::new(),
|
Pixbuf::new(),
|
||||||
@ -172,11 +217,164 @@ impl Board for Esp32Board {
|
|||||||
let nvs = EspDefaultNvsPartition::take().unwrap();
|
let nvs = EspDefaultNvsPartition::take().unwrap();
|
||||||
FixedSizeScheduler::new([
|
FixedSizeScheduler::new([
|
||||||
Box::new(WifiTask::new(self.modem.take().unwrap(), self.sys_loop.clone(), &nvs)),
|
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())
|
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 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 * 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 {
|
impl Debug for WifiTask {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.debug_struct("WifiTask").finish()
|
f.debug_struct("WifiTask").finish()
|
||||||
@ -283,7 +481,7 @@ impl Task for WifiTask {
|
|||||||
self.connect();
|
self.connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tick(&mut self ) {
|
fn tick(&mut self, event: &Event, bus: &mut EventBus) {
|
||||||
if self.connection_check.tick() {
|
if self.connection_check.tick() {
|
||||||
let cur_state = *self.state.lock().unwrap();
|
let cur_state = *self.state.lock().unwrap();
|
||||||
|
|
||||||
@ -298,10 +496,12 @@ impl Task for WifiTask {
|
|||||||
log::info!("online: {:?}", cur_state);
|
log::info!("online: {:?}", cur_state);
|
||||||
|
|
||||||
self.last_state = cur_state;
|
self.last_state = cur_state;
|
||||||
}
|
|
||||||
|
|
||||||
let now: DateTime<Utc> = std::time::SystemTime::now().into();
|
match cur_state {
|
||||||
log::info!("Current time: {} status={:?}", now.format("%d/%m/%Y %T"), self.ntp.get_sync_status());
|
WifiState::Connected => bus.push(Event::new_input_event(crate::events::InputEvent::NetworkOnline)),
|
||||||
|
_ => bus.push(Event::new_input_event(crate::events::InputEvent::NetworkOffline))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use smart_leds_trait::SmartLedsWrite;
|
use smart_leds_trait::SmartLedsWrite;
|
||||||
|
|
||||||
use crate::buffers::Pixbuf;
|
use crate::buffers::Pixbuf;
|
||||||
|
use crate::events::Variant;
|
||||||
use crate::render::{HardwarePixel, Output, PixelView, Sample};
|
use crate::render::{HardwarePixel, Output, PixelView, Sample};
|
||||||
use crate::power::brightness_for_mw;
|
use crate::power::brightness_for_mw;
|
||||||
use crate::geometry::*;
|
use crate::geometry::*;
|
||||||
@ -18,7 +19,8 @@ pub struct StrideOutput<P: Pixbuf, T: FastWrite> {
|
|||||||
pixbuf: P,
|
pixbuf: P,
|
||||||
stride_map: StrideMapping,
|
stride_map: StrideMapping,
|
||||||
target: T,
|
target: T,
|
||||||
max_mw: u32
|
max_mw: u32,
|
||||||
|
brightness: u8
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: Pixbuf, T: FastWrite> StrideOutput<P, T> {
|
impl<P: Pixbuf, T: FastWrite> StrideOutput<P, T> {
|
||||||
@ -28,7 +30,8 @@ impl<P: Pixbuf, T: FastWrite> StrideOutput<P, T> {
|
|||||||
pixbuf,
|
pixbuf,
|
||||||
stride_map,
|
stride_map,
|
||||||
target,
|
target,
|
||||||
max_mw
|
max_mw,
|
||||||
|
brightness: 255
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -46,11 +49,18 @@ impl<P: Pixbuf<Pixel=T::Color>, T: FastWrite> Output for StrideOutput<P, T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn commit(&mut self) {
|
fn commit(&mut self) {
|
||||||
let b = brightness_for_mw(self.pixbuf.as_milliwatts(), 255, self.max_mw);
|
let b = brightness_for_mw(self.pixbuf.as_milliwatts(), self.brightness, self.max_mw);
|
||||||
if self.target.fast_write(self.pixbuf.iter_with_brightness(b)).is_err() {
|
if self.target.fast_write(self.pixbuf.iter_with_brightness(b)).is_err() {
|
||||||
panic!("Could not write frame!");
|
panic!("Could not write frame!");
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_event(&mut self, event: &crate::events::Event) {
|
||||||
|
match event {
|
||||||
|
crate::events::Event::PropertyChange("output.brightness", new_brightness) => self.brightness = new_brightness.clone().into(),
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait FastWrite: Send {
|
pub trait FastWrite: Send {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use rgb::Rgb;
|
use rgb::Rgb;
|
||||||
|
|
||||||
|
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;
|
||||||
@ -42,6 +43,7 @@ pub trait Surface: Send + Sync {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait Output: Sample + Send {
|
pub trait Output: Sample + Send {
|
||||||
|
fn on_event(&mut self, event: &Event);
|
||||||
fn blank(&mut self);
|
fn blank(&mut self);
|
||||||
fn commit(&mut self);
|
fn commit(&mut self);
|
||||||
}
|
}
|
||||||
@ -70,17 +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 tick(&mut self) {
|
fn tick(&mut self, event: &Event, _bus: &mut EventBus) {
|
||||||
self.output.blank();
|
match event {
|
||||||
|
crate::events::Event::Tick => {
|
||||||
|
self.output.blank();
|
||||||
|
|
||||||
self.surfaces.render_to(&mut self.output, self.frame);
|
self.surfaces.render_to(&mut self.output, self.frame);
|
||||||
|
|
||||||
self.output.commit();
|
self.output.commit();
|
||||||
|
|
||||||
self.fps.insert(1);
|
self.fps.insert(1);
|
||||||
self.frame += 1;
|
self.frame += 1;
|
||||||
self.fps_display.run(|| {
|
self.fps_display.run(|| {
|
||||||
log::info!("FPS: {}", self.fps.measurement());
|
log::info!("FPS: {}", self.fps.measurement());
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
_ => self.output.on_event(event)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
14
src/task.rs
14
src/task.rs
@ -1,7 +1,9 @@
|
|||||||
use core::fmt;
|
use core::fmt;
|
||||||
|
|
||||||
|
use crate::events::{Event, EventBus};
|
||||||
|
|
||||||
pub trait Task: Send {
|
pub trait Task: Send {
|
||||||
fn tick(&mut self) {}
|
fn tick(&mut self, event: &Event, bus: &mut EventBus) {}
|
||||||
fn start(&mut self) {}
|
fn start(&mut self) {}
|
||||||
fn stop(&mut self) {}
|
fn stop(&mut self) {}
|
||||||
fn name(&self) -> &'static str {
|
fn name(&self) -> &'static str {
|
||||||
@ -55,7 +57,7 @@ impl ScheduledTask {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tick(&mut self) {
|
fn tick(&mut self, event: &Event, bus: &mut EventBus) {
|
||||||
self.state = 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());
|
||||||
@ -63,7 +65,7 @@ impl ScheduledTask {
|
|||||||
ScheduledState::Running
|
ScheduledState::Running
|
||||||
},
|
},
|
||||||
ScheduledState::Running => {
|
ScheduledState::Running => {
|
||||||
self.task.tick();
|
self.task.tick(event, bus);
|
||||||
ScheduledState::Running
|
ScheduledState::Running
|
||||||
},
|
},
|
||||||
ScheduledState::Stop => {
|
ScheduledState::Stop => {
|
||||||
@ -96,10 +98,10 @@ impl<const TASK_COUNT: usize> FixedSizeScheduler<TASK_COUNT> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<const TASK_COUNT: usize> Scheduler for FixedSizeScheduler<TASK_COUNT> {
|
impl<const TASK_COUNT: usize> Scheduler for FixedSizeScheduler<TASK_COUNT> {
|
||||||
fn tick(&mut self) {
|
fn tick(&mut self, event: &Event, bus: &mut EventBus) {
|
||||||
for slot in &mut self.tasks {
|
for slot in &mut self.tasks {
|
||||||
match slot {
|
match slot {
|
||||||
Some(task) => task.tick(),
|
Some(task) => task.tick(event, bus),
|
||||||
_ => ()
|
_ => ()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -107,5 +109,5 @@ impl<const TASK_COUNT: usize> Scheduler for FixedSizeScheduler<TASK_COUNT> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait Scheduler {
|
pub trait Scheduler {
|
||||||
fn tick(&mut self);
|
fn tick(&mut self, event: &Event, bus: &mut EventBus);
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user