aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWez Furlong <wez@fb.com>2016-11-27 22:48:04 -0800
committerWez Furlong <wez@fb.com>2016-11-27 23:49:44 -0800
commit712476cd288505cabb2ad6163d1c1ba13a7a1cca (patch)
tree92c1ddd43fe1d86940d4a94dc545fabd01904f2a
parent8485bb34d2e291db5b6c81f892850da1cdca37ba (diff)
downloadqmk_firmware-712476cd288505cabb2ad6163d1c1ba13a7a1cca.tar.gz
qmk_firmware-712476cd288505cabb2ad6163d1c1ba13a7a1cca.zip
Add support for Adafruit BLE modules
This implements some helper functions that allow sending key reports to an SPI based Bluetooth Low Energy module, such as the Adafruit Feather 32u4 Bluefruit LE. There is some plumbing required in lufa.c to enable this; that is in a follow-on commit.
-rw-r--r--tmk_core/common.mk6
-rw-r--r--tmk_core/protocol/lufa.mk4
-rw-r--r--tmk_core/protocol/lufa/adafruit_ble.cpp805
-rw-r--r--tmk_core/protocol/lufa/adafruit_ble.h60
-rw-r--r--tmk_core/protocol/lufa/ringbuffer.hpp66
5 files changed, 940 insertions, 1 deletions
diff --git a/tmk_core/common.mk b/tmk_core/common.mk
index f826a7b54..c32a12bb6 100644
--- a/tmk_core/common.mk
+++ b/tmk_core/common.mk
@@ -81,6 +81,10 @@ ifeq ($(strip $(BACKLIGHT_ENABLE)), yes)
81 TMK_COMMON_DEFS += -DBACKLIGHT_ENABLE 81 TMK_COMMON_DEFS += -DBACKLIGHT_ENABLE
82endif 82endif
83 83
84ifeq ($(strip $(ADAFRUIT_BLE_ENABLE)), yes)
85 TMK_COMMON_DEFS += -DADAFRUIT_BLE_ENABLE
86endif
87
84ifeq ($(strip $(BLUETOOTH_ENABLE)), yes) 88ifeq ($(strip $(BLUETOOTH_ENABLE)), yes)
85 TMK_COMMON_DEFS += -DBLUETOOTH_ENABLE 89 TMK_COMMON_DEFS += -DBLUETOOTH_ENABLE
86endif 90endif
@@ -110,4 +114,4 @@ endif
110VPATH += $(TMK_PATH)/$(COMMON_DIR) 114VPATH += $(TMK_PATH)/$(COMMON_DIR)
111ifeq ($(PLATFORM),CHIBIOS) 115ifeq ($(PLATFORM),CHIBIOS)
112VPATH += $(TMK_PATH)/$(COMMON_DIR)/chibios 116VPATH += $(TMK_PATH)/$(COMMON_DIR)/chibios
113endif \ No newline at end of file 117endif
diff --git a/tmk_core/protocol/lufa.mk b/tmk_core/protocol/lufa.mk
index 5b1e3d19d..151d26cbc 100644
--- a/tmk_core/protocol/lufa.mk
+++ b/tmk_core/protocol/lufa.mk
@@ -21,6 +21,10 @@ ifeq ($(strip $(MIDI_ENABLE)), yes)
21 include $(TMK_PATH)/protocol/midi.mk 21 include $(TMK_PATH)/protocol/midi.mk
22endif 22endif
23 23
24ifeq ($(strip $(ADAFRUIT_BLE_ENABLE)), yes)
25 LUFA_SRC += $(LUFA_DIR)/adafruit_ble.cpp
26endif
27
24ifeq ($(strip $(BLUETOOTH_ENABLE)), yes) 28ifeq ($(strip $(BLUETOOTH_ENABLE)), yes)
25 LUFA_SRC += $(LUFA_DIR)/bluetooth.c \ 29 LUFA_SRC += $(LUFA_DIR)/bluetooth.c \
26 $(TMK_DIR)/protocol/serial_uart.c 30 $(TMK_DIR)/protocol/serial_uart.c
diff --git a/tmk_core/protocol/lufa/adafruit_ble.cpp b/tmk_core/protocol/lufa/adafruit_ble.cpp
new file mode 100644
index 000000000..37194e77a
--- /dev/null
+++ b/tmk_core/protocol/lufa/adafruit_ble.cpp
@@ -0,0 +1,805 @@
1#include "adafruit_ble.h"
2#include <stdio.h>
3#include <stdlib.h>
4#include <alloca.h>
5#include <util/delay.h>
6#include <util/atomic.h>
7#include "debug.h"
8#include "pincontrol.h"
9#include "timer.h"
10#include "action_util.h"
11#include "ringbuffer.hpp"
12#include <string.h>
13
14// These are the pin assignments for the 32u4 boards.
15// You may define them to something else in your config.h
16// if yours is wired up differently.
17#ifndef AdafruitBleResetPin
18#define AdafruitBleResetPin D4
19#endif
20
21#ifndef AdafruitBleCSPin
22#define AdafruitBleCSPin B4
23#endif
24
25#ifndef AdafruitBleIRQPin
26#define AdafruitBleIRQPin E6
27#endif
28
29
30#define SAMPLE_BATTERY
31#define ConnectionUpdateInterval 1000 /* milliseconds */
32
33static struct {
34 bool is_connected;
35 bool initialized;
36 bool configured;
37
38#define ProbedEvents 1
39#define UsingEvents 2
40 bool event_flags;
41
42#ifdef SAMPLE_BATTERY
43 uint16_t last_battery_update;
44 uint32_t vbat;
45#endif
46 uint16_t last_connection_update;
47} state;
48
49// Commands are encoded using SDEP and sent via SPI
50// https://github.com/adafruit/Adafruit_BluefruitLE_nRF51/blob/master/SDEP.md
51
52#define SdepMaxPayload 16
53struct sdep_msg {
54 uint8_t type;
55 uint8_t cmd_low;
56 uint8_t cmd_high;
57 struct __attribute__((packed)) {
58 uint8_t len:7;
59 uint8_t more:1;
60 };
61 uint8_t payload[SdepMaxPayload];
62} __attribute__((packed));
63
64// The recv latency is relatively high, so when we're hammering keys quickly,
65// we want to avoid waiting for the responses in the matrix loop. We maintain
66// a short queue for that. Since there is quite a lot of space overhead for
67// the AT command representation wrapped up in SDEP, we queue the minimal
68// information here.
69
70enum queue_type {
71 QTKeyReport, // 1-byte modifier + 6-byte key report
72 QTConsumer, // 16-bit key code
73#ifdef MOUSE_ENABLE
74 QTMouseMove, // 4-byte mouse report
75#endif
76};
77
78struct queue_item {
79 enum queue_type queue_type;
80 uint16_t added;
81 union __attribute__((packed)) {
82 struct __attribute__((packed)) {
83 uint8_t modifier;
84 uint8_t keys[6];
85 } key;
86
87 uint16_t consumer;
88 struct __attribute__((packed)) {
89 uint8_t x, y, scroll, pan;
90 } mousemove;
91 };
92};
93
94// Items that we wish to send
95static RingBuffer<queue_item, 40> send_buf;
96// Pending response; while pending, we can't send any more requests.
97// This records the time at which we sent the command for which we
98// are expecting a response.
99static RingBuffer<uint16_t, 2> resp_buf;
100
101static bool process_queue_item(struct queue_item *item, uint16_t timeout);
102
103enum sdep_type {
104 SdepCommand = 0x10,
105 SdepResponse = 0x20,
106 SdepAlert = 0x40,
107 SdepError = 0x80,
108 SdepSlaveNotReady = 0xfe, // Try again later
109 SdepSlaveOverflow = 0xff, // You read more data than is available
110};
111
112enum ble_cmd {
113 BleInitialize = 0xbeef,
114 BleAtWrapper = 0x0a00,
115 BleUartTx = 0x0a01,
116 BleUartRx = 0x0a02,
117};
118
119enum ble_system_event_bits {
120 BleSystemConnected = 0,
121 BleSystemDisconnected = 1,
122 BleSystemUartRx = 8,
123 BleSystemMidiRx = 10,
124};
125
126// The SDEP.md file says 2MHz but the web page and the sample driver
127// both use 4MHz
128#define SpiBusSpeed 4000000
129
130#define SdepTimeout 150 /* milliseconds */
131#define SdepShortTimeout 10 /* milliseconds */
132#define SdepBackOff 25 /* microseconds */
133#define BatteryUpdateInterval 10000 /* milliseconds */
134
135static bool at_command(const char *cmd, char *resp, uint16_t resplen,
136 bool verbose, uint16_t timeout = SdepTimeout);
137static bool at_command_P(const char *cmd, char *resp, uint16_t resplen,
138 bool verbose = false);
139
140struct SPI_Settings {
141 uint8_t spcr, spsr;
142};
143
144static struct SPI_Settings spi;
145
146// Initialize 4Mhz MSBFIRST MODE0
147void SPI_init(struct SPI_Settings *spi) {
148 spi->spcr = _BV(SPE) | _BV(MSTR);
149 spi->spsr = _BV(SPI2X);
150
151 static_assert(SpiBusSpeed == F_CPU / 2, "hard coded at 4Mhz");
152
153 ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
154 // Ensure that SS is OUTPUT High
155 digitalWrite(B0, PinLevelHigh);
156 pinMode(B0, PinDirectionOutput);
157
158 SPCR |= _BV(MSTR);
159 SPCR |= _BV(SPE);
160 pinMode(B1 /* SCK */, PinDirectionOutput);
161 pinMode(B2 /* MOSI */, PinDirectionOutput);
162 }
163}
164
165static inline void SPI_begin(struct SPI_Settings*spi) {
166 SPCR = spi->spcr;
167 SPSR = spi->spsr;
168}
169
170static inline uint8_t SPI_TransferByte(uint8_t data) {
171 SPDR = data;
172 asm volatile("nop");
173 while (!(SPSR & _BV(SPIF))) {
174 ; // wait
175 }
176 return SPDR;
177}
178
179static inline void spi_send_bytes(const uint8_t *buf, uint8_t len) {
180 if (len == 0) return;
181 const uint8_t *end = buf + len;
182 while (buf < end) {
183 SPDR = *buf;
184 while (!(SPSR & _BV(SPIF))) {
185 ; // wait
186 }
187 ++buf;
188 }
189}
190
191static inline uint16_t spi_read_byte(void) {
192 return SPI_TransferByte(0x00 /* dummy */);
193}
194
195static inline void spi_recv_bytes(uint8_t *buf, uint8_t len) {
196 const uint8_t *end = buf + len;
197 if (len == 0) return;
198 while (buf < end) {
199 SPDR = 0; // write a dummy to initiate read
200 while (!(SPSR & _BV(SPIF))) {
201 ; // wait
202 }
203 *buf = SPDR;
204 ++buf;
205 }
206}
207
208#if 0
209static void dump_pkt(const struct sdep_msg *msg) {
210 print("pkt: type=");
211 print_hex8(msg->type);
212 print(" cmd=");
213 print_hex8(msg->cmd_high);
214 print_hex8(msg->cmd_low);
215 print(" len=");
216 print_hex8(msg->len);
217 print(" more=");
218 print_hex8(msg->more);
219 print("\n");
220}
221#endif
222
223// Send a single SDEP packet
224static bool sdep_send_pkt(const struct sdep_msg *msg, uint16_t timeout) {
225 SPI_begin(&spi);
226
227 digitalWrite(AdafruitBleCSPin, PinLevelLow);
228 uint16_t timerStart = timer_read();
229 bool success = false;
230 bool ready = false;
231
232 do {
233 ready = SPI_TransferByte(msg->type) != SdepSlaveNotReady;
234 if (ready) {
235 break;
236 }
237
238 // Release it and let it initialize
239 digitalWrite(AdafruitBleCSPin, PinLevelHigh);
240 _delay_us(SdepBackOff);
241 digitalWrite(AdafruitBleCSPin, PinLevelLow);
242 } while (timer_elapsed(timerStart) < timeout);
243
244 if (ready) {
245 // Slave is ready; send the rest of the packet
246 spi_send_bytes(&msg->cmd_low,
247 sizeof(*msg) - (1 + sizeof(msg->payload)) + msg->len);
248 success = true;
249 }
250
251 digitalWrite(AdafruitBleCSPin, PinLevelHigh);
252
253 return success;
254}
255
256static inline void sdep_build_pkt(struct sdep_msg *msg, uint16_t command,
257 const uint8_t *payload, uint8_t len,
258 bool moredata) {
259 msg->type = SdepCommand;
260 msg->cmd_low = command & 0xff;
261 msg->cmd_high = command >> 8;
262 msg->len = len;
263 msg->more = (moredata && len == SdepMaxPayload) ? 1 : 0;
264
265 static_assert(sizeof(*msg) == 20, "msg is correctly packed");
266
267 memcpy(msg->payload, payload, len);
268}
269
270// Read a single SDEP packet
271static bool sdep_recv_pkt(struct sdep_msg *msg, uint16_t timeout) {
272 bool success = false;
273 uint16_t timerStart = timer_read();
274 bool ready = false;
275
276 do {
277 ready = digitalRead(AdafruitBleIRQPin);
278 if (ready) {
279 break;
280 }
281 _delay_us(1);
282 } while (timer_elapsed(timerStart) < timeout);
283
284 if (ready) {
285 SPI_begin(&spi);
286
287 digitalWrite(AdafruitBleCSPin, PinLevelLow);
288
289 do {
290 // Read the command type, waiting for the data to be ready
291 msg->type = spi_read_byte();
292 if (msg->type == SdepSlaveNotReady || msg->type == SdepSlaveOverflow) {
293 // Release it and let it initialize
294 digitalWrite(AdafruitBleCSPin, PinLevelHigh);
295 _delay_us(SdepBackOff);
296 digitalWrite(AdafruitBleCSPin, PinLevelLow);
297 continue;
298 }
299
300 // Read the rest of the header
301 spi_recv_bytes(&msg->cmd_low, sizeof(*msg) - (1 + sizeof(msg->payload)));
302
303 // and get the payload if there is any
304 if (msg->len <= SdepMaxPayload) {
305 spi_recv_bytes(msg->payload, msg->len);
306 }
307 success = true;
308 break;
309 } while (timer_elapsed(timerStart) < timeout);
310
311 digitalWrite(AdafruitBleCSPin, PinLevelHigh);
312 }
313 return success;
314}
315
316static void resp_buf_read_one(bool greedy) {
317 uint16_t last_send;
318 if (!resp_buf.peek(last_send)) {
319 return;
320 }
321
322 if (digitalRead(AdafruitBleIRQPin)) {
323 struct sdep_msg msg;
324
325again:
326 if (sdep_recv_pkt(&msg, SdepTimeout)) {
327 if (!msg.more) {
328 // We got it; consume this entry
329 resp_buf.get(last_send);
330 dprintf("recv latency %dms\n", TIMER_DIFF_16(timer_read(), last_send));
331 }
332
333 if (greedy && resp_buf.peek(last_send) && digitalRead(AdafruitBleIRQPin)) {
334 goto again;
335 }
336 }
337
338 } else if (timer_elapsed(last_send) > SdepTimeout * 2) {
339 dprintf("waiting_for_result: timeout, resp_buf size %d\n",
340 (int)resp_buf.size());
341
342 // Timed out: consume this entry
343 resp_buf.get(last_send);
344 }
345}
346
347static void send_buf_send_one(uint16_t timeout = SdepTimeout) {
348 struct queue_item item;
349
350 // Don't send anything more until we get an ACK
351 if (!resp_buf.empty()) {
352 return;
353 }
354
355 if (!send_buf.peek(item)) {
356 return;
357 }
358 if (process_queue_item(&item, timeout)) {
359 // commit that peek
360 send_buf.get(item);
361 dprintf("send_buf_send_one: have %d remaining\n", (int)send_buf.size());
362 } else {
363 dprint("failed to send, will retry\n");
364 _delay_ms(SdepTimeout);
365 resp_buf_read_one(true);
366 }
367}
368
369static void resp_buf_wait(const char *cmd) {
370 bool didPrint = false;
371 while (!resp_buf.empty()) {
372 if (!didPrint) {
373 dprintf("wait on buf for %s\n", cmd);
374 didPrint = true;
375 }
376 resp_buf_read_one(true);
377 }
378}
379
380static bool ble_init(void) {
381 state.initialized = false;
382 state.configured = false;
383 state.is_connected = false;
384
385 pinMode(AdafruitBleIRQPin, PinDirectionInput);
386 pinMode(AdafruitBleCSPin, PinDirectionOutput);
387 digitalWrite(AdafruitBleCSPin, PinLevelHigh);
388
389 SPI_init(&spi);
390
391 // Perform a hardware reset
392 pinMode(AdafruitBleResetPin, PinDirectionOutput);
393 digitalWrite(AdafruitBleResetPin, PinLevelHigh);
394 digitalWrite(AdafruitBleResetPin, PinLevelLow);
395 _delay_ms(10);
396 digitalWrite(AdafruitBleResetPin, PinLevelHigh);
397
398 _delay_ms(1000); // Give it a second to initialize
399
400 state.initialized = true;
401 return state.initialized;
402}
403
404static inline uint8_t min(uint8_t a, uint8_t b) {
405 return a < b ? a : b;
406}
407
408static bool read_response(char *resp, uint16_t resplen, bool verbose) {
409 char *dest = resp;
410 char *end = dest + resplen;
411
412 while (true) {
413 struct sdep_msg msg;
414
415 if (!sdep_recv_pkt(&msg, 2 * SdepTimeout)) {
416 dprint("sdep_recv_pkt failed\n");
417 return false;
418 }
419
420 if (msg.type != SdepResponse) {
421 *resp = 0;
422 return false;
423 }
424
425 uint8_t len = min(msg.len, end - dest);
426 if (len > 0) {
427 memcpy(dest, msg.payload, len);
428 dest += len;
429 }
430
431 if (!msg.more) {
432 // No more data is expected!
433 break;
434 }
435 }
436
437 // Ensure the response is NUL terminated
438 *dest = 0;
439
440 // "Parse" the result text; we want to snip off the trailing OK or ERROR line
441 // Rewind past the possible trailing CRLF so that we can strip it
442 --dest;
443 while (dest > resp && (dest[0] == '\n' || dest[0] == '\r')) {
444 *dest = 0;
445 --dest;
446 }
447
448 // Look back for start of preceeding line
449 char *last_line = strrchr(resp, '\n');
450 if (last_line) {
451 ++last_line;
452 } else {
453 last_line = resp;
454 }
455
456 bool success = false;
457 static const char kOK[] PROGMEM = "OK";
458
459 success = !strcmp_P(last_line, kOK );
460
461 if (verbose || !success) {
462 dprintf("result: %s\n", resp);
463 }
464 return success;
465}
466
467static bool at_command(const char *cmd, char *resp, uint16_t resplen,
468 bool verbose, uint16_t timeout) {
469 const char *end = cmd + strlen(cmd);
470 struct sdep_msg msg;
471
472 if (verbose) {
473 dprintf("ble send: %s\n", cmd);
474 }
475
476 if (resp) {
477 // They want to decode the response, so we need to flush and wait
478 // for all pending I/O to finish before we start this one, so
479 // that we don't confuse the results
480 resp_buf_wait(cmd);
481 *resp = 0;
482 }
483
484 // Fragment the command into a series of SDEP packets
485 while (end - cmd > SdepMaxPayload) {
486 sdep_build_pkt(&msg, BleAtWrapper, (uint8_t *)cmd, SdepMaxPayload, true);
487 if (!sdep_send_pkt(&msg, timeout)) {
488 return false;
489 }
490 cmd += SdepMaxPayload;
491 }
492
493 sdep_build_pkt(&msg, BleAtWrapper, (uint8_t *)cmd, end - cmd, false);
494 if (!sdep_send_pkt(&msg, timeout)) {
495 return false;
496 }
497
498 if (resp == NULL) {
499 auto now = timer_read();
500 while (!resp_buf.enqueue(now)) {
501 resp_buf_read_one(false);
502 }
503 auto later = timer_read();
504 if (TIMER_DIFF_16(later, now) > 0) {
505 dprintf("waited %dms for resp_buf\n", TIMER_DIFF_16(later, now));
506 }
507 return true;
508 }
509
510 return read_response(resp, resplen, verbose);
511}
512
513bool at_command_P(const char *cmd, char *resp, uint16_t resplen, bool verbose) {
514 auto cmdbuf = (char *)alloca(strlen_P(cmd) + 1);
515 strcpy_P(cmdbuf, cmd);
516 return at_command(cmdbuf, resp, resplen, verbose);
517}
518
519bool adafruit_ble_is_connected(void) {
520 return state.is_connected;
521}
522
523bool adafruit_ble_enable_keyboard(void) {
524 char resbuf[128];
525
526 if (!state.initialized && !ble_init()) {
527 return false;
528 }
529
530 state.configured = false;
531
532 // Disable command echo
533 static const char kEcho[] PROGMEM = "ATE=0";
534 // Make the advertised name match the keyboard
535 static const char kGapDevName[] PROGMEM =
536 "AT+GAPDEVNAME=" STR(PRODUCT) " " STR(DESCRIPTION);
537 // Turn on keyboard support
538 static const char kHidEnOn[] PROGMEM = "AT+BLEHIDEN=1";
539
540 // Adjust intervals to improve latency. This causes the "central"
541 // system (computer/tablet) to poll us every 10-30 ms. We can't
542 // set a smaller value than 10ms, and 30ms seems to be the natural
543 // processing time on my macbook. Keeping it constrained to that
544 // feels reasonable to type to.
545 static const char kGapIntervals[] PROGMEM = "AT+GAPINTERVALS=10,30,,";
546
547 // Reset the device so that it picks up the above changes
548 static const char kATZ[] PROGMEM = "ATZ";
549
550 // Turn down the power level a bit
551 static const char kPower[] PROGMEM = "AT+BLEPOWERLEVEL=-12";
552 static PGM_P const configure_commands[] PROGMEM = {
553 kEcho,
554 kGapIntervals,
555 kGapDevName,
556 kHidEnOn,
557 kPower,
558 kATZ,
559 };
560
561 uint8_t i;
562 for (i = 0; i < sizeof(configure_commands) / sizeof(configure_commands[0]);
563 ++i) {
564 PGM_P cmd;
565 memcpy_P(&cmd, configure_commands + i, sizeof(cmd));
566
567 if (!at_command_P(cmd, resbuf, sizeof(resbuf))) {
568 dprintf("failed BLE command: %S: %s\n", cmd, resbuf);
569 goto fail;
570 }
571 }
572
573 state.configured = true;
574
575 // Check connection status in a little while; allow the ATZ time
576 // to kick in.
577 state.last_connection_update = timer_read();
578fail:
579 return state.configured;
580}
581
582static void set_connected(bool connected) {
583 if (connected != state.is_connected) {
584 if (connected) {
585 print("****** BLE CONNECT!!!!\n");
586 } else {
587 print("****** BLE DISCONNECT!!!!\n");
588 }
589 state.is_connected = connected;
590
591 // TODO: if modifiers are down on the USB interface and
592 // we cut over to BLE or vice versa, they will remain stuck.
593 // This feels like a good point to do something like clearing
594 // the keyboard and/or generating a fake all keys up message.
595 // However, I've noticed that it takes a couple of seconds
596 // for macOS to to start recognizing key presses after BLE
597 // is in the connected state, so I worry that doing that
598 // here may not be good enough.
599 }
600}
601
602void adafruit_ble_task(void) {
603 char resbuf[48];
604
605 if (!state.configured && !adafruit_ble_enable_keyboard()) {
606 return;
607 }
608 resp_buf_read_one(true);
609 send_buf_send_one(SdepShortTimeout);
610
611 if (resp_buf.empty() && (state.event_flags & UsingEvents) &&
612 digitalRead(AdafruitBleIRQPin)) {
613 // Must be an event update
614 if (at_command_P(PSTR("AT+EVENTSTATUS"), resbuf, sizeof(resbuf))) {
615 uint32_t mask = strtoul(resbuf, NULL, 16);
616
617 if (mask & BleSystemConnected) {
618 set_connected(true);
619 } else if (mask & BleSystemDisconnected) {
620 set_connected(false);
621 }
622 }
623 }
624
625 if (timer_elapsed(state.last_connection_update) > ConnectionUpdateInterval) {
626 bool shouldPoll = true;
627 if (!(state.event_flags & ProbedEvents)) {
628 // Request notifications about connection status changes.
629 // This only works in SPIFRIEND firmware > 0.6.7, which is why
630 // we check for this conditionally here.
631 // Note that at the time of writing, HID reports only work correctly
632 // with Apple products on firmware version 0.6.7!
633 // https://forums.adafruit.com/viewtopic.php?f=8&t=104052
634 if (at_command_P(PSTR("AT+EVENTENABLE=0x1"), resbuf, sizeof(resbuf))) {
635 at_command_P(PSTR("AT+EVENTENABLE=0x2"), resbuf, sizeof(resbuf));
636 state.event_flags |= UsingEvents;
637 }
638 state.event_flags |= ProbedEvents;
639
640 // leave shouldPoll == true so that we check at least once
641 // before relying solely on events
642 } else {
643 shouldPoll = false;
644 }
645
646 static const char kGetConn[] PROGMEM = "AT+GAPGETCONN";
647 state.last_connection_update = timer_read();
648
649 if (at_command_P(kGetConn, resbuf, sizeof(resbuf))) {
650 set_connected(atoi(resbuf));
651 }
652 }
653
654#ifdef SAMPLE_BATTERY
655 // I don't know if this really does anything useful yet; the reported
656 // voltage level always seems to be around 3200mV. We may want to just rip
657 // this code out.
658 if (timer_elapsed(state.last_battery_update) > BatteryUpdateInterval &&
659 resp_buf.empty()) {
660 state.last_battery_update = timer_read();
661
662 if (at_command_P(PSTR("AT+HWVBAT"), resbuf, sizeof(resbuf))) {
663 state.vbat = atoi(resbuf);
664 }
665 }
666#endif
667}
668
669static bool process_queue_item(struct queue_item *item, uint16_t timeout) {
670 char cmdbuf[48];
671 char fmtbuf[64];
672
673 // Arrange to re-check connection after keys have settled
674 state.last_connection_update = timer_read();
675
676#if 1
677 if (TIMER_DIFF_16(state.last_connection_update, item->added) > 0) {
678 dprintf("send latency %dms\n",
679 TIMER_DIFF_16(state.last_connection_update, item->added));
680 }
681#endif
682
683 switch (item->queue_type) {
684 case QTKeyReport:
685 strcpy_P(fmtbuf,
686 PSTR("AT+BLEKEYBOARDCODE=%02x-00-%02x-%02x-%02x-%02x-%02x-%02x"));
687 snprintf(cmdbuf, sizeof(cmdbuf), fmtbuf, item->key.modifier,
688 item->key.keys[0], item->key.keys[1], item->key.keys[2],
689 item->key.keys[3], item->key.keys[4], item->key.keys[5]);
690 return at_command(cmdbuf, NULL, 0, true, timeout);
691
692 case QTConsumer:
693 strcpy_P(fmtbuf, PSTR("AT+BLEHIDCONTROLKEY=0x%04x"));
694 snprintf(cmdbuf, sizeof(cmdbuf), fmtbuf, item->consumer);
695 return at_command(cmdbuf, NULL, 0, true, timeout);
696
697#ifdef MOUSE_ENABLE
698 case QTMouseMove:
699 strcpy_P(fmtbuf, PSTR("AT+BLEHIDMOUSEMOVE=%d,%d,%d,%d"));
700 snprintf(cmdbuf, sizeof(cmdbuf), fmtbuf, item->mousemove.x,
701 item->mousemove.y, item->mousemove.scroll, item->mousemove.pan);
702 return at_command(cmdbuf, NULL, 0, true, timeout);
703#endif
704 default:
705 return true;
706 }
707}
708
709bool adafruit_ble_send_keys(uint8_t hid_modifier_mask, uint8_t *keys,
710 uint8_t nkeys) {
711 struct queue_item item;
712 bool didWait = false;
713
714 item.queue_type = QTKeyReport;
715 item.key.modifier = hid_modifier_mask;
716 item.added = timer_read();
717
718 while (nkeys >= 0) {
719 item.key.keys[0] = keys[0];
720 item.key.keys[1] = nkeys >= 1 ? keys[1] : 0;
721 item.key.keys[2] = nkeys >= 2 ? keys[2] : 0;
722 item.key.keys[3] = nkeys >= 3 ? keys[3] : 0;
723 item.key.keys[4] = nkeys >= 4 ? keys[4] : 0;
724 item.key.keys[5] = nkeys >= 5 ? keys[5] : 0;
725
726 if (!send_buf.enqueue(item)) {
727 if (!didWait) {
728 dprint("wait for buf space\n");
729 didWait = true;
730 }
731 send_buf_send_one();
732 continue;
733 }
734
735 if (nkeys <= 6) {
736 return true;
737 }
738
739 nkeys -= 6;
740 keys += 6;
741 }
742
743 return true;
744}
745
746bool adafruit_ble_send_consumer_key(uint16_t keycode, int hold_duration) {
747 struct queue_item item;
748
749 item.queue_type = QTConsumer;
750 item.consumer = keycode;
751
752 while (!send_buf.enqueue(item)) {
753 send_buf_send_one();
754 }
755 return true;
756}
757
758#ifdef MOUSE_ENABLE
759bool adafruit_ble_send_mouse_move(int8_t x, int8_t y, int8_t scroll,
760 int8_t pan) {
761 struct queue_item item;
762
763 item.queue_type = QTMouseMove;
764 item.mousemove.x = x;
765 item.mousemove.y = y;
766 item.mousemove.scroll = scroll;
767 item.mousemove.pan = pan;
768
769 while (!send_buf.enqueue(item)) {
770 send_buf_send_one();
771 }
772 return true;
773}
774#endif
775
776uint32_t adafruit_ble_read_battery_voltage(void) {
777 return state.vbat;
778}
779
780bool adafruit_ble_set_mode_leds(bool on) {
781 if (!state.configured) {
782 return false;
783 }
784
785 // The "mode" led is the red blinky one
786 at_command_P(on ? PSTR("AT+HWMODELED=1") : PSTR("AT+HWMODELED=0"), NULL, 0);
787
788 // Pin 19 is the blue "connected" LED; turn that off too.
789 // When turning LEDs back on, don't turn that LED on if we're
790 // not connected, as that would be confusing.
791 at_command_P(on && state.is_connected ? PSTR("AT+HWGPIO=19,1")
792 : PSTR("AT+HWGPIO=19,0"),
793 NULL, 0);
794 return true;
795}
796
797// https://learn.adafruit.com/adafruit-feather-32u4-bluefruit-le/ble-generic#at-plus-blepowerlevel
798bool adafruit_ble_set_power_level(int8_t level) {
799 char cmd[46];
800 if (!state.configured) {
801 return false;
802 }
803 snprintf(cmd, sizeof(cmd), "AT+BLEPOWERLEVEL=%d", level);
804 return at_command(cmd, NULL, 0, false);
805}
diff --git a/tmk_core/protocol/lufa/adafruit_ble.h b/tmk_core/protocol/lufa/adafruit_ble.h
new file mode 100644
index 000000000..351fd55ae
--- /dev/null
+++ b/tmk_core/protocol/lufa/adafruit_ble.h
@@ -0,0 +1,60 @@
1/* Bluetooth Low Energy Protocol for QMK.
2 * Author: Wez Furlong, 2016
3 * Supports the Adafruit BLE board built around the nRF51822 chip.
4 */
5#pragma once
6#ifdef ADAFRUIT_BLE_ENABLE
7#include <stdbool.h>
8#include <stdint.h>
9#include <string.h>
10
11#ifdef __cplusplus
12extern "C" {
13#endif
14
15/* Instruct the module to enable HID keyboard support and reset */
16extern bool adafruit_ble_enable_keyboard(void);
17
18/* Query to see if the BLE module is connected */
19extern bool adafruit_ble_query_is_connected(void);
20
21/* Returns true if we believe that the BLE module is connected.
22 * This uses our cached understanding that is maintained by
23 * calling ble_task() periodically. */
24extern bool adafruit_ble_is_connected(void);
25
26/* Call this periodically to process BLE-originated things */
27extern void adafruit_ble_task(void);
28
29/* Generates keypress events for a set of keys.
30 * The hid modifier mask specifies the state of the modifier keys for
31 * this set of keys.
32 * Also sends a key release indicator, so that the keys do not remain
33 * held down. */
34extern bool adafruit_ble_send_keys(uint8_t hid_modifier_mask, uint8_t *keys,
35 uint8_t nkeys);
36
37/* Send a consumer keycode, holding it down for the specified duration
38 * (milliseconds) */
39extern bool adafruit_ble_send_consumer_key(uint16_t keycode, int hold_duration);
40
41#ifdef MOUSE_ENABLE
42/* Send a mouse/wheel movement report.
43 * The parameters are signed and indicate positive of negative direction
44 * change. */
45extern bool adafruit_ble_send_mouse_move(int8_t x, int8_t y, int8_t scroll,
46 int8_t pan);
47#endif
48
49/* Compute battery voltage by reading an analog pin.
50 * Returns the integer number of millivolts */
51extern uint32_t adafruit_ble_read_battery_voltage(void);
52
53extern bool adafruit_ble_set_mode_leds(bool on);
54extern bool adafruit_ble_set_power_level(int8_t level);
55
56#ifdef __cplusplus
57}
58#endif
59
60#endif // ADAFRUIT_BLE_ENABLE
diff --git a/tmk_core/protocol/lufa/ringbuffer.hpp b/tmk_core/protocol/lufa/ringbuffer.hpp
new file mode 100644
index 000000000..70a3c4881
--- /dev/null
+++ b/tmk_core/protocol/lufa/ringbuffer.hpp
@@ -0,0 +1,66 @@
1#pragma once
2// A simple ringbuffer holding Size elements of type T
3template <typename T, uint8_t Size>
4class RingBuffer {
5 protected:
6 T buf_[Size];
7 uint8_t head_{0}, tail_{0};
8 public:
9 inline uint8_t nextPosition(uint8_t position) {
10 return (position + 1) % Size;
11 }
12
13 inline uint8_t prevPosition(uint8_t position) {
14 if (position == 0) {
15 return Size - 1;
16 }
17 return position - 1;
18 }
19
20 inline bool enqueue(const T &item) {
21 static_assert(Size > 1, "RingBuffer size must be > 1");
22 uint8_t next = nextPosition(head_);
23 if (next == tail_) {
24 // Full
25 return false;
26 }
27
28 buf_[head_] = item;
29 head_ = next;
30 return true;
31 }
32
33 inline bool get(T &dest, bool commit = true) {
34 auto tail = tail_;
35 if (tail == head_) {
36 // No more data
37 return false;
38 }
39
40 dest = buf_[tail];
41 tail = nextPosition(tail);
42
43 if (commit) {
44 tail_ = tail;
45 }
46 return true;
47 }
48
49 inline bool empty() const { return head_ == tail_; }
50
51 inline uint8_t size() const {
52 int diff = head_ - tail_;
53 if (diff >= 0) {
54 return diff;
55 }
56 return Size + diff;
57 }
58
59 inline T& front() {
60 return buf_[tail_];
61 }
62
63 inline bool peek(T &item) {
64 return get(item, false);
65 }
66};