diff --git a/src/commands.h b/src/commands.h index 506c1fd..ae1a8da 100644 --- a/src/commands.h +++ b/src/commands.h @@ -1,8 +1,20 @@ #pragma once // Sent by host when connection is initiated. +// +// Packet structure: +// Data: | cmd | +// # of Bytes: | 1 | #define CMD_HELLO 0x00 +// Sent periodically by host to test connection. +// Keyboard should ignore this command. +// +// Packet structure: +// Data: | cmd | +// # of Bytes: | 1 | +#define CMD_RUTHERE 0x01 + // Send keyboard state to host. // // Packet structure: @@ -13,7 +25,7 @@ // 0x00: RGBMatrix disabled // 0x01: normal animation, no HID data. // 0x02: FFT Animation -#define CMD_SEND_STATE 0x01 +#define CMD_SEND_STATE 0x02 // Animation data. Sent by host. @@ -27,7 +39,7 @@ // // data: // Animation data. Content depends on data type. -#define CMD_ANIM_DATA 0x02 +#define CMD_ANIM_DATA 0x03 // Data for FFT animation. // Data segment consists of 10 bits, each representing the height of a column. diff --git a/src/ergodox.cpp b/src/ergodox.cpp index 6544be3..77c2c37 100644 --- a/src/ergodox.cpp +++ b/src/ergodox.cpp @@ -11,10 +11,10 @@ Ergodox::Ergodox( product_id(product_id), usage(usage), usage_page(usage_page), - handle(NULL) + handle(NULL), + connected(false) { hid_init(); - open(); } @@ -54,21 +54,23 @@ Ergodox& Ergodox::init( // Instance is a static function variable, // and will be deleted at end of program. Ergodox::~Ergodox() { - close(); + disconnect(); hid_exit(); } + /* 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. +Throws a runtime error if device is already open. If we successfully open a device, this method sends a CMD_HELLO packet. + +@returns True if connection was successful, false otherwise. */ -void Ergodox::open() { - if (handle != NULL) { +bool Ergodox::try_connect() { + if (connected) { throw std::runtime_error("Device already open"); } @@ -89,24 +91,47 @@ void Ergodox::open() { cur_dev = cur_dev->next; } + // Return false if connection failed if (path_to_open) { + connected = true; + handle = hid_open_path(path_to_open); + hid_set_nonblocking(handle, 1); hid_free_enumeration(devs); + write(CMD_HELLO, NULL, 0); + + #define MAX_STR 255 + wchar_t wstr[MAX_STR]; + + + wprintf(L"\nConnected to device!\n"); + + // Read metadata + wstr[0] = 0x0000; + hid_get_manufacturer_string(handle, wstr, MAX_STR); + wprintf(L"Manufacturer String: %ls\n", wstr); + + wstr[0] = 0x0000; + hid_get_product_string(handle, wstr, MAX_STR); + wprintf(L"Product String: %ls\n", wstr); + } else { + connected = false; + hid_free_enumeration(devs); - throw std::runtime_error("Could not open hid device"); + return false; } - hid_set_nonblocking(handle, 1); - - write(CMD_HELLO, NULL, 0); + return connected; } // Close hid device if it is open. -void Ergodox::close() { +void Ergodox::disconnect() { + connected = false; if (handle != NULL) { hid_close(handle); + handle = NULL; } } @@ -116,14 +141,15 @@ Send data to Ergodox. @param cmd Command byte @param data Data to send -@returns data_len Data length. Must be shorter than packet_size. +@param data_len Data length. Must be shorter than packet_size. +@returns True if successful, false otherwise. */ -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!"); - } - - if (data_len > packet_size) { +bool Ergodox::write(uint8_t cmd, const uint8_t* data, uint8_t data_len) { + if (!connected) { + throw std::runtime_error("Not connected, cannot write!"); + } else if (handle == NULL) { + throw std::runtime_error("Tried to write a null handle, something is very wrong."); + } else if (data_len > packet_size) { throw std::runtime_error("Data length exceeds packet size"); } @@ -138,7 +164,15 @@ int Ergodox::write(uint8_t cmd, const uint8_t* data, uint8_t data_len) const { // Copy data into rest of packet std::copy(data, data + data_len, packet + 2); - return hid_write(handle, packet, packet_size + 1); + ssize_t res; + res = hid_write(handle, packet, packet_size + 1); + + if (res < 0) { + wprintf(L"Lost device, disconnecting.\n"); + disconnect(); + return false; + } + return true; } @@ -148,16 +182,21 @@ Read data from Ergodox into read_buf. If a CMD_SEND_STATE packet is received, pr @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!"); + if (!connected) { + throw std::runtime_error("Not connected, cannot read!"); + } else if (handle == NULL) { + throw std::runtime_error("Tried to read a null handle, something is very wrong."); } + 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."); + wprintf(L"Lost device, disconnecting.\n"); + disconnect(); + return false; } switch(read_buf[0]) { @@ -173,5 +212,11 @@ bool Ergodox::read() { return false; } - return res > 0; + return true; +} + + +// Simple connectivity check +void Ergodox::test_connection() { + write(CMD_RUTHERE, NULL, 0); } \ No newline at end of file diff --git a/src/ergodox.hpp b/src/ergodox.hpp index 3c63809..b388d72 100644 --- a/src/ergodox.hpp +++ b/src/ergodox.hpp @@ -32,15 +32,22 @@ class Ergodox { ~Ergodox(); - int write(uint8_t cmd, const uint8_t* data, uint8_t data_len) const; + bool try_connect(); + void disconnect(); + void test_connection(); bool read(); + bool write(uint8_t cmd, const uint8_t* data, uint8_t data_len); + // Read buffer, len = packet_size. // Filled by read(). uint8_t read_buf[RAW_EPSIZE]; + + // Getter methods uint8_t get_animation_mode() const { return animation_mode; } + bool is_connected() const { return connected; } private: Ergodox( @@ -55,14 +62,16 @@ class Ergodox { //Ergodox(Ergodox& other); //Ergodox& operator=(Ergodox& other); - void open(); - void close(); - // HID device. // NULL if not opened. hid_device* handle; // Keyboard state variables. // Updated by read(). + + // Which animation is the keyboard running right now? + // See CMD_SEND_STATE in commands.h for docs. uint8_t animation_mode; + // Are we connected to a keyboard right now? + bool connected; }; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 8776846..430b8ce 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,6 +5,9 @@ // For reading FIFO #include #include +// For sleep +#include +#include // Local files #include "utility/bitmap.hpp" @@ -82,37 +85,57 @@ int main(int argc, char *argv[]) { ); // Write buffer - uint8_t hid_buf[12]; + uint8_t hid_buf[Dox.packet_size]; + + wprintf(L"Trying to connect...\n"); + while (!Dox.is_connected()) { + Dox.try_connect(); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + } + while (1) { + if (Dox.is_connected()) { + if (Dox.get_animation_mode() == 0x02) { + buf.update(); + fft.update(buf); - 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] - bottom_skip; - 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] - bottom_skip; + // Enforce max and min + // max implicitly enforces top_skip + h = h>kb_resolution ? kb_resolution : h; + h = h<0 ? 0 : h; - // 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; + hid_buf[i + 1] = h; + } } - Dox.write(CMD_ANIM_DATA, hid_buf, 13); - } - // Read a packet if there is a packet to read - if (Dox.read()) { - uint8_t cmd = Dox.read_buf[0]; + Dox.write(CMD_ANIM_DATA, hid_buf, Dox.packet_size); - switch(cmd) { - case CMD_SEND_STATE: - break; + // Dox.write might detect that we've been disconnected, + // and Dox.read will fail if we are. + // This check prevents it from doing that, and instead jumps to reconnect. + if (!Dox.is_connected()) { continue; } + + // 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; + } + } + } else { + wprintf(L"Trying to connect...\n"); + while (!Dox.is_connected()) { + Dox.try_connect(); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); } } }