moving standalone mode to alsa rust direct binding crate & moving led_driver to a specific package
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
[package]
|
||||
name = "lightsabre_backend"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
alsa = "0.9.1"
|
||||
artnet_protocol = "0.4.3"
|
||||
crossbeam = "0.8.4"
|
||||
ctrlc = { version = "3.4.7", features = ["termination"] }
|
||||
env_logger = "0.11.8"
|
||||
log = "0.4.27"
|
||||
nix = "0.30.1"
|
||||
rpi-mailbox = "0.3.0"
|
||||
rppal = "0.22.1"
|
||||
spectrum-analyzer = "1.7.0"
|
||||
textplots = "0.8.7"
|
||||
led_driver = { path = "../led_driver" }
|
||||
|
||||
[build-dependencies]
|
||||
bindgen = "0.71.0"
|
||||
|
||||
[features]
|
||||
default = ["rpizero2", "16channel"]
|
||||
rpizero2 = ["rpi3"]
|
||||
rpizero = ["rpi"]
|
||||
rpi4 = []
|
||||
rpi3 = []
|
||||
rpi2 = []
|
||||
rpi = []
|
||||
16channel = []
|
||||
@@ -0,0 +1,6 @@
|
||||
use crate::cputasks::modes::AppMode;
|
||||
|
||||
pub enum Message {
|
||||
ModeChanged { mode: AppMode },
|
||||
// Other messages...
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
pub mod modes;
|
||||
@@ -0,0 +1,121 @@
|
||||
pub mod artnet;
|
||||
pub mod diagnostics;
|
||||
pub mod manual;
|
||||
pub mod standalone;
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use crossbeam::channel::Receiver;
|
||||
|
||||
use crate::channels::Message;
|
||||
use crate::devices::led_driver::LedDriver;
|
||||
|
||||
pub trait AppModeHandler {
|
||||
fn enter(&mut self) -> Result<(), Box<dyn std::error::Error>>;
|
||||
fn run(&mut self, led_driver: &mut LedDriver) -> Result<(), Box<dyn std::error::Error>>;
|
||||
fn exit(&mut self) -> Result<(), Box<dyn std::error::Error>>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum AppMode {
|
||||
Diagnostics,
|
||||
ArtNet,
|
||||
Standalone,
|
||||
Manual,
|
||||
}
|
||||
|
||||
impl Default for AppMode {
|
||||
fn default() -> Self {
|
||||
Self::Standalone
|
||||
}
|
||||
}
|
||||
|
||||
impl AppMode {
|
||||
pub fn for_each<F: FnMut(AppMode)>(mut f: F) {
|
||||
f(AppMode::Diagnostics);
|
||||
f(AppMode::ArtNet);
|
||||
f(AppMode::Standalone);
|
||||
f(AppMode::Manual);
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Option<usize>> for AppMode {
|
||||
fn from(value: Option<usize>) -> Self {
|
||||
match value {
|
||||
Some(0) => AppMode::Diagnostics,
|
||||
Some(1) => AppMode::ArtNet,
|
||||
Some(2) => AppMode::Standalone,
|
||||
Some(3) => AppMode::Manual,
|
||||
_ => AppMode::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Box<dyn AppModeHandler + Send>> for AppMode {
|
||||
fn into(self) -> Box<dyn AppModeHandler + Send> {
|
||||
match self {
|
||||
AppMode::Diagnostics => Box::new(diagnostics::DiagnosticsMode::new()),
|
||||
AppMode::ArtNet => Box::new(artnet::ArtNetMode::new()),
|
||||
AppMode::Standalone => Box::new(standalone::StandaloneMode::new()),
|
||||
AppMode::Manual => Box::new(manual::ManualMode::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ModeManager {
|
||||
mode_handler_map: HashMap<AppMode, Box<dyn AppModeHandler + Send>>,
|
||||
mode: Arc<Mutex<AppMode>>,
|
||||
mode_rx: Receiver<Message>,
|
||||
}
|
||||
|
||||
impl ModeManager {
|
||||
pub fn new(mode_rx: Receiver<Message>) -> Self {
|
||||
let mut handlers: HashMap<AppMode, Box<dyn AppModeHandler + Send>> = HashMap::new();
|
||||
AppMode::for_each(|mode| {
|
||||
handlers.insert(mode, mode.into());
|
||||
});
|
||||
|
||||
let mut mode_manager = ModeManager {
|
||||
mode_handler_map: handlers,
|
||||
mode: Arc::new(Mutex::new(AppMode::default())),
|
||||
mode_rx: mode_rx.clone(),
|
||||
};
|
||||
|
||||
log::info!("Starting app with mode {:?}", AppMode::default());
|
||||
mode_manager.get_handler(None).enter();
|
||||
|
||||
mode_manager
|
||||
}
|
||||
|
||||
pub fn run(&mut self, led_driver: &mut LedDriver) {
|
||||
if let Ok(Message::ModeChanged { mode: next }) = self.mode_rx.try_recv() {
|
||||
let current = self.get_current_mode();
|
||||
if current != next {
|
||||
log::info!("Switching mode from {:?} to {:?}", current, next);
|
||||
self.get_handler(Some(current)).exit();
|
||||
self.get_handler(Some(next)).enter();
|
||||
self.set_current_mode(next);
|
||||
}
|
||||
} else {
|
||||
self.get_handler(None).run(led_driver);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_current_mode(&self) -> AppMode {
|
||||
*self.mode.lock().unwrap()
|
||||
}
|
||||
|
||||
fn set_current_mode(&self, mode: AppMode) {
|
||||
*self.mode.lock().unwrap() = mode;
|
||||
}
|
||||
|
||||
fn get_handler(&mut self, mode: Option<AppMode>) -> &mut Box<dyn AppModeHandler + Send> {
|
||||
let mode = &mode.unwrap_or_else(|| self.get_current_mode());
|
||||
self.mode_handler_map
|
||||
.get_mut(mode)
|
||||
.expect(&format!("No handler found for mode: {:?}", mode))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
use artnet_protocol::{ArtCommand, PollReply};
|
||||
use std::fmt::Display;
|
||||
use std::net::{Ipv4Addr, UdpSocket};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use crate::cputasks::modes::AppModeHandler;
|
||||
use crate::devices::led_driver::LedDriver;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ArtNetMode {
|
||||
socket: Option<UdpSocket>,
|
||||
last_frame_time: Option<Instant>,
|
||||
statistics: ArtNetModeStatistics,
|
||||
}
|
||||
|
||||
impl ArtNetMode {
|
||||
pub fn new() -> Self {
|
||||
ArtNetMode::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl AppModeHandler for ArtNetMode {
|
||||
fn enter(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
log::debug!("[ArtNet] Entering ArtNet Mode");
|
||||
|
||||
let mut attempts = 0_usize;
|
||||
let socket = loop {
|
||||
match UdpSocket::bind(("0.0.0.0", 6454)) {
|
||||
Ok(socket) => break socket,
|
||||
Err(e) => {
|
||||
log::error!("[ArtNet] Failed to bind ArtNet socket: {e}, retrying...");
|
||||
std::thread::sleep(std::time::Duration::from_millis(5));
|
||||
}
|
||||
}
|
||||
attempts += 1;
|
||||
if attempts > 10 {
|
||||
panic!("[ArtNet] Failed to bind ArtNet socket after multiple attempts");
|
||||
}
|
||||
};
|
||||
socket.set_broadcast(true).unwrap();
|
||||
socket.set_nonblocking(true).unwrap();
|
||||
self.socket = Some(socket);
|
||||
log::debug!("[ArtNet] ArtNet Mode initialized and listening on port 6454");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run(&mut self, led_driver: &mut LedDriver) -> Result<(), Box<dyn std::error::Error>> {
|
||||
log::trace!("[ArtNet] Running...");
|
||||
let buf = &mut [0; 530];
|
||||
let mut is_first_data_frame = true;
|
||||
loop {
|
||||
match self
|
||||
.socket
|
||||
.as_mut()
|
||||
.expect("ArtNet socket not initialized")
|
||||
.recv_from(buf)
|
||||
{
|
||||
Ok((num_bytes_read, from)) => {
|
||||
log::trace!("[ArtNet] Received {} bytes from {}", num_bytes_read, from);
|
||||
|
||||
let command =
|
||||
ArtCommand::from_buffer(buf).expect("Failed to parse ArtNet command");
|
||||
match command {
|
||||
ArtCommand::Poll(_) => {
|
||||
log::trace!("[ArtNet] Received Poll command, responding...");
|
||||
let poll_reply: PollReply = PollReply {
|
||||
address: Ipv4Addr::from_bits(0),
|
||||
port: 6454,
|
||||
version: [0, 1],
|
||||
port_address: [0; 2],
|
||||
oem: [0x01, 0x90],
|
||||
ubea_version: 0,
|
||||
status_1: 0,
|
||||
esta_code: 0,
|
||||
short_name: b"LightSabre\0\0\0\0\0\0\0\0".to_owned(),
|
||||
long_name: b"LightSabre Artnet Node\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0".to_owned(),
|
||||
node_report: [0; 64],
|
||||
num_ports: [0, 1],
|
||||
port_types: [0x80, 0, 0, 0],
|
||||
good_input: [0; 4],
|
||||
good_output: [0; 4],
|
||||
swin: [0; 4],
|
||||
swout: [0; 4],
|
||||
sw_video: 0,
|
||||
sw_macro: 0,
|
||||
sw_remote: 0,
|
||||
spare: [0; 3],
|
||||
style: 0,
|
||||
mac: [0; 6],
|
||||
bind_ip: [0; 4],
|
||||
bind_index: 0,
|
||||
status_2: 0,
|
||||
filler: [0; 26],
|
||||
};
|
||||
let response = ArtCommand::PollReply(Box::new(poll_reply))
|
||||
.write_to_buffer()
|
||||
.unwrap();
|
||||
self.socket
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.send_to(&response, from)
|
||||
.expect("Failed to send Poll response");
|
||||
}
|
||||
ArtCommand::Output(output) => {
|
||||
log::trace!("[ArtNet] Received Output command with data: {:?}", output);
|
||||
/* compute statistics */
|
||||
if is_first_data_frame {
|
||||
self.statistics.update(self.last_frame_time);
|
||||
self.last_frame_time = Some(Instant::now());
|
||||
is_first_data_frame = false;
|
||||
}
|
||||
|
||||
let led_strip = (u16::from(output.port_address) & 0b0111_u16) as usize;
|
||||
//output.port_address
|
||||
for i in 0..led_driver.get_led_per_strip_count() {
|
||||
let data_index = i * 3;
|
||||
let g = *output.data.as_ref().get(data_index).unwrap_or(&0);
|
||||
let r = *output.data.as_ref().get(data_index + 1).unwrap_or(&0);
|
||||
let b = *output.data.as_ref().get(data_index + 2).unwrap_or(&0);
|
||||
// let color = (r as u32) << 16 + (g as u32) << 8 + b;
|
||||
let color: u32 =
|
||||
((r as u32) << 16) | ((g as u32) << 8) | (b as u32);
|
||||
led_driver.direct_set_color(color, i);
|
||||
}
|
||||
led_driver.refresh();
|
||||
}
|
||||
ArtCommand::PollReply(_) => {
|
||||
log::trace!("[ArtNet] Received PollReply command, ignoring");
|
||||
}
|
||||
_ => {
|
||||
log::warn!("[ArtNet] Received unhandled command: {:?}", command);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => {
|
||||
// No data received, continue running
|
||||
break;
|
||||
}
|
||||
Err(e) => panic!("encountered IO error: {e}"),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn exit(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
log::debug!("[ArtNet] Exiting ArtNet Mode");
|
||||
log::info!("[ArtNet] Statistics: {}", self.statistics);
|
||||
self.socket = None;
|
||||
self.last_frame_time = None;
|
||||
self.statistics = ArtNetModeStatistics::default();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct ArtNetModeStatistics {
|
||||
frame_count: u32,
|
||||
min_time: Option<Duration>,
|
||||
max_time: Option<Duration>,
|
||||
total_time: Duration,
|
||||
avg_delta: Option<Duration>,
|
||||
}
|
||||
|
||||
impl ArtNetModeStatistics {
|
||||
fn update(&mut self, last_frame_time: Option<Instant>) {
|
||||
self.frame_count += 1;
|
||||
if let Some(last_frame_time) = last_frame_time {
|
||||
let elapsed = last_frame_time.elapsed();
|
||||
if self.frame_count > 30 {
|
||||
let avg = self.total_time / self.frame_count;
|
||||
if elapsed > avg * 2 {
|
||||
log::debug!("[ArtNet] Frame took too long: {:?}ms", elapsed.as_millis());
|
||||
}
|
||||
if elapsed < avg / 2 {
|
||||
log::debug!("[ArtNet] Frame took too short: {:?}ms", elapsed.as_millis());
|
||||
}
|
||||
let delta = elapsed.abs_diff(avg);
|
||||
self.avg_delta = if let Some(avg_delta) = self.avg_delta {
|
||||
Some((avg_delta * (self.frame_count - 30) + delta) / (self.frame_count - 29))
|
||||
} else {
|
||||
Some(delta)
|
||||
};
|
||||
}
|
||||
self.total_time += elapsed;
|
||||
self.max_time = Some(match self.max_time {
|
||||
Some(max_time) => std::cmp::max(max_time, elapsed),
|
||||
None => elapsed,
|
||||
});
|
||||
self.min_time = Some(match self.min_time {
|
||||
Some(min_time) => std::cmp::min(min_time, elapsed),
|
||||
None => elapsed,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ArtNetModeStatistics {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let average_time = if self.frame_count > 0 {
|
||||
self.total_time / self.frame_count as u32
|
||||
} else {
|
||||
Duration::default()
|
||||
};
|
||||
write!(
|
||||
f,
|
||||
"\n======================================================\n ArtNet Statistics\n"
|
||||
)?;
|
||||
write!(
|
||||
f,
|
||||
"frame_count: {}, total_time: {:?}, avg_delta: {}µs\n",
|
||||
self.frame_count,
|
||||
self.total_time,
|
||||
self.avg_delta
|
||||
.unwrap_or_else(|| Duration::default())
|
||||
.as_micros()
|
||||
)?;
|
||||
write!(
|
||||
f,
|
||||
"min_time: {}, average_time: {}, max_time: {}\n",
|
||||
self.min_time
|
||||
.unwrap_or_else(|| Duration::default())
|
||||
.as_micros() as f64
|
||||
/ 1000_f64,
|
||||
average_time.as_micros() as f64 / 1000_f64,
|
||||
self.max_time
|
||||
.unwrap_or_else(|| Duration::default())
|
||||
.as_micros() as f64
|
||||
/ 1000_f64
|
||||
)?;
|
||||
write!(
|
||||
f,
|
||||
"min_framerate: {}, average_framerate: {}, max_framerate: {}\n",
|
||||
1_f64
|
||||
/ self
|
||||
.max_time
|
||||
.unwrap_or_else(|| Duration::from_secs(0))
|
||||
.as_secs_f64(),
|
||||
1_f64 / average_time.as_secs_f64(),
|
||||
1_f64
|
||||
/ self
|
||||
.min_time
|
||||
.unwrap_or_else(|| Duration::from_secs(0))
|
||||
.as_secs_f64()
|
||||
)?;
|
||||
write!(f, "==================================================")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
use crate::cputasks::modes::AppModeHandler;
|
||||
|
||||
use crate::devices::led_driver::LedDriver;
|
||||
use std::iter::Peekable;
|
||||
use std::ops::Range;
|
||||
use std::slice::Iter;
|
||||
|
||||
pub struct DiagnosticsMode {
|
||||
cycle_count: usize,
|
||||
color_iterator: Peekable<Iter<'static, u32>>,
|
||||
led_iterator: Range<usize>,
|
||||
}
|
||||
|
||||
impl DiagnosticsMode {
|
||||
const TEST_COLORS: [u32; 7] = [
|
||||
0x1f0000, 0x001f00, 0x00001f, 0x5f5f00, 0x1f001f, 0x001f1f, 0x1f1f1f,
|
||||
];
|
||||
|
||||
pub fn new() -> Self {
|
||||
DiagnosticsMode {
|
||||
cycle_count: 0,
|
||||
color_iterator: Self::color_iterator(),
|
||||
led_iterator: 0..0,
|
||||
}
|
||||
}
|
||||
|
||||
fn color_iterator() -> Peekable<Iter<'static, u32>> {
|
||||
Self::TEST_COLORS.iter().peekable()
|
||||
}
|
||||
|
||||
fn init_led_iterator(led_driver: &LedDriver) -> Range<usize> {
|
||||
// Initialize the LED iterator to cover all LEDs
|
||||
0..led_driver.get_led_per_strip_count()
|
||||
}
|
||||
}
|
||||
|
||||
impl AppModeHandler for DiagnosticsMode {
|
||||
fn enter(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
log::debug!("[Diagnostics] Entering Diagnostics Mode");
|
||||
self.cycle_count = 0;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run(&mut self, led_driver: &mut LedDriver) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if self.cycle_count % 50 == 0 {
|
||||
log::trace!("[Diagnostics] Running...");
|
||||
let (led, color) = match self.led_iterator.next() {
|
||||
Some(led) => {
|
||||
// led_driver.direct_set_color(**self.color_iterator.peek().unwrap(), led);
|
||||
led_driver.set_led_color(**self.color_iterator.peek().unwrap(), led, 0);
|
||||
(led, *self.color_iterator.peek().unwrap())
|
||||
}
|
||||
None => {
|
||||
// Reset the LED iterator if it reaches the end
|
||||
self.led_iterator = DiagnosticsMode::init_led_iterator(led_driver);
|
||||
let led = self.led_iterator.next().unwrap();
|
||||
self.color_iterator.next();
|
||||
(
|
||||
led,
|
||||
match self.color_iterator.peek() {
|
||||
Some(color) => {
|
||||
// led_driver.direct_set_color(**color, led);
|
||||
led_driver.set_led_color(**color, led, 0);
|
||||
*color
|
||||
}
|
||||
None => {
|
||||
// Reset the color iterator if it reaches the end
|
||||
self.color_iterator = DiagnosticsMode::color_iterator();
|
||||
let color = self.color_iterator.peek().unwrap();
|
||||
// led_driver.direct_set_color(**color, led);
|
||||
led_driver.set_led_color(**color, led, 0);
|
||||
*color
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
};
|
||||
log::trace!("Setting LED {} to color {:06x}", led, color);
|
||||
led_driver.refresh();
|
||||
}
|
||||
self.cycle_count += 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn exit(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
log::debug!("[Diagnostics] Exiting Diagnostics Mode");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
use crate::cputasks::modes::AppModeHandler;
|
||||
use crate::devices::led_driver::LedDriver;
|
||||
|
||||
pub struct ManualMode;
|
||||
|
||||
impl ManualMode {
|
||||
pub fn new() -> Self {
|
||||
ManualMode
|
||||
}
|
||||
}
|
||||
|
||||
impl AppModeHandler for ManualMode {
|
||||
fn enter(&mut self) -> std::result::Result<(), Box<(dyn std::error::Error + 'static)>> {
|
||||
log::debug!("[Manual] Entering Manual Mode");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run(
|
||||
&mut self,
|
||||
_: &mut LedDriver,
|
||||
) -> std::result::Result<(), Box<(dyn std::error::Error + 'static)>> {
|
||||
log::trace!("[Manual] Running...");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn exit(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
log::debug!("[Manual] Exiting Manual Mode");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
use core::f32;
|
||||
use crossbeam::channel;
|
||||
use spectrum_analyzer::windows::hann_window;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::thread;
|
||||
use std::time::Instant;
|
||||
|
||||
use rppal::gpio::Gpio;
|
||||
use spectrum_analyzer::scaling::divide_by_N_sqrt;
|
||||
use spectrum_analyzer::{FrequencyLimit, FrequencyValue, samples_fft_to_spectrum};
|
||||
|
||||
use crate::cputasks::modes::AppModeHandler;
|
||||
use crate::devices::led_driver::LedDriver;
|
||||
|
||||
pub struct StandaloneMode {
|
||||
_i2s_mic_pin: Vec<rppal::gpio::IoPin>,
|
||||
audio_processor: Option<AudioProcessorControl>,
|
||||
}
|
||||
|
||||
impl StandaloneMode {
|
||||
const I2S_PINS: [u8; 3] = [20, 19, 18];
|
||||
|
||||
pub fn new() -> Self {
|
||||
let gpio = Gpio::new().expect("Failed to initialize GPIO");
|
||||
let i2s_mic_pin: Vec<rppal::gpio::IoPin> = Self::I2S_PINS
|
||||
.iter()
|
||||
.map(|&pin| {
|
||||
gpio.get(pin)
|
||||
.expect(&format!("Failed to get GPIO pin {}", pin))
|
||||
.into_io(rppal::gpio::Mode::Alt0)
|
||||
})
|
||||
.collect();
|
||||
StandaloneMode {
|
||||
_i2s_mic_pin: i2s_mic_pin,
|
||||
audio_processor: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_log_spectrum(
|
||||
fft_data: &[(f32, f32)],
|
||||
strip_count: usize,
|
||||
f_min: f32,
|
||||
f_max: f32,
|
||||
) -> Vec<f32> {
|
||||
let mut bands = vec![0.0; strip_count];
|
||||
let mut counts = vec![0usize; strip_count];
|
||||
|
||||
// Precompute band edges logarithmically
|
||||
let mut edges = Vec::with_capacity(strip_count + 1);
|
||||
for i in 0..=strip_count {
|
||||
let fraction = i as f32 / strip_count as f32;
|
||||
let edge = f_min * (f_max / f_min).powf(fraction);
|
||||
edges.push(edge);
|
||||
}
|
||||
|
||||
// Assign each frequency to a band
|
||||
for &(freq, amp) in fft_data {
|
||||
if freq < f_min || freq > f_max {
|
||||
continue;
|
||||
}
|
||||
if let Some(idx) = edges.windows(2).position(|w| freq >= w[0] && freq < w[1]) {
|
||||
bands[idx] += amp;
|
||||
counts[idx] += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize
|
||||
for (b, c) in bands.iter_mut().zip(counts) {
|
||||
if c > 0 {
|
||||
*b /= c as f32;
|
||||
}
|
||||
}
|
||||
|
||||
bands
|
||||
}
|
||||
}
|
||||
|
||||
impl AppModeHandler for StandaloneMode {
|
||||
fn enter(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
log::debug!("[Standalone] Entering Standalone Mode");
|
||||
let audio_processor = AudioProcessorControl::new()?;
|
||||
self.audio_processor = Some(audio_processor);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run(&mut self, led_driver: &mut LedDriver) -> Result<(), Box<dyn std::error::Error>> {
|
||||
log::trace!("[Standalone] Running...");
|
||||
if let Some(audio_processor) = &self.audio_processor {
|
||||
match audio_processor.get_data() {
|
||||
Some(data) => {
|
||||
log::trace!("[Standalone] Received audio data: {:?}", data.len());
|
||||
|
||||
log::info!(
|
||||
"Frequency count: {} MAX([|{}; {}|]) -> {:?}",
|
||||
data.len(),
|
||||
data[0].0,
|
||||
data[data.len() - 1].0,
|
||||
data.iter()
|
||||
.fold((0_f32, 0_f32), |(freq_max, val_max), (freq, val)| {
|
||||
if *val >= val_max {
|
||||
(*freq, *val)
|
||||
} else {
|
||||
(freq_max, val_max)
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
let spectrum = StandaloneMode::compute_log_spectrum(
|
||||
&data,
|
||||
led_driver.get_strip_count(),
|
||||
data[0].0,
|
||||
data[data.len() - 1].0,
|
||||
);
|
||||
|
||||
log::debug!(
|
||||
"[Standalone] Computed spectrum: {:?}",
|
||||
spectrum
|
||||
.iter()
|
||||
.map(|v| v.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
);
|
||||
|
||||
let avg_intensity = spectrum.iter().fold(0_f32, |x_max, x| x_max.max(*x));
|
||||
|
||||
let g = avg_intensity.min(255.0) as u8;
|
||||
let r = 0_u8;
|
||||
let b = 255 - avg_intensity.min(255.0) as u8;
|
||||
let color: u32 = ((r as u32) << 16) | ((g as u32) << 8) | (b as u32);
|
||||
|
||||
let light_number =
|
||||
(avg_intensity as usize / 50).min(led_driver.get_led_per_strip_count());
|
||||
|
||||
log::debug!(
|
||||
"[Standalone] Average intensity: {}, Color: {:06x}, Light number: {}",
|
||||
avg_intensity,
|
||||
color,
|
||||
light_number
|
||||
);
|
||||
|
||||
for i in 0..light_number {
|
||||
// Set the color for the first `light_number` LEDs
|
||||
led_driver.direct_set_color(color, i);
|
||||
}
|
||||
for i in light_number..led_driver.get_led_per_strip_count() {
|
||||
// Set the remaining LEDs to black
|
||||
led_driver.direct_set_color(0x000000, i);
|
||||
}
|
||||
led_driver.refresh();
|
||||
}
|
||||
None => {
|
||||
log::trace!("[Standalone] No audio data received");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::warn!("[Standalone] Audio processor is not initialized");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn exit(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
log::debug!("[Standalone] Exiting Standalone Mode");
|
||||
self.audio_processor = None;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct AudioProcessorControl {
|
||||
rx: channel::Receiver<Vec<(f32, f32)>>,
|
||||
stop_flag: Arc<AtomicBool>,
|
||||
handle: Option<std::thread::JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl AudioProcessorControl {
|
||||
pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let stop_flag = Arc::new(AtomicBool::new(false));
|
||||
let flag_clone = Arc::clone(&stop_flag);
|
||||
|
||||
let (tx, rx): (
|
||||
channel::Sender<Vec<(f32, f32)>>,
|
||||
channel::Receiver<Vec<(f32, f32)>>,
|
||||
) = crossbeam::channel::bounded(1);
|
||||
|
||||
let mut audio_processor = AudioProcessor::new(tx)?;
|
||||
let join_handle = thread::spawn(move || {
|
||||
log::info!("Starting audio processing thread");
|
||||
while !flag_clone.load(Ordering::Relaxed) {
|
||||
audio_processor.process_audio();
|
||||
}
|
||||
});
|
||||
Ok(AudioProcessorControl {
|
||||
rx,
|
||||
stop_flag,
|
||||
handle: Some(join_handle),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_data(&self) -> Option<Vec<(f32, f32)>> {
|
||||
match self.rx.try_recv() {
|
||||
Ok(data) => Some(data),
|
||||
Err(crossbeam::channel::TryRecvError::Empty) => None,
|
||||
Err(e) => {
|
||||
log::error!("Failed to receive data from channel: {:?}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Drop for AudioProcessorControl {
|
||||
fn drop(&mut self) {
|
||||
log::info!("Stopping audio processing thread");
|
||||
self.stop_flag.store(true, Ordering::Relaxed);
|
||||
let _ = self.handle.take().unwrap().join().map_err(|e| {
|
||||
log::error!("Failed to stop audio processing thread: {:?}", e);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
struct AudioProcessor {
|
||||
pcm: alsa::pcm::PCM,
|
||||
spectrum: Vec<(f32, f32)>,
|
||||
tx: channel::Sender<Vec<(f32, f32)>>,
|
||||
}
|
||||
|
||||
impl AudioProcessor {
|
||||
pub fn new(tx: channel::Sender<Vec<(f32, f32)>>) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let hints = alsa::device_name::HintIter::new_str(None, "pcm").unwrap();
|
||||
for hint in hints {
|
||||
// When Direction is None it means that both the PCM supports both playback and capture
|
||||
if hint.name.is_some()
|
||||
&& hint.desc.is_some()
|
||||
&& (hint.direction.is_none()
|
||||
|| hint
|
||||
.direction
|
||||
.map(|dir| dir == alsa::Direction::Capture)
|
||||
.unwrap_or_default())
|
||||
{
|
||||
log::debug!(
|
||||
"pcm: {:<35} desc: {:?}",
|
||||
hint.name.unwrap(),
|
||||
hint.desc.unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let pcm = alsa::PCM::new(
|
||||
"plughw:CARD=sndrpigooglevoi,DEV=0",
|
||||
alsa::Direction::Capture,
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
{
|
||||
// For this example, we assume 44100Hz, one channel, 16 bit audio.
|
||||
let hwp = alsa::pcm::HwParams::any(&pcm).unwrap();
|
||||
hwp.set_channels_near(1).unwrap();
|
||||
hwp.set_rate_near(44100, alsa::ValueOr::Nearest).unwrap();
|
||||
hwp.set_format(alsa::pcm::Format::float()).unwrap();
|
||||
hwp.set_access(alsa::pcm::Access::RWInterleaved).unwrap();
|
||||
pcm.hw_params(&hwp).unwrap();
|
||||
}
|
||||
pcm.start()?;
|
||||
|
||||
Ok(AudioProcessor {
|
||||
pcm,
|
||||
spectrum: Vec::new(),
|
||||
tx,
|
||||
})
|
||||
}
|
||||
|
||||
fn process_audio(&mut self) {
|
||||
// Placeholder for audio processing logic
|
||||
// This should return a vector of tuples representing frequency and amplitude
|
||||
let start = Instant::now();
|
||||
|
||||
let binding = &self.pcm;
|
||||
let io = binding.io_f32().unwrap();
|
||||
let mut buf = [0f32; 2048];
|
||||
// Block while waiting for 2048 samples to be read from the device.
|
||||
let nb_samples = io.readi(&mut buf).unwrap();
|
||||
let elapsed_1 = start.elapsed();
|
||||
|
||||
log::trace!(
|
||||
"[Standalone] Read {} samples in {:?}",
|
||||
nb_samples,
|
||||
elapsed_1
|
||||
);
|
||||
|
||||
let start = Instant::now();
|
||||
if nb_samples >= 2048 {
|
||||
let data = buf[..2048].to_vec();
|
||||
let res: spectrum_analyzer::FrequencySpectrum = samples_fft_to_spectrum(
|
||||
&hann_window(&data),
|
||||
44100,
|
||||
FrequencyLimit::Range(20_f32, 20000_f32),
|
||||
// FrequencyLimit::All,
|
||||
Some(÷_by_N_sqrt),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
if self.spectrum.is_empty() {
|
||||
self.spectrum = Vec::from_iter(
|
||||
res.data()
|
||||
.iter()
|
||||
.map(|(fr, fr_val)| (fr.val(), fr_val.val() * 5000.0_f32)),
|
||||
);
|
||||
} else {
|
||||
res.data().iter().zip(self.spectrum.iter_mut()).for_each(
|
||||
|((fr, fr_val), (fr_old, fr_old_val))| {
|
||||
*fr_old = fr.val();
|
||||
let old_val = *fr_old_val * 0.84;
|
||||
let max = (*fr_val * 5000.0_f32.into()).max(FrequencyValue::from(old_val));
|
||||
*fr_old_val = max.val();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
self.tx
|
||||
.try_send(self.spectrum.clone())
|
||||
.expect("Failed to send audio data");
|
||||
|
||||
let elapsed_2 = start.elapsed();
|
||||
log::trace!("{elapsed_1:?} {elapsed_2:?}");
|
||||
} else {
|
||||
log::trace!("{elapsed_1:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod led_driver;
|
||||
pub mod selector;
|
||||
@@ -0,0 +1 @@
|
||||
pub use led_driver::LedDriver;
|
||||
@@ -0,0 +1,160 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
pub mod led {
|
||||
// Constants
|
||||
pub const TX_TEST: u8 = 0;
|
||||
pub const LED_NBITS: usize = 24;
|
||||
pub const LED_PREBITS: usize = 4;
|
||||
pub const LED_POSTBITS: usize = 4;
|
||||
pub const BIT_NPULSES: usize = 3;
|
||||
|
||||
// Helper macros as functions
|
||||
#[inline]
|
||||
pub const fn led_dlen() -> usize {
|
||||
LED_NBITS * BIT_NPULSES
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn led_tx_oset(n: usize) -> usize {
|
||||
LED_PREBITS + (led_dlen() * n)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn tx_buff_len(n: usize) -> usize {
|
||||
led_tx_oset(n) + LED_POSTBITS
|
||||
}
|
||||
|
||||
// TXDATA_T: u8 or u16 depending on channel count
|
||||
#[cfg(feature = "led_nchans_gt_8")]
|
||||
pub type TxData = u16;
|
||||
#[cfg(not(feature = "led_nchans_gt_8"))]
|
||||
pub type TxData = u8;
|
||||
|
||||
// Dummy types for external dependencies
|
||||
pub struct MemMap;
|
||||
pub struct VC_MEM;
|
||||
pub struct DMA_CHAN_A;
|
||||
|
||||
// Dummy global variables
|
||||
pub static mut VC_MEM: Option<MemMap> = None;
|
||||
pub static mut TXDATA: Option<*mut TxData> = None;
|
||||
|
||||
// Buffer for TX data
|
||||
pub static mut TX_BUFFER: [TxData; tx_buff_len(1)] = [0; tx_buff_len(1)];
|
||||
|
||||
pub fn swap_bytes(tx_buffer: &mut [u16]) {
|
||||
for word in tx_buffer.iter_mut() {
|
||||
*word = word.swap_bytes();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn leddriver_setup() {
|
||||
// Placeholder for setup logic
|
||||
// videocore_setup, gpio_setup, smi_setup
|
||||
|
||||
}
|
||||
|
||||
pub fn leddriver_close() {
|
||||
// Placeholder for close logic
|
||||
// videocore_close, smi_close, gpio_close
|
||||
}
|
||||
|
||||
pub fn set_color(rgb: u32, index: usize, tx_buffer: &mut [TxData]) {
|
||||
let mut msk = 0b1 << 23; // Start with the highest bit for the first color channel
|
||||
let mut txd_index = led_tx_oset(index);
|
||||
|
||||
for n in 0..LED_NBITS {
|
||||
msk = match n {
|
||||
0 => 0x80_0000,
|
||||
8 => 0x8000,
|
||||
16 => 0x80,
|
||||
_ => msk >> 1,
|
||||
};
|
||||
tx_buffer[txd_index + 0] = TxData::MAX;
|
||||
tx_buffer[txd_index + 1] = 0;
|
||||
tx_buffer[txd_index + 2] = 0;
|
||||
if (rgb & msk) != 0 {
|
||||
tx_buffer[txd_index + 1] = TxData::MAX;
|
||||
}
|
||||
txd_index += BIT_NPULSES;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rgb_txdata(rgbs: &[u32], index: usize, tx_buffer: &mut [TxData], led_nchans: usize) {
|
||||
let mut msk = 0;
|
||||
let mut txd_index = led_tx_oset(index);
|
||||
|
||||
for n in 0..LED_NBITS {
|
||||
msk = match n {
|
||||
0 => 0x800000,
|
||||
8 => 0x8000,
|
||||
16 => 0x80,
|
||||
_ => msk >> 1,
|
||||
};
|
||||
tx_buffer[txd_index + 0] = TxData::MAX;
|
||||
tx_buffer[txd_index + 1] = 0;
|
||||
tx_buffer[txd_index + 2] = 0;
|
||||
for i in 0..led_nchans {
|
||||
if (rgbs[i] & msk) != 0 {
|
||||
tx_buffer[txd_index + 1] |= 1 << i;
|
||||
}
|
||||
}
|
||||
txd_index += BIT_NPULSES;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn leddriver_refresh() {
|
||||
// Placeholder for refresh logic
|
||||
// swap_bytes, dma_active, memcpy, enable_dma, start_smi, usleep
|
||||
}
|
||||
|
||||
/// Convert HSV to packed RGB (GRB order)
|
||||
pub fn color_hsv(hue: u16, sat: u8, val: u8) -> u32 {
|
||||
let mut r: u8 = 0;
|
||||
let mut g: u8 = 0;
|
||||
let mut b: u8 = 0;
|
||||
|
||||
let mut hue = ((hue as u32 * 1530 + 32768) / 65536) as u16;
|
||||
|
||||
if hue < 510 {
|
||||
b = 0;
|
||||
if hue < 255 {
|
||||
r = 255;
|
||||
g = hue as u8;
|
||||
} else {
|
||||
r = (510 - hue) as u8;
|
||||
g = 255;
|
||||
}
|
||||
} else if hue < 1020 {
|
||||
r = 0;
|
||||
if hue < 765 {
|
||||
g = 255;
|
||||
b = (hue - 510) as u8;
|
||||
} else {
|
||||
g = (1020 - hue) as u8;
|
||||
b = 255;
|
||||
}
|
||||
} else if hue < 1530 {
|
||||
g = 0;
|
||||
if hue < 1275 {
|
||||
r = (hue - 1020) as u8;
|
||||
b = 255;
|
||||
} else {
|
||||
r = 255;
|
||||
b = (1530 - hue) as u8;
|
||||
}
|
||||
} else {
|
||||
r = 255;
|
||||
g = 0;
|
||||
b = 0;
|
||||
}
|
||||
|
||||
let v1 = 1 + val as u32;
|
||||
let s1 = 1 + sat as u32;
|
||||
let s2 = 255 - sat as u32;
|
||||
|
||||
((((((r as u32 * s1) >> 8) + s2) * v1) & 0xff00) << 8)
|
||||
| (((((g as u32 * s1) >> 8) + s2) * v1) & 0xff00)
|
||||
| (((((b as u32 * s1) >> 8) + s2) * v1) >> 8)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// Constants
|
||||
const LED_NBITS: usize = 24; // Number of data bits per LED
|
||||
const LED_PREBITS: usize = 4; // Number of zero bits before LED data
|
||||
const LED_POSTBITS: usize = 4; // Number of zero bits after LED data
|
||||
const BIT_NPULSES: usize = 3; // Number of O/P pulses per LED bit
|
||||
|
||||
#[cfg(feature = "rpi4")] // Timings for RPi v4 (1.5 GHz)
|
||||
const SMI_TIMING: [u32; 4] = [10, 15, 30, 15]; // 400 ns cycle time
|
||||
#[cfg(not(feature = "rpi4"))] // Timings for RPi v0-3 (1 GHz)
|
||||
const SMI_TIMING: [u32; 4] = [10, 10, 20, 10]; // 400 ns cycle time
|
||||
|
||||
// Use 16-bit data type for TxDataT
|
||||
#[cfg(feature = "16channel")]
|
||||
type TxDataT = u16;
|
||||
// Use 8-bit data type for TxDataT
|
||||
#[cfg(not(feature = "16channel"))]
|
||||
type TxDataT = u8;
|
||||
|
||||
// Helper macros as functions
|
||||
// Length of data for 1 row (1 LED on each channel)
|
||||
#[inline]
|
||||
const fn led_dlen() -> usize {
|
||||
LED_NBITS * BIT_NPULSES
|
||||
}
|
||||
|
||||
// Offset into Tx data buffer, given LED number in chan
|
||||
#[inline]
|
||||
const fn led_tx_offset(n: usize) -> usize {
|
||||
LED_PREBITS + (led_dlen() * n)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
const fn tx_buff_len(n: usize) -> usize {
|
||||
led_tx_offset(n) + LED_POSTBITS
|
||||
}
|
||||
|
||||
pub struct LedDriver {
|
||||
// Placeholder for LED driver state
|
||||
tx_buffer: [TxDataT; tx_buff_len(30)],
|
||||
}
|
||||
|
||||
impl LedDriver {
|
||||
pub fn new() -> LedDriver {
|
||||
// Initialize the LED driver
|
||||
LedDriver {
|
||||
tx_buffer: [TxDataT::default(); tx_buff_len(30)],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_color(&self, rgb: u32, index: usize) -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Set the color for the LED at the specified index
|
||||
// Placeholder for actual implementation
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn refresh(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Refresh the LED states
|
||||
// Placeholder for actual implementation
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
use std::fs::OpenOptions;
|
||||
use std::io;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::ptr;
|
||||
use libc::{mmap, munmap, MAP_FAILED, MAP_SHARED, PROT_READ, PROT_WRITE, O_RDWR, O_SYNC, O_CLOEXEC};
|
||||
use log::{info};
|
||||
|
||||
// Location of peripheral registers in physical memory
|
||||
#[cfg(feature = "rpi")]
|
||||
const PHYS_REG_BASE: usize = 0x2000_0000; // Pi Zero or 1
|
||||
#[cfg(any(feature = "rpi2", feature = "rpi3"))]
|
||||
const PHYS_REG_BASE: usize = 0x3F00_0000; // Pi 2 or 3 or Zero 2
|
||||
#[cfg(feature = "rpi4")]
|
||||
const PHYS_REG_BASE: usize = 0xFE00_0000; // Pi 4
|
||||
|
||||
// Clock frequency
|
||||
#[cfg(feature = "rpi")]
|
||||
const CLOCK_HZ: usize = 400_000_000; // Pi Zero
|
||||
#[cfg(any(feature = "rpi2", feature = "rpi3", feature = "rpi4"))]
|
||||
const CLOCK_HZ: usize = 250_000_000; // Pi 2 - 4
|
||||
|
||||
// Location of peripheral registers in bus memory
|
||||
const BUS_REG_BASE: usize = 0x7E00_0000;
|
||||
|
||||
const MEM_DEVICE_FILE: &str = "/dev/mem"; // Memory device file
|
||||
|
||||
|
||||
// Get virtual 8 and 32-bit pointers to register
|
||||
#[inline]
|
||||
const unsafe fn reg8(m: &MemMap, x: usize) -> *mut u8 {
|
||||
(m.virt as usize + x) as *mut u8
|
||||
}
|
||||
|
||||
#[inline]
|
||||
const unsafe fn reg32(m: &MemMap, x: usize) -> *mut u32 {
|
||||
(m.virt as usize + x) as *mut u32
|
||||
}
|
||||
|
||||
// Get bus address of register
|
||||
#[inline]
|
||||
const fn reg_bus_addr(m: &MemMap, x: usize) -> usize {
|
||||
m.bus as usize + x
|
||||
}
|
||||
|
||||
// Convert uncached memory virtual address to bus address
|
||||
#[inline]
|
||||
const fn mem_bus_addr(mp: &MemMap, a: usize) -> usize {
|
||||
a - mp.virt as usize + mp.bus as usize
|
||||
}
|
||||
|
||||
// Convert bus address to physical address (for mmap)
|
||||
#[inline]
|
||||
const fn bus_phys_addr(a: usize) -> usize {
|
||||
a & !0xC000_0000
|
||||
}
|
||||
|
||||
const PAGE_SIZE: usize = 0x1000; // 4 KiB page size
|
||||
const fn page_roundup(size: usize) -> usize {
|
||||
(size + PAGE_SIZE - 1) & !(PAGE_SIZE - 1)
|
||||
}
|
||||
|
||||
// Structure for mapped peripheral or memory
|
||||
#[repr(C)]
|
||||
pub struct MemMap {
|
||||
pub fd: i32, // File descriptor
|
||||
pub h: i32, // Memory handle
|
||||
pub size: usize, // Memory size
|
||||
pub bus: *mut libc::c_void, // Bus address
|
||||
pub virt: *mut libc::c_void, // Virtual address
|
||||
pub phys: *mut libc::c_void, // Physical address
|
||||
}
|
||||
|
||||
impl MemMap {
|
||||
// Create a new memory map
|
||||
pub fn new(size: usize, phys: *mut libc::c_void) -> io::Result<MemMap> {
|
||||
let size = page_roundup(size);
|
||||
let bus_addr = phys as usize - PHYS_REG_BASE + BUS_REG_BASE;
|
||||
let virt_addr = unsafe { map_segment(phys as usize, size) };
|
||||
|
||||
if virt_addr.is_null() {
|
||||
return Err(io::Error::new(io::ErrorKind::Other, "Memory mapping failed"));
|
||||
}
|
||||
|
||||
Ok(MemMap {
|
||||
fd: -1,
|
||||
h: -1,
|
||||
size: size,
|
||||
bus: virt_addr,
|
||||
virt: virt_addr,
|
||||
phys: phys,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Implement Drop to automatically unmap memory when MemMap goes out of scope
|
||||
impl Drop for MemMap {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
unmap_segment(self.virt, self.size);
|
||||
}
|
||||
self.virt = ptr::null_mut();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
unsafe fn map_segment(addr: usize, size: usize) -> *mut libc::c_void {
|
||||
let size = page_roundup(size);
|
||||
|
||||
let file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.custom_flags(O_SYNC | O_CLOEXEC)
|
||||
.open("/dev/mem")
|
||||
.unwrap_or_else(|_| {
|
||||
panic!("can't open /dev/mem, run using sudo");
|
||||
});
|
||||
|
||||
let fd = file.as_raw_fd();
|
||||
|
||||
let mem = mmap(
|
||||
ptr::null_mut(),
|
||||
size,
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED,
|
||||
fd,
|
||||
addr as libc::off_t,
|
||||
);
|
||||
drop(file);
|
||||
|
||||
info("Map {:p} -> {:p}", addr as *const (), mem);
|
||||
|
||||
if mem == MAP_FAILED {
|
||||
panic!("Memory mapping {:p} -> {:p} failed", addr as *const (), mem);
|
||||
}
|
||||
mem
|
||||
}
|
||||
|
||||
// Free mapped memory
|
||||
unsafe fn unmap_segment(mem: *mut libc::c_void, size: usize) {
|
||||
if !mem.is_null() {
|
||||
munmap(mem, page_roundup(size));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
use std::fs::OpenOptions;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::io;
|
||||
use std::mem;
|
||||
use std::os::raw::c_void;
|
||||
|
||||
|
||||
|
||||
|
||||
bitflags::bitflags! {
|
||||
pub struct VCAllocFlags: u32 {
|
||||
const MEM_FLAG_DISCARDABLE = 1 << 0; // can be resized to 0 at any time. Use for cached data
|
||||
const MEM_FLAG_NORMAL = 0 << 2; // normal allocating alias. Don't use from ARM
|
||||
const MEM_FLAG_DIRECT = 1 << 2; // 0xC alias uncached
|
||||
const MEM_FLAG_COHERENT = 2 << 2; // 0x8 alias. Non-allocating in L2 but coherent
|
||||
const MEM_FLAG_ZERO = 1 << 4; // initialise buffer to all zeros
|
||||
const MEM_FLAG_NO_INIT = 1 << 5; // don't initialise (default is initialise to all ones)
|
||||
const MEM_FLAG_HINT_PERMALOCK = 1 << 6; // Likely to be locked for long periods of time
|
||||
const MEM_FLAG_L1_NONALLOCATING = Self::MEM_FLAG_DIRECT.bits | Self::MEM_FLAG_COHERENT.bits; // Allocating in L2
|
||||
}
|
||||
}
|
||||
|
||||
pub const DMA_MEM_FLAGS: VCAllocFlags = VCAllocFlags::MEM_FLAG_DIRECT | VCAllocFlags::MEM_FLAG_ZERO;
|
||||
|
||||
#[repr(C, align(16))]
|
||||
pub struct VcMsg {
|
||||
pub len: u32, // Overall length (bytes)
|
||||
pub req: u32, // Zero for request, 1<<31 for response
|
||||
pub tag: u32, // Command number
|
||||
pub blen: u32, // Buffer length (bytes)
|
||||
pub dlen: u32, // Data length (bytes)
|
||||
pub uints: [u32; 27], // Data (108 bytes maximum)
|
||||
}
|
||||
|
||||
pub fn open_mbox() -> io::Result<RawFd> {
|
||||
let file = OpenOptions::new().read(true).open("/dev/vcio");
|
||||
match file {
|
||||
Ok(f) => Ok(f.as_raw_fd()),
|
||||
Err(e) => {
|
||||
log::error!("can't open VC mailbox: {}", e);
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn close_mbox(fd: RawFd) {
|
||||
if fd >= 0 {
|
||||
// SAFETY: closing a valid file descriptor
|
||||
unsafe { libc::close(fd) };
|
||||
}
|
||||
}
|
||||
|
||||
const VC_IOC_MAGIC: u8 = 100;
|
||||
const VC_IOC_MBOX: u64 = nix::request_code_readwrite!(VC_IOC_MAGIC, 0, mem::size_of::<VcMsg>());
|
||||
|
||||
pub fn msg_mbox(fd: RawFd, msg: &mut VcMsg) -> u32 {
|
||||
// Zero out unused message buffer
|
||||
let dlen_words = (msg.dlen / 4) as usize;
|
||||
let blen_words = (msg.blen / 4) as usize;
|
||||
for i in dlen_words..=blen_words {
|
||||
if i < msg.uints.len() {
|
||||
msg.uints[i] = 0;
|
||||
}
|
||||
}
|
||||
msg.len = ((msg.blen + 6) * 4) as u32;
|
||||
msg.req = 0;
|
||||
|
||||
let ret = ioctl_write_ptr!(fd, VC_IOC_MAGIC, 0, VcMsg, msg);
|
||||
|
||||
if ret < 0 {
|
||||
log::error!("VC IOCTL failed");
|
||||
0
|
||||
} else if (msg.req & 0x8000_0000) == 0 {
|
||||
log::error!("VC IOCTL error");
|
||||
0
|
||||
} else if msg.req == 0x8000_0001 {
|
||||
log::error!("VC IOCTL partial error");
|
||||
0
|
||||
} else {
|
||||
msg.uints[0]
|
||||
}
|
||||
disp_vc_msg(msgp);
|
||||
}
|
||||
|
||||
pub fn disp_vc_msg(msg: &VcMsg) {
|
||||
print!(
|
||||
"VC msg len={:X}, req={:X}, tag={:X}, blen={:x}, dlen={:x}, data ",
|
||||
msg.len, msg.req, msg.tag, msg.blen, msg.dlen
|
||||
);
|
||||
let blen_words = (msg.blen / 4) as usize;
|
||||
for i in 0..blen_words.min(msg.uints.len()) {
|
||||
print!("{:08X} ", msg.uints[i]);
|
||||
}
|
||||
println!();
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
use rppal::gpio::{Event, Gpio, InputPin};
|
||||
|
||||
use rppal::gpio::Error;
|
||||
// GPIO pin number for the LED
|
||||
|
||||
pub struct Selector {
|
||||
selector_pins: Vec<InputPin>,
|
||||
}
|
||||
|
||||
impl Selector {
|
||||
const SELECTOR_PINS: [u8; 4] = [27, 5, 6, 26];
|
||||
|
||||
pub fn new() -> Result<Selector, Error> {
|
||||
let gpio = Gpio::new()?;
|
||||
// Set up the GPIO pins for the selector, use pull-down resistors to ensure a known state when not pressed
|
||||
log::debug!(
|
||||
"Setting up selector with pins {:?} as input_pullup",
|
||||
Self::SELECTOR_PINS
|
||||
);
|
||||
let selector_pins: Vec<InputPin> = Self::SELECTOR_PINS
|
||||
.iter()
|
||||
.map(|&pin| gpio.get(pin).unwrap().into_input_pullup())
|
||||
.collect();
|
||||
Ok(Selector { selector_pins })
|
||||
}
|
||||
|
||||
pub fn set_callback<F>(&mut self, index: usize, callback: F)
|
||||
where
|
||||
F: FnMut(Event) + Send + 'static,
|
||||
{
|
||||
log::debug!("Setting callback for selector pin at index {}", index);
|
||||
assert!(index < self.selector_pins.len(), "Index out of bounds");
|
||||
self.selector_pins[index]
|
||||
.set_async_interrupt(
|
||||
rppal::gpio::Trigger::FallingEdge,
|
||||
Some(std::time::Duration::from_millis(10)),
|
||||
callback,
|
||||
)
|
||||
.expect("Failed to set interrupt");
|
||||
}
|
||||
|
||||
pub fn get_current_index(&self) -> Option<usize> {
|
||||
self.selector_pins.iter().position(|pin| pin.is_low())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod selector;
|
||||
@@ -0,0 +1,44 @@
|
||||
use crossbeam::channel::Sender;
|
||||
use rppal::gpio::Event;
|
||||
use std::error::Error;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::channels::Message;
|
||||
use crate::cputasks::modes::AppMode;
|
||||
use crate::devices::selector::Selector as SelectorDevice;
|
||||
|
||||
pub struct SelectorTask {
|
||||
_selector_device: SelectorDevice,
|
||||
}
|
||||
|
||||
impl SelectorTask {
|
||||
pub fn new(tx: Sender<Message>) -> Result<SelectorTask, Box<dyn Error>> {
|
||||
log::debug!("Setting up selector task");
|
||||
let mut selector_device: SelectorDevice = SelectorDevice::new()?;
|
||||
let tx: Arc<Mutex<Sender<Message>>> = Arc::new(Mutex::new(tx));
|
||||
AppMode::for_each(|mode| {
|
||||
log::debug!(
|
||||
"Setting up selector callback for mode: ({}){:?}",
|
||||
mode as isize,
|
||||
mode
|
||||
);
|
||||
let tx = tx.clone();
|
||||
selector_device.set_callback(mode as usize, get_mode_callback(mode, tx));
|
||||
});
|
||||
get_mode_callback(selector_device.get_current_index().into(), tx)(Event::default());
|
||||
Ok(SelectorTask {
|
||||
_selector_device: selector_device,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn get_mode_callback(
|
||||
mode: AppMode,
|
||||
tx: Arc<Mutex<Sender<Message>>>,
|
||||
) -> impl Fn(Event) + Send + 'static {
|
||||
move |_| {
|
||||
log::trace!("Selector mode changed: {:?}", mode);
|
||||
let tx: std::sync::MutexGuard<'_, Sender<Message>> = tx.lock().unwrap();
|
||||
let _ = tx.send(Message::ModeChanged { mode });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
mod channels;
|
||||
mod config;
|
||||
mod cputasks;
|
||||
mod devices;
|
||||
mod iotasks;
|
||||
|
||||
use crate::cputasks::modes::ModeManager;
|
||||
use crate::devices::led_driver::LedDriver;
|
||||
use crate::iotasks::selector::SelectorTask;
|
||||
|
||||
use crossbeam::channel::unbounded;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
struct GlobalContext {
|
||||
mode_manager: ModeManager,
|
||||
_selector_task: SelectorTask,
|
||||
led_driver: LedDriver,
|
||||
}
|
||||
|
||||
pub struct LightSabre;
|
||||
pub struct LightSabreIntitialized {
|
||||
ctx: GlobalContext,
|
||||
}
|
||||
pub struct LightSabreRunning {
|
||||
join_handle: std::thread::JoinHandle<()>,
|
||||
}
|
||||
|
||||
fn setup() -> GlobalContext {
|
||||
log::info!("Setting up LightSabre...");
|
||||
ctrlc::set_handler(|| {
|
||||
cleanup();
|
||||
std::process::exit(0);
|
||||
})
|
||||
.expect("Error setting SIGINT/SIGTERM/SIGHUP handler");
|
||||
let led_driver = LedDriver::new(5, 3);
|
||||
|
||||
let (tx, rx) = unbounded();
|
||||
let mode_manager = ModeManager::new(rx);
|
||||
let selector_task = SelectorTask::new(tx.clone()).expect("Failed to create selector task");
|
||||
|
||||
// Initialization code here
|
||||
log::info!("Setup complete.");
|
||||
GlobalContext {
|
||||
mode_manager,
|
||||
_selector_task: selector_task,
|
||||
led_driver,
|
||||
}
|
||||
}
|
||||
|
||||
fn run(ctx: &mut GlobalContext) {
|
||||
// let period = Duration::from_secs(2);
|
||||
let period = Duration::from_millis(10);
|
||||
loop {
|
||||
let start = Instant::now();
|
||||
ctx.mode_manager.run(&mut ctx.led_driver);
|
||||
let elapsed = start.elapsed();
|
||||
|
||||
if elapsed > period {
|
||||
log::warn!(
|
||||
"Mode {:?} execution took too long: {:?}/{:?}",
|
||||
ctx.mode_manager.get_current_mode(),
|
||||
elapsed,
|
||||
period
|
||||
);
|
||||
}
|
||||
|
||||
std::thread::sleep(period.saturating_sub(elapsed));
|
||||
}
|
||||
}
|
||||
|
||||
fn cleanup() {
|
||||
log::info!("Cleaning up before quitting...");
|
||||
}
|
||||
|
||||
impl LightSabre {
|
||||
pub fn setup(self) -> LightSabreIntitialized {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl LightSabreIntitialized {
|
||||
pub fn run(self) -> LightSabreRunning {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl LightSabreRunning {
|
||||
pub fn wait(self) -> LightSabre {
|
||||
// Wait for the running state to finish
|
||||
// This is a placeholder; actual waiting logic would depend on the application
|
||||
self.join_handle.join().expect("Thread panicked");
|
||||
LightSabre
|
||||
}
|
||||
|
||||
pub fn stop(self) -> LightSabre {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LightSabre> for LightSabreIntitialized {
|
||||
fn from(_: LightSabre) -> Self {
|
||||
let ctx = setup();
|
||||
LightSabreIntitialized { ctx }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LightSabreIntitialized> for LightSabreRunning {
|
||||
fn from(mut value: LightSabreIntitialized) -> Self {
|
||||
LightSabreRunning {
|
||||
join_handle: std::thread::spawn(move || run(&mut value.ctx)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LightSabreRunning> for LightSabre {
|
||||
fn from(_: LightSabreRunning) -> Self {
|
||||
cleanup();
|
||||
LightSabre
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
use lightsabre_backend::LightSabre;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
env_logger::init();
|
||||
// let mut scheduler = statemachine::Scheduler::new(statemachine::ExecModeTest::new())?;
|
||||
// scheduler.start()
|
||||
let ls = LightSabre;
|
||||
ls.setup().run().wait();
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
use async_trait::async_trait;
|
||||
use std::error::Error;
|
||||
use std::ops::Index;
|
||||
|
||||
use env_logger::fmt::style::Color;
|
||||
// If 'devices' is an external crate, add it to Cargo.toml and use:
|
||||
// use devices::led_driver::LedDriver;
|
||||
// use devices::selector::Selector;
|
||||
use log::{debug, info, trace};
|
||||
use rppal::system::DeviceInfo;
|
||||
|
||||
use crate::devices::{self, Device, led_driver};
|
||||
use devices::led_driver::LedDriver;
|
||||
use devices::selector::Selector;
|
||||
|
||||
pub struct Scheduler {
|
||||
mode: Box<dyn Mode>,
|
||||
led_driver: LedDriver,
|
||||
selector: Selector,
|
||||
}
|
||||
|
||||
// trait UserCode {
|
||||
// fn setup(&self);
|
||||
// fn execute(&self);
|
||||
// }
|
||||
|
||||
impl Scheduler {
|
||||
pub fn new(startup_mode: T) -> Result<Scheduler<T>, Box<dyn Error>> {
|
||||
//setup devices
|
||||
info!("Executing on device: {}", DeviceInfo::new()?.model());
|
||||
// Initialize GPIO
|
||||
let selector = Selector::new()?;
|
||||
// #[allow(unused_variables)]
|
||||
let led_driver = LedDriver::new();
|
||||
debug!("Selector initialized.");
|
||||
// Main loop
|
||||
Ok(Scheduler {
|
||||
mode: startup_mode,
|
||||
led_driver,
|
||||
selector,
|
||||
})
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
pub async fn start(&mut self) -> Result<(), Box<dyn Error>> {
|
||||
loop {
|
||||
self.mode.execute(&mut self.led_driver).await;
|
||||
// Read the selector state
|
||||
let selector_state = self.selector.read();
|
||||
trace!("Selector state: {:?}", selector_state);
|
||||
match selector_state.iter().position(|x| *x) {
|
||||
Some(index) => {
|
||||
// If a button is pressed, change the mode based on the index
|
||||
match index {
|
||||
0 => {
|
||||
info!("Switching to Test Mode");
|
||||
self.set_mode(ExecModeTest::new());
|
||||
}
|
||||
1 => {
|
||||
info!("Switching to ArtNet Mode");
|
||||
self.set_mode(ExecModeArtNet);
|
||||
}
|
||||
2 => {
|
||||
info!("Switching to Standalone Mode");
|
||||
self.set_mode(ExecModeStandalone);
|
||||
}
|
||||
3 => {
|
||||
info!("Switching to Manual Mode");
|
||||
self.set_mode(ExecModeManual);
|
||||
}
|
||||
_ => {
|
||||
warn!("Unknown selector index: {}", index);
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// If no button is pressed, continue with the current mode
|
||||
warning!("No selector input is it connected, continuing in current mode.");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||
}
|
||||
}
|
||||
|
||||
fn set_mode(&mut self, next_mode: T) {
|
||||
self.mode = next_mode;
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait Mode {
|
||||
async fn execute(&mut self, led_driver: &mut LedDriver);
|
||||
}
|
||||
|
||||
pub struct ExecModeTest<'a> {
|
||||
color_iterator: std::iter::Peekable<std::slice::Iter<'a, u32>>,
|
||||
led_iterator: std::ops::Range<usize>,
|
||||
}
|
||||
pub struct ExecModeArtNet;
|
||||
pub struct ExecModeStandalone;
|
||||
pub struct ExecModeManual;
|
||||
|
||||
impl ExecModeTest<'_> {
|
||||
const TEST_COLORS: [u32; 7] = [
|
||||
0x1f0000, 0x001f00, 0x00001f, 0x5f5f00, 0x1f001f, 0x001f1f, 0x1f1f1f,
|
||||
];
|
||||
|
||||
pub fn new() -> Self {
|
||||
ExecModeTest {
|
||||
color_iterator: ExecModeTest::TEST_COLORS.iter().peekable(),
|
||||
led_iterator: 0..5,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mode for ExecModeTest<'_> {
|
||||
async fn execute(&mut self, led_driver: &mut LedDriver) {
|
||||
let (led, color) = match self.led_iterator.next() {
|
||||
Some(led) => {
|
||||
led_driver.set_color(**self.color_iterator.peek().unwrap(), led);
|
||||
(led, *self.color_iterator.peek().unwrap())
|
||||
}
|
||||
None => {
|
||||
// Reset the LED iterator if it reaches the end
|
||||
self.led_iterator = 0..5;
|
||||
let led = self.led_iterator.next().unwrap();
|
||||
self.color_iterator.next();
|
||||
(
|
||||
led,
|
||||
match self.color_iterator.peek() {
|
||||
Some(color) => {
|
||||
led_driver.set_color(**color, led);
|
||||
*color
|
||||
}
|
||||
None => {
|
||||
// Reset the color iterator if it reaches the end
|
||||
self.color_iterator = ExecModeTest::TEST_COLORS.iter().peekable();
|
||||
let color = self.color_iterator.peek().unwrap();
|
||||
led_driver.set_color(**color, led);
|
||||
*color
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
};
|
||||
trace!("Setting LED {} to color {:06x}", led, color);
|
||||
led_driver.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
impl Mode for ExecModeArtNet {
|
||||
async fn execute(&mut self, _led_driver: &mut LedDriver) {
|
||||
info!("Executing ArtNet mode");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user