Initial prototype
This commit is contained in:
commit
8de3aa6cce
|
@ -0,0 +1,3 @@
|
||||||
|
/target
|
||||||
|
.direnv
|
||||||
|
config.dhall
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,13 @@
|
||||||
|
[package]
|
||||||
|
name = "sampad"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
hidapi = "1"
|
||||||
|
rumqttc = "0.12"
|
||||||
|
bitvec = "1"
|
||||||
|
serde = "1"
|
||||||
|
serde_dhall = "0.11"
|
||||||
|
log = "*"
|
||||||
|
env_logger = "*"
|
|
@ -0,0 +1,16 @@
|
||||||
|
{ mqtt_servers.default = { host = "1.2.3.4", user = Some "username", pass = Some
|
||||||
|
"password" }
|
||||||
|
, layers.default
|
||||||
|
=
|
||||||
|
{ default_mapping = Mapping.Print "no mapping!"
|
||||||
|
, mappings =
|
||||||
|
let sometopic = "homeassistant/switch/something/set"
|
||||||
|
|
||||||
|
in { key_0 =
|
||||||
|
Mapping.Trigger (Action.MQTTPub { topic = sometopic, payload = "ON" })
|
||||||
|
, key_1 =
|
||||||
|
Mapping.Trigger
|
||||||
|
(Action.MQTTPub { topic = sometopic, payload = "OFF" })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,176 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"devshell": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1650900878,
|
||||||
|
"narHash": "sha256-qhNncMBSa9STnhiLfELEQpYC1L4GrYHNIzyCZ/pilsI=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "devshell",
|
||||||
|
"rev": "d97df53b5ddaa1cfbea7cddbd207eb2634304733",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "devshell",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-utils": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1642700792,
|
||||||
|
"narHash": "sha256-XqHrk7hFb+zBvRg6Ghl+AZDq03ov6OshJLiSWOoX5es=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "846b2ae0fc4cc943637d3d1def4454213e203cba",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-utils_2": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1637014545,
|
||||||
|
"narHash": "sha256-26IZAc5yzlD9FlDT54io1oqG/bBoyka+FJk5guaX4x4=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "bba5dcc8e0b20ab664967ad83d24d64cb64ec4f4",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"naersk": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": "nixpkgs_2"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1651574473,
|
||||||
|
"narHash": "sha256-wQhFORvRjo8LB2hTmETmv6cbyKGDPbfWqvZ/0chnDE4=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "naersk",
|
||||||
|
"rev": "f21309b38e1da0d61b881b6b6d41b81c1aed4e1d",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "naersk",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1643381941,
|
||||||
|
"narHash": "sha256-pHTwvnN4tTsEKkWlXQ8JMY423epos8wUOhthpwJjtpc=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "5efc8ca954272c4376ac929f4c5ffefcc20551d5",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixpkgs-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs_2": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1652059086,
|
||||||
|
"narHash": "sha256-CjHSbr6LSFkN4YBdTB6+8ZQmSqhsbiXqAeQ9hQJ/gBI=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "934e076a441e318897aa17540f6cf7caadc69028",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"id": "nixpkgs",
|
||||||
|
"type": "indirect"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs_3": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1652059086,
|
||||||
|
"narHash": "sha256-CjHSbr6LSFkN4YBdTB6+8ZQmSqhsbiXqAeQ9hQJ/gBI=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "934e076a441e318897aa17540f6cf7caadc69028",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"id": "nixpkgs",
|
||||||
|
"type": "indirect"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs_4": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1637453606,
|
||||||
|
"narHash": "sha256-Gy6cwUswft9xqsjWxFYEnx/63/qzaFUwatcbV5GF/GQ=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "8afc4e543663ca0a6a4f496262cd05233737e732",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixpkgs-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"devshell": "devshell",
|
||||||
|
"naersk": "naersk",
|
||||||
|
"nixpkgs": "nixpkgs_3",
|
||||||
|
"rust-overlay": "rust-overlay",
|
||||||
|
"utils": "utils"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rust-overlay": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils_2",
|
||||||
|
"nixpkgs": "nixpkgs_4"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1652064227,
|
||||||
|
"narHash": "sha256-ZpIfELJNVzcxa6+YUr1KfbpTkHh02FASdlHaCC5/Q6w=",
|
||||||
|
"owner": "oxalica",
|
||||||
|
"repo": "rust-overlay",
|
||||||
|
"rev": "31db726b18ad2620fead6e7cd31b6e57d1cd061b",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "oxalica",
|
||||||
|
"repo": "rust-overlay",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"utils": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1649676176,
|
||||||
|
"narHash": "sha256-OWKJratjt2RW151VUlJPRALb7OU2S5s+f0vLj4o1bHM=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "a4b154ebbdc88c8498a5c7b01589addc9e9cb678",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
{
|
||||||
|
description = "Sampad server";
|
||||||
|
inputs = {
|
||||||
|
utils.url = "github:numtide/flake-utils";
|
||||||
|
devshell.url = "github:numtide/devshell";
|
||||||
|
naersk.url = "github:nix-community/naersk";
|
||||||
|
rust-overlay.url = "github:oxalica/rust-overlay";
|
||||||
|
};
|
||||||
|
outputs = { self, nixpkgs, utils, naersk, devshell, rust-overlay }:
|
||||||
|
utils.lib.eachDefaultSystem (system:
|
||||||
|
let
|
||||||
|
pkgs = import nixpkgs { inherit system; };
|
||||||
|
naersk-lib = pkgs.callPackage naersk { };
|
||||||
|
in
|
||||||
|
{
|
||||||
|
defaultPackage = naersk-lib.buildPackage ./.;
|
||||||
|
|
||||||
|
defaultApp = utils.lib.mkApp {
|
||||||
|
drv = self.defaultPackage."${system}";
|
||||||
|
};
|
||||||
|
devShell =
|
||||||
|
let pkgs = import nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
|
||||||
|
overlays = [ devshell.overlay (import rust-overlay)];
|
||||||
|
};
|
||||||
|
in
|
||||||
|
pkgs.devshell.mkShell {
|
||||||
|
packages = with pkgs; [ dhall (rust-bin.stable.latest.default.override {
|
||||||
|
extensions = [ "rls" ];
|
||||||
|
})];
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
# Samw's Another Macro PAD
|
||||||
|
<img src="https://img.shields.io/badge/PRJ-13-blue" />
|
||||||
|
<img src="https://img.shields.io/badge/Status-Prototype-orange" />
|
||||||
|
A flexible macropad server. Receives macropad state reports over USB, maps them to MQTT
|
||||||
|
commands.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph LR;
|
||||||
|
pad<-- USB HID --->sampad
|
||||||
|
sampad-- MQTT ---MQTT broker
|
||||||
|
```
|
|
@ -0,0 +1,57 @@
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde_dhall::StaticType;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
#[derive(Deserialize, StaticType, Debug)]
|
||||||
|
pub enum Action {
|
||||||
|
MQTTPub { topic: String, payload: String }, // Publish something (server, topic, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, StaticType, Debug)]
|
||||||
|
pub enum Mapping {
|
||||||
|
NOP, // Do nothing.
|
||||||
|
Passthrough, // Passthrough to the layer below
|
||||||
|
ActivateLayer(String), // Activate a layer
|
||||||
|
Print(String), // Print a string to console
|
||||||
|
Trigger(Action), // Trigger an action
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct Layer {
|
||||||
|
pub default_mapping: Mapping, // What do we do if there's no mapping for a button?
|
||||||
|
pub mappings: HashMap<String, Mapping>, // Button id -> mapping
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct MQTTServer {
|
||||||
|
pub host: String,
|
||||||
|
pub port: Option<u16>,
|
||||||
|
pub user: Option<String>,
|
||||||
|
pub pass: Option<String>,
|
||||||
|
pub client_id: Option<String>,
|
||||||
|
pub topic_prefix: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct Config {
|
||||||
|
pub mqtt_servers: HashMap<String, MQTTServer>,
|
||||||
|
pub layers: HashMap<String, Layer>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub fn from_file(p: &Path) -> Result<Self, Box<dyn Error>> {
|
||||||
|
match serde_dhall::from_file(p)
|
||||||
|
.with_builtin_type("Mapping".to_string(), Mapping::static_type())
|
||||||
|
.with_builtin_type("Action".to_string(), Action::static_type())
|
||||||
|
.parse::<Self>()
|
||||||
|
{
|
||||||
|
Ok(c) => Ok(c),
|
||||||
|
Err(e) => {
|
||||||
|
println!("{}", e);
|
||||||
|
Err(Box::new(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
use bitvec::prelude::*;
|
||||||
|
use hidapi::{DeviceInfo, HidApi, HidDevice};
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
const HID_USAGE_PAGE: u16 = 0xFF;
|
||||||
|
const HID_USAGE: u16 = 0x1;
|
||||||
|
|
||||||
|
pub fn get_devices<'a>(hid: &'a HidApi, print: bool) -> Vec<&'a DeviceInfo> {
|
||||||
|
if print {
|
||||||
|
println!("All HIDs attached:");
|
||||||
|
println!("Vendor ID, Product ID, Manufacturer, Produce, Usage Page, Usage");
|
||||||
|
}
|
||||||
|
let mut devices = vec![];
|
||||||
|
for device in hid.device_list() {
|
||||||
|
if (device.usage_page(), device.usage()) == (HID_USAGE_PAGE, HID_USAGE) {
|
||||||
|
devices.push(device);
|
||||||
|
}
|
||||||
|
if print {
|
||||||
|
println!(
|
||||||
|
"{}, {}, {}, {}, {}, {}",
|
||||||
|
device.vendor_id(),
|
||||||
|
device.product_id(),
|
||||||
|
device.manufacturer_string().unwrap_or("Unknown"),
|
||||||
|
device.product_string().unwrap_or("Unknown"),
|
||||||
|
device.usage_page(),
|
||||||
|
device.usage(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
devices
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ButtonEvent {
|
||||||
|
KeyDown(u8),
|
||||||
|
KeyUp(u8),
|
||||||
|
}
|
||||||
|
|
||||||
|
type State = u16;
|
||||||
|
|
||||||
|
pub struct ButtonPad<'a> {
|
||||||
|
dev: &'a HidDevice,
|
||||||
|
state: State,
|
||||||
|
queued: VecDeque<ButtonEvent>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ButtonPad<'_> {
|
||||||
|
pub fn new(dev: &HidDevice) -> ButtonPad {
|
||||||
|
ButtonPad {
|
||||||
|
dev,
|
||||||
|
state: 0,
|
||||||
|
queued: VecDeque::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for ButtonPad<'_> {
|
||||||
|
type Item = Result<ButtonEvent, Box<dyn Error>>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
loop {
|
||||||
|
// If we have queued events pop them first
|
||||||
|
if let Some(ev) = self.queued.pop_front() {
|
||||||
|
return Some(Ok(ev));
|
||||||
|
}
|
||||||
|
// Read from the device, blocking until the next report
|
||||||
|
let mut buf: [u8; 2] = [0; 2];
|
||||||
|
self.dev.read(&mut buf).expect("error reading");
|
||||||
|
log::info!("{:?}", buf);
|
||||||
|
|
||||||
|
let new: u16 = buf[0] as u16 | (buf[1] as u16) << 8;
|
||||||
|
|
||||||
|
// Work out what, if anything, changed between the last report and this one, and
|
||||||
|
// queue all the
|
||||||
|
self.queued.extend(
|
||||||
|
self.state
|
||||||
|
.view_bits::<Lsb0>()
|
||||||
|
.iter()
|
||||||
|
.zip(new.view_bits::<Lsb0>().iter())
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(
|
||||||
|
|(i, (state_last, state_new))| match (*state_last, *state_new) {
|
||||||
|
(false, true) => Some(ButtonEvent::KeyDown(i.try_into().unwrap())),
|
||||||
|
(true, false) => Some(ButtonEvent::KeyUp(i.try_into().unwrap())),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
self.state = new;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,130 @@
|
||||||
|
use env_logger::Env;
|
||||||
|
use hidapi::HidApi;
|
||||||
|
use log::info;
|
||||||
|
use rumqttc::{Client, MqttOptions, QoS};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
|
mod config;
|
||||||
|
mod device;
|
||||||
|
|
||||||
|
struct State<'a> {
|
||||||
|
mqtt_servers: HashMap<String, Client>,
|
||||||
|
conf: &'a config::Config,
|
||||||
|
key_state: [bool; 16],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> State<'a> {
|
||||||
|
fn init(c: &'a config::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_mapping(&mut self, m: &config::Mapping) {
|
||||||
|
match m {
|
||||||
|
config::Mapping::Print(s) => println!("{}", s),
|
||||||
|
config::Mapping::Trigger(config::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();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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>> {
|
||||||
|
env_logger::Builder::from_env(Env::default().default_filter_or("info")).init();
|
||||||
|
let conf = config::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 {
|
||||||
|
info!("{:?}", ev);
|
||||||
|
state.handle_button(&ev);
|
||||||
|
let mut data = state.get_led_data().to_vec();
|
||||||
|
data.insert(0, 0x0);
|
||||||
|
data.insert(0, 0x0);
|
||||||
|
log::info!("{:?}", data);
|
||||||
|
device.write(data.as_slice())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Reference in New Issue