diff --git a/src/commands.h b/src/commands.h new file mode 100644 index 0000000..506c1fd --- /dev/null +++ b/src/commands.h @@ -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 diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..63cd111 --- /dev/null +++ b/src/config.h @@ -0,0 +1,5 @@ +#pragma once + +// USB packet size, in bytes. +// Usually 32, but depends on keyboard. +#define RAW_EPSIZE 32 \ No newline at end of file diff --git a/src/ergodox.cpp b/src/ergodox.cpp index 8f14cb9..6544be3 100644 --- a/src/ergodox.cpp +++ b/src/ergodox.cpp @@ -1,7 +1,6 @@ #include "ergodox.hpp" - Ergodox::Ergodox( unsigned short vendor_id, unsigned short product_id, @@ -64,6 +63,9 @@ Ergodox::~Ergodox() { Try to open this keyboard's hid device. Throws a runtime error if device is already open, 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() { if (handle != NULL) { @@ -94,6 +96,10 @@ void Ergodox::open() { hid_free_enumeration(devs); 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. +@param cmd Command byte @param data Data to send -@param length How many bytes to send -@returns Actual number of bytes written, or -1 on error. +@returns data_len Data length. Must be shorter than packet_size. */ -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) { 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; } \ No newline at end of file diff --git a/src/ergodox.hpp b/src/ergodox.hpp index d178b1d..3c63809 100644 --- a/src/ergodox.hpp +++ b/src/ergodox.hpp @@ -5,12 +5,24 @@ #include #include "hidapi.h" +#include "config.h" +#include "commands.h" + + /* A singleton Ergodox interface. Wraps all hidapi methods, including hid_init() and hid_exit(). */ class Ergodox { 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( unsigned short vendor_id, unsigned short product_id, @@ -20,11 +32,15 @@ class Ergodox { ~Ergodox(); - int write( - uint8_t* data, - size_t length - ) const; + int write(uint8_t cmd, const uint8_t* data, uint8_t data_len) 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: Ergodox( @@ -42,13 +58,11 @@ class Ergodox { void open(); 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. // NULL if not opened. hid_device* handle; + + // Keyboard state variables. + // Updated by read(). + uint8_t animation_mode; }; \ No newline at end of file diff --git a/src/test.cpp b/src/test.cpp index b9dbfe8..abc0dbe 100644 --- a/src/test.cpp +++ b/src/test.cpp @@ -2,7 +2,6 @@ #include #include #include - // For reading FIFO #include #include @@ -12,6 +11,7 @@ #include "utility/buffer.hpp" #include "signal_processing/fft.hpp" #include "ergodox.hpp" +#include "commands.h" // 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 height = 150; +const size_t height = bottom_skip + kb_resolution + top_skip; int main(int argc, char *argv[]) { @@ -52,7 +63,7 @@ int main(int argc, char *argv[]) { // FFT generator FFT_Visualizer fft = FFT_Visualizer( width, height, - 100, 10000 + 100, 5000 ); // Audio buffer @@ -70,24 +81,40 @@ int main(int argc, char *argv[]) { 0xFF60 ); - // Data buffer + // Write buffer uint8_t hid_buf[12]; - while (1) { - buf.update(); - fft.update(buf); - hid_buf[0] = 0x01; - hid_buf[1] = 0x02; - for (size_t i = 0; i < 10; i++) { - ssize_t h = fft.get_output()[i + 1] - 10; - h = h>100 ? 100 : h; - h = h<0 ? 0 : h; - hid_buf[i + 2] = h; + + if (Dox.get_animation_mode() == 0x02) { + buf.update(); + fft.update(buf); + + hid_buf[0] = CMD_ANIM_DATA_fft; + for (size_t i = 0; i < 10; i++) { + // Get height from fft, apply bottom_skip + 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; + + 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;