Upgraded hid interface

master
Mark 2022-07-08 10:41:19 -07:00
parent dacb8d820d
commit 6f2c2e313e
Signed by: Mark
GPG Key ID: AD62BB059C2AAEE4
5 changed files with 167 additions and 30 deletions

35
src/commands.h Normal file
View File

@ -0,0 +1,35 @@
#pragma once
// Sent by host when connection is initiated.
#define CMD_HELLO 0x00
// Send keyboard state to host.
//
// Packet structure:
// Data: | cmd | anim state |
// # of Bytes: | 1 | 1 |
//
// anim state:
// 0x00: RGBMatrix disabled
// 0x01: normal animation, no HID data.
// 0x02: FFT Animation
#define CMD_SEND_STATE 0x01
// Animation data. Sent by host.
//
// Packet structure:
// Data: | cmd | data type | data |
// # of Bytes: | 1 | 1 | ? |
//
// data type:
// Which animation this data is for. These are defined below.
//
// data:
// Animation data. Content depends on data type.
#define CMD_ANIM_DATA 0x02
// Data for FFT animation.
// Data segment consists of 10 bits, each representing the height of a column.
// Minimum height is 0, maximum is 250.
#define CMD_ANIM_DATA_fft 0x00

5
src/config.h Normal file
View File

@ -0,0 +1,5 @@
#pragma once
// USB packet size, in bytes.
// Usually 32, but depends on keyboard.
#define RAW_EPSIZE 32

View File

@ -1,7 +1,6 @@
#include "ergodox.hpp" #include "ergodox.hpp"
Ergodox::Ergodox( Ergodox::Ergodox(
unsigned short vendor_id, unsigned short vendor_id,
unsigned short product_id, unsigned short product_id,
@ -64,6 +63,9 @@ Ergodox::~Ergodox() {
Try to open this keyboard's hid device. Try to open this keyboard's hid device.
Throws a runtime error if device is already open, Throws a runtime error if device is already open,
or if we can't find a device with the given params. or if we can't find a device with the given params.
If we successfully open a device, this method sends a
CMD_HELLO packet.
*/ */
void Ergodox::open() { void Ergodox::open() {
if (handle != NULL) { if (handle != NULL) {
@ -94,6 +96,10 @@ void Ergodox::open() {
hid_free_enumeration(devs); hid_free_enumeration(devs);
throw std::runtime_error("Could not open hid device"); throw std::runtime_error("Could not open hid device");
} }
hid_set_nonblocking(handle, 1);
write(CMD_HELLO, NULL, 0);
} }
@ -108,14 +114,64 @@ void Ergodox::close() {
/* /*
Send data to Ergodox. Send data to Ergodox.
@param cmd Command byte
@param data Data to send @param data Data to send
@param length How many bytes to send @returns data_len Data length. Must be shorter than packet_size.
@returns Actual number of bytes written, or -1 on error.
*/ */
int Ergodox::write(uint8_t* data, size_t length) const { int Ergodox::write(uint8_t cmd, const uint8_t* data, uint8_t data_len) const {
if (handle == NULL) { if (handle == NULL) {
throw std::runtime_error("Cannot write, device is not open!"); throw std::runtime_error("Cannot write, device is not open!");
} }
return hid_write(handle, data, length); if (data_len > packet_size) {
throw std::runtime_error("Data length exceeds packet size");
}
if (data == NULL) {
data = {0x00};
}
uint8_t packet[packet_size];
packet[0] = 0x00; // Report number. Not seen by keyboard.
packet[1] = cmd; // First byte is always command
// Copy data into rest of packet
std::copy(data, data + data_len, packet + 2);
return hid_write(handle, packet, packet_size + 1);
}
/*
Read data from Ergodox into read_buf. If a CMD_SEND_STATE packet is received, process it and return false.
@returns True if a new command was received, false otherwise.
*/
bool Ergodox::read() {
if (handle == NULL) {
throw std::runtime_error("Cannot write, device is not open!");
}
ssize_t res;
res = hid_read(handle, read_buf, packet_size);
if (res == 0) {
return false;
} else if (res < 0) {
throw std::runtime_error("Read failed.");
}
switch(read_buf[0]) {
// If keyboard sends a state packet, parse it.
case CMD_SEND_STATE:
if (animation_mode != read_buf[1]) {
wprintf(L"Mode set to %#x\n", read_buf[1]);
animation_mode = read_buf[1];
}
// Main code should not parse state packets.
return false;
}
return res > 0;
} }

View File

@ -5,12 +5,24 @@
#include <wchar.h> #include <wchar.h>
#include "hidapi.h" #include "hidapi.h"
#include "config.h"
#include "commands.h"
/* /*
A singleton Ergodox interface. Wraps all hidapi methods, including A singleton Ergodox interface. Wraps all hidapi methods, including
hid_init() and hid_exit(). hid_init() and hid_exit().
*/ */
class Ergodox { class Ergodox {
public: public:
// USB Device paramaters
const unsigned short vendor_id;
const unsigned short product_id;
const unsigned short usage;
const unsigned short usage_page;
const uint8_t packet_size = RAW_EPSIZE;
static Ergodox& init( static Ergodox& init(
unsigned short vendor_id, unsigned short vendor_id,
unsigned short product_id, unsigned short product_id,
@ -20,11 +32,15 @@ class Ergodox {
~Ergodox(); ~Ergodox();
int write( int write(uint8_t cmd, const uint8_t* data, uint8_t data_len) const;
uint8_t* data,
size_t length
) const;
bool read();
// Read buffer, len = packet_size.
// Filled by read().
uint8_t read_buf[RAW_EPSIZE];
uint8_t get_animation_mode() const { return animation_mode; }
private: private:
Ergodox( Ergodox(
@ -42,13 +58,11 @@ class Ergodox {
void open(); void open();
void close(); void close();
// USB Device paramaters
const unsigned short vendor_id;
const unsigned short product_id;
const unsigned short usage;
const unsigned short usage_page;
// HID device. // HID device.
// NULL if not opened. // NULL if not opened.
hid_device* handle; hid_device* handle;
// Keyboard state variables.
// Updated by read().
uint8_t animation_mode;
}; };

View File

@ -2,7 +2,6 @@
#include <cstdint> #include <cstdint>
#include <vector> #include <vector>
#include <stdexcept> #include <stdexcept>
// For reading FIFO // For reading FIFO
#include <fcntl.h> #include <fcntl.h>
#include <unistd.h> #include <unistd.h>
@ -12,6 +11,7 @@
#include "utility/buffer.hpp" #include "utility/buffer.hpp"
#include "signal_processing/fft.hpp" #include "signal_processing/fft.hpp"
#include "ergodox.hpp" #include "ergodox.hpp"
#include "commands.h"
// TODO: // TODO:
@ -38,8 +38,19 @@ void draw_spectrum_bitmap(
} }
} }
// How many keys in a column * resolution per key.
// this MUST fit inside a uint8_t.
const uint8_t kb_resolution = 5 * 50;
// How many resolution steps to skip at the top and bottom.
const size_t bottom_skip = 25;
const size_t top_skip = 50;
const size_t width = 12; const size_t width = 12;
const size_t height = 150; const size_t height = bottom_skip + kb_resolution + top_skip;
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
@ -52,7 +63,7 @@ int main(int argc, char *argv[]) {
// FFT generator // FFT generator
FFT_Visualizer fft = FFT_Visualizer( FFT_Visualizer fft = FFT_Visualizer(
width, height, width, height,
100, 10000 100, 5000
); );
// Audio buffer // Audio buffer
@ -70,24 +81,40 @@ int main(int argc, char *argv[]) {
0xFF60 0xFF60
); );
// Data buffer // Write buffer
uint8_t hid_buf[12]; uint8_t hid_buf[12];
while (1) { while (1) {
if (Dox.get_animation_mode() == 0x02) {
buf.update(); buf.update();
fft.update(buf); fft.update(buf);
hid_buf[0] = 0x01; hid_buf[0] = CMD_ANIM_DATA_fft;
hid_buf[1] = 0x02;
for (size_t i = 0; i < 10; i++) { for (size_t i = 0; i < 10; i++) {
ssize_t h = fft.get_output()[i + 1] - 10; // Get height from fft, apply bottom_skip
h = h>100 ? 100 : h; ssize_t h = fft.get_output()[i + 1] - bottom_skip;
// Enforce max and min
// max implicitly enforces top_skip
h = h>kb_resolution ? kb_resolution : h;
h = h<0 ? 0 : h; h = h<0 ? 0 : h;
hid_buf[i + 2] = h;
hid_buf[i + 1] = h;
}
Dox.write(CMD_ANIM_DATA, hid_buf, 13);
} }
Dox.write(hid_buf, 12); // Read a packet if there is a packet to read
if (Dox.read()) {
uint8_t cmd = Dox.read_buf[0];
switch(cmd) {
case CMD_SEND_STATE:
break;
}
}
} }
return 0; return 0;