sampad/src/main.rs

139 lines
4.2 KiB
Rust

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<String, Client>,
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::<Vec<u8>>();
data[0..48].try_into().unwrap()
}
}
fn main() -> Result<(), Box<dyn Error>> {
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(())
}