From 60f8c5aee0eec61440c4506059dc7c1bc276b8d1 Mon Sep 17 00:00:00 2001 From: Joshua Rubin Date: Thu, 30 Apr 2020 11:45:20 -0600 Subject: [PATCH] add adafruit bluefruit le uart support Signed-off-by: Joshua Rubin --- keyboards/bioi/g60ble/config.h | 3 + keyboards/bioi/g60ble/rules.mk | 3 +- .../promethium/keymaps/default/keymap.c | 6 +- .../promethium/keymaps/priyadi/keymap.c | 6 +- quantum/rgblight.c | 8 + quantum/rgblight.h | 10 + tmk_core/common.mk | 8 +- tmk_core/common/command.c | 27 + tmk_core/common/uart.h | 8 + tmk_core/protocol/bluefruit_le/ATParser.cpp | 330 ++++++++++++ tmk_core/protocol/bluefruit_le/ATParser.h | 230 ++++++++ tmk_core/protocol/bluefruit_le/BLE.cpp | 504 ++++++++++++++++++ tmk_core/protocol/bluefruit_le/BLE.h | 133 +++++ tmk_core/protocol/bluefruit_le/BLEBattery.cpp | 103 ++++ tmk_core/protocol/bluefruit_le/BLEBattery.h | 51 ++ .../bluefruit_le/BluefruitLE_UART.cpp | 170 ++++++ .../protocol/bluefruit_le/BluefruitLE_UART.h | 70 +++ .../protocol/bluefruit_le/HardwareSerial.cpp | 194 +++++++ .../protocol/bluefruit_le/HardwareSerial.h | 115 ++++ .../bluefruit_le/HardwareSerial_private.h | 117 ++++ tmk_core/protocol/bluefruit_le/Print.cpp | 205 +++++++ tmk_core/protocol/bluefruit_le/Print.h | 84 +++ tmk_core/protocol/bluefruit_le/Stream.cpp | 261 +++++++++ tmk_core/protocol/bluefruit_le/Stream.h | 118 ++++ tmk_core/protocol/bluefruit_le/new.cpp | 9 + tmk_core/protocol/bluefruit_le/qmk.cpp | 175 ++++++ tmk_core/protocol/bluefruit_le/qmk.h | 18 + tmk_core/protocol/bluefruit_le/sdep.h | 39 ++ tmk_core/protocol/lufa.mk | 18 +- tmk_core/protocol/lufa/adafruit_ble.h | 4 +- tmk_core/protocol/lufa/lufa.c | 16 +- tmk_core/protocol/lufa/outputselect.c | 7 +- tmk_core/protocol/serial.h | 8 + 33 files changed, 3036 insertions(+), 22 deletions(-) create mode 100644 tmk_core/protocol/bluefruit_le/ATParser.cpp create mode 100644 tmk_core/protocol/bluefruit_le/ATParser.h create mode 100644 tmk_core/protocol/bluefruit_le/BLE.cpp create mode 100644 tmk_core/protocol/bluefruit_le/BLE.h create mode 100644 tmk_core/protocol/bluefruit_le/BLEBattery.cpp create mode 100644 tmk_core/protocol/bluefruit_le/BLEBattery.h create mode 100644 tmk_core/protocol/bluefruit_le/BluefruitLE_UART.cpp create mode 100644 tmk_core/protocol/bluefruit_le/BluefruitLE_UART.h create mode 100644 tmk_core/protocol/bluefruit_le/HardwareSerial.cpp create mode 100644 tmk_core/protocol/bluefruit_le/HardwareSerial.h create mode 100644 tmk_core/protocol/bluefruit_le/HardwareSerial_private.h create mode 100644 tmk_core/protocol/bluefruit_le/Print.cpp create mode 100644 tmk_core/protocol/bluefruit_le/Print.h create mode 100644 tmk_core/protocol/bluefruit_le/Stream.cpp create mode 100644 tmk_core/protocol/bluefruit_le/Stream.h create mode 100644 tmk_core/protocol/bluefruit_le/new.cpp create mode 100644 tmk_core/protocol/bluefruit_le/qmk.cpp create mode 100644 tmk_core/protocol/bluefruit_le/qmk.h create mode 100644 tmk_core/protocol/bluefruit_le/sdep.h diff --git a/keyboards/bioi/g60ble/config.h b/keyboards/bioi/g60ble/config.h index e7515ec8945f..a1b25cc64f10 100644 --- a/keyboards/bioi/g60ble/config.h +++ b/keyboards/bioi/g60ble/config.h @@ -48,3 +48,6 @@ #define KEYBOARD_LOCK_ENABLE #define MAGIC_KEY_LOCK L +#define AdafruitBleBaud 76800 +// #define AdafruitBleBattery +#define OUTPUT_AUTO_ENABLE diff --git a/keyboards/bioi/g60ble/rules.mk b/keyboards/bioi/g60ble/rules.mk index 5590b7af32c1..6a300a9e9566 100644 --- a/keyboards/bioi/g60ble/rules.mk +++ b/keyboards/bioi/g60ble/rules.mk @@ -16,7 +16,7 @@ BOOTLOADER = qmk-dfu # comment out to disable the options. # BOOTMAGIC_ENABLE = lite # Virtual DIP switch configuration(+1000) -MOUSEKEY_ENABLE = yes # Mouse keys(+4700) +# MOUSEKEY_ENABLE = yes # Mouse keys(+4700) EXTRAKEY_ENABLE = yes # Audio control and System control(+450) # CONSOLE_ENABLE = yes # Console for debug(+400) COMMAND_ENABLE = yes # Commands for debug and configuration @@ -25,5 +25,6 @@ NKRO_ENABLE = yes # USB Nkey Rollover - if this doesn't work, see here: BACKLIGHT_ENABLE = yes # Enable keyboard backlight functionality RGBLIGHT_ENABLE = yes LTO_ENABLE = yes +BLUETOOTH = AdafruitBLEUART LAYOUTS = 60_ansi 60_iso 60_hhkb 60_ansi_split_bs_rshift 60_tsangan_hhkb diff --git a/keyboards/handwired/promethium/keymaps/default/keymap.c b/keyboards/handwired/promethium/keymaps/default/keymap.c index 14a293bb39cd..a2a37f7c8988 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 MODULE_ADAFRUIT_BLE_SPI 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 MODULE_ADAFRUIT_BLE_SPI 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 MODULE_ADAFRUIT_BLE_SPI 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 094eb1576211..c9d658869844 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 MODULE_ADAFRUIT_BLE_SPI OUT_BT, #endif RGBDEMO, @@ -1264,7 +1264,7 @@ bool process_record_user(uint16_t keycode, keyrecord_t *record) { } void set_output_user(uint8_t output) { -#ifdef MODULE_ADAFRUIT_BLE +#ifdef MODULE_ADAFRUIT_BLE_SPI switch(output) { case OUTPUT_USB: led_set_output_usb(); @@ -1288,7 +1288,7 @@ void matrix_init_user() { #endif // auto detect output on init -#ifdef MODULE_ADAFRUIT_BLE +#ifdef MODULE_ADAFRUIT_BLE_SPI uint8_t output = auto_detect_output(); if (output == OUTPUT_USB) { set_output(OUTPUT_USB); diff --git a/quantum/rgblight.c b/quantum/rgblight.c index 26cb41a96f99..6a16d8b8f144 100644 --- a/quantum/rgblight.c +++ b/quantum/rgblight.c @@ -226,6 +226,14 @@ void rgblight_init(void) { is_rgblight_initialized = true; } +void rgblight_restore_from_eeprom(void) { + if (!is_rgblight_initialized) return; + rgblight_config.raw = eeconfig_read_rgblight(); + if (rgblight_config.enable) { + rgblight_mode_noeeprom(rgblight_config.mode); + } +} + uint32_t rgblight_read_dword(void) { return rgblight_config.raw; } void rgblight_update_dword(uint32_t dword) { diff --git a/quantum/rgblight.h b/quantum/rgblight.h index b1585b158bcb..6a2fb6376d50 100644 --- a/quantum/rgblight.h +++ b/quantum/rgblight.h @@ -16,6 +16,10 @@ #ifndef RGBLIGHT_H #define RGBLIGHT_H +#ifdef __cplusplus +extern "C" { +#endif + /***** rgblight_mode(mode)/rgblight_mode_noeeprom(mode) **** old mode number (before 0.6.117) to new mode name table @@ -317,6 +321,7 @@ uint8_t rgblight_get_val(void); /* === qmk_firmware (core)internal Functions === */ void rgblight_init(void); +void rgblight_restore_from_eeprom(void); uint32_t rgblight_read_dword(void); void rgblight_update_dword(uint32_t dword); uint32_t eeconfig_read_rgblight(void); @@ -396,4 +401,9 @@ void rgblight_effect_alternating(animation_status_t *anim); # endif #endif // #ifndef RGBLIGHT_H_DUMMY_DEFINE + +#ifdef __cplusplus +} +#endif + #endif // RGBLIGHT_H diff --git a/tmk_core/common.mk b/tmk_core/common.mk index 3d0b83a01c94..8f2ef40b770b 100644 --- a/tmk_core/common.mk +++ b/tmk_core/common.mk @@ -120,7 +120,13 @@ endif ifeq ($(strip $(BLUETOOTH)), AdafruitBLE) TMK_COMMON_DEFS += -DBLUETOOTH_ENABLE - TMK_COMMON_DEFS += -DMODULE_ADAFRUIT_BLE + TMK_COMMON_DEFS += -DMODULE_ADAFRUIT_BLE_SPI + TMK_COMMON_DEFS += -DNO_USB_STARTUP_CHECK +endif + +ifeq ($(strip $(BLUETOOTH)), AdafruitBLEUART) + TMK_COMMON_DEFS += -DBLUETOOTH_ENABLE + TMK_COMMON_DEFS += -DMODULE_ADAFRUIT_BLE_UART TMK_COMMON_DEFS += -DNO_USB_STARTUP_CHECK endif diff --git a/tmk_core/common/command.c b/tmk_core/common/command.c index 77a205eac4cb..befe08d40bc5 100644 --- a/tmk_core/common/command.c +++ b/tmk_core/common/command.c @@ -292,6 +292,33 @@ static void print_eeconfig(void) { print("\n"); # endif /* BACKLIGHT_ENABLE */ +# ifdef RGBLIGHT_ENABLE + rgblight_config_t rgb; + rgb.speed = 0; + rgb.raw = eeconfig_read_rgblight(); + print("rgblight_config.raw: "); + print_hex8(rgb.raw); + print("\n"); + print(".enable: "); + print_dec(rgb.enable); + print("\n"); + print(".mode: "); + print_dec(rgb.mode); + print("\n"); + print(".hue: "); + print_dec(rgb.hue); + print("\n"); + print(".sat: "); + print_dec(rgb.sat); + print("\n"); + print(".val: "); + print_dec(rgb.val); + print("\n"); + print(".speed: "); + print_dec(rgb.speed); + print("\n"); +# endif /* RGBLIGHT_ENABLE */ + #endif /* !NO_PRINT */ } diff --git a/tmk_core/common/uart.h b/tmk_core/common/uart.h index 59a1a7cd12e4..a35b35688eaf 100644 --- a/tmk_core/common/uart.h +++ b/tmk_core/common/uart.h @@ -1,6 +1,10 @@ #ifndef _uart_included_h_ #define _uart_included_h_ +#ifdef __cplusplus +extern "C" { +#endif + #include void uart_init(uint32_t baud); @@ -8,4 +12,8 @@ void uart_putchar(uint8_t c); uint8_t uart_getchar(void); uint8_t uart_available(void); +#ifdef __cplusplus +} +#endif + #endif diff --git a/tmk_core/protocol/bluefruit_le/ATParser.cpp b/tmk_core/protocol/bluefruit_le/ATParser.cpp new file mode 100644 index 000000000000..95f4ce47d508 --- /dev/null +++ b/tmk_core/protocol/bluefruit_le/ATParser.cpp @@ -0,0 +1,330 @@ +/**************************************************************************/ +/*! + Modified from the original ATParser.cpp which was released under + the BSD License (below). + + Software License Agreement (BSD License) + + Copyright (c) 2020, Joshua Rubin + Copyright (c) 2016, Adafruit Industries (adafruit.com) + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holders nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +/**************************************************************************/ + +#include +#include "ATParser.h" +#include "wait.h" +#include "print.h" + +#define lowByte(w) ((uint8_t)((w)&0xff)) + +static inline char digit2ascii(uint8_t digit) { return (digit + ((digit) < 10 ? '0' : ('A' - 10))); } + +/******************************************************************************/ +/*! + @brief Constructor +*/ +/******************************************************************************/ +ATParser::ATParser(void) { + _mode = BLUEFRUIT_MODE_COMMAND; + _verbose = false; +} + +/******************************************************************************/ +/*! + @brief Read the whole response and check if it ended up with OK. + @return true if response is ended with "OK". Otherwise it could be "ERROR" +*/ +/******************************************************************************/ +bool ATParser::waitForOK(void) { + if (_verbose) print("\n<- "); + + // Use temp buffer to avoid overwrite returned result if any + char tempbuf[BLE_BUFSIZE + 1]; + + while (readline(tempbuf, BLE_BUFSIZE)) { + if (strcmp(tempbuf, "OK") == 0) return true; + if (strcmp(tempbuf, "ERROR") == 0) return false; + + // Copy to internal buffer if not OK or ERROR + strcpy(this->buffer, tempbuf); + } + return false; +} + +/******************************************************************************/ +/*! + @brief + @param +*/ +/******************************************************************************/ +bool ATParser::send_arg_get_resp(int32_t* reply, uint8_t argcount, uint16_t argtype[], uint32_t args[]) { + // Command arguments according to its type + for (uint8_t i = 0; i < argcount; i++) { + // print '=' for WRITE mode + if (i == 0) this->pprint('='); + + switch (argtype[i] & 0xFF00) { + case AT_ARGTYPE_STRING: + this->pprint((char const*)args[i]); + break; + + case AT_ARGTYPE_BYTEARRAY: { + uint8_t count = lowByte(argtype[i]); + this->printByteArray((uint8_t const*)args[i], count); + } break; + + case AT_ARGTYPE_UINT32: + this->pprint((uint32_t)args[i]); + break; + + case AT_ARGTYPE_INT32: + this->pprint((int32_t)args[i]); + break; + + case AT_ARGTYPE_UINT16: + this->pprint((uint16_t)args[i]); + break; + + case AT_ARGTYPE_INT16: + this->pprint((int16_t)args[i]); + break; + + case AT_ARGTYPE_UINT8: + this->pprint((uint8_t)((uint32_t)args[i])); + break; + + case AT_ARGTYPE_INT8: + this->pprint((int8_t)((int32_t)args[i])); + break; + + default: + break; + } + + if (i != argcount - 1) this->pprint(','); + } + this->pprintln(); // execute command + + // parse integer response if required + if (reply) { + if (_verbose) print("\n<- "); + (*reply) = readline_parseInt(); + } + + // check OK or ERROR status + return waitForOK(); +} + +/******************************************************************************/ +/*! + @brief + @param +*/ +/******************************************************************************/ +bool ATParser::atcommand_full(const char cmd[], int32_t* reply, uint8_t argcount, uint16_t argtype[], uint32_t args[]) { + bool result; + uint8_t current_mode = _mode; + + // switch mode if necessary to execute command + if (current_mode == BLUEFRUIT_MODE_DATA) setMode(BLUEFRUIT_MODE_COMMAND); + + // Execute command with parameter and get response + this->pprint(cmd); + result = this->send_arg_get_resp(reply, argcount, argtype, args); + + // switch back if necessary + if (current_mode == BLUEFRUIT_MODE_DATA) setMode(BLUEFRUIT_MODE_DATA); + + return result; +} + +/******************************************************************************/ +/*! + @brief + @param +*/ +/******************************************************************************/ +bool ATParser::atcommand_full(const __FlashStringHelper* cmd, int32_t* reply, uint8_t argcount, uint16_t argtype[], uint32_t args[]) { + bool result; + uint8_t current_mode = _mode; + + // switch mode if necessary to execute command + if (current_mode == BLUEFRUIT_MODE_DATA) setMode(BLUEFRUIT_MODE_COMMAND); + + // Execute command with parameter and get response + this->pprint(cmd); + result = this->send_arg_get_resp(reply, argcount, argtype, args); + + // switch back if necessary + if (current_mode == BLUEFRUIT_MODE_DATA) setMode(BLUEFRUIT_MODE_DATA); + + return result; +} + +/******************************************************************************/ +/*! + @brief Get a line of response data (see \ref readline) and try to interpret + it to an integer number. If the number is prefix with '0x', it will + be interpreted as hex number. This function also drop the rest of + data to the end of the line. +*/ +/******************************************************************************/ +int32_t ATParser::readline_parseInt(void) { + uint16_t len = readline(); + if (len == 0) return 0; + + // also parsed hex number e.g 0xADAF + int32_t val = strtol(buffer, NULL, 0); + + return val; +} + +/******************************************************************************/ +/*! + @brief Get a line of response data into provided buffer. + + @param[in] buf Provided buffer + @param[in] bufsize buffer size + @param[in] timeout timeout in milliseconds + @param[in] multiline Read multiple line if true, otherwise only read 1 line + + @note '\r' and '\n' are not included in returned buffer. +*/ +/******************************************************************************/ +uint16_t ATParser::readline(char* buf, uint16_t bufsize, uint16_t timeout, bool multiline) { + uint16_t replyidx = 0; + + while (timeout--) { + while (available()) { + char c = read(); + + if (c == '\r') continue; + + if (c == '\n') { + // the first '\n' is ignored + if (replyidx == 0) continue; + + if (!multiline) { + timeout = 0; + break; + } + } + + buf[replyidx] = c; + replyidx++; + + // Buffer is full + if (replyidx >= bufsize) { + timeout = 0; + break; + } + } + + // delay if needed + if (timeout) wait_ms(1); + } + + buf[replyidx] = 0; // null term + + // Print out if is verbose + if (_verbose && replyidx > 0) { + xprintf("%s", buf); + if (replyidx < bufsize) println(); + } + + return replyidx; +} + +/******************************************************************************/ +/*! + @brief Get raw binary data to internal buffer, only stop when encountering + either "OK\r\n" or "ERROR\r\n" or timed out. Buffer does not contain + OK or ERROR + + @param[in] timeout + Timeout for each read() operation + + @return The number of bytes read excluding OK, ERROR ending. + 0 usually means error +*/ +/******************************************************************************/ +uint16_t ATParser::readraw(uint16_t timeout) { + uint16_t replyidx = 0; + + while (timeout--) { + while (available()) { + char c = read(); + + if (c == '\n') { + // done if ends with "OK\r\n" + if ((replyidx >= 3) && !strncmp(this->buffer + replyidx - 3, "OK\r", 3)) { + replyidx -= 3; // chop OK\r + timeout = 0; + break; + } else if ((replyidx >= 6) && !strncmp(this->buffer + replyidx - 6, "ERROR\r", 6)) { + // done if ends with "ERROR\r\n" + replyidx -= 6; // chop ERROR\r + timeout = 0; + break; + } + } + + this->buffer[replyidx] = c; + replyidx++; + + // Buffer is full + if (replyidx >= BLE_BUFSIZE) { + timeout = 0; + break; + } + } + + if (timeout == 0) break; + wait_ms(1); + } + this->buffer[replyidx] = 0; // null term + + return replyidx; +} + +/******************************************************************************/ +/*! + @brief Print a buffer to BYTE ARRAY format e.g 11-22-33-44-55 + @param bytearray buffer to print + @param size number of byte + @return number of printed characters +*/ +/******************************************************************************/ +int ATParser::printByteArray(uint8_t const bytearray[], int size) { + while (size--) { + uint8_t byte = *bytearray++; + write(digit2ascii((byte & 0xF0) >> 4)); + write(digit2ascii(byte & 0x0F)); + if (size != 0) write('-'); + } + + return (size * 3) - 1; +} diff --git a/tmk_core/protocol/bluefruit_le/ATParser.h b/tmk_core/protocol/bluefruit_le/ATParser.h new file mode 100644 index 000000000000..6f68bc39c192 --- /dev/null +++ b/tmk_core/protocol/bluefruit_le/ATParser.h @@ -0,0 +1,230 @@ +/**************************************************************************/ +/*! + Modified from the original Adafruit_ATParser.h which was released under + the BSD License (below). + + Software License Agreement (BSD License) + + Copyright (c) 2020, Joshua Rubin + Copyright (c) 2016, Adafruit Industries (adafruit.com) + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holders nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +/**************************************************************************/ + +#pragma once + +#include "Stream.h" + +#include "sdep.h" + +// Class to facilitate sending AT Command and check response + +#define HIGH 0x1 +#define LOW 0x0 + +#define BLUEFRUIT_MODE_COMMAND HIGH +#define BLUEFRUIT_MODE_DATA LOW +#define BLE_BUFSIZE 4 * SDEP_MAX_PACKETSIZE + +// High byte is type, Low byte is datacount +// datacount is needed when passing ByteArray argument +enum { + AT_ARGTYPE_STRING = 0x0100, + AT_ARGTYPE_BYTEARRAY = 0x0200, + AT_ARGTYPE_INT32 = 0x0300, + AT_ARGTYPE_UINT32 = 0x0400, + AT_ARGTYPE_INT16 = 0x0500, + AT_ARGTYPE_UINT16 = 0x0600, + AT_ARGTYPE_INT8 = 0x0700, + AT_ARGTYPE_UINT8 = 0x0800, +}; + +class ATParser : public Stream { + protected: + uint8_t _mode; + bool _verbose; + + // internal function + bool send_arg_get_resp(int32_t* reply, uint8_t argcount, uint16_t argtype[], uint32_t args[]); + + public: + ATParser(void); + + char buffer[BLE_BUFSIZE + 1]; + + uint8_t getMode(void) { return _mode; } + virtual bool setMode(uint8_t mode) = 0; + + // Auto print out TX & RX data to normal Serial + void verbose(bool enable) { _verbose = enable; } + + bool atcommand_full(const char cmd[], int32_t* reply, uint8_t argcount, uint16_t argtype[], uint32_t args[]); + bool atcommand_full(const __FlashStringHelper* cmd, int32_t* reply, uint8_t argcount, uint16_t argtype[], uint32_t args[]); + + //--------------------------------------------------------------------+ + // Without Reply + //--------------------------------------------------------------------+ + bool atcommand(const char cmd[]) { return this->atcommand_full(cmd, NULL, 0, NULL, NULL); } + bool atcommand(const __FlashStringHelper* cmd) { return this->atcommand_full(cmd, NULL, 0, NULL, NULL); } + + //------------- One integer argument -------------// + bool atcommand(const char cmd[], int32_t para1) { + uint16_t type[] = {AT_ARGTYPE_INT32}; + uint32_t args[] = {(uint32_t)para1}; + return this->atcommand_full(cmd, NULL, 1, type, args); + } + + bool atcommand(const __FlashStringHelper* cmd, int32_t para1) { + uint16_t type[] = {AT_ARGTYPE_INT32}; + uint32_t args[] = {(uint32_t)para1}; + return this->atcommand_full(cmd, NULL, 1, type, args); + } + + //------------- Two integer arguments -------------// + bool atcommand(const char cmd[], int32_t para1, int32_t para2) { + uint16_t type[] = {AT_ARGTYPE_INT32, AT_ARGTYPE_INT32}; + uint32_t args[] = {(uint32_t)para1, (uint32_t)para2}; + return this->atcommand_full(cmd, NULL, 2, type, args); + } + + bool atcommand(const __FlashStringHelper* cmd, int32_t para1, int32_t para2) { + uint16_t type[] = {AT_ARGTYPE_INT32, AT_ARGTYPE_INT32}; + uint32_t args[] = {(uint32_t)para1, (uint32_t)para2}; + return this->atcommand_full(cmd, NULL, 2, type, args); + } + + //------------- One ByteArray arguments -------------// + bool atcommand(const char cmd[], const uint8_t bytearray[], uint16_t count) { + uint16_t type[] = {(uint16_t)(AT_ARGTYPE_BYTEARRAY + count)}; + uint32_t args[] = {(uint32_t)bytearray}; + return this->atcommand_full(cmd, NULL, 1, type, args); + } + + bool atcommand(const __FlashStringHelper* cmd, const uint8_t bytearray[], uint16_t count) { + uint16_t type[] = {(uint16_t)(AT_ARGTYPE_BYTEARRAY + count)}; + uint32_t args[] = {(uint32_t)bytearray}; + return this->atcommand_full(cmd, NULL, 1, type, args); + } + + //------------- One String argument -------------// + bool atcommand(const char cmd[], const char* str) { + uint16_t type[] = {AT_ARGTYPE_STRING}; + uint32_t args[] = {(uint32_t)str}; + return this->atcommand_full(cmd, NULL, 1, type, args); + } + + bool atcommand(const __FlashStringHelper* cmd, const char* str) { + uint16_t type[] = {AT_ARGTYPE_STRING}; + uint32_t args[] = {(uint32_t)str}; + return this->atcommand_full(cmd, NULL, 1, type, args); + } + + //--------------------------------------------------------------------+ + // With Reply + //--------------------------------------------------------------------+ + bool atcommandIntReply(const char cmd[], int32_t* reply) { return this->atcommand_full(cmd, reply, 0, NULL, NULL); } + bool atcommandIntReply(const __FlashStringHelper* cmd, int32_t* reply) { return this->atcommand_full(cmd, reply, 0, NULL, NULL); } + + //------------- One integer argument -------------// + bool atcommandIntReply(const char cmd[], int32_t* reply, int32_t para1) { + uint16_t type[] = {AT_ARGTYPE_INT32}; + uint32_t args[] = {(uint32_t)para1}; + return this->atcommand_full(cmd, reply, 1, type, args); + } + + bool atcommandIntReply(const __FlashStringHelper* cmd, int32_t* reply, int32_t para1) { + uint16_t type[] = {AT_ARGTYPE_INT32}; + uint32_t args[] = {(uint32_t)para1}; + return this->atcommand_full(cmd, reply, 1, type, args); + } + + //------------- Two integer arguments -------------// + bool atcommandIntReply(const char cmd[], int32_t* reply, int32_t para1, int32_t para2) { + uint16_t type[] = {AT_ARGTYPE_INT32, AT_ARGTYPE_INT32}; + uint32_t args[] = {(uint32_t)para1, (uint32_t)para2}; + return this->atcommand_full(cmd, reply, 2, type, args); + } + + bool atcommandIntReply(const __FlashStringHelper* cmd, int32_t* reply, int32_t para1, int32_t para2) { + uint16_t type[] = {AT_ARGTYPE_INT32, AT_ARGTYPE_INT32}; + uint32_t args[] = {(uint32_t)para1, (uint32_t)para2}; + return this->atcommand_full(cmd, reply, 2, type, args); + } + + //------------- One ByteArray arguments -------------// + bool atcommandIntReply(const char cmd[], int32_t* reply, const uint8_t bytearray[], uint16_t count) { + uint16_t type[] = {(uint16_t)(AT_ARGTYPE_BYTEARRAY + count)}; + uint32_t args[] = {(uint32_t)bytearray}; + return this->atcommand_full(cmd, reply, 1, type, args); + } + + bool atcommandIntReply(const __FlashStringHelper* cmd, int32_t* reply, const uint8_t bytearray[], uint16_t count) { + uint16_t type[] = {(uint16_t)(AT_ARGTYPE_BYTEARRAY + count)}; + uint32_t args[] = {(uint32_t)bytearray}; + return this->atcommand_full(cmd, reply, 1, type, args); + } + + //------------- One String argument -------------// + bool atcommandIntReply(const char cmd[], int32_t* reply, const char* str) { + uint16_t type[] = {AT_ARGTYPE_STRING}; + uint32_t args[] = {(uint32_t)str}; + return this->atcommand_full(cmd, reply, 1, type, args); + } + + bool atcommandIntReply(const __FlashStringHelper* cmd, int32_t* reply, const char* str) { + uint16_t type[] = {AT_ARGTYPE_STRING}; + uint32_t args[] = {(uint32_t)str}; + return this->atcommand_full(cmd, reply, 1, type, args); + } + + //--------------------------------------------------------------------+ + // RESPONSE PROCESSING + //--------------------------------------------------------------------+ + bool waitForOK(void); + + // Read one line of response into internal buffer TODO use below API + + // Read one line of response into provided buffer + uint16_t readline(char* buf, uint16_t bufsize, uint16_t timeout, bool multiline = false); + uint16_t readline(uint8_t* buf, uint16_t bufsize, uint16_t timeout, bool multiline = false) { return readline((char*)buf, bufsize, timeout, multiline); } + + uint16_t readline(char* buf, uint16_t bufsize) { return readline(buf, bufsize, _timeout, false); } + uint16_t readline(uint8_t* buf, uint16_t bufsize) { return readline(buf, bufsize, _timeout, false); } + + uint16_t readline(uint16_t timeout, bool multiline = false) { return readline(this->buffer, BLE_BUFSIZE, timeout, multiline); } + + uint16_t readline(void) { return this->readline(this->buffer, BLE_BUFSIZE, _timeout, false); } + + // read one line and convert the string to integer number + int32_t readline_parseInt(void); + + uint16_t readraw(uint16_t timeout); + uint16_t readraw(void) { return readraw(_timeout); } + + //--------------------------------------------------------------------+ + // HELPER + //--------------------------------------------------------------------+ + int printByteArray(uint8_t const bytearray[], int size); +}; diff --git a/tmk_core/protocol/bluefruit_le/BLE.cpp b/tmk_core/protocol/bluefruit_le/BLE.cpp new file mode 100644 index 000000000000..31eda2085805 --- /dev/null +++ b/tmk_core/protocol/bluefruit_le/BLE.cpp @@ -0,0 +1,504 @@ +/**************************************************************************/ +/*! + Modified from the original Adafruit_BLE.cpp which was released under + the BSD License (below). + + Software License Agreement (BSD License) + + Copyright (c) 2020, Joshua Rubin + Copyright (c) 2014, Adafruit Industries (adafruit.com) + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holders nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +/**************************************************************************/ + +#include +#include "BLE.h" +#include "wait.h" +#include "timer.h" +#include "print.h" + +#define bit(b) (1UL << (b)) +#define bitRead(value, bit) (((value) >> (bit)) & 0x01) + +#ifndef min +# define min(a, b) ((a) < (b) ? (a) : (b)) +#endif + +enum { + EVENT_SYSTEM_CONNECT = 0, + EVENT_SYSTEM_DISCONNECT = 1, + + EVENT_SYSTEM_BLE_UART_RX = 8, + // 9 reserved + // 10 reserved + // 11 reserved +}; + +enum { NVM_USERDATA_SIZE = 256 }; + +/******************************************************************************/ +/*! + @brief Constructor +*/ +/******************************************************************************/ +BLE::BLE(void) { + _timeout = BLE_DEFAULT_TIMEOUT; + + _reset_started_timestamp = 0; + + _disconnect_callback = NULL; + _connect_callback = NULL; + _ble_uart_rx_callback = NULL; +} + +/******************************************************************************/ +/*! + @brief Helper to install callback + @param +*/ +/******************************************************************************/ +void BLE::install_callback(bool enable, int8_t system_id) { + uint8_t current_mode = _mode; + + // switch mode if necessary to execute command + if (current_mode == BLUEFRUIT_MODE_DATA) setMode(BLUEFRUIT_MODE_COMMAND); + + this->pprint(enable ? F("AT+EVENTENABLE=0x") : F("AT+EVENTDISABLE=0x")); + this->pprint((system_id < 0) ? 0 : bit(system_id), HEX); + + this->pprintln(); + + waitForOK(); + + // switch back if necessary + if (current_mode == BLUEFRUIT_MODE_DATA) setMode(BLUEFRUIT_MODE_DATA); +} + +/******************************************************************************/ +/*! + @brief Performs a system reset using AT command + @param blocking blocking until bluefruit is ready, will take 1 second mostly +*/ +/******************************************************************************/ +bool BLE::reset(bool blocking) { + bool isOK; + for (uint8_t t = 0; t < 5; t++) { + isOK = atcommand(F("ATZ")); + + if (isOK) break; + } + + if (!isOK) { + // ok we're going to get desperate + wait_ms(50); + setMode(BLUEFRUIT_MODE_COMMAND); + wait_ms(50); + + for (uint8_t t = 0; t < 5; t++) { + isOK = atcommand(F("ATZ")); + + if (isOK) break; + } + + if (!isOK) return false; + } + + _reset_started_timestamp = timer_read32(); + + // Bluefruit need 1 second to reboot + if (blocking) { + wait_ms(1000); + } + + // flush all left over + flush(); + + return isOK; +} + +/******************************************************************************/ +/*! + @brief Performs a factory reset +*/ +/******************************************************************************/ +bool BLE::factoryReset(bool blocking) { + this->pprintln(F("AT+FACTORYRESET")); + bool isOK = waitForOK(); + + _reset_started_timestamp = timer_read32(); + + // Bluefruit need 1 second to reboot + if (blocking) { + wait_ms(1000); + } + + // flush all left over + flush(); + + return isOK; +} + +/******************************************************************************/ +/*! + @brief Check if the reset process is completed, should be used if user + reset Bluefruit with non-blocking aka reset(false) +*/ +/******************************************************************************/ +bool BLE::resetCompleted(void) { return timer_elapsed32(_reset_started_timestamp) > 1000; } + +/******************************************************************************/ +/*! + @brief Enable or disable AT Command echo from Bluefruit + + @parma[in] enable + true to enable (default), false to disable +*/ +/******************************************************************************/ +bool BLE::echo(bool enable) { return atcommand(F("ATE"), (int32_t)enable); } + +/******************************************************************************/ +/*! + @brief Check connection state, returns true is connected! +*/ +/******************************************************************************/ +bool BLE::isConnected(void) { + int32_t connected = 0; + atcommandIntReply(F("AT+GAPGETCONN"), &connected); + return connected; +} + +/******************************************************************************/ +/*! + @brief Disconnect if currently connected +*/ +/******************************************************************************/ +void BLE::disconnect(void) { atcommand(F("AT+GAPDISCONNECT")); } + +/******************************************************************************/ +/*! + @brief Print Bluefruit's information retrieved by ATI command +*/ +/******************************************************************************/ +void BLE::info(void) { + uint8_t current_mode = _mode; + + bool v = _verbose; + _verbose = false; + + println("----------------"); + + // switch mode if necessary to execute command + if (current_mode == BLUEFRUIT_MODE_DATA) setMode(BLUEFRUIT_MODE_COMMAND); + + pprintln(F("ATI")); + + while (readline()) { + if (!strcmp(buffer, "OK") || !strcmp(buffer, "ERROR")) break; + xprintf("%s\n", buffer); + } + + // switch back if necessary + if (current_mode == BLUEFRUIT_MODE_DATA) setMode(BLUEFRUIT_MODE_DATA); + + println("----------------"); + + _verbose = v; +} + +/**************************************************************************/ +/*! + @brief Checks if firmware is equal or later than specified version +*/ +/**************************************************************************/ +bool BLE::isVersionAtLeast(const char* versionString) { + uint8_t current_mode = _mode; + + // switch mode if necessary to execute command + if (current_mode == BLUEFRUIT_MODE_DATA) setMode(BLUEFRUIT_MODE_COMMAND); + + // requesting version number + this->pprintln(F("ATI=4")); + + readline(); + bool result = (strcmp(buffer, versionString) >= 0); + waitForOK(); + + // switch back if necessary + if (current_mode == BLUEFRUIT_MODE_DATA) setMode(BLUEFRUIT_MODE_DATA); + + return result; +} + +/******************************************************************************/ +/*! + @brief Get (multiple) lines of response data into internal buffer. + + @param[in] period_ms + period in milliseconds between each event scanning + @return None +*/ +/******************************************************************************/ +void BLE::update(uint32_t period_ms) { + static uint32_t tt = 0; + + if (timer_elapsed32(tt) > period_ms) { + tt = timer_read32(); + + bool v = _verbose; + _verbose = false; + + uint8_t current_mode = _mode; + + // switch mode if necessary to execute command + if (current_mode == BLUEFRUIT_MODE_DATA) setMode(BLUEFRUIT_MODE_COMMAND); + + this->pprintln(F("AT+EVENTSTATUS")); + readline(); + waitForOK(); + + // parse event status system_event + uint8_t tempbuf[BLE_BUFSIZE + 1]; + uint32_t system_event; + char* p_comma = NULL; + + system_event = strtoul(this->buffer, &p_comma, 16); + + //--------------------------------------------------------------------+ + // System Event + //--------------------------------------------------------------------+ + if (this->_connect_callback && bitRead(system_event, EVENT_SYSTEM_CONNECT)) this->_connect_callback(); + if (this->_disconnect_callback && bitRead(system_event, EVENT_SYSTEM_DISCONNECT)) this->_disconnect_callback(); + + if (this->_ble_uart_rx_callback && bitRead(system_event, EVENT_SYSTEM_BLE_UART_RX)) { + this->pprintln(F("AT+BLEUARTRX")); + uint16_t len = readline(tempbuf, BLE_BUFSIZE); + waitForOK(); + + this->_ble_uart_rx_callback((char*)tempbuf, len); + } + + // switch back if necessary + if (current_mode == BLUEFRUIT_MODE_DATA) setMode(BLUEFRUIT_MODE_DATA); + + _verbose = v; + } +} + +/******************************************************************************/ +/*! + @brief Set custom ADV data packet + @param +*/ +/******************************************************************************/ +bool BLE::setAdvData(uint8_t advdata[], uint8_t size) { return this->atcommand(F("AT+GAPSETADVDATA"), advdata, size); } + +/******************************************************************************/ +/*! + @brief Save user information to NVM section, current size limit is 256 bytes + @param data buffer holding data + @param size number of bytes + @param offset relative offset in the NVM section +*/ +/******************************************************************************/ +bool BLE::writeNVM(uint16_t offset, uint8_t const data[], uint16_t size) { + VERIFY_(offset + size <= NVM_USERDATA_SIZE); + + uint16_t type[] = {AT_ARGTYPE_UINT16, AT_ARGTYPE_UINT8, (uint16_t)(AT_ARGTYPE_BYTEARRAY + size)}; + uint32_t args[] = {offset, BLE_DATATYPE_BYTEARRAY, (uint32_t)data}; + + return this->atcommand_full(F("AT+NVMWRITE"), NULL, 3, type, args); +} + +/******************************************************************************/ +/*! + @brief Save String to NVM section, current size limit is 256 bytes + @param data buffer holding data + @param size number of bytes + @param offset relative offset in the NVM section +*/ +/******************************************************************************/ +bool BLE::writeNVM(uint16_t offset, char const* str) { + VERIFY_(offset + strlen(str) <= NVM_USERDATA_SIZE); + + uint16_t type[] = {AT_ARGTYPE_UINT16, AT_ARGTYPE_UINT8, AT_ARGTYPE_STRING}; + uint32_t args[] = {offset, BLE_DATATYPE_STRING, (uint32_t)str}; + + return this->atcommand_full(F("AT+NVMWRITE"), NULL, 3, type, args); +} + +/******************************************************************************/ +/*! + @brief Save an 32-bit number to NVM + @param number Number to be saved + @param offset relative offset in the NVM section +*/ +/******************************************************************************/ +bool BLE::writeNVM(uint16_t offset, int32_t number) { + VERIFY_(offset + 4 <= NVM_USERDATA_SIZE); + + uint16_t type[] = {AT_ARGTYPE_UINT16, AT_ARGTYPE_UINT8, AT_ARGTYPE_INT32}; + uint32_t args[] = {offset, BLE_DATATYPE_INTEGER, (uint32_t)number}; + + return this->atcommand_full(F("AT+NVMWRITE"), NULL, 3, type, args); +} + +/******************************************************************************/ +/*! + @brief Read an number of bytes from NVM at offset to buffer + @param +*/ +/******************************************************************************/ +bool BLE::readNVM(uint16_t offset, uint8_t data[], uint16_t size) { + VERIFY_(offset < NVM_USERDATA_SIZE); + + uint8_t current_mode = _mode; + + // switch mode if necessary to execute command + if (current_mode == BLUEFRUIT_MODE_DATA) setMode(BLUEFRUIT_MODE_COMMAND); + + // use RAW command version + this->pprint(F("AT+NVMREADRAW=")); + this->pprint(offset); + + this->pprint(','); + this->pprintln(size); + + uint16_t len = readraw(); // readraw swallow OK/ERROR already + + // Check for an error reading + if (len != size) return false; + + // skip if NULL is entered + if (data) memcpy(data, this->buffer, min(size, BLE_BUFSIZE)); + + // switch back if necessary + if (current_mode == BLUEFRUIT_MODE_DATA) setMode(BLUEFRUIT_MODE_DATA); + + return true; +} + +/******************************************************************************/ +/*! + @brief Read a string from NVM at offset to buffer + @param +*/ +/******************************************************************************/ +bool BLE::readNVM(uint16_t offset, char* str, uint16_t size) { + VERIFY_(offset < NVM_USERDATA_SIZE); + + uint16_t type[] = {AT_ARGTYPE_UINT16, AT_ARGTYPE_UINT16, AT_ARGTYPE_UINT8}; + uint32_t args[] = {offset, size, BLE_DATATYPE_STRING}; + + bool isOK = this->atcommand_full(F("AT+NVMREAD"), NULL, 3, type, args); + + // skip if NULL is entered + if (isOK && str) strncpy(str, this->buffer, min(size, BLE_BUFSIZE)); + + return isOK; +} + +/******************************************************************************/ +/*! + @brief Read an 32-bit number from NVM + @param +*/ +/******************************************************************************/ +bool BLE::readNVM(uint16_t offset, int32_t* number) { return this->readNVM(offset, (uint8_t*)number, 4); } + +/** + * + * @param buffer + * @param size + * @return + */ +int BLE::writeBLEUart(uint8_t const* buffer, int size) { + uint8_t current_mode = _mode; + + // switch mode if necessary to execute command + if (current_mode == BLUEFRUIT_MODE_COMMAND) setMode(BLUEFRUIT_MODE_DATA); + + size_t n = write(buffer, size); + + // switch back if necessary + if (current_mode == BLUEFRUIT_MODE_COMMAND) setMode(BLUEFRUIT_MODE_COMMAND); + + return n; +} + +/** + * + * @param buffer + * @param size + * @return + */ +int BLE::readBLEUart(uint8_t* buffer, int size) { + uint8_t current_mode = _mode; + + // switch mode if necessary to execute command + if (current_mode == BLUEFRUIT_MODE_COMMAND) setMode(BLUEFRUIT_MODE_DATA); + + size_t n = readBytes(buffer, size); + + // switch back if necessary + if (current_mode == BLUEFRUIT_MODE_COMMAND) setMode(BLUEFRUIT_MODE_COMMAND); + + return n; +} + +/******************************************************************************/ +/*! + @brief Set handle for connect callback + + @param[in] fp function pointer, NULL will discard callback +*/ +/******************************************************************************/ +void BLE::setConnectCallback(void (*fp)(void)) { + this->_connect_callback = fp; + install_callback(fp != NULL, EVENT_SYSTEM_CONNECT); +} + +/******************************************************************************/ +/*! + @brief Set handle for disconnection callback + + @param[in] fp function pointer, NULL will discard callback +*/ +/******************************************************************************/ +void BLE::setDisconnectCallback(void (*fp)(void)) { + this->_disconnect_callback = fp; + install_callback(fp != NULL, EVENT_SYSTEM_DISCONNECT); +} + +/******************************************************************************/ +/*! + @brief Set handle for BLE Uart Rx callback + + @param[in] fp function pointer, NULL will discard callback +*/ +/******************************************************************************/ +void BLE::setBleUartRxCallback(void (*fp)(char data[], uint16_t len)) { + this->_ble_uart_rx_callback = fp; + install_callback(fp != NULL, EVENT_SYSTEM_BLE_UART_RX); +} diff --git a/tmk_core/protocol/bluefruit_le/BLE.h b/tmk_core/protocol/bluefruit_le/BLE.h new file mode 100644 index 000000000000..273918ad3dc0 --- /dev/null +++ b/tmk_core/protocol/bluefruit_le/BLE.h @@ -0,0 +1,133 @@ +/**************************************************************************/ +/*! + Modified from the original Adafruit_BLE.h which was released under + the BSD License (below). + + Software License Agreement (BSD License) + + Copyright (c) 2020, Joshua Rubin + Copyright (c) 2014, Adafruit Industries (adafruit.com) + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holders nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +/**************************************************************************/ + +#pragma once + +#include "qmk.h" + +#ifdef __cplusplus +# include "ATParser.h" + +# define BLE_DEFAULT_TIMEOUT 250 +# define VERIFY_(condition) \ + if (!(condition)) return false; + +# ifndef AdafruitBleVerbose +# define AdafruitBleVerbose true +# endif + +enum BLEDataType_t { + BLE_DATATYPE_AUTO = 0, + BLE_DATATYPE_STRING, + BLE_DATATYPE_BYTEARRAY, + BLE_DATATYPE_INTEGER, +}; + +class BLE : public ATParser { + protected: + enum { + BLUEFRUIT_TRANSPORT_INVALID, + BLUEFRUIT_TRANSPORT_HWUART, + BLUEFRUIT_TRANSPORT_HWSPI, + }; + + uint8_t _physical_transport; + uint32_t _reset_started_timestamp; + + public: + // Constructor + BLE(void); + + // Functions implemented in this base class + bool reset(bool blocking = true); + bool factoryReset(bool blocking = true); + bool resetCompleted(void); + + void info(void); + bool echo(bool enable); + + bool isConnected(void); + bool isVersionAtLeast(const char *versionString); + void disconnect(void); + + bool setAdvData(uint8_t advdata[], uint8_t size); + + bool writeNVM(uint16_t offset, uint8_t const data[], uint16_t size); + bool writeNVM(uint16_t offset, char const *str); + bool writeNVM(uint16_t offset, int32_t number); + + bool readNVM(uint16_t offset, uint8_t data[], uint16_t size); + bool readNVM(uint16_t offset, char *str, uint16_t size); + bool readNVM(uint16_t offset, int32_t *number); + + // helper with bleuart + int writeBLEUart(uint8_t const *buffer, int size); + int writeBLEUart(char const *str) { return writeBLEUart((uint8_t const *)str, strlen(str)); } + + int readBLEUart(uint8_t *buffer, int size); + + // No parameters + bool sendCommandCheckOK(const __FlashStringHelper *cmd) { return this->atcommand(cmd); } + bool sendCommandCheckOK(const char cmd[]) { return this->atcommand(cmd); } + + bool sendCommandWithIntReply(const __FlashStringHelper *cmd, int32_t *reply) { return this->atcommandIntReply(cmd, reply); } + bool sendCommandWithIntReply(const char cmd[], int32_t *reply) { return this->atcommandIntReply(cmd, reply); } + + // Physical transportation checking + bool isTransportHwUart(void) { return _physical_transport == BLUEFRUIT_TRANSPORT_HWUART; } + bool isTransportUart(void) { return isTransportHwUart(); } + + bool isTransportHwSpi(void) { return _physical_transport == BLUEFRUIT_TRANSPORT_HWSPI; } + bool isTransportSpi(void) { return isTransportHwSpi(); } + + ///////////////////// + // callback functions + ///////////////////// + void update(uint32_t period_ms = 200); + void handleDfuIrq(void) { this->update(0); } + + void setDisconnectCallback(void (*fp)(void)); + void setConnectCallback(void (*fp)(void)); + void setBleUartRxCallback(void (*fp)(char data[], uint16_t len)); + + protected: + // helper + void install_callback(bool enable, int8_t system_id); + + void (*_disconnect_callback)(void); + void (*_connect_callback)(void); + void (*_ble_uart_rx_callback)(char data[], uint16_t len); +}; +#endif diff --git a/tmk_core/protocol/bluefruit_le/BLEBattery.cpp b/tmk_core/protocol/bluefruit_le/BLEBattery.cpp new file mode 100644 index 000000000000..1d9929d3c76e --- /dev/null +++ b/tmk_core/protocol/bluefruit_le/BLEBattery.cpp @@ -0,0 +1,103 @@ +/**************************************************************************/ +/*! + Modified from the original Adafruit_BLEBatterry.h which was released under + the BSD License (below). + + Software License Agreement (BSD License) + + Copyright (c) 2020, Joshua Rubin + Copyright (c) 2016, Adafruit Industries (adafruit.com) + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holders nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +/**************************************************************************/ + +#include "BLEBattery.h" +#include "timer.h" + +/******************************************************************************/ +/*! + @brief Constructor +*/ +/******************************************************************************/ +BLEBattery::BLEBattery(BLE& ble) : _ble(ble) {} + +/******************************************************************************/ +/*! + @brief Enable Battery service if not already enabled + @param reset true will reset Bluefruit +*/ +/******************************************************************************/ +bool BLEBattery::begin(bool reset) { + int32_t enabled = 0; + VERIFY_(_ble.atcommandIntReply(F("AT+BLEBATTEN"), &enabled)); + if (enabled) return true; + + VERIFY_(_ble.atcommand(F("AT+BLEBATTEN=1"))); + + // Perform Bluefruit reset if needed + if (reset) _ble.reset(); + + return true; +} + +/******************************************************************************/ +/*! + @brief Stop Battery service if it is enabled + @param reset true will reset Bluefruit +*/ +/******************************************************************************/ +bool BLEBattery::stop(bool reset) { + int32_t enabled = 0; + VERIFY_(_ble.atcommandIntReply(F("AT+BLEBATTEN"), &enabled)); + if (!enabled) return true; + + VERIFY_(_ble.atcommand(F("AT+BLEBATTEN=0"))); + + // Perform Bluefruit reset if needed + if (reset) _ble.reset(); + + return true; +} + +static inline bool is_within(uint32_t lower, uint32_t value, uint32_t upper) { return (lower <= value) && (value <= upper); } + +/******************************************************************************/ +/*! + @brief Update Battery level value + @param percent Battery value in percentage 0-100 +*/ +/******************************************************************************/ +bool BLEBattery::update(uint8_t percent, uint32_t period_ms) { + static uint32_t tt = 0; + + VERIFY_(is_within(0, percent, 100)); + + if (!period_ms || timer_elapsed32(tt) > period_ms) { + if (period_ms) tt = timer_read32(); + return _ble.atcommand(F("AT+BLEBATTVAL"), percent); + } + + return false; +} diff --git a/tmk_core/protocol/bluefruit_le/BLEBattery.h b/tmk_core/protocol/bluefruit_le/BLEBattery.h new file mode 100644 index 000000000000..cfe42f39aa02 --- /dev/null +++ b/tmk_core/protocol/bluefruit_le/BLEBattery.h @@ -0,0 +1,51 @@ +/**************************************************************************/ +/*! + Modified from the original Adafruit_BLEBatterry.h which was released under + the BSD License (below). + + Software License Agreement (BSD License) + + Copyright (c) 2020, Joshua Rubin + Copyright (c) 2016, Adafruit Industries (adafruit.com) + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holders nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +/**************************************************************************/ + +#pragma once + +#include "BLE.h" + +class BLEBattery { + private: + BLE& _ble; + + public: + BLEBattery(BLE& ble); + + bool begin(bool reset = true); + bool stop(bool reset = true); + + bool update(uint8_t percent, uint32_t period_ms = 5000); +}; diff --git a/tmk_core/protocol/bluefruit_le/BluefruitLE_UART.cpp b/tmk_core/protocol/bluefruit_le/BluefruitLE_UART.cpp new file mode 100644 index 000000000000..0803827ff314 --- /dev/null +++ b/tmk_core/protocol/bluefruit_le/BluefruitLE_UART.cpp @@ -0,0 +1,170 @@ +/**************************************************************************/ +/*! + Modified from the original Adafruit_BluefruitLE_UART.cpp which was + released under the BSD License (below). + + Software License Agreement (BSD License) + + Copyright (c) 2020, Joshua Rubin + Copyright (c) 2015, Adafruit Industries (adafruit.com) + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holders nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +/**************************************************************************/ + +#include "BluefruitLE_UART.h" +#include "HardwareSerial.h" +#include "wait.h" +#include "print.h" + +/******************************************************************************/ +/*! + @brief Instantiates a new instance of the BluefruitLE_UART class +*/ +/******************************************************************************/ +BluefruitLE_UART::BluefruitLE_UART() { _physical_transport = BLUEFRUIT_TRANSPORT_HWUART; } + +/******************************************************************************/ +/*! + @brief Class's Destructor +*/ +/******************************************************************************/ +BluefruitLE_UART::~BluefruitLE_UART() { end(); } + +/******************************************************************************/ +/*! + @brief Initialize the HW to enable communication with the BLE module + + @return Returns 'true' if everything initialised correctly, otherwise + 'false' if there was a problem during HW initialisation. If + 'irqPin' is not a HW interrupt pin false will be returned. +*/ +/******************************************************************************/ +bool BluefruitLE_UART::begin(uint32_t baud, bool debug, bool blocking) { + _verbose = debug; + + Serial.begin(baud); + Serial.setTimeout(_timeout); + + // reset Bluefruit module upon connect + return reset(blocking); +} + +/******************************************************************************/ +/*! + @brief Uninitializes the SPI interface +*/ +/******************************************************************************/ +void BluefruitLE_UART::end(void) { Serial.end(); } + +/******************************************************************************/ +/*! + @brief Set the hardware MODE Pin if it is enabled, or performs a SW based + mode switch if no MODE pin is available (SPI Friend, etc.) + + @param[in] mode + The mode to change to, either BLUEFRUIT_MODE_COMMAND or + BLUEFRUIT_MODE_DATA + + @return true if the mode switch was successful, otherwise false +*/ +/******************************************************************************/ +bool BluefruitLE_UART::setMode(uint8_t new_mode) { + // invalid mode + if (!(new_mode == BLUEFRUIT_MODE_COMMAND || new_mode == BLUEFRUIT_MODE_DATA)) return false; + + bool isOK; + + // Switch mode using +++ command, at worst switch 2 times + int32_t updated_mode; + + isOK = atcommandIntReply(F("+++"), &updated_mode); + + if (isOK) { + // Ahhh, we are already in the wanted mode before sending +++ + // Switch again. This is required to make sure it is always correct + if (updated_mode != new_mode) { + isOK = atcommandIntReply(F("+++"), &updated_mode); + // Still does not match -> give up + if (updated_mode != new_mode) return false; + } + } + + _mode = new_mode; + + return isOK; +} + +/******************************************************************************/ +/*! + @brief Print API. Either buffer the data internally or send it to bus + if possible. \r and \n are command terminators and will force the + packet to be sent to the Bluefruit LE module. + + @param[in] c + Character to send +*/ +/******************************************************************************/ +size_t BluefruitLE_UART::write(uint8_t c) { + if (_verbose) xprintf("%c", c); + wait_us(50); + return Serial.write(c); +} + +/******************************************************************************/ +/*! + @brief Check if the response from the previous command is ready + + @return 'true' if a response is ready, otherwise 'false' +*/ +/******************************************************************************/ +int BluefruitLE_UART::available(void) { return Serial.available(); } + +/******************************************************************************/ +/*! + @brief Get a byte from response data, perform SPI transaction if needed + + @return -1 if no data is available +*/ +/******************************************************************************/ +int BluefruitLE_UART::read(void) { return Serial.read(); } + +/******************************************************************************/ +/*! + @brief Get a byte from response without removing it, perform SPI transaction + if needed + + @return -1 if no data is available +*/ +/******************************************************************************/ +int BluefruitLE_UART::peek(void) { return Serial.peek(); } + +/******************************************************************************/ +/*! + @brief Flush current response data in the internal FIFO + + @return -1 if no data is available +*/ +/******************************************************************************/ +void BluefruitLE_UART::flush(void) { Serial.flush(); } diff --git a/tmk_core/protocol/bluefruit_le/BluefruitLE_UART.h b/tmk_core/protocol/bluefruit_le/BluefruitLE_UART.h new file mode 100644 index 000000000000..653250cdc668 --- /dev/null +++ b/tmk_core/protocol/bluefruit_le/BluefruitLE_UART.h @@ -0,0 +1,70 @@ +/**************************************************************************/ +/*! + Modified from the original Adafruit_BluefruitLE_UART.h which was released under + the BSD License (below). + + Software License Agreement (BSD License) + + Copyright (c) 2020, Joshua Rubin + Copyright (c) 2015, Adafruit Industries (adafruit.com) + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holders nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +/**************************************************************************/ + +#pragma once + +#include "BLE.h" + +#ifdef __cplusplus + +# ifndef AdafruitBleBaud +# define AdafruitBleBaud 9600 +# endif + +class BluefruitLE_UART : public BLE { + public: + BluefruitLE_UART(); + + virtual ~BluefruitLE_UART(); + + // HW initialisation + bool begin(uint32_t baud = 9600, bool debug = false, bool blocking = true); + void end(void); + + bool setMode(uint8_t new_mode); + + // Class Print virtual function Interface + virtual size_t write(uint8_t c); + + // pull in write(str) and write(buf, size) from Print + using Print::write; + + // Class Stream interface + virtual int available(void); + virtual int read(void); + virtual void flush(void); + virtual int peek(void); +}; +#endif diff --git a/tmk_core/protocol/bluefruit_le/HardwareSerial.cpp b/tmk_core/protocol/bluefruit_le/HardwareSerial.cpp new file mode 100644 index 000000000000..f609fc35b9ca --- /dev/null +++ b/tmk_core/protocol/bluefruit_le/HardwareSerial.cpp @@ -0,0 +1,194 @@ +/* + Modified from the original HardwareSerial.h which was released under the GPLv2 + License. + + Copyright (c) 2020 Joshua Rubin + Copyright (c) 2013 Matthijs Kooijman + Copyright (c) 2012 Alarus + Copyright (c) 2010 Mark Sproul + Copyright (c) 2006 David A. Mellis + Copyright (c) 2006 Nicholas Zambetti + All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include +#include + +#include "HardwareSerial.h" +#include "HardwareSerial_private.h" + +// Actual interrupt handlers ////////////////////////////////////////////////////////////// + +void HardwareSerial::_tx_udr_empty_irq(void) { + // If interrupts are enabled, there must be more data in the output + // buffer. Send the next byte + unsigned char c = _tx_buffer[_tx_buffer_tail]; + _tx_buffer_tail = (_tx_buffer_tail + 1) % SERIAL_TX_BUFFER_SIZE; + + UDRn = c; + + // clear the TXCn bit -- "can be cleared by writing a one to its bit + // location". This makes sure flush() won't return until the bytes + // actually got written. Other r/w bits are preserved, and zeroes + // written to the rest. + + UCSRnA = ((UCSRnA) & ((1 << U2Xn) | (1 << TXCn))); + + if (_tx_buffer_head == _tx_buffer_tail) { + // Buffer empty, so disable interrupts + cbi(UCSRnB, UDRIEn); + } +} + +// Public Methods ////////////////////////////////////////////////////////////// + +void HardwareSerial::begin(unsigned long baud, uint8_t config) { + uint16_t baud_setting = (F_CPU / 4 / baud - 1) / 2; + UCSRnA = 1 << U2Xn; + + // assign the baud_setting, a.k.a. ubrr (USART Baud Rate Register) + UBRRnH = baud_setting >> 8; + UBRRnL = baud_setting; + + _written = false; + + // set the data bits, parity, and stop bits + UCSRnC = config; + + sbi(UCSRnB, RXENn); + sbi(UCSRnB, TXENn); + sbi(UCSRnB, RXCIEn); + cbi(UCSRnB, UDRIEn); +} + +void HardwareSerial::end() { + // wait for transmission of outgoing data + flush(); + + cbi(UCSRnB, RXENn); + cbi(UCSRnB, TXENn); + cbi(UCSRnB, RXCIEn); + cbi(UCSRnB, UDRIEn); + + // clear any received data + _rx_buffer_head = _rx_buffer_tail; +} + +int HardwareSerial::available(void) { return ((unsigned int)(SERIAL_RX_BUFFER_SIZE + _rx_buffer_head - _rx_buffer_tail)) % SERIAL_RX_BUFFER_SIZE; } + +int HardwareSerial::peek(void) { + if (_rx_buffer_head == _rx_buffer_tail) { + return -1; + } + + return _rx_buffer[_rx_buffer_tail]; +} + +int HardwareSerial::read(void) { + // if the head isn't ahead of the tail, we don't have any characters + if (_rx_buffer_head == _rx_buffer_tail) { + return -1; + } + + unsigned char c = _rx_buffer[_rx_buffer_tail]; + _rx_buffer_tail = (rx_buffer_index_t)(_rx_buffer_tail + 1) % SERIAL_RX_BUFFER_SIZE; + return c; +} + +int HardwareSerial::availableForWrite(void) { + tx_buffer_index_t head = _tx_buffer_head; + tx_buffer_index_t tail = _tx_buffer_tail; + + if (head >= tail) return SERIAL_TX_BUFFER_SIZE - 1 - head + tail; + return tail - head - 1; +} + +void HardwareSerial::flush() { + // If we have never written a byte, no need to flush. This special + // case is needed since there is no way to force the TXCn (transmit + // complete) bit to 1 during initialization + if (!_written) return; + + while (bit_is_set(UCSRnB, UDRIEn) || bit_is_clear(UCSRnA, TXCn)) { + if (bit_is_clear(SREG, SREG_I) && bit_is_set(UCSRnB, UDRIEn)) + // Interrupts are globally disabled, but the DR empty + // interrupt should be enabled, so poll the DR empty flag to + // prevent deadlock + if (bit_is_set(UCSRnA, UDREn)) _tx_udr_empty_irq(); + } + // If we get here, nothing is queued anymore (DRIE is disabled) and + // the hardware finished tranmission (TXCn is set). +} + +size_t HardwareSerial::write(uint8_t c) { + _written = true; + + // If the buffer and the data register is empty, just write the byte + // to the data register and be done. This shortcut helps + // significantly improve the effective datarate at high (> + // 500kbit/s) bitrates, where interrupt overhead becomes a slowdown. + if (_tx_buffer_head == _tx_buffer_tail && bit_is_set(UCSRnA, UDREn)) { + // If TXCn is cleared before writing UDR and the previous byte + // completes before writing to UDR, TXCn will be set but a byte + // is still being transmitted causing flush() to return too soon. + // So writing UDR must happen first. + // Writing UDR and clearing TC must be done atomically, otherwise + // interrupts might delay the TXCn clear so the byte written to UDR + // is transmitted (setting TXCn) before clearing TXCn. Then TXCn will + // be cleared when no bytes are left, causing flush() to hang + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + UDRn = c; + UCSRnA = ((UCSRnA) & ((1 << U2Xn) | (1 << TXCn))); + } + return 1; + } + tx_buffer_index_t i = (_tx_buffer_head + 1) % SERIAL_TX_BUFFER_SIZE; + + // If the output buffer is full, there's nothing for it other than to + // wait for the interrupt handler to empty it a bit + while (i == _tx_buffer_tail) { + if (bit_is_clear(SREG, SREG_I)) { + // Interrupts are disabled, so we'll have to poll the data + // register empty flag ourselves. If it is set, pretend an + // interrupt has happened and call the handler to free up + // space for us. + if (bit_is_set(UCSRnA, UDREn)) _tx_udr_empty_irq(); + } else { + // nop, the interrupt handler will free up space for us + } + } + + _tx_buffer[_tx_buffer_head] = c; + + // make atomic to prevent execution of ISR between setting the + // head pointer and setting the interrupt flag resulting in buffer + // retransmission + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + _tx_buffer_head = i; + sbi(UCSRnB, UDRIEn); + } + + return 1; +} + +HardwareSerial Serial; + +bool Serial_available() { return Serial.available(); } + +ISR(USARTn_RX_vect) { Serial._rx_complete_irq(); } + +ISR(USARTn_UDRE_vect) { Serial._tx_udr_empty_irq(); } diff --git a/tmk_core/protocol/bluefruit_le/HardwareSerial.h b/tmk_core/protocol/bluefruit_le/HardwareSerial.h new file mode 100644 index 000000000000..d922d7a3940f --- /dev/null +++ b/tmk_core/protocol/bluefruit_le/HardwareSerial.h @@ -0,0 +1,115 @@ +/* + Modified from the original HardwareSerial.h which was released under the GPLv2 + License. + + Copyright (c) 2020 Joshua Rubin + Copyright (c) 2013 by Matthijs Kooijman + Copyright (c) 2012 by Alarus + Copyright (c) 2010 by Mark Sproul + Copyright (c) 2006 Nicholas Zambetti + All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#pragma once + +#include + +#include "Stream.h" + +/* +// Define constants and variables for buffering incoming serial data. We're +// using a ring buffer (I think), in which head is the index of the location +// to which to write the next incoming character and tail is the index of the +// location from which to read. +// NOTE: a "power of 2" buffer size is reccomended to dramatically +// optimize all the modulo operations for ring buffers. +// WARNING: When buffer sizes are increased to > 256, the buffer index +// variables are automatically increased in size, but the extra +// atomicity guards needed for that are not implemented. This will +// often work, but occasionally a race condition can occur that makes +// Serial behave erratically. See https://github.com/arduino/Arduino/issues/2405 +*/ +#define SERIAL_TX_BUFFER_SIZE 64 +#define SERIAL_RX_BUFFER_SIZE 64 +typedef uint8_t tx_buffer_index_t; +typedef uint8_t rx_buffer_index_t; + +// Define config for Serial.begin(baud, config); +#define SERIAL_5N1 0x00 +#define SERIAL_6N1 0x02 +#define SERIAL_7N1 0x04 +#define SERIAL_8N1 0x06 +#define SERIAL_5N2 0x08 +#define SERIAL_6N2 0x0A +#define SERIAL_7N2 0x0C +#define SERIAL_8N2 0x0E +#define SERIAL_5E1 0x20 +#define SERIAL_6E1 0x22 +#define SERIAL_7E1 0x24 +#define SERIAL_8E1 0x26 +#define SERIAL_5E2 0x28 +#define SERIAL_6E2 0x2A +#define SERIAL_7E2 0x2C +#define SERIAL_8E2 0x2E +#define SERIAL_5O1 0x30 +#define SERIAL_6O1 0x32 +#define SERIAL_7O1 0x34 +#define SERIAL_8O1 0x36 +#define SERIAL_5O2 0x38 +#define SERIAL_6O2 0x3A +#define SERIAL_7O2 0x3C +#define SERIAL_8O2 0x3E + +class HardwareSerial : public Stream { + protected: + // Has any byte been written to the UART since begin() + bool _written; + + volatile rx_buffer_index_t _rx_buffer_head; + volatile rx_buffer_index_t _rx_buffer_tail; + volatile tx_buffer_index_t _tx_buffer_head; + volatile tx_buffer_index_t _tx_buffer_tail; + + // Don't put any members after these buffers, since only the first + // 32 bytes of this struct can be accessed quickly using the ldd + // instruction. + unsigned char _rx_buffer[64]; + unsigned char _tx_buffer[SERIAL_TX_BUFFER_SIZE]; + + public: + inline HardwareSerial(); + void begin(unsigned long baud) { begin(baud, SERIAL_8N1); } + void begin(unsigned long, uint8_t); + void end(); + virtual int available(void); + virtual int peek(void); + virtual int read(void); + virtual int availableForWrite(void); + virtual void flush(void); + virtual size_t write(uint8_t); + inline size_t write(unsigned long n) { return write((uint8_t)n); } + inline size_t write(long n) { return write((uint8_t)n); } + inline size_t write(unsigned int n) { return write((uint8_t)n); } + inline size_t write(int n) { return write((uint8_t)n); } + using Print::write; // pull in write(str) and write(buf, size) from Print + + // Interrupt handlers - Not intended to be called externally + inline void _rx_complete_irq(void); + void _tx_udr_empty_irq(void); +}; + +extern HardwareSerial Serial; diff --git a/tmk_core/protocol/bluefruit_le/HardwareSerial_private.h b/tmk_core/protocol/bluefruit_le/HardwareSerial_private.h new file mode 100644 index 000000000000..117b305c140f --- /dev/null +++ b/tmk_core/protocol/bluefruit_le/HardwareSerial_private.h @@ -0,0 +1,117 @@ +/* + HardwareSerial_private.h - Hardware serial library for Wiring + Copyright (c) 2006 Nicholas Zambetti. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Modified 23 November 2006 by David A. Mellis + Modified 28 September 2010 by Mark Sproul + Modified 14 August 2012 by Alarus +*/ + +#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega168P__) || defined(__AVR_ATmega328P__) +# define UDRn UDR0 +# define UBRRnL UBRR0L +# define UBRRnH UBRR0H +# define UCSRnA UCSR0A +# define UCSRnB UCSR0B +# define UCSRnC UCSR0C +# define U2Xn U2X0 +# define RXENn RXEN0 +# define TXENn TXEN0 +# define RXCIEn RXCIE0 +# define UCSZn1 UCSZ01 +# define UCSZn0 UCSZ00 +# define UDRIEn UDRIE0 +# define USARTn_UDRE_vect USART_UDRE_vect +# define USARTn_RX_vect USART_RX_vect +# define UPEn UPE0 +# define TXCn TXC0 +# define UDREn UDRE0 +#elif defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega32U2__) || defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB647__) || defined(__AVR_AT90USB1286__) || defined(__AVR_AT90USB1287__) +# define UDRn UDR1 +# define UBRRnL UBRR1L +# define UBRRnH UBRR1H +# define UCSRnA UCSR1A +# define UCSRnB UCSR1B +# define UCSRnC UCSR1C +# define U2Xn U2X1 +# define RXENn RXEN1 +# define TXENn TXEN1 +# define RXCIEn RXCIE1 +# define UCSZn1 UCSZ11 +# define UCSZn0 UCSZ10 +# define UDRIEn UDRIE1 +# define USARTn_UDRE_vect USART1_UDRE_vect +# define USARTn_RX_vect USART1_RX_vect +# define UPEn UPE1 +# define TXCn TXC1 +# define UDREn UDRE1 +#elif defined(__AVR_ATmega32A__) +# define UDRn UDR +# define UBRRnL UBRRL +# define UBRRnH UBRRH +# define UCSRnA UCSRA +# define UCSRnB UCSRB +# define UCSRnC UCSRC +# define U2Xn U2X +# define RXENn RXEN +# define TXENn TXEN +# define RXCIEn RXCIE +# define UCSZn1 UCSZ1 +# define UCSZn0 UCSZ0 +# define UDRIEn UDRIE +# define USARTn_UDRE_vect USART_UDRE_vect +# define USARTn_RX_vect USART_RX_vect +# define UPEn UPE +# define TXCn TXC +# define UDREn UDRE +#endif + +#ifndef cbi +# define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) +#endif +#ifndef sbi +# define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) +#endif + +// Constructors //////////////////////////////////////////////////////////////// + +HardwareSerial::HardwareSerial() : _rx_buffer_head(0), _rx_buffer_tail(0), _tx_buffer_head(0), _tx_buffer_tail(0) {} + +// Actual interrupt handlers ////////////////////////////////////////////////////////////// + +void HardwareSerial::_rx_complete_irq(void) { + if (bit_is_clear(UCSRnA, UPEn)) { + // No Parity error, read byte and store it in the buffer if there is + // room + unsigned char c = UDRn; + rx_buffer_index_t i = (unsigned int)(_rx_buffer_head + 1) % SERIAL_RX_BUFFER_SIZE; + + // if we should be storing the received character into the location + // just before the tail (meaning that the head would advance to the + // current location of the tail), we're about to overflow the buffer + // and so we don't write the character or advance the head. + if (i != _rx_buffer_tail) { + _rx_buffer[_rx_buffer_head] = c; + _rx_buffer_head = i; + } + } else { + // Parity error, read byte but discard it + UDRn; + }; +} + +// #endif // whole file diff --git a/tmk_core/protocol/bluefruit_le/Print.cpp b/tmk_core/protocol/bluefruit_le/Print.cpp new file mode 100644 index 000000000000..2e720ca2fadc --- /dev/null +++ b/tmk_core/protocol/bluefruit_le/Print.cpp @@ -0,0 +1,205 @@ +/* + Modified from the original Print.h which was released under + the BSD License (below). + + Copyright (c) 2020 Joshua Rubin + Copyright (c) 2008 David A. Mellis + All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include +#include "Print.h" + +// Public Methods ////////////////////////////////////////////////////////////// + +/* default implementation: may be overridden */ +size_t Print::write(const uint8_t *buffer, size_t size) { + size_t n = 0; + while (size--) { + if (write(*buffer++)) + n++; + else + break; + } + return n; +} + +size_t Print::pprint(const __FlashStringHelper *ifsh) { + PGM_P p = reinterpret_cast(ifsh); + size_t n = 0; + while (1) { + unsigned char c = pgm_read_byte(p++); + if (c == 0) break; + if (write(c)) + n++; + else + break; + } + return n; +} + +size_t Print::pprint(const char str[]) { return write(str); } + +size_t Print::pprint(char c) { return write(c); } + +size_t Print::pprint(unsigned char b, int base) { return pprint((unsigned long)b, base); } + +size_t Print::pprint(int n, int base) { return pprint((long)n, base); } + +size_t Print::pprint(unsigned int n, int base) { return pprint((unsigned long)n, base); } + +size_t Print::pprint(long n, int base) { + if (base == 0) { + return write(n); + } else if (base == 10) { + if (n < 0) { + int t = pprint('-'); + n = -n; + return printNumber(n, 10) + t; + } + return printNumber(n, 10); + } else { + return printNumber(n, base); + } +} + +size_t Print::pprint(unsigned long n, int base) { + if (base == 0) + return write(n); + else + return printNumber(n, base); +} + +size_t Print::pprint(double n, int digits) { return printFloat(n, digits); } + +size_t Print::pprintln(const __FlashStringHelper *ifsh) { + size_t n = pprint(ifsh); + n += pprintln(); + return n; +} + +size_t Print::pprintln(void) { return write("\r\n"); } + +size_t Print::pprintln(const char c[]) { + size_t n = pprint(c); + n += pprintln(); + return n; +} + +size_t Print::pprintln(char c) { + size_t n = pprint(c); + n += pprintln(); + return n; +} + +size_t Print::pprintln(unsigned char b, int base) { + size_t n = pprint(b, base); + n += pprintln(); + return n; +} + +size_t Print::pprintln(int num, int base) { + size_t n = pprint(num, base); + n += pprintln(); + return n; +} + +size_t Print::pprintln(unsigned int num, int base) { + size_t n = pprint(num, base); + n += pprintln(); + return n; +} + +size_t Print::pprintln(long num, int base) { + size_t n = pprint(num, base); + n += pprintln(); + return n; +} + +size_t Print::pprintln(unsigned long num, int base) { + size_t n = pprint(num, base); + n += pprintln(); + return n; +} + +size_t Print::pprintln(double num, int digits) { + size_t n = pprint(num, digits); + n += pprintln(); + return n; +} + +// Private Methods ///////////////////////////////////////////////////////////// + +size_t Print::printNumber(unsigned long n, uint8_t base) { + char buf[8 * sizeof(long) + 1]; // Assumes 8-bit chars plus zero byte. + char *str = &buf[sizeof(buf) - 1]; + + *str = '\0'; + + // prevent crash if called with base == 1 + if (base < 2) base = 10; + + do { + char c = n % base; + n /= base; + + *--str = c < 10 ? c + '0' : c + 'A' - 10; + } while (n); + + return write(str); +} + +size_t Print::printFloat(double number, uint8_t digits) { + size_t n = 0; + + if (isnan(number)) return pprint("nan"); + if (isinf(number)) return pprint("inf"); + if (number > 4294967040.0) return pprint("ovf"); // constant determined empirically + if (number < -4294967040.0) return pprint("ovf"); // constant determined empirically + + // Handle negative numbers + if (number < 0.0) { + n += pprint('-'); + number = -number; + } + + // Round correctly so that pprint(1.999, 2) prints as "2.00" + double rounding = 0.5; + for (uint8_t i = 0; i < digits; ++i) rounding /= 10.0; + + number += rounding; + + // Extract the integer part of the number and print it + unsigned long int_part = (unsigned long)number; + double remainder = number - (double)int_part; + n += pprint(int_part); + + // Print the decimal point, but only if there are digits beyond + if (digits > 0) { + n += pprint('.'); + } + + // Extract digits from the remainder one at a time + while (digits-- > 0) { + remainder *= 10.0; + unsigned int toPrint = (unsigned int)(remainder); + n += pprint(toPrint); + remainder -= toPrint; + } + + return n; +} diff --git a/tmk_core/protocol/bluefruit_le/Print.h b/tmk_core/protocol/bluefruit_le/Print.h new file mode 100644 index 000000000000..7cd4157999cc --- /dev/null +++ b/tmk_core/protocol/bluefruit_le/Print.h @@ -0,0 +1,84 @@ +/* + Modified from the original Print.h which was released under + the BSD License (below). + + Copyright (c) 2020 Joshua Rubin + Copyright (c) 2008 David A. Mellis + All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#pragma once + +#include // for size_t +#include +#include + +#define DEC 10 +#define HEX 16 +#define OCT 8 +#ifdef BIN // Prevent warnings if BIN is previously defined in "iotnx4.h" or similar +# undef BIN +#endif +#define BIN 2 + +class __FlashStringHelper; +#define F(string_literal) (reinterpret_cast(PSTR(string_literal))) + +class Print { + private: + size_t printNumber(unsigned long, uint8_t); + size_t printFloat(double, uint8_t); + + public: + Print() {} + + virtual size_t write(uint8_t) = 0; + size_t write(const char *str) { + if (str == NULL) return 0; + return write((const uint8_t *)str, strlen(str)); + } + virtual size_t write(const uint8_t *buffer, size_t size); + size_t write(const char *buffer, size_t size) { return write((const uint8_t *)buffer, size); } + + // default to zero, meaning "a single write may block" + // should be overriden by subclasses with buffering + virtual int availableForWrite() { return 0; } + + size_t pprint(const __FlashStringHelper *); + size_t pprint(const char[]); + size_t pprint(char); + size_t pprint(unsigned char, int = DEC); + size_t pprint(int, int = DEC); + size_t pprint(unsigned int, int = DEC); + size_t pprint(long, int = DEC); + size_t pprint(unsigned long, int = DEC); + size_t pprint(double, int = 2); + + size_t pprintln(const __FlashStringHelper *); + size_t pprintln(const char[]); + size_t pprintln(char); + size_t pprintln(unsigned char, int = DEC); + size_t pprintln(int, int = DEC); + size_t pprintln(unsigned int, int = DEC); + size_t pprintln(long, int = DEC); + size_t pprintln(unsigned long, int = DEC); + size_t pprintln(double, int = 2); + size_t pprintln(void); + + /* Empty implementation for backward compatibility */ + virtual void flush() {} +}; diff --git a/tmk_core/protocol/bluefruit_le/Stream.cpp b/tmk_core/protocol/bluefruit_le/Stream.cpp new file mode 100644 index 000000000000..8d5f288c3ae7 --- /dev/null +++ b/tmk_core/protocol/bluefruit_le/Stream.cpp @@ -0,0 +1,261 @@ +/* + Modified from the original Stream.cpp which was released under the GPLv2 + License. + + Copyright (c) 2020 Joshua Rubin + Copyright (c) 2008 David A. Mellis + All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + parsing functions based on TextFinder library by Michael Margolis +*/ + +#include "Stream.h" +#include "timer.h" + +// protected method to read stream with timeout +int Stream::timedRead() { + int c; + _startMillis = timer_read32(); + do { + c = read(); + if (c >= 0) return c; + } while (timer_elapsed32(_startMillis) < _timeout); + return -1; // -1 indicates timeout +} + +// protected method to peek stream with timeout +int Stream::timedPeek() { + int c; + _startMillis = timer_read32(); + do { + c = peek(); + if (c >= 0) return c; + } while (timer_elapsed32(_startMillis) < _timeout); + return -1; // -1 indicates timeout +} + +// returns peek of the next digit in the stream or -1 if timeout +// discards non-numeric characters +int Stream::peekNextDigit(LookaheadMode lookahead, bool detectDecimal) { + int c; + while (1) { + c = timedPeek(); + + if (c < 0 || c == '-' || (c >= '0' && c <= '9') || (detectDecimal && c == '.')) return c; + + switch (lookahead) { + case SKIP_NONE: + return -1; // Fail code. + case SKIP_WHITESPACE: + switch (c) { + case ' ': + case '\t': + case '\r': + case '\n': + break; + default: + return -1; // Fail code. + } + case SKIP_ALL: + break; + } + read(); // discard non-numeric + } +} + +// Public Methods +////////////////////////////////////////////////////////////// + +void Stream::setTimeout(unsigned long timeout) { // sets the maximum number of milliseconds to wait + _timeout = timeout; +} + +// find returns true if the target string is found +bool Stream::find(char *target) { return findUntil(target, strlen(target), NULL, 0); } + +// reads data from the stream until the target string of given length is found +// returns true if target string is found, false if timed out +bool Stream::find(char *target, size_t length) { return findUntil(target, length, NULL, 0); } + +// as find but search ends if the terminator string is found +bool Stream::findUntil(char *target, char *terminator) { return findUntil(target, strlen(target), terminator, strlen(terminator)); } + +// reads data from the stream until the target string of the given length is found +// search terminated if the terminator string is found +// returns true if target string is found, false if terminated or timed out +bool Stream::findUntil(char *target, size_t targetLen, char *terminator, size_t termLen) { + if (terminator == NULL) { + MultiTarget t[1] = {{target, targetLen, 0}}; + return findMulti(t, 1) == 0 ? true : false; + } else { + MultiTarget t[2] = {{target, targetLen, 0}, {terminator, termLen, 0}}; + return findMulti(t, 2) == 0 ? true : false; + } +} + +// returns the first valid (long) integer value from the current position. +// lookahead determines how parseInt looks ahead in the stream. +// See LookaheadMode enumeration at the top of the file. +// Lookahead is terminated by the first character that is not a valid part of an integer. +// Once parsing commences, 'ignore' will be skipped in the stream. +long Stream::parseInt(LookaheadMode lookahead, char ignore) { + bool isNegative = false; + long value = 0; + int c; + + c = peekNextDigit(lookahead, false); + // ignore non numeric leading characters + if (c < 0) return 0; // zero returned if timeout + + do { + if (c == ignore) + ; // ignore this character + else if (c == '-') + isNegative = true; + else if (c >= '0' && c <= '9') // is c a digit? + value = value * 10 + c - '0'; + read(); // consume the character we got with peek + c = timedPeek(); + } while ((c >= '0' && c <= '9') || c == ignore); + + if (isNegative) value = -value; + return value; +} + +// as parseInt but returns a floating point value +float Stream::parseFloat(LookaheadMode lookahead, char ignore) { + bool isNegative = false; + bool isFraction = false; + long value = 0; + int c; + float fraction = 1.0; + + c = peekNextDigit(lookahead, true); + // ignore non numeric leading characters + if (c < 0) return 0; // zero returned if timeout + + do { + if (c == ignore) + ; // ignore + else if (c == '-') + isNegative = true; + else if (c == '.') + isFraction = true; + else if (c >= '0' && c <= '9') { // is c a digit? + value = value * 10 + c - '0'; + if (isFraction) fraction *= 0.1; + } + read(); // consume the character we got with peek + c = timedPeek(); + } while ((c >= '0' && c <= '9') || (c == '.' && !isFraction) || c == ignore); + + if (isNegative) value = -value; + if (isFraction) + return value * fraction; + else + return value; +} + +// read characters from stream into buffer +// terminates if length characters have been read, or timeout (see setTimeout) +// returns the number of characters placed in the buffer +// the buffer is NOT null terminated. +size_t Stream::readBytes(char *buffer, size_t length) { + size_t count = 0; + while (count < length) { + int c = timedRead(); + if (c < 0) break; + *buffer++ = (char)c; + count++; + } + return count; +} + +// as readBytes with terminator character +// terminates if length characters have been read, timeout, or if the terminator character detected +// returns the number of characters placed in the buffer (0 means no valid data found) + +size_t Stream::readBytesUntil(char terminator, char *buffer, size_t length) { + size_t index = 0; + while (index < length) { + int c = timedRead(); + if (c < 0 || c == terminator) break; + *buffer++ = (char)c; + index++; + } + return index; // return number of characters, not including null terminator +} + +int Stream::findMulti(struct Stream::MultiTarget *targets, int tCount) { + // any zero length target string automatically matches and would make + // a mess of the rest of the algorithm. + for (struct MultiTarget *t = targets; t < targets + tCount; ++t) { + if (t->len <= 0) return t - targets; + } + + while (1) { + int c = timedRead(); + if (c < 0) return -1; + + for (struct MultiTarget *t = targets; t < targets + tCount; ++t) { + // the simple case is if we match, deal with that first. + if (c == t->str[t->index]) { + if (++t->index == t->len) + return t - targets; + else + continue; + } + + // if not we need to walk back and see if we could have matched further + // down the stream (ie '1112' doesn't match the first position in '11112' + // but it will match the second position so we can't just reset the current + // index to 0 when we find a mismatch. + if (t->index == 0) continue; + + int origIndex = t->index; + do { + --t->index; + // first check if current char works against the new current index + if (c != t->str[t->index]) continue; + + // if it's the only char then we're good, nothing more to check + if (t->index == 0) { + t->index++; + break; + } + + // otherwise we need to check the rest of the found string + int diff = origIndex - t->index; + size_t i; + for (i = 0; i < t->index; ++i) { + if (t->str[i] != t->str[i + diff]) break; + } + + // if we successfully got through the previous loop then our current + // index is good. + if (i == t->index) { + t->index++; + break; + } + + // otherwise we just try the next index + } while (t->index); + } + } + // unreachable + return -1; +} diff --git a/tmk_core/protocol/bluefruit_le/Stream.h b/tmk_core/protocol/bluefruit_le/Stream.h new file mode 100644 index 000000000000..538a0099f6cc --- /dev/null +++ b/tmk_core/protocol/bluefruit_le/Stream.h @@ -0,0 +1,118 @@ +/* + Modified from the original Stream.h which was released under the GPLv2 + License. + + Copyright (c) 2020 Joshua Rubin + Copyright (c) 2010 David A. Mellis + All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + parsing functions based on TextFinder library by Michael Margolis +*/ + +#pragma once + +#include +#include "Print.h" + +// This enumeration provides the lookahead options for parseInt(), parseFloat() +// The rules set out here are used until either the first valid character is found +// or a time out occurs due to lack of input. +enum LookaheadMode { + SKIP_ALL, // All invalid characters are ignored. + SKIP_NONE, // Nothing is skipped, and the stream is not touched unless the first waiting character is valid. + SKIP_WHITESPACE // Only tabs, spaces, line feeds & carriage returns are skipped. +}; + +#define NO_IGNORE_CHAR '\x01' // a char not found in a valid ASCII numeric field + +class Stream : public Print { + protected: + unsigned long _timeout; // number of milliseconds to wait for the next char before aborting timed read + unsigned long _startMillis; // used for timeout measurement + int timedRead(); // read stream with timeout + + int timedPeek(); // peek stream with timeout + int peekNextDigit(LookaheadMode lookahead, bool detectDecimal); // returns the next numeric digit in the stream or -1 if timeout + + public: + virtual int available() = 0; + virtual int read() = 0; + virtual int peek() = 0; + + Stream() { _timeout = 1000; } + + // parsing methods + + void setTimeout(unsigned long timeout); // sets maximum milliseconds to wait for stream data, default is 1 second + + unsigned long getTimeout(void) { return _timeout; } + + bool find(char *target); // reads data from the stream until the target string is found + bool find(uint8_t *target) { return find((char *)target); } + // returns true if target string is found, false if timed out (see setTimeout) + + bool find(char *target, size_t length); // reads data from the stream until the target string of given length is found + bool find(uint8_t *target, size_t length) { return find((char *)target, length); } + // returns true if target string is found, false if timed out + + bool find(char target) { return find(&target, 1); } + + bool findUntil(char *target, char *terminator); // as find but search ends if the terminator string is found + bool findUntil(uint8_t *target, char *terminator) { return findUntil((char *)target, terminator); } + + bool findUntil(char *target, size_t targetLen, char *terminate, size_t termLen); // as above but search ends if the terminate string is found + bool findUntil(uint8_t *target, size_t targetLen, char *terminate, size_t termLen) { return findUntil((char *)target, targetLen, terminate, termLen); } + + long parseInt(LookaheadMode lookahead = SKIP_ALL, char ignore = NO_IGNORE_CHAR); + // returns the first valid (long) integer value from the current position. + // lookahead determines how parseInt looks ahead in the stream. + // See LookaheadMode enumeration at the top of the file. + // Lookahead is terminated by the first character that is not a valid part of an integer. + // Once parsing commences, 'ignore' will be skipped in the stream. + + float parseFloat(LookaheadMode lookahead = SKIP_ALL, char ignore = NO_IGNORE_CHAR); + // float version of parseInt + + size_t readBytes(char *buffer, size_t length); // read chars from stream into buffer + size_t readBytes(uint8_t *buffer, size_t length) { return readBytes((char *)buffer, length); } + // terminates if length characters have been read or timeout (see setTimeout) + // returns the number of characters placed in the buffer (0 means no valid data found) + + size_t readBytesUntil(char terminator, char *buffer, size_t length); // as readBytes with terminator character + size_t readBytesUntil(char terminator, uint8_t *buffer, size_t length) { return readBytesUntil(terminator, (char *)buffer, length); } + // terminates if length characters have been read, timeout, or if the terminator character detected + // returns the number of characters placed in the buffer (0 means no valid data found) + + protected: + long parseInt(char ignore) { return parseInt(SKIP_ALL, ignore); } + float parseFloat(char ignore) { return parseFloat(SKIP_ALL, ignore); } + // These overload exists for compatibility with any class that has derived + // Stream and used parseFloat/Int with a custom ignore character. To keep + // the public API simple, these overload remains protected. + + struct MultiTarget { + const char *str; // string you're searching for + size_t len; // length of string you're searching for + size_t index; // index used by the search routine. + }; + + // This allows you to search for an arbitrary number of strings. + // Returns index of the target that is found first or -1 if timeout occurs. + int findMulti(struct MultiTarget *targets, int tCount); +}; + +#undef NO_IGNORE_CHAR diff --git a/tmk_core/protocol/bluefruit_le/new.cpp b/tmk_core/protocol/bluefruit_le/new.cpp new file mode 100644 index 000000000000..d95743a32a8a --- /dev/null +++ b/tmk_core/protocol/bluefruit_le/new.cpp @@ -0,0 +1,9 @@ +#include + +void *operator new(size_t size) { return malloc(size); } + +void *operator new[](size_t size) { return malloc(size); } + +void operator delete(void *ptr) { free(ptr); } + +void operator delete[](void *ptr) { free(ptr); } diff --git a/tmk_core/protocol/bluefruit_le/qmk.cpp b/tmk_core/protocol/bluefruit_le/qmk.cpp new file mode 100644 index 000000000000..2bf042eeed12 --- /dev/null +++ b/tmk_core/protocol/bluefruit_le/qmk.cpp @@ -0,0 +1,175 @@ +#include "BLE.h" +#include "lufa.h" +#include "print.h" + +#ifdef MODULE_ADAFRUIT_BLE_UART +# include "BluefruitLE_UART.h" +#elif MODULE_ADAFRUIT_BLE_SPI +// TODO +#endif + +#ifdef RGBLIGHT_ENABLE +# include "rgblight.h" +#endif + +#ifdef MOUSE_ENABLE +# include "report.h" +#endif + +#ifdef AdafruitBleBattery +# include "BLEBattery.h" +#endif + +#ifdef MODULE_ADAFRUIT_BLE_UART +static BluefruitLE_UART ble; +#elif MODULE_ADAFRUIT_BLE_SPI +// TODO +#endif + +#ifdef AdafruitBleBattery +BLEBattery battery(ble); +#endif + +static struct { + bool is_connected; + bool configured; + int32_t connectable; + uint8_t USB_DeviceState; +} state; + +void connected(void) { state.is_connected = true; } + +void disconnected(void) { state.is_connected = false; } + +bool adafruit_ble_is_connected(void) { return state.is_connected; } + +// we don't want bt enabled if we're using usb output. this is super annoying on +// phones, for example, when they will not show the on screen keyboard if a bt +// keyboard is connected. +bool set_connectable(bool v) { + if (!!v == !!state.connectable) return true; + state.connectable = v ? 1 : 0; + return ble.atcommand(F("AT+GAPCONNECTABLE"), state.connectable); +} + +bool ble_init() { + state.configured = false; + state.is_connected = false; + state.connectable = 1; + state.USB_DeviceState = -1; + +#ifdef MODULE_ADAFRUIT_BLE_UART + if (!ble.begin(AdafruitBleBaud, AdafruitBleVerbose)) { + return false; + } +#elif MODULE_ADAFRUIT_BLE_SPI +// TODO +#endif + + if (!ble.echo(false)) return false; + if (!ble.atcommand(F("AT+GAPINTERVALS"), "10,30,,")) return false; + if (!ble.atcommand(F("AT+GAPDEVNAME"), STR(PRODUCT))) return false; + if (!ble.atcommand(F("AT+BLEHIDEN"), 1)) return false; + if (!ble.atcommand(F("AT+BLEPOWERLEVEL"), -12)) return false; + +#ifdef AdafruitBleBattery + if (!battery.begin(false)) return false; +#else + // this setting persists across resets, so if we had the battery service + // enabled, it will stay enabled even ifndef AdafruitBleBattery, so we + // forcibly disable it here. + if (!ble.atcommand(F("AT+BLEBATTEN=0"))) return false; +#endif + + if (!ble.reset()) return false; + + ble.setConnectCallback(connected); + ble.setDisconnectCallback(disconnected); + + state.configured = true; + return true; +} + +#ifdef RGBLIGHT_ENABLE +void rgb_update() { + if (USB_DeviceState == state.USB_DeviceState) return; + + state.USB_DeviceState = USB_DeviceState; + + switch (USB_DeviceState) { + case DEVICE_STATE_Configured: + // if rgb showed a solid, non-animated, color when the cord was + // removed, since it doesn't need any timers, it won't turn on when + // the cord is plugged back in. we'll fix that here. + rgblight_restore_from_eeprom(); + break; + default: + // rgb already turns off when the cord is removed, since it loses + // its required 5v, but qmk thinks it's still running. let's just + // stop that. + rgblight_disable_noeeprom(); + } +} +#endif + +void adafruit_ble_task() { + if (!state.configured && !ble_init()) return; + + // ensure bt is only available when not connected to usb + set_connectable(USB_DeviceState != DEVICE_STATE_Configured); + +#ifdef RGBLIGHT_ENABLE + rgb_update(); +#endif + + ble.update(200); + +#ifdef AdafruitBleBattery + // if you have a way of reading the battery level using analog inputs, you + // can use that value as a percentage (uint8_t) here to update the bt + // battery service value + // + // uint8_t percent = 50; + // battery.update(percent); +#endif +} + +bool adafruit_ble_send_keys(uint8_t hid_modifier_mask, uint8_t* keys, uint8_t nkeys) { + char cmdbuf[48]; + char fmtbuf[64]; + + strcpy_P(fmtbuf, PSTR("AT+BLEKEYBOARDCODE=%02x-00-%02x-%02x-%02x-%02x-%02x-%02x")); + + snprintf(cmdbuf, sizeof(cmdbuf), fmtbuf, hid_modifier_mask, keys[0], nkeys >= 1 ? keys[1] : 0, nkeys >= 2 ? keys[2] : 0, nkeys >= 3 ? keys[3] : 0, nkeys >= 4 ? keys[4] : 0, nkeys >= 5 ? keys[5] : 0); + + return ble.atcommand(cmdbuf); +} + +bool adafruit_ble_send_consumer_key(uint16_t keycode, int hold_duration) { + char cmdbuf[48]; + char fmtbuf[64]; + + strcpy_P(fmtbuf, PSTR("AT+BLEHIDCONTROLKEY=0x%04x")); + snprintf(cmdbuf, sizeof(cmdbuf), fmtbuf, keycode); + return ble.atcommand(cmdbuf); +} + +#ifdef MOUSE_ENABLE +bool adafruit_ble_send_mouse_move(int8_t x, int8_t y, int8_t scroll, int8_t pan, uint8_t buttons) { + char cmdbuf[48]; + char fmtbuf[64]; + + strcpy_P(fmtbuf, PSTR("AT+BLEHIDMOUSEMOVE=%d,%d,%d,%d")); + snprintf(cmdbuf, sizeof(cmdbuf), fmtbuf, x, y, scroll, pan); + if (!ble.atcommand(cmdbuf)) { + return false; + } + + strcpy_P(cmdbuf, PSTR("AT+BLEHIDMOUSEBUTTON=")); + if (buttons & MOUSE_BTN1) strcat(cmdbuf, "L"); + if (buttons & MOUSE_BTN2) strcat(cmdbuf, "R"); + if (buttons & MOUSE_BTN3) strcat(cmdbuf, "M"); + if (buttons == 0) strcat(cmdbuf, "0"); + return ble.atcommand(cmdbuf); +} +#endif diff --git a/tmk_core/protocol/bluefruit_le/qmk.h b/tmk_core/protocol/bluefruit_le/qmk.h new file mode 100644 index 000000000000..4a13451b8098 --- /dev/null +++ b/tmk_core/protocol/bluefruit_le/qmk.h @@ -0,0 +1,18 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +extern void adafruit_ble_task(void); +extern bool adafruit_ble_send_keys(uint8_t hid_modifier_mask, uint8_t *keys, uint8_t nkeys); +extern bool adafruit_ble_is_connected(void); +extern bool adafruit_ble_send_consumer_key(uint16_t keycode, int hold_duration); + +#ifdef MOUSE_ENABLE +extern bool adafruit_ble_send_mouse_move(int8_t x, int8_t y, int8_t scroll, int8_t pan, uint8_t buttons); +#endif + +#ifdef __cplusplus +} +#endif diff --git a/tmk_core/protocol/bluefruit_le/sdep.h b/tmk_core/protocol/bluefruit_le/sdep.h new file mode 100644 index 000000000000..739e2b234510 --- /dev/null +++ b/tmk_core/protocol/bluefruit_le/sdep.h @@ -0,0 +1,39 @@ +/******************************************************************************/ +/*! + Modified from the original sdep.h which was released under the BSD License + (below). + + Software License Agreement (BSD License) + + Copyright (c) 2020, Joshua Rubin + Copyright (c) 2013, K. Townsend (microBuilder.eu) + Copyright (c) 2014, Adafruit Industries (adafruit.com) + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holders nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +/******************************************************************************/ +#pragma once + +#define SDEP_MAX_PACKETSIZE 16 // Maximum payload per packet diff --git a/tmk_core/protocol/lufa.mk b/tmk_core/protocol/lufa.mk index d87802992e93..61723d6d1786 100644 --- a/tmk_core/protocol/lufa.mk +++ b/tmk_core/protocol/lufa.mk @@ -29,9 +29,21 @@ ifeq ($(strip $(BLUETOOTH_ENABLE)), yes) endif ifeq ($(strip $(BLUETOOTH)), AdafruitBLE) - LUFA_SRC += spi_master.c - LUFA_SRC += analog.c - LUFA_SRC += $(LUFA_DIR)/adafruit_ble.cpp + LUFA_SRC += spi_master.c + LUFA_SRC += analog.c + LUFA_SRC += $(LUFA_DIR)/adafruit_ble.cpp +endif + +ifeq ($(strip $(BLUETOOTH)), AdafruitBLEUART) + LUFA_SRC += $(TMK_DIR)/protocol/bluefruit_le/new.cpp + LUFA_SRC += $(TMK_DIR)/protocol/bluefruit_le/HardwareSerial.cpp + LUFA_SRC += $(TMK_DIR)/protocol/bluefruit_le/Print.cpp + LUFA_SRC += $(TMK_DIR)/protocol/bluefruit_le/Stream.cpp + LUFA_SRC += $(TMK_DIR)/protocol/bluefruit_le/ATParser.cpp + LUFA_SRC += $(TMK_DIR)/protocol/bluefruit_le/BLE.cpp + LUFA_SRC += $(TMK_DIR)/protocol/bluefruit_le/BluefruitLE_UART.cpp + LUFA_SRC += $(TMK_DIR)/protocol/bluefruit_le/BLEBattery.cpp + LUFA_SRC += $(TMK_DIR)/protocol/bluefruit_le/qmk.cpp endif ifeq ($(strip $(BLUETOOTH)), AdafruitEZKey) diff --git a/tmk_core/protocol/lufa/adafruit_ble.h b/tmk_core/protocol/lufa/adafruit_ble.h index cef46fe9f75a..3cfe3a6c55c9 100644 --- a/tmk_core/protocol/lufa/adafruit_ble.h +++ b/tmk_core/protocol/lufa/adafruit_ble.h @@ -3,7 +3,7 @@ * Supports the Adafruit BLE board built around the nRF51822 chip. */ #pragma once -#ifdef MODULE_ADAFRUIT_BLE +#ifdef MODULE_ADAFRUIT_BLE_SPI # include # include # include @@ -58,4 +58,4 @@ extern bool adafruit_ble_set_power_level(int8_t level); } # endif -#endif // MODULE_ADAFRUIT_BLE +#endif // MODULE_ADAFRUIT_BLE_SPI diff --git a/tmk_core/protocol/lufa/lufa.c b/tmk_core/protocol/lufa/lufa.c index d673841fd55c..9cd1c946c012 100644 --- a/tmk_core/protocol/lufa/lufa.c +++ b/tmk_core/protocol/lufa/lufa.c @@ -66,8 +66,10 @@ extern keymap_config_t keymap_config; #endif #ifdef BLUETOOTH_ENABLE -# ifdef MODULE_ADAFRUIT_BLE +# ifdef MODULE_ADAFRUIT_BLE_SPI # include "adafruit_ble.h" +# elif MODULE_ADAFRUIT_BLE_UART +# include "bluefruit_le/BluefruitLE_UART.h" # else # include "bluetooth.h" # endif @@ -557,9 +559,9 @@ static void send_keyboard(report_keyboard_t *report) { #ifdef BLUETOOTH_ENABLE if (where == OUTPUT_BLUETOOTH || where == OUTPUT_USB_AND_BT) { -# ifdef MODULE_ADAFRUIT_BLE +# if defined(MODULE_ADAFRUIT_BLE_SPI) || defined(MODULE_ADAFRUIT_BLE_UART) adafruit_ble_send_keys(report->mods, report->keys, sizeof(report->keys)); -# elif MODULE_RN42 +# elif defined(MODULE_RN42) bluefruit_serial_send(0xFD); bluefruit_serial_send(0x09); bluefruit_serial_send(0x01); @@ -621,7 +623,7 @@ static void send_mouse(report_mouse_t *report) { # ifdef BLUETOOTH_ENABLE if (where == OUTPUT_BLUETOOTH || where == OUTPUT_USB_AND_BT) { -# ifdef MODULE_ADAFRUIT_BLE +# if defined(MODULE_ADAFRUIT_BLE_SPI) || defined(MODULE_ADAFRUIT_BLE_UART) // FIXME: mouse buttons adafruit_ble_send_mouse_move(report->x, report->y, report->v, report->h, report->buttons); # else @@ -699,9 +701,9 @@ static void send_consumer(uint16_t data) { # ifdef BLUETOOTH_ENABLE if (where == OUTPUT_BLUETOOTH || where == OUTPUT_USB_AND_BT) { -# ifdef MODULE_ADAFRUIT_BLE +# if defined(MODULE_ADAFRUIT_BLE_SPI) || defined(MODULE_ADAFRUIT_BLE_UART) adafruit_ble_send_consumer_key(data, 0); -# elif MODULE_RN42 +# elif defined(MODULE_RN42) static uint16_t last_data = 0; if (data == last_data) return; last_data = data; @@ -994,7 +996,7 @@ int main(void) { MIDI_Device_USBTask(&USB_MIDI_Interface); #endif -#ifdef MODULE_ADAFRUIT_BLE +#if defined(MODULE_ADAFRUIT_BLE_SPI) || defined(MODULE_ADAFRUIT_BLE_UART) adafruit_ble_task(); #endif diff --git a/tmk_core/protocol/lufa/outputselect.c b/tmk_core/protocol/lufa/outputselect.c index b115ea9691ef..28690b7f3a9c 100644 --- a/tmk_core/protocol/lufa/outputselect.c +++ b/tmk_core/protocol/lufa/outputselect.c @@ -14,9 +14,12 @@ along with this program. If not, see . #include "lufa.h" #include "outputselect.h" -#ifdef MODULE_ADAFRUIT_BLE +#ifdef MODULE_ADAFRUIT_BLE_SPI # include "adafruit_ble.h" #endif +#ifdef MODULE_ADAFRUIT_BLE_UART +# include "bluefruit_le/BluefruitLE_UART.h" +#endif uint8_t desired_output = OUTPUT_DEFAULT; @@ -44,7 +47,7 @@ uint8_t auto_detect_output(void) { return OUTPUT_USB; } -#ifdef MODULE_ADAFRUIT_BLE +#if defined(MODULE_ADAFRUIT_BLE_SPI) || defined(MODULE_ADAFRUIT_BLE_UART) if (adafruit_ble_is_connected()) { return OUTPUT_BLUETOOTH; } diff --git a/tmk_core/protocol/serial.h b/tmk_core/protocol/serial.h index 93ac998983fe..32bcfc1bdb32 100644 --- a/tmk_core/protocol/serial.h +++ b/tmk_core/protocol/serial.h @@ -38,10 +38,18 @@ POSSIBILITY OF SUCH DAMAGE. #ifndef SERIAL_H #define SERIAL_H +#ifdef __cplusplus +extern "C" { +#endif + /* host role */ void serial_init(void); uint8_t serial_recv(void); int16_t serial_recv2(void); void serial_send(uint8_t data); +#ifdef __cplusplus +} +#endif + #endif