Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change the Bluetooth API to use a driver framework #22783

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions builddefs/common_features.mk
Original file line number Diff line number Diff line change
Expand Up @@ -884,18 +884,16 @@ ifeq ($(strip $(BLUETOOTH_ENABLE)), yes)
OPT_DEFS += -DBLUETOOTH_$(strip $(shell echo $(BLUETOOTH_DRIVER) | tr '[:lower:]' '[:upper:]'))
NO_USB_STARTUP_CHECK := yes
COMMON_VPATH += $(DRIVER_PATH)/bluetooth
SRC += outputselect.c
SRC += $(DRIVER_PATH)/bluetooth/bluetooth.c

ifeq ($(strip $(BLUETOOTH_DRIVER)), bluefruit_le)
SPI_DRIVER_REQUIRED = yes
ANALOG_DRIVER_REQUIRED = yes
SRC += $(DRIVER_PATH)/bluetooth/bluetooth.c
SRC += $(DRIVER_PATH)/bluetooth/bluefruit_le.cpp
SRC += $(DRIVER_PATH)/bluetooth/bluefruit_le_spi.cpp
endif

ifeq ($(strip $(BLUETOOTH_DRIVER)), rn42)
UART_DRIVER_REQUIRED = yes
SRC += $(DRIVER_PATH)/bluetooth/bluetooth.c
SRC += $(DRIVER_PATH)/bluetooth/rn42.c
endif
endif
Expand Down
2 changes: 1 addition & 1 deletion docs/config_options.md
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ Use these to enable or disable building certain features. The more you have enab
* `UNICODE_ENABLE`
* Unicode
* `BLUETOOTH_ENABLE`
* Current options are bluefruit_le, rn42
* Enable Bluetooth driver. Also requires `BLUETOOTH_DRIVER`. Set to one of `bluefruit_le`, `rn42` or `custom`.
* `SPLIT_KEYBOARD`
* Enables split keyboard support (dual MCU like the let's split and bakingpy's boards) and includes all necessary files located at quantum/split_common
* `CUSTOM_MATRIX`
Expand Down
4 changes: 2 additions & 2 deletions docs/faq_keymap.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,9 @@ Japanese JIS keyboard specific keys like `無変換(Muhenkan)`, `変換(Henkan)`

https://pqrs.org/osx/karabiner/seil.html

## RN-42 Bluetooth Doesn't Work with Karabiner
## RN-42 Bluetooth Doesn't Work with Karabiner on OSX

Karabiner - Keymapping tool on Mac OSX - ignores inputs from RN-42 module by default. You have to enable this option to make Karabiner working with your keyboard.
Karabiner - Keymapping tool on Mac OSX - ignores inputs from RN-42 module by default. You have to enable this option to make Karabiner work with your keyboard.
https://github.com/tekezo/Karabiner/issues/403#issuecomment-102559237

See these for the detail of this problem.
Expand Down
169 changes: 141 additions & 28 deletions docs/feature_bluetooth.md
Original file line number Diff line number Diff line change
@@ -1,46 +1,159 @@
# Bluetooth

## Bluetooth Known Supported Hardware
This feature allows you to integrate into a Bluetooth module to create a Bluetooth keyboard for wireless operation.

Currently Bluetooth support is limited to AVR based chips. For Bluetooth 2.1, QMK has support for RN-42 modules. For more recent BLE protocols, currently only the Adafruit Bluefruit SPI Friend is directly supported. BLE is needed to connect to iOS devices. Note iOS does not support mouse input.
## Supported Modules

|Board |Bluetooth Protocol |Connection Type|rules.mk |Bluetooth Chip|
|----------------------------------------------------------------|--------------------|---------------|---------------------------------|--------------|
|Roving Networks RN-42 (Sparkfun Bluesmirf) |Bluetooth Classic |UART |`BLUETOOTH_DRIVER = rn42` |RN-42 |
|[Bluefruit LE SPI Friend](https://www.adafruit.com/product/2633)|Bluetooth Low Energy|SPI |`BLUETOOTH_DRIVER = bluefruit_le`|nRF51822 |
Currently on the following modules are supported:

Not Supported Yet but possible:
* [Bluefruit LE UART Friend](https://www.adafruit.com/product/2479). [Possible tmk implementation found in](https://github.com/tmk/tmk_keyboard/issues/514)
* HC-05 boards flashed with RN-42 firmware. They apparently both use the CSR BC417 Chip. Flashing it with RN-42 firmware gives it HID capability.
* Sparkfun Bluetooth Mate
* HM-13 based boards
| Board | Module based on | Bluetooth Protocol | Connection Type | rules.mk |Bluetooth Chip |
|--------------------------------------------------------------------|---------------------|-----------------------------|-----------------|---------------------------------|----------------|
|[Sparkfun BlueSMiRF Silver](https://www.sparkfun.com/products/12577)|Roving Networks RN-42|Bluetooth 2.1 *"Classic"* |UART |`BLUETOOTH_DRIVER = rn42` |RN42<sup>1</sup>|
|[Bluefruit LE SPI Friend](https://www.adafruit.com/product/2633) |Raytac MDBT40-256RV3 |Bluetooth 4.1 BLE<sup>2</sup>|SPI |`BLUETOOTH_DRIVER = bluefruit_le`|nRF51822 |

> <sup>1</sup> RN42 based are modules mostly retired or EOL as the BT2.1 spec is deprecated and withdrawn as of July 2020.
>
> <sup>2</sup> BLE is needed to connect to iOS devices.

Not supported yet, but possible:

* [Bluefruit LE UART Friend](https://www.adafruit.com/product/2479).
* HC-05, HC-06 and HC-08 modules.
* Modules based on Nordic nRF51*, nRF52* and nRF53* SoC's - however in most cases one will also need to develop firmware for those modules.

> **NOTE:** QMK does not currently support MCU sleep modes, and this may impact the longevity of battery based builds.

## Driver configuration

### Adafruit BLE SPI Friend
Currently The only bluetooth chipset supported by QMK is the Adafruit Bluefruit SPI Friend. It's a Nordic nRF51822 based chip running Adafruit's custom firmware. Data is transmitted via Adafruit's SDEP over Hardware SPI. The [Feather 32u4 Bluefruit LE](https://www.adafruit.com/product/2829) is supported as it's an AVR mcu connected via SPI to the Nordic BLE chip with Adafruit firmware. If Building a custom board with the SPI friend it would be easiest to just use the pin selection that the 32u4 feather uses but you can change the pins in the config.h options with the following defines:
* `#define BLUEFRUIT_LE_RST_PIN D4`
* `#define BLUEFRUIT_LE_CS_PIN B4`
* `#define BLUEFRUIT_LE_IRQ_PIN E6`

A Bluefruit UART friend can be converted to an SPI friend, however this [requires](https://github.com/qmk/qmk_firmware/issues/2274) some reflashing and soldering directly to the MDBT40 chip.
To enable support for the Adafruit Bluefruit LE SPI Friend, add this to your `rules.mk` file:

```make
NKRO_ENABLE = no # ** Required
BLUETOOTH_ENABLE = yes
BLUETOOTH_DRIVER = bluefruit_le
```

> ** This module does not support [N-Key Rollover (NKRO)](reference_glossary.md#n-key-rollover-nkro);

The Adafruit Bluefruit SPI Friend is a module based on the MDBT30 module with embedded Nordic nRF51822 chip, and is flashed with Adafruit's custom firmware.
This firmware uses AT Command sets over Adafruit's [SDEP](https://learn.adafruit.com/introducing-the-adafruit-bluefruit-spi-breakout/sdep-spi-data-transport) data transfer protocol.

#### SPI Configuration

<!-- FIXME: Document bluetooth support more completely. -->
## Bluetooth Rules.mk Options
QMK’s `spi_master` must already be correctly configured for the platform you’re building for.
In addition, you will also need to define the following items in `config.h``:

The currently supported Bluetooth chipsets do not support [N-Key Rollover (NKRO)](reference_glossary.md#n-key-rollover-nkro), so `rules.mk` must contain `NKRO_ENABLE = no`.
| Variable | Description | Default |
|------------------------|-------------------------------------------|---------|
| `BLUEFRUIT_LE_RST_PIN` | Used to perform a reset on initialization | D4 |
| `BLUEFRUIT_LE_CS_PIN` | SPI SS/CI "Chip Select" pin | B4 |
| `BLUEFRUIT_LE_IRQ_PIN` | Module Interrupt Request pin | E6 |

> Defaults are based on the [Adafruit Feather 32u4 Bluefruit](https://learn.adafruit.com/adafruit-feather-32u4-bluefruit-le/overview).

## Keycodes

The following keycodes will allow you change the output selector for the keyboard.
This allows for switching between USB and Bluetooth on keyboards that support both.

| Key | Aliases | Description |
|---------------------|---------|----------------------------------------------------------|
|`QK_OUTPUT_AUTO` |`OU_AUTO`|Automatically switch between USB and Bluetooth *(Default)*|
|`QK_OUTPUT_USB` |`OU_USB` |USB only |
|`QK_OUTPUT_BLUETOOTH`|`OU_BT` |Bluetooth only |

## Output Interface Selection

The above keycodes will in turn call the output selector API to direct all key, mouse, consumer, and system output events to the selected interface:

```c
send_output_t set_send_output(send_output_t send_output);
send_output_t get_send_output(void);
```

The *`send_output`* variable should be one of:

```c
enum send_output_t {
SEND_OUTPUT_AUTO, // Selection is USB if USB cable connected, else is Bluetooth is Bluetooth is connected. (Default)
SEND_OUTPUT_NONE, // No output is sent.
SEND_OUTPUT_USB, // Output is always USB.
SEND_OUTPUT_BLUETOOTH, // Output is always Bluetooth.
SEND_OUTPUT_BOTH // Output is sent to both USb and Bluetooth (used for testing).
};
```

Add the following to your `rules.mk`:
You can change the initial state with the following define in the `config.h` file:

```c
#define SEND_OUTPUT_DEFAULT SEND_OUTPUT_AUTO
```

Vendor and keymap code can also use the keyboard-level and user-level (to either perform ancillary functions on the setting, or override the setting) by creating the following functions in the `kb_name.c` or the `keymap.c` files:

```c
send_output_t set_send_output_kb(send_output_t send_output); // kb-level function usually in the `kb_name.c file`. Should also call `set_send_output_user`.
send_output_t set_send_output_user(send_output_t send_output); // user-level function usually in `keymap.c` file.
```

## Bluetooth Driver API

### Custom Bluetooth Driver

You can create your own Bluetooth driver (or for that matter, any alternate output interface) with the `bluetooth_driver` variable, and the `bluetooth_driver_t` struct.

Then, to enable a custom Bluetooth driver add this to your `rules.mk` file:

```make
BLUETOOTH_ENABLE = yes
BLUETOOTH_DRIVER = bluefruit_le # or rn42
BLUETOOTH_DRIVER = custom
```

#### Example

You can create your own Bluetooth driver by creating compliant functions and referencing these with within the `bluetooth_driver` variable.

In your `kb_name.c` file, create that variable as such:

```c
const bluetooth_driver_t bluetooth_driver = {
.init = xyz_bt_init,
.task = xyz_bt_task, // Optional
.is_connected = xyz_bt_is_connected, // Optional
.send_keyboard = xyz_bt_send_keyboard,
.send_mouse = xyz_bt_send_mouse,
.send_consumer = xyz_bt_send_consumer,
.send_system = xyz_bt_send_system, // Optional
};
```

## Bluetooth Keycodes
> * Members `init`, `send_keyboard`, `send_mouse` and `send_consumer` must be provided.
> * Members `task`, `is_connected` and `send_system` are optional and may be set to `NULL`.


Then code for the following functions:

```c
/* Initialize the Bluetooth system. */
void xyz_bt_init(void) {}

This is used when multiple keyboard outputs can be selected. Currently this only allows for switching between USB and Bluetooth on keyboards that support both.
/* Perform housekeeping tasks. Called every loop. (Optional) */
void xyz_bt_task(void) {}

|Key |Aliases |Description |
|---------------------|---------|----------------------------------------------|
|`QK_OUTPUT_AUTO` |`OU_AUTO`|Automatically switch between USB and Bluetooth|
|`QK_OUTPUT_USB` |`OU_USB` |USB only |
|`QK_OUTPUT_BLUETOOTH`|`OU_BT` |Bluetooth only |
/* Detects if BT is connected, also used by `SEND_OUTPUT_AUTO`. (Optional) */
bool xyz_bt_is_connected(void) {}

/* Send a keyboard report. */
void xyz_bt_send_keyboard(report_keyboard_t *report) {}

/* Send a mouse report. */
void xyz_bt_send_mouse(report_mouse_t *report) {}

/* Send a consumer usage. */
void xyz_bt_send_consumer(uint16_t usage) {}

/* Send a system usage. (Optional) */
void xyz_bt_send_system(uint16_t usage) {}
```
4 changes: 4 additions & 0 deletions docs/hardware_drivers.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,7 @@ Support for up to a single driver with room for expansion. Each driver can contr
## 24xx series external I2C EEPROM

Support for an external I2C-based EEPROM instead of using the on-chip EEPROM. For more information on how to setup the driver see the [EEPROM Driver](eeprom_driver.md) page.

## BlueFruit LE SPI

Support for Bluetooth is provided by the inclusion of the BlueFruit LE SPI module driver. For more information on how to setup the driver see the [Bluetooth](feature_bluetooth.md) page.
94 changes: 63 additions & 31 deletions drivers/bluetooth/bluetooth.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2022
* Copyright 2024
*
* 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
Expand All @@ -15,48 +15,80 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include <stdbool.h>
#include <stddef.h>
#include "bluetooth.h"
#include "usb_util.h"

#if defined(BLUETOOTH_BLUEFRUIT_LE)
# include "bluefruit_le.h"
#elif defined(BLUETOOTH_RN42)
# include "rn42.h"
#endif
/* Each driver needs to define the struct
* const bluetooth_driver_t bluetooth_driver;
* Members `init`, `send_keyboard`, `send_mouse` and `send_consumer` must be provided.
* Members `task`, `is_connected` and `send_system` are optional and may be set to `NULL`.
* Keyboard custom drivers can define this in their own files, it should only
* be here if shared between boards on a QMK core driver.
*/

void bluetooth_init(void) {
#if defined(BLUETOOTH_BLUEFRUIT_LE)
bluefruit_le_init();
const bluetooth_driver_t bluetooth_driver = {
.init = bluefruit_le_init,
.task = bluefruit_le_task,
.is_connected = bluefruit_le_is_connected,
.send_keyboard = bluefruit_le_send_keyboard,
.send_mouse = bluefruit_le_send_mouse,
.send_consumer = bluefruit_le_send_consumer,
.send_system = NULL,
};
# ifdef ENABLE_NKRO
# error BlueFruit LE does not support NKRO, do not declare `ENABLE_NKRO`
# endif

#elif defined(BLUETOOTH_RN42)
rn42_init();
const bluetooth_driver_t bluetooth_driver = {
.init = rn42_init,
.task = NULL,
.is_connected = NULL,
.send_keyboard = rn42_send_keyboard,
.send_mouse = rn42_send_mouse,
.send_consumer = rn42_send_consumer,
.send_system = NULL,
};
# ifdef ENABLE_NKRO
# error RN42 does not support NKRO, do not declare `ENABLE_NKRO`
# endif

#endif
}

void bluetooth_task(void) {
#if defined(BLUETOOTH_BLUEFRUIT_LE)
bluefruit_le_task();
#ifndef SEND_OUTPUT_DEFAULT
# define SEND_OUTPUT_DEFAULT SEND_OUTPUT_AUTO
#endif

send_output_t desired_send_output = SEND_OUTPUT_DEFAULT;

send_output_t set_send_output(send_output_t send_output) {
desired_send_output = set_send_output_kb(send_output);
return desired_send_output;
}

void bluetooth_send_keyboard(report_keyboard_t *report) {
#if defined(BLUETOOTH_BLUEFRUIT_LE)
bluefruit_le_send_keyboard(report);
#elif defined(BLUETOOTH_RN42)
rn42_send_keyboard(report);
#endif
__attribute__((weak)) send_output_t set_send_output_kb(send_output_t send_output) {
return set_send_output_user(send_output);
}

void bluetooth_send_mouse(report_mouse_t *report) {
#if defined(BLUETOOTH_BLUEFRUIT_LE)
bluefruit_le_send_mouse(report);
#elif defined(BLUETOOTH_RN42)
rn42_send_mouse(report);
#endif
__attribute__((weak)) send_output_t set_send_output_user(send_output_t send_output) {
return send_output;
}

void bluetooth_send_consumer(uint16_t usage) {
#if defined(BLUETOOTH_BLUEFRUIT_LE)
bluefruit_le_send_consumer(usage);
#elif defined(BLUETOOTH_RN42)
rn42_send_consumer(usage);
#endif
send_output_t get_send_output(void) {
if (desired_send_output == SEND_OUTPUT_AUTO) {
// only if USB is **disconnected**
if (usb_connected_state()) {
return SEND_OUTPUT_USB;
}
if ((NULL != bluetooth_driver.is_connected) && (bluetooth_driver.is_connected())) {
return SEND_OUTPUT_BLUETOOTH;
} else {
return SEND_OUTPUT_NONE;
}
} else {
return desired_send_output;
}
}
Loading