deckster/src/coordinator/mod.rs

136 lines
4.5 KiB
Rust

use std::path::Path;
use std::sync::Arc;
use std::thread;
use color_eyre::eyre::{ContextCompat, WrapErr};
use color_eyre::Result;
use nanoid::nanoid;
use tokio::sync::broadcast;
use deckster_shared::handler_communication::{HandlerCommand, HandlerEvent};
use deckster_shared::path::{KeyPath, KnobPath};
use loupedeck_serial::commands::VibrationPattern;
use loupedeck_serial::device::LoupedeckDevice;
use crate::coordinator::io_worker::{do_io_work, CoordinatorCommand, IoWorkerContext};
use crate::coordinator::mqtt::start_mqtt_client;
use crate::handler_runner;
use crate::handler_runner::KeyOrKnobHandlerConfig;
use crate::model::coordinator_config::Config;
use crate::model::mqtt::HandlerHostsConfig;
mod graphics;
mod io_worker;
mod mqtt;
pub mod state;
pub async fn start(config_directory: &Path, config: Config) -> Result<()> {
let config = Arc::new(config);
log::info!("Discovering devices…");
let available_devices = LoupedeckDevice::discover()?;
let available_device = available_devices.first().wrap_err("No device connected.")?;
log::info!("Found {} device(s).", available_devices.len());
let (coordinator_commands_sender, coordinator_commands_receiver) = flume::bounded::<CoordinatorCommand>(5);
let (handler_commands_sender, handler_commands_receiver) = flume::bounded::<HandlerCommand>(5);
let events_sender = broadcast::Sender::<HandlerEvent>::new(5);
coordinator_commands_sender
.send(CoordinatorCommand::SetActivePages {
knob_page_id: config.initial.knob_page.clone(),
key_page_id: config.initial.key_page.clone(),
})
.unwrap();
let handler_hosts_config = HandlerHostsConfig {
run_id: nanoid!().into_boxed_str(),
keys: config
.key_pages_by_id
.iter()
.flat_map(|(page_id, p)| {
p.keys.iter().map(|(position, k)| {
(
KeyPath {
page_id: page_id.clone(),
position: *position,
},
KeyOrKnobHandlerConfig {
host_id: k.host.clone(),
name: k.handler.clone(),
config: Arc::clone(&k.config),
},
)
})
})
.collect(),
knobs: config
.knob_pages_by_id
.iter()
.flat_map(|(page_id, p)| {
p.knobs.iter().filter_map(|(position, k)| {
if k.handler.is_empty() {
return None;
}
Some((
KnobPath {
page_id: page_id.clone(),
position,
},
KeyOrKnobHandlerConfig {
host_id: k.host.clone(),
name: k.handler.clone(),
config: Arc::clone(&k.config),
},
))
})
})
.collect(),
};
if let Some(mqtt_config) = &config.mqtt {
log::info!("Initializing MQTT client…");
start_mqtt_client(
mqtt_config,
&handler_hosts_config,
coordinator_commands_sender.clone(),
handler_commands_sender.clone(),
events_sender.subscribe(),
)
.await;
}
log::info!("Initializing handler processes…");
handler_runner::start(
String::default().into_boxed_str(),
&config_directory.join("handlers"),
&config.handlers,
handler_hosts_config,
handler_commands_sender.clone(),
events_sender.subscribe(),
)
.await?;
log::info!("Connecting to the device…");
let device = available_device.connect().wrap_err("Connecting to the device failed.")?;
log::info!("Connected.");
device.set_brightness(config.initial.screen_brightness);
device.vibrate(VibrationPattern::RiseFall);
let io_worker_context = IoWorkerContext::create(config_directory, Arc::clone(&config), device, coordinator_commands_sender, events_sender);
let io_worker_thread = thread::Builder::new()
.name("deckster IO worker".to_owned())
.spawn(move || {
do_io_work(io_worker_context, &coordinator_commands_receiver, &handler_commands_receiver);
})
.wrap_err("Could not spawn the worker thread")?;
log::info!("Ready.");
io_worker_thread.join().unwrap();
Ok(())
}