feat: 支持 MQTT 上报灯条颜色数据
This commit is contained in:
@@ -3,21 +3,19 @@
|
||||
windows_subsystem = "windows"
|
||||
)]
|
||||
|
||||
mod led_color;
|
||||
mod screen;
|
||||
mod screen_color_picker;
|
||||
mod picker;
|
||||
mod rpc;
|
||||
|
||||
use led_color::LedColor;
|
||||
use paris::*;
|
||||
use screen_color_picker::ScreenColorPicker;
|
||||
use picker::led_color::LedColor;
|
||||
use picker::manager::Picker;
|
||||
use std::{
|
||||
sync::{Arc, Mutex},
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
#[tauri::command]
|
||||
fn refresh_displays() {
|
||||
match ScreenColorPicker::global().refresh_displays() {
|
||||
async fn refresh_displays() {
|
||||
match Picker::global().refresh_displays().await {
|
||||
Ok(_) => {}
|
||||
Err(error) => {
|
||||
error!("{}", error)
|
||||
@@ -26,67 +24,39 @@ fn refresh_displays() {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn take_snapshot() -> Arc<Mutex<Vec<String>>> {
|
||||
async fn take_snapshot() -> Vec<String> {
|
||||
let start = Instant::now();
|
||||
let manager = Picker::global();
|
||||
|
||||
let screenshot_futures = match ScreenColorPicker::global().take_screenshots_for_all() {
|
||||
Ok(bitmaps) => {
|
||||
info!("bitmaps len: {}", bitmaps.len());
|
||||
match ScreenColorPicker::global().screens.lock() {
|
||||
Ok(screens) => Some(Vec::from_iter(screens.iter().enumerate().map(
|
||||
|(index, screen)| {
|
||||
bitmap_to_webp_base64(screen.width, screen.height, bitmaps[index].to_vec())
|
||||
},
|
||||
))),
|
||||
Err(error) => {
|
||||
error!("can not lock screens. {}", error);
|
||||
None
|
||||
}
|
||||
match manager.take_screenshots_for_all().await {
|
||||
Ok(screenshots) => {
|
||||
info!("screenshots len: {}", screenshots.len());
|
||||
let mut futures = Vec::new();
|
||||
for screenshot in screenshots {
|
||||
let future = screenshot.to_webp_base64().await;
|
||||
futures.push(future);
|
||||
}
|
||||
futures
|
||||
}
|
||||
Err(error) => {
|
||||
error!("can not take screenshots for all. {}", error);
|
||||
None
|
||||
vec![]
|
||||
}
|
||||
};
|
||||
match screenshot_futures {
|
||||
Some(futures) => {
|
||||
let mut handles = Vec::with_capacity(futures.len());
|
||||
|
||||
for fut in futures {
|
||||
handles.push(tokio::spawn(fut));
|
||||
}
|
||||
|
||||
let mut results = Vec::with_capacity(handles.len());
|
||||
for handle in handles {
|
||||
results.push(handle.await.unwrap());
|
||||
}
|
||||
println!("运行耗时: {:?}", start.elapsed());
|
||||
Arc::new(Mutex::new(results))
|
||||
}
|
||||
None => Arc::new(Mutex::new(Vec::new())),
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn get_led_strip_colors() -> Result<Vec<LedColor>, String> {
|
||||
let screens = ScreenColorPicker::global()
|
||||
.screens
|
||||
.lock()
|
||||
.map_err(|error| {
|
||||
error!("can not lock ScreenColorPick.screens. {}", error);
|
||||
"failed to lock."
|
||||
})?;
|
||||
let mut colors = Vec::new();
|
||||
for screen in screens.iter() {
|
||||
let result = screen.get_top_colors();
|
||||
if let Ok(result) = result {
|
||||
colors.extend_from_slice(&result);
|
||||
} else if let Err(result) = result {
|
||||
return Err(format!("can not get led strip colors. {}", result));
|
||||
let colors = Picker::global().get_led_strip_colors().await;
|
||||
match colors {
|
||||
Ok(colors) => {
|
||||
rpc::manager::Manager::global()
|
||||
.publish_led_colors(&colors)
|
||||
.await;
|
||||
Ok(colors)
|
||||
}
|
||||
Err(error) => Err(format!("{}", error)),
|
||||
}
|
||||
Ok(colors)
|
||||
}
|
||||
|
||||
async fn bitmap_to_webp_base64(width: usize, height: usize, bitmap: Vec<u8>) -> String {
|
||||
@@ -111,7 +81,9 @@ async fn bitmap_to_webp_base64(width: usize, height: usize, bitmap: Vec<u8>) ->
|
||||
return base64::encode(&*webp_memory);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
rpc::manager::Manager::global();
|
||||
tauri::Builder::default()
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
take_snapshot,
|
||||
|
82
src-tauri/src/picker/manager.rs
Normal file
82
src-tauri/src/picker/manager.rs
Normal file
@@ -0,0 +1,82 @@
|
||||
use once_cell::sync::OnceCell;
|
||||
use paris::*;
|
||||
use scrap::{Capturer, Display};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::picker::screen::Screen;
|
||||
|
||||
use super::{
|
||||
led_color::LedColor,
|
||||
screenshot::{Screenshot},
|
||||
};
|
||||
|
||||
pub struct Picker {
|
||||
pub screens: Arc<Mutex<Vec<Screen>>>,
|
||||
pub screenshots: Arc<Mutex<Vec<Screenshot>>>,
|
||||
}
|
||||
|
||||
impl Picker {
|
||||
pub fn global() -> &'static Picker {
|
||||
static SCREEN_COLOR_PICKER: OnceCell<Picker> = OnceCell::new();
|
||||
|
||||
SCREEN_COLOR_PICKER.get_or_init(|| Picker {
|
||||
screens: Arc::new(Mutex::new(vec![])),
|
||||
screenshots: Arc::new(Mutex::new(vec![])),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn refresh_displays(&self) -> anyhow::Result<()> {
|
||||
let displays = Display::all()
|
||||
.map_err(|error| anyhow::anyhow!("Can not get all of displays. {}", error))?;
|
||||
let mut screens = self.screens.lock().await;
|
||||
let mut screenshots = self.screenshots.lock().await;
|
||||
screens.clear();
|
||||
info!("number of displays: {}", displays.len());
|
||||
for display in displays {
|
||||
let height = display.height();
|
||||
let width = display.width();
|
||||
match Capturer::new(display) {
|
||||
Ok(capturer) => screens.push(Screen::new(capturer, width, height)),
|
||||
Err(error) => screens.push(Screen::new_failed(
|
||||
anyhow::anyhow!("{}", error),
|
||||
width,
|
||||
height,
|
||||
)),
|
||||
};
|
||||
screenshots.push(Screenshot::new(width, height));
|
||||
}
|
||||
|
||||
screens.reverse();
|
||||
screenshots.reverse();
|
||||
screenshots[0].set_number_of_leds(22, 0);
|
||||
screenshots[1].set_number_of_leds(38, 0);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn take_screenshots_for_all(&self) -> anyhow::Result<Vec<Screenshot>> {
|
||||
let mut screens = self.screens.lock().await;
|
||||
let mut screenshots = self.screenshots.lock().await;
|
||||
for (index, screen) in screens.iter_mut().enumerate() {
|
||||
let bitmap = screen.take().map_err(|error| {
|
||||
anyhow::anyhow!("take screenshot for display failed. {}", error)
|
||||
})?;
|
||||
screenshots[index].set_bitmap(bitmap).await
|
||||
}
|
||||
Ok(screenshots.to_vec())
|
||||
}
|
||||
|
||||
pub async fn get_led_strip_colors(&self) -> anyhow::Result<Vec<LedColor>> {
|
||||
let screenshots = self.screenshots.lock().await;
|
||||
let mut colors = Vec::new();
|
||||
for screenshot in screenshots.iter() {
|
||||
let result = screenshot
|
||||
.get_top_colors()
|
||||
.await
|
||||
.map_err(|error| anyhow::anyhow!("get top colors failed. {}", error))?;
|
||||
colors.extend_from_slice(&result);
|
||||
}
|
||||
Ok(colors)
|
||||
}
|
||||
}
|
4
src-tauri/src/picker/mod.rs
Normal file
4
src-tauri/src/picker/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
pub mod led_color;
|
||||
pub mod screen;
|
||||
pub mod manager;
|
||||
pub mod screenshot;
|
43
src-tauri/src/picker/screen.rs
Normal file
43
src-tauri/src/picker/screen.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
|
||||
use scrap::Capturer;
|
||||
|
||||
pub struct Screen {
|
||||
capturer: Option<Capturer>,
|
||||
init_error: Option<anyhow::Error>,
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
}
|
||||
|
||||
impl Screen {
|
||||
pub fn new(capturer: Capturer, width: usize, height: usize) -> Self {
|
||||
Self {
|
||||
capturer: Some(capturer),
|
||||
init_error: None,
|
||||
width,
|
||||
height,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_failed(init_error: anyhow::Error, width: usize, height: usize) -> Self {
|
||||
Self {
|
||||
capturer: None,
|
||||
init_error: Some(init_error),
|
||||
width,
|
||||
height,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn take(&mut self) -> anyhow::Result<Vec<u8>> {
|
||||
match self.capturer.as_mut() {
|
||||
Some(capturer) => {
|
||||
let buffer = capturer
|
||||
.frame()
|
||||
.map_err(|error| anyhow::anyhow!("failed to frame of display. {}", error))?;
|
||||
anyhow::Ok(buffer.to_vec())
|
||||
}
|
||||
None => anyhow::bail!("Do not initialized"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for Screen {}
|
@@ -1,81 +1,53 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use anyhow::Ok;
|
||||
use scrap::Capturer;
|
||||
use super::led_color::LedColor;
|
||||
|
||||
use crate::led_color::LedColor;
|
||||
|
||||
pub struct Screen {
|
||||
#[derive(Clone)]
|
||||
pub struct Screenshot {
|
||||
bitmap: Arc<Mutex<Option<Vec<u8>>>>,
|
||||
capturer: Option<Capturer>,
|
||||
init_error: Option<anyhow::Error>,
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
pub led_number_of_x: usize,
|
||||
pub led_number_of_y: usize,
|
||||
width: usize,
|
||||
height: usize,
|
||||
led_number_of_x: usize,
|
||||
led_number_of_y: usize,
|
||||
}
|
||||
|
||||
impl Screen {
|
||||
pub fn new(capturer: Capturer, width: usize, height: usize) -> Self {
|
||||
impl Screenshot {
|
||||
pub fn new(width: usize, height: usize) -> Self {
|
||||
Self {
|
||||
bitmap: Arc::new(Mutex::new(None)),
|
||||
capturer: Some(capturer),
|
||||
init_error: None,
|
||||
width,
|
||||
height,
|
||||
led_number_of_x: 0,
|
||||
led_number_of_y: 0,
|
||||
width,
|
||||
height,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_failed(init_error: anyhow::Error, width: usize, height: usize) -> Self {
|
||||
Self {
|
||||
bitmap: Arc::new(Mutex::new(None)),
|
||||
capturer: None,
|
||||
init_error: Some(init_error),
|
||||
width,
|
||||
height,
|
||||
led_number_of_x: 0,
|
||||
led_number_of_y: 0,
|
||||
}
|
||||
pub fn get_size(&self) -> (usize, usize) {
|
||||
(self.width, self.height)
|
||||
}
|
||||
|
||||
pub fn set_number_of_leds(&mut self, led_number_of_x: usize, led_number_of_y: usize) -> &Self {
|
||||
pub fn get_number_of_leds(&self) -> (usize, usize) {
|
||||
(self.led_number_of_x, self.led_number_of_y)
|
||||
}
|
||||
|
||||
pub fn set_number_of_leds(&mut self, led_number_of_x: usize, led_number_of_y: usize) {
|
||||
self.led_number_of_x = led_number_of_x;
|
||||
self.led_number_of_y = led_number_of_y;
|
||||
self
|
||||
}
|
||||
pub async fn get_top_colors(&self) -> anyhow::Result<Vec<LedColor>> {
|
||||
self.get_x_colors(XPosition::Top).await
|
||||
}
|
||||
pub async fn get_bottom_colors(&self) -> anyhow::Result<Vec<LedColor>> {
|
||||
self.get_x_colors(XPosition::Bottom).await
|
||||
}
|
||||
|
||||
pub fn take(&mut self) -> anyhow::Result<Vec<u8>> {
|
||||
match self.capturer.as_mut() {
|
||||
Some(capturer) => {
|
||||
let buffer = capturer
|
||||
.frame()
|
||||
.map_err(|error| anyhow::anyhow!("failed to frame of display. {}", error))?;
|
||||
|
||||
self.bitmap = Arc::new(Mutex::new(Some(buffer.to_vec())));
|
||||
anyhow::Ok(buffer.to_vec())
|
||||
}
|
||||
None => anyhow::bail!("Do not initialized"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_top_colors(&self) -> anyhow::Result<Vec<LedColor>> {
|
||||
self.get_x_colors(XPosition::Top)
|
||||
}
|
||||
pub fn get_bottom_colors(&self) -> anyhow::Result<Vec<LedColor>> {
|
||||
self.get_x_colors(XPosition::Bottom)
|
||||
}
|
||||
|
||||
fn get_x_colors(&self, position: XPosition) -> anyhow::Result<Vec<LedColor>> {
|
||||
async fn get_x_colors(&self, position: XPosition) -> anyhow::Result<Vec<LedColor>> {
|
||||
if self.led_number_of_x == 0 {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
||||
let bitmap = self
|
||||
.bitmap
|
||||
.lock()
|
||||
.map_err(|error| anyhow::anyhow!("can not lock Screen#bitmap. {}", error))?;
|
||||
let bitmap = self.bitmap.lock().await;
|
||||
match bitmap.as_ref() {
|
||||
Some(bitmap) => {
|
||||
let cell_size_x = self.width / self.led_number_of_x;
|
||||
@@ -90,8 +62,6 @@ impl Screen {
|
||||
let stride = bitmap.len() / self.height;
|
||||
|
||||
for pos in 0..self.led_number_of_x {
|
||||
let mut y_range = y_range.to_owned();
|
||||
|
||||
let mut r = 0u32;
|
||||
let mut g = 0u32;
|
||||
let mut b = 0u32;
|
||||
@@ -114,9 +84,40 @@ impl Screen {
|
||||
None => Ok(vec![]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for Screen {}
|
||||
pub async fn set_bitmap(&mut self, bitmap: Vec<u8>) {
|
||||
let mut self_bitmap = self.bitmap.lock().await;
|
||||
*self_bitmap = Some(bitmap);
|
||||
}
|
||||
|
||||
pub async fn to_webp_base64(&self) -> String {
|
||||
let bitmap = self.bitmap.lock().await;
|
||||
match bitmap.to_owned() {
|
||||
Some(bitmap) => {
|
||||
let mut bitflipped = Vec::with_capacity(self.width * self.height * 3);
|
||||
let stride = bitmap.len() / self.height;
|
||||
|
||||
for y in 0..self.height {
|
||||
for x in 0..self.width {
|
||||
let i = stride * y + 4 * x;
|
||||
bitflipped.extend_from_slice(&[bitmap[i + 2], bitmap[i + 1], bitmap[i]]);
|
||||
}
|
||||
}
|
||||
|
||||
let webp_memory = webp::Encoder::from_rgb(
|
||||
bitflipped.as_slice(),
|
||||
self.width as u32,
|
||||
self.height as u32,
|
||||
)
|
||||
.encode(100.0);
|
||||
return base64::encode(&*webp_memory);
|
||||
},
|
||||
None => {
|
||||
"".to_owned()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum XPosition {
|
||||
Top,
|
41
src-tauri/src/rpc/manager.rs
Normal file
41
src-tauri/src/rpc/manager.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use crate::picker::led_color::LedColor;
|
||||
|
||||
use super::mqtt::MqttConnection;
|
||||
use once_cell::sync::OnceCell;
|
||||
|
||||
pub struct Manager {
|
||||
mqtt: MqttConnection,
|
||||
}
|
||||
|
||||
impl Manager {
|
||||
pub fn global() -> &'static Self {
|
||||
static RPC_MANAGER: OnceCell<Manager> = OnceCell::new();
|
||||
|
||||
RPC_MANAGER.get_or_init(|| Manager::new())
|
||||
}
|
||||
|
||||
pub fn new() -> Self {
|
||||
let mut mqtt = MqttConnection::new();
|
||||
mqtt.initialize();
|
||||
Self { mqtt }
|
||||
}
|
||||
|
||||
pub async fn publish_led_colors(&self, colors: &Vec<LedColor>) -> anyhow::Result<()> {
|
||||
let payload = colors
|
||||
.iter()
|
||||
.map(|c| c.get_rgb().clone())
|
||||
.flatten()
|
||||
.collect::<Vec<u8>>();
|
||||
|
||||
self.mqtt
|
||||
.client
|
||||
.publish(
|
||||
"screen-bg-light/desktop/colors",
|
||||
rumqttc::QoS::AtMostOnce,
|
||||
false,
|
||||
payload,
|
||||
)
|
||||
.await
|
||||
.map_err(|error| anyhow::anyhow!("mqtt publish failed. {}", error))
|
||||
}
|
||||
}
|
2
src-tauri/src/rpc/mod.rs
Normal file
2
src-tauri/src/rpc/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod manager;
|
||||
pub mod mqtt;
|
66
src-tauri/src/rpc/mqtt.rs
Normal file
66
src-tauri/src/rpc/mqtt.rs
Normal file
@@ -0,0 +1,66 @@
|
||||
use rumqttc::{AsyncClient, MqttOptions, QoS};
|
||||
use std::{time::Duration};
|
||||
use time::{format_description, OffsetDateTime};
|
||||
use tokio::task;
|
||||
use tracing::warn;
|
||||
|
||||
pub struct MqttConnection {
|
||||
pub client: AsyncClient,
|
||||
}
|
||||
|
||||
impl MqttConnection {
|
||||
pub fn new() -> Self {
|
||||
let mut options = MqttOptions::new("rumqtt-async", "192.168.31.11", 1883);
|
||||
options.set_keep_alive(Duration::from_secs(5));
|
||||
|
||||
let (mut client, mut eventloop) = AsyncClient::new(options, 10);
|
||||
task::spawn(async move {
|
||||
while let Ok(notification) = eventloop.poll().await {
|
||||
println!("Received = {:?}", notification);
|
||||
}
|
||||
});
|
||||
Self { client }
|
||||
}
|
||||
|
||||
pub fn initialize(&mut self) {
|
||||
self.subscribe_board();
|
||||
self.broadcast_desktop_online();
|
||||
}
|
||||
|
||||
fn subscribe_board(&self) {
|
||||
self.client
|
||||
.subscribe("screen-bg-light/board/#", QoS::AtMostOnce);
|
||||
}
|
||||
|
||||
fn broadcast_desktop_online(&mut self) {
|
||||
let client = self.client.to_owned();
|
||||
task::spawn(async move {
|
||||
loop {
|
||||
match OffsetDateTime::now_utc()
|
||||
.format(&format_description::well_known::Iso8601::DEFAULT)
|
||||
{
|
||||
Ok(now_str) => {
|
||||
match client
|
||||
.publish(
|
||||
"screen-bg-light/desktop/last-online-at",
|
||||
QoS::AtLeastOnce,
|
||||
false,
|
||||
now_str.as_bytes(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {}
|
||||
Err(error) => {
|
||||
warn!("can not publish last online time. {}", error)
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
warn!("can not get time for now. {}", error);
|
||||
}
|
||||
}
|
||||
tokio::time::sleep(Duration::from_millis(1000)).await;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@@ -1,69 +0,0 @@
|
||||
use once_cell::sync::OnceCell;
|
||||
use paris::*;
|
||||
use scrap::{Capturer, Display};
|
||||
use tracing::field::display;
|
||||
use std::{
|
||||
borrow::BorrowMut,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use crate::screen::Screen;
|
||||
|
||||
pub struct ScreenColorPicker {
|
||||
pub screens: Arc<Mutex<Vec<Screen>>>,
|
||||
}
|
||||
|
||||
impl ScreenColorPicker {
|
||||
pub fn global() -> &'static ScreenColorPicker {
|
||||
static SCREEN_COLOR_PICKER: OnceCell<ScreenColorPicker> = OnceCell::new();
|
||||
|
||||
SCREEN_COLOR_PICKER.get_or_init(|| ScreenColorPicker {
|
||||
screens: Arc::new(Mutex::new(Vec::<Screen>::new())),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn refresh_displays(&self) -> anyhow::Result<()> {
|
||||
let displays = Display::all()
|
||||
.map_err(|error| anyhow::anyhow!("Can not get all of displays. {}", error))?;
|
||||
let mut screens = self
|
||||
.screens
|
||||
.lock()
|
||||
.map_err(|error| anyhow::anyhow!("lock screens failed. {}", error))?;
|
||||
screens.clear();
|
||||
info!("number of displays: {}", displays.len());
|
||||
for display in displays {
|
||||
let height = display.height();
|
||||
let width = display.width();
|
||||
match Capturer::new(display) {
|
||||
Ok(capturer) => screens.push(Screen::new(capturer, width, height)),
|
||||
Err(error) => screens.push(Screen::new_failed(
|
||||
anyhow::anyhow!("{}", error),
|
||||
width,
|
||||
height,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
screens.reverse();
|
||||
screens[0].set_number_of_leds(22, 0);
|
||||
screens[1].set_number_of_leds(38, 0);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn take_screenshots_for_all(&self) -> anyhow::Result<Vec<Vec<u8>>> {
|
||||
let mut screens = self
|
||||
.screens
|
||||
.lock()
|
||||
.map_err(|error| anyhow::anyhow!("lock screens failed. {}", error))?;
|
||||
let mut screenshots = Vec::<Vec<u8>>::new();
|
||||
for screen in screens.iter_mut() {
|
||||
let screenshot = screen.take().map_err(|error| {
|
||||
anyhow::anyhow!("take screenshot for display failed. {}", error)
|
||||
})?;
|
||||
screenshots.push(screenshot);
|
||||
}
|
||||
|
||||
anyhow::Ok(screenshots)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user