use std::path::Path; use std::sync::Arc; use std::thread; use color_eyre::eyre::{ContextCompat, WrapErr}; use color_eyre::Result; use log::info; 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, 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::get_default_host_id; 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); info!("Discovering devices…"); let available_devices = LoupedeckDevice::discover()?; let available_device = available_devices.first().wrap_err("No device connected.")?; info!("Found {} device(s).", available_devices.len()); let (commands_sender, commands_receiver) = flume::bounded::(5); let events_sender = broadcast::Sender::::new(5); commands_sender .send(HandlerCommand::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 { info!("Initializing MQTT client…"); start_mqtt_client(mqtt_config, &handler_hosts_config, commands_sender.clone(), events_sender.subscribe()).await; } info!("Initializing handler processes…"); handler_runner::start( get_default_host_id(), &config_directory.join("handlers"), handler_hosts_config, commands_sender.clone(), events_sender.subscribe(), ) .await?; info!("Connecting to the device…"); let device = available_device.connect().wrap_err("Connecting to the device failed.")?; info!("Connected."); device.set_brightness(0.5); device.vibrate(VibrationPattern::RiseFall); let io_worker_context = IoWorkerContext::create(config_directory, Arc::clone(&config), device, commands_sender.clone(), events_sender); let io_worker_thread = thread::Builder::new() .name("deckster IO worker".to_owned()) .spawn(move || { do_io_work(io_worker_context, commands_receiver); }) .wrap_err("Could not spawn the worker thread")?; info!("Ready."); io_worker_thread.join().unwrap(); Ok(()) }