Compare commits

23 Commits

Author SHA1 Message Date
da2a8f5bfc task: remove a copy operation 2024-12-14 14:46:16 +01:00
d09c82c3fc mappings: add a map for albus the tree 2024-12-14 14:44:53 +01:00
c9496e3dc3 mappings: drop a rect clone when we only need the x coord for a linear map 2024-12-14 14:44:13 +01:00
bbdb3d7404 platform: smart_leds: drop a clone we dont need 2024-12-14 14:43:18 +01:00
a237bb7dc8 platform: esp32: drop buggy and slow i2s implementation 2024-12-14 14:41:26 +01:00
3a850105a8 espflash: bump partition size 2024-12-14 14:41:00 +01:00
7d44c11c78 time: clone-- 2024-12-14 14:15:28 +01:00
457ae73c50 main: emojify boot output 2024-12-14 11:11:38 +01:00
8bb46b6f78 platform: smart_leds: warning-- 2024-12-14 11:10:40 +01:00
4b256bc0d9 platform: esp32: wifi: drop event subscriptions on stop 2024-12-14 11:10:15 +01:00
2af9918239 platform: esp32: move output creation into output() method 2024-12-14 11:09:34 +01:00
bfdf7c3230 mappings: debug-- 2024-12-14 11:08:00 +01:00
b6e34111ff platform: add chip_id function to board api 2024-12-14 11:07:34 +01:00
Torrie Fischer
e57ceeb149 todo: update 2024-12-13 01:00:23 +01:00
Torrie Fischer
e0491fafe8 platform: esp32: fix crash when hours wrap around 2024-12-13 01:00:23 +01:00
Torrie Fischer
bf4ef46699 platform: esp32: create cyberplague mask map 2024-12-13 01:00:23 +01:00
Torrie Fischer
42fc0b0c62 platform: esp32: first implementation of mqtt client 2024-12-13 01:00:17 +01:00
Torrie Fischer
3c3952a8a9 platform: esp32: implement a circadian rhythm event source 2024-12-13 00:59:00 +01:00
Torrie Fischer
2f8b94ae61 platform: esp32: document some more chip ids 2024-12-13 00:57:51 +01:00
Torrie Fischer
d7f312ffe4 events: implement a first attempt at an eventing system 2024-12-13 00:56:50 +01:00
Torrie Fischer
9a749c40a1 mappings: add more chip IDs 2024-12-13 00:52:37 +01:00
Torrie Fischer
272bc49eaa lib8: interpolate: add lerp8by8 and map8 functions 2024-12-13 00:47:16 +01:00
Torrie Fischer
b468eb8533 vscode: add cargo tasks 2024-12-13 00:46:09 +01:00
14 changed files with 112 additions and 187 deletions

3
.gitignore vendored
View File

@@ -1,3 +1,4 @@
/.vscode
/.embuild
/target
/Cargo.lock
/Cargo.lock

26
.vscode/launch.json vendored
View File

@@ -1,26 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "probe-rs-debug",
"request": "launch",
"name": "probe-rs Test",
"cwd": "${workspaceFolder}",
"connectUnderReset": true,
"chip": "ESP32S3",
"flashingConfig": {
"flashingEnabled": true,
"haltAfterReset": true
},
"coreConfigs": [
{
"coreIndex": 0,
"programBinary": "./target/xtensa-esp32s3-espidf/debug/${workspaceFolderBasename}"
}
]
}
]
}

View File

@@ -11,13 +11,11 @@ name = "renderbug"
harness = false # do not use the built in cargo test harness -> resolve rust-analyzer errors
[profile.release]
opt-level = 3
lto = true
opt-level = "s"
[profile.dev]
debug = true # Symbols are nice and they don't increase the size on Flash
opt-level = "z"
lto = true
[features]
default = ["std", "esp-idf-svc/native", "rmt", "smart-leds"]
@@ -50,7 +48,6 @@ embedded-graphics = { version = "0.8.1", optional = true, features = ["fixed_poi
ansi_term = "0.12.1"
num = "0.4.3"
chrono = "0.4.38"
fugit = "0.3.7"
[build-dependencies]
embuild = "0.32.0"

1
espflash.toml Normal file
View File

@@ -0,0 +1 @@
partition_table = "partitions.csv"

5
partitions.csv Normal file
View 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,
1 # Name, Type, SubType, Offset, Size, Flags
2 # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
3 nvs, data, nvs, , 0x6000,
4 phy_init, data, phy, , 0x1000,
5 factory, app, factory, , 3M,

View File

@@ -1,15 +1,18 @@
[x] cfg macros
[ ] warnings
[x] rgb crate
[ ] Layer blending
[ ] Refactor idle pattern into test pattern
[ ] Wifi
[x] Layer blending
[x] Refactor idle pattern into test pattern
[x] Wifi
[ ] JSON surface map loading
[ ] Weather
[ ] Circadian Rhythm
[ ] NTP
[x] Circadian Rhythm
[x] NTP
[ ] Config to only start a subset of tasks on startup
[ ] Serial CLI
[ ] Surface blending API
[ ] Layer blending equations
[ ] Surface rotation
[ ] esp8266 port
[ ] event system
[ ] threaded schedulers

View File

@@ -21,29 +21,31 @@ use crate::geometry::Rectangle;
fn main() {
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();
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();
log::info!("Output: {}", core::any::type_name_of_val(&output));
log::info!("Preparing surfaces");
log::info!("🎨 Preparing surfaces");
let mut surfaces = board.surfaces();
log::info!("Surface implementation: {}", core::any::type_name_of_val(&output));
log::info!("Creating animations");
log::info!("🌌 Creating animations");
let mut animations = FixedSizeScheduler::new([
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 renderer = FixedSizeScheduler::new([Box::new(Renderer::new(output, surfaces))]);
log::info!("Starting event bus");
log::info!("🚌 Starting event bus");
let mut bus = EventBus::new();
log::info!("Ready to rock and roll");

View File

@@ -20,7 +20,7 @@ pub trait Select<'a> {
#[derive(Debug)]
pub struct LinearCoordView {
rect: Rectangle<Virtual>,
max_x: u8,
idx: usize,
}
@@ -34,7 +34,7 @@ pub type LinearCoords = Coordinates<LinearSpace>;
impl<'a> CoordinateView<'a> for LinearCoordView {
type Space = LinearSpace;
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
} else {
let virt = VirtualCoordinates::new(self.idx as u8, 0); // FIXME: scale8
@@ -62,7 +62,7 @@ impl<'a> Select<'a> for LinearPixelMapping {
type View = LinearCoordView;
fn select(&'a self, rect: &Rectangle<Virtual>) -> Self::View {
LinearCoordView {
rect: rect.clone(),
max_x: rect.bottom_right.x,
idx: 0,
}
}
@@ -107,6 +107,24 @@ impl<const STRIDE_NUM: usize> StrideMapping<STRIDE_NUM> {
])
}
pub fn new_cyberplague() -> Self {
Self::from_json(&[
(0, 6, 6, false),
(1, 6, 6, true),
(2, 6, 6, false),
(3, 4, 9, true),
(4, 4, 14, false),
(5, 0, 17, true),
(6, 2, 12, false),
(7, 0, 18, true),
(8, 4, 14, false),
(9, 5, 9, true),
(10, 4, 7, false),
(11, 5, 6, true),
(12, 5, 6, false)
])
}
pub fn new_jar() -> Self {
Self::from_json(&[
(0, 0, 17, false),
@@ -151,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 {
let mut strides = [Stride::default(); STRIDE_NUM];
let stride_count = stride_json.len();
@@ -191,8 +215,6 @@ impl<const STRIDE_NUM: usize> StrideMapping<STRIDE_NUM> {
let s = size.take().unwrap();
log::info!("size={:?}", s);
log::info!("strides={:?}", strides);
Self {
strides,
pixel_count: physical_idx,

View File

@@ -10,8 +10,10 @@ use chrono::Timelike;
use chrono::Utc;
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::prelude::Peripherals;
use esp_idf_svc::hal::rmt::RMT;
use esp_idf_svc::hal::task::thread::ThreadSpawnConfiguration;
use esp_idf_svc::mqtt::client::EspMqttClient;
use esp_idf_svc::mqtt::client::EspMqttConnection;
@@ -37,72 +39,26 @@ use crate::task::FixedSizeScheduler;
use crate::task::Task;
use crate::time::Periodically;
pub mod i2s {
use esp_idf_svc::hal::i2s::*;
use rgb::ComponentBytes;
use rgb::Rgb;
use crate::mappings::*;
use crate::buffers::Pixbuf;
use crate::render::Output;
use crate::render::Sample;
pub struct I2SOutput<'d> {
driver: I2sDriver<'d, I2sTx>,
pixbuf: [Rgb<u8>; 310],
pixmap: StrideMapping,
}
impl<'d> I2SOutput<'d> {
fn new(driver: I2sDriver<'d, I2sTx>) -> Self {
I2SOutput {
driver,
pixbuf: Pixbuf::new(),
pixmap: StrideMapping::new_jar()
}
}
}
impl<'d> Output for I2SOutput<'d> {
fn on_event(&mut self, event: &crate::events::Event) {
}
fn blank(&mut self) {
self.pixbuf.blank();
}
fn commit(&mut self) {
let bytes = self.pixbuf.as_bytes();
let mut written = self.driver.preload_data(bytes).unwrap();
self.driver.tx_enable().unwrap();
while written < bytes.len() {
let next = &bytes[written..];
written += self.driver.write(next, 0).unwrap();
}
self.driver.tx_disable().unwrap();
}
}
impl<'d> Sample for I2SOutput<'d> {
type Pixel = Rgb<u8>;
fn sample(&mut self, rect: &crate::geometry::Rectangle<crate::geometry::Virtual>) -> impl crate::render::PixelView<Pixel = Self::Pixel> {
StrideSampler::new(&mut self.pixbuf, self.pixmap.select(rect))
}
}
}
pub struct Esp32Board {
sys_loop: EspSystemEventLoop,
modem: Option<Modem>,
pins: Option<Pins>,
rmt: Option<RMT>,
surfaces: BufferedSurfacePool,
output: Option<<Self as Board>::Output>
}
impl Board for Esp32Board {
type Output = StrideOutput<[Rgb<u8>; 310], FastWs2812Esp32Rmt<'static>>;
type Surfaces = BufferedSurfacePool;
type Scheduler = FixedSizeScheduler<4>;
fn chip_id() -> u64 {
let mut chip_id: [u8; 8] = [0; 8];
unsafe {
esp_efuse_mac_get_default(&mut chip_id as *mut u8);
}
return u64::from_be_bytes(chip_id);
}
fn take() -> Self {
// It is necessary to call this function once. Otherwise some patches to the runtime
@@ -115,18 +71,25 @@ impl Board for Esp32Board {
let peripherals = Peripherals::take().unwrap();
let sys_loop = EspSystemEventLoop::take().unwrap();
let mut chip_id: [u8; 8] = [0; 8];
unsafe {
esp_efuse_mac_get_default(&mut chip_id as *mut u8);
Esp32Board {
modem: Some(peripherals.modem),
sys_loop: sys_loop.clone(),
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_MA : u32 = 500;
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 {
pin_to_core: Some(esp_idf_svc::hal::cpu::Core::Core1),
..Default::default()
@@ -135,12 +98,12 @@ impl Board for Esp32Board {
// But the implementation spawns a thread based on the core the driver was created in,
// so we create the driver in another thread briefly.
// Fun stuff.
let output = match chip_id { // panel test board
let output = match Self::chip_id().to_be_bytes() { // panel test board
[72, 202, 67, 89, 145, 204, 0, 0] => {
StrideOutput::new(
Pixbuf::new(),
StrideMapping::new_panel(),
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(peripherals.rmt.channel0, pins.gpio5).unwrap() }).join().unwrap(),
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(rmt.channel0, pins.gpio5).unwrap() }).join().unwrap(),
MAX_POWER_MW
)
},
@@ -148,15 +111,15 @@ impl Board for Esp32Board {
StrideOutput::new(
Pixbuf::new(),
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
)
},
[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(),
StrideMapping::new_albus(),
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(rmt.channel0, pins.gpio5).unwrap() }).join().unwrap(),
MAX_POWER_MW
)
},
@@ -164,7 +127,7 @@ impl Board for Esp32Board {
StrideOutput::new(
Pixbuf::new(),
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
)
},
@@ -172,15 +135,23 @@ impl Board for Esp32Board {
StrideOutput::new(
Pixbuf::new(),
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
)
}
},
[0xfc, 0xf5, 0xc4, 0x05, 0xb8, 0x30, 0x0, 0x0] => { // cyberplague
StrideOutput::new(
Pixbuf::new(),
StrideMapping::new_cyberplague(),
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(rmt.channel0, pins.gpio13).unwrap() }).join().unwrap(),
MAX_POWER_MW
)
},
_ => {
StrideOutput::new(
Pixbuf::new(),
StrideMapping::new(),
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(peripherals.rmt.channel0, pins.gpio5).unwrap() }).join().unwrap(),
std::thread::spawn(move || { FastWs2812Esp32Rmt::new(rmt.channel0, pins.gpio5).unwrap() }).join().unwrap(),
MAX_POWER_MW
)
}
@@ -189,16 +160,7 @@ impl Board for Esp32Board {
..Default::default()
}.set().unwrap();
Esp32Board {
modem: Some(peripherals.modem),
sys_loop: sys_loop.clone(),
surfaces: BufferedSurfacePool::new(),
output: Some(output)
}
}
fn output(&mut self) -> Self::Output {
self.output.take().unwrap()
output
}
fn surfaces(&mut self) -> Self::Surfaces {
@@ -265,9 +227,9 @@ impl CircadianRhythm {
adjusted_end.hour += 24;
}
let start_time = start.hour * 60;
let end_time = end.hour * 60;
let now_time = hour * 60 + minute;
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;
@@ -499,6 +461,8 @@ impl Task for WifiTask {
fn stop(&mut self) {
log::info!("Stopping wifi");
self.wifi_sub.take().unwrap();
self.ip_sub.take().unwrap();
self.disconnect();
}
}

View File

@@ -1,44 +0,0 @@
use rgb::Rgb;
use super::smart_leds_lib::rmt::FastWs2812Esp32Rmt;
use super::smart_leds_lib::StrideOutput;
use crate::task::{FixedSizeScheduler, Task};
use crate::buffers::StaticSurfacePool;
use super::Board;
pub struct Esp32Board<'a> {
output: Option<<Self as Board>::Output>,
surfaces: Option<StaticSurfacePool>,
tasks: Option<[&'a mut dyn Task; 1]>
}
impl<'a> Board for Esp32Board<'a> {
type Output = StrideOutput<[Rgb<u8>; 310], FastWs2812Esp32Rmt<'a>>;
type Surfaces = StaticSurfacePool;
type Scheduler = FixedSizeScheduler<0>;
fn take() -> Self {
let peripherals = esp_hal::init(esp_hal::Config::default());
//esp_alloc::heap_allocator!(72 * 1024);
const POWER_VOLTS : u32 = 5;
const POWER_MA : u32 = 500;
const MAX_POWER_MW : u32 = POWER_VOLTS * POWER_MA;
let pins = peripherals.pins;
Esp32Board { output: None, surfaces: None, tasks: None }
}
fn output(&mut self) -> Self::Output {
self.output.take().unwrap()
}
fn surfaces(&mut self) -> Self::Surfaces {
self.surfaces.take().unwrap()
}
fn system_tasks(&mut self) -> Self::Scheduler {
FixedSizeScheduler::new([])
}
}

View File

@@ -20,4 +20,5 @@ pub trait Board {
fn output(&mut self) -> Self::Output;
fn surfaces(&mut self) -> Self::Surfaces;
fn system_tasks(&mut self) -> Self::Scheduler;
fn chip_id() -> u64;
}

View File

@@ -1,7 +1,6 @@
use smart_leds_trait::SmartLedsWrite;
use crate::buffers::Pixbuf;
use crate::events::Variant;
use crate::render::{HardwarePixel, Output, PixelView, Sample};
use crate::power::brightness_for_mw;
use crate::geometry::*;
@@ -57,7 +56,7 @@ impl<P: Pixbuf<Pixel=T::Color>, T: FastWrite> Output for StrideOutput<P, T> {
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(),
crate::events::Event::PropertyChange("output.brightness", Variant::Byte(new_brightness)) => self.brightness = *new_brightness,
_ => ()
}
}

View File

@@ -42,18 +42,18 @@ impl ScheduledTask {
}
fn start(&mut self) {
self.state = match self.state {
ScheduledState::Stopped => ScheduledState::Start,
ScheduledState::Stop => ScheduledState::Running,
_ => self.state
match self.state {
ScheduledState::Stopped => self.state = ScheduledState::Start,
ScheduledState::Stop => self.state = ScheduledState::Running,
_ => ()
}
}
fn stop(&mut self) {
self.state = match self.state {
ScheduledState::Running => ScheduledState::Stop,
ScheduledState::Start => ScheduledState::Stopped,
_ => self.state
match self.state {
ScheduledState::Running => self.state = ScheduledState::Stop,
ScheduledState::Start => self.state = ScheduledState::Stopped,
_ => ()
}
}

View File

@@ -1,7 +1,7 @@
use core::time::Duration;
use std::time::Instant;
#[derive(Debug, Clone, Copy)]
#[derive(Debug)]
pub struct Periodically {
last_run: Instant,
duration: Duration