feat: Add RGBW LED support and hardware communication protocol
- Add RGBW LED type support alongside existing RGB LEDs - Implement 4-channel RGBW data transmission (R,G,B,W bytes) - Add RGBW visual preview with half-color, half-white gradient display - Fix RGB color calibration bug in publisher (was not being applied) - Create comprehensive hardware communication protocol documentation - Support mixed RGB/RGBW LED strips on same display - Add W channel color temperature adjustment in white balance page - Hardware acts as simple UDP-to-WS2812 bridge without type distinction
This commit is contained in:
@ -16,6 +16,18 @@ pub enum Border {
|
||||
Right,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Serialize, Deserialize, Debug, PartialEq)]
|
||||
pub enum LedType {
|
||||
RGB,
|
||||
RGBW,
|
||||
}
|
||||
|
||||
impl Default for LedType {
|
||||
fn default() -> Self {
|
||||
LedType::RGB
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Serialize, Deserialize, Debug)]
|
||||
pub struct LedStripConfig {
|
||||
pub index: usize,
|
||||
@ -23,6 +35,8 @@ pub struct LedStripConfig {
|
||||
pub display_id: u32,
|
||||
pub start_pos: usize,
|
||||
pub len: usize,
|
||||
#[serde(default)]
|
||||
pub led_type: LedType,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Serialize, Deserialize, Debug)]
|
||||
@ -30,6 +44,12 @@ pub struct ColorCalibration {
|
||||
r: f32,
|
||||
g: f32,
|
||||
b: f32,
|
||||
#[serde(default = "default_w_value")]
|
||||
w: f32,
|
||||
}
|
||||
|
||||
fn default_w_value() -> f32 {
|
||||
1.0
|
||||
}
|
||||
|
||||
impl ColorCalibration {
|
||||
@ -40,6 +60,15 @@ impl ColorCalibration {
|
||||
(self.b * 255.0) as u8,
|
||||
]
|
||||
}
|
||||
|
||||
pub fn to_bytes_rgbw(&self) -> [u8; 4] {
|
||||
[
|
||||
(self.r * 255.0) as u8,
|
||||
(self.g * 255.0) as u8,
|
||||
(self.b * 255.0) as u8,
|
||||
(self.w * 255.0) as u8,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||
@ -122,6 +151,7 @@ impl LedStripConfigGroup {
|
||||
},
|
||||
start_pos: j + i * 4 * 30,
|
||||
len: 30,
|
||||
led_type: LedType::RGB,
|
||||
};
|
||||
configs.push(item);
|
||||
strips.push(item);
|
||||
@ -136,6 +166,7 @@ impl LedStripConfigGroup {
|
||||
r: 1.0,
|
||||
g: 1.0,
|
||||
b: 1.0,
|
||||
w: 1.0,
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
|
@ -5,7 +5,7 @@ use tokio::{sync::OnceCell, task::yield_now};
|
||||
|
||||
use crate::ambient_light::{config, LedStripConfigGroup};
|
||||
|
||||
use super::{Border, SamplePointMapper, ColorCalibration};
|
||||
use super::{Border, SamplePointMapper, ColorCalibration, LedType};
|
||||
|
||||
pub struct ConfigManager {
|
||||
config: Arc<RwLock<LedStripConfigGroup>>,
|
||||
@ -94,6 +94,33 @@ impl ConfigManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn patch_led_strip_type(
|
||||
&self,
|
||||
display_id: u32,
|
||||
border: Border,
|
||||
led_type: LedType,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut config = self.config.write().await;
|
||||
|
||||
for strip in config.strips.iter_mut() {
|
||||
if strip.display_id == display_id && strip.border == border {
|
||||
strip.led_type = led_type;
|
||||
}
|
||||
}
|
||||
|
||||
let cloned_config = config.clone();
|
||||
|
||||
drop(config);
|
||||
|
||||
self.update(&cloned_config).await?;
|
||||
|
||||
self.config_update_sender
|
||||
.send(cloned_config)
|
||||
.map_err(|e| anyhow::anyhow!("Failed to send config update: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn move_strip_part(
|
||||
&self,
|
||||
display_id: u32,
|
||||
|
@ -18,7 +18,7 @@ use crate::{
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
use super::{LedStripConfigGroup, SamplePointMapper};
|
||||
use super::{LedStripConfigGroup, SamplePointMapper, LedStripConfig, ColorCalibration, LedType};
|
||||
|
||||
pub struct LedColorsPublisher {
|
||||
sorted_colors_rx: Arc<RwLock<watch::Receiver<Vec<u8>>>>,
|
||||
@ -56,6 +56,8 @@ impl LedColorsPublisher {
|
||||
bound_scale_factor: f32,
|
||||
mappers: Vec<SamplePointMapper>,
|
||||
display_colors_tx: broadcast::Sender<(u32, Vec<u8>)>,
|
||||
strips: Vec<LedStripConfig>,
|
||||
color_calibration: ColorCalibration,
|
||||
) {
|
||||
let internal_tasks_version = self.inner_tasks_version.clone();
|
||||
let screenshot_manager = ScreenshotManager::global().await;
|
||||
@ -79,7 +81,7 @@ impl LedColorsPublisher {
|
||||
|
||||
let mappers = mappers.clone();
|
||||
|
||||
match Self::send_colors_by_display(colors, mappers).await {
|
||||
match Self::send_colors_by_display(colors, mappers, &strips, &color_calibration).await {
|
||||
Ok(_) => {
|
||||
// log::info!("sent colors: #{: >15}", display_id);
|
||||
}
|
||||
@ -209,9 +211,9 @@ impl LedColorsPublisher {
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_config_change(&self, configs: LedStripConfigGroup) {
|
||||
async fn handle_config_change(&self, original_configs: LedStripConfigGroup) {
|
||||
let inner_tasks_version = self.inner_tasks_version.clone();
|
||||
let configs = Self::get_colors_configs(&configs).await;
|
||||
let configs = Self::get_colors_configs(&original_configs).await;
|
||||
|
||||
if let Err(err) = configs {
|
||||
warn!("Failed to get configs: {}", err);
|
||||
@ -231,12 +233,22 @@ impl LedColorsPublisher {
|
||||
let display_id = sample_point_group.display_id;
|
||||
let sample_points = sample_point_group.points;
|
||||
let bound_scale_factor = sample_point_group.bound_scale_factor;
|
||||
|
||||
// Get strips for this display
|
||||
let display_strips: Vec<LedStripConfig> = original_configs.strips
|
||||
.iter()
|
||||
.filter(|strip| strip.display_id == display_id)
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
self.start_one_display_colors_fetcher(
|
||||
display_id,
|
||||
sample_points,
|
||||
bound_scale_factor,
|
||||
sample_point_group.mappers,
|
||||
display_colors_tx.clone(),
|
||||
display_strips,
|
||||
original_configs.color_calibration,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
@ -266,6 +278,8 @@ impl LedColorsPublisher {
|
||||
pub async fn send_colors_by_display(
|
||||
colors: Vec<LedColor>,
|
||||
mappers: Vec<SamplePointMapper>,
|
||||
strips: &[LedStripConfig],
|
||||
color_calibration: &ColorCalibration,
|
||||
) -> anyhow::Result<()> {
|
||||
// let color_len = colors.len();
|
||||
let display_led_offset = mappers
|
||||
@ -282,7 +296,7 @@ impl LedColorsPublisher {
|
||||
let udp_rpc = udp_rpc.as_ref().unwrap();
|
||||
|
||||
// let socket = UdpSocket::bind("0.0.0.0:0").await?;
|
||||
for group in mappers.clone() {
|
||||
for (group_index, group) in mappers.clone().iter().enumerate() {
|
||||
if (group.start.abs_diff(group.end)) > colors.len() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"get_sorted_colors: color_index out of range. color_index: {}, strip len: {}, colors.len(): {}",
|
||||
@ -293,7 +307,20 @@ impl LedColorsPublisher {
|
||||
}
|
||||
|
||||
let group_size = group.start.abs_diff(group.end);
|
||||
let mut buffer = Vec::<u8>::with_capacity(group_size * 3);
|
||||
|
||||
// Find the corresponding LED strip config to get LED type
|
||||
let led_type = if group_index < strips.len() {
|
||||
strips[group_index].led_type
|
||||
} else {
|
||||
LedType::RGB // fallback to RGB
|
||||
};
|
||||
|
||||
let bytes_per_led = match led_type {
|
||||
LedType::RGB => 3,
|
||||
LedType::RGBW => 4,
|
||||
};
|
||||
|
||||
let mut buffer = Vec::<u8>::with_capacity(group_size * bytes_per_led);
|
||||
|
||||
if group.end > group.start {
|
||||
// Prevent integer underflow by using saturating subtraction
|
||||
@ -310,12 +337,37 @@ impl LedColorsPublisher {
|
||||
|
||||
for i in start_index..end_index {
|
||||
if i < colors.len() {
|
||||
let bytes = colors[i].as_bytes();
|
||||
buffer.append(&mut bytes.to_vec());
|
||||
let bytes = match led_type {
|
||||
LedType::RGB => {
|
||||
let calibration_bytes = color_calibration.to_bytes();
|
||||
let color_bytes = colors[i].as_bytes();
|
||||
// Apply calibration to RGB values
|
||||
vec![
|
||||
((color_bytes[0] as f32 * calibration_bytes[0] as f32 / 255.0) as u8),
|
||||
((color_bytes[1] as f32 * calibration_bytes[1] as f32 / 255.0) as u8),
|
||||
((color_bytes[2] as f32 * calibration_bytes[2] as f32 / 255.0) as u8),
|
||||
]
|
||||
}
|
||||
LedType::RGBW => {
|
||||
let calibration_bytes = color_calibration.to_bytes_rgbw();
|
||||
let color_bytes = colors[i].as_bytes();
|
||||
// Apply calibration to RGB values and use calibrated W
|
||||
vec![
|
||||
((color_bytes[0] as f32 * calibration_bytes[0] as f32 / 255.0) as u8),
|
||||
((color_bytes[1] as f32 * calibration_bytes[1] as f32 / 255.0) as u8),
|
||||
((color_bytes[2] as f32 * calibration_bytes[2] as f32 / 255.0) as u8),
|
||||
calibration_bytes[3], // W channel
|
||||
]
|
||||
}
|
||||
};
|
||||
buffer.extend_from_slice(&bytes);
|
||||
} else {
|
||||
log::warn!("Index {} out of bounds for colors array of length {}", i, colors.len());
|
||||
// Add black color as fallback
|
||||
buffer.append(&mut vec![0, 0, 0]);
|
||||
match led_type {
|
||||
LedType::RGB => buffer.extend_from_slice(&[0, 0, 0]),
|
||||
LedType::RGBW => buffer.extend_from_slice(&[0, 0, 0, 0]),
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -333,12 +385,37 @@ impl LedColorsPublisher {
|
||||
|
||||
for i in (start_index..end_index).rev() {
|
||||
if i < colors.len() {
|
||||
let bytes = colors[i].as_bytes();
|
||||
buffer.append(&mut bytes.to_vec());
|
||||
let bytes = match led_type {
|
||||
LedType::RGB => {
|
||||
let calibration_bytes = color_calibration.to_bytes();
|
||||
let color_bytes = colors[i].as_bytes();
|
||||
// Apply calibration to RGB values
|
||||
vec![
|
||||
((color_bytes[0] as f32 * calibration_bytes[0] as f32 / 255.0) as u8),
|
||||
((color_bytes[1] as f32 * calibration_bytes[1] as f32 / 255.0) as u8),
|
||||
((color_bytes[2] as f32 * calibration_bytes[2] as f32 / 255.0) as u8),
|
||||
]
|
||||
}
|
||||
LedType::RGBW => {
|
||||
let calibration_bytes = color_calibration.to_bytes_rgbw();
|
||||
let color_bytes = colors[i].as_bytes();
|
||||
// Apply calibration to RGB values and use calibrated W
|
||||
vec![
|
||||
((color_bytes[0] as f32 * calibration_bytes[0] as f32 / 255.0) as u8),
|
||||
((color_bytes[1] as f32 * calibration_bytes[1] as f32 / 255.0) as u8),
|
||||
((color_bytes[2] as f32 * calibration_bytes[2] as f32 / 255.0) as u8),
|
||||
calibration_bytes[3], // W channel
|
||||
]
|
||||
}
|
||||
};
|
||||
buffer.extend_from_slice(&bytes);
|
||||
} else {
|
||||
log::warn!("Index {} out of bounds for colors array of length {}", i, colors.len());
|
||||
// Add black color as fallback
|
||||
buffer.append(&mut vec![0, 0, 0]);
|
||||
match led_type {
|
||||
LedType::RGB => buffer.extend_from_slice(&[0, 0, 0]),
|
||||
LedType::RGBW => buffer.extend_from_slice(&[0, 0, 0, 0]),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -43,6 +43,10 @@ impl LedColor {
|
||||
pub fn as_bytes (&self) -> [u8; 3] {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn as_bytes_rgbw(&self, w: u8) -> [u8; 4] {
|
||||
[self.0[0], self.0[1], self.0[2], w]
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for LedColor {
|
||||
|
@ -10,7 +10,7 @@ mod screenshot_manager;
|
||||
mod screen_stream;
|
||||
mod volume;
|
||||
|
||||
use ambient_light::{Border, ColorCalibration, LedStripConfig, LedStripConfigGroup};
|
||||
use ambient_light::{Border, ColorCalibration, LedStripConfig, LedStripConfigGroup, LedType};
|
||||
use display::{DisplayManager, DisplayState};
|
||||
use display_info::DisplayInfo;
|
||||
use paris::{error, info, warn};
|
||||
@ -138,6 +138,25 @@ async fn patch_led_strip_len(display_id: u32, border: Border, delta_len: i8) ->
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn patch_led_strip_type(display_id: u32, border: Border, led_type: LedType) -> Result<(), String> {
|
||||
info!(
|
||||
"patch_led_strip_type: {} {:?} {:?}",
|
||||
display_id, border, led_type
|
||||
);
|
||||
let config_manager = ambient_light::ConfigManager::global().await;
|
||||
config_manager
|
||||
.patch_led_strip_type(display_id, border, led_type)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("can not patch led strip type: {}", e);
|
||||
e.to_string()
|
||||
})?;
|
||||
|
||||
info!("patch_led_strip_type: ok");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn send_colors(offset: u16, buffer: Vec<u8>) -> Result<(), String> {
|
||||
ambient_light::LedColorsPublisher::send_colors(offset, buffer)
|
||||
@ -383,6 +402,7 @@ async fn main() {
|
||||
get_led_strips_sample_points,
|
||||
get_one_edge_colors,
|
||||
patch_led_strip_len,
|
||||
patch_led_strip_type,
|
||||
send_colors,
|
||||
move_strip_part,
|
||||
reverse_led_strip_part,
|
||||
|
Reference in New Issue
Block a user