pub mod engine;
pub mod midi_dmx_store;
pub mod ola_client;
pub mod universe;
pub mod watcher;
use crate::config;
use crate::dmx::ola_client::OlaClientFactory;
use engine::Engine;
#[cfg(not(test))]
use ola::client::StreamingClientConfig;
#[cfg(not(test))]
use std::thread;
#[cfg(not(test))]
use std::time::Duration;
use std::{error::Error, path::Path, sync::Arc};
use tracing::info;
#[derive(Debug, thiserror::Error)]
pub enum DmxError {
#[error("DMX engine initialization error: {0}")]
Init(String),
#[error("DMX playback error: {0}")]
Playback(String),
#[error("DMX validation error: {0}")]
Validation(String),
#[error(transparent)]
Other(Box<dyn Error + Send + Sync>),
}
impl From<Box<dyn Error + Send + Sync>> for DmxError {
fn from(e: Box<dyn Error + Send + Sync>) -> Self {
DmxError::Other(e)
}
}
pub fn create_engine(
config: Option<&config::Dmx>,
base_path: Option<&Path>,
) -> Result<Option<Arc<Engine>>, Box<dyn Error>> {
create_engine_inner(config, base_path, true)
}
fn create_engine_inner(
config: Option<&config::Dmx>,
base_path: Option<&Path>,
allow_null_client: bool,
) -> Result<Option<Arc<Engine>>, Box<dyn Error>> {
let config = match config {
Some(config) => config,
None => return Ok(None),
};
let lighting_config = config.lighting();
#[cfg(test)]
let ola_client = {
let _ = allow_null_client; OlaClientFactory::create_mock_client_unconditional()
};
#[cfg(not(test))]
let ola_client = if config.null_client() {
info!("null_client enabled, skipping OLA connection");
Box::new(ola_client::NullOlaClient) as Box<dyn ola_client::OlaClient>
} else {
let ola_client_config = StreamingClientConfig {
server_port: config.ola_port(),
auto_start: false,
};
let mut last_err: Option<Box<dyn Error>> = None;
let mut found: Option<Box<dyn ola_client::OlaClient>> = None;
for attempt in 0..10 {
if attempt > 0 {
thread::sleep(Duration::from_secs(5));
}
match OlaClientFactory::create_real_client(ola_client_config.clone()) {
Ok(client) => {
found = Some(client);
break;
}
Err(e) => {
last_err = Some(e);
}
}
}
match (found, last_err) {
(Some(client), _) => client,
(None, Some(e)) => {
if allow_null_client {
info!("OLA not available, using null DMX client");
Box::new(ola_client::NullOlaClient) as Box<dyn ola_client::OlaClient>
} else {
return Err(e);
}
}
(None, None) => unreachable!(),
}
};
let engine = Arc::new(Engine::new(config, lighting_config, base_path, ola_client)?);
info!(
lighting = lighting_config.is_some(),
"DMX engine initialized"
);
Engine::start_persistent_effects_loop(engine.clone());
Ok(Some(engine))
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn create_engine_none_config_returns_none() {
let result = create_engine(None, None).unwrap();
assert!(result.is_none());
}
#[test]
fn create_engine_with_config_returns_some() {
let dmx_config = config::Dmx::new(
None,
None,
Some(9090),
vec![config::Universe::new(1, "test".to_string())],
None,
);
let result = create_engine(Some(&dmx_config), None).unwrap();
assert!(result.is_some());
}
}