feat: 灯光跟随屏幕内容。

feat: 灯光跟随屏幕内容。
This commit is contained in:
Ivan Li 2022-11-24 23:21:46 +08:00
parent 1b10c6bea9
commit 56edd8ac77
7 changed files with 139 additions and 84 deletions

View File

@ -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(())
} }
} }

View File

@ -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");

View File

@ -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] }
} }

View File

@ -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![]),

View File

@ -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,

View File

@ -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 }

View File

@ -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">