moving standalone mode to alsa rust direct binding crate & moving led_driver to a specific package
This commit is contained in:
@@ -1,4 +0,0 @@
|
||||
fn main() {
|
||||
println!("cargo::rustc-link-search=/workspaces/LightSabre/lightsabre_backend/drivers/lib/");
|
||||
// println!("cargo:rustc-link-search=/workspaces/LightSabre/lightsabre_backend/drivers/lib");
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "led_driver"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
||||
[build-dependencies]
|
||||
bindgen = "0.72.0"
|
||||
@@ -0,0 +1,26 @@
|
||||
use std::{env, path::PathBuf};
|
||||
|
||||
fn main() {
|
||||
println!("cargo::rustc-link-search=/workspaces/LightSabre/lightsabre_backend/led_driver/lib");
|
||||
|
||||
// Tell cargo to tell rustc to link the RPiLedBars_drivers and logc libraries.
|
||||
println!("cargo:rustc-link-lib=RpiLedBars_drivers_8ch");
|
||||
println!("cargo:rustc-link-lib=logc");
|
||||
|
||||
// The bindgen::Builder is the main entry point to bindgen, and lets you build up options for the resulting bindings.
|
||||
let bindings = bindgen::Builder::default()
|
||||
// The input header we would like to generate bindings for.
|
||||
.header("lib/wrapper.h")
|
||||
// Tell cargo to invalidate the built crate whenever any of the included header files changed.
|
||||
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
|
||||
// Finish the builder and generate the bindings.
|
||||
.generate()
|
||||
// Unwrap the Result and panic on failure.
|
||||
.expect("Unable to generate bindings");
|
||||
|
||||
// Write the bindings to the $OUT_DIR/bindings.rs file.
|
||||
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
|
||||
bindings
|
||||
.write_to_file(out_path.join("bindings.rs"))
|
||||
.expect("Couldn't write bindings!");
|
||||
}
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
#! /usr/bin/env bash
|
||||
|
||||
dest_path=${0%/*}/lib
|
||||
source_host=${1:-raspberrypi.local}
|
||||
|
||||
|
||||
scp \
|
||||
raspberrypi.local:RpiLedBars/backend/.build/src/drivers/libRpiLedBars_drivers.a \
|
||||
${dest_path}/bin/libRpiLedBars_drivers_8ch.a
|
||||
|
||||
scp \
|
||||
raspberrypi.local:RpiLedBars/backend/.build/_deps/logc-build/liblogc.a \
|
||||
${dest_path}/bin/liblogc.a
|
||||
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,34 @@
|
||||
#include "include/log.h"
|
||||
#include "src/leddriver/rpi_leddriver.h"
|
||||
#include "src/common.h"
|
||||
|
||||
#define CHAN_MAXLEDS 60 // Maximum number of LEDs per channel
|
||||
#define LED_NCHANS 8 // Number of LED channels (8 or 16)
|
||||
|
||||
#define TX_TEST 0 // If non-zero, use dummy Tx data
|
||||
#define LED_NBITS 24 // Number of data bits per LED
|
||||
#define LED_PREBITS 4 // Number of zero bits before LED data
|
||||
#define LED_POSTBITS 4 // Number of zero bits after LED data
|
||||
#define BIT_NPULSES 3 // Number of O/P pulses per LED bit
|
||||
|
||||
// Length of data for 1 row (1 LED on each channel)
|
||||
#define LED_DLEN (LED_NBITS * BIT_NPULSES)
|
||||
|
||||
// Transmit data type, 8 or 16 bits
|
||||
#if LED_NCHANS > 8
|
||||
typedef uint16_t TXDATA_T;
|
||||
#else
|
||||
typedef uint8_t TXDATA_T;
|
||||
#endif
|
||||
|
||||
// Ofset into Tx data buffer, given LED number in chan
|
||||
#define LED_TX_OSET(n) (LED_PREBITS + (LED_DLEN * (n)))
|
||||
|
||||
// Size of data buffers & NV memory, given number of LEDs per chan
|
||||
#define TX_BUFF_LEN(n) (LED_TX_OSET(n) + LED_POSTBITS)
|
||||
#define TX_BUFF_SIZE(n) (TX_BUFF_LEN(n) * sizeof(TXDATA_T))
|
||||
#define VC_MEM_SIZE (PAGE_SIZE + TX_BUFF_SIZE(CHAN_MAXLEDS))
|
||||
|
||||
extern TXDATA_T tx_buffer[TX_BUFF_LEN(CHAN_MAXLEDS)];
|
||||
extern TXDATA_T *txdata;
|
||||
extern MEM_MAP vc_mem;
|
||||
@@ -0,0 +1,159 @@
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
|
||||
|
||||
pub struct LedDriver {
|
||||
led_per_strip_count: usize,
|
||||
strip_count: usize,
|
||||
}
|
||||
|
||||
// Offset into Tx data buffer, given LED number in chan
|
||||
macro_rules! LED_TX_OSET {
|
||||
($n:expr) => {
|
||||
LED_PREBITS + (LED_DLEN * $n)
|
||||
};
|
||||
}
|
||||
|
||||
impl LedDriver {
|
||||
const LED_NCHANS: usize = LED_NCHANS as usize; // Number of channels (strips)
|
||||
const TX_TEST: usize = TX_TEST as usize; // If non-zero, use dummy Tx data
|
||||
const LED_NBITS: usize = LED_NBITS as usize; // Number of data bits per LED
|
||||
pub const LED_PREBITS: usize = LED_PREBITS as usize; // Number of zero bits before LED data
|
||||
const LED_POSTBITS: usize = LED_POSTBITS as usize; // Number of zero bits after LED data
|
||||
const BIT_NPULSES: usize = BIT_NPULSES as usize; // Number of O/P pulses per LED bit
|
||||
|
||||
// Length of data for 1 row (1 LED on each channel)
|
||||
pub const LED_DLEN: usize = Self::LED_NBITS * Self::BIT_NPULSES;
|
||||
|
||||
pub fn new(led_per_strip_count: usize, strip_count: usize) -> LedDriver {
|
||||
unsafe { leddriver_setup() };
|
||||
LedDriver {
|
||||
led_per_strip_count,
|
||||
strip_count,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_led_per_strip_count(&self) -> usize {
|
||||
self.led_per_strip_count
|
||||
}
|
||||
|
||||
pub fn get_strip_count(&self) -> usize {
|
||||
self.strip_count
|
||||
}
|
||||
|
||||
pub fn set_color(&self, rgb: u32, index: usize) {
|
||||
unsafe { set_color(rgb, index as i32) };
|
||||
}
|
||||
|
||||
pub fn set_color_till_led(&self, color: u32, led_num: usize) {
|
||||
let led_num = led_num.min(self.get_led_per_strip_count());
|
||||
// Set color for all strips
|
||||
for led_index in 0..=led_num {
|
||||
self.direct_set_color(color, led_index);
|
||||
}
|
||||
for led in led_num + 1..self.get_led_per_strip_count() {
|
||||
self.direct_set_color(0x000000, led);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn direct_set_color(&self, rgb: u32, index: usize) {
|
||||
let mut mask = 0_u32;
|
||||
let mut offset = LED_TX_OSET!(index as u32) as usize;
|
||||
|
||||
// For each bit of the 24-bit RGB values..
|
||||
for n in 0..Self::LED_NBITS {
|
||||
let tx_data = unsafe { &mut tx_buffer[offset..offset + Self::BIT_NPULSES] };
|
||||
// Mask to convert RGB to GRB, M.S bit first
|
||||
Self::compute_mask(&mut mask, n);
|
||||
|
||||
// 1st byte or word is a high pulse on all lines
|
||||
tx_data[0] = 0xff;
|
||||
// 2nd has high or low bits from data
|
||||
tx_data[1] = if rgb & mask != 0_u32 { 0xff } else { 0x00 };
|
||||
// 3rd is a low pulse
|
||||
tx_data[2] = 0x00;
|
||||
offset += Self::BIT_NPULSES;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_led_color(&self, rgb: u32, led_index: usize, strip_index: usize) {
|
||||
let mut mask = 0_u32;
|
||||
let mut offset = LED_TX_OSET!(led_index as u32) as usize;
|
||||
let strip_mask = !(0b1_u8 << strip_index); // Mask to clear the led bit of desired strip
|
||||
|
||||
// For each bit of the 24-bit RGB values..
|
||||
for n in 0..Self::LED_NBITS {
|
||||
let tx_data = unsafe { &mut tx_buffer[offset..offset + Self::BIT_NPULSES] };
|
||||
// Mask to convert RGB to GRB, M.S bit first
|
||||
Self::compute_mask(&mut mask, n);
|
||||
|
||||
// 1st byte or word is a high pulse on all lines
|
||||
tx_data[0] = 0xff;
|
||||
// 2nd has high or low bits from data
|
||||
tx_data[1] = tx_data[1] & strip_mask
|
||||
| if rgb & mask != 0_u32 {
|
||||
0b1 << strip_index
|
||||
} else {
|
||||
0x00
|
||||
};
|
||||
// 3rd is a low pulse
|
||||
tx_data[2] = 0x00;
|
||||
offset += Self::BIT_NPULSES;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_strip_index_colors(&self, rgb: [u32; Self::LED_NCHANS], led_index: usize) {
|
||||
let mut mask = 0_u32;
|
||||
let mut offset = LED_TX_OSET!(led_index as u32) as usize;
|
||||
|
||||
// For each bit of the 24-bit RGB values..
|
||||
for n in 0..Self::LED_NBITS {
|
||||
let tx_data = unsafe { &mut tx_buffer[offset..offset + Self::BIT_NPULSES] };
|
||||
// Mask to convert RGB to GRB, M.S bit first
|
||||
Self::compute_mask(&mut mask, n);
|
||||
|
||||
// 1st byte or word is a high pulse on all lines
|
||||
tx_data[offset] = 0xff;
|
||||
tx_data[offset + 1] = 0x00; // Clear the strip bits first
|
||||
// 2nd has high or low bits from data
|
||||
for (strip_index, rgb) in rgb.iter().enumerate() {
|
||||
if rgb & mask != 0_u32 {
|
||||
// Set the bit for this strip
|
||||
tx_data[offset + 1] |= 0b1 << strip_index;
|
||||
}
|
||||
}
|
||||
// 3rd is a low pulse
|
||||
tx_data[offset + 2] = 0x00;
|
||||
offset += Self::BIT_NPULSES;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn refresh(&self) {
|
||||
unsafe { leddriver_refresh() };
|
||||
}
|
||||
|
||||
// pub fn color_hsv(&self, hue: u16, sat: u8, val: u8) -> u32 {
|
||||
// unsafe { ColorHSV(hue, sat, val) }
|
||||
// }
|
||||
|
||||
fn compute_mask(mask: &mut u32, n: usize) {
|
||||
*mask = if n == 0 {
|
||||
0x800000
|
||||
} else if n == 8 {
|
||||
0x8000
|
||||
} else if n == 16 {
|
||||
0x80
|
||||
} else {
|
||||
*mask >> 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for LedDriver {
|
||||
fn drop(&mut self) {
|
||||
unsafe { leddriver_close() };
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
use cpal::traits::{DeviceTrait, HostTrait};
|
||||
use rppal::gpio::Gpio;
|
||||
|
||||
use crate::cputasks::modes::AppModeHandler;
|
||||
use crate::devices::led_driver::LedDriver;
|
||||
|
||||
pub struct StandaloneMode {
|
||||
_i2s_mic_pin: Vec<rppal::gpio::IoPin>,
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AppModeHandler for StandaloneMode {
|
||||
fn enter(&mut self) {
|
||||
log::debug!("[Standalone] Entering Standalone Mode");
|
||||
let host = cpal::default_host();
|
||||
|
||||
// default_input_device()
|
||||
host.devices()
|
||||
.unwrap()
|
||||
.for_each(|device| log::info!("{:?}", device.name()));
|
||||
|
||||
if let Some(device) = host
|
||||
.input_devices()
|
||||
.unwrap()
|
||||
.find(|device| device.name() == Ok(String::from("snd_rpi_googlevoicehat_soundcar")))
|
||||
{
|
||||
log::info!("Found record device {:?}", device.name());
|
||||
log::info!("Default config : {:?}", device.default_input_config())
|
||||
} else {
|
||||
log::error!("Record device not found");
|
||||
}
|
||||
}
|
||||
|
||||
fn run(&mut self, _: &mut LedDriver) {
|
||||
log::trace!("[Standalone] Running...");
|
||||
}
|
||||
|
||||
fn exit(&mut self) {
|
||||
log::debug!("[Standalone] Exiting Standalone Mode");
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
#[link(name = "logc", kind = "static")]
|
||||
unsafe extern "C" {
|
||||
unsafe fn log_log(level: u32, file: *const u8, line: i32, fmt: *const u8, ...);
|
||||
}
|
||||
|
||||
#[link(name = "RpiLedBars_drivers", kind = "static")]
|
||||
unsafe extern "C" {
|
||||
|
||||
unsafe fn leddriver_setup();
|
||||
|
||||
unsafe fn leddriver_close();
|
||||
|
||||
unsafe fn set_color(rgb: u32, index: usize);
|
||||
|
||||
// unsafe fn rgb_txdata(rgbs: *mut u32, index: usize);
|
||||
|
||||
unsafe fn leddriver_refresh();
|
||||
|
||||
// unsafe fn ColorHSV(hue: u16, sat: u8, val: u8) -> u32;
|
||||
}
|
||||
|
||||
pub struct LedDriver {
|
||||
led_per_strips: usize,
|
||||
}
|
||||
|
||||
impl LedDriver {
|
||||
pub fn new(led_per_strips: usize) -> LedDriver {
|
||||
unsafe {
|
||||
log_log(
|
||||
0,
|
||||
"coucou".as_bytes().as_ptr(),
|
||||
13,
|
||||
"hello".as_bytes().as_ptr(),
|
||||
)
|
||||
};
|
||||
unsafe { leddriver_setup() };
|
||||
LedDriver { led_per_strips }
|
||||
}
|
||||
|
||||
pub fn get_led_per_strips(&self) -> usize {
|
||||
self.led_per_strips
|
||||
}
|
||||
|
||||
pub fn set_color(&self, rgb: u32, index: usize) {
|
||||
unsafe { set_color(rgb, index) };
|
||||
}
|
||||
|
||||
pub fn refresh(&self) {
|
||||
unsafe { leddriver_refresh() };
|
||||
}
|
||||
|
||||
// pub fn color_hsv(&self, hue: u16, sat: u8, val: u8) -> u32 {
|
||||
// unsafe { ColorHSV(hue, sat, val) }
|
||||
// }
|
||||
}
|
||||
|
||||
impl Drop for LedDriver {
|
||||
fn drop(&mut self) {
|
||||
unsafe { leddriver_close() };
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,8 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
alsa = "0.9.1"
|
||||
artnet_protocol = "0.4.3"
|
||||
cpal = "0.16.0"
|
||||
crossbeam = "0.8.4"
|
||||
ctrlc = { version = "3.4.7", features = ["termination"] }
|
||||
env_logger = "0.11.8"
|
||||
@@ -13,6 +13,12 @@ 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"]
|
||||
+3
-3
@@ -14,9 +14,9 @@ use crate::channels::Message;
|
||||
use crate::devices::led_driver::LedDriver;
|
||||
|
||||
pub trait AppModeHandler {
|
||||
fn enter(&mut self);
|
||||
fn run(&mut self, led_driver: &mut LedDriver);
|
||||
fn exit(&mut self);
|
||||
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)]
|
||||
+9
-7
@@ -20,7 +20,7 @@ impl ArtNetMode {
|
||||
}
|
||||
|
||||
impl AppModeHandler for ArtNetMode {
|
||||
fn enter(&mut self) {
|
||||
fn enter(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
log::debug!("[ArtNet] Entering ArtNet Mode");
|
||||
|
||||
let mut attempts = 0_usize;
|
||||
@@ -41,9 +41,10 @@ impl AppModeHandler for ArtNetMode {
|
||||
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) {
|
||||
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;
|
||||
@@ -109,8 +110,9 @@ impl AppModeHandler for ArtNetMode {
|
||||
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_strips() {
|
||||
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);
|
||||
@@ -118,11 +120,9 @@ impl AppModeHandler for ArtNetMode {
|
||||
// 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.set_color(color, i);
|
||||
led_driver.direct_set_color(color, i);
|
||||
}
|
||||
led_driver.refresh();
|
||||
// Here you would typically handle the output data, e.g., send it to the LED driver
|
||||
// For now, we just log it
|
||||
}
|
||||
ArtCommand::PollReply(_) => {
|
||||
log::trace!("[ArtNet] Received PollReply command, ignoring");
|
||||
@@ -139,14 +139,16 @@ impl AppModeHandler for ArtNetMode {
|
||||
Err(e) => panic!("encountered IO error: {e}"),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn exit(&mut self) {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
+13
-7
@@ -30,22 +30,24 @@ impl DiagnosticsMode {
|
||||
|
||||
fn init_led_iterator(led_driver: &LedDriver) -> Range<usize> {
|
||||
// Initialize the LED iterator to cover all LEDs
|
||||
0..led_driver.get_led_per_strips()
|
||||
0..led_driver.get_led_per_strip_count()
|
||||
}
|
||||
}
|
||||
|
||||
impl AppModeHandler for DiagnosticsMode {
|
||||
fn enter(&mut self) {
|
||||
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) {
|
||||
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.set_color(**self.color_iterator.peek().unwrap(), 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 => {
|
||||
@@ -57,14 +59,16 @@ impl AppModeHandler for DiagnosticsMode {
|
||||
led,
|
||||
match self.color_iterator.peek() {
|
||||
Some(color) => {
|
||||
led_driver.set_color(**color, led);
|
||||
// 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.set_color(**color, led);
|
||||
// led_driver.direct_set_color(**color, led);
|
||||
led_driver.set_led_color(**color, led, 0);
|
||||
*color
|
||||
}
|
||||
},
|
||||
@@ -75,9 +79,11 @@ impl AppModeHandler for DiagnosticsMode {
|
||||
led_driver.refresh();
|
||||
}
|
||||
self.cycle_count += 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn exit(&mut self) {
|
||||
fn exit(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
log::debug!("[Diagnostics] Exiting Diagnostics Mode");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+9
-3
@@ -10,15 +10,21 @@ impl ManualMode {
|
||||
}
|
||||
|
||||
impl AppModeHandler for ManualMode {
|
||||
fn enter(&mut self) {
|
||||
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) {
|
||||
fn run(
|
||||
&mut self,
|
||||
_: &mut LedDriver,
|
||||
) -> std::result::Result<(), Box<(dyn std::error::Error + 'static)>> {
|
||||
log::trace!("[Manual] Running...");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn exit(&mut self) {
|
||||
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 @@
|
||||
pub use led_driver::LedDriver;
|
||||
@@ -32,7 +32,7 @@ fn setup() -> GlobalContext {
|
||||
std::process::exit(0);
|
||||
})
|
||||
.expect("Error setting SIGINT/SIGTERM/SIGHUP handler");
|
||||
let led_driver = LedDriver::new(5);
|
||||
let led_driver = LedDriver::new(5, 3);
|
||||
|
||||
let (tx, rx) = unbounded();
|
||||
let mode_manager = ModeManager::new(rx);
|
||||
@@ -57,7 +57,7 @@ fn run(ctx: &mut GlobalContext) {
|
||||
|
||||
if elapsed > period {
|
||||
log::warn!(
|
||||
"Mode {:?} execution took too long: {:?}/{:?}ms",
|
||||
"Mode {:?} execution took too long: {:?}/{:?}",
|
||||
ctx.mode_manager.get_current_mode(),
|
||||
elapsed,
|
||||
period
|
||||
@@ -70,7 +70,6 @@ fn run(ctx: &mut GlobalContext) {
|
||||
|
||||
fn cleanup() {
|
||||
log::info!("Cleaning up before quitting...");
|
||||
// Perform cleanup here
|
||||
}
|
||||
|
||||
impl LightSabre {
|
||||
Reference in New Issue
Block a user