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::(5); let (handler_commands_sender, handler_commands_receiver) = flume::bounded::(5); let events_sender = broadcast::Sender::::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(()) }