feat: 灯光跟随屏幕内容。
feat: 灯光跟随屏幕内容。
This commit is contained in:
parent
1b10c6bea9
commit
56edd8ac77
@ -1,51 +1,125 @@
|
|||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use paris::info;
|
use paris::info;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{sync::Arc, time::Duration};
|
use std::{sync::Arc, time::Duration};
|
||||||
|
use tauri::async_runtime::RwLock;
|
||||||
use tokio::{sync::Mutex, time::sleep};
|
use tokio::{sync::Mutex, time::sleep};
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
use crate::{picker::led_color::LedColor, rpc};
|
use crate::{
|
||||||
|
picker::{led_color::LedColor, manager::Picker},
|
||||||
|
rpc,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub enum AmbientLightMode {
|
||||||
|
None,
|
||||||
|
Follow,
|
||||||
|
Flowing,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct CoreManager {
|
pub struct CoreManager {
|
||||||
flowing_light_mode: Arc<Mutex<bool>>,
|
ambient_light_mode: Arc<RwLock<AmbientLightMode>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CoreManager {
|
impl CoreManager {
|
||||||
pub fn global() -> &'static CoreManager {
|
pub fn global() -> &'static CoreManager {
|
||||||
static CORE_MANAGER: OnceCell<CoreManager> = OnceCell::new();
|
static CORE_MANAGER: OnceCell<CoreManager> = OnceCell::new();
|
||||||
|
|
||||||
CORE_MANAGER.get_or_init(|| CoreManager {
|
let core = CORE_MANAGER.get_or_init(|| CoreManager {
|
||||||
flowing_light_mode: Arc::new(Mutex::new(false)),
|
ambient_light_mode: Arc::new(RwLock::new(AmbientLightMode::None)),
|
||||||
})
|
});
|
||||||
|
|
||||||
|
tokio::spawn(async {
|
||||||
|
loop {
|
||||||
|
core.play_flowing_light().await;
|
||||||
|
match core.play_follow().await {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(error) => {
|
||||||
|
warn!("Can not following displays. {}", error);
|
||||||
|
sleep(Duration::from_millis(1000)).await;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
sleep(Duration::from_millis(10)).await;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
core
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_ambient_light(&self, target_mode: AmbientLightMode) {
|
||||||
|
let mut mode = self.ambient_light_mode.write().await;
|
||||||
|
*mode = target_mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn play_flowing_light(&self) {
|
pub async fn play_flowing_light(&self) {
|
||||||
*self.flowing_light_mode.lock().await = true;
|
|
||||||
|
|
||||||
let mut hue = 0f64;
|
let mut hue = 0f64;
|
||||||
let step_length = 2.0;
|
let step_length = 2.0;
|
||||||
while self.flowing_light_mode.lock().await.to_owned() {
|
loop {
|
||||||
let mut colors = Vec::<LedColor>::new();
|
let lock = self.ambient_light_mode.read().await;
|
||||||
for i in 0..60 {
|
if let AmbientLightMode::Flowing = *lock {
|
||||||
let color = LedColor::from_hsv((hue + i as f64 * step_length) % 360.0, 1.0, 0.5);
|
let mut colors = Vec::<LedColor>::new();
|
||||||
colors.push(color);
|
for i in 0..60 {
|
||||||
}
|
let color =
|
||||||
hue = (hue + 1.0) % 360.0;
|
LedColor::from_hsv((hue + i as f64 * step_length) % 360.0, 1.0, 0.5);
|
||||||
match rpc::manager::Manager::global()
|
colors.push(color);
|
||||||
.publish_led_colors(&colors)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(error) => {
|
|
||||||
warn!("publish led colors failed. {}", error);
|
|
||||||
}
|
}
|
||||||
|
hue = (hue + 1.0) % 360.0;
|
||||||
|
match rpc::manager::Manager::global()
|
||||||
|
.publish_led_colors(&colors)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(error) => {
|
||||||
|
warn!("publish led colors failed. {}", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
sleep(Duration::from_millis(10)).await;
|
sleep(Duration::from_millis(50)).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn stop_flowing_light(&self) {
|
pub async fn play_follow(&self) -> anyhow::Result<()> {
|
||||||
let mut mode = self.flowing_light_mode.lock().await;
|
{
|
||||||
*mode = false;
|
info!("Following A");
|
||||||
|
let lock = self.ambient_light_mode.read().await;
|
||||||
|
if let AmbientLightMode::Follow = *lock {
|
||||||
|
Picker::global().refresh_displays().await?;
|
||||||
|
info!("Following B");
|
||||||
|
} else {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
info!("Following");
|
||||||
|
let lock = self.ambient_light_mode.read().await;
|
||||||
|
if let AmbientLightMode::Follow = *lock {
|
||||||
|
match Picker::global().take_screenshots_for_all().await {
|
||||||
|
Ok(_) => {
|
||||||
|
let colors = Picker::global().get_led_strip_colors().await;
|
||||||
|
match colors {
|
||||||
|
Ok(colors) => {
|
||||||
|
let colors = colors.into_iter().rev().collect();
|
||||||
|
rpc::manager::Manager::global()
|
||||||
|
.publish_led_colors(&colors)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
warn!("get strip colors failed. {}", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
warn!("take screenshots failed. {}", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,10 +7,11 @@ mod core;
|
|||||||
mod picker;
|
mod picker;
|
||||||
mod rpc;
|
mod rpc;
|
||||||
|
|
||||||
|
use crate::core::CoreManager;
|
||||||
|
use crate::core::AmbientLightMode;
|
||||||
use paris::*;
|
use paris::*;
|
||||||
use picker::led_color::LedColor;
|
use picker::led_color::LedColor;
|
||||||
use picker::manager::Picker;
|
use picker::manager::Picker;
|
||||||
use crate::core::CoreManager;
|
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@ -60,12 +61,10 @@ async fn get_led_strip_colors() -> Result<Vec<LedColor>, String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn play_flowing_light() {
|
async fn play_mode(target_mode: AmbientLightMode) {
|
||||||
CoreManager::global().play_flowing_light().await;
|
info!("target mode: {:?}", target_mode);
|
||||||
}
|
|
||||||
#[tauri::command]
|
CoreManager::global().set_ambient_light(target_mode).await;
|
||||||
async fn stop_flowing_light() {
|
|
||||||
CoreManager::global().stop_flowing_light().await;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
@ -76,8 +75,7 @@ async fn main() {
|
|||||||
take_snapshot,
|
take_snapshot,
|
||||||
refresh_displays,
|
refresh_displays,
|
||||||
get_led_strip_colors,
|
get_led_strip_colors,
|
||||||
play_flowing_light,
|
play_mode,
|
||||||
stop_flowing_light,
|
|
||||||
])
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
use color_space::{Hsv, Rgb};
|
use color_space::{Hsv, Rgb};
|
||||||
use paris::info;
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
@ -18,28 +17,6 @@ impl LedColor {
|
|||||||
|
|
||||||
pub fn from_hsv(h: f64, s: f64, v: f64) -> Self {
|
pub fn from_hsv(h: f64, s: f64, v: f64) -> Self {
|
||||||
let rgb = Rgb::from(Hsv::new(h, s, v));
|
let rgb = Rgb::from(Hsv::new(h, s, v));
|
||||||
// let h = h % 360;
|
|
||||||
// let rgb_max = v * 255.0;
|
|
||||||
// let rgb_min = rgb_max * (1.0 - s as f32);
|
|
||||||
// let i = h / 60;
|
|
||||||
// let diff = h % 60;
|
|
||||||
|
|
||||||
// let rgb_adj = (rgb_max - rgb_min) * diff as f32 / 60.0;
|
|
||||||
|
|
||||||
// let bits: [u8; 3] = if i == 0 {
|
|
||||||
// [rgb_max as u8, (rgb_min + rgb_adj) as u8, rgb_min as u8]
|
|
||||||
// } else if i == 1 {
|
|
||||||
// [(rgb_max - rgb_adj) as u8, rgb_max as u8, rgb_min as u8]
|
|
||||||
// } else if i == 2 {
|
|
||||||
// [rgb_min as u8, rgb_max as u8, (rgb_min + rgb_adj) as u8]
|
|
||||||
// } else if i == 3 {
|
|
||||||
// [rgb_min as u8, (rgb_max - rgb_adj) as u8, rgb_max as u8]
|
|
||||||
// } else if i == 4 {
|
|
||||||
// [(rgb_min + rgb_adj) as u8, rgb_min as u8, rgb_max as u8]
|
|
||||||
// } else {
|
|
||||||
// [rgb_max as u8, rgb_min as u8, (rgb_max - rgb_adj) as u8]
|
|
||||||
// };
|
|
||||||
|
|
||||||
Self { bits: [rgb.r as u8, rgb.g as u8, rgb.b as u8] }
|
Self { bits: [rgb.r as u8, rgb.g as u8, rgb.b as u8] }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,12 +81,11 @@ impl Screenshot {
|
|||||||
b / cell_size as f64,
|
b / cell_size as f64,
|
||||||
);
|
);
|
||||||
let hsv = Hsv::from(rgb);
|
let hsv = Hsv::from(rgb);
|
||||||
info!("HSV: {:?}", [hsv.h, hsv.s, hsv.v]);
|
// info!("HSV: {:?}", [hsv.h, hsv.s, hsv.v]);
|
||||||
let color = LedColor::from_hsv(hsv.h, hsv.s * 10.0, hsv.v / 2.0);
|
let color = LedColor::from_hsv(hsv.h, hsv.s, hsv.v);
|
||||||
info!("color: {:?}", color.get_rgb());
|
// info!("color: {:?}", color.get_rgb());
|
||||||
colors.push(color);
|
colors.push(color);
|
||||||
}
|
}
|
||||||
colors.reverse();
|
|
||||||
return Ok(colors);
|
return Ok(colors);
|
||||||
}
|
}
|
||||||
None => Ok(vec![]),
|
None => Ok(vec![]),
|
||||||
|
@ -2,7 +2,6 @@ use crate::picker::led_color::LedColor;
|
|||||||
|
|
||||||
use super::mqtt::MqttConnection;
|
use super::mqtt::MqttConnection;
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use paris::info;
|
|
||||||
|
|
||||||
pub struct Manager {
|
pub struct Manager {
|
||||||
mqtt: MqttConnection,
|
mqtt: MqttConnection,
|
||||||
|
@ -13,10 +13,10 @@ impl MqttConnection {
|
|||||||
let mut options = MqttOptions::new("rumqtt-async", "192.168.31.11", 1883);
|
let mut options = MqttOptions::new("rumqtt-async", "192.168.31.11", 1883);
|
||||||
options.set_keep_alive(Duration::from_secs(5));
|
options.set_keep_alive(Duration::from_secs(5));
|
||||||
|
|
||||||
let (mut client, mut eventloop) = AsyncClient::new(options, 10);
|
let (client, mut eventloop) = AsyncClient::new(options, 10);
|
||||||
task::spawn(async move {
|
task::spawn(async move {
|
||||||
while let Ok(notification) = eventloop.poll().await {
|
while let Ok(notification) = eventloop.poll().await {
|
||||||
println!("Received = {:?}", notification);
|
// println!("Received = {:?}", notification);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Self { client }
|
Self { client }
|
||||||
|
48
src/App.tsx
48
src/App.tsx
@ -4,17 +4,12 @@ import { invoke } from '@tauri-apps/api/tauri';
|
|||||||
import './App.css';
|
import './App.css';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
|
||||||
|
type Mode = 'Flowing' | 'Follow' | null;
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [greetMsg, setGreetMsg] = useState('');
|
|
||||||
const [name, setName] = useState('');
|
|
||||||
const [screenshots, setScreenshots] = useState<string[]>([]);
|
const [screenshots, setScreenshots] = useState<string[]>([]);
|
||||||
const [ledStripColors, setLedStripColors] = useState<string[]>([]);
|
const [ledStripColors, setLedStripColors] = useState<string[]>([]);
|
||||||
const [flowingLightMode, setFlowingLightMode] = useState<boolean>(false);
|
const [currentMode, setCurrentMode] = useState<Mode>(null);
|
||||||
|
|
||||||
async function greet() {
|
|
||||||
// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
|
|
||||||
setGreetMsg(await invoke('greet', { name }));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function takeSnapshot() {
|
async function takeSnapshot() {
|
||||||
const base64TextList: string[] = await invoke('take_snapshot');
|
const base64TextList: string[] = await invoke('take_snapshot');
|
||||||
@ -30,16 +25,20 @@ function App() {
|
|||||||
setLedStripColors(await invoke('get_led_strip_colors'));
|
setLedStripColors(await invoke('get_led_strip_colors'));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const switchFlowingLightMode = useCallback(async () => {
|
const switchCurrentMode = useCallback(
|
||||||
console.log('A', flowingLightMode);
|
async (targetMode: Mode) => {
|
||||||
if (flowingLightMode) {
|
console.log(targetMode, currentMode, currentMode === targetMode);
|
||||||
await invoke('stop_flowing_light');
|
if (currentMode === targetMode) {
|
||||||
} else {
|
await invoke('play_mode', { targetMode: 'None' });
|
||||||
invoke('play_flowing_light');
|
setCurrentMode(null);
|
||||||
}
|
} else {
|
||||||
console.log('B', flowingLightMode);
|
await invoke('play_mode', { targetMode });
|
||||||
setFlowingLightMode(!flowingLightMode);
|
setCurrentMode(targetMode);
|
||||||
}, [flowingLightMode]);
|
}
|
||||||
|
console.log(targetMode, currentMode, currentMode === targetMode);
|
||||||
|
},
|
||||||
|
[currentMode, setCurrentMode],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@ -81,13 +80,22 @@ function App() {
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className={clsx('bg-black', 'bg-opacity-20', {
|
className={clsx('bg-black', 'bg-opacity-20', {
|
||||||
'bg-gradient-to-r from-purple-500 to-blue-500': flowingLightMode,
|
'bg-gradient-to-r from-purple-500 to-blue-500': currentMode === 'Flowing',
|
||||||
})}
|
})}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => switchFlowingLightMode()}
|
onClick={() => switchCurrentMode('Flowing')}
|
||||||
>
|
>
|
||||||
Flowing Light
|
Flowing Light
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
className={clsx('bg-black', 'bg-opacity-20', {
|
||||||
|
'bg-gradient-to-r from-purple-500 to-blue-500': currentMode === 'Follow',
|
||||||
|
})}
|
||||||
|
type="button"
|
||||||
|
onClick={() => switchCurrentMode('Follow')}
|
||||||
|
>
|
||||||
|
Follow
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-5 justify-center">
|
<div className="flex gap-5 justify-center">
|
||||||
|
Loading…
Reference in New Issue
Block a user