From 7d685956cc616a8d76aa7866f42bf569a9ca3fca Mon Sep 17 00:00:00 2001 From: Ryan Date: Mon, 31 Jan 2022 04:29:42 +1100 Subject: Rename `AdafruitBLE` to `BluefruitLE` (#16127) --- common_features.mk | 10 +- data/schemas/keyboard.jsonschema | 2 +- docs/config_options.md | 4 +- docs/feature_bluetooth.md | 10 +- drivers/bluetooth/adafruit_ble.cpp | 699 --------------------- drivers/bluetooth/adafruit_ble.h | 59 -- drivers/bluetooth/bluefruit_le.cpp | 699 +++++++++++++++++++++ drivers/bluetooth/bluefruit_le.h | 59 ++ drivers/bluetooth/outputselect.c | 8 +- keyboards/10bleoledhub/rules.mk | 2 +- .../40percentclub/mf68/keymaps/mf68_ble/rules.mk | 2 +- keyboards/atreus/feather/rules.mk | 2 +- keyboards/bioi/main.c | 6 +- keyboards/converter/m0110_usb/readme.md | 2 +- keyboards/converter/usb_usb/ble/config.h | 4 +- keyboards/converter/usb_usb/ble/rules.mk | 2 +- keyboards/dtisaac/dosa40rgb/readme.md | 2 +- keyboards/dtisaac/dosa40rgb/rules.mk | 2 +- keyboards/handwired/42/rules.mk | 2 +- keyboards/handwired/bdn9_ble/rules.mk | 2 +- keyboards/handwired/fruity60/rules.mk | 2 +- keyboards/handwired/prkl30/feather/rules.mk | 2 +- .../handwired/promethium/keymaps/default/keymap.c | 6 +- .../handwired/promethium/keymaps/priyadi/keymap.c | 6 +- keyboards/handwired/promethium/promethium.c | 4 +- keyboards/handwired/promethium/rules.mk | 2 +- keyboards/handwired/pterodactyl/rules.mk | 2 +- keyboards/handwired/slash/rules.mk | 2 +- keyboards/latin47ble/rules.mk | 2 +- keyboards/latin64ble/rules.mk | 2 +- keyboards/latinpadble/rules.mk | 2 +- keyboards/meira/featherble/rules.mk | 2 +- keyboards/nek_type_a/rules.mk | 2 +- keyboards/spaceman/pancake/rev1/feather/rules.mk | 2 +- keyboards/tokyokeyboard/alix40/rules.mk | 2 +- tmk_core/protocol/lufa/lufa.c | 30 +- users/wanleg/rules.mk | 2 +- 37 files changed, 825 insertions(+), 825 deletions(-) delete mode 100644 drivers/bluetooth/adafruit_ble.cpp delete mode 100644 drivers/bluetooth/adafruit_ble.h create mode 100644 drivers/bluetooth/bluefruit_le.cpp create mode 100644 drivers/bluetooth/bluefruit_le.h diff --git a/common_features.mk b/common_features.mk index e00f95f8e4..d3da487e69 100644 --- a/common_features.mk +++ b/common_features.mk @@ -717,7 +717,7 @@ ifeq ($(strip $(USBPD_ENABLE)), yes) endif BLUETOOTH_ENABLE ?= no -VALID_BLUETOOTH_DRIVER_TYPES := AdafruitBLE RN42 custom +VALID_BLUETOOTH_DRIVER_TYPES := BluefruitLE RN42 custom ifeq ($(strip $(BLUETOOTH_ENABLE)), yes) ifeq ($(filter $(strip $(BLUETOOTH_DRIVER)),$(VALID_BLUETOOTH_DRIVER_TYPES)),) $(error "$(BLUETOOTH_DRIVER)" is not a valid Bluetooth driver type) @@ -727,15 +727,15 @@ ifeq ($(strip $(BLUETOOTH_ENABLE)), yes) COMMON_VPATH += $(DRIVER_PATH)/bluetooth SRC += outputselect.c - ifeq ($(strip $(BLUETOOTH_DRIVER)), AdafruitBLE) - OPT_DEFS += -DMODULE_ADAFRUIT_BLE + ifeq ($(strip $(BLUETOOTH_DRIVER)), BluefruitLE) + OPT_DEFS += -DBLUETOOTH_BLUEFRUIT_LE SRC += analog.c - SRC += $(DRIVER_PATH)/bluetooth/adafruit_ble.cpp + SRC += $(DRIVER_PATH)/bluetooth/bluefruit_le.cpp QUANTUM_LIB_SRC += spi_master.c endif ifeq ($(strip $(BLUETOOTH_DRIVER)), RN42) - OPT_DEFS += -DMODULE_RN42 + OPT_DEFS += -DBLUETOOTH_RN42 SRC += $(DRIVER_PATH)/bluetooth/rn42.c QUANTUM_LIB_SRC += uart.c endif diff --git a/data/schemas/keyboard.jsonschema b/data/schemas/keyboard.jsonschema index 547df94d1f..132484b708 100644 --- a/data/schemas/keyboard.jsonschema +++ b/data/schemas/keyboard.jsonschema @@ -45,7 +45,7 @@ "properties": { "driver": { "type": "string", - "enum": ["AdafruitBLE", "RN42"] + "enum": ["BluefruitLE", "RN42"] }, "lto": {"type": "boolean"}, } diff --git a/docs/config_options.md b/docs/config_options.md index 7657fae02e..832af78b22 100644 --- a/docs/config_options.md +++ b/docs/config_options.md @@ -429,8 +429,8 @@ Use these to enable or disable building certain features. The more you have enab * MIDI controls * `UNICODE_ENABLE` * Unicode -* `BLUETOOTH` - * Current options are AdafruitBLE, RN42 +* `BLUETOOTH_ENABLE` + * Current options are BluefruitLE, RN42 * `SPLIT_KEYBOARD` * Enables split keyboard support (dual MCU like the let's split and bakingpy's boards) and includes all necessary files located at quantum/split_common * `CUSTOM_MATRIX` diff --git a/docs/feature_bluetooth.md b/docs/feature_bluetooth.md index f6fb02d948..d4ed494053 100644 --- a/docs/feature_bluetooth.md +++ b/docs/feature_bluetooth.md @@ -7,7 +7,7 @@ Currently Bluetooth support is limited to AVR based chips. For Bluetooth 2.1, QM |Board |Bluetooth Protocol |Connection Type|rules.mk |Bluetooth Chip| |----------------------------------------------------------------|--------------------|---------------|--------------------------------|--------------| |Roving Networks RN-42 (Sparkfun Bluesmirf) |Bluetooth Classic |UART |`BLUETOOTH_DRIVER = RN42` |RN-42 | -|[Bluefruit LE SPI Friend](https://www.adafruit.com/product/2633)|Bluetooth Low Energy|SPI |`BLUETOOTH_DRIVER = AdafruitBLE`|nRF51822 | +|[Bluefruit LE SPI Friend](https://www.adafruit.com/product/2633)|Bluetooth Low Energy|SPI |`BLUETOOTH_DRIVER = BluefruitLE`|nRF51822 | Not Supported Yet but possible: * [Bluefruit LE UART Friend](https://www.adafruit.com/product/2479). [Possible tmk implementation found in](https://github.com/tmk/tmk_keyboard/issues/514) @@ -17,9 +17,9 @@ Not Supported Yet but possible: ### Adafruit BLE SPI Friend Currently The only bluetooth chipset supported by QMK is the Adafruit Bluefruit SPI Friend. It's a Nordic nRF51822 based chip running Adafruit's custom firmware. Data is transmitted via Adafruit's SDEP over Hardware SPI. The [Feather 32u4 Bluefruit LE](https://www.adafruit.com/product/2829) is supported as it's an AVR mcu connected via SPI to the Nordic BLE chip with Adafruit firmware. If Building a custom board with the SPI friend it would be easiest to just use the pin selection that the 32u4 feather uses but you can change the pins in the config.h options with the following defines: -* `#define ADAFRUIT_BLE_RST_PIN D4` -* `#define ADAFRUIT_BLE_CS_PIN B4` -* `#define ADAFRUIT_BLE_IRQ_PIN E6` +* `#define BLUEFRUIT_LE_RST_PIN D4` +* `#define BLUEFRUIT_LE_CS_PIN B4` +* `#define BLUEFRUIT_LE_IRQ_PIN E6` A Bluefruit UART friend can be converted to an SPI friend, however this [requires](https://github.com/qmk/qmk_firmware/issues/2274) some reflashing and soldering directly to the MDBT40 chip. @@ -32,7 +32,7 @@ Add the following to your `rules.mk`: ```make BLUETOOTH_ENABLE = yes -BLUETOOTH_DRIVER = AdafruitBLE # or RN42 +BLUETOOTH_DRIVER = BluefruitLE # or RN42 ``` ## Bluetooth Keycodes diff --git a/drivers/bluetooth/adafruit_ble.cpp b/drivers/bluetooth/adafruit_ble.cpp deleted file mode 100644 index 34a780e9a5..0000000000 --- a/drivers/bluetooth/adafruit_ble.cpp +++ /dev/null @@ -1,699 +0,0 @@ -#include "adafruit_ble.h" - -#include -#include -#include -#include "debug.h" -#include "timer.h" -#include "action_util.h" -#include "ringbuffer.hpp" -#include -#include "spi_master.h" -#include "wait.h" -#include "analog.h" -#include "progmem.h" - -// These are the pin assignments for the 32u4 boards. -// You may define them to something else in your config.h -// if yours is wired up differently. -#ifndef ADAFRUIT_BLE_RST_PIN -# define ADAFRUIT_BLE_RST_PIN D4 -#endif - -#ifndef ADAFRUIT_BLE_CS_PIN -# define ADAFRUIT_BLE_CS_PIN B4 -#endif - -#ifndef ADAFRUIT_BLE_IRQ_PIN -# define ADAFRUIT_BLE_IRQ_PIN E6 -#endif - -#ifndef ADAFRUIT_BLE_SCK_DIVISOR -# define ADAFRUIT_BLE_SCK_DIVISOR 2 // 4MHz SCK/8MHz CPU, calculated for Feather 32U4 BLE -#endif - -#define SAMPLE_BATTERY -#define ConnectionUpdateInterval 1000 /* milliseconds */ - -#ifndef BATTERY_LEVEL_PIN -# define BATTERY_LEVEL_PIN B5 -#endif - -static struct { - bool is_connected; - bool initialized; - bool configured; - -#define ProbedEvents 1 -#define UsingEvents 2 - bool event_flags; - -#ifdef SAMPLE_BATTERY - uint16_t last_battery_update; - uint32_t vbat; -#endif - uint16_t last_connection_update; -} state; - -// Commands are encoded using SDEP and sent via SPI -// https://github.com/adafruit/Adafruit_BluefruitLE_nRF51/blob/master/SDEP.md - -#define SdepMaxPayload 16 -struct sdep_msg { - uint8_t type; - uint8_t cmd_low; - uint8_t cmd_high; - struct __attribute__((packed)) { - uint8_t len : 7; - uint8_t more : 1; - }; - uint8_t payload[SdepMaxPayload]; -} __attribute__((packed)); - -// The recv latency is relatively high, so when we're hammering keys quickly, -// we want to avoid waiting for the responses in the matrix loop. We maintain -// a short queue for that. Since there is quite a lot of space overhead for -// the AT command representation wrapped up in SDEP, we queue the minimal -// information here. - -enum queue_type { - QTKeyReport, // 1-byte modifier + 6-byte key report - QTConsumer, // 16-bit key code -#ifdef MOUSE_ENABLE - QTMouseMove, // 4-byte mouse report -#endif -}; - -struct queue_item { - enum queue_type queue_type; - uint16_t added; - union __attribute__((packed)) { - struct __attribute__((packed)) { - uint8_t modifier; - uint8_t keys[6]; - } key; - - uint16_t consumer; - struct __attribute__((packed)) { - int8_t x, y, scroll, pan; - uint8_t buttons; - } mousemove; - }; -}; - -// Items that we wish to send -static RingBuffer send_buf; -// Pending response; while pending, we can't send any more requests. -// This records the time at which we sent the command for which we -// are expecting a response. -static RingBuffer resp_buf; - -static bool process_queue_item(struct queue_item *item, uint16_t timeout); - -enum sdep_type { - SdepCommand = 0x10, - SdepResponse = 0x20, - SdepAlert = 0x40, - SdepError = 0x80, - SdepSlaveNotReady = 0xFE, // Try again later - SdepSlaveOverflow = 0xFF, // You read more data than is available -}; - -enum ble_cmd { - BleInitialize = 0xBEEF, - BleAtWrapper = 0x0A00, - BleUartTx = 0x0A01, - BleUartRx = 0x0A02, -}; - -enum ble_system_event_bits { - BleSystemConnected = 0, - BleSystemDisconnected = 1, - BleSystemUartRx = 8, - BleSystemMidiRx = 10, -}; - -#define SdepTimeout 150 /* milliseconds */ -#define SdepShortTimeout 10 /* milliseconds */ -#define SdepBackOff 25 /* microseconds */ -#define BatteryUpdateInterval 10000 /* milliseconds */ - -static bool at_command(const char *cmd, char *resp, uint16_t resplen, bool verbose, uint16_t timeout = SdepTimeout); -static bool at_command_P(const char *cmd, char *resp, uint16_t resplen, bool verbose = false); - -// Send a single SDEP packet -static bool sdep_send_pkt(const struct sdep_msg *msg, uint16_t timeout) { - spi_start(ADAFRUIT_BLE_CS_PIN, false, 0, ADAFRUIT_BLE_SCK_DIVISOR); - uint16_t timerStart = timer_read(); - bool success = false; - bool ready = false; - - do { - ready = spi_write(msg->type) != SdepSlaveNotReady; - if (ready) { - break; - } - - // Release it and let it initialize - spi_stop(); - wait_us(SdepBackOff); - spi_start(ADAFRUIT_BLE_CS_PIN, false, 0, ADAFRUIT_BLE_SCK_DIVISOR); - } while (timer_elapsed(timerStart) < timeout); - - if (ready) { - // Slave is ready; send the rest of the packet - spi_transmit(&msg->cmd_low, sizeof(*msg) - (1 + sizeof(msg->payload)) + msg->len); - success = true; - } - - spi_stop(); - - return success; -} - -static inline void sdep_build_pkt(struct sdep_msg *msg, uint16_t command, const uint8_t *payload, uint8_t len, bool moredata) { - msg->type = SdepCommand; - msg->cmd_low = command & 0xFF; - msg->cmd_high = command >> 8; - msg->len = len; - msg->more = (moredata && len == SdepMaxPayload) ? 1 : 0; - - static_assert(sizeof(*msg) == 20, "msg is correctly packed"); - - memcpy(msg->payload, payload, len); -} - -// Read a single SDEP packet -static bool sdep_recv_pkt(struct sdep_msg *msg, uint16_t timeout) { - bool success = false; - uint16_t timerStart = timer_read(); - bool ready = false; - - do { - ready = readPin(ADAFRUIT_BLE_IRQ_PIN); - if (ready) { - break; - } - wait_us(1); - } while (timer_elapsed(timerStart) < timeout); - - if (ready) { - spi_start(ADAFRUIT_BLE_CS_PIN, false, 0, ADAFRUIT_BLE_SCK_DIVISOR); - - do { - // Read the command type, waiting for the data to be ready - msg->type = spi_read(); - if (msg->type == SdepSlaveNotReady || msg->type == SdepSlaveOverflow) { - // Release it and let it initialize - spi_stop(); - wait_us(SdepBackOff); - spi_start(ADAFRUIT_BLE_CS_PIN, false, 0, ADAFRUIT_BLE_SCK_DIVISOR); - continue; - } - - // Read the rest of the header - spi_receive(&msg->cmd_low, sizeof(*msg) - (1 + sizeof(msg->payload))); - - // and get the payload if there is any - if (msg->len <= SdepMaxPayload) { - spi_receive(msg->payload, msg->len); - } - success = true; - break; - } while (timer_elapsed(timerStart) < timeout); - - spi_stop(); - } - return success; -} - -static void resp_buf_read_one(bool greedy) { - uint16_t last_send; - if (!resp_buf.peek(last_send)) { - return; - } - - if (readPin(ADAFRUIT_BLE_IRQ_PIN)) { - struct sdep_msg msg; - - again: - if (sdep_recv_pkt(&msg, SdepTimeout)) { - if (!msg.more) { - // We got it; consume this entry - resp_buf.get(last_send); - dprintf("recv latency %dms\n", TIMER_DIFF_16(timer_read(), last_send)); - } - - if (greedy && resp_buf.peek(last_send) && readPin(ADAFRUIT_BLE_IRQ_PIN)) { - goto again; - } - } - - } else if (timer_elapsed(last_send) > SdepTimeout * 2) { - dprintf("waiting_for_result: timeout, resp_buf size %d\n", (int)resp_buf.size()); - - // Timed out: consume this entry - resp_buf.get(last_send); - } -} - -static void send_buf_send_one(uint16_t timeout = SdepTimeout) { - struct queue_item item; - - // Don't send anything more until we get an ACK - if (!resp_buf.empty()) { - return; - } - - if (!send_buf.peek(item)) { - return; - } - if (process_queue_item(&item, timeout)) { - // commit that peek - send_buf.get(item); - dprintf("send_buf_send_one: have %d remaining\n", (int)send_buf.size()); - } else { - dprint("failed to send, will retry\n"); - wait_ms(SdepTimeout); - resp_buf_read_one(true); - } -} - -static void resp_buf_wait(const char *cmd) { - bool didPrint = false; - while (!resp_buf.empty()) { - if (!didPrint) { - dprintf("wait on buf for %s\n", cmd); - didPrint = true; - } - resp_buf_read_one(true); - } -} - -static bool ble_init(void) { - state.initialized = false; - state.configured = false; - state.is_connected = false; - - setPinInput(ADAFRUIT_BLE_IRQ_PIN); - - spi_init(); - - // Perform a hardware reset - setPinOutput(ADAFRUIT_BLE_RST_PIN); - writePinHigh(ADAFRUIT_BLE_RST_PIN); - writePinLow(ADAFRUIT_BLE_RST_PIN); - wait_ms(10); - writePinHigh(ADAFRUIT_BLE_RST_PIN); - - wait_ms(1000); // Give it a second to initialize - - state.initialized = true; - return state.initialized; -} - -static inline uint8_t min(uint8_t a, uint8_t b) { return a < b ? a : b; } - -static bool read_response(char *resp, uint16_t resplen, bool verbose) { - char *dest = resp; - char *end = dest + resplen; - - while (true) { - struct sdep_msg msg; - - if (!sdep_recv_pkt(&msg, 2 * SdepTimeout)) { - dprint("sdep_recv_pkt failed\n"); - return false; - } - - if (msg.type != SdepResponse) { - *resp = 0; - return false; - } - - uint8_t len = min(msg.len, end - dest); - if (len > 0) { - memcpy(dest, msg.payload, len); - dest += len; - } - - if (!msg.more) { - // No more data is expected! - break; - } - } - - // Ensure the response is NUL terminated - *dest = 0; - - // "Parse" the result text; we want to snip off the trailing OK or ERROR line - // Rewind past the possible trailing CRLF so that we can strip it - --dest; - while (dest > resp && (dest[0] == '\n' || dest[0] == '\r')) { - *dest = 0; - --dest; - } - - // Look back for start of preceeding line - char *last_line = strrchr(resp, '\n'); - if (last_line) { - ++last_line; - } else { - last_line = resp; - } - - bool success = false; - static const char kOK[] PROGMEM = "OK"; - - success = !strcmp_P(last_line, kOK); - - if (verbose || !success) { - dprintf("result: %s\n", resp); - } - return success; -} - -static bool at_command(const char *cmd, char *resp, uint16_t resplen, bool verbose, uint16_t timeout) { - const char * end = cmd + strlen(cmd); - struct sdep_msg msg; - - if (verbose) { - dprintf("ble send: %s\n", cmd); - } - - if (resp) { - // They want to decode the response, so we need to flush and wait - // for all pending I/O to finish before we start this one, so - // that we don't confuse the results - resp_buf_wait(cmd); - *resp = 0; - } - - // Fragment the command into a series of SDEP packets - while (end - cmd > SdepMaxPayload) { - sdep_build_pkt(&msg, BleAtWrapper, (uint8_t *)cmd, SdepMaxPayload, true); - if (!sdep_send_pkt(&msg, timeout)) { - return false; - } - cmd += SdepMaxPayload; - } - - sdep_build_pkt(&msg, BleAtWrapper, (uint8_t *)cmd, end - cmd, false); - if (!sdep_send_pkt(&msg, timeout)) { - return false; - } - - if (resp == NULL) { - uint16_t now = timer_read(); - while (!resp_buf.enqueue(now)) { - resp_buf_read_one(false); - } - uint16_t later = timer_read(); - if (TIMER_DIFF_16(later, now) > 0) { - dprintf("waited %dms for resp_buf\n", TIMER_DIFF_16(later, now)); - } - return true; - } - - return read_response(resp, resplen, verbose); -} - -bool at_command_P(const char *cmd, char *resp, uint16_t resplen, bool verbose) { - char *cmdbuf = (char *)alloca(strlen_P(cmd) + 1); - strcpy_P(cmdbuf, cmd); - return at_command(cmdbuf, resp, resplen, verbose); -} - -bool adafruit_ble_is_connected(void) { return state.is_connected; } - -bool adafruit_ble_enable_keyboard(void) { - char resbuf[128]; - - if (!state.initialized && !ble_init()) { - return false; - } - - state.configured = false; - - // Disable command echo - static const char kEcho[] PROGMEM = "ATE=0"; - // Make the advertised name match the keyboard - static const char kGapDevName[] PROGMEM = "AT+GAPDEVNAME=" STR(PRODUCT); - // Turn on keyboard support - static const char kHidEnOn[] PROGMEM = "AT+BLEHIDEN=1"; - - // Adjust intervals to improve latency. This causes the "central" - // system (computer/tablet) to poll us every 10-30 ms. We can't - // set a smaller value than 10ms, and 30ms seems to be the natural - // processing time on my macbook. Keeping it constrained to that - // feels reasonable to type to. - static const char kGapIntervals[] PROGMEM = "AT+GAPINTERVALS=10,30,,"; - - // Reset the device so that it picks up the above changes - static const char kATZ[] PROGMEM = "ATZ"; - - // Turn down the power level a bit - static const char kPower[] PROGMEM = "AT+BLEPOWERLEVEL=-12"; - static PGM_P const configure_commands[] PROGMEM = { - kEcho, kGapIntervals, kGapDevName, kHidEnOn, kPower, kATZ, - }; - - uint8_t i; - for (i = 0; i < sizeof(configure_commands) / sizeof(configure_commands[0]); ++i) { - PGM_P cmd; - memcpy_P(&cmd, configure_commands + i, sizeof(cmd)); - - if (!at_command_P(cmd, resbuf, sizeof(resbuf))) { - dprintf("failed BLE command: %S: %s\n", cmd, resbuf); - goto fail; - } - } - - state.configured = true; - - // Check connection status in a little while; allow the ATZ time - // to kick in. - state.last_connection_update = timer_read(); -fail: - return state.configured; -} - -static void set_connected(bool connected) { - if (connected != state.is_connected) { - if (connected) { - dprint("BLE connected\n"); - } else { - dprint("BLE disconnected\n"); - } - state.is_connected = connected; - - // TODO: if modifiers are down on the USB interface and - // we cut over to BLE or vice versa, they will remain stuck. - // This feels like a good point to do something like clearing - // the keyboard and/or generating a fake all keys up message. - // However, I've noticed that it takes a couple of seconds - // for macOS to to start recognizing key presses after BLE - // is in the connected state, so I worry that doing that - // here may not be good enough. - } -} - -void adafruit_ble_task(void) { - char resbuf[48]; - - if (!state.configured && !adafruit_ble_enable_keyboard()) { - return; - } - resp_buf_read_one(true); - send_buf_send_one(SdepShortTimeout); - - if (resp_buf.empty() && (state.event_flags & UsingEvents) && readPin(ADAFRUIT_BLE_IRQ_PIN)) { - // Must be an event update - if (at_command_P(PSTR("AT+EVENTSTATUS"), resbuf, sizeof(resbuf))) { - uint32_t mask = strtoul(resbuf, NULL, 16); - - if (mask & BleSystemConnected) { - set_connected(true); - } else if (mask & BleSystemDisconnected) { - set_connected(false); - } - } - } - - if (timer_elapsed(state.last_connection_update) > ConnectionUpdateInterval) { - bool shouldPoll = true; - if (!(state.event_flags & ProbedEvents)) { - // Request notifications about connection status changes. - // This only works in SPIFRIEND firmware > 0.6.7, which is why - // we check for this conditionally here. - // Note that at the time of writing, HID reports only work correctly - // with Apple products on firmware version 0.6.7! - // https://forums.adafruit.com/viewtopic.php?f=8&t=104052 - if (at_command_P(PSTR("AT+EVENTENABLE=0x1"), resbuf, sizeof(resbuf))) { - at_command_P(PSTR("AT+EVENTENABLE=0x2"), resbuf, sizeof(resbuf)); - state.event_flags |= UsingEvents; - } - state.event_flags |= ProbedEvents; - - // leave shouldPoll == true so that we check at least once - // before relying solely on events - } else { - shouldPoll = false; - } - - static const char kGetConn[] PROGMEM = "AT+GAPGETCONN"; - state.last_connection_update = timer_read(); - - if (at_command_P(kGetConn, resbuf, sizeof(resbuf))) { - set_connected(atoi(resbuf)); - } - } - -#ifdef SAMPLE_BATTERY - if (timer_elapsed(state.last_battery_update) > BatteryUpdateInterval && resp_buf.empty()) { - state.last_battery_update = timer_read(); - - state.vbat = analogReadPin(BATTERY_LEVEL_PIN); - } -#endif -} - -static bool process_queue_item(struct queue_item *item, uint16_t timeout) { - char cmdbuf[48]; - char fmtbuf[64]; - - // Arrange to re-check connection after keys have settled - state.last_connection_update = timer_read(); - -#if 1 - if (TIMER_DIFF_16(state.last_connection_update, item->added) > 0) { - dprintf("send latency %dms\n", TIMER_DIFF_16(state.last_connection_update, item->added)); - } -#endif - - switch (item->queue_type) { - case QTKeyReport: - strcpy_P(fmtbuf, PSTR("AT+BLEKEYBOARDCODE=%02x-00-%02x-%02x-%02x-%02x-%02x-%02x")); - snprintf(cmdbuf, sizeof(cmdbuf), fmtbuf, item->key.modifier, item->key.keys[0], item->key.keys[1], item->key.keys[2], item->key.keys[3], item->key.keys[4], item->key.keys[5]); - return at_command(cmdbuf, NULL, 0, true, timeout); - - case QTConsumer: - strcpy_P(fmtbuf, PSTR("AT+BLEHIDCONTROLKEY=0x%04x")); - snprintf(cmdbuf, sizeof(cmdbuf), fmtbuf, item->consumer); - return at_command(cmdbuf, NULL, 0, true, timeout); - -#ifdef MOUSE_ENABLE - case QTMouseMove: - strcpy_P(fmtbuf, PSTR("AT+BLEHIDMOUSEMOVE=%d,%d,%d,%d")); - snprintf(cmdbuf, sizeof(cmdbuf), fmtbuf, item->mousemove.x, item->mousemove.y, item->mousemove.scroll, item->mousemove.pan); - if (!at_command(cmdbuf, NULL, 0, true, timeout)) { - return false; - } - strcpy_P(cmdbuf, PSTR("AT+BLEHIDMOUSEBUTTON=")); - if (item->mousemove.buttons & MOUSE_BTN1) { - strcat(cmdbuf, "L"); - } - if (item->mousemove.buttons & MOUSE_BTN2) { - strcat(cmdbuf, "R"); - } - if (item->mousemove.buttons & MOUSE_BTN3) { - strcat(cmdbuf, "M"); - } - if (item->mousemove.buttons == 0) { - strcat(cmdbuf, "0"); - } - return at_command(cmdbuf, NULL, 0, true, timeout); -#endif - default: - return true; - } -} - -void adafruit_ble_send_keys(uint8_t hid_modifier_mask, uint8_t *keys, uint8_t nkeys) { - struct queue_item item; - bool didWait = false; - - item.queue_type = QTKeyReport; - item.key.modifier = hid_modifier_mask; - item.added = timer_read(); - - while (nkeys >= 0) { - item.key.keys[0] = keys[0]; - item.key.keys[1] = nkeys >= 1 ? keys[1] : 0; - item.key.keys[2] = nkeys >= 2 ? keys[2] : 0; - item.key.keys[3] = nkeys >= 3 ? keys[3] : 0; - item.key.keys[4] = nkeys >= 4 ? keys[4] : 0; - item.key.keys[5] = nkeys >= 5 ? keys[5] : 0; - - if (!send_buf.enqueue(item)) { - if (!didWait) { - dprint("wait for buf space\n"); - didWait = true; - } - send_buf_send_one(); - continue; - } - - if (nkeys <= 6) { - return; - } - - nkeys -= 6; - keys += 6; - } -} - -void adafruit_ble_send_consumer_key(uint16_t usage) { - struct queue_item item; - - item.queue_type = QTConsumer; - item.consumer = usage; - - while (!send_buf.enqueue(item)) { - send_buf_send_one(); - } -} - -#ifdef MOUSE_ENABLE -void adafruit_ble_send_mouse_move(int8_t x, int8_t y, int8_t scroll, int8_t pan, uint8_t buttons) { - struct queue_item item; - - item.queue_type = QTMouseMove; - item.mousemove.x = x; - item.mousemove.y = y; - item.mousemove.scroll = scroll; - item.mousemove.pan = pan; - item.mousemove.buttons = buttons; - - while (!send_buf.enqueue(item)) { - send_buf_send_one(); - } -} -#endif - -uint32_t adafruit_ble_read_battery_voltage(void) { return state.vbat; } - -bool adafruit_ble_set_mode_leds(bool on) { - if (!state.configured) { - return false; - } - - // The "mode" led is the red blinky one - at_command_P(on ? PSTR("AT+HWMODELED=1") : PSTR("AT+HWMODELED=0"), NULL, 0); - - // Pin 19 is the blue "connected" LED; turn that off too. - // When turning LEDs back on, don't turn that LED on if we're - // not connected, as that would be confusing. - at_command_P(on && state.is_connected ? PSTR("AT+HWGPIO=19,1") : PSTR("AT+HWGPIO=19,0"), NULL, 0); - return true; -} - -// https://learn.adafruit.com/adafruit-feather-32u4-bluefruit-le/ble-generic#at-plus-blepowerlevel -bool adafruit_ble_set_power_level(int8_t level) { - char cmd[46]; - if (!state.configured) { - return false; - } - snprintf(cmd, sizeof(cmd), "AT+BLEPOWERLEVEL=%d", level); - return at_command(cmd, NULL, 0, false); -} diff --git a/drivers/bluetooth/adafruit_ble.h b/drivers/bluetooth/adafruit_ble.h deleted file mode 100644 index b43e0771d9..0000000000 --- a/drivers/bluetooth/adafruit_ble.h +++ /dev/null @@ -1,59 +0,0 @@ -/* Bluetooth Low Energy Protocol for QMK. - * Author: Wez Furlong, 2016 - * Supports the Adafruit BLE board built around the nRF51822 chip. - */ - -#pragma once - -#include -#include -#include - -#include "config_common.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/* Instruct the module to enable HID keyboard support and reset */ -extern bool adafruit_ble_enable_keyboard(void); - -/* Query to see if the BLE module is connected */ -extern bool adafruit_ble_query_is_connected(void); - -/* Returns true if we believe that the BLE module is connected. - * This uses our cached understanding that is maintained by - * calling ble_task() periodically. */ -extern bool adafruit_ble_is_connected(void); - -/* Call this periodically to process BLE-originated things */ -extern void adafruit_ble_task(void); - -/* Generates keypress events for a set of keys. - * The hid modifier mask specifies the state of the modifier keys for - * this set of keys. - * Also sends a key release indicator, so that the keys do not remain - * held down. */ -extern void adafruit_ble_send_keys(uint8_t hid_modifier_mask, uint8_t *keys, uint8_t nkeys); - -/* Send a consumer usage. - * (milliseconds) */ -extern void adafruit_ble_send_consumer_key(uint16_t usage); - -#ifdef MOUSE_ENABLE -/* Send a mouse/wheel movement report. - * The parameters are signed and indicate positive or negative direction - * change. */ -extern void adafruit_ble_send_mouse_move(int8_t x, int8_t y, int8_t scroll, int8_t pan, uint8_t buttons); -#endif - -/* Compute battery voltage by reading an analog pin. - * Returns the integer number of millivolts */ -extern uint32_t adafruit_ble_read_battery_voltage(void); - -extern bool adafruit_ble_set_mode_leds(bool on); -extern bool adafruit_ble_set_power_level(int8_t level); - -#ifdef __cplusplus -} -#endif diff --git a/drivers/bluetooth/bluefruit_le.cpp b/drivers/bluetooth/bluefruit_le.cpp new file mode 100644 index 0000000000..86581a1a48 --- /dev/null +++ b/drivers/bluetooth/bluefruit_le.cpp @@ -0,0 +1,699 @@ +#include "bluefruit_le.h" + +#include +#include +#include +#include "debug.h" +#include "timer.h" +#include "action_util.h" +#include "ringbuffer.hpp" +#include +#include "spi_master.h" +#include "wait.h" +#include "analog.h" +#include "progmem.h" + +// These are the pin assignments for the 32u4 boards. +// You may define them to something else in your config.h +// if yours is wired up differently. +#ifndef BLUEFRUIT_LE_RST_PIN +# define BLUEFRUIT_LE_RST_PIN D4 +#endif + +#ifndef BLUEFRUIT_LE_CS_PIN +# define BLUEFRUIT_LE_CS_PIN B4 +#endif + +#ifndef BLUEFRUIT_LE_IRQ_PIN +# define BLUEFRUIT_LE_IRQ_PIN E6 +#endif + +#ifndef BLUEFRUIT_LE_SCK_DIVISOR +# define BLUEFRUIT_LE_SCK_DIVISOR 2 // 4MHz SCK/8MHz CPU, calculated for Feather 32U4 BLE +#endif + +#define SAMPLE_BATTERY +#define ConnectionUpdateInterval 1000 /* milliseconds */ + +#ifndef BATTERY_LEVEL_PIN +# define BATTERY_LEVEL_PIN B5 +#endif + +static struct { + bool is_connected; + bool initialized; + bool configured; + +#define ProbedEvents 1 +#define UsingEvents 2 + bool event_flags; + +#ifdef SAMPLE_BATTERY + uint16_t last_battery_update; + uint32_t vbat; +#endif + uint16_t last_connection_update; +} state; + +// Commands are encoded using SDEP and sent via SPI +// https://github.com/adafruit/Adafruit_BluefruitLE_nRF51/blob/master/SDEP.md + +#define SdepMaxPayload 16 +struct sdep_msg { + uint8_t type; + uint8_t cmd_low; + uint8_t cmd_high; + struct __attribute__((packed)) { + uint8_t len : 7; + uint8_t more : 1; + }; + uint8_t payload[SdepMaxPayload]; +} __attribute__((packed)); + +// The recv latency is relatively high, so when we're hammering keys quickly, +// we want to avoid waiting for the responses in the matrix loop. We maintain +// a short queue for that. Since there is quite a lot of space overhead for +// the AT command representation wrapped up in SDEP, we queue the minimal +// information here. + +enum queue_type { + QTKeyReport, // 1-byte modifier + 6-byte key report + QTConsumer, // 16-bit key code +#ifdef MOUSE_ENABLE + QTMouseMove, // 4-byte mouse report +#endif +}; + +struct queue_item { + enum queue_type queue_type; + uint16_t added; + union __attribute__((packed)) { + struct __attribute__((packed)) { + uint8_t modifier; + uint8_t keys[6]; + } key; + + uint16_t consumer; + struct __attribute__((packed)) { + int8_t x, y, scroll, pan; + uint8_t buttons; + } mousemove; + }; +}; + +// Items that we wish to send +static RingBuffer send_buf; +// Pending response; while pending, we can't send any more requests. +// This records the time at which we sent the command for which we +// are expecting a response. +static RingBuffer resp_buf; + +static bool process_queue_item(struct queue_item *item, uint16_t timeout); + +enum sdep_type { + SdepCommand = 0x10, + SdepResponse = 0x20, + SdepAlert = 0x40, + SdepError = 0x80, + SdepSlaveNotReady = 0xFE, // Try again later + SdepSlaveOverflow = 0xFF, // You read more data than is available +}; + +enum ble_cmd { + BleInitialize = 0xBEEF, + BleAtWrapper = 0x0A00, + BleUartTx = 0x0A01, + BleUartRx = 0x0A02, +}; + +enum ble_system_event_bits { + BleSystemConnected = 0, + BleSystemDisconnected = 1, + BleSystemUartRx = 8, + BleSystemMidiRx = 10, +}; + +#define SdepTimeout 150 /* milliseconds */ +#define SdepShortTimeout 10 /* milliseconds */ +#define SdepBackOff 25 /* microseconds */ +#define BatteryUpdateInterval 10000 /* milliseconds */ + +static bool at_command(const char *cmd, char *resp, uint16_t resplen, bool verbose, uint16_t timeout = SdepTimeout); +static bool at_command_P(const char *cmd, char *resp, uint16_t resplen, bool verbose = false); + +// Send a single SDEP packet +static bool sdep_send_pkt(const struct sdep_msg *msg, uint16_t timeout) { + spi_start(BLUEFRUIT_LE_CS_PIN, false, 0, BLUEFRUIT_LE_SCK_DIVISOR); + uint16_t timerStart = timer_read(); + bool success = false; + bool ready = false; + + do { + ready = spi_write(msg->type) != SdepSlaveNotReady; + if (ready) { + break; + } + + // Release it and let it initialize + spi_stop(); + wait_us(SdepBackOff); + spi_start(BLUEFRUIT_LE_CS_PIN, false, 0, BLUEFRUIT_LE_SCK_DIVISOR); + } while (timer_elapsed(timerStart) < timeout); + + if (ready) { + // Slave is ready; send the rest of the packet + spi_transmit(&msg->cmd_low, sizeof(*msg) - (1 + sizeof(msg->payload)) + msg->len); + success = true; + } + + spi_stop(); + + return success; +} + +static inline void sdep_build_pkt(struct sdep_msg *msg, uint16_t command, const uint8_t *payload, uint8_t len, bool moredata) { + msg->type = SdepCommand; + msg->cmd_low = command & 0xFF; + msg->cmd_high = command >> 8; + msg->len = len; + msg->more = (moredata && len == SdepMaxPayload) ? 1 : 0; + + static_assert(sizeof(*msg) == 20, "msg is correctly packed"); + + memcpy(msg->payload, payload, len); +} + +// Read a single SDEP packet +static bool sdep_recv_pkt(struct sdep_msg *msg, uint16_t timeout) { + bool success = false; + uint16_t timerStart = timer_read(); + bool ready = false; + + do { + ready = readPin(BLUEFRUIT_LE_IRQ_PIN); + if (ready) { + break; + } + wait_us(1); + } while (timer_elapsed(timerStart) < timeout); + + if (ready) { + spi_start(BLUEFRUIT_LE_CS_PIN, false, 0, BLUEFRUIT_LE_SCK_DIVISOR); + + do { + // Read the command type, waiting for the data to be ready + msg->type = spi_read(); + if (msg->type == SdepSlaveNotReady || msg->type == SdepSlaveOverflow) { + // Release it and let it initialize + spi_stop(); + wait_us(SdepBackOff); + spi_start(BLUEFRUIT_LE_CS_PIN, false, 0, BLUEFRUIT_LE_SCK_DIVISOR); + continue; + } + + // Read the rest of the header + spi_receive(&msg->cmd_low, sizeof(*msg) - (1 + sizeof(msg->payload))); + + // and get the payload if there is any + if (msg->len <= SdepMaxPayload) { + spi_receive(msg->payload, msg->len); + } + success = true; + break; + } while (timer_elapsed(timerStart) < timeout); + + spi_stop(); + } + return success; +} + +static void resp_buf_read_one(bool greedy) { + uint16_t last_send; + if (!resp_buf.peek(last_send)) { + return; + } + + if (readPin(BLUEFRUIT_LE_IRQ_PIN)) { + struct sdep_msg msg; + + again: + if (sdep_recv_pkt(&msg, SdepTimeout)) { + if (!msg.more) { + // We got it; consume this entry + resp_buf.get(last_send); + dprintf("recv latency %dms\n", TIMER_DIFF_16(timer_read(), last_send)); + } + + if (greedy && resp_buf.peek(last_send) && readPin(BLUEFRUIT_LE_IRQ_PIN)) { + goto again; + } + } + + } else if (timer_elapsed(last_send) > SdepTimeout * 2) { + dprintf("waiting_for_result: timeout, resp_buf size %d\n", (int)resp_buf.size()); + + // Timed out: consume this entry + resp_buf.get(last_send); + } +} + +static void send_buf_send_one(uint16_t timeout = SdepTimeout) { + struct queue_item item; + + // Don't send anything more until we get an ACK + if (!resp_buf.empty()) { + return; + } + + if (!send_buf.peek(item)) { + return; + } + if (process_queue_item(&item, timeout)) { + // commit that peek + send_buf.get(item); + dprintf("send_buf_send_one: have %d remaining\n", (int)send_buf.size()); + } else { + dprint("failed to send, will retry\n"); + wait_ms(SdepTimeout); + resp_buf_read_one(true); + } +} + +static void resp_buf_wait(const char *cmd) { + bool didPrint = false; + while (!resp_buf.empty()) { + if (!didPrint) { + dprintf("wait on buf for %s\n", cmd); + didPrint = true; + } + resp_buf_read_one(true); + } +} + +static bool ble_init(void) { + state.initialized = false; + state.configured = false; + state.is_connected = false; + + setPinInput(BLUEFRUIT_LE_IRQ_PIN); + + spi_init(); + + // Perform a hardware reset + setPinOutput(BLUEFRUIT_LE_RST_PIN); + writePinHigh(BLUEFRUIT_LE_RST_PIN); + writePinLow(BLUEFRUIT_LE_RST_PIN); + wait_ms(10); + writePinHigh(BLUEFRUIT_LE_RST_PIN); + + wait_ms(1000); // Give it a second to initialize + + state.initialized = true; + return state.initialized; +} + +static inline uint8_t min(uint8_t a, uint8_t b) { return a < b ? a : b; } + +static bool read_response(char *resp, uint16_t resplen, bool verbose) { + char *dest = resp; + char *end = dest + resplen; + + while (true) { + struct sdep_msg msg; + + if (!sdep_recv_pkt(&msg, 2 * SdepTimeout)) { + dprint("sdep_recv_pkt failed\n"); + return false; + } + + if (msg.type != SdepResponse) { + *resp = 0; + return false; + } + + uint8_t len = min(msg.len, end - dest); + if (len > 0) { + memcpy(dest, msg.payload, len); + dest += len; + } + + if (!msg.more) { + // No more data is expected! + break; + } + } + + // Ensure the response is NUL terminated + *dest = 0; + + // "Parse" the result text; we want to snip off the trailing OK or ERROR line + // Rewind past the possible trailing CRLF so that we can strip it + --dest; + while (dest > resp && (dest[0] == '\n' || dest[0] == '\r')) { + *dest = 0; + --dest; + } + + // Look back for start of preceeding line + char *last_line = strrchr(resp, '\n'); + if (last_line) { + ++last_line; + } else { + last_line = resp; + } + + bool success = false; + static const char kOK[] PROGMEM = "OK"; + + success = !strcmp_P(last_line, kOK); + + if (verbose || !success) { + dprintf("result: %s\n", resp); + } + return success; +} + +static bool at_command(const char *cmd, char *resp, uint16_t resplen, bool verbose, uint16_t timeout) { + const char * end = cmd + strlen(cmd); + struct sdep_msg msg; + + if (verbose) { + dprintf("ble send: %s\n", cmd); + } + + if (resp) { + // They want to decode the response, so we need to flush and wait + // for all pending I/O to finish before we start this one, so + // that we don't confuse the results + resp_buf_wait(cmd); + *resp = 0; + } + + // Fragment the command into a series of SDEP packets + while (end - cmd > SdepMaxPayload) { + sdep_build_pkt(&msg, BleAtWrapper, (uint8_t *)cmd, SdepMaxPayload, true); + if (!sdep_send_pkt(&msg, timeout)) { + return false; + } + cmd += SdepMaxPayload; + } + + sdep_build_pkt(&msg, BleAtWrapper, (uint8_t *)cmd, end - cmd, false); + if (!sdep_send_pkt(&msg, timeout)) { + return false; + } + + if (resp == NULL) { + uint16_t now = timer_read(); + while (!resp_buf.enqueue(now)) { + resp_buf_read_one(false); + } + uint16_t later = timer_read(); + if (TIMER_DIFF_16(later, now) > 0) { + dprintf("waited %dms for resp_buf\n", TIMER_DIFF_16(later, now)); + } + return true; + } + + return read_response(resp, resplen, verbose); +} + +bool at_command_P(const char *cmd, char *resp, uint16_t resplen, bool verbose) { + char *cmdbuf = (char *)alloca(strlen_P(cmd) + 1); + strcpy_P(cmdbuf, cmd); + return at_command(cmdbuf, resp, resplen, verbose); +} + +bool bluefruit_le_is_connected(void) { return state.is_connected; } + +bool bluefruit_le_enable_keyboard(void) { + char resbuf[128]; + + if (!state.initialized && !ble_init()) { + return false; + } + + state.configured = false; + + // Disable command echo + static const char kEcho[] PROGMEM = "ATE=0"; + // Make the advertised name match the keyboard + static const char kGapDevName[] PROGMEM = "AT+GAPDEVNAME=" STR(PRODUCT); + // Turn on keyboard support + static const char kHidEnOn[] PROGMEM = "AT+BLEHIDEN=1"; + + // Adjust intervals to improve latency. This causes the "central" + // system (computer/tablet) to poll us every 10-30 ms. We can't + // set a smaller value than 10ms, and 30ms seems to be the natural + // processing time on my macbook. Keeping it constrained to that + // feels reasonable to type to. + static const char kGapIntervals[] PROGMEM = "AT+GAPINTERVALS=10,30,,"; + + // Reset the device so that it picks up the above changes + static const char kATZ[] PROGMEM = "ATZ"; + + // Turn down the power level a bit + static const char kPower[] PROGMEM = "AT+BLEPOWERLEVEL=-12"; + static PGM_P const configure_commands[] PROGMEM = { + kEcho, kGapIntervals, kGapDevName, kHidEnOn, kPower, kATZ, + }; + + uint8_t i; + for (i = 0; i < sizeof(configure_commands) / sizeof(configure_commands[0]); ++i) { + PGM_P cmd; + memcpy_P(&cmd, configure_commands + i, sizeof(cmd)); + + if (!at_command_P(cmd, resbuf, sizeof(resbuf))) { + dprintf("failed BLE command: %S: %s\n", cmd, resbuf); + goto fail; + } + } + + state.configured = true; + + // Check connection status in a little while; allow the ATZ time + // to kick in. + state.last_connection_update = timer_read(); +fail: + return state.configured; +} + +static void set_connected(bool connected) { + if (connected != state.is_connected) { + if (connected) { + dprint("BLE connected\n"); + } else { + dprint("BLE disconnected\n"); + } + state.is_connected = connected; + + // TODO: if modifiers are down on the USB interface and + // we cut over to BLE or vice versa, they will remain stuck. + // This feels like a good point to do something like clearing + // the keyboard and/or generating a fake all keys up message. + // However, I've noticed that it takes a couple of seconds + // for macOS to to start recognizing key presses after BLE + // is in the connected state, so I worry that doing that + // here may not be good enough. + } +} + +void bluefruit_le_task(void) { + char resbuf[48]; + + if (!state.configured && !bluefruit_le_enable_keyboard()) { + return; + } + resp_buf_read_one(true); + send_buf_send_one(SdepShortTimeout); + + if (resp_buf.empty() && (state.event_flags & UsingEvents) && readPin(BLUEFRUIT_LE_IRQ_PIN)) { + // Must be an event update + if (at_command_P(PSTR("AT+EVENTSTATUS"), resbuf, sizeof(resbuf))) { + uint32_t mask = strtoul(resbuf, NULL, 16); + + if (mask & BleSystemConnected) { + set_connected(true); + } else if (mask & BleSystemDisconnected) { + set_connected(false); + } + } + } + + if (timer_elapsed(state.last_connection_update) > ConnectionUpdateInterval) { + bool shouldPoll = true; + if (!(state.event_flags & ProbedEvents)) { + // Request notifications about connection status changes. + // This only works in SPIFRIEND firmware > 0.6.7, which is why + // we check for this conditionally here. + // Note that at the time of writing, HID reports only work correctly + // with Apple products on firmware version 0.6.7! + // https://forums.adafruit.com/viewtopic.php?f=8&t=104052 + if (at_command_P(PSTR("AT+EVENTENABLE=0x1"), resbuf, sizeof(resbuf))) { + at_command_P(PSTR("AT+EVENTENABLE=0x2"), resbuf, sizeof(resbuf)); + state.event_flags |= UsingEvents; + } + state.event_flags |= ProbedEvents; + + // leave shouldPoll == true so that we check at least once + // before relying solely on events + } else { + shouldPoll = false; + } + + static const char kGetConn[] PROGMEM = "AT+GAPGETCONN"; + state.last_connection_update = timer_read(); + + if (at_command_P(kGetConn, resbuf, sizeof(resbuf))) { + set_connected(atoi(resbuf)); + } + } + +#ifdef SAMPLE_BATTERY + if (timer_elapsed(state.last_battery_update) > BatteryUpdateInterval && resp_buf.empty()) { + state.last_battery_update = timer_read(); + + state.vbat = analogReadPin(BATTERY_LEVEL_PIN); + } +#endif +} + +static bool process_queue_item(struct queue_item *item, uint16_t timeout) { + char cmdbuf[48]; + char fmtbuf[64]; + + // Arrange to re-check connection after keys have settled + state.last_connection_update = timer_read(); + +#if 1 + if (TIMER_DIFF_16(state.last_connection_update, item->added) > 0) { + dprintf("send latency %dms\n", TIMER_DIFF_16(state.last_connection_update, item->added)); + } +#endif + + switch (item->queue_type) { + case QTKeyReport: + strcpy_P(fmtbuf, PSTR("AT+BLEKEYBOARDCODE=%02x-00-%02x-%02x-%02x-%02x-%02x-%02x")); + snprintf(cmdbuf, sizeof(cmdbuf), fmtbuf, item->key.modifier, item->key.keys[0], item->key.keys[1], item->key.keys[2], item->key.keys[3], item->key.keys[4], item->key.keys[5]); + return at_command(cmdbuf, NULL, 0, true, timeout); + + case QTConsumer: + strcpy_P(fmtbuf, PSTR("AT+BLEHIDCONTROLKEY=0x%04x")); + snprintf(cmdbuf, sizeof(cmdbuf), fmtbuf, item->consumer); + return at_command(cmdbuf, NULL, 0, true, timeout); + +#ifdef MOUSE_ENABLE + case QTMouseMove: + strcpy_P(fmtbuf, PSTR("AT+BLEHIDMOUSEMOVE=%d,%d,%d,%d")); + snprintf(cmdbuf, sizeof(cmdbuf), fmtbuf, item->mousemove.x, item->mousemove.y, item->mousemove.scroll, item->mousemove.pan); + if (!at_command(cmdbuf, NULL, 0, true, timeout)) { + return false; + } + strcpy_P(cmdbuf, PSTR("AT+BLEHIDMOUSEBUTTON=")); + if (item->mousemove.buttons & MOUSE_BTN1) { + strcat(cmdbuf, "L"); + } + if (item->mousemove.buttons & MOUSE_BTN2) { + strcat(cmdbuf, "R"); + } + if (item->mousemove.buttons & MOUSE_BTN3) { + strcat(cmdbuf, "M"); + } + if (item->mousemove.buttons == 0) { + strcat(cmdbuf, "0"); + } + return at_command(cmdbuf, NULL, 0, true, timeout); +#endif + default: + return true; + } +} + +void bluefruit_le_send_keys(uint8_t hid_modifier_mask, uint8_t *keys, uint8_t nkeys) { + struct queue_item item; + bool didWait = false; + + item.queue_type = QTKeyReport; + item.key.modifier = hid_modifier_mask; + item.added = timer_read(); + + while (nkeys >= 0) { + item.key.keys[0] = keys[0]; + item.key.keys[1] = nkeys >= 1 ? keys[1] : 0; + item.key.keys[2] = nkeys >= 2 ? keys[2] : 0; + item.key.keys[3] = nkeys >= 3 ? keys[3] : 0; + item.key.keys[4] = nkeys >= 4 ? keys[4] : 0; + item.key.keys[5] = nkeys >= 5 ? keys[5] : 0; + + if (!send_buf.enqueue(item)) { + if (!didWait) { + dprint("wait for buf space\n"); + didWait = true; + } + send_buf_send_one(); + continue; + } + + if (nkeys <= 6) { + return; + } + + nkeys -= 6; + keys += 6; + } +} + +void bluefruit_le_send_consumer_key(uint16_t usage) { + struct queue_item item; + + item.queue_type = QTConsumer; + item.consumer = usage; + + while (!send_buf.enqueue(item)) { + send_buf_send_one(); + } +} + +#ifdef MOUSE_ENABLE +void bluefruit_le_send_mouse_move(int8_t x, int8_t y, int8_t scroll, int8_t pan, uint8_t buttons) { + struct queue_item item; + + item.queue_type = QTMouseMove; + item.mousemove.x = x; + item.mousemove.y = y; + item.mousemove.scroll = scroll; + item.mousemove.pan = pan; + item.mousemove.buttons = buttons; + + while (!send_buf.enqueue(item)) { + send_buf_send_one(); + } +} +#endif + +uint32_t bluefruit_le_read_battery_voltage(void) { return state.vbat; } + +bool bluefruit_le_set_mode_leds(bool on) { + if (!state.configured) { + return false; + } + + // The "mode" led is the red blinky one + at_command_P(on ? PSTR("AT+HWMODELED=1") : PSTR("AT+HWMODELED=0"), NULL, 0); + + // Pin 19 is the blue "connected" LED; turn that off too. + // When turning LEDs back on, don't turn that LED on if we're + // not connected, as that would be confusing. + at_command_P(on && state.is_connected ? PSTR("AT+HWGPIO=19,1") : PSTR("AT+HWGPIO=19,0"), NULL, 0); + return true; +} + +// https://learn.adafruit.com/adafruit-feather-32u4-bluefruit-le/ble-generic#at-plus-blepowerlevel +bool bluefruit_le_set_power_level(int8_t level) { + char cmd[46]; + if (!state.configured) { + return false; + } + snprintf(cmd, sizeof(cmd), "AT+BLEPOWERLEVEL=%d", level); + return at_command(cmd, NULL, 0, false); +} diff --git a/drivers/bluetooth/bluefruit_le.h b/drivers/bluetooth/bluefruit_le.h new file mode 100644 index 0000000000..de301c6167 --- /dev/null +++ b/drivers/bluetooth/bluefruit_le.h @@ -0,0 +1,59 @@ +/* Bluetooth Low Energy Protocol for QMK. + * Author: Wez Furlong, 2016 + * Supports the Adafruit BLE board built around the nRF51822 chip. + */ + +#pragma once + +#include +#include +#include + +#include "config_common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Instruct the module to enable HID keyboard support and reset */ +extern bool bluefruit_le_enable_keyboard(void); + +/* Query to see if the BLE module is connected */ +extern bool bluefruit_le_query_is_connected(void); + +/* Returns true if we believe that the BLE module is connected. + * This uses our cached understanding that is maintained by + * calling ble_task() periodically. */ +extern bool bluefruit_le_is_connected(void); + +/* Call this periodically to process BLE-originated things */ +extern void bluefruit_le_task(void); + +/* Generates keypress events for a set of keys. + * The hid modifier mask specifies the state of the modifier keys for + * this set of keys. + * Also sends a key release indicator, so that the keys do not remain + * held down. */ +extern void bluefruit_le_send_keys(uint8_t hid_modifier_mask, uint8_t *keys, uint8_t nkeys); + +/* Send a consumer usage. + * (milliseconds) */ +extern void bluefruit_le_send_consumer_key(uint16_t usage); + +#ifdef MOUSE_ENABLE +/* Send a mouse/wheel movement report. + * The parameters are signed and indicate positive or negative direction + * change. */ +extern void bluefruit_le_send_mouse_move(int8_t x, int8_t y, int8_t scroll, int8_t pan, uint8_t buttons); +#endif + +/* Compute battery voltage by reading an analog pin. + * Returns the integer number of millivolts */ +extern uint32_t bluefruit_le_read_battery_voltage(void); + +extern bool bluefruit_le_set_mode_leds(bool on); +extern bool bluefruit_le_set_power_level(int8_t level); + +#ifdef __cplusplus +} +#endif diff --git a/drivers/bluetooth/outputselect.c b/drivers/bluetooth/outputselect.c index f758c65280..cdd2e64cfd 100644 --- a/drivers/bluetooth/outputselect.c +++ b/drivers/bluetooth/outputselect.c @@ -18,8 +18,8 @@ along with this program. If not, see . # include "lufa.h" #endif -#ifdef MODULE_ADAFRUIT_BLE -# include "adafruit_ble.h" +#ifdef BLUETOOTH_BLUEFRUIT_LE +# include "bluefruit_le.h" #endif uint8_t desired_output = OUTPUT_DEFAULT; @@ -54,8 +54,8 @@ uint8_t auto_detect_output(void) { return OUTPUT_USB; } -#ifdef MODULE_ADAFRUIT_BLE - if (adafruit_ble_is_connected()) { +#ifdef BLUETOOTH_BLUEFRUIT_LE + if (bluefruit_le_is_connected()) { return OUTPUT_BLUETOOTH; } #endif diff --git a/keyboards/10bleoledhub/rules.mk b/keyboards/10bleoledhub/rules.mk index 33170c43a2..bd10e11022 100644 --- a/keyboards/10bleoledhub/rules.mk +++ b/keyboards/10bleoledhub/rules.mk @@ -20,7 +20,7 @@ BACKLIGHT_ENABLE = no # Enable keyboard backlight functionality RGBLIGHT_ENABLE = no # Enable keyboard RGB underglow AUDIO_ENABLE = no # Audio output BLUETOOTH_ENABLE = yes -BLUETOOTH_DRIVER = AdafruitBLE +BLUETOOTH_DRIVER = BluefruitLE OLED_ENABLE = yes OLED_DRIVER = SSD1306 ENCODER_ENABLE = yes diff --git a/keyboards/40percentclub/mf68/keymaps/mf68_ble/rules.mk b/keyboards/40percentclub/mf68/keymaps/mf68_ble/rules.mk index 160b3684b3..975c581883 100644 --- a/keyboards/40percentclub/mf68/keymaps/mf68_ble/rules.mk +++ b/keyboards/40percentclub/mf68/keymaps/mf68_ble/rules.mk @@ -5,5 +5,5 @@ F_CPU = 8000000 # change yes to no to disable # BLUETOOTH_ENABLE = yes -BLUETOOTH_DRIVER = AdafruitBLE +BLUETOOTH_DRIVER = BluefruitLE BACKLIGHT_ENABLE = no diff --git a/keyboards/atreus/feather/rules.mk b/keyboards/atreus/feather/rules.mk index ad0b4a5046..6280275c1e 100644 --- a/keyboards/atreus/feather/rules.mk +++ b/keyboards/atreus/feather/rules.mk @@ -11,5 +11,5 @@ BOOTLOADER = caterina # change yes to no to disable # BLUETOOTH_ENABLE = yes -BLUETOOTH_DRIVER = AdafruitBLE +BLUETOOTH_DRIVER = BluefruitLE CONSOLE_ENABLE = no diff --git a/keyboards/bioi/main.c b/keyboards/bioi/main.c index 7c8922c0a0..54caf6937b 100644 --- a/keyboards/bioi/main.c +++ b/keyboards/bioi/main.c @@ -45,8 +45,8 @@ extern keymap_config_t keymap_config; #endif #ifdef BLUETOOTH_ENABLE -#ifdef MODULE_ADAFRUIT_BLE -#include "adafruit_ble.h" +#ifdef BLUETOOTH_BLUEFRUIT_LE +#include "bluefruit_le.h" #else #include "bluetooth.h" #endif @@ -319,7 +319,7 @@ int main(void) setup_usb(); sei(); -#if defined(MODULE_ADAFRUIT_EZKEY) || defined(MODULE_RN42) +#if defined(BLUETOOTH_RN42) serial_init(); #endif diff --git a/keyboards/converter/m0110_usb/readme.md b/keyboards/converter/m0110_usb/readme.md index a148091cd9..d066aa5a7c 100644 --- a/keyboards/converter/m0110_usb/readme.md +++ b/keyboards/converter/m0110_usb/readme.md @@ -5,7 +5,7 @@ This is a port of the original M0110 converter from TMK to QMK. The original con ## Enabling Bluetooth for the Adafruit Feather 32U4 BLE -Simply add `BLUETOOTH = AdafruitBLE` to your `rules.mk` file. This enables code specifically for the Adafruit Feather 32U4 BLE. If enabled, the device will use the `PRODUCT` value from `config.h` for the device name displayed by the Feather on host devices. You can simply change these values to change the device name. +Simply add `BLUETOOTH = BluefruitLE` to your `rules.mk` file. This enables code specifically for the Adafruit Feather 32U4 BLE. If enabled, the device will use the `PRODUCT` value from `config.h` for the device name displayed by the Feather on host devices. You can simply change these values to change the device name. ## Pins diff --git a/keyboards/converter/usb_usb/ble/config.h b/keyboards/converter/usb_usb/ble/config.h index 80e4968c41..92df24d003 100644 --- a/keyboards/converter/usb_usb/ble/config.h +++ b/keyboards/converter/usb_usb/ble/config.h @@ -4,8 +4,8 @@ #define PRODUCT QMK BLE Adapter // Turn off the mode leds on the BLE module -#define ADAFRUIT_BLE_ENABLE_MODE_LEDS 0 -#define ADAFRUIT_BLE_ENABLE_POWER_LED 0 +#define BLUEFRUIT_LE_ENABLE_MODE_LEDS 0 +#define BLUEFRUIT_LE_ENABLE_POWER_LED 0 #define NO_ACTION_MACRO #define NO_ACTION_FUNCTION diff --git a/keyboards/converter/usb_usb/ble/rules.mk b/keyboards/converter/usb_usb/ble/rules.mk index 596f719630..df5bc36635 100644 --- a/keyboards/converter/usb_usb/ble/rules.mk +++ b/keyboards/converter/usb_usb/ble/rules.mk @@ -12,6 +12,6 @@ RGBLIGHT_ENABLE = no # Enable keyboard RGB underglow AUDIO_ENABLE = no # Audio output BLUETOOTH_ENABLE = yes -BLUETOOTH_DRIVER = AdafruitBLE +BLUETOOTH_DRIVER = BluefruitLE LTO_ENABLE = yes diff --git a/keyboards/dtisaac/dosa40rgb/readme.md b/keyboards/dtisaac/dosa40rgb/readme.md index 5f288c6b76..a2995a5c0f 100644 --- a/keyboards/dtisaac/dosa40rgb/readme.md +++ b/keyboards/dtisaac/dosa40rgb/readme.md @@ -5,7 +5,7 @@ A 40 percent keyboard dosa40rgb Bluetooth Low Energy * Keyboard Maintainer: [DTIsaac](https://github.com/daotakisaac) -* Hardware Supported: ATmega32U4 + AdafruitBLE SPI +* Hardware Supported: ATmega32U4 + Bluefruit LE SPI Make example for this keyboard (after setting up your build environment): diff --git a/keyboards/dtisaac/dosa40rgb/rules.mk b/keyboards/dtisaac/dosa40rgb/rules.mk index 91d3c94ba5..caecba365e 100644 --- a/keyboards/dtisaac/dosa40rgb/rules.mk +++ b/keyboards/dtisaac/dosa40rgb/rules.mk @@ -17,7 +17,7 @@ BACKLIGHT_ENABLE = no # Enable keyboard backlight functionality RGBLIGHT_ENABLE = no # Enable keyboard RGB underglow AUDIO_ENABLE = no # Audio output BLUETOOTH_ENABLE = yes # Enable Bluetooth -BLUETOOTH_DRIVER = AdafruitBLE +BLUETOOTH_DRIVER = BluefruitLE RGB_MATRIX_ENABLE = yes RGB_MATRIX_DRIVER = WS2812 diff --git a/keyboards/handwired/42/rules.mk b/keyboards/handwired/42/rules.mk index 5742e84bf5..655ce00bef 100644 --- a/keyboards/handwired/42/rules.mk +++ b/keyboards/handwired/42/rules.mk @@ -21,4 +21,4 @@ RGBLIGHT_ENABLE = no # Enable keyboard RGB underglow AUDIO_ENABLE = no # Audio output BLUETOOTH_ENABLE = yes -BLUETOOTH_DRIVER = AdafruitBLE +BLUETOOTH_DRIVER = BluefruitLE diff --git a/keyboards/handwired/bdn9_ble/rules.mk b/keyboards/handwired/bdn9_ble/rules.mk index e5abbcfb88..a3082cf364 100644 --- a/keyboards/handwired/bdn9_ble/rules.mk +++ b/keyboards/handwired/bdn9_ble/rules.mk @@ -21,4 +21,4 @@ RGBLIGHT_ENABLE = no # Enable keyboard RGB underglow AUDIO_ENABLE = no # Audio output ENCODER_ENABLE = no BLUETOOTH_ENABLE = yes -BLUETOOTH_DRIVER = AdafruitBLE +BLUETOOTH_DRIVER = BluefruitLE diff --git a/keyboards/handwired/fruity60/rules.mk b/keyboards/handwired/fruity60/rules.mk index 74628e3e6f..c84f68f0bf 100644 --- a/keyboards/handwired/fruity60/rules.mk +++ b/keyboards/handwired/fruity60/rules.mk @@ -20,6 +20,6 @@ BACKLIGHT_ENABLE = no # Enable keyboard backlight functionality RGBLIGHT_ENABLE = no # Enable keyboard RGB underglow AUDIO_ENABLE = no # Audio output BLUETOOTH_ENABLE = yes -BLUETOOTH_DRIVER = AdafruitBLE +BLUETOOTH_DRIVER = BluefruitLE LAYOUTS = 60_tsangan_hhkb diff --git a/keyboards/handwired/prkl30/feather/rules.mk b/keyboards/handwired/prkl30/feather/rules.mk index f83173fa0b..7578dcef74 100644 --- a/keyboards/handwired/prkl30/feather/rules.mk +++ b/keyboards/handwired/prkl30/feather/rules.mk @@ -22,4 +22,4 @@ AUDIO_ENABLE = no # This can be enabled if a speaker is connected to t RGBLIGHT_ENABLE = no # This can be enabled if a ws2812 strip is connected to the expansion port. BLUETOOTH_ENABLE = yes -BLUETOOTH_DRIVER = AdafruitBLE +BLUETOOTH_DRIVER = BluefruitLE diff --git a/keyboards/handwired/promethium/keymaps/default/keymap.c b/keyboards/handwired/promethium/keymaps/default/keymap.c index 14a293bb39..e12142c50b 100644 --- a/keyboards/handwired/promethium/keymaps/default/keymap.c +++ b/keyboards/handwired/promethium/keymaps/default/keymap.c @@ -137,7 +137,7 @@ enum planck_keycodes { #ifndef FAUXCLICKY_ENABLE FC_TOG, #endif -#ifndef MODULE_ADAFRUIT_BLE +#ifndef BLUETOOTH_BLUEFRUIT_LE OUT_BT, #endif RGBDEMO, @@ -1261,7 +1261,7 @@ bool process_record_user(uint16_t keycode, keyrecord_t *record) { } void set_output_user(uint8_t output) { -#ifdef MODULE_ADAFRUIT_BLE +#ifdef BLUETOOTH_BLUEFRUIT_LE switch(output) { case OUTPUT_USB: led_set_output_usb(); @@ -1285,7 +1285,7 @@ void matrix_init_user() { #endif // auto detect output on init -#ifdef MODULE_ADAFRUIT_BLE +#ifdef BLUETOOTH_BLUEFRUIT_LE uint8_t output = auto_detect_output(); if (output == OUTPUT_USB) { set_output(OUTPUT_USB); diff --git a/keyboards/handwired/promethium/keymaps/priyadi/keymap.c b/keyboards/handwired/promethium/keymaps/priyadi/keymap.c index 094eb15762..94b505aa2c 100644 --- a/keyboards/handwired/promethium/keymaps/priyadi/keymap.c +++ b/keyboards/handwired/promethium/keymaps/priyadi/keymap.c @@ -140,7 +140,7 @@ enum planck_keycodes { #ifndef FAUXCLICKY_ENABLE FC_TOG, #endif -#ifndef MODULE_ADAFRUIT_BLE +#ifndef BLUETOOTH_BLUEFRUIT_LE OUT_BT, #endif RGBDEMO, @@ -1264,7 +1264,7 @@ bool process_record_user(uint16_t keycode, keyrecord_t *record) { }