From 121aeadbd8de083fde80a8a6524650b3b60d9bc0 Mon Sep 17 00:00:00 2001 From: 4yn <4yn@users.noreply.github.com> Date: Sun, 24 Jul 2022 23:11:29 +0800 Subject: [PATCH] air leds --- public/global.css | 45 ++++++++-- src-interception/src/bindings.rs | 2 + src-slider_io/src/config.rs | 2 + src-slider_io/src/lighting/config.rs | 16 ++++ src-slider_io/src/lighting/lighting.rs | 94 ++++++++++++++++---- src-slider_io/src/lighting/umgr_websocket.rs | 6 ++ src-slider_io/src/state.rs | 23 +++++ src/App.svelte | 60 ++++++++++--- src/Preview.svelte | 37 +++++++- 9 files changed, 246 insertions(+), 39 deletions(-) diff --git a/public/global.css b/public/global.css index 26587eb..a25e9a9 100644 --- a/public/global.css +++ b/public/global.css @@ -183,19 +183,52 @@ button.primary { .air { height: 2rem; + position: relative; + + border-radius: 0.5rem 0.5rem 0 0; + overflow: clip; + + background: #000; +} + +.air-btn, .air-led { + height: 2rem; + position: absolute; + top: 0; + left: 0; + width: 100%; + + display: flex; + align-items: stretch; + justify-content: flex-start; +} + +.air-led { + flex-flow: row nowrap; +} + +.air-led-left, .air-led-right { display: flex; flex-flow: column-reverse nowrap; align-items: stretch; justify-content: flex-start; - - border-radius: 0.5rem 0.5rem 0 0; - overflow: clip; + flex: 1; } -.air-data { +.air-led-data { flex: 1 0; } -.air-data-0 { - background: #000; + +.air-led-space { + flex: 14; +} + +.air-btn { + background: #0008; + flex-flow: column-reverse nowrap; +} + +.air-data { + flex: 1 0; } .air-data-1 { background: #0aa; diff --git a/src-interception/src/bindings.rs b/src-interception/src/bindings.rs index 9bcc038..4b86e87 100644 --- a/src-interception/src/bindings.rs +++ b/src-interception/src/bindings.rs @@ -1,3 +1,5 @@ +#![ allow( dead_code, unused_imports, non_upper_case_globals ) ] + /* automatically generated by rust-bindgen 0.54.1 */ pub const INTERCEPTION_MAX_KEYBOARD: u32 = 10; diff --git a/src-slider_io/src/config.rs b/src-slider_io/src/config.rs index cf9a5cc..a707a82 100644 --- a/src-slider_io/src/config.rs +++ b/src-slider_io/src/config.rs @@ -43,6 +43,8 @@ impl Config { "ledFaster": false, "ledColorActive": "#ff00ff", "ledColorInactive": "#ffff00", + "ledColorAirActive": "#0086ed", + "ledColorAirInactive": "#000000", "ledSensitivity": 20, "ledWebsocketUrl": "localhost:3001", "ledSerialPort": "COM5" diff --git a/src-slider_io/src/lighting/config.rs b/src-slider_io/src/lighting/config.rs index db19608..d93302c 100644 --- a/src-slider_io/src/lighting/config.rs +++ b/src-slider_io/src/lighting/config.rs @@ -13,6 +13,8 @@ pub enum ReactiveLayout { pub struct ColorScheme { pub active: [u8; 3], pub inactive: [u8; 3], + pub air_active: [u8; 3], + pub air_inactive: [u8; 3], } impl ColorScheme { @@ -28,6 +30,16 @@ impl ColorScheme { u8::from_str_radix(&v["ledColorInactive"].as_str()?[3..5], 16).ok()?, u8::from_str_radix(&v["ledColorInactive"].as_str()?[5..7], 16).ok()?, ], + air_active: [ + u8::from_str_radix(&v["ledColorAirActive"].as_str()?[1..3], 16).ok()?, + u8::from_str_radix(&v["ledColorAirActive"].as_str()?[3..5], 16).ok()?, + u8::from_str_radix(&v["ledColorAirActive"].as_str()?[5..7], 16).ok()?, + ], + air_inactive: [ + u8::from_str_radix(&v["ledColorAirInactive"].as_str()?[1..3], 16).ok()?, + u8::from_str_radix(&v["ledColorAirInactive"].as_str()?[3..5], 16).ok()?, + u8::from_str_radix(&v["ledColorAirInactive"].as_str()?[5..7], 16).ok()?, + ], }) } @@ -35,6 +47,8 @@ impl ColorScheme { Self { active: [255, 0, 255], inactive: [255, 255, 0], + air_active: [0, 134, 237], + air_inactive: [0, 0, 0], } } @@ -43,6 +57,8 @@ impl ColorScheme { .or(Some(Self { active: [255, 0, 255], inactive: [255, 255, 0], + air_active: [0, 134, 237], + air_inactive: [0, 0, 0], })) .unwrap() } diff --git a/src-slider_io/src/lighting/lighting.rs b/src-slider_io/src/lighting/lighting.rs index 2c87f14..8726793 100644 --- a/src-slider_io/src/lighting/lighting.rs +++ b/src-slider_io/src/lighting/lighting.rs @@ -1,6 +1,6 @@ use async_trait::async_trait; use log::{error, info}; -use palette::{FromColor, Hsv, Srgb}; +use palette::{encoding::Srgb as SrgbEncoding, rgb::Rgb, FromColor, Hsv, Srgb}; use serialport::{ClearBuffer, SerialPort}; use std::{ ops::DerefMut, @@ -15,6 +15,22 @@ use crate::{ use super::config::{LightsMode, ReactiveLayout}; +fn get_rainbow(phase: f64, desaturate: bool) -> Rgb { + let phase = ((phase % 1.0) + 1.0) % 1.0; + let color = Srgb::from_color(Hsv::new( + phase * 360.0, + match desaturate { + false => 1.0, + true => 0.2, + _ => unreachable!(), + }, + 1.0, + )) + .into_format::(); + + return color; +} + pub struct LightsJob { state: SliderState, mode: LightsMode, @@ -75,6 +91,16 @@ impl LightsJob { }, ); } + + for idx in 0..3 { + lights.paint_air( + idx, + match flat_input[32 + idx * 2] || flat_input[33 + idx * 2] { + true => &color.air_active, + false => &color.air_inactive, + }, + ) + } } ReactiveLayout::Six => { let banks: Vec = [0..6, 6..10, 10..16, 16..22, 22..26, 26..32] @@ -106,6 +132,16 @@ impl LightsJob { ) } } + + for idx in 0..3 { + lights.paint_air( + idx, + match flat_input[32 + idx * 2] || flat_input[33 + idx * 2] { + true => &color.air_active, + false => &color.air_inactive, + }, + ) + } } ReactiveLayout::Voltex => { lights.ground.fill(0); @@ -207,23 +243,32 @@ impl LightsJob { .elapsed() .div_duration_f64(Duration::from_secs(4)) % 1.0; + for idx in 0..31 { - let slice_theta = (&theta + (idx as f64) / 32.0) % 1.0; - let color = Srgb::from_color(Hsv::new( - slice_theta * 360.0, - match idx % 2 { - 0 => match banks[idx / 2] { - true => 0.2, - false => 1.0, - }, - 1 => 1.0, - _ => unreachable!(), - }, - 1.0, - )) - .into_format::(); + let slice_theta = theta + (idx as f64) / 32.0; + let color = get_rainbow(slice_theta, ((idx % 2) == 0) && banks[idx / 2]); lights.paint(idx, &[color.red, color.green, color.blue]); } + + // left + for idx in 0..3 { + let slice_theta = theta - ((idx + 1) as f64) / 32.0; + let color = get_rainbow( + slice_theta, + flat_input[32 + idx * 2] || flat_input[33 + idx * 2], + ); + lights.paint_air_left(idx, &[color.red, color.green, color.blue]); + } + + // right + for idx in 0..3 { + let slice_theta = theta + (idx as f64) / 32.0; + let color = get_rainbow( + slice_theta, + flat_input[32 + idx * 2] || flat_input[33 + idx * 2], + ); + lights.paint_air_right(idx, &[color.red, color.green, color.blue]); + } } } } @@ -233,11 +278,26 @@ impl LightsJob { .elapsed() .div_duration_f64(Duration::from_secs(4)) % 1.0; + for idx in 0..31 { - let slice_theta = (&theta + (idx as f64) / 32.0) % 1.0; - let color = Srgb::from_color(Hsv::new(slice_theta * 360.0, 1.0, 1.0)).into_format::(); + let slice_theta = theta + (idx as f64) / 32.0; + let color = get_rainbow(slice_theta, false); lights.paint(idx, &[color.red, color.green, color.blue]); } + + // left + for idx in 0..3 { + let slice_theta = theta - ((idx + 1) as f64) / 32.0; + let color = get_rainbow(slice_theta, false); + lights.paint_air_left(idx, &[color.red, color.green, color.blue]); + } + + // right + for idx in 0..3 { + let slice_theta = theta + (idx as f64) / 32.0; + let color = get_rainbow(slice_theta, false); + lights.paint_air_right(idx, &[color.red, color.green, color.blue]); + } } LightsMode::Serial { .. } => { // https://github.com/jmontineri/OpeNITHM/blob/89e9a43f7484e8949cd31bbff79c32f21ea3ec1d/Firmware/OpeNITHM/SerialProcessor.h diff --git a/src-slider_io/src/lighting/umgr_websocket.rs b/src-slider_io/src/lighting/umgr_websocket.rs index 7177e37..4f9e7fc 100644 --- a/src-slider_io/src/lighting/umgr_websocket.rs +++ b/src-slider_io/src/lighting/umgr_websocket.rs @@ -108,6 +108,12 @@ async fn handle_umgr_leds(ws_stream: WebSocketStream, state: SliderSta ); } + for i in 0..3 { + let pos = 94 + i * 3; + lights_handle + .paint_air(2 - i, &[payload[pos], payload[pos + 1], payload[pos + 2]]); + } + if latest_lights.elapsed() > delay { lights_handle.dirty = true; latest_lights = Instant::now(); diff --git a/src-slider_io/src/state.rs b/src-slider_io/src/state.rs index 13d3989..3d78ce6 100644 --- a/src-slider_io/src/state.rs +++ b/src-slider_io/src/state.rs @@ -61,6 +61,10 @@ pub struct SliderLights { /// right. Alternates between 16 touch pad pixels and 15 divider pixels. pub ground: [u8; 3 * 31], + // RGB light values for left and right air sensors, bottom to top + pub air_left: [u8; 3 * 3], + pub air_right: [u8; 3 * 3], + /// Internal dirty flag used to indicate that new lighting data is available. pub dirty: bool, @@ -73,6 +77,8 @@ impl SliderLights { pub fn new() -> Self { Self { ground: [0; 3 * 31], + air_left: [0; 3 * 3], + air_right: [0; 3 * 3], dirty: false, start: Instant::now(), } @@ -83,8 +89,23 @@ impl SliderLights { self.ground[3 * idx..3 * (idx + 1)].copy_from_slice(color); } + pub fn paint_air(&mut self, idx: usize, color: &[u8; 3]) { + self.air_left[3 * idx..3 * (idx + 1)].copy_from_slice(color); + self.air_right[3 * idx..3 * (idx + 1)].copy_from_slice(color); + } + + pub fn paint_air_left(&mut self, idx: usize, color: &[u8; 3]) { + self.air_left[3 * idx..3 * (idx + 1)].copy_from_slice(color); + } + + pub fn paint_air_right(&mut self, idx: usize, color: &[u8; 3]) { + self.air_right[3 * idx..3 * (idx + 1)].copy_from_slice(color); + } + pub fn reset(&mut self) { self.ground.fill(0); + self.air_left.fill(0); + self.air_right.fill(0); self.dirty = true; } } @@ -122,6 +143,8 @@ impl SliderState { { let lights_handle = self.lights.lock(); buf.extend(lights_handle.ground); + buf.extend(lights_handle.air_left); + buf.extend(lights_handle.air_right); }; buf diff --git a/src/App.svelte b/src/App.svelte index 3d0a1a1..33dafae 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -21,6 +21,8 @@ let ledFaster = false; let ledColorActive = "#ff00ff"; let ledColorInactive = "#ffff00"; + let ledColorAirActive = "#0086ed"; + let ledColorAirInactive = "#000000"; let ledSensitivity = 20; let ledWebsocketUrl = "http://localhost:3001"; let ledUmgrWebsocketPort = 7124; @@ -77,6 +79,8 @@ ledFaster = payload.ledFaster || false; ledColorActive = payload.ledColorActive || "#ff00ff"; ledColorInactive = payload.ledColorInactive || "#ffff00"; + ledColorAirActive = payload.ledColorAirActive || "#0086ed"; + ledColorAirInactive = payload.ledColorAirInactive || "#000000"; ledSensitivity = payload.ledSensitivity || 20; ledWebsocketUrl = payload.ledWebsocketUrl || "http://localhost:3001"; ledUmgrWebsocketPort = payload.ledUmgrWebsocketPort || 7124; @@ -133,6 +137,8 @@ ledFaster, ledColorActive, ledColorInactive, + ledColorAirActive, + ledColorAirInactive, ledSensitivity, ledWebsocketUrl, ledUmgrWebsocketPort, @@ -470,23 +476,53 @@ {/if} {#if ledMode.slice(0, 8) === "reactive" && ["16", "8", "6", "4"].includes(ledMode.slice(9))}
-
Active Color
+
Slider Color
- + + + + + + + +
-
Base Color
+
Air Color
- + + + + + + + +
{/if} diff --git a/src/Preview.svelte b/src/Preview.svelte index 758f4e2..efec89d 100644 --- a/src/Preview.svelte +++ b/src/Preview.svelte @@ -8,9 +8,11 @@ let ledDatas = Array(16).fill("#ff0"); let ledDividerDatas = Array(15).fill("#ff0"); + let airLedLeftDatas = Array(3).fill(0); + let airLedRightDatas = Array(3).fill(0); $: { - if (data.length === 134) { + if (data.length === 152) { // console.log(data); for (let i = 0; i < 16; i++) { topDatas[i] = data[i * 2 + 1]; @@ -33,15 +35,42 @@ ledDividerDatas[(i - 1) / 2] = rgbstr; } } + for (let i = 0; i < 3; i++) { + let rgbstr = `rgb(${data[134 + i * 3]}, ${data[135 + i * 3]}, ${ + data[136 + i * 3] + })`; + airLedLeftDatas[i] = rgbstr; + } + for (let i = 0; i < 3; i++) { + let rgbstr = `rgb(${data[143 + i * 3]}, ${data[144 + i * 3]}, ${ + data[145 + i * 3] + })`; + airLedRightDatas[i] = rgbstr; + } } }
- {#each airDatas as airData, idx (idx)} -
- {/each} +
+
+ {#each airLedLeftDatas as airLedData, idx (idx)} +
+ {/each} +
+
+
+ {#each airLedRightDatas as airLedData, idx (idx)} +
+ {/each} +
+
+
+ {#each airDatas as airData, idx (idx)} +
+ {/each} +