diff --git a/common_features.mk b/common_features.mk index 8443a759b3a7..9330bd488da2 100644 --- a/common_features.mk +++ b/common_features.mk @@ -456,6 +456,7 @@ ifeq ($(strip $(OLED_DRIVER_ENABLE)), yes) endif include $(DRIVER_PATH)/qwiic/qwiic.mk +include $(DRIVER_PATH)/bluetooth/bluetooth.mk ifeq ($(strip $(UCIS_ENABLE)), yes) OPT_DEFS += -DUCIS_ENABLE diff --git a/drivers/bluetooth/adafruit_ble.cpp b/drivers/bluetooth/adafruit_ble.cpp new file mode 100644 index 000000000000..b5a990b13ba6 --- /dev/null +++ b/drivers/bluetooth/adafruit_ble.cpp @@ -0,0 +1,179 @@ +#include "BLE.h" +#include "debug.h" +#include "outputselect.h" +#include "host_driver.h" +#include "bluetooth.h" +#include "report.h" + +#ifdef MODULE_ADAFRUIT_BLE_UART +# include "BluefruitLE_UART.h" +#elif MODULE_ADAFRUIT_BLE_SPI +# include "BluefruitLE_SPI.h" +#endif + +#ifdef BLUETOOTH_BATTERY_ENABLE +# include "BLEBattery.h" +#endif + +#ifdef MODULE_ADAFRUIT_BLE_UART +static BluefruitLE_UART ble; +#elif MODULE_ADAFRUIT_BLE_SPI +static BluefruitLE_SPI ble; +#endif + +#ifdef BLUETOOTH_BATTERY_ENABLE +BLEBattery battery(ble); +#endif + +static struct { + bool is_connected; + bool configured; + int32_t connectable; + uint8_t USB_DeviceState; +} state; + +void connected(void) { + dprintf("adafruit ble connected\n"); + state.is_connected = true; +} + +void disconnected(void) { + dprintf("adafruit ble disconnected\n"); + state.is_connected = false; +} + +bool bluetooth_is_connected(void) { return state.is_connected; } + +bool reset() { + if (!ble.reset(true)) return false; + + ble.setConnectCallback(connected); + ble.setDisconnectCallback(disconnected); + + // This only works in SPIFRIEND firmware > 0.6.7, which is why + // we check for this conditionally here. + // Note that at the time of writing, HID reports only work correctly + // with Apple products on firmware version 0.6.7! + // https://forums.adafruit.com/viewtopic.php?f=8&t=104052 + if (!ble.usingEvents()) state.is_connected = true; + + return true; +} + +// 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 (state.connectable != -1 && !!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; + + if (!ble.begin()) return false; + + if (!ble.echo(false)) return false; + ble.atcommand(F("AT+GAPINTERVALS"), "10,30,,"); + if (!ble.atcommand(F("AT+GAPDEVNAME"), STR(PRODUCT))) return false; + if (!ble.atcommand(F("AT+BLEHIDEN"), 1)) return false; + ble.atcommand(F("AT+BLEPOWERLEVEL"), -12); + +#ifdef BLUETOOTH_BATTERY_ENABLE + 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 BLUETOOTH_BATTERY_ENABLE, so we + // forcibly disable it here. + ble.atcommand(F("AT+BLEBATTEN=0")); +#endif + + if (!reset()) return false; + + state.configured = true; + return true; +} + +void bluetooth_task() { + if (!state.configured && !ble_init()) return; + + switch (where_to_send()) { + case OUTPUT_NONE: + if (output_auto()) { + // if bluetooth is disconnected, where_to_send returns + // OUTPUT_NONE but if the desired output is OUTPUT_AUTO then we + // must make it possible to connect if usb is not connected + set_connectable(USB_DeviceState != DEVICE_STATE_Configured); + break; + } + + set_connectable(false); + break; + case OUTPUT_USB: + set_connectable(false); + break; + case OUTPUT_BLUETOOTH: + case OUTPUT_USB_AND_BT: + set_connectable(true); + break; + } + + ble.update(200); + +#ifdef BLUETOOTH_BATTERY_ENABLE + battery.update(bluetooth_get_battery_level()); +#endif +} + +void bluetooth_send_keyboard(report_keyboard_t *report) { + char cmdbuf[48]; + char fmtbuf[64]; + + uint8_t * keys = report->keys; + const uint8_t nkeys = sizeof(keys); + + strcpy_P(fmtbuf, PSTR("AT+BLEKEYBOARDCODE=%02x-00-%02x-%02x-%02x-%02x-%02x-%02x")); + + snprintf(cmdbuf, sizeof(cmdbuf), fmtbuf, report->mods, 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); + + ble.atcommand(cmdbuf); +} + +void bluetooth_send_consumer(uint16_t keycode) { + char cmdbuf[48]; + char fmtbuf[64]; + + strcpy_P(fmtbuf, PSTR("AT+BLEHIDCONTROLKEY=0x%04x")); + snprintf(cmdbuf, sizeof(cmdbuf), fmtbuf, keycode); + ble.atcommand(cmdbuf); +} + +#ifdef MOUSE_ENABLE +void bluetooth_send_mouse(report_mouse_t *report) { + char cmdbuf[48]; + char fmtbuf[64]; + + strcpy_P(fmtbuf, PSTR("AT+BLEHIDMOUSEMOVE=%d,%d,%d,%d")); + snprintf(cmdbuf, sizeof(cmdbuf), fmtbuf, report->x, report->y, report->v, report->h); + if (!ble.atcommand(cmdbuf)) { + return; + } + + strcpy_P(cmdbuf, PSTR("AT+BLEHIDMOUSEBUTTON=")); + if (report->buttons & MOUSE_BTN1) strcat(cmdbuf, "L"); + if (report->buttons & MOUSE_BTN2) strcat(cmdbuf, "R"); + if (report->buttons & MOUSE_BTN3) strcat(cmdbuf, "M"); + if (report->buttons == 0) strcat(cmdbuf, "0"); + ble.atcommand(cmdbuf); +} +#endif + +void bluetooth_unpair(void) { + set_connectable(false); + if (!ble.atcommand(F("AT+GAPDELBONDS"))) return; + reset(); // trigger a reset and reconfigure callbacks +} diff --git a/drivers/bluetooth/adafruit_ble/ATParser.cpp b/drivers/bluetooth/adafruit_ble/ATParser.cpp new file mode 100644 index 000000000000..dac51b5fc40d --- /dev/null +++ b/drivers/bluetooth/adafruit_ble/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 "debug.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) dprint("\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) dprint("\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) { + dprintf("%s", buf); + if (replyidx < bufsize) dprintln(); + } + + 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/drivers/bluetooth/adafruit_ble/ATParser.h b/drivers/bluetooth/adafruit_ble/ATParser.h new file mode 100644 index 000000000000..6f68bc39c192 --- /dev/null +++ b/drivers/bluetooth/adafruit_ble/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/drivers/bluetooth/adafruit_ble/BLE.cpp b/drivers/bluetooth/adafruit_ble/BLE.cpp new file mode 100644 index 000000000000..81f54c2e7bcc --- /dev/null +++ b/drivers/bluetooth/adafruit_ble/BLE.cpp @@ -0,0 +1,498 @@ +/**************************************************************************/ +/*! + 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; + + _using_events = false; + + _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(); + + if (waitForOK()) { + _using_events = true; + } + + // 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; + } + + // 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(); + + // Bluefruit need 1 second to reboot + if (blocking) { + wait_ms(1000); + } + + // flush all left over + flush(); + + return isOK; +} + +/******************************************************************************/ +/*! + @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) { + if (!_using_events) return; + + 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); +} + +bool BLE::usingEvents() { return _using_events; } diff --git a/drivers/bluetooth/adafruit_ble/BLE.h b/drivers/bluetooth/adafruit_ble/BLE.h new file mode 100644 index 000000000000..ff770fee9ccf --- /dev/null +++ b/drivers/bluetooth/adafruit_ble/BLE.h @@ -0,0 +1,123 @@ +/**************************************************************************/ +/*! + 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 + +#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, + }; + + bool _using_events; + + public: + // Constructor + BLE(void); + + // Functions implemented in this base class + bool reset(bool blocking = true); + bool factoryReset(bool blocking = true); + + void info(void); + bool echo(bool enable); + bool usingEvents(); + + 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); } + + ///////////////////// + // 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/drivers/bluetooth/adafruit_ble/BLEBattery.cpp b/drivers/bluetooth/adafruit_ble/BLEBattery.cpp new file mode 100644 index 000000000000..1d9929d3c76e --- /dev/null +++ b/drivers/bluetooth/adafruit_ble/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/drivers/bluetooth/adafruit_ble/BLEBattery.h b/drivers/bluetooth/adafruit_ble/BLEBattery.h new file mode 100644 index 000000000000..cfe42f39aa02 --- /dev/null +++ b/drivers/bluetooth/adafruit_ble/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/drivers/bluetooth/adafruit_ble/BluefruitLE_SPI.cpp b/drivers/bluetooth/adafruit_ble/BluefruitLE_SPI.cpp new file mode 100644 index 000000000000..4d6c6c283753 --- /dev/null +++ b/drivers/bluetooth/adafruit_ble/BluefruitLE_SPI.cpp @@ -0,0 +1,473 @@ +/**************************************************************************/ +/*! + Modified from the original Adafruit_BluefruitLE_SPI.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_SPI.h" +#include "quantum.h" +#include "spi_master.h" +#include + +#ifndef min +# define min(a, b) ((a) < (b) ? (a) : (b)) +#endif + +#define SPI_IGNORED_BYTE 0xFEu /**< SPI default character. Character clocked out in case of an ignored transaction. */ +#define SPI_OVERREAD_BYTE 0xFFu /**< SPI over-read character. Character clocked out after an over-read of the transmit buffer. */ +#define SPI_DEFAULT_DELAY_US 50 + +#define memclr(buffer, size) memset(buffer, 0, size) + +#ifndef AdafruitBleResetPin +# define AdafruitBleResetPin D4 +#endif + +#ifndef AdafruitBleCSPin +# define AdafruitBleCSPin B4 +#endif + +#ifndef AdafruitBleIRQPin +# define AdafruitBleIRQPin E6 +#endif + +#ifndef AdafruitBleSpiClockSpeed +# define AdafruitBleSpiClockSpeed 4000000UL // SCK frequency +#endif + +#define SCK_DIVISOR (F_CPU / AdafruitBleSpiClockSpeed) + +#define lowByte(w) ((uint8_t)((w)&0xff)) +#define highByte(w) ((uint8_t)((w) >> 8)) + +unsigned int makeWord(unsigned int w) { return w; } +unsigned int makeWord(unsigned char h, unsigned char l) { return (h << 8) | l; } +#define word(...) makeWord(__VA_ARGS__) + +/******************************************************************************/ +/*! + @brief Instantiates a new instance of the BluefruitLE_SPI class +*/ +/******************************************************************************/ +BluefruitLE_SPI::BluefruitLE_SPI() : m_rx_fifo(m_rx_buffer, sizeof(m_rx_buffer), 1, true) { + m_tx_count = 0; + spi_init(); +} + +/******************************************************************************/ +/*! + @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_SPI::begin() { + _verbose = AdafruitBleVerbose; + + setPinInput(AdafruitBleIRQPin); + + // Set CS pin to output and de-assert by default + setPinOutput(AdafruitBleCSPin); + writePinHigh(AdafruitBleCSPin); + + // use hardware reset + // pull the RST to GND for 10 ms + setPinOutput(AdafruitBleResetPin); + writePinHigh(AdafruitBleResetPin); + writePinLow(AdafruitBleResetPin); + wait_ms(10); + writePinHigh(AdafruitBleResetPin); + + // Bluefruit takes 1 second to reboot + wait_ms(1000); + + return true; +} + +bool BluefruitLE_SPI::setMode(uint8_t new_mode) { + // invalid mode + if (!(new_mode == BLUEFRUIT_MODE_COMMAND || new_mode == BLUEFRUIT_MODE_DATA)) return false; + + // Already in the wanted mode + if (_mode == new_mode) return true; + + // SPI use different SDEP command when in DATA/COMMAND mode. + // --> does not switch using +++ command + _mode = new_mode; + + // If we're entering DATA mode, flush any old response, so that it isn't + // interpreted as incoming UART data + if (_mode == BLUEFRUIT_MODE_DATA) flush(); + + return true; +} + +/******************************************************************************/ +/*! + @brief Send out an packet with data in m_tx_buffer + + @param[in] more_data + More Data bitfield, 0 indicates this is not end of transfer yet +*/ +/******************************************************************************/ +bool BluefruitLE_SPI::sendPacket(uint16_t command, const uint8_t *buf, uint8_t count, uint8_t more_data) { + // flush old response before sending the new command, but only if we're *not* + // in DATA mode, as the RX FIFO may containg incoming UART data that hasn't + // been read yet + if (more_data == 0 && _mode != BLUEFRUIT_MODE_DATA) flush(); + + sdepMsgCommand_t msgCmd; + + msgCmd.header.msg_type = SDEP_MSGTYPE_COMMAND; + msgCmd.header.cmd_id_high = highByte(command); + msgCmd.header.cmd_id_low = lowByte(command); + msgCmd.header.length = count; + msgCmd.header.more_data = (count == SDEP_MAX_PACKETSIZE) ? more_data : 0; + + // Copy payload + if (buf != NULL && count > 0) memcpy(msgCmd.payload, buf, count); + + spi_start(AdafruitBleCSPin, false, 0, SCK_DIVISOR); + + uint32_t tt = timer_read32(); + + // Bluefruit may not be ready + while ((spi_write(msgCmd.header.msg_type) == SPI_IGNORED_BYTE) && (timer_elapsed32(tt) < _timeout)) { + // Disable & Re-enable CS with a bit of delay for Bluefruit to ready itself + spi_stop(); + wait_us(SPI_DEFAULT_DELAY_US); + spi_start(AdafruitBleCSPin, false, 0, SCK_DIVISOR); + } + + bool result = (timer_elapsed32(tt) < _timeout); + if (result) { + // transfer the rest of the data + spi_transmit((((uint8_t *)&msgCmd) + 1), sizeof(sdepMsgHeader_t) + count - 1); + } + + spi_stop(); + + return result; +} + +/******************************************************************************/ +/*! + @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_SPI::write(uint8_t c) { + if (_mode == BLUEFRUIT_MODE_DATA) { + sendPacket(SDEP_CMDTYPE_BLE_UARTTX, &c, 1, 0); + getResponse(); + return 1; + } + + // Following code handle BLUEFRUIT_MODE_COMMAND + + // Final packet due to \r or \n terminator + if (c == '\r' || c == '\n') { + if (m_tx_count > 0) { + sendPacket(SDEP_CMDTYPE_AT_WRAPPER, m_tx_buffer, m_tx_count, 0); + m_tx_count = 0; + } + } else if (m_tx_count == SDEP_MAX_PACKETSIZE) { + // More than max packet buffered --> send with more_data = 1 + sendPacket(SDEP_CMDTYPE_AT_WRAPPER, m_tx_buffer, m_tx_count, 1); + + m_tx_buffer[0] = c; + m_tx_count = 1; + } else { + // Not enough data, continue to buffer + m_tx_buffer[m_tx_count++] = c; + } + + if (_verbose) dprintf("%c", (char)c); + + return 1; +} + +/******************************************************************************/ +/*! + +*/ +/******************************************************************************/ +size_t BluefruitLE_SPI::write(const uint8_t *buf, size_t size) { + if (_mode == BLUEFRUIT_MODE_DATA) { + size_t remain = size; + while (remain) { + size_t len = min(remain, SDEP_MAX_PACKETSIZE); + remain -= len; + + sendPacket(SDEP_CMDTYPE_BLE_UARTTX, buf, (uint8_t)len, remain ? 1 : 0); + buf += len; + } + + getResponse(); + + return size; + } + + // Command mode + size_t n = 0; + while (size--) { + n += write(*buf++); + } + return n; +} + +/******************************************************************************/ +/*! + @brief Check if the response from the previous command is ready + + @return 'true' if a response is ready, otherwise 'false' +*/ +/******************************************************************************/ +int BluefruitLE_SPI::available(void) { + if (!m_rx_fifo.empty()) { + return m_rx_fifo.count(); + } + + if (_mode == BLUEFRUIT_MODE_DATA) { + // DATA Mode: query for BLE UART data + sendPacket(SDEP_CMDTYPE_BLE_UARTRX, NULL, 0, 0); + + // Waiting to get response from Bluefruit + getResponse(); + + return m_rx_fifo.count(); + } + + return readPin(AdafruitBleIRQPin); +} + +/******************************************************************************/ +/*! + @brief Get a byte from response data, perform SPI transaction if needed + + @return -1 if no data is available +*/ +/******************************************************************************/ +int BluefruitLE_SPI::read(void) { + uint8_t ch; + + // try to grab from buffer first... + if (!m_rx_fifo.empty()) { + m_rx_fifo.read(&ch); + return (int)ch; + } + + if (_mode == BLUEFRUIT_MODE_DATA) { + // DATA Mode: query for BLE UART data + sendPacket(SDEP_CMDTYPE_BLE_UARTRX, NULL, 0, 0); + + // Waiting to get response from Bluefruit + getResponse(); + } else { + // COMMAND Mode: Only read data from Bluefruit if IRQ is raised + if (readPin(AdafruitBleIRQPin)) getResponse(); + } + + return m_rx_fifo.read(&ch) ? ((int)ch) : EOF; +} + +/******************************************************************************/ +/*! + @brief Get a byte from response without removing it, perform SPI transaction + if needed + + @return -1 if no data is available +*/ +/******************************************************************************/ +int BluefruitLE_SPI::peek(void) { + uint8_t ch; + + // try to grab from buffer first... + if (m_rx_fifo.peek(&ch)) { + return (int)ch; + } + + if (_mode == BLUEFRUIT_MODE_DATA) { + // DATA Mode: query for BLE UART data + sendPacket(SDEP_CMDTYPE_BLE_UARTRX, NULL, 0, 0); + + // Waiting to get response from Bluefruit + getResponse(); + } else { + // Read data from Bluefruit if possible + if (readPin(AdafruitBleIRQPin)) getResponse(); + } + + return m_rx_fifo.peek(&ch) ? ch : EOF; +} + +/******************************************************************************/ +/*! + @brief Flush current response data in the internal FIFO + + @return -1 if no data is available +*/ +/******************************************************************************/ +void BluefruitLE_SPI::flush(void) { m_rx_fifo.clear(); } + +/******************************************************************************/ +/*! + @brief Try to perform an full AT response transfer from Bluefruit, or execute + as many SPI transaction as internal FIFO can hold up. + + @note If verbose is enabled, all the received data will be print to Serial + + @return + - true : if succeeded + - false : if failed +*/ +/******************************************************************************/ +bool BluefruitLE_SPI::getResponse(void) { + // Try to read data from Bluefruit if there is enough room in the fifo + while (m_rx_fifo.remaining() >= SDEP_MAX_PACKETSIZE) { + // Get a SDEP packet + sdepMsgResponse_t msg_response; + memclr(&msg_response, sizeof(sdepMsgResponse_t)); + + if (!getPacket(&msg_response)) return false; + + // Write to fifo + if (msg_response.header.length > 0) { + m_rx_fifo.write_n(msg_response.payload, msg_response.header.length); + } + + // No more packet data + if (!msg_response.header.more_data) break; + } + + return true; +} + +/******************************************************************************/ +/*! + @brief Perform a single SPI SDEP transaction and is used by getReponse to + get a full response composed of multiple packets. + + @param[in] buf + Memory location where payload is copied to + + @return number of bytes in SDEP payload +*/ +/******************************************************************************/ +bool BluefruitLE_SPI::getPacket(sdepMsgResponse_t *p_response) { + // Wait until IRQ is asserted, double timeout since some commands take long time to start responding + uint32_t tt = timer_read32(); + + while (!readPin(AdafruitBleIRQPin)) { + if (timer_elapsed32(tt) > 2 * _timeout) return false; + } + + sdepMsgHeader_t *p_header = &p_response->header; + + spi_start(AdafruitBleCSPin, false, 0, SCK_DIVISOR); + + tt = timer_read32(); + + do { + if (timer_elapsed32(tt) > _timeout) break; + + p_header->msg_type = spi_read(); + + switch (p_header->msg_type) { + case SPI_IGNORED_BYTE: + // Bluefruit may not be ready + // + // Disable & Re-enable CS with a bit of delay for Bluefruit to + // ready itself + // + // intentional fallthrough, no break + case SPI_OVERREAD_BYTE: + // IRQ may not be pulled down by Bluefruit when returning all + // data in previous transfer. This could happen when Arduino MCU + // is running at fast rate comparing to Bluefruit's MCU, causing + // an SPI_OVERREAD_BYTE to be returned at stage. + // + // Walkaround: Disable & Re-enable CS with a bit of delay and + // keep waiting + // TODO IRQ is supposed to be OFF then ON, it is better to use + // GPIO trigger interrupt. + spi_stop(); + wait_us(SPI_DEFAULT_DELAY_US); + spi_start(AdafruitBleCSPin, false, 0, SCK_DIVISOR); + } + } while (p_header->msg_type == SPI_IGNORED_BYTE || p_header->msg_type == SPI_OVERREAD_BYTE); + + bool result = false; + + // Not a loop, just a way to avoid goto with error handling + do { + // Look for the header + // note that we should always get the right header at this point, and not doing so will really mess up things. + while (p_header->msg_type != SDEP_MSGTYPE_RESPONSE && p_header->msg_type != SDEP_MSGTYPE_ERROR && timer_elapsed32(tt) < _timeout) { + p_header->msg_type = spi_read(); + } + + if (timer_elapsed32(tt) > _timeout) break; + + spi_receive((&p_header->msg_type) + 1, sizeof(sdepMsgHeader_t) - 1); + + // Error Message Response + if (p_header->msg_type == SDEP_MSGTYPE_ERROR) break; + + // Command is 16-bit at odd address, may have alignment issue with 32-bit chip + uint16_t cmd_id = word(p_header->cmd_id_high, p_header->cmd_id_low); + + // Invalid command + if (!(cmd_id == SDEP_CMDTYPE_AT_WRAPPER || cmd_id == SDEP_CMDTYPE_BLE_UARTTX || cmd_id == SDEP_CMDTYPE_BLE_UARTRX)) { + break; + } + + // Invalid length + if (p_header->length > SDEP_MAX_PACKETSIZE) break; + + // read payload + spi_receive(p_response->payload, p_header->length); + + result = true; + } while (0); + + spi_stop(); + + return result; +} diff --git a/drivers/bluetooth/adafruit_ble/BluefruitLE_SPI.h b/drivers/bluetooth/adafruit_ble/BluefruitLE_SPI.h new file mode 100644 index 000000000000..3164938976a0 --- /dev/null +++ b/drivers/bluetooth/adafruit_ble/BluefruitLE_SPI.h @@ -0,0 +1,78 @@ +/**************************************************************************/ +/*! + Modified from the original Adafruit_BluefruitLE_SPI.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" +#include "FIFO.h" + +class BluefruitLE_SPI : public BLE { + private: + // TX + uint8_t m_tx_buffer[SDEP_MAX_PACKETSIZE]; + uint8_t m_tx_count; + + // RX + uint8_t m_rx_buffer[BLE_BUFSIZE]; + FIFO m_rx_fifo; + + // Low level transportation I/O functions + bool sendPacket(uint16_t command, const uint8_t* buffer, uint8_t count, uint8_t more_data); + bool getPacket(sdepMsgResponse_t* p_response); + + bool getResponse(void); + + public: + // Constructor + BluefruitLE_SPI(); + + // HW initialisation + bool begin(); + + bool setMode(uint8_t new_mode); + + // Class Print virtual function Interface + virtual size_t write(uint8_t c); + virtual size_t write(const uint8_t* buffer, size_t size); + + // 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); +}; diff --git a/drivers/bluetooth/adafruit_ble/BluefruitLE_UART.cpp b/drivers/bluetooth/adafruit_ble/BluefruitLE_UART.cpp new file mode 100644 index 000000000000..4220f7f78061 --- /dev/null +++ b/drivers/bluetooth/adafruit_ble/BluefruitLE_UART.cpp @@ -0,0 +1,165 @@ +/**************************************************************************/ +/*! + 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 "uart.h" +#include "debug.h" +#include "wait.h" + +/******************************************************************************/ +/*! + @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) { + _verbose = AdafruitBleVerbose; + + uart_init(baud); + + // reset Bluefruit module upon connect + bool isOK = reset(true); + + if (!isOK && baud != 9600) { + // sometimes the modules get reset and the baud goes to default (9600) + // so if there was an issue, let's retry at the default rate + if (begin(9600)) { + // since this worked, let's reset the baud rate to where it's + // supposed to be + if (atcommand(F("AT+BAUDRATE"), baud)) { + // and now we try one more time at the correct rate + return begin(baud); + } + } + } + + return isOK; +} + +/******************************************************************************/ +/*! + @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) dprintf("%c", c); + wait_us(50); + uart_putchar(c); + return 1; +} + +/******************************************************************************/ +/*! + @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 uart_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 uart_getchar(); } + +/******************************************************************************/ +/*! + @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 uart_peek(); } + +/******************************************************************************/ +/*! + @brief Flush current response data in the internal FIFO + + @return -1 if no data is available +*/ +/******************************************************************************/ +void BluefruitLE_UART::flush(void) { uart_flush(); } diff --git a/drivers/bluetooth/adafruit_ble/BluefruitLE_UART.h b/drivers/bluetooth/adafruit_ble/BluefruitLE_UART.h new file mode 100644 index 000000000000..c4e527bd5e29 --- /dev/null +++ b/drivers/bluetooth/adafruit_ble/BluefruitLE_UART.h @@ -0,0 +1,65 @@ +/**************************************************************************/ +/*! + 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: + // HW initialisation + bool begin(uint32_t baud = AdafruitBleBaud); + + 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/drivers/bluetooth/adafruit_ble/FIFO.cpp b/drivers/bluetooth/adafruit_ble/FIFO.cpp new file mode 100644 index 000000000000..4da377404f1f --- /dev/null +++ b/drivers/bluetooth/adafruit_ble/FIFO.cpp @@ -0,0 +1,198 @@ +/**************************************************************************/ +/*! + Modified from the original Adafruit_FIFO.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 "FIFO.h" +#include + +/******************************************************************************/ +/*! + @brief Constructor + + @param[in] buffer + Memory location to store data + @param[in] depth + Maximum number of items can be hold in buffer + @param[in] item_size + Number of bytes of each item + @param[in] overwrite + Should the buffer is overwitten to the first item when it is full +*/ +/******************************************************************************/ +FIFO::FIFO(void* buffer, uint16_t depth, uint8_t item_size, bool overwrite) { + m_buffer = (uint8_t*)buffer; + m_depth = depth; + m_item_size = item_size; + m_overwritable = overwrite; + + m_count = m_wr_idx = m_rd_idx = 0; +} + +/******************************************************************************/ +/*! + @brief Clear the FIFO +*/ +/******************************************************************************/ +void FIFO::clear(void) { m_rd_idx = m_wr_idx = m_count = 0; } + +/******************************************************************************/ +/*! + @brief Write an item to the FIFO + + @param[in] item + Memory address of the item +*/ +/******************************************************************************/ +bool FIFO::write(void const* item) { + if (full() && !m_overwritable) return false; + + memcpy(m_buffer + (m_wr_idx * m_item_size), item, m_item_size); + + m_wr_idx = (m_wr_idx + 1) % m_depth; + + if (full()) { + m_rd_idx = m_wr_idx; // keep the full state (rd == wr && len = size) + } else { + m_count++; + } + + return true; +} + +/******************************************************************************/ +/*! + @brief Write array of items to the FIFO + + @param[in] data + Memory address of the item's array + @param[in] n + Number of items to write + + @return Number of written items +*/ +/******************************************************************************/ +uint16_t FIFO::write_n(void const* data, uint16_t n) { + if (n == 0) return 0; + + uint8_t* buf = (uint8_t*)data; + + uint16_t len = 0; + while ((len < n) && write(buf)) { + len++; + buf += m_item_size; + } + + return len; +} + +/******************************************************************************/ +/*! + @brief Read an item from FIFO + + @param[in] buffer + Memory address to store item +*/ +/******************************************************************************/ +bool FIFO::read(void* buffer) { + if (empty()) return false; + + memcpy(buffer, m_buffer + (m_rd_idx * m_item_size), m_item_size); + m_rd_idx = (m_rd_idx + 1) % m_depth; + m_count--; + + return true; +} + +/******************************************************************************/ +/*! + @brief Read multiple items to an array + + @param[in] buffer + Memory address of the item's array + @param[in] n + Number of items to read + + @return Number of read items +*/ +/******************************************************************************/ + +uint16_t FIFO::read_n(void* buffer, uint16_t n) { + if (n == 0) return 0; + + uint8_t* buf = (uint8_t*)buffer; + + uint16_t len = 0; + while ((len < n) && read(buf)) { + len++; + buf += m_item_size; + } + + return len; +} + +/******************************************************************************/ +/*! + @brief Read an item without removing it from the FIFO + + @param[in] buffer + Memory address to store item +*/ +/******************************************************************************/ +bool FIFO::peek(void* buffer) { + if (empty()) return false; + + memcpy(buffer, m_buffer + (m_rd_idx * m_item_size), m_item_size); + + return true; +} + +/******************************************************************************/ +/*! + @brief Read an item without removing it from the FIFO at the specific index + + @param[in] position + Position to read from in the FIFO buffer + + @param[in] buffer + Memory address to store item +*/ +/******************************************************************************/ +bool FIFO::peekAt(uint16_t position, void* p_buffer) { + if (empty() || (position >= m_count)) return false; + + uint16_t index = (m_rd_idx + position) % m_depth; // rd_idx is position=0 + memcpy(p_buffer, m_buffer + (index * m_item_size), m_item_size); + + return true; +} diff --git a/drivers/bluetooth/adafruit_ble/FIFO.h b/drivers/bluetooth/adafruit_ble/FIFO.h new file mode 100644 index 000000000000..62039c8d7750 --- /dev/null +++ b/drivers/bluetooth/adafruit_ble/FIFO.h @@ -0,0 +1,69 @@ +/**************************************************************************/ +/*! + Modified from the original Adafruit_FIFO.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 +#include + +class FIFO { + private: + uint8_t* m_buffer; ///< buffer pointer + uint16_t m_depth; ///< max items + uint8_t m_item_size; ///< size of each item + bool m_overwritable; + volatile uint16_t m_count; ///< number of items in queue + volatile uint16_t m_wr_idx; ///< write pointer + volatile uint16_t m_rd_idx; ///< read pointer + + public: + // Constructor + FIFO(void* buffer, uint16_t depth, uint8_t item_size, bool overwrite); + + void clear(void); + bool peek(void* buffer); + bool peekAt(uint16_t position, void* p_buffer); + + bool write(void const* item); + uint16_t write_n(void const* data, uint16_t n); + + bool read(void* buffer); + uint16_t read_n(void* buffer, uint16_t n); + + inline bool empty(void) { return m_count == 0; } + inline bool full(void) { return m_count == m_depth; } + inline uint16_t count(void) { return m_count; } + inline uint16_t remaining(void) { return m_depth - m_count; } +}; diff --git a/drivers/bluetooth/adafruit_ble/Print.cpp b/drivers/bluetooth/adafruit_ble/Print.cpp new file mode 100644 index 000000000000..2e720ca2fadc --- /dev/null +++ b/drivers/bluetooth/adafruit_ble/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/drivers/bluetooth/adafruit_ble/Print.h b/drivers/bluetooth/adafruit_ble/Print.h new file mode 100644 index 000000000000..7cd4157999cc --- /dev/null +++ b/drivers/bluetooth/adafruit_ble/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/drivers/bluetooth/adafruit_ble/Stream.cpp b/drivers/bluetooth/adafruit_ble/Stream.cpp new file mode 100644 index 000000000000..8d5f288c3ae7 --- /dev/null +++ b/drivers/bluetooth/adafruit_ble/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/drivers/bluetooth/adafruit_ble/Stream.h b/drivers/bluetooth/adafruit_ble/Stream.h new file mode 100644 index 000000000000..538a0099f6cc --- /dev/null +++ b/drivers/bluetooth/adafruit_ble/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/drivers/bluetooth/adafruit_ble/new.cpp b/drivers/bluetooth/adafruit_ble/new.cpp new file mode 100644 index 000000000000..d95743a32a8a --- /dev/null +++ b/drivers/bluetooth/adafruit_ble/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/drivers/bluetooth/adafruit_ble/sdep.h b/drivers/bluetooth/adafruit_ble/sdep.h new file mode 100644 index 000000000000..a10e3b220f7f --- /dev/null +++ b/drivers/bluetooth/adafruit_ble/sdep.h @@ -0,0 +1,110 @@ +/******************************************************************************/ +/*! + 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 ATTR_PACKED __attribute__((packed)) +#define SDEP_MAX_PACKETSIZE 16 // Maximum payload per packet + +/******************************************************************************/ +/*! + This enumeration is used to make sure that each command has a unique + ID, and is used to create the command lookup table enum further down +*/ +/******************************************************************************/ +typedef enum { + SDEP_CMDTYPE_AT_WRAPPER = 0x0A00, + SDEP_CMDTYPE_BLE_UARTTX = 0x0A01, + SDEP_CMDTYPE_BLE_UARTRX = 0x0A02, +} sdepCmdType_t; + +/******************************************************************************/ +/*! + The first byte of every transfer defines the message type +*/ +/******************************************************************************/ +typedef enum { + SDEP_MSGTYPE_COMMAND = 0x10, + SDEP_MSGTYPE_RESPONSE = 0x20, + SDEP_MSGTYPE_ALERT = 0x40, + SDEP_MSGTYPE_ERROR = 0x80, +} sdepMsgType_t; + +/******************************************************************************/ +/*! + 4-byte header for SDEP messages +*/ +/******************************************************************************/ +typedef struct ATTR_PACKED { + uint8_t msg_type; // 8-bit message type indicator (sdepMsgType_t) + + union { + uint16_t cmd_id; // 16-bit command ID + struct { + uint8_t cmd_id_low; + uint8_t cmd_id_high; + }; + }; + + struct ATTR_PACKED { + uint8_t length : 7; // Payload length (for this packet) + uint8_t more_data : 1; // 'more' bit for multiple packet transfers + }; +} sdepMsgHeader_t; + +/******************************************************************************/ +/*! + SDEP command message +*/ +/******************************************************************************/ +typedef struct ATTR_PACKED { + sdepMsgHeader_t header; + uint8_t payload[SDEP_MAX_PACKETSIZE]; +} sdepMsgCommand_t; + +/******************************************************************************/ +/*! + Response message struct (same as sdepMsgCommand_t) +*/ +/******************************************************************************/ +typedef sdepMsgCommand_t sdepMsgResponse_t; + +/******************************************************************************/ +/*! + Alert message struct +*/ +/******************************************************************************/ +typedef sdepMsgCommand_t sdepMsgAlert_t; diff --git a/drivers/bluetooth/bluetooth.c b/drivers/bluetooth/bluetooth.c new file mode 100644 index 000000000000..f36eac99485d --- /dev/null +++ b/drivers/bluetooth/bluetooth.c @@ -0,0 +1,23 @@ +#include "bluetooth.h" + +__attribute__((weak)) void bluetooth_init(void) {} + +__attribute__((weak)) void bluetooth_task(void) {} + +__attribute__((weak)) bool bluetooth_is_connected(void) { return true; } + +__attribute__((weak)) void bluetooth_unpair(void) {} + +__attribute__((weak)) void bluetooth_send_keyboard(report_keyboard_t *report) {} + +__attribute__((weak)) void bluetooth_send_consumer(uint16_t keycode) {} + +#ifdef MOUSE_ENABLE +__attribute__((weak)) void bluetooth_send_mouse(report_mouse_t *report) {} +#endif + +#ifdef BLUETOOTH_BATTERY_ENABLE +__attribute__((weak)) uint8_t bluetooth_get_battery_level_kb(void) { return bluetooth_get_battery_level_user(); } + +__attribute__((weak)) uint8_t bluetooth_get_battery_level_user() { return 100; } +#endif diff --git a/drivers/bluetooth/bluetooth.h b/drivers/bluetooth/bluetooth.h new file mode 100644 index 000000000000..ed2e25eab7c2 --- /dev/null +++ b/drivers/bluetooth/bluetooth.h @@ -0,0 +1,27 @@ +#pragma once + +#include "report.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void bluetooth_init(void); +void bluetooth_task(void); +bool bluetooth_is_connected(void); +void bluetooth_unpair(void); +void bluetooth_send_keyboard(report_keyboard_t *report); +void bluetooth_send_consumer(uint16_t keycode); + +#ifdef MOUSE_ENABLE +void bluetooth_send_mouse(report_mouse_t *report); +#endif + +#ifdef BLUETOOTH_BATTERY_ENABLE +uint8_t bluetooth_get_battery_level_kb(void); +uint8_t bluetooth_get_battery_level_user(); +#endif + +#ifdef __cplusplus +} +#endif diff --git a/drivers/bluetooth/bluetooth.mk b/drivers/bluetooth/bluetooth.mk new file mode 100644 index 000000000000..3876b53cb261 --- /dev/null +++ b/drivers/bluetooth/bluetooth.mk @@ -0,0 +1,74 @@ +ifeq ($(strip $(BLUETOOTH_ENABLE)), yes) + COMMON_VPATH += $(DRIVER_PATH)/bluetooth + OPT_DEFS += -DBLUETOOTH_ENABLE + OPT_DEFS += -DNO_USB_STARTUP_CHECK + SRC += bluetooth.c + SRC += bluetooth_classic.c + SRC += $(TMK_PATH)/protocol/serial_uart.c +endif + +ifeq ($(strip $(BLUETOOTH)), AdafruitEZKey) + COMMON_VPATH += $(DRIVER_PATH)/bluetooth + OPT_DEFS += -DBLUETOOTH_ENABLE + OPT_DEFS += -DMODULE_ADAFRUIT_EZKEY + OPT_DEFS += -DNO_USB_STARTUP_CHECK + SRC += bluetooth.c + SRC += bluetooth_classic.c + SRC += $(TMK_PATH)/protocol/serial_uart.c +endif + +ifeq ($(strip $(BLUETOOTH)), RN42) + COMMON_VPATH += $(DRIVER_PATH)/bluetooth + OPT_DEFS += -DBLUETOOTH_ENABLE + OPT_DEFS += -DMODULE_RN42 + OPT_DEFS += -DNO_USB_STARTUP_CHECK + SRC += bluetooth.c + SRC += bluetooth_classic.c + SRC += $(TMK_PATH)/protocol/serial_uart.c +endif + +ifeq ($(strip $(BLUETOOTH)), AdafruitBLE) + COMMON_VPATH += $(DRIVER_PATH)/bluetooth + COMMON_VPATH += $(DRIVER_PATH)/bluetooth/adafruit_ble + OPT_DEFS += -DBLUETOOTH_ENABLE + OPT_DEFS += -DMODULE_ADAFRUIT_BLE + OPT_DEFS += -DMODULE_ADAFRUIT_BLE_SPI + OPT_DEFS += -DNO_USB_STARTUP_CHECK + SRC += bluetooth.c + SRC += spi_master.c + SRC += new.cpp + SRC += FIFO.cpp + SRC += Print.cpp + SRC += Stream.cpp + SRC += ATParser.cpp + SRC += BLE.cpp + SRC += BluefruitLE_SPI.cpp + SRC += adafruit_ble.cpp +endif + +ifeq ($(strip $(BLUETOOTH)), AdafruitBLEUART) + COMMON_VPATH += $(DRIVER_PATH)/bluetooth + COMMON_VPATH += $(DRIVER_PATH)/bluetooth/adafruit_ble + OPT_DEFS += -DBLUETOOTH_ENABLE + OPT_DEFS += -DMODULE_ADAFRUIT_BLE + OPT_DEFS += -DMODULE_ADAFRUIT_BLE_UART + OPT_DEFS += -DNO_USB_STARTUP_CHECK + SRC += bluetooth.c + SRC += uart.c + SRC += new.cpp + SRC += Print.cpp + SRC += Stream.cpp + SRC += ATParser.cpp + SRC += BLE.cpp + SRC += BluefruitLE_UART.cpp + SRC += adafruit_ble.cpp +endif + +ifeq ($(strip $(BLUETOOTH_BATTERY_ENABLE)), yes) + COMMON_VPATH += $(DRIVER_PATH)/bluetooth + COMMON_VPATH += $(DRIVER_PATH)/bluetooth/adafruit_ble + OPT_DEFS += -DBLUETOOTH_BATTERY_ENABLE + SRC += analog.c + SRC += bluetooth.c + SRC += adafruit_ble/BLEBattery.cpp +endif diff --git a/tmk_core/protocol/lufa/bluetooth.h b/drivers/bluetooth/bluetooth_classic.c similarity index 61% rename from tmk_core/protocol/lufa/bluetooth.h rename to drivers/bluetooth/bluetooth_classic.c index 081271a4e646..befadc527e45 100644 --- a/tmk_core/protocol/lufa/bluetooth.h +++ b/drivers/bluetooth/bluetooth_classic.c @@ -15,13 +15,11 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -#ifndef BLUETOOTH_H -#define BLUETOOTH_H - +#include +#include "host_driver.h" +#include "bluetooth.h" #include "../serial.h" -void bluefruit_serial_send(uint8_t data); - /* +-----------------+-------------------+-------+ | Consumer Key | Bit Map | Hex | @@ -45,4 +43,66 @@ void bluefruit_serial_send(uint8_t data); #define CONSUMER2RN42(usage) (usage == AUDIO_MUTE ? 0x0040 : (usage == AUDIO_VOL_UP ? 0x0010 : (usage == AUDIO_VOL_DOWN ? 0x0020 : (usage == TRANSPORT_NEXT_TRACK ? 0x0100 : (usage == TRANSPORT_PREV_TRACK ? 0x0200 : (usage == TRANSPORT_STOP ? 0x0400 : (usage == TRANSPORT_STOP_EJECT ? 0x0800 : (usage == TRANSPORT_PLAY_PAUSE ? 0x0080 : (usage == AL_EMAIL ? 0x0200 : (usage == AL_LOCAL_BROWSER ? 0x8000 : (usage == AC_SEARCH ? 0x0400 : (usage == AC_HOME ? 0x0100 : 0)))))))))))) +void bluetooth_init() { serial_init(); } + +void bluetooth_send_keyboard(report_keyboard_t *report) { +#ifdef MODULE_RN42 + serial_send(0xFD); + serial_send(0x09); + serial_send(0x01); + serial_send(report->mods); + serial_send(report->reserved); + for (uint8_t i = 0; i < KEYBOARD_REPORT_KEYS; i++) { + serial_send(report->keys[i]); + } +#else + serial_send(0xFD); + serial_send(report->mods); + serial_send(report->reserved); + for (uint8_t i = 0; i < KEYBOARD_REPORT_KEYS; i++) { + serial_send(report->keys[i]); + } +#endif +} + +void bluetooth_send_consumer(uint16_t data) { +#ifdef MODULE_RN42 + static uint16_t last_data = 0; + if (data == last_data) return; + last_data = data; + uint16_t bitmap = CONSUMER2RN42(data); + serial_send(0xFD); + serial_send(0x03); + serial_send(0x03); + serial_send(bitmap & 0xFF); + serial_send((bitmap >> 8) & 0xFF); +#else + static uint16_t last_data = 0; + if (data == last_data) return; + last_data = data; + uint16_t bitmap = CONSUMER2BLUEFRUIT(data); + serial_send(0xFD); + serial_send(0x00); + serial_send(0x02); + serial_send((bitmap >> 8) & 0xFF); + serial_send(bitmap & 0xFF); + serial_send(0x00); + serial_send(0x00); + serial_send(0x00); + serial_send(0x00); +#endif +} + +#ifdef MOUSE_ENABLE +void bluetooth_send_mouse(report_mouse_t *report) { + serial_send(0xFD); + serial_send(0x00); + serial_send(0x03); + serial_send(report->buttons); + serial_send(report->x); + serial_send(report->y); + serial_send(report->v); // should try sending the wheel v here + serial_send(report->h); // should try sending the wheel h here + serial_send(0x00); +} #endif diff --git a/keyboards/bioi/g60ble/config.h b/keyboards/bioi/g60ble/config.h index e7515ec8945f..647820e1b420 100644 --- a/keyboards/bioi/g60ble/config.h +++ b/keyboards/bioi/g60ble/config.h @@ -48,3 +48,5 @@ #define KEYBOARD_LOCK_ENABLE #define MAGIC_KEY_LOCK L +#define AdafruitBleBaud 76800 +#define OUTPUT_AUTO_ENABLE diff --git a/keyboards/bioi/g60ble/g60ble.h b/keyboards/bioi/g60ble/g60ble.h index f1e1699ddcf9..14d208c7e2d8 100644 --- a/keyboards/bioi/g60ble/g60ble.h +++ b/keyboards/bioi/g60ble/g60ble.h @@ -4,6 +4,7 @@ #define XXX KC_NO +// clang-format off #define LAYOUT_all( \ K00, K01, K02, K03, K04, K05, K06, K07, K08, K09, K0A, K0B, K0C, K0D, K49, \ K10, K11, K12, K13, K14, K15, K16, K17, K18, K19, K1A, K1B, K1C, K1D, \ @@ -87,3 +88,4 @@ { K30, XXX, K32, K33, K34, K35, K36, K37, K38, K39, K3A, K3B, K3C, K3D }, \ { K40, K41, K42, XXX, XXX, K45, XXX, XXX, XXX, K49, XXX, K4B, K4C, K4D } \ } +// clang-format on diff --git a/keyboards/bioi/g60ble/rules.mk b/keyboards/bioi/g60ble/rules.mk index 464a6d136c67..30249da576d6 100644 --- a/keyboards/bioi/g60ble/rules.mk +++ b/keyboards/bioi/g60ble/rules.mk @@ -18,7 +18,7 @@ BOOTLOADER = qmk-dfu # comment out to disable the options. # BOOTMAGIC_ENABLE = lite # Virtual DIP switch configuration -MOUSEKEY_ENABLE = yes # Mouse keys +MOUSEKEY_ENABLE = no # Mouse keys EXTRAKEY_ENABLE = yes # Audio control and System control CONSOLE_ENABLE = no # Console for debug COMMAND_ENABLE = yes # Commands for debug and configuration @@ -27,5 +27,7 @@ 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 +# BLUETOOTH_BATTERY_ENABLE = yes 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/quantum.c b/quantum/quantum.c index 044a15612845..280179fa3b94 100644 --- a/quantum/quantum.c +++ b/quantum/quantum.c @@ -17,8 +17,9 @@ #include #include "quantum.h" -#ifdef PROTOCOL_LUFA +#ifdef BLUETOOTH_ENABLE # include "outputselect.h" +# include "bluetooth.h" #endif #ifdef BACKLIGHT_ENABLE @@ -327,6 +328,9 @@ bool process_record_quantum(keyrecord_t *record) { case OUT_BT: set_output(OUTPUT_BLUETOOTH); return false; + case BT_UNPAIR: + bluetooth_unpair(); + return false; #endif } } diff --git a/quantum/quantum_keycodes.h b/quantum/quantum_keycodes.h index d8f1fa4bbbc2..976c3aead65e 100644 --- a/quantum/quantum_keycodes.h +++ b/quantum/quantum_keycodes.h @@ -512,6 +512,10 @@ enum quantum_keycodes { DYN_MACRO_PLAY1, DYN_MACRO_PLAY2, +#ifdef BLUETOOTH_ENABLE + BT_UNPAIR, +#endif + // always leave at the end SAFE_RANGE }; diff --git a/quantum/rgblight.c b/quantum/rgblight.c index d33484ccfdf2..350a34eaa7c5 100644 --- a/quantum/rgblight.c +++ b/quantum/rgblight.c @@ -27,6 +27,9 @@ # include "hal.h" # include "eeprom_stm32.h" #endif +#ifdef PROTOCOL_LUFA +# include "lufa.h" +#endif #include "wait.h" #include "progmem.h" #include "timer.h" @@ -227,6 +230,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) { @@ -792,6 +803,30 @@ void rgblight_update_sync(rgblight_syncinfo_t *syncinfo, bool write_to_eeprom) { } #endif /* RGBLIGHT_SPLIT */ +#ifdef PROTOCOL_LUFA +void rgblight_usb_task(void) { + static uint8_t last_usb_devicestate = -1; + + if (USB_DeviceState != last_usb_devicestate) { + last_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 + #ifdef RGBLIGHT_USE_TIMER typedef void (*effect_func_t)(animation_status_t *anim); @@ -858,6 +893,10 @@ static void rgblight_effect_dummy(animation_status_t *anim) { } void rgblight_task(void) { +# ifdef PROTOCOL_LUFA + rgblight_usb_task(); +# endif + if (rgblight_status.timer_enabled) { effect_func_t effect_func = rgblight_effect_dummy; uint16_t interval_time = 2000; // dummy interval @@ -963,6 +1002,14 @@ void rgblight_task(void) { # endif } +#else + +void rgblight_task(void) { +# ifdef PROTOCOL_LUFA + rgblight_usb_task(); +# endif +} + #endif /* RGBLIGHT_USE_TIMER */ // Effects diff --git a/quantum/rgblight.h b/quantum/rgblight.h index c36b328a35f8..384e4ca53b4f 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 @@ -355,6 +359,7 @@ HSV rgblight_get_hsv(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); @@ -372,14 +377,14 @@ void rgblight_mode_eeprom_helper(uint8_t mode, bool write_to_eeprom); # define EZ_RGB(val) rgblight_show_solid_color((val >> 16) & 0xFF, (val >> 8) & 0xFF, val & 0xFF) void rgblight_show_solid_color(uint8_t r, uint8_t g, uint8_t b); -# ifdef RGBLIGHT_USE_TIMER void rgblight_task(void); + +# ifdef RGBLIGHT_USE_TIMER void rgblight_timer_init(void); void rgblight_timer_enable(void); void rgblight_timer_disable(void); void rgblight_timer_toggle(void); # else -# define rgblight_task() # define rgblight_timer_init() # define rgblight_timer_enable() # define rgblight_timer_disable() @@ -435,4 +440,9 @@ void rgblight_effect_twinkle(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 63de7c7edeeb..90b523231716 100644 --- a/tmk_core/common.mk +++ b/tmk_core/common.mk @@ -121,29 +121,6 @@ ifeq ($(strip $(NO_SUSPEND_POWER_DOWN)), yes) TMK_COMMON_DEFS += -DNO_SUSPEND_POWER_DOWN endif -ifeq ($(strip $(BLUETOOTH_ENABLE)), yes) - TMK_COMMON_DEFS += -DBLUETOOTH_ENABLE - TMK_COMMON_DEFS += -DNO_USB_STARTUP_CHECK -endif - -ifeq ($(strip $(BLUETOOTH)), AdafruitBLE) - TMK_COMMON_DEFS += -DBLUETOOTH_ENABLE - TMK_COMMON_DEFS += -DMODULE_ADAFRUIT_BLE - TMK_COMMON_DEFS += -DNO_USB_STARTUP_CHECK -endif - -ifeq ($(strip $(BLUETOOTH)), AdafruitEZKey) - TMK_COMMON_DEFS += -DBLUETOOTH_ENABLE - TMK_COMMON_DEFS += -DMODULE_ADAFRUIT_EZKEY - TMK_COMMON_DEFS += -DNO_USB_STARTUP_CHECK -endif - -ifeq ($(strip $(BLUETOOTH)), RN42) - TMK_COMMON_DEFS += -DBLUETOOTH_ENABLE - TMK_COMMON_DEFS += -DMODULE_RN42 - TMK_COMMON_DEFS += -DNO_USB_STARTUP_CHECK -endif - ifeq ($(strip $(ONEHAND_ENABLE)), yes) SWAP_HANDS_ENABLE = yes # backwards compatibility endif diff --git a/tmk_core/common/command.c b/tmk_core/common/command.c index ef6a39c0fe23..4a2402069600 100644 --- a/tmk_core/common/command.c +++ b/tmk_core/common/command.c @@ -287,6 +287,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/host.c b/tmk_core/common/host.c index e7d92cfac69e..f7b785129aa4 100644 --- a/tmk_core/common/host.c +++ b/tmk_core/common/host.c @@ -16,12 +16,16 @@ along with this program. If not, see . */ #include -//#include #include "keycode.h" #include "host.h" #include "util.h" #include "debug.h" +#ifdef BLUETOOTH_ENABLE +# include "outputselect.h" +# include "bluetooth.h" +#endif + #ifdef NKRO_ENABLE # include "keycode_config.h" extern keymap_config_t keymap_config; @@ -47,7 +51,6 @@ led_t host_keyboard_led_state(void) { /* send report */ void host_keyboard_send(report_keyboard_t *report) { - if (!driver) return; #if defined(NKRO_ENABLE) && defined(NKRO_SHARED_EP) if (keyboard_protocol && keymap_config.nkro) { /* The callers of this function assume that report->mods is where mods go in. @@ -62,6 +65,21 @@ void host_keyboard_send(report_keyboard_t *report) { report->report_id = REPORT_ID_KEYBOARD; #endif } + + if (!driver) return; + +#ifdef BLUETOOTH_ENABLE + uint8_t where = where_to_send(); + + if (where == OUTPUT_BLUETOOTH || where == OUTPUT_USB_AND_BT) { + bluetooth_send_keyboard(report); + } + + if (where != OUTPUT_USB && where != OUTPUT_USB_AND_BT) { + return; + } +#endif + (*driver->send_keyboard)(report); if (debug_keyboard) { @@ -74,10 +92,24 @@ void host_keyboard_send(report_keyboard_t *report) { } void host_mouse_send(report_mouse_t *report) { - if (!driver) return; #ifdef MOUSE_SHARED_EP report->report_id = REPORT_ID_MOUSE; #endif + + if (!driver) return; + +#if defined(BLUETOOTH_ENABLE) && defined(MOUSE_ENABLE) + uint8_t where = where_to_send(); + + if (where == OUTPUT_BLUETOOTH || where == OUTPUT_USB_AND_BT) { + bluetooth_send_mouse(report); + } + + if (where != OUTPUT_USB && where != OUTPUT_USB_AND_BT) { + return; + } +#endif + (*driver->send_mouse)(report); } @@ -94,6 +126,19 @@ void host_consumer_send(uint16_t report) { last_consumer_report = report; if (!driver) return; + +#ifdef BLUETOOTH_ENABLE + uint8_t where = where_to_send(); + + if (where == OUTPUT_BLUETOOTH || where == OUTPUT_USB_AND_BT) { + bluetooth_send_consumer(report); + } + + if (where != OUTPUT_USB && where != OUTPUT_USB_AND_BT) { + return; + } +#endif + (*driver->send_consumer)(report); } diff --git a/tmk_core/common/keyboard.c b/tmk_core/common/keyboard.c index 714c3d048f07..7e680e3e29d6 100644 --- a/tmk_core/common/keyboard.c +++ b/tmk_core/common/keyboard.c @@ -92,6 +92,9 @@ along with this program. If not, see . #ifdef DIP_SWITCH_ENABLE # include "dip_switch.h" #endif +#ifdef BLUETOOTH_ENABLE +# include "bluetooth.h" +#endif // Only enable this if console is enabled to print to #if defined(DEBUG_MATRIX_SCAN_RATE) && defined(CONSOLE_ENABLE) @@ -274,6 +277,9 @@ void keyboard_init(void) { #ifdef POINTING_DEVICE_ENABLE pointing_device_init(); #endif +#ifdef BLUETOOTH_ENABLE + bluetooth_init(); +#endif #if defined(NKRO_ENABLE) && defined(FORCE_NKRO) keymap_config.nkro = 1; eeconfig_update_keymap(keymap_config.raw); @@ -420,6 +426,10 @@ void keyboard_task(void) { } #endif +#ifdef BLUETOOTH_ENABLE + bluetooth_task(); +#endif + // update LED if (led_status != host_keyboard_leds()) { led_status = host_keyboard_leds(); diff --git a/tmk_core/common/uart.c b/tmk_core/common/uart.c index 150e256c8f2f..1a1b3ba44915 100644 --- a/tmk_core/common/uart.c +++ b/tmk_core/common/uart.c @@ -28,12 +28,14 @@ #include #include +#include #include "uart.h" #if defined(__AVR_ATmega168__) || defined(__AVR_ATmega168P__) || defined(__AVR_ATmega328P__) || defined(__AVR_ATmega328__) # define UDRn UDR0 # define UBRRnL UBRR0L +# define UBRRnH UBRR0H # define UCSRnA UCSR0A # define UCSRnB UCSR0B # define UCSRnC UCSR0C @@ -46,9 +48,13 @@ # 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 @@ -61,9 +67,13 @@ # 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 @@ -76,97 +86,178 @@ # 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 // These buffers may be any size from 2 to 256 bytes. #define RX_BUFFER_SIZE 64 #define TX_BUFFER_SIZE 256 +#ifndef cbi +# define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) +#endif +#ifndef sbi +# define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) +#endif + static volatile uint8_t tx_buffer[TX_BUFFER_SIZE]; -static volatile uint8_t tx_buffer_head; -static volatile uint8_t tx_buffer_tail; +static volatile uint8_t tx_buffer_head = 0; +static volatile uint8_t tx_buffer_tail = 0; static volatile uint8_t rx_buffer[RX_BUFFER_SIZE]; -static volatile uint8_t rx_buffer_head; -static volatile uint8_t rx_buffer_tail; +static volatile uint8_t rx_buffer_head = 0; +static volatile uint8_t rx_buffer_tail = 0; + +static uint8_t written = 0; // Initialize the UART void uart_init(uint32_t baud) { - cli(); - UBRRnL = (F_CPU / 4 / baud - 1) / 2; - UCSRnA = (1 << U2Xn); - UCSRnB = (1 << RXENn) | (1 << TXENn) | (1 << RXCIEn); - UCSRnC = (1 << UCSZn1) | (1 << UCSZn0); - tx_buffer_head = tx_buffer_tail = 0; - rx_buffer_head = rx_buffer_tail = 0; - sei(); + 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; + + // set the data bits, parity, and stop bits + UCSRnC = (1 << UCSZn1) | (1 << UCSZn0); + + sbi(UCSRnB, RXENn); + sbi(UCSRnB, TXENn); + sbi(UCSRnB, RXCIEn); + cbi(UCSRnB, UDRIEn); +} + +inline static void 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) % 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); + } } // Transmit a byte void uart_putchar(uint8_t c) { - uint8_t i; - - i = tx_buffer_head + 1; - if (i >= TX_BUFFER_SIZE) i = 0; - // return immediately to avoid deadlock when interrupt is disabled(called from ISR) - if (tx_buffer_tail == i && (SREG & (1 << SREG_I)) == 0) return; - while (tx_buffer_tail == i) - ; // wait until space in buffer - // cli(); - tx_buffer[i] = c; - tx_buffer_head = i; - UCSRnB = (1 << RXENn) | (1 << TXENn) | (1 << RXCIEn) | (1 << UDRIEn); - // sei(); + written = 1; + + // 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; + } + uint8_t i = (tx_buffer_head + 1) % 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); + } } // Receive a byte uint8_t uart_getchar(void) { - uint8_t c, i; - while (rx_buffer_head == rx_buffer_tail) ; // wait for character - i = rx_buffer_tail + 1; - if (i >= RX_BUFFER_SIZE) i = 0; - c = rx_buffer[i]; - rx_buffer_tail = i; + + unsigned char c = rx_buffer[rx_buffer_tail]; + rx_buffer_tail = (rx_buffer_tail + 1) % RX_BUFFER_SIZE; return c; } +void uart_flush(void) { + // 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). +} + +uint8_t uart_peek(void) { + if (rx_buffer_head == rx_buffer_tail) return -1; + return rx_buffer[rx_buffer_tail]; +} + // Return the number of bytes waiting in the receive buffer. // Call this before uart_getchar() to check if it will need // to wait for a byte to arrive. -uint8_t uart_available(void) { - uint8_t head, tail; - - head = rx_buffer_head; - tail = rx_buffer_tail; - if (head >= tail) return head - tail; - return RX_BUFFER_SIZE + head - tail; -} +uint8_t uart_available(void) { return (RX_BUFFER_SIZE + rx_buffer_head - rx_buffer_tail) % RX_BUFFER_SIZE; } // Transmit Interrupt -ISR(USARTn_UDRE_vect) { - uint8_t i; - - if (tx_buffer_head == tx_buffer_tail) { - // buffer is empty, disable transmit interrupt - UCSRnB = (1 << RXENn) | (1 << TXENn) | (1 << RXCIEn); - } else { - i = tx_buffer_tail + 1; - if (i >= TX_BUFFER_SIZE) i = 0; - UDRn = tx_buffer[i]; - tx_buffer_tail = i; - } -} +ISR(USARTn_UDRE_vect) { tx_udr_empty_irq(); } // Receive Interrupt ISR(USARTn_RX_vect) { - uint8_t c, i; - - c = UDRn; - i = rx_buffer_head + 1; - if (i >= RX_BUFFER_SIZE) i = 0; - if (i != rx_buffer_tail) { - rx_buffer[i] = c; - rx_buffer_head = i; - } + 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; + uint8_t i = (rx_buffer_head + 1) % 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; + }; } diff --git a/tmk_core/common/uart.h b/tmk_core/common/uart.h index 59a1a7cd12e4..bdb814fb8bf3 100644 --- a/tmk_core/common/uart.h +++ b/tmk_core/common/uart.h @@ -1,11 +1,21 @@ #ifndef _uart_included_h_ #define _uart_included_h_ +#ifdef __cplusplus +extern "C" { +#endif + #include void uart_init(uint32_t baud); void uart_putchar(uint8_t c); +void uart_flush(void); +uint8_t uart_peek(void); uint8_t uart_getchar(void); uint8_t uart_available(void); +#ifdef __cplusplus +} +#endif + #endif diff --git a/tmk_core/protocol/lufa.mk b/tmk_core/protocol/lufa.mk index d87802992e93..dd0d601603fa 100644 --- a/tmk_core/protocol/lufa.mk +++ b/tmk_core/protocol/lufa.mk @@ -23,27 +23,6 @@ ifeq ($(strip $(MIDI_ENABLE)), yes) include $(TMK_PATH)/protocol/midi.mk endif -ifeq ($(strip $(BLUETOOTH_ENABLE)), yes) - LUFA_SRC += $(LUFA_DIR)/bluetooth.c \ - $(TMK_DIR)/protocol/serial_uart.c -endif - -ifeq ($(strip $(BLUETOOTH)), AdafruitBLE) - LUFA_SRC += spi_master.c - LUFA_SRC += analog.c - LUFA_SRC += $(LUFA_DIR)/adafruit_ble.cpp -endif - -ifeq ($(strip $(BLUETOOTH)), AdafruitEZKey) - LUFA_SRC += $(LUFA_DIR)/bluetooth.c \ - $(TMK_DIR)/protocol/serial_uart.c -endif - -ifeq ($(strip $(BLUETOOTH)), RN42) - LUFA_SRC += $(LUFA_DIR)/bluetooth.c \ - $(TMK_DIR)/protocol/serial_uart.c -endif - ifeq ($(strip $(VIRTSER_ENABLE)), yes) LUFA_SRC += $(LUFA_ROOT_PATH)/Drivers/USB/Class/Device/CDCClassDevice.c endif diff --git a/tmk_core/protocol/lufa/adafruit_ble.cpp b/tmk_core/protocol/lufa/adafruit_ble.cpp deleted file mode 100644 index b07407f38775..000000000000 --- a/tmk_core/protocol/lufa/adafruit_ble.cpp +++ /dev/null @@ -1,706 +0,0 @@ -#include "adafruit_ble.h" - -#include -#include -#include -#include "debug.h" -#include "timer.h" -#include "action_util.h" -#include "ringbuffer.hpp" -#include -#include "spi_master.h" -#include "wait.h" -#include "analog.h" - -// These are the pin assignments for the 32u4 boards. -// You may define them to something else in your config.h -// if yours is wired up differently. -#ifndef AdafruitBleResetPin -# define AdafruitBleResetPin D4 -#endif - -#ifndef AdafruitBleCSPin -# define AdafruitBleCSPin B4 -#endif - -#ifndef AdafruitBleIRQPin -# define AdafruitBleIRQPin E6 -#endif - -#ifndef AdafruitBleSpiClockSpeed -# define AdafruitBleSpiClockSpeed 4000000UL // SCK frequency -#endif - -#define SCK_DIVISOR (F_CPU / AdafruitBleSpiClockSpeed) - -#define SAMPLE_BATTERY -#define ConnectionUpdateInterval 1000 /* milliseconds */ - -#ifdef SAMPLE_BATTERY -# ifndef BATTERY_LEVEL_PIN -# define BATTERY_LEVEL_PIN 7 -# endif -#endif - -static struct { - bool is_connected; - bool initialized; - bool configured; - -#define ProbedEvents 1 -#define UsingEvents 2 - bool event_flags; - -#ifdef SAMPLE_BATTERY - uint16_t last_battery_update; - uint32_t vbat; -#endif - uint16_t last_connection_update; -} state; - -// Commands are encoded using SDEP and sent via SPI -// https://github.com/adafruit/Adafruit_BluefruitLE_nRF51/blob/master/SDEP.md - -#define SdepMaxPayload 16 -struct sdep_msg { - uint8_t type; - uint8_t cmd_low; - uint8_t cmd_high; - struct __attribute__((packed)) { - uint8_t len : 7; - uint8_t more : 1; - }; - uint8_t payload[SdepMaxPayload]; -} __attribute__((packed)); - -// The recv latency is relatively high, so when we're hammering keys quickly, -// we want to avoid waiting for the responses in the matrix loop. We maintain -// a short queue for that. Since there is quite a lot of space overhead for -// the AT command representation wrapped up in SDEP, we queue the minimal -// information here. - -enum queue_type { - QTKeyReport, // 1-byte modifier + 6-byte key report - QTConsumer, // 16-bit key code -#ifdef MOUSE_ENABLE - QTMouseMove, // 4-byte mouse report -#endif -}; - -struct queue_item { - enum queue_type queue_type; - uint16_t added; - union __attribute__((packed)) { - struct __attribute__((packed)) { - uint8_t modifier; - uint8_t keys[6]; - } key; - - uint16_t consumer; - struct __attribute__((packed)) { - int8_t x, y, scroll, pan; - uint8_t buttons; - } mousemove; - }; -}; - -// Items that we wish to send -static RingBuffer send_buf; -// Pending response; while pending, we can't send any more requests. -// This records the time at which we sent the command for which we -// are expecting a response. -static RingBuffer resp_buf; - -static bool process_queue_item(struct queue_item *item, uint16_t timeout); - -enum sdep_type { - SdepCommand = 0x10, - SdepResponse = 0x20, - SdepAlert = 0x40, - SdepError = 0x80, - SdepSlaveNotReady = 0xfe, // Try again later - SdepSlaveOverflow = 0xff, // You read more data than is available -}; - -enum ble_cmd { - BleInitialize = 0xbeef, - BleAtWrapper = 0x0a00, - BleUartTx = 0x0a01, - BleUartRx = 0x0a02, -}; - -enum ble_system_event_bits { - BleSystemConnected = 0, - BleSystemDisconnected = 1, - BleSystemUartRx = 8, - BleSystemMidiRx = 10, -}; - -#define SdepTimeout 150 /* milliseconds */ -#define SdepShortTimeout 10 /* milliseconds */ -#define SdepBackOff 25 /* microseconds */ -#define BatteryUpdateInterval 10000 /* milliseconds */ - -static bool at_command(const char *cmd, char *resp, uint16_t resplen, bool verbose, uint16_t timeout = SdepTimeout); -static bool at_command_P(const char *cmd, char *resp, uint16_t resplen, bool verbose = false); - -// Send a single SDEP packet -static bool sdep_send_pkt(const struct sdep_msg *msg, uint16_t timeout) { - spi_start(AdafruitBleCSPin, false, 0, SCK_DIVISOR); - uint16_t timerStart = timer_read(); - bool success = false; - bool ready = false; - - do { - ready = spi_write(msg->type) != SdepSlaveNotReady; - if (ready) { - break; - } - - // Release it and let it initialize - spi_stop(); - wait_us(SdepBackOff); - spi_start(AdafruitBleCSPin, false, 0, SCK_DIVISOR); - } while (timer_elapsed(timerStart) < timeout); - - if (ready) { - // Slave is ready; send the rest of the packet - spi_transmit(&msg->cmd_low, sizeof(*msg) - (1 + sizeof(msg->payload)) + msg->len); - success = true; - } - - spi_stop(); - - return success; -} - -static inline void sdep_build_pkt(struct sdep_msg *msg, uint16_t command, const uint8_t *payload, uint8_t len, bool moredata) { - msg->type = SdepCommand; - msg->cmd_low = command & 0xff; - msg->cmd_high = command >> 8; - msg->len = len; - msg->more = (moredata && len == SdepMaxPayload) ? 1 : 0; - - static_assert(sizeof(*msg) == 20, "msg is correctly packed"); - - memcpy(msg->payload, payload, len); -} - -// Read a single SDEP packet -static bool sdep_recv_pkt(struct sdep_msg *msg, uint16_t timeout) { - bool success = false; - uint16_t timerStart = timer_read(); - bool ready = false; - - do { - ready = readPin(AdafruitBleIRQPin); - if (ready) { - break; - } - wait_us(1); - } while (timer_elapsed(timerStart) < timeout); - - if (ready) { - spi_start(AdafruitBleCSPin, false, 0, SCK_DIVISOR); - - do { - // Read the command type, waiting for the data to be ready - msg->type = spi_read(); - if (msg->type == SdepSlaveNotReady || msg->type == SdepSlaveOverflow) { - // Release it and let it initialize - spi_stop(); - wait_us(SdepBackOff); - spi_start(AdafruitBleCSPin, false, 0, SCK_DIVISOR); - continue; - } - - // Read the rest of the header - spi_receive(&msg->cmd_low, sizeof(*msg) - (1 + sizeof(msg->payload))); - - // and get the payload if there is any - if (msg->len <= SdepMaxPayload) { - spi_receive(msg->payload, msg->len); - } - success = true; - break; - } while (timer_elapsed(timerStart) < timeout); - - spi_stop(); - } - return success; -} - -static void resp_buf_read_one(bool greedy) { - uint16_t last_send; - if (!resp_buf.peek(last_send)) { - return; - } - - if (readPin(AdafruitBleIRQPin)) { - struct sdep_msg msg; - - again: - if (sdep_recv_pkt(&msg, SdepTimeout)) { - if (!msg.more) { - // We got it; consume this entry - resp_buf.get(last_send); - dprintf("recv latency %dms\n", TIMER_DIFF_16(timer_read(), last_send)); - } - - if (greedy && resp_buf.peek(last_send) && readPin(AdafruitBleIRQPin)) { - goto again; - } - } - - } else if (timer_elapsed(last_send) > SdepTimeout * 2) { - dprintf("waiting_for_result: timeout, resp_buf size %d\n", (int)resp_buf.size()); - - // Timed out: consume this entry - resp_buf.get(last_send); - } -} - -static void send_buf_send_one(uint16_t timeout = SdepTimeout) { - struct queue_item item; - - // Don't send anything more until we get an ACK - if (!resp_buf.empty()) { - return; - } - - if (!send_buf.peek(item)) { - return; - } - if (process_queue_item(&item, timeout)) { - // commit that peek - send_buf.get(item); - dprintf("send_buf_send_one: have %d remaining\n", (int)send_buf.size()); - } else { - dprint("failed to send, will retry\n"); - wait_ms(SdepTimeout); - resp_buf_read_one(true); - } -} - -static void resp_buf_wait(const char *cmd) { - bool didPrint = false; - while (!resp_buf.empty()) { - if (!didPrint) { - dprintf("wait on buf for %s\n", cmd); - didPrint = true; - } - resp_buf_read_one(true); - } -} - -static bool ble_init(void) { - state.initialized = false; - state.configured = false; - state.is_connected = false; - - setPinInput(AdafruitBleIRQPin); - - spi_init(); - - // Perform a hardware reset - setPinOutput(AdafruitBleResetPin); - writePinHigh(AdafruitBleResetPin); - writePinLow(AdafruitBleResetPin); - wait_ms(10); - writePinHigh(AdafruitBleResetPin); - - wait_ms(1000); // Give it a second to initialize - - state.initialized = true; - return state.initialized; -} - -static inline uint8_t min(uint8_t a, uint8_t b) { return a < b ? a : b; } - -static bool read_response(char *resp, uint16_t resplen, bool verbose) { - char *dest = resp; - char *end = dest + resplen; - - while (true) { - struct sdep_msg msg; - - if (!sdep_recv_pkt(&msg, 2 * SdepTimeout)) { - dprint("sdep_recv_pkt failed\n"); - return false; - } - - if (msg.type != SdepResponse) { - *resp = 0; - return false; - } - - uint8_t len = min(msg.len, end - dest); - if (len > 0) { - memcpy(dest, msg.payload, len); - dest += len; - } - - if (!msg.more) { - // No more data is expected! - break; - } - } - - // Ensure the response is NUL terminated - *dest = 0; - - // "Parse" the result text; we want to snip off the trailing OK or ERROR line - // Rewind past the possible trailing CRLF so that we can strip it - --dest; - while (dest > resp && (dest[0] == '\n' || dest[0] == '\r')) { - *dest = 0; - --dest; - } - - // Look back for start of preceeding line - char *last_line = strrchr(resp, '\n'); - if (last_line) { - ++last_line; - } else { - last_line = resp; - } - - bool success = false; - static const char kOK[] PROGMEM = "OK"; - - success = !strcmp_P(last_line, kOK); - - if (verbose || !success) { - dprintf("result: %s\n", resp); - } - return success; -} - -static bool at_command(const char *cmd, char *resp, uint16_t resplen, bool verbose, uint16_t timeout) { - const char * end = cmd + strlen(cmd); - struct sdep_msg msg; - - if (verbose) { - dprintf("ble send: %s\n", cmd); - } - - if (resp) { - // They want to decode the response, so we need to flush and wait - // for all pending I/O to finish before we start this one, so - // that we don't confuse the results - resp_buf_wait(cmd); - *resp = 0; - } - - // Fragment the command into a series of SDEP packets - while (end - cmd > SdepMaxPayload) { - sdep_build_pkt(&msg, BleAtWrapper, (uint8_t *)cmd, SdepMaxPayload, true); - if (!sdep_send_pkt(&msg, timeout)) { - return false; - } - cmd += SdepMaxPayload; - } - - sdep_build_pkt(&msg, BleAtWrapper, (uint8_t *)cmd, end - cmd, false); - if (!sdep_send_pkt(&msg, timeout)) { - return false; - } - - if (resp == NULL) { - auto now = timer_read(); - while (!resp_buf.enqueue(now)) { - resp_buf_read_one(false); - } - auto later = timer_read(); - if (TIMER_DIFF_16(later, now) > 0) { - dprintf("waited %dms for resp_buf\n", TIMER_DIFF_16(later, now)); - } - return true; - } - - return read_response(resp, resplen, verbose); -} - -bool at_command_P(const char *cmd, char *resp, uint16_t resplen, bool verbose) { - auto cmdbuf = (char *)alloca(strlen_P(cmd) + 1); - strcpy_P(cmdbuf, cmd); - return at_command(cmdbuf, resp, resplen, verbose); -} - -bool adafruit_ble_is_connected(void) { return state.is_connected; } - -bool adafruit_ble_enable_keyboard(void) { - char resbuf[128]; - - if (!state.initialized && !ble_init()) { - return false; - } - - state.configured = false; - - // Disable command echo - static const char kEcho[] PROGMEM = "ATE=0"; - // Make the advertised name match the keyboard - static const char kGapDevName[] PROGMEM = "AT+GAPDEVNAME=" STR(PRODUCT); - // Turn on keyboard support - static const char kHidEnOn[] PROGMEM = "AT+BLEHIDEN=1"; - - // Adjust intervals to improve latency. This causes the "central" - // system (computer/tablet) to poll us every 10-30 ms. We can't - // set a smaller value than 10ms, and 30ms seems to be the natural - // processing time on my macbook. Keeping it constrained to that - // feels reasonable to type to. - static const char kGapIntervals[] PROGMEM = "AT+GAPINTERVALS=10,30,,"; - - // Reset the device so that it picks up the above changes - static const char kATZ[] PROGMEM = "ATZ"; - - // Turn down the power level a bit - static const char kPower[] PROGMEM = "AT+BLEPOWERLEVEL=-12"; - static PGM_P const configure_commands[] PROGMEM = { - kEcho, kGapIntervals, kGapDevName, kHidEnOn, kPower, kATZ, - }; - - uint8_t i; - for (i = 0; i < sizeof(configure_commands) / sizeof(configure_commands[0]); ++i) { - PGM_P cmd; - memcpy_P(&cmd, configure_commands + i, sizeof(cmd)); - - if (!at_command_P(cmd, resbuf, sizeof(resbuf))) { - dprintf("failed BLE command: %S: %s\n", cmd, resbuf); - goto fail; - } - } - - state.configured = true; - - // Check connection status in a little while; allow the ATZ time - // to kick in. - state.last_connection_update = timer_read(); -fail: - return state.configured; -} - -static void set_connected(bool connected) { - if (connected != state.is_connected) { - if (connected) { - print("****** BLE CONNECT!!!!\n"); - } else { - print("****** BLE DISCONNECT!!!!\n"); - } - state.is_connected = connected; - - // TODO: if modifiers are down on the USB interface and - // we cut over to BLE or vice versa, they will remain stuck. - // This feels like a good point to do something like clearing - // the keyboard and/or generating a fake all keys up message. - // However, I've noticed that it takes a couple of seconds - // for macOS to to start recognizing key presses after BLE - // is in the connected state, so I worry that doing that - // here may not be good enough. - } -} - -void adafruit_ble_task(void) { - char resbuf[48]; - - if (!state.configured && !adafruit_ble_enable_keyboard()) { - return; - } - resp_buf_read_one(true); - send_buf_send_one(SdepShortTimeout); - - if (resp_buf.empty() && (state.event_flags & UsingEvents) && readPin(AdafruitBleIRQPin)) { - // Must be an event update - if (at_command_P(PSTR("AT+EVENTSTATUS"), resbuf, sizeof(resbuf))) { - uint32_t mask = strtoul(resbuf, NULL, 16); - - if (mask & BleSystemConnected) { - set_connected(true); - } else if (mask & BleSystemDisconnected) { - set_connected(false); - } - } - } - - if (timer_elapsed(state.last_connection_update) > ConnectionUpdateInterval) { - bool shouldPoll = true; - if (!(state.event_flags & ProbedEvents)) { - // Request notifications about connection status changes. - // This only works in SPIFRIEND firmware > 0.6.7, which is why - // we check for this conditionally here. - // Note that at the time of writing, HID reports only work correctly - // with Apple products on firmware version 0.6.7! - // https://forums.adafruit.com/viewtopic.php?f=8&t=104052 - if (at_command_P(PSTR("AT+EVENTENABLE=0x1"), resbuf, sizeof(resbuf))) { - at_command_P(PSTR("AT+EVENTENABLE=0x2"), resbuf, sizeof(resbuf)); - state.event_flags |= UsingEvents; - } - state.event_flags |= ProbedEvents; - - // leave shouldPoll == true so that we check at least once - // before relying solely on events - } else { - shouldPoll = false; - } - - static const char kGetConn[] PROGMEM = "AT+GAPGETCONN"; - state.last_connection_update = timer_read(); - - if (at_command_P(kGetConn, resbuf, sizeof(resbuf))) { - set_connected(atoi(resbuf)); - } - } - -#ifdef SAMPLE_BATTERY - if (timer_elapsed(state.last_battery_update) > BatteryUpdateInterval && resp_buf.empty()) { - state.last_battery_update = timer_read(); - - state.vbat = analogRead(BATTERY_LEVEL_PIN); - } -#endif -} - -static bool process_queue_item(struct queue_item *item, uint16_t timeout) { - char cmdbuf[48]; - char fmtbuf[64]; - - // Arrange to re-check connection after keys have settled - state.last_connection_update = timer_read(); - -#if 1 - if (TIMER_DIFF_16(state.last_connection_update, item->added) > 0) { - dprintf("send latency %dms\n", TIMER_DIFF_16(state.last_connection_update, item->added)); - } -#endif - - switch (item->queue_type) { - case QTKeyReport: - strcpy_P(fmtbuf, PSTR("AT+BLEKEYBOARDCODE=%02x-00-%02x-%02x-%02x-%02x-%02x-%02x")); - snprintf(cmdbuf, sizeof(cmdbuf), fmtbuf, item->key.modifier, item->key.keys[0], item->key.keys[1], item->key.keys[2], item->key.keys[3], item->key.keys[4], item->key.keys[5]); - return at_command(cmdbuf, NULL, 0, true, timeout); - - case QTConsumer: - strcpy_P(fmtbuf, PSTR("AT+BLEHIDCONTROLKEY=0x%04x")); - snprintf(cmdbuf, sizeof(cmdbuf), fmtbuf, item->consumer); - return at_command(cmdbuf, NULL, 0, true, timeout); - -#ifdef MOUSE_ENABLE - case QTMouseMove: - strcpy_P(fmtbuf, PSTR("AT+BLEHIDMOUSEMOVE=%d,%d,%d,%d")); - snprintf(cmdbuf, sizeof(cmdbuf), fmtbuf, item->mousemove.x, item->mousemove.y, item->mousemove.scroll, item->mousemove.pan); - if (!at_command(cmdbuf, NULL, 0, true, timeout)) { - return false; - } - strcpy_P(cmdbuf, PSTR("AT+BLEHIDMOUSEBUTTON=")); - if (item->mousemove.buttons & MOUSE_BTN1) { - strcat(cmdbuf, "L"); - } - if (item->mousemove.buttons & MOUSE_BTN2) { - strcat(cmdbuf, "R"); - } - if (item->mousemove.buttons & MOUSE_BTN3) { - strcat(cmdbuf, "M"); - } - if (item->mousemove.buttons == 0) { - strcat(cmdbuf, "0"); - } - return at_command(cmdbuf, NULL, 0, true, timeout); -#endif - default: - return true; - } -} - -bool adafruit_ble_send_keys(uint8_t hid_modifier_mask, uint8_t *keys, uint8_t nkeys) { - struct queue_item item; - bool didWait = false; - - item.queue_type = QTKeyReport; - item.key.modifier = hid_modifier_mask; - item.added = timer_read(); - - while (nkeys >= 0) { - item.key.keys[0] = keys[0]; - item.key.keys[1] = nkeys >= 1 ? keys[1] : 0; - item.key.keys[2] = nkeys >= 2 ? keys[2] : 0; - item.key.keys[3] = nkeys >= 3 ? keys[3] : 0; - item.key.keys[4] = nkeys >= 4 ? keys[4] : 0; - item.key.keys[5] = nkeys >= 5 ? keys[5] : 0; - - if (!send_buf.enqueue(item)) { - if (!didWait) { - dprint("wait for buf space\n"); - didWait = true; - } - send_buf_send_one(); - continue; - } - - if (nkeys <= 6) { - return true; - } - - nkeys -= 6; - keys += 6; - } - - return true; -} - -bool adafruit_ble_send_consumer_key(uint16_t keycode, int hold_duration) { - struct queue_item item; - - item.queue_type = QTConsumer; - item.consumer = keycode; - - while (!send_buf.enqueue(item)) { - send_buf_send_one(); - } - return true; -} - -#ifdef MOUSE_ENABLE -bool adafruit_ble_send_mouse_move(int8_t x, int8_t y, int8_t scroll, int8_t pan, uint8_t buttons) { - struct queue_item item; - - item.queue_type = QTMouseMove; - item.mousemove.x = x; - item.mousemove.y = y; - item.mousemove.scroll = scroll; - item.mousemove.pan = pan; - item.mousemove.buttons = buttons; - - while (!send_buf.enqueue(item)) { - send_buf_send_one(); - } - return true; -} -#endif - -uint32_t adafruit_ble_read_battery_voltage(void) { return state.vbat; } - -bool adafruit_ble_set_mode_leds(bool on) { - if (!state.configured) { - return false; - } - - // The "mode" led is the red blinky one - at_command_P(on ? PSTR("AT+HWMODELED=1") : PSTR("AT+HWMODELED=0"), NULL, 0); - - // Pin 19 is the blue "connected" LED; turn that off too. - // When turning LEDs back on, don't turn that LED on if we're - // not connected, as that would be confusing. - at_command_P(on && state.is_connected ? PSTR("AT+HWGPIO=19,1") : PSTR("AT+HWGPIO=19,0"), NULL, 0); - return true; -} - -// https://learn.adafruit.com/adafruit-feather-32u4-bluefruit-le/ble-generic#at-plus-blepowerlevel -bool adafruit_ble_set_power_level(int8_t level) { - char cmd[46]; - if (!state.configured) { - return false; - } - snprintf(cmd, sizeof(cmd), "AT+BLEPOWERLEVEL=%d", level); - return at_command(cmd, NULL, 0, false); -} diff --git a/tmk_core/protocol/lufa/adafruit_ble.h b/tmk_core/protocol/lufa/adafruit_ble.h deleted file mode 100644 index cef46fe9f75a..000000000000 --- a/tmk_core/protocol/lufa/adafruit_ble.h +++ /dev/null @@ -1,61 +0,0 @@ -/* Bluetooth Low Energy Protocol for QMK. - * Author: Wez Furlong, 2016 - * Supports the Adafruit BLE board built around the nRF51822 chip. - */ -#pragma once -#ifdef MODULE_ADAFRUIT_BLE -# include -# include -# include - -# include "config_common.h" -# include "progmem.h" - -# ifdef __cplusplus -extern "C" { -# endif - -/* Instruct the module to enable HID keyboard support and reset */ -extern bool adafruit_ble_enable_keyboard(void); - -/* Query to see if the BLE module is connected */ -extern bool adafruit_ble_query_is_connected(void); - -/* Returns true if we believe that the BLE module is connected. - * This uses our cached understanding that is maintained by - * calling ble_task() periodically. */ -extern bool adafruit_ble_is_connected(void); - -/* Call this periodically to process BLE-originated things */ -extern void adafruit_ble_task(void); - -/* Generates keypress events for a set of keys. - * The hid modifier mask specifies the state of the modifier keys for - * this set of keys. - * Also sends a key release indicator, so that the keys do not remain - * held down. */ -extern bool adafruit_ble_send_keys(uint8_t hid_modifier_mask, uint8_t *keys, uint8_t nkeys); - -/* Send a consumer keycode, holding it down for the specified duration - * (milliseconds) */ -extern bool adafruit_ble_send_consumer_key(uint16_t keycode, int hold_duration); - -# ifdef MOUSE_ENABLE -/* Send a mouse/wheel movement report. - * The parameters are signed and indicate positive of negative direction - * change. */ -extern bool adafruit_ble_send_mouse_move(int8_t x, int8_t y, int8_t scroll, int8_t pan, uint8_t buttons); -# endif - -/* Compute battery voltage by reading an analog pin. - * Returns the integer number of millivolts */ -extern uint32_t adafruit_ble_read_battery_voltage(void); - -extern bool adafruit_ble_set_mode_leds(bool on); -extern bool adafruit_ble_set_power_level(int8_t level); - -# ifdef __cplusplus -} -# endif - -#endif // MODULE_ADAFRUIT_BLE diff --git a/tmk_core/protocol/lufa/bluetooth.c b/tmk_core/protocol/lufa/bluetooth.c deleted file mode 100644 index 5eb52860b14a..000000000000 --- a/tmk_core/protocol/lufa/bluetooth.c +++ /dev/null @@ -1,38 +0,0 @@ -/* -Bluefruit Protocol for TMK firmware -Author: Benjamin Gould, 2013 - Jack Humbert, 2015 -Based on code Copyright 2011 Jun Wako -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 2 of the License, or -(at your option) any later version. -This program 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 General Public License for more details. -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -#include -#include "report.h" -#include "print.h" -#include "debug.h" -#include "bluetooth.h" - -void bluefruit_keyboard_print_report(report_keyboard_t *report) { - if (!debug_keyboard) return; - dprintf("keys: "); - for (int i = 0; i < KEYBOARD_REPORT_KEYS; i++) { - debug_hex8(report->keys[i]); - dprintf(" "); - } - dprintf(" mods: "); - debug_hex8(report->mods); - dprintf(" reserved: "); - debug_hex8(report->reserved); - dprintf("\n"); -} - -void bluefruit_serial_send(uint8_t data) { serial_send(data); } \ No newline at end of file diff --git a/tmk_core/protocol/lufa/lufa.c b/tmk_core/protocol/lufa/lufa.c index 374add20f977..e37dae16062e 100644 --- a/tmk_core/protocol/lufa/lufa.c +++ b/tmk_core/protocol/lufa/lufa.c @@ -53,7 +53,6 @@ #include "lufa.h" #include "quantum.h" #include -#include "outputselect.h" #ifdef NKRO_ENABLE # include "keycode_config.h" @@ -65,14 +64,6 @@ extern keymap_config_t keymap_config; # include #endif -#ifdef BLUETOOTH_ENABLE -# ifdef MODULE_ADAFRUIT_BLE -# include "adafruit_ble.h" -# else -# include "bluetooth.h" -# endif -#endif - #ifdef VIRTSER_ENABLE # include "virtser.h" #endif @@ -553,35 +544,6 @@ static uint8_t keyboard_leds(void) { return keyboard_led_stats; } */ static void send_keyboard(report_keyboard_t *report) { uint8_t timeout = 255; - uint8_t where = where_to_send(); - -#ifdef BLUETOOTH_ENABLE - if (where == OUTPUT_BLUETOOTH || where == OUTPUT_USB_AND_BT) { -# ifdef MODULE_ADAFRUIT_BLE - adafruit_ble_send_keys(report->mods, report->keys, sizeof(report->keys)); -# elif MODULE_RN42 - bluefruit_serial_send(0xFD); - bluefruit_serial_send(0x09); - bluefruit_serial_send(0x01); - bluefruit_serial_send(report->mods); - bluefruit_serial_send(report->reserved); - for (uint8_t i = 0; i < KEYBOARD_REPORT_KEYS; i++) { - bluefruit_serial_send(report->keys[i]); - } -# else - bluefruit_serial_send(0xFD); - bluefruit_serial_send(report->mods); - bluefruit_serial_send(report->reserved); - for (uint8_t i = 0; i < KEYBOARD_REPORT_KEYS; i++) { - bluefruit_serial_send(report->keys[i]); - } -# endif - } -#endif - - if (where != OUTPUT_USB && where != OUTPUT_USB_AND_BT) { - return; - } /* Select the Keyboard Report Endpoint */ uint8_t ep = KEYBOARD_IN_EPNUM; @@ -617,30 +579,6 @@ static void send_keyboard(report_keyboard_t *report) { static void send_mouse(report_mouse_t *report) { #ifdef MOUSE_ENABLE uint8_t timeout = 255; - uint8_t where = where_to_send(); - -# ifdef BLUETOOTH_ENABLE - if (where == OUTPUT_BLUETOOTH || where == OUTPUT_USB_AND_BT) { -# ifdef MODULE_ADAFRUIT_BLE - // FIXME: mouse buttons - adafruit_ble_send_mouse_move(report->x, report->y, report->v, report->h, report->buttons); -# else - bluefruit_serial_send(0xFD); - bluefruit_serial_send(0x00); - bluefruit_serial_send(0x03); - bluefruit_serial_send(report->buttons); - bluefruit_serial_send(report->x); - bluefruit_serial_send(report->y); - bluefruit_serial_send(report->v); // should try sending the wheel v here - bluefruit_serial_send(report->h); // should try sending the wheel h here - bluefruit_serial_send(0x00); -# endif - } -# endif - - if (where != OUTPUT_USB && where != OUTPUT_USB_AND_BT) { - return; - } /* Select the Mouse Report Endpoint */ Endpoint_SelectEndpoint(MOUSE_IN_EPNUM); @@ -695,44 +633,6 @@ static void send_system(uint16_t data) { */ static void send_consumer(uint16_t data) { #ifdef EXTRAKEY_ENABLE - uint8_t where = where_to_send(); - -# ifdef BLUETOOTH_ENABLE - if (where == OUTPUT_BLUETOOTH || where == OUTPUT_USB_AND_BT) { -# ifdef MODULE_ADAFRUIT_BLE - adafruit_ble_send_consumer_key(data, 0); -# elif MODULE_RN42 - static uint16_t last_data = 0; - if (data == last_data) return; - last_data = data; - uint16_t bitmap = CONSUMER2RN42(data); - bluefruit_serial_send(0xFD); - bluefruit_serial_send(0x03); - bluefruit_serial_send(0x03); - bluefruit_serial_send(bitmap & 0xFF); - bluefruit_serial_send((bitmap >> 8) & 0xFF); -# else - static uint16_t last_data = 0; - if (data == last_data) return; - last_data = data; - uint16_t bitmap = CONSUMER2BLUEFRUIT(data); - bluefruit_serial_send(0xFD); - bluefruit_serial_send(0x00); - bluefruit_serial_send(0x02); - bluefruit_serial_send((bitmap >> 8) & 0xFF); - bluefruit_serial_send(bitmap & 0xFF); - bluefruit_serial_send(0x00); - bluefruit_serial_send(0x00); - bluefruit_serial_send(0x00); - bluefruit_serial_send(0x00); -# endif - } -# endif - - if (where != OUTPUT_USB && where != OUTPUT_USB_AND_BT) { - return; - } - send_extra(REPORT_ID_CONSUMER, data); #endif } @@ -945,10 +845,6 @@ int main(void) { setup_usb(); sei(); -#if defined(MODULE_ADAFRUIT_EZKEY) || defined(MODULE_RN42) - serial_init(); -#endif - /* wait for USB startup & debug output */ #ifdef WAIT_FOR_USB @@ -992,10 +888,6 @@ int main(void) { MIDI_Device_USBTask(&USB_MIDI_Interface); #endif -#ifdef MODULE_ADAFRUIT_BLE - adafruit_ble_task(); -#endif - #ifdef VIRTSER_ENABLE virtser_task(); CDC_Device_USBTask(&cdc_device); diff --git a/tmk_core/protocol/lufa/outputselect.c b/tmk_core/protocol/lufa/outputselect.c index b115ea9691ef..a3508f2f97fd 100644 --- a/tmk_core/protocol/lufa/outputselect.c +++ b/tmk_core/protocol/lufa/outputselect.c @@ -14,8 +14,8 @@ along with this program. If not, see . #include "lufa.h" #include "outputselect.h" -#ifdef MODULE_ADAFRUIT_BLE -# include "adafruit_ble.h" +#ifdef BLUETOOTH_ENABLE +# include "bluetooth.h" #endif uint8_t desired_output = OUTPUT_DEFAULT; @@ -44,16 +44,12 @@ uint8_t auto_detect_output(void) { return OUTPUT_USB; } -#ifdef MODULE_ADAFRUIT_BLE - if (adafruit_ble_is_connected()) { +#ifdef BLUETOOTH_ENABLE + if (bluetooth_is_connected()) { return OUTPUT_BLUETOOTH; } #endif -#ifdef BLUETOOTH_ENABLE - return OUTPUT_BLUETOOTH; // should check if BT is connected here -#endif - return OUTPUT_NONE; } @@ -67,3 +63,5 @@ uint8_t where_to_send(void) { } return desired_output; } + +bool output_auto() { return (desired_output == OUTPUT_AUTO); } diff --git a/tmk_core/protocol/lufa/outputselect.h b/tmk_core/protocol/lufa/outputselect.h index 24fe4daa24fe..a82c38eea7dd 100644 --- a/tmk_core/protocol/lufa/outputselect.h +++ b/tmk_core/protocol/lufa/outputselect.h @@ -12,6 +12,12 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + enum outputs { OUTPUT_AUTO, @@ -37,4 +43,9 @@ enum outputs { void set_output(uint8_t output); void set_output_user(uint8_t output); uint8_t auto_detect_output(void); -uint8_t where_to_send(void); \ No newline at end of file +uint8_t where_to_send(void); +bool output_auto(void); + +#ifdef __cplusplus +} +#endif 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