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 #22746

Closed
wants to merge 33 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
80b4cf6
initial attempt at a bluetooth driver framework
silvinor Dec 24, 2023
bf1bb60
clang-format
silvinor Dec 24, 2023
15ed59c
[Keyboard] Add Nuxros RE65 (#22078)
MaiTheSan Dec 24, 2023
2b8799a
clang-format
silvinor Dec 24, 2023
83b8418
[Keyboard] Add Noodlepad_Micro (#22703)
jessel92 Dec 24, 2023
6c3315f
post sigprof review fixes
silvinor Dec 25, 2023
e526da1
post sigprof review fixes, r2
silvinor Dec 25, 2023
cc08f23
bm40hsrgb/rev2: disable some RGB Matrix effects to reduce filesize (#…
fauxpark Dec 27, 2023
2505d7e
vendor keymaps-mechboards via updates (#22767)
keyboard-magpie Dec 27, 2023
420b134
[Keyboard] Add meetlab kafkasplit (#22756)
LXF-YZP Dec 28, 2023
fc20e0b
Waffling60 iso rev e (#22733)
4pplet Dec 29, 2023
3a1f5cf
Edit of docs and refinement of `set_send_output` function to allow ov…
silvinor Dec 30, 2023
e008123
Merge branch 'qmk:master' into vr-bluetooth-driver
silvinor Dec 30, 2023
5328f2d
initial attempt at a bluetooth driver framework
silvinor Dec 24, 2023
99d207c
clang-format
silvinor Dec 24, 2023
62eef50
clang-format
silvinor Dec 24, 2023
8a6dcc3
post sigprof review fixes
silvinor Dec 25, 2023
d240346
post sigprof review fixes, r2
silvinor Dec 25, 2023
ed53c7f
Edit of docs and refinement of `set_send_output` function to allow ov…
silvinor Dec 30, 2023
dcc80b1
[Keyboard] Add redragon k617 port (#22410)
abhijithabhiakl Dec 23, 2023
5ec0482
[Keyboard] Add Scotto108 handwired keyboard (#22720)
joe-scotto Dec 23, 2023
8dea03f
[Keyboard] Fix Scotto61 Configurator Layout (#22718)
joe-scotto Dec 23, 2023
be773c4
[Keyboard] Add kafka68 (#22684)
LXF-YZP Dec 23, 2023
3746cf8
Add Momokai Aurora Image (#22728)
peepeetee Dec 23, 2023
7a8d099
[Keyboard] Add darmoshark k3 (#21980)
proceee Dec 24, 2023
a7d6391
[Keyboard] add scorpio pcb (#22732)
khchen2004 Dec 24, 2023
ab5972d
[Keyboard] Add Nuxros RE65 (#22078)
MaiTheSan Dec 24, 2023
c76c9f5
[Keyboard] Add Noodlepad_Micro (#22703)
jessel92 Dec 24, 2023
e46dae2
bm40hsrgb/rev2: disable some RGB Matrix effects to reduce filesize (#…
fauxpark Dec 27, 2023
5622f55
vendor keymaps-mechboards via updates (#22767)
keyboard-magpie Dec 27, 2023
dd480ef
[Keyboard] Add meetlab kafkasplit (#22756)
LXF-YZP Dec 28, 2023
79f9720
Waffling60 iso rev e (#22733)
4pplet Dec 29, 2023
91ed1f2
Merge branch 'vr-bluetooth-driver' of https://github.com/vinorodrigue…
silvinor Dec 30, 2023
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 onr 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
167 changes: 139 additions & 28 deletions docs/feature_bluetooth.md
Original file line number Diff line number Diff line change
@@ -1,46 +1,157 @@
# 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 |Bluettoth 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 no 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, using a Nordic nRF51822 chip, and is flashed with Adafruit's custom firmware that using AT Command sets.
Data is transmitted via Adafruit's [SDEP](https://learn.adafruit.com/introducing-the-adafruit-bluefruit-spi-breakout/sdep-spi-data-transport) data packets.

#### SPI Configuration

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``:

| 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).
};
```

You can change the initial state with the following define in the `config.h` file:

<!-- FIXME: Document bluetooth support more completely. -->
## Bluetooth Rules.mk Options
```c
#define SEND_OUTPUT_DEFAULT SEND_OUTPUT_AUTO
```

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`.
Vendor and keymap code can also use the keyboard-level and user-level (to either perform ancillary functions on the setting, or overrode the setting) by creating the following functions in the `kb_name.c` or the `keymap.c` files:

Add the following to your `rules.mk`:
```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 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
> *Optional* items can 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.
93 changes: 62 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,79 @@
* 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 rgb_matrix_driver_t rgb_matrix_driver;
* All members (except `task`, `is_connected` and `send_system`) must be provided.
* Keyboard custom drivers can define this in their own files, it should only
* be here if shared between boards.
*/

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
Loading