mod config; mod device; use config::{Action, Config}; use hidapi::HidApi; use rumqttc::{Client, MqttOptions, QoS}; use std::collections::HashMap; use std::error::Error; use std::path::Path; use std::thread; use tracing::{event, Level}; struct State<'a> { mqtt_servers: HashMap, conf: &'a Config, key_state: [bool; 16], } impl<'a> State<'a> { fn init(c: &'a Config) -> Self { let mut s = State { mqtt_servers: HashMap::new(), conf: &c, key_state: [false; 16], }; for (id, srv) in c.mqtt_servers.iter() { let mut opts = MqttOptions::new( srv.client_id .as_ref() .unwrap_or(&"samspad-client".to_owned()), &srv.host, srv.port.unwrap_or(1883), ); // Only set credentials if we have both user and pass specified if let (Some(u), Some(p)) = (&srv.user, &srv.pass) { opts.set_credentials(u, p); } let (cli, mut conn) = Client::new(opts, 10); thread::spawn(move || { for notification in conn.iter() { match notification { Ok(n) => println!("Notification = {:?}", n), Err(e) => { println!("MQTT error: {}", e); } } } }); s.mqtt_servers.insert(id.to_string(), cli); } s } fn handle_button(&mut self, ev: &device::ButtonEvent) { let layer = &self.conf.layers["default"]; match ev { device::ButtonEvent::KeyDown(key_id) => { self.key_state[*key_id as usize] = true; let mapping = if let Some(mapping) = layer.mappings.get(&format!("key_{}", key_id)) { mapping } else { &layer.default_mapping }; self.execute_mapping(mapping); } device::ButtonEvent::KeyUp(key_id) => { self.key_state[*key_id as usize] = false; } } } fn execute_action(&mut self, action: &Action) { match action { Action::MQTTPub { topic, payload } => { let mut topic = topic.to_owned(); let srv = &self.conf.mqtt_servers["default"]; if let Some(prefix) = &srv.topic_prefix { topic.insert_str(0, &prefix); } let cli = &mut self.mqtt_servers.get_mut("default").unwrap(); cli.publish(topic, QoS::AtLeastOnce, false, payload.as_bytes()) .unwrap(); } Action::Print(s) => println!("{}", s), _ => event!(Level::WARN, %action, "Ignoring unimplemented action"), } } fn execute_mapping(&mut self, m: &config::Mapping) { match m { config::Mapping::Trigger(t) => self.execute_action(t), _ => {} } } fn get_led_data(&self) -> [u8; 48] { let data = self .key_state .iter() .flat_map(|s| if *s { [255, 255, 255] } else { [0, 0, 0] }) .collect::>(); data[0..48].try_into().unwrap() } } fn main() -> Result<(), Box> { tracing_subscriber::fmt::init(); event!(Level::INFO, "Starting..."); let conf = Config::from_file(Path::new("./config.dhall"))?; let mut state = State::init(&conf); let hid = HidApi::new().unwrap(); // TODO: handle multiple devices let device = device::get_devices(&hid, true) .get(0) .expect("No device found") .open_device(&hid) .expect("Couldn't open device"); let pad = device::ButtonPad::new(&device); for ev in pad { if let Ok(ev) = ev { event!(Level::INFO, ?ev, "Got event"); state.handle_button(&ev); let mut data = state.get_led_data().to_vec(); data.insert(0, 0x0); data.insert(0, 0x0); event!(Level::INFO, ?data, "Sending LED data"); device.write(data.as_slice())?; } } Ok(()) }