mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-06-18 20:05:06 +00:00
Bluetooth (#518)
* Bluetooth LE addition * fixes * use the psram! helps a little on S3 (t-deck) * custom device name * Update symbols.c * Feedback + fixes Fixes external app start/stop server (child devices) Fixes BtManage causing a full system hang upon disabling bt when a device is connected to the host. * updoot * more updoot * move back! * Revert "move back!" This reverts commit d3694365c634acc5db62ac59771c496cb971a727. * fix some of the things * Addressing feedback? hmm * Fixes Bug 1 — Reconnect loop / Reconnect not working fixed Bug 2 — Name-only advertising overwrites HID advertising Bug 3 — BleHidDeviceCtx leak on re-enable Enhancement — HID device auto-start on radio re-enable * stuff... * update for consistency with others * fix crashes and some bonus symbols * a few symbols, i2c speed, cdn message 100kHz i2c speed seems to be more compatible with m5stack modules...and probably in general. cdn message no longer applies * Hide BT Settings when bt not enabled * Addressing things and device fixes * Missed one! * stuff
This commit is contained in:
parent
895e6bc50d
commit
5c78d55b04
1
Data/data/service/bluetooth/settings.properties
Normal file
1
Data/data/service/bluetooth/settings.properties
Normal file
@ -0,0 +1 @@
|
||||
enableOnBoot=false
|
||||
@ -29,3 +29,4 @@ CONFIG_ESP_HOSTED_ENABLED=y
|
||||
CONFIG_ESP_HOSTED_P4_DEV_BOARD_FUNC_BOARD=y
|
||||
CONFIG_ESP_HOSTED_SDIO_HOST_INTERFACE=y
|
||||
CONFIG_SLAVE_IDF_TARGET_ESP32C6=y
|
||||
CONFIG_ESP_HOSTED_USE_MEMPOOL=n
|
||||
|
||||
@ -13,6 +13,7 @@ spiRamMode=OCT
|
||||
spiRamSpeed=120M
|
||||
tinyUsb=true
|
||||
esptoolFlashFreq=120M
|
||||
bluetooth=true
|
||||
|
||||
[display]
|
||||
size=2.8"
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
#include <tactility/bindings/esp32_gpio.h>
|
||||
#include <tactility/bindings/esp32_i2c.h>
|
||||
#include <tactility/bindings/esp32_i2s.h>
|
||||
#include <tactility/bindings/esp32_ble.h>
|
||||
#include <tactility/bindings/esp32_spi.h>
|
||||
#include <tactility/bindings/esp32_uart.h>
|
||||
|
||||
@ -12,6 +13,10 @@
|
||||
compatible = "root";
|
||||
model = "LilyGO T-Deck";
|
||||
|
||||
ble0 {
|
||||
compatible = "espressif,esp32-ble";
|
||||
};
|
||||
|
||||
gpio0 {
|
||||
compatible = "espressif,esp32-gpio";
|
||||
gpio-count = <49>;
|
||||
|
||||
@ -27,7 +27,7 @@
|
||||
|
||||
bmi270 {
|
||||
compatible = "bosch,bmi270";
|
||||
reg = <0x68>;
|
||||
reg = <0x69>;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -28,7 +28,7 @@
|
||||
|
||||
bmi270 {
|
||||
compatible = "bosch,bmi270";
|
||||
reg = <0x68>;
|
||||
reg = <0x69>;
|
||||
};
|
||||
|
||||
bm8563 {
|
||||
|
||||
@ -9,9 +9,10 @@ launcherAppId=Launcher
|
||||
target=ESP32P4
|
||||
flashSize=16MB
|
||||
spiRam=true
|
||||
spiRamMode=OCT
|
||||
spiRamMode=HEX
|
||||
spiRamSpeed=200M
|
||||
esptoolFlashFreq=80M
|
||||
bluetooth=true
|
||||
|
||||
[display]
|
||||
size=5"
|
||||
@ -23,9 +24,6 @@ colorDepth=16
|
||||
fontSize=28
|
||||
dpi=250
|
||||
|
||||
[cdn]
|
||||
warningMessage=Only the original hardware variant with the ILI9881C display is supported for now
|
||||
|
||||
[sdkconfig]
|
||||
CONFIG_WIFI_PROV_SCAN_MAX_ENTRIES=16
|
||||
CONFIG_WIFI_PROV_AUTOSTOP_TIMEOUT=30
|
||||
@ -41,3 +39,5 @@ CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D1_4BIT_BUS_SLOT_1=10
|
||||
CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D2_4BIT_BUS_SLOT_1=9
|
||||
CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D3_4BIT_BUS_SLOT_1=8
|
||||
CONFIG_ESP_HOSTED_SDIO_GPIO_RESET_SLAVE=15
|
||||
# Fixes recent changes to esp_hosted
|
||||
CONFIG_ESP_HOSTED_USE_MEMPOOL=n
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
#include <tactility/bindings/esp32_i2c.h>
|
||||
#include <tactility/bindings/esp32_i2s.h>
|
||||
#include <tactility/bindings/esp32_spi.h>
|
||||
#include <tactility/bindings/esp32_ble.h>
|
||||
#include <bindings/bmi270.h>
|
||||
#include <bindings/pi4ioe5v6408.h>
|
||||
#include <bindings/rx8130ce.h>
|
||||
@ -13,6 +14,10 @@
|
||||
compatible = "root";
|
||||
model = "Tab5";
|
||||
|
||||
ble0 {
|
||||
compatible = "espressif,esp32-ble";
|
||||
};
|
||||
|
||||
gpio0 {
|
||||
compatible = "espressif,esp32-gpio";
|
||||
gpio-count = <57>;
|
||||
@ -49,7 +54,7 @@
|
||||
i2c_port_a: i2c1 {
|
||||
compatible = "espressif,esp32-i2c";
|
||||
port = <I2C_NUM_1>;
|
||||
clock-frequency = <400000>;
|
||||
clock-frequency = <100000>;
|
||||
pin-sda = <&gpio0 53 GPIO_FLAG_NONE>;
|
||||
pin-scl = <&gpio0 54 GPIO_FLAG_NONE>;
|
||||
};
|
||||
|
||||
@ -18,6 +18,15 @@ endif ()
|
||||
|
||||
set(DEVICETREE_LOCATION "${PROJECT_ROOT}/Devices/${TACTILITY_DEVICE_ID}")
|
||||
|
||||
# Check if device has Bluetooth enabled
|
||||
# Fixes the sdkconfig bluetooth enable options from getting nuked on non-P4+C6 builds when idf build runs
|
||||
if (DEFINED ENV{ESP_IDF_VERSION})
|
||||
file(READ "${DEVICETREE_LOCATION}/device.properties" device_properties_content)
|
||||
if (device_properties_content MATCHES "bluetooth=true")
|
||||
list(APPEND REQUIRES_LIST bt)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
#
|
||||
# DTS compiler python dependencies
|
||||
#
|
||||
|
||||
@ -109,6 +109,7 @@ shared_symbol_code_point_names = [
|
||||
"keyboard_alt", # Keyboard (settings) app
|
||||
"usb", # Power (settings) app
|
||||
"wifi", # WiFi (settings) app
|
||||
"bluetooth", # Bluetooth (settings) app
|
||||
]
|
||||
|
||||
# Get more from https://fonts.google.com/icons?icon.set=Material+Symbols&icon.style=Rounded
|
||||
@ -138,7 +139,12 @@ statusbar_symbol_code_point_names = [
|
||||
"battery_android_frame_5",
|
||||
"battery_android_frame_6",
|
||||
"battery_android_frame_full",
|
||||
"battery_android_frame_bolt"
|
||||
"battery_android_frame_bolt",
|
||||
# Bluetooth
|
||||
"bluetooth",
|
||||
"bluetooth_searching",
|
||||
"bluetooth_connected",
|
||||
"bluetooth_disabled",
|
||||
]
|
||||
|
||||
# Get more from https://fonts.google.com/icons?icon.set=Material+Symbols&icon.style=Rounded
|
||||
|
||||
@ -42,3 +42,4 @@
|
||||
#define LVGL_ICON_SHARED_KEYBOARD_ALT "\xEF\x80\xA8"
|
||||
#define LVGL_ICON_SHARED_USB "\xEE\x87\xA0"
|
||||
#define LVGL_ICON_SHARED_WIFI "\xEE\x98\xBE"
|
||||
#define LVGL_ICON_SHARED_BLUETOOTH "\xEE\x86\xA7"
|
||||
|
||||
@ -20,3 +20,7 @@
|
||||
#define LVGL_ICON_STATUSBAR_BATTERY_ANDROID_FRAME_6 "\xEF\x89\x92"
|
||||
#define LVGL_ICON_STATUSBAR_BATTERY_ANDROID_FRAME_FULL "\xEF\x89\x8F"
|
||||
#define LVGL_ICON_STATUSBAR_BATTERY_ANDROID_FRAME_BOLT "\xEF\x89\x90"
|
||||
#define LVGL_ICON_STATUSBAR_BLUETOOTH "\xEE\x86\xA7"
|
||||
#define LVGL_ICON_STATUSBAR_BLUETOOTH_SEARCHING "\xEE\x98\x8F"
|
||||
#define LVGL_ICON_STATUSBAR_BLUETOOTH_CONNECTED "\xEE\x86\xA8"
|
||||
#define LVGL_ICON_STATUSBAR_BLUETOOTH_DISABLED "\xEE\x86\xA9"
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*******************************************************************************
|
||||
* Size: 12 px
|
||||
* Bpp: 2
|
||||
* Opts: --no-compress --no-prefilter --bpp 2 --size 12 --font MaterialSymbolsRounded.ttf -r 0xE145,0xE5C3,0xE770,0xEF40,0xEBCC,0xEFE6,0xEF4A,0xE5CD,0xF15C,0xE5CA,0xE92E,0xE326,0xEB97,0xE745,0xEC1C,0xE2C7,0xF720,0xF090,0xE8AF,0xE30F,0xE8FD,0xE9F4,0xE3F4,0xE316,0xE90F,0xE894,0xE9B9,0xE159,0xE5D2,0xE28D,0xE5D4,0xE405,0xE89C,0xF8C7,0xE5D5,0xE8B6,0xE8B8,0xE9F7,0xE55D,0xF028,0xE1E0,0xE63E --format lvgl -o ../source-fonts/material_symbols_shared_12.c --force-fast-kern-format
|
||||
* Opts: --no-compress --no-prefilter --bpp 2 --size 12 --font MaterialSymbolsRounded.ttf -r 0xE145,0xE5C3,0xE770,0xEF40,0xEBCC,0xEFE6,0xEF4A,0xE5CD,0xF15C,0xE5CA,0xE92E,0xE326,0xEB97,0xE745,0xEC1C,0xE2C7,0xF720,0xF090,0xE8AF,0xE30F,0xE8FD,0xE9F4,0xE3F4,0xE316,0xE90F,0xE894,0xE9B9,0xE159,0xE5D2,0xE28D,0xE5D4,0xE405,0xE89C,0xF8C7,0xE5D5,0xE8B6,0xE8B8,0xE9F7,0xE55D,0xF028,0xE1E0,0xE63E,0xE1A7 --format lvgl -o ../source-fonts/material_symbols_shared_12.c --force-fast-kern-format
|
||||
******************************************************************************/
|
||||
|
||||
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
|
||||
@ -31,6 +31,11 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
|
||||
0x2f, 0x83, 0xc0, 0x0, 0x3c, 0x0, 0x3, 0xc0,
|
||||
0x0, 0x3f, 0xff, 0xff,
|
||||
|
||||
/* U+E1A7 "" */
|
||||
0x1, 0x40, 0xb, 0x47, 0x2b, 0x7, 0xec, 0x7,
|
||||
0xc0, 0x1f, 0x1, 0xfb, 0x1c, 0x9d, 0x2, 0xd0,
|
||||
0x9, 0x0,
|
||||
|
||||
/* U+E1E0 "" */
|
||||
0x2, 0x0, 0x7, 0x80, 0x3, 0x0, 0x73, 0x3c,
|
||||
0xb3, 0x3c, 0x33, 0x18, 0x3f, 0xf4, 0x3, 0x0,
|
||||
@ -248,46 +253,47 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
|
||||
{.bitmap_index = 0, .adv_w = 0, .box_w = 0, .box_h = 0, .ofs_x = 0, .ofs_y = 0} /* id = 0 reserved */,
|
||||
{.bitmap_index = 0, .adv_w = 192, .box_w = 8, .box_h = 8, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 16, .adv_w = 192, .box_w = 10, .box_h = 8, .ofs_x = 1, .ofs_y = 2},
|
||||
{.bitmap_index = 36, .adv_w = 192, .box_w = 8, .box_h = 10, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 56, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = 0},
|
||||
{.bitmap_index = 89, .adv_w = 192, .box_w = 10, .box_h = 8, .ofs_x = 1, .ofs_y = 2},
|
||||
{.bitmap_index = 109, .adv_w = 192, .box_w = 10, .box_h = 10, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 134, .adv_w = 192, .box_w = 6, .box_h = 4, .ofs_x = 3, .ofs_y = 4},
|
||||
{.bitmap_index = 140, .adv_w = 192, .box_w = 10, .box_h = 8, .ofs_x = 1, .ofs_y = 2},
|
||||
{.bitmap_index = 160, .adv_w = 192, .box_w = 10, .box_h = 10, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 185, .adv_w = 192, .box_w = 6, .box_h = 10, .ofs_x = 3, .ofs_y = 1},
|
||||
{.bitmap_index = 200, .adv_w = 192, .box_w = 8, .box_h = 10, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 220, .adv_w = 192, .box_w = 8, .box_h = 8, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 236, .adv_w = 192, .box_w = 8, .box_h = 6, .ofs_x = 2, .ofs_y = 3},
|
||||
{.bitmap_index = 248, .adv_w = 192, .box_w = 8, .box_h = 8, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 264, .adv_w = 192, .box_w = 10, .box_h = 6, .ofs_x = 1, .ofs_y = 3},
|
||||
{.bitmap_index = 279, .adv_w = 192, .box_w = 2, .box_h = 8, .ofs_x = 5, .ofs_y = 2},
|
||||
{.bitmap_index = 283, .adv_w = 192, .box_w = 8, .box_h = 8, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 299, .adv_w = 192, .box_w = 12, .box_h = 9, .ofs_x = 0, .ofs_y = 1},
|
||||
{.bitmap_index = 326, .adv_w = 192, .box_w = 9, .box_h = 8, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 344, .adv_w = 192, .box_w = 10, .box_h = 9, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 367, .adv_w = 192, .box_w = 10, .box_h = 10, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 392, .adv_w = 192, .box_w = 8, .box_h = 10, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 412, .adv_w = 192, .box_w = 10, .box_h = 10, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 437, .adv_w = 192, .box_w = 10, .box_h = 10, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 462, .adv_w = 192, .box_w = 10, .box_h = 10, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 487, .adv_w = 192, .box_w = 10, .box_h = 10, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 512, .adv_w = 192, .box_w = 8, .box_h = 10, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 532, .adv_w = 192, .box_w = 8, .box_h = 10, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 552, .adv_w = 192, .box_w = 10, .box_h = 8, .ofs_x = 1, .ofs_y = 2},
|
||||
{.bitmap_index = 572, .adv_w = 192, .box_w = 12, .box_h = 12, .ofs_x = 0, .ofs_y = 0},
|
||||
{.bitmap_index = 608, .adv_w = 192, .box_w = 10, .box_h = 10, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 633, .adv_w = 192, .box_w = 10, .box_h = 10, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 658, .adv_w = 192, .box_w = 10, .box_h = 10, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 683, .adv_w = 192, .box_w = 8, .box_h = 10, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 703, .adv_w = 192, .box_w = 9, .box_h = 8, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 721, .adv_w = 192, .box_w = 10, .box_h = 10, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 746, .adv_w = 192, .box_w = 10, .box_h = 10, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 771, .adv_w = 192, .box_w = 12, .box_h = 8, .ofs_x = 0, .ofs_y = 2},
|
||||
{.bitmap_index = 795, .adv_w = 192, .box_w = 8, .box_h = 8, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 811, .adv_w = 192, .box_w = 12, .box_h = 8, .ofs_x = 0, .ofs_y = 2},
|
||||
{.bitmap_index = 835, .adv_w = 192, .box_w = 9, .box_h = 10, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 858, .adv_w = 192, .box_w = 10, .box_h = 10, .ofs_x = 1, .ofs_y = 1}
|
||||
{.bitmap_index = 36, .adv_w = 192, .box_w = 7, .box_h = 10, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 54, .adv_w = 192, .box_w = 8, .box_h = 10, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 74, .adv_w = 192, .box_w = 11, .box_h = 12, .ofs_x = 0, .ofs_y = 0},
|
||||
{.bitmap_index = 107, .adv_w = 192, .box_w = 10, .box_h = 8, .ofs_x = 1, .ofs_y = 2},
|
||||
{.bitmap_index = 127, .adv_w = 192, .box_w = 10, .box_h = 10, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 152, .adv_w = 192, .box_w = 6, .box_h = 4, .ofs_x = 3, .ofs_y = 4},
|
||||
{.bitmap_index = 158, .adv_w = 192, .box_w = 10, .box_h = 8, .ofs_x = 1, .ofs_y = 2},
|
||||
{.bitmap_index = 178, .adv_w = 192, .box_w = 10, .box_h = 10, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 203, .adv_w = 192, .box_w = 6, .box_h = 10, .ofs_x = 3, .ofs_y = 1},
|
||||
{.bitmap_index = 218, .adv_w = 192, .box_w = 8, .box_h = 10, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 238, .adv_w = 192, .box_w = 8, .box_h = 8, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 254, .adv_w = 192, .box_w = 8, .box_h = 6, .ofs_x = 2, .ofs_y = 3},
|
||||
{.bitmap_index = 266, .adv_w = 192, .box_w = 8, .box_h = 8, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 282, .adv_w = 192, .box_w = 10, .box_h = 6, .ofs_x = 1, .ofs_y = 3},
|
||||
{.bitmap_index = 297, .adv_w = 192, .box_w = 2, .box_h = 8, .ofs_x = 5, .ofs_y = 2},
|
||||
{.bitmap_index = 301, .adv_w = 192, .box_w = 8, .box_h = 8, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 317, .adv_w = 192, .box_w = 12, .box_h = 9, .ofs_x = 0, .ofs_y = 1},
|
||||
{.bitmap_index = 344, .adv_w = 192, .box_w = 9, .box_h = 8, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 362, .adv_w = 192, .box_w = 10, .box_h = 9, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 385, .adv_w = 192, .box_w = 10, .box_h = 10, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 410, .adv_w = 192, .box_w = 8, .box_h = 10, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 430, .adv_w = 192, .box_w = 10, .box_h = 10, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 455, .adv_w = 192, .box_w = 10, .box_h = 10, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 480, .adv_w = 192, .box_w = 10, .box_h = 10, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 505, .adv_w = 192, .box_w = 10, .box_h = 10, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 530, .adv_w = 192, .box_w = 8, .box_h = 10, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 550, .adv_w = 192, .box_w = 8, .box_h = 10, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 570, .adv_w = 192, .box_w = 10, .box_h = 8, .ofs_x = 1, .ofs_y = 2},
|
||||
{.bitmap_index = 590, .adv_w = 192, .box_w = 12, .box_h = 12, .ofs_x = 0, .ofs_y = 0},
|
||||
{.bitmap_index = 626, .adv_w = 192, .box_w = 10, .box_h = 10, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 651, .adv_w = 192, .box_w = 10, .box_h = 10, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 676, .adv_w = 192, .box_w = 10, .box_h = 10, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 701, .adv_w = 192, .box_w = 8, .box_h = 10, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 721, .adv_w = 192, .box_w = 9, .box_h = 8, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 739, .adv_w = 192, .box_w = 10, .box_h = 10, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 764, .adv_w = 192, .box_w = 10, .box_h = 10, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 789, .adv_w = 192, .box_w = 12, .box_h = 8, .ofs_x = 0, .ofs_y = 2},
|
||||
{.bitmap_index = 813, .adv_w = 192, .box_w = 8, .box_h = 8, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 829, .adv_w = 192, .box_w = 12, .box_h = 8, .ofs_x = 0, .ofs_y = 2},
|
||||
{.bitmap_index = 853, .adv_w = 192, .box_w = 9, .box_h = 10, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 876, .adv_w = 192, .box_w = 10, .box_h = 10, .ofs_x = 1, .ofs_y = 1}
|
||||
};
|
||||
|
||||
/*---------------------
|
||||
@ -295,12 +301,12 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
|
||||
*--------------------*/
|
||||
|
||||
static const uint16_t unicode_list_0[] = {
|
||||
0x0, 0x14, 0x9b, 0x148, 0x182, 0x1ca, 0x1d1, 0x1e1,
|
||||
0x2af, 0x2c0, 0x418, 0x47e, 0x485, 0x488, 0x48d, 0x48f,
|
||||
0x490, 0x4f9, 0x600, 0x62b, 0x74f, 0x757, 0x76a, 0x771,
|
||||
0x773, 0x7b8, 0x7ca, 0x7e9, 0x874, 0x8af, 0x8b2, 0xa52,
|
||||
0xa87, 0xad7, 0xdfb, 0xe05, 0xea1, 0xee3, 0xf4b, 0x1017,
|
||||
0x15db, 0x1782
|
||||
0x0, 0x14, 0x62, 0x9b, 0x148, 0x182, 0x1ca, 0x1d1,
|
||||
0x1e1, 0x2af, 0x2c0, 0x418, 0x47e, 0x485, 0x488, 0x48d,
|
||||
0x48f, 0x490, 0x4f9, 0x600, 0x62b, 0x74f, 0x757, 0x76a,
|
||||
0x771, 0x773, 0x7b8, 0x7ca, 0x7e9, 0x874, 0x8af, 0x8b2,
|
||||
0xa52, 0xa87, 0xad7, 0xdfb, 0xe05, 0xea1, 0xee3, 0xf4b,
|
||||
0x1017, 0x15db, 0x1782
|
||||
};
|
||||
|
||||
/*Collect the unicode lists and glyph_id offsets*/
|
||||
@ -308,7 +314,7 @@ static const lv_font_fmt_txt_cmap_t cmaps[] =
|
||||
{
|
||||
{
|
||||
.range_start = 57669, .range_length = 6019, .glyph_id_start = 1,
|
||||
.unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 42, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
|
||||
.unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 43, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*******************************************************************************
|
||||
* Size: 16 px
|
||||
* Bpp: 2
|
||||
* Opts: --no-compress --no-prefilter --bpp 2 --size 16 --font MaterialSymbolsRounded.ttf -r 0xE145,0xE5C3,0xE770,0xEF40,0xEBCC,0xEFE6,0xEF4A,0xE5CD,0xF15C,0xE5CA,0xE92E,0xE326,0xEB97,0xE745,0xEC1C,0xE2C7,0xF720,0xF090,0xE8AF,0xE30F,0xE8FD,0xE9F4,0xE3F4,0xE316,0xE90F,0xE894,0xE9B9,0xE159,0xE5D2,0xE28D,0xE5D4,0xE405,0xE89C,0xF8C7,0xE5D5,0xE8B6,0xE8B8,0xE9F7,0xE55D,0xF028,0xE1E0,0xE63E --format lvgl -o ../source-fonts/material_symbols_shared_16.c --force-fast-kern-format
|
||||
* Opts: --no-compress --no-prefilter --bpp 2 --size 16 --font MaterialSymbolsRounded.ttf -r 0xE145,0xE5C3,0xE770,0xEF40,0xEBCC,0xEFE6,0xEF4A,0xE5CD,0xF15C,0xE5CA,0xE92E,0xE326,0xEB97,0xE745,0xEC1C,0xE2C7,0xF720,0xF090,0xE8AF,0xE30F,0xE8FD,0xE9F4,0xE3F4,0xE316,0xE90F,0xE894,0xE9B9,0xE159,0xE5D2,0xE28D,0xE5D4,0xE405,0xE89C,0xF8C7,0xE5D5,0xE8B6,0xE8B8,0xE9F7,0xE55D,0xF028,0xE1E0,0xE63E,0xE1A7 --format lvgl -o ../source-fonts/material_symbols_shared_16.c --force-fast-kern-format
|
||||
******************************************************************************/
|
||||
|
||||
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
|
||||
@ -36,6 +36,12 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
|
||||
0x0, 0x0, 0xd, 0xbf, 0xff, 0xff, 0xd1, 0x55,
|
||||
0x55, 0x50,
|
||||
|
||||
/* U+E1A7 "" */
|
||||
0x0, 0x40, 0x0, 0x3c, 0x1, 0xf, 0xc1, 0xd3,
|
||||
0x6c, 0x1d, 0xef, 0x1, 0xff, 0x0, 0x1f, 0x0,
|
||||
0xf, 0xd0, 0xf, 0xfd, 0xf, 0x36, 0xcb, 0xe,
|
||||
0xe0, 0x3, 0xe0, 0x0, 0xe0, 0x0, 0x0, 0x0,
|
||||
|
||||
/* U+E1E0 "" */
|
||||
0x0, 0x0, 0x0, 0xe, 0x0, 0x2, 0xf0, 0x0,
|
||||
0xe, 0x0, 0x10, 0xd1, 0x5b, 0xcd, 0x3e, 0x78,
|
||||
@ -343,46 +349,47 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
|
||||
{.bitmap_index = 0, .adv_w = 0, .box_w = 0, .box_h = 0, .ofs_x = 0, .ofs_y = 0} /* id = 0 reserved */,
|
||||
{.bitmap_index = 0, .adv_w = 256, .box_w = 10, .box_h = 10, .ofs_x = 3, .ofs_y = 3},
|
||||
{.bitmap_index = 25, .adv_w = 256, .box_w = 14, .box_h = 12, .ofs_x = 1, .ofs_y = 2},
|
||||
{.bitmap_index = 67, .adv_w = 256, .box_w = 10, .box_h = 14, .ofs_x = 3, .ofs_y = 1},
|
||||
{.bitmap_index = 102, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = 0},
|
||||
{.bitmap_index = 166, .adv_w = 256, .box_w = 14, .box_h = 12, .ofs_x = 1, .ofs_y = 2},
|
||||
{.bitmap_index = 208, .adv_w = 256, .box_w = 14, .box_h = 14, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 257, .adv_w = 256, .box_w = 8, .box_h = 5, .ofs_x = 4, .ofs_y = 6},
|
||||
{.bitmap_index = 267, .adv_w = 256, .box_w = 14, .box_h = 12, .ofs_x = 1, .ofs_y = 2},
|
||||
{.bitmap_index = 309, .adv_w = 256, .box_w = 12, .box_h = 12, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 345, .adv_w = 256, .box_w = 8, .box_h = 12, .ofs_x = 4, .ofs_y = 2},
|
||||
{.bitmap_index = 369, .adv_w = 256, .box_w = 12, .box_h = 12, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 405, .adv_w = 256, .box_w = 12, .box_h = 12, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 441, .adv_w = 256, .box_w = 12, .box_h = 8, .ofs_x = 2, .ofs_y = 4},
|
||||
{.bitmap_index = 465, .adv_w = 256, .box_w = 10, .box_h = 10, .ofs_x = 3, .ofs_y = 3},
|
||||
{.bitmap_index = 490, .adv_w = 256, .box_w = 12, .box_h = 8, .ofs_x = 2, .ofs_y = 4},
|
||||
{.bitmap_index = 514, .adv_w = 256, .box_w = 4, .box_h = 12, .ofs_x = 6, .ofs_y = 2},
|
||||
{.bitmap_index = 526, .adv_w = 256, .box_w = 12, .box_h = 12, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 562, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 2},
|
||||
{.bitmap_index = 610, .adv_w = 256, .box_w = 13, .box_h = 12, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 649, .adv_w = 256, .box_w = 12, .box_h = 12, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 685, .adv_w = 256, .box_w = 14, .box_h = 14, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 734, .adv_w = 256, .box_w = 12, .box_h = 14, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 776, .adv_w = 256, .box_w = 14, .box_h = 13, .ofs_x = 1, .ofs_y = 2},
|
||||
{.bitmap_index = 822, .adv_w = 256, .box_w = 12, .box_h = 12, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 858, .adv_w = 256, .box_w = 14, .box_h = 14, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 907, .adv_w = 256, .box_w = 14, .box_h = 14, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 956, .adv_w = 256, .box_w = 10, .box_h = 14, .ofs_x = 3, .ofs_y = 1},
|
||||
{.bitmap_index = 991, .adv_w = 256, .box_w = 12, .box_h = 12, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 1027, .adv_w = 256, .box_w = 14, .box_h = 12, .ofs_x = 1, .ofs_y = 2},
|
||||
{.bitmap_index = 1069, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = 0},
|
||||
{.bitmap_index = 1133, .adv_w = 256, .box_w = 12, .box_h = 12, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 1169, .adv_w = 256, .box_w = 14, .box_h = 13, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 1215, .adv_w = 256, .box_w = 12, .box_h = 14, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 1257, .adv_w = 256, .box_w = 12, .box_h = 14, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 1299, .adv_w = 256, .box_w = 13, .box_h = 12, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 1338, .adv_w = 256, .box_w = 14, .box_h = 14, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 1387, .adv_w = 256, .box_w = 12, .box_h = 12, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 1423, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 2},
|
||||
{.bitmap_index = 1471, .adv_w = 256, .box_w = 12, .box_h = 12, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 1507, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 2},
|
||||
{.bitmap_index = 1555, .adv_w = 256, .box_w = 12, .box_h = 14, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 1597, .adv_w = 256, .box_w = 14, .box_h = 14, .ofs_x = 1, .ofs_y = 1}
|
||||
{.bitmap_index = 67, .adv_w = 256, .box_w = 9, .box_h = 14, .ofs_x = 3, .ofs_y = 1},
|
||||
{.bitmap_index = 99, .adv_w = 256, .box_w = 10, .box_h = 14, .ofs_x = 3, .ofs_y = 1},
|
||||
{.bitmap_index = 134, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = 0},
|
||||
{.bitmap_index = 198, .adv_w = 256, .box_w = 14, .box_h = 12, .ofs_x = 1, .ofs_y = 2},
|
||||
{.bitmap_index = 240, .adv_w = 256, .box_w = 14, .box_h = 14, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 289, .adv_w = 256, .box_w = 8, .box_h = 5, .ofs_x = 4, .ofs_y = 6},
|
||||
{.bitmap_index = 299, .adv_w = 256, .box_w = 14, .box_h = 12, .ofs_x = 1, .ofs_y = 2},
|
||||
{.bitmap_index = 341, .adv_w = 256, .box_w = 12, .box_h = 12, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 377, .adv_w = 256, .box_w = 8, .box_h = 12, .ofs_x = 4, .ofs_y = 2},
|
||||
{.bitmap_index = 401, .adv_w = 256, .box_w = 12, .box_h = 12, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 437, .adv_w = 256, .box_w = 12, .box_h = 12, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 473, .adv_w = 256, .box_w = 12, .box_h = 8, .ofs_x = 2, .ofs_y = 4},
|
||||
{.bitmap_index = 497, .adv_w = 256, .box_w = 10, .box_h = 10, .ofs_x = 3, .ofs_y = 3},
|
||||
{.bitmap_index = 522, .adv_w = 256, .box_w = 12, .box_h = 8, .ofs_x = 2, .ofs_y = 4},
|
||||
{.bitmap_index = 546, .adv_w = 256, .box_w = 4, .box_h = 12, .ofs_x = 6, .ofs_y = 2},
|
||||
{.bitmap_index = 558, .adv_w = 256, .box_w = 12, .box_h = 12, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 594, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 2},
|
||||
{.bitmap_index = 642, .adv_w = 256, .box_w = 13, .box_h = 12, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 681, .adv_w = 256, .box_w = 12, .box_h = 12, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 717, .adv_w = 256, .box_w = 14, .box_h = 14, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 766, .adv_w = 256, .box_w = 12, .box_h = 14, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 808, .adv_w = 256, .box_w = 14, .box_h = 13, .ofs_x = 1, .ofs_y = 2},
|
||||
{.bitmap_index = 854, .adv_w = 256, .box_w = 12, .box_h = 12, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 890, .adv_w = 256, .box_w = 14, .box_h = 14, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 939, .adv_w = 256, .box_w = 14, .box_h = 14, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 988, .adv_w = 256, .box_w = 10, .box_h = 14, .ofs_x = 3, .ofs_y = 1},
|
||||
{.bitmap_index = 1023, .adv_w = 256, .box_w = 12, .box_h = 12, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 1059, .adv_w = 256, .box_w = 14, .box_h = 12, .ofs_x = 1, .ofs_y = 2},
|
||||
{.bitmap_index = 1101, .adv_w = 256, .box_w = 16, .box_h = 16, .ofs_x = 0, .ofs_y = 0},
|
||||
{.bitmap_index = 1165, .adv_w = 256, .box_w = 12, .box_h = 12, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 1201, .adv_w = 256, .box_w = 14, .box_h = 13, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 1247, .adv_w = 256, .box_w = 12, .box_h = 14, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 1289, .adv_w = 256, .box_w = 12, .box_h = 14, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 1331, .adv_w = 256, .box_w = 13, .box_h = 12, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 1370, .adv_w = 256, .box_w = 14, .box_h = 14, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 1419, .adv_w = 256, .box_w = 12, .box_h = 12, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 1455, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 2},
|
||||
{.bitmap_index = 1503, .adv_w = 256, .box_w = 12, .box_h = 12, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 1539, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 2},
|
||||
{.bitmap_index = 1587, .adv_w = 256, .box_w = 12, .box_h = 14, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 1629, .adv_w = 256, .box_w = 14, .box_h = 14, .ofs_x = 1, .ofs_y = 1}
|
||||
};
|
||||
|
||||
/*---------------------
|
||||
@ -390,12 +397,12 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
|
||||
*--------------------*/
|
||||
|
||||
static const uint16_t unicode_list_0[] = {
|
||||
0x0, 0x14, 0x9b, 0x148, 0x182, 0x1ca, 0x1d1, 0x1e1,
|
||||
0x2af, 0x2c0, 0x418, 0x47e, 0x485, 0x488, 0x48d, 0x48f,
|
||||
0x490, 0x4f9, 0x600, 0x62b, 0x74f, 0x757, 0x76a, 0x771,
|
||||
0x773, 0x7b8, 0x7ca, 0x7e9, 0x874, 0x8af, 0x8b2, 0xa52,
|
||||
0xa87, 0xad7, 0xdfb, 0xe05, 0xea1, 0xee3, 0xf4b, 0x1017,
|
||||
0x15db, 0x1782
|
||||
0x0, 0x14, 0x62, 0x9b, 0x148, 0x182, 0x1ca, 0x1d1,
|
||||
0x1e1, 0x2af, 0x2c0, 0x418, 0x47e, 0x485, 0x488, 0x48d,
|
||||
0x48f, 0x490, 0x4f9, 0x600, 0x62b, 0x74f, 0x757, 0x76a,
|
||||
0x771, 0x773, 0x7b8, 0x7ca, 0x7e9, 0x874, 0x8af, 0x8b2,
|
||||
0xa52, 0xa87, 0xad7, 0xdfb, 0xe05, 0xea1, 0xee3, 0xf4b,
|
||||
0x1017, 0x15db, 0x1782
|
||||
};
|
||||
|
||||
/*Collect the unicode lists and glyph_id offsets*/
|
||||
@ -403,7 +410,7 @@ static const lv_font_fmt_txt_cmap_t cmaps[] =
|
||||
{
|
||||
{
|
||||
.range_start = 57669, .range_length = 6019, .glyph_id_start = 1,
|
||||
.unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 42, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
|
||||
.unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 43, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*******************************************************************************
|
||||
* Size: 20 px
|
||||
* Bpp: 2
|
||||
* Opts: --no-compress --no-prefilter --bpp 2 --size 20 --font MaterialSymbolsRounded.ttf -r 0xE145,0xE5C3,0xE770,0xEF40,0xEBCC,0xEFE6,0xEF4A,0xE5CD,0xF15C,0xE5CA,0xE92E,0xE326,0xEB97,0xE745,0xEC1C,0xE2C7,0xF720,0xF090,0xE8AF,0xE30F,0xE8FD,0xE9F4,0xE3F4,0xE316,0xE90F,0xE894,0xE9B9,0xE159,0xE5D2,0xE28D,0xE5D4,0xE405,0xE89C,0xF8C7,0xE5D5,0xE8B6,0xE8B8,0xE9F7,0xE55D,0xF028,0xE1E0,0xE63E --format lvgl -o ../source-fonts/material_symbols_shared_20.c --force-fast-kern-format
|
||||
* Opts: --no-compress --no-prefilter --bpp 2 --size 20 --font MaterialSymbolsRounded.ttf -r 0xE145,0xE5C3,0xE770,0xEF40,0xEBCC,0xEFE6,0xEF4A,0xE5CD,0xF15C,0xE5CA,0xE92E,0xE326,0xEB97,0xE745,0xEC1C,0xE2C7,0xF720,0xF090,0xE8AF,0xE30F,0xE8FD,0xE9F4,0xE3F4,0xE316,0xE90F,0xE894,0xE9B9,0xE159,0xE5D2,0xE28D,0xE5D4,0xE405,0xE89C,0xF8C7,0xE5D5,0xE8B6,0xE8B8,0xE9F7,0xE55D,0xF028,0xE1E0,0xE63E,0xE1A7 --format lvgl -o ../source-fonts/material_symbols_shared_20.c --force-fast-kern-format
|
||||
******************************************************************************/
|
||||
|
||||
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
|
||||
@ -39,6 +39,14 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
|
||||
0x0, 0xe3, 0x80, 0x0, 0x0, 0xe, 0x3f, 0xff,
|
||||
0xff, 0xff, 0xd1, 0xaa, 0xaa, 0xaa, 0xa8,
|
||||
|
||||
/* U+E1A7 "" */
|
||||
0x0, 0x28, 0x0, 0x0, 0xf8, 0x1, 0x3, 0xf8,
|
||||
0x1e, 0xe, 0xb8, 0x2e, 0x38, 0xf0, 0x2e, 0xfb,
|
||||
0x80, 0x2f, 0xf8, 0x0, 0x2f, 0x80, 0x0, 0xbe,
|
||||
0x0, 0xb, 0xfe, 0x0, 0xbb, 0xee, 0xb, 0x8e,
|
||||
0x3c, 0x78, 0x3a, 0xe0, 0x40, 0xfe, 0x0, 0x3,
|
||||
0xe0, 0x0, 0xa, 0x0,
|
||||
|
||||
/* U+E1E0 "" */
|
||||
0x0, 0x0, 0x0, 0x0, 0x28, 0x0, 0x0, 0x7d,
|
||||
0x0, 0x0, 0xff, 0x0, 0x0, 0x38, 0x0, 0x10,
|
||||
@ -451,46 +459,47 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
|
||||
{.bitmap_index = 0, .adv_w = 0, .box_w = 0, .box_h = 0, .ofs_x = 0, .ofs_y = 0} /* id = 0 reserved */,
|
||||
{.bitmap_index = 0, .adv_w = 320, .box_w = 12, .box_h = 12, .ofs_x = 4, .ofs_y = 4},
|
||||
{.bitmap_index = 36, .adv_w = 320, .box_w = 18, .box_h = 14, .ofs_x = 1, .ofs_y = 3},
|
||||
{.bitmap_index = 99, .adv_w = 320, .box_w = 12, .box_h = 18, .ofs_x = 4, .ofs_y = 1},
|
||||
{.bitmap_index = 153, .adv_w = 320, .box_w = 19, .box_h = 20, .ofs_x = 1, .ofs_y = 0},
|
||||
{.bitmap_index = 248, .adv_w = 320, .box_w = 18, .box_h = 14, .ofs_x = 1, .ofs_y = 3},
|
||||
{.bitmap_index = 311, .adv_w = 320, .box_w = 18, .box_h = 18, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 392, .adv_w = 320, .box_w = 10, .box_h = 6, .ofs_x = 5, .ofs_y = 7},
|
||||
{.bitmap_index = 407, .adv_w = 320, .box_w = 18, .box_h = 14, .ofs_x = 1, .ofs_y = 3},
|
||||
{.bitmap_index = 470, .adv_w = 320, .box_w = 16, .box_h = 16, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 534, .adv_w = 320, .box_w = 10, .box_h = 16, .ofs_x = 5, .ofs_y = 2},
|
||||
{.bitmap_index = 574, .adv_w = 320, .box_w = 14, .box_h = 16, .ofs_x = 3, .ofs_y = 2},
|
||||
{.bitmap_index = 630, .adv_w = 320, .box_w = 14, .box_h = 14, .ofs_x = 3, .ofs_y = 3},
|
||||
{.bitmap_index = 679, .adv_w = 320, .box_w = 14, .box_h = 10, .ofs_x = 3, .ofs_y = 5},
|
||||
{.bitmap_index = 714, .adv_w = 320, .box_w = 12, .box_h = 12, .ofs_x = 4, .ofs_y = 4},
|
||||
{.bitmap_index = 750, .adv_w = 320, .box_w = 16, .box_h = 10, .ofs_x = 2, .ofs_y = 5},
|
||||
{.bitmap_index = 790, .adv_w = 320, .box_w = 4, .box_h = 14, .ofs_x = 8, .ofs_y = 3},
|
||||
{.bitmap_index = 804, .adv_w = 320, .box_w = 14, .box_h = 14, .ofs_x = 3, .ofs_y = 3},
|
||||
{.bitmap_index = 853, .adv_w = 320, .box_w = 20, .box_h = 15, .ofs_x = 0, .ofs_y = 2},
|
||||
{.bitmap_index = 928, .adv_w = 320, .box_w = 16, .box_h = 14, .ofs_x = 3, .ofs_y = 3},
|
||||
{.bitmap_index = 984, .adv_w = 320, .box_w = 16, .box_h = 14, .ofs_x = 2, .ofs_y = 3},
|
||||
{.bitmap_index = 1040, .adv_w = 320, .box_w = 18, .box_h = 18, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 1121, .adv_w = 320, .box_w = 14, .box_h = 18, .ofs_x = 3, .ofs_y = 1},
|
||||
{.bitmap_index = 1184, .adv_w = 320, .box_w = 18, .box_h = 17, .ofs_x = 1, .ofs_y = 2},
|
||||
{.bitmap_index = 1261, .adv_w = 320, .box_w = 16, .box_h = 15, .ofs_x = 2, .ofs_y = 3},
|
||||
{.bitmap_index = 1321, .adv_w = 320, .box_w = 17, .box_h = 18, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 1398, .adv_w = 320, .box_w = 18, .box_h = 18, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 1479, .adv_w = 320, .box_w = 14, .box_h = 18, .ofs_x = 3, .ofs_y = 1},
|
||||
{.bitmap_index = 1542, .adv_w = 320, .box_w = 14, .box_h = 16, .ofs_x = 3, .ofs_y = 2},
|
||||
{.bitmap_index = 1598, .adv_w = 320, .box_w = 18, .box_h = 14, .ofs_x = 1, .ofs_y = 3},
|
||||
{.bitmap_index = 1661, .adv_w = 320, .box_w = 20, .box_h = 20, .ofs_x = 0, .ofs_y = 0},
|
||||
{.bitmap_index = 1761, .adv_w = 320, .box_w = 16, .box_h = 16, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 1825, .adv_w = 320, .box_w = 18, .box_h = 16, .ofs_x = 1, .ofs_y = 2},
|
||||
{.bitmap_index = 1897, .adv_w = 320, .box_w = 16, .box_h = 18, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 1969, .adv_w = 320, .box_w = 14, .box_h = 18, .ofs_x = 3, .ofs_y = 1},
|
||||
{.bitmap_index = 2032, .adv_w = 320, .box_w = 16, .box_h = 14, .ofs_x = 3, .ofs_y = 3},
|
||||
{.bitmap_index = 2088, .adv_w = 320, .box_w = 18, .box_h = 18, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 2169, .adv_w = 320, .box_w = 16, .box_h = 16, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 2233, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 2303, .adv_w = 320, .box_w = 14, .box_h = 14, .ofs_x = 3, .ofs_y = 3},
|
||||
{.bitmap_index = 2352, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 2422, .adv_w = 320, .box_w = 16, .box_h = 18, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 2494, .adv_w = 320, .box_w = 18, .box_h = 18, .ofs_x = 1, .ofs_y = 1}
|
||||
{.bitmap_index = 99, .adv_w = 320, .box_w = 11, .box_h = 16, .ofs_x = 4, .ofs_y = 2},
|
||||
{.bitmap_index = 143, .adv_w = 320, .box_w = 12, .box_h = 18, .ofs_x = 4, .ofs_y = 1},
|
||||
{.bitmap_index = 197, .adv_w = 320, .box_w = 19, .box_h = 20, .ofs_x = 1, .ofs_y = 0},
|
||||
{.bitmap_index = 292, .adv_w = 320, .box_w = 18, .box_h = 14, .ofs_x = 1, .ofs_y = 3},
|
||||
{.bitmap_index = 355, .adv_w = 320, .box_w = 18, .box_h = 18, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 436, .adv_w = 320, .box_w = 10, .box_h = 6, .ofs_x = 5, .ofs_y = 7},
|
||||
{.bitmap_index = 451, .adv_w = 320, .box_w = 18, .box_h = 14, .ofs_x = 1, .ofs_y = 3},
|
||||
{.bitmap_index = 514, .adv_w = 320, .box_w = 16, .box_h = 16, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 578, .adv_w = 320, .box_w = 10, .box_h = 16, .ofs_x = 5, .ofs_y = 2},
|
||||
{.bitmap_index = 618, .adv_w = 320, .box_w = 14, .box_h = 16, .ofs_x = 3, .ofs_y = 2},
|
||||
{.bitmap_index = 674, .adv_w = 320, .box_w = 14, .box_h = 14, .ofs_x = 3, .ofs_y = 3},
|
||||
{.bitmap_index = 723, .adv_w = 320, .box_w = 14, .box_h = 10, .ofs_x = 3, .ofs_y = 5},
|
||||
{.bitmap_index = 758, .adv_w = 320, .box_w = 12, .box_h = 12, .ofs_x = 4, .ofs_y = 4},
|
||||
{.bitmap_index = 794, .adv_w = 320, .box_w = 16, .box_h = 10, .ofs_x = 2, .ofs_y = 5},
|
||||
{.bitmap_index = 834, .adv_w = 320, .box_w = 4, .box_h = 14, .ofs_x = 8, .ofs_y = 3},
|
||||
{.bitmap_index = 848, .adv_w = 320, .box_w = 14, .box_h = 14, .ofs_x = 3, .ofs_y = 3},
|
||||
{.bitmap_index = 897, .adv_w = 320, .box_w = 20, .box_h = 15, .ofs_x = 0, .ofs_y = 2},
|
||||
{.bitmap_index = 972, .adv_w = 320, .box_w = 16, .box_h = 14, .ofs_x = 3, .ofs_y = 3},
|
||||
{.bitmap_index = 1028, .adv_w = 320, .box_w = 16, .box_h = 14, .ofs_x = 2, .ofs_y = 3},
|
||||
{.bitmap_index = 1084, .adv_w = 320, .box_w = 18, .box_h = 18, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 1165, .adv_w = 320, .box_w = 14, .box_h = 18, .ofs_x = 3, .ofs_y = 1},
|
||||
{.bitmap_index = 1228, .adv_w = 320, .box_w = 18, .box_h = 17, .ofs_x = 1, .ofs_y = 2},
|
||||
{.bitmap_index = 1305, .adv_w = 320, .box_w = 16, .box_h = 15, .ofs_x = 2, .ofs_y = 3},
|
||||
{.bitmap_index = 1365, .adv_w = 320, .box_w = 17, .box_h = 18, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 1442, .adv_w = 320, .box_w = 18, .box_h = 18, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 1523, .adv_w = 320, .box_w = 14, .box_h = 18, .ofs_x = 3, .ofs_y = 1},
|
||||
{.bitmap_index = 1586, .adv_w = 320, .box_w = 14, .box_h = 16, .ofs_x = 3, .ofs_y = 2},
|
||||
{.bitmap_index = 1642, .adv_w = 320, .box_w = 18, .box_h = 14, .ofs_x = 1, .ofs_y = 3},
|
||||
{.bitmap_index = 1705, .adv_w = 320, .box_w = 20, .box_h = 20, .ofs_x = 0, .ofs_y = 0},
|
||||
{.bitmap_index = 1805, .adv_w = 320, .box_w = 16, .box_h = 16, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 1869, .adv_w = 320, .box_w = 18, .box_h = 16, .ofs_x = 1, .ofs_y = 2},
|
||||
{.bitmap_index = 1941, .adv_w = 320, .box_w = 16, .box_h = 18, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 2013, .adv_w = 320, .box_w = 14, .box_h = 18, .ofs_x = 3, .ofs_y = 1},
|
||||
{.bitmap_index = 2076, .adv_w = 320, .box_w = 16, .box_h = 14, .ofs_x = 3, .ofs_y = 3},
|
||||
{.bitmap_index = 2132, .adv_w = 320, .box_w = 18, .box_h = 18, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 2213, .adv_w = 320, .box_w = 16, .box_h = 16, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 2277, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 2347, .adv_w = 320, .box_w = 14, .box_h = 14, .ofs_x = 3, .ofs_y = 3},
|
||||
{.bitmap_index = 2396, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 2466, .adv_w = 320, .box_w = 16, .box_h = 18, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 2538, .adv_w = 320, .box_w = 18, .box_h = 18, .ofs_x = 1, .ofs_y = 1}
|
||||
};
|
||||
|
||||
/*---------------------
|
||||
@ -498,12 +507,12 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
|
||||
*--------------------*/
|
||||
|
||||
static const uint16_t unicode_list_0[] = {
|
||||
0x0, 0x14, 0x9b, 0x148, 0x182, 0x1ca, 0x1d1, 0x1e1,
|
||||
0x2af, 0x2c0, 0x418, 0x47e, 0x485, 0x488, 0x48d, 0x48f,
|
||||
0x490, 0x4f9, 0x600, 0x62b, 0x74f, 0x757, 0x76a, 0x771,
|
||||
0x773, 0x7b8, 0x7ca, 0x7e9, 0x874, 0x8af, 0x8b2, 0xa52,
|
||||
0xa87, 0xad7, 0xdfb, 0xe05, 0xea1, 0xee3, 0xf4b, 0x1017,
|
||||
0x15db, 0x1782
|
||||
0x0, 0x14, 0x62, 0x9b, 0x148, 0x182, 0x1ca, 0x1d1,
|
||||
0x1e1, 0x2af, 0x2c0, 0x418, 0x47e, 0x485, 0x488, 0x48d,
|
||||
0x48f, 0x490, 0x4f9, 0x600, 0x62b, 0x74f, 0x757, 0x76a,
|
||||
0x771, 0x773, 0x7b8, 0x7ca, 0x7e9, 0x874, 0x8af, 0x8b2,
|
||||
0xa52, 0xa87, 0xad7, 0xdfb, 0xe05, 0xea1, 0xee3, 0xf4b,
|
||||
0x1017, 0x15db, 0x1782
|
||||
};
|
||||
|
||||
/*Collect the unicode lists and glyph_id offsets*/
|
||||
@ -511,7 +520,7 @@ static const lv_font_fmt_txt_cmap_t cmaps[] =
|
||||
{
|
||||
{
|
||||
.range_start = 57669, .range_length = 6019, .glyph_id_start = 1,
|
||||
.unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 42, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
|
||||
.unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 43, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*******************************************************************************
|
||||
* Size: 24 px
|
||||
* Bpp: 2
|
||||
* Opts: --no-compress --no-prefilter --bpp 2 --size 24 --font MaterialSymbolsRounded.ttf -r 0xE145,0xE5C3,0xE770,0xEF40,0xEBCC,0xEFE6,0xEF4A,0xE5CD,0xF15C,0xE5CA,0xE92E,0xE326,0xEB97,0xE745,0xEC1C,0xE2C7,0xF720,0xF090,0xE8AF,0xE30F,0xE8FD,0xE9F4,0xE3F4,0xE316,0xE90F,0xE894,0xE9B9,0xE159,0xE5D2,0xE28D,0xE5D4,0xE405,0xE89C,0xF8C7,0xE5D5,0xE8B6,0xE8B8,0xE9F7,0xE55D,0xF028,0xE1E0,0xE63E --format lvgl -o ../source-fonts/material_symbols_shared_24.c --force-fast-kern-format
|
||||
* Opts: --no-compress --no-prefilter --bpp 2 --size 24 --font MaterialSymbolsRounded.ttf -r 0xE145,0xE5C3,0xE770,0xEF40,0xEBCC,0xEFE6,0xEF4A,0xE5CD,0xF15C,0xE5CA,0xE92E,0xE326,0xEB97,0xE745,0xEC1C,0xE2C7,0xF720,0xF090,0xE8AF,0xE30F,0xE8FD,0xE9F4,0xE3F4,0xE316,0xE90F,0xE894,0xE9B9,0xE159,0xE5D2,0xE28D,0xE5D4,0xE405,0xE89C,0xF8C7,0xE5D5,0xE8B6,0xE8B8,0xE9F7,0xE55D,0xF028,0xE1E0,0xE63E,0xE1A7 --format lvgl -o ../source-fonts/material_symbols_shared_24.c --force-fast-kern-format
|
||||
******************************************************************************/
|
||||
|
||||
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
|
||||
@ -43,6 +43,17 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
|
||||
0xf, 0xf0, 0x0, 0x0, 0x0, 0xf, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xff, 0xfd,
|
||||
|
||||
/* U+E1A7 "" */
|
||||
0x0, 0x0, 0x0, 0x0, 0x3, 0xd0, 0x0, 0x0,
|
||||
0xfd, 0x0, 0x40, 0x3f, 0xd0, 0xb8, 0xf, 0xbd,
|
||||
0xf, 0x83, 0xcb, 0xc0, 0xf8, 0xf2, 0xf0, 0xf,
|
||||
0xbe, 0xf0, 0x0, 0xff, 0xf0, 0x0, 0xf, 0xf0,
|
||||
0x0, 0x2, 0xf8, 0x0, 0x2, 0xff, 0x80, 0x2,
|
||||
0xff, 0xf8, 0x2, 0xf3, 0xcf, 0x82, 0xf0, 0xf1,
|
||||
0xf1, 0xf0, 0x3c, 0xf8, 0x60, 0xf, 0xf8, 0x0,
|
||||
0x3, 0xf8, 0x0, 0x0, 0xf8, 0x0, 0x0, 0x28,
|
||||
0x0,
|
||||
|
||||
/* U+E1E0 "" */
|
||||
0x0, 0x5, 0x0, 0x0, 0x0, 0xf0, 0x0, 0x0,
|
||||
0x3f, 0xc0, 0x0, 0x7, 0xfd, 0x0, 0x0, 0xf,
|
||||
@ -543,46 +554,47 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
|
||||
{.bitmap_index = 0, .adv_w = 0, .box_w = 0, .box_h = 0, .ofs_x = 0, .ofs_y = 0} /* id = 0 reserved */,
|
||||
{.bitmap_index = 0, .adv_w = 384, .box_w = 14, .box_h = 14, .ofs_x = 5, .ofs_y = 5},
|
||||
{.bitmap_index = 49, .adv_w = 384, .box_w = 20, .box_h = 16, .ofs_x = 2, .ofs_y = 4},
|
||||
{.bitmap_index = 129, .adv_w = 384, .box_w = 14, .box_h = 20, .ofs_x = 5, .ofs_y = 2},
|
||||
{.bitmap_index = 199, .adv_w = 384, .box_w = 22, .box_h = 22, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 320, .adv_w = 384, .box_w = 20, .box_h = 16, .ofs_x = 2, .ofs_y = 4},
|
||||
{.bitmap_index = 400, .adv_w = 384, .box_w = 20, .box_h = 20, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 500, .adv_w = 384, .box_w = 12, .box_h = 7, .ofs_x = 6, .ofs_y = 9},
|
||||
{.bitmap_index = 521, .adv_w = 384, .box_w = 20, .box_h = 16, .ofs_x = 2, .ofs_y = 4},
|
||||
{.bitmap_index = 601, .adv_w = 384, .box_w = 18, .box_h = 18, .ofs_x = 3, .ofs_y = 3},
|
||||
{.bitmap_index = 682, .adv_w = 384, .box_w = 12, .box_h = 18, .ofs_x = 6, .ofs_y = 3},
|
||||
{.bitmap_index = 736, .adv_w = 384, .box_w = 16, .box_h = 18, .ofs_x = 4, .ofs_y = 3},
|
||||
{.bitmap_index = 808, .adv_w = 384, .box_w = 16, .box_h = 16, .ofs_x = 4, .ofs_y = 4},
|
||||
{.bitmap_index = 872, .adv_w = 384, .box_w = 16, .box_h = 12, .ofs_x = 4, .ofs_y = 6},
|
||||
{.bitmap_index = 920, .adv_w = 384, .box_w = 14, .box_h = 14, .ofs_x = 5, .ofs_y = 5},
|
||||
{.bitmap_index = 969, .adv_w = 384, .box_w = 18, .box_h = 12, .ofs_x = 3, .ofs_y = 6},
|
||||
{.bitmap_index = 1023, .adv_w = 384, .box_w = 4, .box_h = 16, .ofs_x = 10, .ofs_y = 4},
|
||||
{.bitmap_index = 1039, .adv_w = 384, .box_w = 16, .box_h = 16, .ofs_x = 4, .ofs_y = 4},
|
||||
{.bitmap_index = 1103, .adv_w = 384, .box_w = 24, .box_h = 17, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 1205, .adv_w = 384, .box_w = 18, .box_h = 16, .ofs_x = 4, .ofs_y = 4},
|
||||
{.bitmap_index = 1277, .adv_w = 384, .box_w = 18, .box_h = 17, .ofs_x = 3, .ofs_y = 4},
|
||||
{.bitmap_index = 1354, .adv_w = 384, .box_w = 20, .box_h = 20, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 1454, .adv_w = 384, .box_w = 16, .box_h = 20, .ofs_x = 4, .ofs_y = 2},
|
||||
{.bitmap_index = 1534, .adv_w = 384, .box_w = 20, .box_h = 19, .ofs_x = 2, .ofs_y = 3},
|
||||
{.bitmap_index = 1629, .adv_w = 384, .box_w = 18, .box_h = 18, .ofs_x = 3, .ofs_y = 3},
|
||||
{.bitmap_index = 1710, .adv_w = 384, .box_w = 20, .box_h = 20, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 1810, .adv_w = 384, .box_w = 20, .box_h = 20, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 1910, .adv_w = 384, .box_w = 16, .box_h = 20, .ofs_x = 4, .ofs_y = 2},
|
||||
{.bitmap_index = 1990, .adv_w = 384, .box_w = 16, .box_h = 18, .ofs_x = 4, .ofs_y = 3},
|
||||
{.bitmap_index = 2062, .adv_w = 384, .box_w = 20, .box_h = 16, .ofs_x = 2, .ofs_y = 4},
|
||||
{.bitmap_index = 2142, .adv_w = 384, .box_w = 24, .box_h = 23, .ofs_x = 0, .ofs_y = 1},
|
||||
{.bitmap_index = 2280, .adv_w = 384, .box_w = 18, .box_h = 18, .ofs_x = 3, .ofs_y = 3},
|
||||
{.bitmap_index = 2361, .adv_w = 384, .box_w = 20, .box_h = 18, .ofs_x = 2, .ofs_y = 3},
|
||||
{.bitmap_index = 2451, .adv_w = 384, .box_w = 18, .box_h = 20, .ofs_x = 3, .ofs_y = 2},
|
||||
{.bitmap_index = 2541, .adv_w = 384, .box_w = 16, .box_h = 20, .ofs_x = 4, .ofs_y = 2},
|
||||
{.bitmap_index = 2621, .adv_w = 384, .box_w = 18, .box_h = 16, .ofs_x = 4, .ofs_y = 4},
|
||||
{.bitmap_index = 2693, .adv_w = 384, .box_w = 20, .box_h = 20, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 2793, .adv_w = 384, .box_w = 18, .box_h = 18, .ofs_x = 3, .ofs_y = 3},
|
||||
{.bitmap_index = 2874, .adv_w = 384, .box_w = 22, .box_h = 16, .ofs_x = 1, .ofs_y = 4},
|
||||
{.bitmap_index = 2962, .adv_w = 384, .box_w = 16, .box_h = 16, .ofs_x = 4, .ofs_y = 4},
|
||||
{.bitmap_index = 3026, .adv_w = 384, .box_w = 22, .box_h = 16, .ofs_x = 1, .ofs_y = 4},
|
||||
{.bitmap_index = 3114, .adv_w = 384, .box_w = 18, .box_h = 20, .ofs_x = 3, .ofs_y = 2},
|
||||
{.bitmap_index = 3204, .adv_w = 384, .box_w = 20, .box_h = 20, .ofs_x = 2, .ofs_y = 2}
|
||||
{.bitmap_index = 129, .adv_w = 384, .box_w = 13, .box_h = 20, .ofs_x = 5, .ofs_y = 2},
|
||||
{.bitmap_index = 194, .adv_w = 384, .box_w = 14, .box_h = 20, .ofs_x = 5, .ofs_y = 2},
|
||||
{.bitmap_index = 264, .adv_w = 384, .box_w = 22, .box_h = 22, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 385, .adv_w = 384, .box_w = 20, .box_h = 16, .ofs_x = 2, .ofs_y = 4},
|
||||
{.bitmap_index = 465, .adv_w = 384, .box_w = 20, .box_h = 20, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 565, .adv_w = 384, .box_w = 12, .box_h = 7, .ofs_x = 6, .ofs_y = 9},
|
||||
{.bitmap_index = 586, .adv_w = 384, .box_w = 20, .box_h = 16, .ofs_x = 2, .ofs_y = 4},
|
||||
{.bitmap_index = 666, .adv_w = 384, .box_w = 18, .box_h = 18, .ofs_x = 3, .ofs_y = 3},
|
||||
{.bitmap_index = 747, .adv_w = 384, .box_w = 12, .box_h = 18, .ofs_x = 6, .ofs_y = 3},
|
||||
{.bitmap_index = 801, .adv_w = 384, .box_w = 16, .box_h = 18, .ofs_x = 4, .ofs_y = 3},
|
||||
{.bitmap_index = 873, .adv_w = 384, .box_w = 16, .box_h = 16, .ofs_x = 4, .ofs_y = 4},
|
||||
{.bitmap_index = 937, .adv_w = 384, .box_w = 16, .box_h = 12, .ofs_x = 4, .ofs_y = 6},
|
||||
{.bitmap_index = 985, .adv_w = 384, .box_w = 14, .box_h = 14, .ofs_x = 5, .ofs_y = 5},
|
||||
{.bitmap_index = 1034, .adv_w = 384, .box_w = 18, .box_h = 12, .ofs_x = 3, .ofs_y = 6},
|
||||
{.bitmap_index = 1088, .adv_w = 384, .box_w = 4, .box_h = 16, .ofs_x = 10, .ofs_y = 4},
|
||||
{.bitmap_index = 1104, .adv_w = 384, .box_w = 16, .box_h = 16, .ofs_x = 4, .ofs_y = 4},
|
||||
{.bitmap_index = 1168, .adv_w = 384, .box_w = 24, .box_h = 17, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 1270, .adv_w = 384, .box_w = 18, .box_h = 16, .ofs_x = 4, .ofs_y = 4},
|
||||
{.bitmap_index = 1342, .adv_w = 384, .box_w = 18, .box_h = 17, .ofs_x = 3, .ofs_y = 4},
|
||||
{.bitmap_index = 1419, .adv_w = 384, .box_w = 20, .box_h = 20, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 1519, .adv_w = 384, .box_w = 16, .box_h = 20, .ofs_x = 4, .ofs_y = 2},
|
||||
{.bitmap_index = 1599, .adv_w = 384, .box_w = 20, .box_h = 19, .ofs_x = 2, .ofs_y = 3},
|
||||
{.bitmap_index = 1694, .adv_w = 384, .box_w = 18, .box_h = 18, .ofs_x = 3, .ofs_y = 3},
|
||||
{.bitmap_index = 1775, .adv_w = 384, .box_w = 20, .box_h = 20, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 1875, .adv_w = 384, .box_w = 20, .box_h = 20, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 1975, .adv_w = 384, .box_w = 16, .box_h = 20, .ofs_x = 4, .ofs_y = 2},
|
||||
{.bitmap_index = 2055, .adv_w = 384, .box_w = 16, .box_h = 18, .ofs_x = 4, .ofs_y = 3},
|
||||
{.bitmap_index = 2127, .adv_w = 384, .box_w = 20, .box_h = 16, .ofs_x = 2, .ofs_y = 4},
|
||||
{.bitmap_index = 2207, .adv_w = 384, .box_w = 24, .box_h = 23, .ofs_x = 0, .ofs_y = 1},
|
||||
{.bitmap_index = 2345, .adv_w = 384, .box_w = 18, .box_h = 18, .ofs_x = 3, .ofs_y = 3},
|
||||
{.bitmap_index = 2426, .adv_w = 384, .box_w = 20, .box_h = 18, .ofs_x = 2, .ofs_y = 3},
|
||||
{.bitmap_index = 2516, .adv_w = 384, .box_w = 18, .box_h = 20, .ofs_x = 3, .ofs_y = 2},
|
||||
{.bitmap_index = 2606, .adv_w = 384, .box_w = 16, .box_h = 20, .ofs_x = 4, .ofs_y = 2},
|
||||
{.bitmap_index = 2686, .adv_w = 384, .box_w = 18, .box_h = 16, .ofs_x = 4, .ofs_y = 4},
|
||||
{.bitmap_index = 2758, .adv_w = 384, .box_w = 20, .box_h = 20, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 2858, .adv_w = 384, .box_w = 18, .box_h = 18, .ofs_x = 3, .ofs_y = 3},
|
||||
{.bitmap_index = 2939, .adv_w = 384, .box_w = 22, .box_h = 16, .ofs_x = 1, .ofs_y = 4},
|
||||
{.bitmap_index = 3027, .adv_w = 384, .box_w = 16, .box_h = 16, .ofs_x = 4, .ofs_y = 4},
|
||||
{.bitmap_index = 3091, .adv_w = 384, .box_w = 22, .box_h = 16, .ofs_x = 1, .ofs_y = 4},
|
||||
{.bitmap_index = 3179, .adv_w = 384, .box_w = 18, .box_h = 20, .ofs_x = 3, .ofs_y = 2},
|
||||
{.bitmap_index = 3269, .adv_w = 384, .box_w = 20, .box_h = 20, .ofs_x = 2, .ofs_y = 2}
|
||||
};
|
||||
|
||||
/*---------------------
|
||||
@ -590,12 +602,12 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
|
||||
*--------------------*/
|
||||
|
||||
static const uint16_t unicode_list_0[] = {
|
||||
0x0, 0x14, 0x9b, 0x148, 0x182, 0x1ca, 0x1d1, 0x1e1,
|
||||
0x2af, 0x2c0, 0x418, 0x47e, 0x485, 0x488, 0x48d, 0x48f,
|
||||
0x490, 0x4f9, 0x600, 0x62b, 0x74f, 0x757, 0x76a, 0x771,
|
||||
0x773, 0x7b8, 0x7ca, 0x7e9, 0x874, 0x8af, 0x8b2, 0xa52,
|
||||
0xa87, 0xad7, 0xdfb, 0xe05, 0xea1, 0xee3, 0xf4b, 0x1017,
|
||||
0x15db, 0x1782
|
||||
0x0, 0x14, 0x62, 0x9b, 0x148, 0x182, 0x1ca, 0x1d1,
|
||||
0x1e1, 0x2af, 0x2c0, 0x418, 0x47e, 0x485, 0x488, 0x48d,
|
||||
0x48f, 0x490, 0x4f9, 0x600, 0x62b, 0x74f, 0x757, 0x76a,
|
||||
0x771, 0x773, 0x7b8, 0x7ca, 0x7e9, 0x874, 0x8af, 0x8b2,
|
||||
0xa52, 0xa87, 0xad7, 0xdfb, 0xe05, 0xea1, 0xee3, 0xf4b,
|
||||
0x1017, 0x15db, 0x1782
|
||||
};
|
||||
|
||||
/*Collect the unicode lists and glyph_id offsets*/
|
||||
@ -603,7 +615,7 @@ static const lv_font_fmt_txt_cmap_t cmaps[] =
|
||||
{
|
||||
{
|
||||
.range_start = 57669, .range_length = 6019, .glyph_id_start = 1,
|
||||
.unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 42, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
|
||||
.unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 43, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*******************************************************************************
|
||||
* Size: 32 px
|
||||
* Bpp: 2
|
||||
* Opts: --no-compress --no-prefilter --bpp 2 --size 32 --font MaterialSymbolsRounded.ttf -r 0xE145,0xE5C3,0xE770,0xEF40,0xEBCC,0xEFE6,0xEF4A,0xE5CD,0xF15C,0xE5CA,0xE92E,0xE326,0xEB97,0xE745,0xEC1C,0xE2C7,0xF720,0xF090,0xE8AF,0xE30F,0xE8FD,0xE9F4,0xE3F4,0xE316,0xE90F,0xE894,0xE9B9,0xE159,0xE5D2,0xE28D,0xE5D4,0xE405,0xE89C,0xF8C7,0xE5D5,0xE8B6,0xE8B8,0xE9F7,0xE55D,0xF028,0xE1E0,0xE63E --format lvgl -o ../source-fonts/material_symbols_shared_32.c --force-fast-kern-format
|
||||
* Opts: --no-compress --no-prefilter --bpp 2 --size 32 --font MaterialSymbolsRounded.ttf -r 0xE145,0xE5C3,0xE770,0xEF40,0xEBCC,0xEFE6,0xEF4A,0xE5CD,0xF15C,0xE5CA,0xE92E,0xE326,0xEB97,0xE745,0xEC1C,0xE2C7,0xF720,0xF090,0xE8AF,0xE30F,0xE8FD,0xE9F4,0xE3F4,0xE316,0xE90F,0xE894,0xE9B9,0xE159,0xE5D2,0xE28D,0xE5D4,0xE405,0xE89C,0xF8C7,0xE5D5,0xE8B6,0xE8B8,0xE9F7,0xE55D,0xF028,0xE1E0,0xE63E,0xE1A7 --format lvgl -o ../source-fonts/material_symbols_shared_32.c --force-fast-kern-format
|
||||
******************************************************************************/
|
||||
|
||||
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
|
||||
@ -59,6 +59,22 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
|
||||
0xff, 0xff, 0xfc, 0x6, 0xaa, 0xaa, 0xaa, 0xaa,
|
||||
0xaa, 0xa0,
|
||||
|
||||
/* U+E1A7 "" */
|
||||
0x0, 0x0, 0x60, 0x0, 0x0, 0x0, 0x3f, 0x0,
|
||||
0x0, 0x0, 0xf, 0xf0, 0x0, 0x0, 0x3, 0xff,
|
||||
0x0, 0x24, 0x0, 0xff, 0xf0, 0x2f, 0x80, 0x3e,
|
||||
0xff, 0x3, 0xf8, 0xf, 0x8b, 0xf0, 0x7f, 0x83,
|
||||
0xe0, 0xfc, 0x7, 0xf8, 0xf8, 0xfe, 0x0, 0x7f,
|
||||
0xbe, 0xfe, 0x0, 0x7, 0xff, 0xfe, 0x0, 0x0,
|
||||
0x7f, 0xfe, 0x0, 0x0, 0x3, 0xfe, 0x0, 0x0,
|
||||
0x0, 0xff, 0x80, 0x0, 0x0, 0xff, 0xf8, 0x0,
|
||||
0x0, 0xff, 0xff, 0x80, 0x0, 0xfe, 0xff, 0xf8,
|
||||
0x0, 0xfe, 0x3e, 0x7f, 0x80, 0xfe, 0xf, 0x87,
|
||||
0xf0, 0xfe, 0x3, 0xe2, 0xfc, 0xbe, 0x0, 0xfa,
|
||||
0xfc, 0xe, 0x0, 0x3f, 0xfc, 0x0, 0x0, 0xf,
|
||||
0xfc, 0x0, 0x0, 0x3, 0xfc, 0x0, 0x0, 0x0,
|
||||
0xfc, 0x0, 0x0, 0x0, 0x2c, 0x0, 0x0,
|
||||
|
||||
/* U+E1E0 "" */
|
||||
0x0, 0x0, 0x18, 0x0, 0x0, 0x0, 0x0, 0x3e,
|
||||
0x0, 0x0, 0x0, 0x0, 0xff, 0x40, 0x0, 0x0,
|
||||
@ -907,46 +923,47 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
|
||||
{.bitmap_index = 0, .adv_w = 0, .box_w = 0, .box_h = 0, .ofs_x = 0, .ofs_y = 0} /* id = 0 reserved */,
|
||||
{.bitmap_index = 0, .adv_w = 512, .box_w = 20, .box_h = 20, .ofs_x = 6, .ofs_y = 6},
|
||||
{.bitmap_index = 100, .adv_w = 512, .box_w = 28, .box_h = 22, .ofs_x = 2, .ofs_y = 5},
|
||||
{.bitmap_index = 254, .adv_w = 512, .box_w = 20, .box_h = 27, .ofs_x = 6, .ofs_y = 2},
|
||||
{.bitmap_index = 389, .adv_w = 512, .box_w = 30, .box_h = 30, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 614, .adv_w = 512, .box_w = 28, .box_h = 22, .ofs_x = 2, .ofs_y = 5},
|
||||
{.bitmap_index = 768, .adv_w = 512, .box_w = 28, .box_h = 28, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 964, .adv_w = 512, .box_w = 16, .box_h = 9, .ofs_x = 8, .ofs_y = 12},
|
||||
{.bitmap_index = 1000, .adv_w = 512, .box_w = 28, .box_h = 22, .ofs_x = 2, .ofs_y = 5},
|
||||
{.bitmap_index = 1154, .adv_w = 512, .box_w = 24, .box_h = 24, .ofs_x = 4, .ofs_y = 4},
|
||||
{.bitmap_index = 1298, .adv_w = 512, .box_w = 16, .box_h = 24, .ofs_x = 8, .ofs_y = 4},
|
||||
{.bitmap_index = 1394, .adv_w = 512, .box_w = 22, .box_h = 24, .ofs_x = 5, .ofs_y = 4},
|
||||
{.bitmap_index = 1526, .adv_w = 512, .box_w = 22, .box_h = 22, .ofs_x = 5, .ofs_y = 5},
|
||||
{.bitmap_index = 1647, .adv_w = 512, .box_w = 22, .box_h = 16, .ofs_x = 5, .ofs_y = 8},
|
||||
{.bitmap_index = 1735, .adv_w = 512, .box_w = 18, .box_h = 18, .ofs_x = 7, .ofs_y = 7},
|
||||
{.bitmap_index = 1816, .adv_w = 512, .box_w = 24, .box_h = 16, .ofs_x = 4, .ofs_y = 8},
|
||||
{.bitmap_index = 1912, .adv_w = 512, .box_w = 6, .box_h = 22, .ofs_x = 13, .ofs_y = 5},
|
||||
{.bitmap_index = 1945, .adv_w = 512, .box_w = 22, .box_h = 22, .ofs_x = 5, .ofs_y = 5},
|
||||
{.bitmap_index = 2066, .adv_w = 512, .box_w = 32, .box_h = 23, .ofs_x = 0, .ofs_y = 4},
|
||||
{.bitmap_index = 2250, .adv_w = 512, .box_w = 25, .box_h = 22, .ofs_x = 5, .ofs_y = 5},
|
||||
{.bitmap_index = 2388, .adv_w = 512, .box_w = 24, .box_h = 22, .ofs_x = 4, .ofs_y = 5},
|
||||
{.bitmap_index = 2520, .adv_w = 512, .box_w = 28, .box_h = 28, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 2716, .adv_w = 512, .box_w = 22, .box_h = 28, .ofs_x = 5, .ofs_y = 2},
|
||||
{.bitmap_index = 2870, .adv_w = 512, .box_w = 28, .box_h = 26, .ofs_x = 2, .ofs_y = 4},
|
||||
{.bitmap_index = 3052, .adv_w = 512, .box_w = 24, .box_h = 24, .ofs_x = 4, .ofs_y = 4},
|
||||
{.bitmap_index = 3196, .adv_w = 512, .box_w = 26, .box_h = 28, .ofs_x = 3, .ofs_y = 2},
|
||||
{.bitmap_index = 3378, .adv_w = 512, .box_w = 28, .box_h = 28, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 3574, .adv_w = 512, .box_w = 20, .box_h = 28, .ofs_x = 6, .ofs_y = 2},
|
||||
{.bitmap_index = 3714, .adv_w = 512, .box_w = 22, .box_h = 24, .ofs_x = 5, .ofs_y = 4},
|
||||
{.bitmap_index = 3846, .adv_w = 512, .box_w = 28, .box_h = 22, .ofs_x = 2, .ofs_y = 5},
|
||||
{.bitmap_index = 4000, .adv_w = 512, .box_w = 32, .box_h = 31, .ofs_x = 0, .ofs_y = 1},
|
||||
{.bitmap_index = 4248, .adv_w = 512, .box_w = 24, .box_h = 24, .ofs_x = 4, .ofs_y = 4},
|
||||
{.bitmap_index = 4392, .adv_w = 512, .box_w = 28, .box_h = 24, .ofs_x = 2, .ofs_y = 4},
|
||||
{.bitmap_index = 4560, .adv_w = 512, .box_w = 24, .box_h = 28, .ofs_x = 4, .ofs_y = 2},
|
||||
{.bitmap_index = 4728, .adv_w = 512, .box_w = 22, .box_h = 28, .ofs_x = 5, .ofs_y = 2},
|
||||
{.bitmap_index = 4882, .adv_w = 512, .box_w = 25, .box_h = 22, .ofs_x = 5, .ofs_y = 5},
|
||||
{.bitmap_index = 5020, .adv_w = 512, .box_w = 28, .box_h = 28, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 5216, .adv_w = 512, .box_w = 24, .box_h = 24, .ofs_x = 4, .ofs_y = 4},
|
||||
{.bitmap_index = 5360, .adv_w = 512, .box_w = 30, .box_h = 22, .ofs_x = 1, .ofs_y = 5},
|
||||
{.bitmap_index = 5525, .adv_w = 512, .box_w = 22, .box_h = 22, .ofs_x = 5, .ofs_y = 5},
|
||||
{.bitmap_index = 5646, .adv_w = 512, .box_w = 30, .box_h = 22, .ofs_x = 1, .ofs_y = 5},
|
||||
{.bitmap_index = 5811, .adv_w = 512, .box_w = 25, .box_h = 28, .ofs_x = 4, .ofs_y = 2},
|
||||
{.bitmap_index = 5986, .adv_w = 512, .box_w = 28, .box_h = 28, .ofs_x = 2, .ofs_y = 2}
|
||||
{.bitmap_index = 254, .adv_w = 512, .box_w = 17, .box_h = 26, .ofs_x = 7, .ofs_y = 3},
|
||||
{.bitmap_index = 365, .adv_w = 512, .box_w = 20, .box_h = 27, .ofs_x = 6, .ofs_y = 2},
|
||||
{.bitmap_index = 500, .adv_w = 512, .box_w = 30, .box_h = 30, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 725, .adv_w = 512, .box_w = 28, .box_h = 22, .ofs_x = 2, .ofs_y = 5},
|
||||
{.bitmap_index = 879, .adv_w = 512, .box_w = 28, .box_h = 28, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 1075, .adv_w = 512, .box_w = 16, .box_h = 9, .ofs_x = 8, .ofs_y = 12},
|
||||
{.bitmap_index = 1111, .adv_w = 512, .box_w = 28, .box_h = 22, .ofs_x = 2, .ofs_y = 5},
|
||||
{.bitmap_index = 1265, .adv_w = 512, .box_w = 24, .box_h = 24, .ofs_x = 4, .ofs_y = 4},
|
||||
{.bitmap_index = 1409, .adv_w = 512, .box_w = 16, .box_h = 24, .ofs_x = 8, .ofs_y = 4},
|
||||
{.bitmap_index = 1505, .adv_w = 512, .box_w = 22, .box_h = 24, .ofs_x = 5, .ofs_y = 4},
|
||||
{.bitmap_index = 1637, .adv_w = 512, .box_w = 22, .box_h = 22, .ofs_x = 5, .ofs_y = 5},
|
||||
{.bitmap_index = 1758, .adv_w = 512, .box_w = 22, .box_h = 16, .ofs_x = 5, .ofs_y = 8},
|
||||
{.bitmap_index = 1846, .adv_w = 512, .box_w = 18, .box_h = 18, .ofs_x = 7, .ofs_y = 7},
|
||||
{.bitmap_index = 1927, .adv_w = 512, .box_w = 24, .box_h = 16, .ofs_x = 4, .ofs_y = 8},
|
||||
{.bitmap_index = 2023, .adv_w = 512, .box_w = 6, .box_h = 22, .ofs_x = 13, .ofs_y = 5},
|
||||
{.bitmap_index = 2056, .adv_w = 512, .box_w = 22, .box_h = 22, .ofs_x = 5, .ofs_y = 5},
|
||||
{.bitmap_index = 2177, .adv_w = 512, .box_w = 32, .box_h = 23, .ofs_x = 0, .ofs_y = 4},
|
||||
{.bitmap_index = 2361, .adv_w = 512, .box_w = 25, .box_h = 22, .ofs_x = 5, .ofs_y = 5},
|
||||
{.bitmap_index = 2499, .adv_w = 512, .box_w = 24, .box_h = 22, .ofs_x = 4, .ofs_y = 5},
|
||||
{.bitmap_index = 2631, .adv_w = 512, .box_w = 28, .box_h = 28, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 2827, .adv_w = 512, .box_w = 22, .box_h = 28, .ofs_x = 5, .ofs_y = 2},
|
||||
{.bitmap_index = 2981, .adv_w = 512, .box_w = 28, .box_h = 26, .ofs_x = 2, .ofs_y = 4},
|
||||
{.bitmap_index = 3163, .adv_w = 512, .box_w = 24, .box_h = 24, .ofs_x = 4, .ofs_y = 4},
|
||||
{.bitmap_index = 3307, .adv_w = 512, .box_w = 26, .box_h = 28, .ofs_x = 3, .ofs_y = 2},
|
||||
{.bitmap_index = 3489, .adv_w = 512, .box_w = 28, .box_h = 28, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 3685, .adv_w = 512, .box_w = 20, .box_h = 28, .ofs_x = 6, .ofs_y = 2},
|
||||
{.bitmap_index = 3825, .adv_w = 512, .box_w = 22, .box_h = 24, .ofs_x = 5, .ofs_y = 4},
|
||||
{.bitmap_index = 3957, .adv_w = 512, .box_w = 28, .box_h = 22, .ofs_x = 2, .ofs_y = 5},
|
||||
{.bitmap_index = 4111, .adv_w = 512, .box_w = 32, .box_h = 31, .ofs_x = 0, .ofs_y = 1},
|
||||
{.bitmap_index = 4359, .adv_w = 512, .box_w = 24, .box_h = 24, .ofs_x = 4, .ofs_y = 4},
|
||||
{.bitmap_index = 4503, .adv_w = 512, .box_w = 28, .box_h = 24, .ofs_x = 2, .ofs_y = 4},
|
||||
{.bitmap_index = 4671, .adv_w = 512, .box_w = 24, .box_h = 28, .ofs_x = 4, .ofs_y = 2},
|
||||
{.bitmap_index = 4839, .adv_w = 512, .box_w = 22, .box_h = 28, .ofs_x = 5, .ofs_y = 2},
|
||||
{.bitmap_index = 4993, .adv_w = 512, .box_w = 25, .box_h = 22, .ofs_x = 5, .ofs_y = 5},
|
||||
{.bitmap_index = 5131, .adv_w = 512, .box_w = 28, .box_h = 28, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 5327, .adv_w = 512, .box_w = 24, .box_h = 24, .ofs_x = 4, .ofs_y = 4},
|
||||
{.bitmap_index = 5471, .adv_w = 512, .box_w = 30, .box_h = 22, .ofs_x = 1, .ofs_y = 5},
|
||||
{.bitmap_index = 5636, .adv_w = 512, .box_w = 22, .box_h = 22, .ofs_x = 5, .ofs_y = 5},
|
||||
{.bitmap_index = 5757, .adv_w = 512, .box_w = 30, .box_h = 22, .ofs_x = 1, .ofs_y = 5},
|
||||
{.bitmap_index = 5922, .adv_w = 512, .box_w = 25, .box_h = 28, .ofs_x = 4, .ofs_y = 2},
|
||||
{.bitmap_index = 6097, .adv_w = 512, .box_w = 28, .box_h = 28, .ofs_x = 2, .ofs_y = 2}
|
||||
};
|
||||
|
||||
/*---------------------
|
||||
@ -954,12 +971,12 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
|
||||
*--------------------*/
|
||||
|
||||
static const uint16_t unicode_list_0[] = {
|
||||
0x0, 0x14, 0x9b, 0x148, 0x182, 0x1ca, 0x1d1, 0x1e1,
|
||||
0x2af, 0x2c0, 0x418, 0x47e, 0x485, 0x488, 0x48d, 0x48f,
|
||||
0x490, 0x4f9, 0x600, 0x62b, 0x74f, 0x757, 0x76a, 0x771,
|
||||
0x773, 0x7b8, 0x7ca, 0x7e9, 0x874, 0x8af, 0x8b2, 0xa52,
|
||||
0xa87, 0xad7, 0xdfb, 0xe05, 0xea1, 0xee3, 0xf4b, 0x1017,
|
||||
0x15db, 0x1782
|
||||
0x0, 0x14, 0x62, 0x9b, 0x148, 0x182, 0x1ca, 0x1d1,
|
||||
0x1e1, 0x2af, 0x2c0, 0x418, 0x47e, 0x485, 0x488, 0x48d,
|
||||
0x48f, 0x490, 0x4f9, 0x600, 0x62b, 0x74f, 0x757, 0x76a,
|
||||
0x771, 0x773, 0x7b8, 0x7ca, 0x7e9, 0x874, 0x8af, 0x8b2,
|
||||
0xa52, 0xa87, 0xad7, 0xdfb, 0xe05, 0xea1, 0xee3, 0xf4b,
|
||||
0x1017, 0x15db, 0x1782
|
||||
};
|
||||
|
||||
/*Collect the unicode lists and glyph_id offsets*/
|
||||
@ -967,7 +984,7 @@ static const lv_font_fmt_txt_cmap_t cmaps[] =
|
||||
{
|
||||
{
|
||||
.range_start = 57669, .range_length = 6019, .glyph_id_start = 1,
|
||||
.unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 42, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
|
||||
.unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 43, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*******************************************************************************
|
||||
* Size: 12 px
|
||||
* Bpp: 2
|
||||
* Opts: --no-compress --no-prefilter --bpp 2 --size 12 --font MaterialSymbolsRounded.ttf -r 0xF1DB,0xF15C,0xE322,0xE623,0xF057,0xF0B0,0xEBE4,0xEBD6,0xEBE1,0xF065,0xE1DA,0xF064,0xF257,0xF256,0xF255,0xF254,0xF253,0xF252,0xF24F,0xF250 --format lvgl -o ../source-fonts/material_symbols_statusbar_12.c --force-fast-kern-format
|
||||
* Opts: --no-compress --no-prefilter --bpp 2 --size 12 --font MaterialSymbolsRounded.ttf -r 0xF1DB,0xF15C,0xE322,0xE623,0xF057,0xF0B0,0xEBE4,0xEBD6,0xEBE1,0xF065,0xE1DA,0xF064,0xF257,0xF256,0xF255,0xF254,0xF253,0xF252,0xF24F,0xF250,0xE1A7,0xE60F,0xE1A8,0xE1A9 --format lvgl -o ../source-fonts/material_symbols_statusbar_12.c --force-fast-kern-format
|
||||
******************************************************************************/
|
||||
|
||||
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
|
||||
@ -22,6 +22,23 @@
|
||||
|
||||
/*Store the image of the glyphs*/
|
||||
static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
|
||||
/* U+E1A7 "" */
|
||||
0x1, 0x40, 0xb, 0x47, 0x2b, 0x7, 0xec, 0x7,
|
||||
0xc0, 0x1f, 0x1, 0xfb, 0x1c, 0x9d, 0x2, 0xd0,
|
||||
0x9, 0x0,
|
||||
|
||||
/* U+E1A8 "" */
|
||||
0x0, 0x20, 0x0, 0x7, 0x80, 0xd, 0x7a, 0x0,
|
||||
0x3b, 0xd0, 0x20, 0xf4, 0x82, 0xf, 0x48, 0x3,
|
||||
0xbd, 0x0, 0xd6, 0xa0, 0x0, 0x7c, 0x0, 0x2,
|
||||
0x0,
|
||||
|
||||
/* U+E1A9 "" */
|
||||
0x0, 0x14, 0x0, 0xd0, 0xb4, 0x0, 0xd2, 0xb0,
|
||||
0x0, 0xd6, 0xc0, 0x0, 0xd4, 0x0, 0x2, 0xd0,
|
||||
0x0, 0x1f, 0xd0, 0x1, 0xca, 0xd0, 0x0, 0x2e,
|
||||
0xd0, 0x0, 0x90, 0xc0, 0x0, 0x0, 0x0,
|
||||
|
||||
/* U+E1DA "" */
|
||||
0x50, 0x0, 0x0, 0x38, 0xff, 0x80, 0x2e, 0x0,
|
||||
0x78, 0xa3, 0x80, 0xe, 0x34, 0xe0, 0x1c, 0xd,
|
||||
@ -35,6 +52,12 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
|
||||
0xa3, 0x82, 0x55, 0x70, 0x1b, 0xfe, 0x0, 0x11,
|
||||
0x0,
|
||||
|
||||
/* U+E60F "" */
|
||||
0x1, 0x40, 0x0, 0x2d, 0x0, 0x72, 0xb0, 0x1,
|
||||
0xfb, 0xc, 0x7, 0xc8, 0x80, 0x7c, 0x88, 0x1f,
|
||||
0xb0, 0xc7, 0x27, 0x40, 0x2, 0xd0, 0x0, 0x24,
|
||||
0x0,
|
||||
|
||||
/* U+E623 "" */
|
||||
0x7, 0xff, 0x1c, 0x3, 0x70, 0x43, 0xc5, 0xd7,
|
||||
0xc0, 0x43, 0xc0, 0x3, 0xc0, 0x3, 0xc0, 0x3,
|
||||
@ -139,26 +162,30 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
|
||||
|
||||
static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
|
||||
{.bitmap_index = 0, .adv_w = 0, .box_w = 0, .box_h = 0, .ofs_x = 0, .ofs_y = 0} /* id = 0 reserved */,
|
||||
{.bitmap_index = 0, .adv_w = 192, .box_w = 12, .box_h = 11, .ofs_x = 0, .ofs_y = 0},
|
||||
{.bitmap_index = 33, .adv_w = 192, .box_w = 10, .box_h = 10, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 58, .adv_w = 192, .box_w = 8, .box_h = 10, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 78, .adv_w = 192, .box_w = 12, .box_h = 9, .ofs_x = 0, .ofs_y = 1},
|
||||
{.bitmap_index = 105, .adv_w = 192, .box_w = 12, .box_h = 9, .ofs_x = 0, .ofs_y = 1},
|
||||
{.bitmap_index = 132, .adv_w = 192, .box_w = 12, .box_h = 9, .ofs_x = 0, .ofs_y = 1},
|
||||
{.bitmap_index = 159, .adv_w = 192, .box_w = 8, .box_h = 10, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 179, .adv_w = 192, .box_w = 12, .box_h = 9, .ofs_x = 0, .ofs_y = 1},
|
||||
{.bitmap_index = 206, .adv_w = 192, .box_w = 12, .box_h = 9, .ofs_x = 0, .ofs_y = 1},
|
||||
{.bitmap_index = 233, .adv_w = 192, .box_w = 12, .box_h = 9, .ofs_x = 0, .ofs_y = 1},
|
||||
{.bitmap_index = 260, .adv_w = 192, .box_w = 12, .box_h = 8, .ofs_x = 0, .ofs_y = 2},
|
||||
{.bitmap_index = 284, .adv_w = 192, .box_w = 8, .box_h = 10, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 304, .adv_w = 192, .box_w = 12, .box_h = 6, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 322, .adv_w = 192, .box_w = 12, .box_h = 6, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 340, .adv_w = 192, .box_w = 12, .box_h = 6, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 358, .adv_w = 192, .box_w = 12, .box_h = 6, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 376, .adv_w = 192, .box_w = 12, .box_h = 6, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 394, .adv_w = 192, .box_w = 12, .box_h = 6, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 412, .adv_w = 192, .box_w = 12, .box_h = 6, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 430, .adv_w = 192, .box_w = 12, .box_h = 6, .ofs_x = 0, .ofs_y = 3}
|
||||
{.bitmap_index = 0, .adv_w = 192, .box_w = 7, .box_h = 10, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 18, .adv_w = 192, .box_w = 10, .box_h = 10, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 43, .adv_w = 192, .box_w = 11, .box_h = 11, .ofs_x = 0, .ofs_y = 0},
|
||||
{.bitmap_index = 74, .adv_w = 192, .box_w = 12, .box_h = 11, .ofs_x = 0, .ofs_y = 0},
|
||||
{.bitmap_index = 107, .adv_w = 192, .box_w = 10, .box_h = 10, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 132, .adv_w = 192, .box_w = 10, .box_h = 10, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 157, .adv_w = 192, .box_w = 8, .box_h = 10, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 177, .adv_w = 192, .box_w = 12, .box_h = 9, .ofs_x = 0, .ofs_y = 1},
|
||||
{.bitmap_index = 204, .adv_w = 192, .box_w = 12, .box_h = 9, .ofs_x = 0, .ofs_y = 1},
|
||||
{.bitmap_index = 231, .adv_w = 192, .box_w = 12, .box_h = 9, .ofs_x = 0, .ofs_y = 1},
|
||||
{.bitmap_index = 258, .adv_w = 192, .box_w = 8, .box_h = 10, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 278, .adv_w = 192, .box_w = 12, .box_h = 9, .ofs_x = 0, .ofs_y = 1},
|
||||
{.bitmap_index = 305, .adv_w = 192, .box_w = 12, .box_h = 9, .ofs_x = 0, .ofs_y = 1},
|
||||
{.bitmap_index = 332, .adv_w = 192, .box_w = 12, .box_h = 9, .ofs_x = 0, .ofs_y = 1},
|
||||
{.bitmap_index = 359, .adv_w = 192, .box_w = 12, .box_h = 8, .ofs_x = 0, .ofs_y = 2},
|
||||
{.bitmap_index = 383, .adv_w = 192, .box_w = 8, .box_h = 10, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 403, .adv_w = 192, .box_w = 12, .box_h = 6, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 421, .adv_w = 192, .box_w = 12, .box_h = 6, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 439, .adv_w = 192, .box_w = 12, .box_h = 6, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 457, .adv_w = 192, .box_w = 12, .box_h = 6, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 475, .adv_w = 192, .box_w = 12, .box_h = 6, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 493, .adv_w = 192, .box_w = 12, .box_h = 6, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 511, .adv_w = 192, .box_w = 12, .box_h = 6, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 529, .adv_w = 192, .box_w = 12, .box_h = 6, .ofs_x = 0, .ofs_y = 3}
|
||||
};
|
||||
|
||||
/*---------------------
|
||||
@ -166,17 +193,17 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
|
||||
*--------------------*/
|
||||
|
||||
static const uint16_t unicode_list_0[] = {
|
||||
0x0, 0x148, 0x449, 0x9fc, 0xa07, 0xa0a, 0xe7d, 0xe8a,
|
||||
0xe8b, 0xed6, 0xf82, 0x1001, 0x1075, 0x1076, 0x1078, 0x1079,
|
||||
0x107a, 0x107b, 0x107c, 0x107d
|
||||
0x0, 0x1, 0x2, 0x33, 0x17b, 0x468, 0x47c, 0xa2f,
|
||||
0xa3a, 0xa3d, 0xeb0, 0xebd, 0xebe, 0xf09, 0xfb5, 0x1034,
|
||||
0x10a8, 0x10a9, 0x10ab, 0x10ac, 0x10ad, 0x10ae, 0x10af, 0x10b0
|
||||
};
|
||||
|
||||
/*Collect the unicode lists and glyph_id offsets*/
|
||||
static const lv_font_fmt_txt_cmap_t cmaps[] =
|
||||
{
|
||||
{
|
||||
.range_start = 57818, .range_length = 4222, .glyph_id_start = 1,
|
||||
.unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 20, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
|
||||
.range_start = 57767, .range_length = 4273, .glyph_id_start = 1,
|
||||
.unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 24, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*******************************************************************************
|
||||
* Size: 16 px
|
||||
* Bpp: 2
|
||||
* Opts: --no-compress --no-prefilter --bpp 2 --size 16 --font MaterialSymbolsRounded.ttf -r 0xF1DB,0xF15C,0xE322,0xE623,0xF057,0xF0B0,0xEBE4,0xEBD6,0xEBE1,0xF065,0xE1DA,0xF064,0xF257,0xF256,0xF255,0xF254,0xF253,0xF252,0xF24F,0xF250 --format lvgl -o ../source-fonts/material_symbols_statusbar_16.c --force-fast-kern-format
|
||||
* Opts: --no-compress --no-prefilter --bpp 2 --size 16 --font MaterialSymbolsRounded.ttf -r 0xF1DB,0xF15C,0xE322,0xE623,0xF057,0xF0B0,0xEBE4,0xEBD6,0xEBE1,0xF065,0xE1DA,0xF064,0xF257,0xF256,0xF255,0xF254,0xF253,0xF252,0xF24F,0xF250,0xE1A7,0xE60F,0xE1A8,0xE1A9 --format lvgl -o ../source-fonts/material_symbols_statusbar_16.c --force-fast-kern-format
|
||||
******************************************************************************/
|
||||
|
||||
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
|
||||
@ -22,6 +22,28 @@
|
||||
|
||||
/*Store the image of the glyphs*/
|
||||
static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
|
||||
/* U+E1A7 "" */
|
||||
0x0, 0x40, 0x0, 0x3c, 0x1, 0xf, 0xc1, 0xd3,
|
||||
0x6c, 0x1d, 0xef, 0x1, 0xff, 0x0, 0x1f, 0x0,
|
||||
0xf, 0xd0, 0xf, 0xfd, 0xf, 0x36, 0xcb, 0xe,
|
||||
0xe0, 0x3, 0xe0, 0x0, 0xe0, 0x0, 0x0, 0x0,
|
||||
|
||||
/* U+E1A8 "" */
|
||||
0x0, 0x10, 0x0, 0x0, 0x3c, 0x0, 0x14, 0x3f,
|
||||
0x0, 0x1d, 0x36, 0xc0, 0x7, 0x77, 0xc0, 0x1,
|
||||
0xff, 0x0, 0xb0, 0xbc, 0xe, 0x60, 0xfd, 0x9,
|
||||
0x3, 0xff, 0x40, 0xf, 0x36, 0xc0, 0x2c, 0x3b,
|
||||
0x80, 0x0, 0x3e, 0x0, 0x0, 0x38, 0x0, 0x0,
|
||||
0x0, 0x0,
|
||||
|
||||
/* U+E1A9 "" */
|
||||
0x0, 0x4, 0x0, 0x38, 0x3, 0xc0, 0x7, 0x80,
|
||||
0xfc, 0x0, 0x78, 0x36, 0xc0, 0x7, 0x8a, 0xf0,
|
||||
0x0, 0x78, 0xb0, 0x0, 0x7, 0x80, 0x0, 0x0,
|
||||
0xf8, 0x0, 0x0, 0xff, 0x80, 0x0, 0xf3, 0xf8,
|
||||
0x0, 0xb0, 0xef, 0x80, 0x0, 0x3e, 0x78, 0x0,
|
||||
0xe, 0x7, 0x40, 0x0, 0x0, 0x40,
|
||||
|
||||
/* U+E1DA "" */
|
||||
0x0, 0x0, 0x0, 0x0, 0x34, 0x1, 0x40, 0x0,
|
||||
0x1d, 0x2f, 0xfe, 0x40, 0x1f, 0x40, 0x6, 0xf0,
|
||||
@ -40,6 +62,14 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
|
||||
0x40, 0x7f, 0xff, 0xc0, 0x0, 0xca, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0,
|
||||
|
||||
/* U+E60F "" */
|
||||
0x0, 0x40, 0x0, 0x0, 0xf0, 0x0, 0x50, 0xfc,
|
||||
0x0, 0x74, 0xdb, 0x0, 0x2d, 0xdf, 0x9, 0xb,
|
||||
0xfc, 0x7, 0x2, 0xf0, 0xd3, 0x3, 0xf4, 0xd3,
|
||||
0xf, 0xfd, 0x7, 0x3c, 0xdb, 0x9, 0xb0, 0xee,
|
||||
0x0, 0x0, 0xf8, 0x0, 0x0, 0xe0, 0x0, 0x0,
|
||||
0x0, 0x0,
|
||||
|
||||
/* U+E623 "" */
|
||||
0x0, 0x15, 0x54, 0x1, 0xff, 0xfc, 0x7, 0x80,
|
||||
0xd, 0x1e, 0x0, 0xd, 0x38, 0xdd, 0xdd, 0x70,
|
||||
@ -176,26 +206,30 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
|
||||
|
||||
static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
|
||||
{.bitmap_index = 0, .adv_w = 0, .box_w = 0, .box_h = 0, .ofs_x = 0, .ofs_y = 0} /* id = 0 reserved */,
|
||||
{.bitmap_index = 0, .adv_w = 256, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = 0},
|
||||
{.bitmap_index = 60, .adv_w = 256, .box_w = 13, .box_h = 13, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 103, .adv_w = 256, .box_w = 12, .box_h = 14, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 145, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 2},
|
||||
{.bitmap_index = 193, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 2},
|
||||
{.bitmap_index = 241, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 2},
|
||||
{.bitmap_index = 289, .adv_w = 256, .box_w = 12, .box_h = 14, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 331, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 2},
|
||||
{.bitmap_index = 379, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 2},
|
||||
{.bitmap_index = 427, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 2},
|
||||
{.bitmap_index = 475, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 2},
|
||||
{.bitmap_index = 523, .adv_w = 256, .box_w = 12, .box_h = 14, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 565, .adv_w = 256, .box_w = 16, .box_h = 8, .ofs_x = 0, .ofs_y = 4},
|
||||
{.bitmap_index = 597, .adv_w = 256, .box_w = 16, .box_h = 8, .ofs_x = 0, .ofs_y = 4},
|
||||
{.bitmap_index = 629, .adv_w = 256, .box_w = 16, .box_h = 8, .ofs_x = 0, .ofs_y = 4},
|
||||
{.bitmap_index = 661, .adv_w = 256, .box_w = 16, .box_h = 8, .ofs_x = 0, .ofs_y = 4},
|
||||
{.bitmap_index = 693, .adv_w = 256, .box_w = 16, .box_h = 8, .ofs_x = 0, .ofs_y = 4},
|
||||
{.bitmap_index = 725, .adv_w = 256, .box_w = 16, .box_h = 8, .ofs_x = 0, .ofs_y = 4},
|
||||
{.bitmap_index = 757, .adv_w = 256, .box_w = 16, .box_h = 8, .ofs_x = 0, .ofs_y = 4},
|
||||
{.bitmap_index = 789, .adv_w = 256, .box_w = 16, .box_h = 8, .ofs_x = 0, .ofs_y = 4}
|
||||
{.bitmap_index = 0, .adv_w = 256, .box_w = 9, .box_h = 14, .ofs_x = 3, .ofs_y = 1},
|
||||
{.bitmap_index = 32, .adv_w = 256, .box_w = 12, .box_h = 14, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 74, .adv_w = 256, .box_w = 13, .box_h = 14, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 120, .adv_w = 256, .box_w = 16, .box_h = 15, .ofs_x = 0, .ofs_y = 0},
|
||||
{.bitmap_index = 180, .adv_w = 256, .box_w = 13, .box_h = 13, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 223, .adv_w = 256, .box_w = 12, .box_h = 14, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 265, .adv_w = 256, .box_w = 12, .box_h = 14, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 307, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 2},
|
||||
{.bitmap_index = 355, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 2},
|
||||
{.bitmap_index = 403, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 2},
|
||||
{.bitmap_index = 451, .adv_w = 256, .box_w = 12, .box_h = 14, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 493, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 2},
|
||||
{.bitmap_index = 541, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 2},
|
||||
{.bitmap_index = 589, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 2},
|
||||
{.bitmap_index = 637, .adv_w = 256, .box_w = 16, .box_h = 12, .ofs_x = 0, .ofs_y = 2},
|
||||
{.bitmap_index = 685, .adv_w = 256, .box_w = 12, .box_h = 14, .ofs_x = 2, .ofs_y = 1},
|
||||
{.bitmap_index = 727, .adv_w = 256, .box_w = 16, .box_h = 8, .ofs_x = 0, .ofs_y = 4},
|
||||
{.bitmap_index = 759, .adv_w = 256, .box_w = 16, .box_h = 8, .ofs_x = 0, .ofs_y = 4},
|
||||
{.bitmap_index = 791, .adv_w = 256, .box_w = 16, .box_h = 8, .ofs_x = 0, .ofs_y = 4},
|
||||
{.bitmap_index = 823, .adv_w = 256, .box_w = 16, .box_h = 8, .ofs_x = 0, .ofs_y = 4},
|
||||
{.bitmap_index = 855, .adv_w = 256, .box_w = 16, .box_h = 8, .ofs_x = 0, .ofs_y = 4},
|
||||
{.bitmap_index = 887, .adv_w = 256, .box_w = 16, .box_h = 8, .ofs_x = 0, .ofs_y = 4},
|
||||
{.bitmap_index = 919, .adv_w = 256, .box_w = 16, .box_h = 8, .ofs_x = 0, .ofs_y = 4},
|
||||
{.bitmap_index = 951, .adv_w = 256, .box_w = 16, .box_h = 8, .ofs_x = 0, .ofs_y = 4}
|
||||
};
|
||||
|
||||
/*---------------------
|
||||
@ -203,17 +237,17 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
|
||||
*--------------------*/
|
||||
|
||||
static const uint16_t unicode_list_0[] = {
|
||||
0x0, 0x148, 0x449, 0x9fc, 0xa07, 0xa0a, 0xe7d, 0xe8a,
|
||||
0xe8b, 0xed6, 0xf82, 0x1001, 0x1075, 0x1076, 0x1078, 0x1079,
|
||||
0x107a, 0x107b, 0x107c, 0x107d
|
||||
0x0, 0x1, 0x2, 0x33, 0x17b, 0x468, 0x47c, 0xa2f,
|
||||
0xa3a, 0xa3d, 0xeb0, 0xebd, 0xebe, 0xf09, 0xfb5, 0x1034,
|
||||
0x10a8, 0x10a9, 0x10ab, 0x10ac, 0x10ad, 0x10ae, 0x10af, 0x10b0
|
||||
};
|
||||
|
||||
/*Collect the unicode lists and glyph_id offsets*/
|
||||
static const lv_font_fmt_txt_cmap_t cmaps[] =
|
||||
{
|
||||
{
|
||||
.range_start = 57818, .range_length = 4222, .glyph_id_start = 1,
|
||||
.unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 20, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
|
||||
.range_start = 57767, .range_length = 4273, .glyph_id_start = 1,
|
||||
.unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 24, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*******************************************************************************
|
||||
* Size: 20 px
|
||||
* Bpp: 2
|
||||
* Opts: --no-compress --no-prefilter --bpp 2 --size 20 --font MaterialSymbolsRounded.ttf -r 0xF1DB,0xF15C,0xE322,0xE623,0xF057,0xF0B0,0xEBE4,0xEBD6,0xEBE1,0xF065,0xE1DA,0xF064,0xF257,0xF256,0xF255,0xF254,0xF253,0xF252,0xF24F,0xF250 --format lvgl -o ../source-fonts/material_symbols_statusbar_20.c --force-fast-kern-format
|
||||
* Opts: --no-compress --no-prefilter --bpp 2 --size 20 --font MaterialSymbolsRounded.ttf -r 0xF1DB,0xF15C,0xE322,0xE623,0xF057,0xF0B0,0xEBE4,0xEBD6,0xEBE1,0xF065,0xE1DA,0xF064,0xF257,0xF256,0xF255,0xF254,0xF253,0xF252,0xF24F,0xF250,0xE1A7,0xE60F,0xE1A8,0xE1A9 --format lvgl -o ../source-fonts/material_symbols_statusbar_20.c --force-fast-kern-format
|
||||
******************************************************************************/
|
||||
|
||||
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
|
||||
@ -22,6 +22,36 @@
|
||||
|
||||
/*Store the image of the glyphs*/
|
||||
static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
|
||||
/* U+E1A7 "" */
|
||||
0x0, 0x28, 0x0, 0x0, 0xf8, 0x1, 0x3, 0xf8,
|
||||
0x1e, 0xe, 0xb8, 0x2e, 0x38, 0xf0, 0x2e, 0xfb,
|
||||
0x80, 0x2f, 0xf8, 0x0, 0x2f, 0x80, 0x0, 0xbe,
|
||||
0x0, 0xb, 0xfe, 0x0, 0xbb, 0xee, 0xb, 0x8e,
|
||||
0x3c, 0x78, 0x3a, 0xe0, 0x40, 0xfe, 0x0, 0x3,
|
||||
0xe0, 0x0, 0xa, 0x0,
|
||||
|
||||
/* U+E1A8 "" */
|
||||
0x0, 0x2, 0x80, 0x0, 0x0, 0x3, 0xe0, 0x0,
|
||||
0x1, 0x3, 0xf8, 0x0, 0x7, 0x83, 0xae, 0x0,
|
||||
0x2, 0xe3, 0x8f, 0x0, 0x0, 0xbb, 0xee, 0x0,
|
||||
0x0, 0x2f, 0xf8, 0x0, 0x3d, 0xb, 0xe0, 0x7c,
|
||||
0x3d, 0xb, 0xe0, 0x7c, 0x0, 0x2f, 0xf8, 0x0,
|
||||
0x0, 0xbb, 0xee, 0x0, 0x2, 0xe3, 0x8f, 0x0,
|
||||
0x7, 0x83, 0xae, 0x0, 0x1, 0x3, 0xf8, 0x0,
|
||||
0x0, 0x3, 0xe0, 0x0, 0x0, 0x2, 0x80, 0x0,
|
||||
|
||||
/* U+E1A9 "" */
|
||||
0x0, 0x0, 0xa0, 0x0, 0x1d, 0x0, 0x3e, 0x0,
|
||||
0x3, 0xd0, 0xf, 0xe0, 0x0, 0x3d, 0x3, 0xae,
|
||||
0x0, 0x3, 0xd0, 0xe3, 0xc0, 0x0, 0x3d, 0x1a,
|
||||
0xe0, 0x0, 0x3, 0xd1, 0xe0, 0x0, 0x0, 0x3d,
|
||||
0x0, 0x0, 0x0, 0x3, 0xd0, 0x0, 0x0, 0x2,
|
||||
0xfd, 0x0, 0x0, 0x2, 0xef, 0xd0, 0x0, 0x2,
|
||||
0xe3, 0xfd, 0x0, 0x1, 0xe0, 0xeb, 0xd0, 0x0,
|
||||
0x10, 0x3f, 0xbd, 0x0, 0x0, 0xf, 0x83, 0xd0,
|
||||
0x0, 0x2, 0x80, 0x3c, 0x0, 0x0, 0x0, 0x1,
|
||||
0x0,
|
||||
|
||||
/* U+E1DA "" */
|
||||
0x34, 0x0, 0x0, 0x0, 0x0, 0x3d, 0x1, 0xaa,
|
||||
0x40, 0x0, 0xf, 0x47, 0xff, 0xfd, 0x0, 0xb,
|
||||
@ -46,6 +76,16 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
|
||||
0xf, 0xaa, 0xaa, 0xe0, 0x7, 0xff, 0xff, 0xc0,
|
||||
0x0, 0x2c, 0x34, 0x0, 0x0, 0x4, 0x20, 0x0,
|
||||
|
||||
/* U+E60F "" */
|
||||
0x0, 0x1c, 0x0, 0x0, 0x0, 0x2f, 0x0, 0x0,
|
||||
0x10, 0x2f, 0xc0, 0x0, 0x3c, 0x2d, 0xf0, 0x0,
|
||||
0x1f, 0x2c, 0xb8, 0x28, 0x7, 0xee, 0xf0, 0x2c,
|
||||
0x1, 0xff, 0xc1, 0xc, 0x0, 0x7f, 0xb, 0xd,
|
||||
0x0, 0x7f, 0xb, 0xd, 0x1, 0xff, 0xc1, 0xc,
|
||||
0x7, 0xee, 0xf0, 0x2c, 0x1f, 0x2c, 0xb8, 0x28,
|
||||
0x3c, 0x2d, 0xf0, 0x0, 0x10, 0x2f, 0xc0, 0x0,
|
||||
0x0, 0x2f, 0x0, 0x0, 0x0, 0x1c, 0x0, 0x0,
|
||||
|
||||
/* U+E623 "" */
|
||||
0x0, 0x1a, 0xaa, 0x40, 0xb, 0xff, 0xfd, 0x2,
|
||||
0xe0, 0x0, 0xe0, 0xb8, 0x0, 0xe, 0x2e, 0x10,
|
||||
@ -232,26 +272,30 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
|
||||
|
||||
static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
|
||||
{.bitmap_index = 0, .adv_w = 0, .box_w = 0, .box_h = 0, .ofs_x = 0, .ofs_y = 0} /* id = 0 reserved */,
|
||||
{.bitmap_index = 0, .adv_w = 320, .box_w = 20, .box_h = 18, .ofs_x = 0, .ofs_y = 0},
|
||||
{.bitmap_index = 90, .adv_w = 320, .box_w = 16, .box_h = 16, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 154, .adv_w = 320, .box_w = 14, .box_h = 18, .ofs_x = 3, .ofs_y = 1},
|
||||
{.bitmap_index = 217, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 287, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 357, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 427, .adv_w = 320, .box_w = 14, .box_h = 18, .ofs_x = 3, .ofs_y = 1},
|
||||
{.bitmap_index = 490, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 560, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 630, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 700, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 770, .adv_w = 320, .box_w = 14, .box_h = 17, .ofs_x = 3, .ofs_y = 2},
|
||||
{.bitmap_index = 830, .adv_w = 320, .box_w = 20, .box_h = 10, .ofs_x = 0, .ofs_y = 5},
|
||||
{.bitmap_index = 880, .adv_w = 320, .box_w = 19, .box_h = 10, .ofs_x = 0, .ofs_y = 5},
|
||||
{.bitmap_index = 928, .adv_w = 320, .box_w = 20, .box_h = 10, .ofs_x = 0, .ofs_y = 5},
|
||||
{.bitmap_index = 978, .adv_w = 320, .box_w = 20, .box_h = 10, .ofs_x = 0, .ofs_y = 5},
|
||||
{.bitmap_index = 1028, .adv_w = 320, .box_w = 20, .box_h = 10, .ofs_x = 0, .ofs_y = 5},
|
||||
{.bitmap_index = 1078, .adv_w = 320, .box_w = 20, .box_h = 10, .ofs_x = 0, .ofs_y = 5},
|
||||
{.bitmap_index = 1128, .adv_w = 320, .box_w = 20, .box_h = 10, .ofs_x = 0, .ofs_y = 5},
|
||||
{.bitmap_index = 1178, .adv_w = 320, .box_w = 20, .box_h = 10, .ofs_x = 0, .ofs_y = 5}
|
||||
{.bitmap_index = 0, .adv_w = 320, .box_w = 11, .box_h = 16, .ofs_x = 4, .ofs_y = 2},
|
||||
{.bitmap_index = 44, .adv_w = 320, .box_w = 16, .box_h = 16, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 108, .adv_w = 320, .box_w = 17, .box_h = 17, .ofs_x = 1, .ofs_y = 1},
|
||||
{.bitmap_index = 181, .adv_w = 320, .box_w = 20, .box_h = 18, .ofs_x = 0, .ofs_y = 0},
|
||||
{.bitmap_index = 271, .adv_w = 320, .box_w = 16, .box_h = 16, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 335, .adv_w = 320, .box_w = 16, .box_h = 16, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 399, .adv_w = 320, .box_w = 14, .box_h = 18, .ofs_x = 3, .ofs_y = 1},
|
||||
{.bitmap_index = 462, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 532, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 602, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 672, .adv_w = 320, .box_w = 14, .box_h = 18, .ofs_x = 3, .ofs_y = 1},
|
||||
{.bitmap_index = 735, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 805, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 875, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 945, .adv_w = 320, .box_w = 20, .box_h = 14, .ofs_x = 0, .ofs_y = 3},
|
||||
{.bitmap_index = 1015, .adv_w = 320, .box_w = 14, .box_h = 17, .ofs_x = 3, .ofs_y = 2},
|
||||
{.bitmap_index = 1075, .adv_w = 320, .box_w = 20, .box_h = 10, .ofs_x = 0, .ofs_y = 5},
|
||||
{.bitmap_index = 1125, .adv_w = 320, .box_w = 19, .box_h = 10, .ofs_x = 0, .ofs_y = 5},
|
||||
{.bitmap_index = 1173, .adv_w = 320, .box_w = 20, .box_h = 10, .ofs_x = 0, .ofs_y = 5},
|
||||
{.bitmap_index = 1223, .adv_w = 320, .box_w = 20, .box_h = 10, .ofs_x = 0, .ofs_y = 5},
|
||||
{.bitmap_index = 1273, .adv_w = 320, .box_w = 20, .box_h = 10, .ofs_x = 0, .ofs_y = 5},
|
||||
{.bitmap_index = 1323, .adv_w = 320, .box_w = 20, .box_h = 10, .ofs_x = 0, .ofs_y = 5},
|
||||
{.bitmap_index = 1373, .adv_w = 320, .box_w = 20, .box_h = 10, .ofs_x = 0, .ofs_y = 5},
|
||||
{.bitmap_index = 1423, .adv_w = 320, .box_w = 20, .box_h = 10, .ofs_x = 0, .ofs_y = 5}
|
||||
};
|
||||
|
||||
/*---------------------
|
||||
@ -259,17 +303,17 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
|
||||
*--------------------*/
|
||||
|
||||
static const uint16_t unicode_list_0[] = {
|
||||
0x0, 0x148, 0x449, 0x9fc, 0xa07, 0xa0a, 0xe7d, 0xe8a,
|
||||
0xe8b, 0xed6, 0xf82, 0x1001, 0x1075, 0x1076, 0x1078, 0x1079,
|
||||
0x107a, 0x107b, 0x107c, 0x107d
|
||||
0x0, 0x1, 0x2, 0x33, 0x17b, 0x468, 0x47c, 0xa2f,
|
||||
0xa3a, 0xa3d, 0xeb0, 0xebd, 0xebe, 0xf09, 0xfb5, 0x1034,
|
||||
0x10a8, 0x10a9, 0x10ab, 0x10ac, 0x10ad, 0x10ae, 0x10af, 0x10b0
|
||||
};
|
||||
|
||||
/*Collect the unicode lists and glyph_id offsets*/
|
||||
static const lv_font_fmt_txt_cmap_t cmaps[] =
|
||||
{
|
||||
{
|
||||
.range_start = 57818, .range_length = 4222, .glyph_id_start = 1,
|
||||
.unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 20, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
|
||||
.range_start = 57767, .range_length = 4273, .glyph_id_start = 1,
|
||||
.unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 24, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
/*******************************************************************************
|
||||
* Size: 30 px
|
||||
* Bpp: 2
|
||||
* Opts: --no-compress --no-prefilter --bpp 2 --size 30 --font MaterialSymbolsRounded.ttf -r 0xF1DB,0xF15C,0xE322,0xE623,0xF057,0xF0B0,0xEBE4,0xEBD6,0xEBE1,0xF065,0xE1DA,0xF064,0xF257,0xF256,0xF255,0xF254,0xF253,0xF252,0xF24F,0xF250 --format lvgl -o ../source-fonts/material_symbols_statusbar_30.c --force-fast-kern-format
|
||||
* Opts: --no-compress --no-prefilter --bpp 2 --size 30 --font MaterialSymbolsRounded.ttf -r 0xF1DB,0xF15C,0xE322,0xE623,0xF057,0xF0B0,0xEBE4,0xEBD6,0xEBE1,0xF065,0xE1DA,0xF064,0xF257,0xF256,0xF255,0xF254,0xF253,0xF252,0xF24F,0xF250,0xE1A7,0xE60F,0xE1A8,0xE1A9 --format lvgl -o ../source-fonts/material_symbols_statusbar_30.c --force-fast-kern-format
|
||||
******************************************************************************/
|
||||
|
||||
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
|
||||
@ -22,6 +22,61 @@
|
||||
|
||||
/*Store the image of the glyphs*/
|
||||
static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
|
||||
/* U+E1A7 "" */
|
||||
0x0, 0x0, 0xb0, 0x0, 0x0, 0x0, 0xfc, 0x0,
|
||||
0x0, 0x0, 0xff, 0x0, 0x0, 0x0, 0xff, 0xc0,
|
||||
0x3d, 0x0, 0xff, 0xf0, 0x3f, 0x40, 0xf5, 0xfc,
|
||||
0xf, 0xd0, 0xf4, 0x7e, 0x3, 0xf4, 0xf4, 0xfd,
|
||||
0x0, 0xfe, 0xfb, 0xf4, 0x0, 0x3f, 0xff, 0xd0,
|
||||
0x0, 0xf, 0xff, 0x40, 0x0, 0x3, 0xfd, 0x0,
|
||||
0x0, 0x3, 0xfd, 0x0, 0x0, 0xf, 0xff, 0x40,
|
||||
0x0, 0x3f, 0xff, 0xd0, 0x0, 0xfe, 0xfb, 0xf4,
|
||||
0x3, 0xf4, 0xf4, 0xfd, 0xf, 0xd0, 0xf4, 0x7e,
|
||||
0x3f, 0x40, 0xf5, 0xfc, 0x3d, 0x0, 0xff, 0xf0,
|
||||
0x0, 0x0, 0xff, 0xc0, 0x0, 0x0, 0xff, 0x0,
|
||||
0x0, 0x0, 0xfc, 0x0, 0x0, 0x0, 0xb0, 0x0,
|
||||
|
||||
/* U+E1A8 "" */
|
||||
0x0, 0x0, 0xb, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0xfc, 0x0, 0x0, 0x0, 0x0, 0xf, 0xf0, 0x0,
|
||||
0x0, 0x0, 0x0, 0xff, 0xc0, 0x0, 0x3, 0xd0,
|
||||
0xf, 0xff, 0x0, 0x0, 0x3f, 0x40, 0xf5, 0xfc,
|
||||
0x0, 0x0, 0xfd, 0xf, 0x47, 0xe0, 0x0, 0x3,
|
||||
0xf4, 0xf4, 0xfd, 0x0, 0x0, 0xf, 0xef, 0xbf,
|
||||
0x40, 0x0, 0x0, 0x3f, 0xff, 0xd0, 0x0, 0x2d,
|
||||
0x0, 0xff, 0xf4, 0x7, 0x8b, 0xf0, 0x3, 0xfd,
|
||||
0x0, 0xfe, 0xbf, 0x0, 0x3f, 0xd0, 0xf, 0xe2,
|
||||
0xd0, 0xf, 0xff, 0x40, 0x78, 0x0, 0x3, 0xff,
|
||||
0xfd, 0x0, 0x0, 0x0, 0xfe, 0xfb, 0xf4, 0x0,
|
||||
0x0, 0x3f, 0x4f, 0x4f, 0xd0, 0x0, 0xf, 0xd0,
|
||||
0xf4, 0x7e, 0x0, 0x3, 0xf4, 0xf, 0x5f, 0xc0,
|
||||
0x0, 0x3d, 0x0, 0xff, 0xf0, 0x0, 0x0, 0x0,
|
||||
0xf, 0xfc, 0x0, 0x0, 0x0, 0x0, 0xff, 0x0,
|
||||
0x0, 0x0, 0x0, 0xf, 0xc0, 0x0, 0x0, 0x0,
|
||||
0x0, 0xb0, 0x0, 0x0,
|
||||
|
||||
/* U+E1A9 "" */
|
||||
0x0, 0x0, 0x0, 0xb0, 0x0, 0x0, 0x1e, 0x0,
|
||||
0x0, 0x3f, 0x0, 0x0, 0x7, 0xe0, 0x0, 0xf,
|
||||
0xf0, 0x0, 0x0, 0xfe, 0x0, 0x3, 0xff, 0x0,
|
||||
0x0, 0xf, 0xe0, 0x0, 0xff, 0xf0, 0x0, 0x0,
|
||||
0xfe, 0x0, 0x3d, 0x7f, 0x0, 0x0, 0xf, 0xe0,
|
||||
0xf, 0x47, 0xe0, 0x0, 0x0, 0xfe, 0x3, 0xd3,
|
||||
0xf4, 0x0, 0x0, 0xf, 0xe0, 0x3b, 0xf4, 0x0,
|
||||
0x0, 0x0, 0xfe, 0x3, 0xf4, 0x0, 0x0, 0x0,
|
||||
0xf, 0xe0, 0x34, 0x0, 0x0, 0x0, 0x0, 0xfe,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0xf, 0xe0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x3, 0xfe, 0x0, 0x0, 0x0,
|
||||
0x0, 0x3, 0xff, 0xe0, 0x0, 0x0, 0x0, 0x3,
|
||||
0xfb, 0xfe, 0x0, 0x0, 0x0, 0x3, 0xf4, 0xff,
|
||||
0xe0, 0x0, 0x0, 0x3, 0xf4, 0x3d, 0xfe, 0x0,
|
||||
0x0, 0x3, 0xf4, 0xf, 0x5f, 0xe0, 0x0, 0x0,
|
||||
0xf4, 0x3, 0xff, 0xfe, 0x0, 0x0, 0x0, 0x0,
|
||||
0xff, 0xdf, 0xe0, 0x0, 0x0, 0x0, 0x3f, 0xc0,
|
||||
0xfe, 0x0, 0x0, 0x0, 0xf, 0xc0, 0xf, 0xe0,
|
||||
0x0, 0x0, 0x2, 0xc0, 0x0, 0xfc, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0xa, 0x0,
|
||||
|
||||
/* U+E1DA "" */
|
||||
0x1d, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3,
|
||||
0xf4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf,
|
||||
@ -69,6 +124,26 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
|
||||
0xf8, 0x3e, 0x0, 0x0, 0x0, 0x0, 0xf4, 0x3d,
|
||||
0x0, 0x0, 0x0, 0x0, 0x10, 0x4, 0x0, 0x0,
|
||||
|
||||
/* U+E60F "" */
|
||||
0x0, 0x2, 0xd0, 0x0, 0x0, 0x0, 0x0, 0xf,
|
||||
0xd0, 0x0, 0x0, 0x0, 0x0, 0x3f, 0xd0, 0x0,
|
||||
0x0, 0x0, 0x0, 0xff, 0xd0, 0x0, 0x0, 0xb8,
|
||||
0x3, 0xff, 0xd0, 0x0, 0x2, 0xf8, 0xf, 0x9f,
|
||||
0xd0, 0x0, 0x3, 0xf8, 0x3e, 0x1f, 0xc0, 0x28,
|
||||
0x3, 0xf8, 0xf8, 0xfe, 0x0, 0xf0, 0x3, 0xfb,
|
||||
0xef, 0xe0, 0x2, 0xe0, 0x3, 0xff, 0xfe, 0x0,
|
||||
0x7, 0xc0, 0x3, 0xff, 0xe0, 0x3c, 0xf, 0x0,
|
||||
0x3, 0xfe, 0x3, 0xf0, 0x3c, 0x0, 0xf, 0xf8,
|
||||
0xf, 0xc0, 0xf0, 0x0, 0xff, 0xf8, 0xf, 0x3,
|
||||
0xc0, 0xf, 0xff, 0xf8, 0x0, 0x1f, 0x0, 0xfe,
|
||||
0xfb, 0xf8, 0x0, 0xb4, 0xf, 0xe3, 0xe3, 0xf8,
|
||||
0x3, 0xc0, 0xfe, 0xf, 0x87, 0xf0, 0xa, 0xb,
|
||||
0xe0, 0x3e, 0x3f, 0x40, 0x0, 0x2e, 0x0, 0xff,
|
||||
0xf4, 0x0, 0x0, 0x0, 0x3, 0xff, 0x40, 0x0,
|
||||
0x0, 0x0, 0xf, 0xf4, 0x0, 0x0, 0x0, 0x0,
|
||||
0x3f, 0x40, 0x0, 0x0, 0x0, 0x0, 0xb4, 0x0,
|
||||
0x0, 0x0,
|
||||
|
||||
/* U+E623 "" */
|
||||
0x0, 0x0, 0x6a, 0xaa, 0x90, 0x0, 0x7, 0xff,
|
||||
0xff, 0xfd, 0x0, 0x1f, 0xff, 0xff, 0xff, 0x0,
|
||||
@ -407,26 +482,30 @@ static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
|
||||
|
||||
static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
|
||||
{.bitmap_index = 0, .adv_w = 0, .box_w = 0, .box_h = 0, .ofs_x = 0, .ofs_y = 0} /* id = 0 reserved */,
|
||||
{.bitmap_index = 0, .adv_w = 480, .box_w = 30, .box_h = 26, .ofs_x = 0, .ofs_y = 1},
|
||||
{.bitmap_index = 195, .adv_w = 480, .box_w = 24, .box_h = 24, .ofs_x = 3, .ofs_y = 3},
|
||||
{.bitmap_index = 339, .adv_w = 480, .box_w = 20, .box_h = 26, .ofs_x = 5, .ofs_y = 2},
|
||||
{.bitmap_index = 469, .adv_w = 480, .box_w = 28, .box_h = 21, .ofs_x = 1, .ofs_y = 4},
|
||||
{.bitmap_index = 616, .adv_w = 480, .box_w = 28, .box_h = 21, .ofs_x = 1, .ofs_y = 4},
|
||||
{.bitmap_index = 763, .adv_w = 480, .box_w = 28, .box_h = 21, .ofs_x = 1, .ofs_y = 4},
|
||||
{.bitmap_index = 910, .adv_w = 480, .box_w = 20, .box_h = 26, .ofs_x = 5, .ofs_y = 2},
|
||||
{.bitmap_index = 1040, .adv_w = 480, .box_w = 28, .box_h = 21, .ofs_x = 1, .ofs_y = 4},
|
||||
{.bitmap_index = 1187, .adv_w = 480, .box_w = 28, .box_h = 21, .ofs_x = 1, .ofs_y = 4},
|
||||
{.bitmap_index = 1334, .adv_w = 480, .box_w = 28, .box_h = 21, .ofs_x = 1, .ofs_y = 4},
|
||||
{.bitmap_index = 1481, .adv_w = 480, .box_w = 28, .box_h = 20, .ofs_x = 1, .ofs_y = 5},
|
||||
{.bitmap_index = 1621, .adv_w = 480, .box_w = 20, .box_h = 25, .ofs_x = 5, .ofs_y = 3},
|
||||
{.bitmap_index = 1746, .adv_w = 480, .box_w = 28, .box_h = 16, .ofs_x = 1, .ofs_y = 7},
|
||||
{.bitmap_index = 1858, .adv_w = 480, .box_w = 28, .box_h = 16, .ofs_x = 1, .ofs_y = 7},
|
||||
{.bitmap_index = 1970, .adv_w = 480, .box_w = 28, .box_h = 16, .ofs_x = 1, .ofs_y = 7},
|
||||
{.bitmap_index = 2082, .adv_w = 480, .box_w = 28, .box_h = 16, .ofs_x = 1, .ofs_y = 7},
|
||||
{.bitmap_index = 2194, .adv_w = 480, .box_w = 28, .box_h = 16, .ofs_x = 1, .ofs_y = 7},
|
||||
{.bitmap_index = 2306, .adv_w = 480, .box_w = 28, .box_h = 16, .ofs_x = 1, .ofs_y = 7},
|
||||
{.bitmap_index = 2418, .adv_w = 480, .box_w = 28, .box_h = 16, .ofs_x = 1, .ofs_y = 7},
|
||||
{.bitmap_index = 2530, .adv_w = 480, .box_w = 28, .box_h = 16, .ofs_x = 1, .ofs_y = 7}
|
||||
{.bitmap_index = 0, .adv_w = 480, .box_w = 16, .box_h = 24, .ofs_x = 6, .ofs_y = 3},
|
||||
{.bitmap_index = 96, .adv_w = 480, .box_w = 22, .box_h = 24, .ofs_x = 4, .ofs_y = 3},
|
||||
{.bitmap_index = 228, .adv_w = 480, .box_w = 25, .box_h = 25, .ofs_x = 2, .ofs_y = 2},
|
||||
{.bitmap_index = 385, .adv_w = 480, .box_w = 30, .box_h = 26, .ofs_x = 0, .ofs_y = 1},
|
||||
{.bitmap_index = 580, .adv_w = 480, .box_w = 24, .box_h = 24, .ofs_x = 3, .ofs_y = 3},
|
||||
{.bitmap_index = 724, .adv_w = 480, .box_w = 23, .box_h = 24, .ofs_x = 4, .ofs_y = 3},
|
||||
{.bitmap_index = 862, .adv_w = 480, .box_w = 20, .box_h = 26, .ofs_x = 5, .ofs_y = 2},
|
||||
{.bitmap_index = 992, .adv_w = 480, .box_w = 28, .box_h = 21, .ofs_x = 1, .ofs_y = 4},
|
||||
{.bitmap_index = 1139, .adv_w = 480, .box_w = 28, .box_h = 21, .ofs_x = 1, .ofs_y = 4},
|
||||
{.bitmap_index = 1286, .adv_w = 480, .box_w = 28, .box_h = 21, .ofs_x = 1, .ofs_y = 4},
|
||||
{.bitmap_index = 1433, .adv_w = 480, .box_w = 20, .box_h = 26, .ofs_x = 5, .ofs_y = 2},
|
||||
{.bitmap_index = 1563, .adv_w = 480, .box_w = 28, .box_h = 21, .ofs_x = 1, .ofs_y = 4},
|
||||
{.bitmap_index = 1710, .adv_w = 480, .box_w = 28, .box_h = 21, .ofs_x = 1, .ofs_y = 4},
|
||||
{.bitmap_index = 1857, .adv_w = 480, .box_w = 28, .box_h = 21, .ofs_x = 1, .ofs_y = 4},
|
||||
{.bitmap_index = 2004, .adv_w = 480, .box_w = 28, .box_h = 20, .ofs_x = 1, .ofs_y = 5},
|
||||
{.bitmap_index = 2144, .adv_w = 480, .box_w = 20, .box_h = 25, .ofs_x = 5, .ofs_y = 3},
|
||||
{.bitmap_index = 2269, .adv_w = 480, .box_w = 28, .box_h = 16, .ofs_x = 1, .ofs_y = 7},
|
||||
{.bitmap_index = 2381, .adv_w = 480, .box_w = 28, .box_h = 16, .ofs_x = 1, .ofs_y = 7},
|
||||
{.bitmap_index = 2493, .adv_w = 480, .box_w = 28, .box_h = 16, .ofs_x = 1, .ofs_y = 7},
|
||||
{.bitmap_index = 2605, .adv_w = 480, .box_w = 28, .box_h = 16, .ofs_x = 1, .ofs_y = 7},
|
||||
{.bitmap_index = 2717, .adv_w = 480, .box_w = 28, .box_h = 16, .ofs_x = 1, .ofs_y = 7},
|
||||
{.bitmap_index = 2829, .adv_w = 480, .box_w = 28, .box_h = 16, .ofs_x = 1, .ofs_y = 7},
|
||||
{.bitmap_index = 2941, .adv_w = 480, .box_w = 28, .box_h = 16, .ofs_x = 1, .ofs_y = 7},
|
||||
{.bitmap_index = 3053, .adv_w = 480, .box_w = 28, .box_h = 16, .ofs_x = 1, .ofs_y = 7}
|
||||
};
|
||||
|
||||
/*---------------------
|
||||
@ -434,17 +513,17 @@ static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
|
||||
*--------------------*/
|
||||
|
||||
static const uint16_t unicode_list_0[] = {
|
||||
0x0, 0x148, 0x449, 0x9fc, 0xa07, 0xa0a, 0xe7d, 0xe8a,
|
||||
0xe8b, 0xed6, 0xf82, 0x1001, 0x1075, 0x1076, 0x1078, 0x1079,
|
||||
0x107a, 0x107b, 0x107c, 0x107d
|
||||
0x0, 0x1, 0x2, 0x33, 0x17b, 0x468, 0x47c, 0xa2f,
|
||||
0xa3a, 0xa3d, 0xeb0, 0xebd, 0xebe, 0xf09, 0xfb5, 0x1034,
|
||||
0x10a8, 0x10a9, 0x10ab, 0x10ac, 0x10ad, 0x10ae, 0x10af, 0x10b0
|
||||
};
|
||||
|
||||
/*Collect the unicode lists and glyph_id offsets*/
|
||||
static const lv_font_fmt_txt_cmap_t cmaps[] =
|
||||
{
|
||||
{
|
||||
.range_start = 57818, .range_length = 4222, .glyph_id_start = 1,
|
||||
.unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 20, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
|
||||
.range_start = 57767, .range_length = 4273, .glyph_id_start = 1,
|
||||
.unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 24, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -359,6 +359,7 @@ const struct ModuleSymbol lvgl_module_symbols[] = {
|
||||
DEFINE_MODULE_SYMBOL(lv_indev_active),
|
||||
DEFINE_MODULE_SYMBOL(lv_indev_get_next),
|
||||
DEFINE_MODULE_SYMBOL(lv_indev_set_group),
|
||||
DEFINE_MODULE_SYMBOL(lv_indev_wait_release),
|
||||
// lv_timer
|
||||
DEFINE_MODULE_SYMBOL(lv_timer_handler),
|
||||
DEFINE_MODULE_SYMBOL(lv_timer_handler_run_in_period),
|
||||
@ -400,6 +401,8 @@ const struct ModuleSymbol lvgl_module_symbols[] = {
|
||||
DEFINE_MODULE_SYMBOL(lv_tabview_create),
|
||||
DEFINE_MODULE_SYMBOL(lv_tabview_set_tab_bar_position),
|
||||
DEFINE_MODULE_SYMBOL(lv_tabview_set_tab_bar_size),
|
||||
DEFINE_MODULE_SYMBOL(lv_tabview_get_tab_active),
|
||||
DEFINE_MODULE_SYMBOL(lv_tabview_get_content),
|
||||
// lv_screen
|
||||
DEFINE_MODULE_SYMBOL(lv_scr_act),
|
||||
DEFINE_MODULE_SYMBOL(lv_screen_active),
|
||||
@ -456,6 +459,9 @@ const struct ModuleSymbol lvgl_module_symbols[] = {
|
||||
// lv_draw_line
|
||||
DEFINE_MODULE_SYMBOL(lv_draw_line),
|
||||
DEFINE_MODULE_SYMBOL(lv_draw_line_dsc_init),
|
||||
// lv_draw_rect
|
||||
DEFINE_MODULE_SYMBOL(lv_draw_fill),
|
||||
DEFINE_MODULE_SYMBOL(lv_draw_fill_dsc_init),
|
||||
// lv_area
|
||||
DEFINE_MODULE_SYMBOL(lv_area_get_width),
|
||||
DEFINE_MODULE_SYMBOL(lv_area_get_height),
|
||||
|
||||
@ -8,3 +8,5 @@ idf_component_register(
|
||||
PRIV_INCLUDE_DIRS "private/"
|
||||
REQUIRES TactilityKernel driver vfs fatfs
|
||||
)
|
||||
|
||||
idf_component_optional_requires(PRIVATE bt)
|
||||
|
||||
@ -0,0 +1,8 @@
|
||||
description: ESP32 BLE driver (NimBLE)
|
||||
|
||||
compatible: "espressif,esp32-ble"
|
||||
|
||||
properties:
|
||||
_unused:
|
||||
type: int
|
||||
default: 0
|
||||
@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <tactility/bindings/bindings.h>
|
||||
#include <tactility/drivers/esp32_ble.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
DEFINE_DEVICETREE(esp32_ble, struct Esp32BleConfig)
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** No device-tree configuration required for the NimBLE BLE driver. */
|
||||
struct Esp32BleConfig {
|
||||
int _unused; /**< Placeholder — driver reads all config from NimBLE Kconfig. */
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
34
Platforms/platform-esp32/private/bluetooth/esp32_ble_hid.h
Normal file
34
Platforms/platform-esp32/private/bluetooth/esp32_ble_hid.h
Normal file
@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
#include <sdkconfig.h>
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_BT_NIMBLE_ENABLED)
|
||||
|
||||
#include <tactility/drivers/bluetooth_hid_device.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
enum BleHidProfile { None, KbConsumer, Mouse, KbMouse, Gamepad };
|
||||
|
||||
struct Device;
|
||||
|
||||
bool ble_hid_get_active(struct Device* device);
|
||||
void ble_hid_set_active(struct Device* device, bool v);
|
||||
uint16_t ble_hid_get_conn_handle(struct Device* device);
|
||||
void ble_hid_set_conn_handle(struct Device* device, uint16_t h);
|
||||
|
||||
// device must be the hid_device child Device*.
|
||||
void ble_hid_init_gatt();
|
||||
bool ble_hid_switch_profile(struct Device* hid_child, BleHidProfile profile);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // CONFIG_BT_NIMBLE_ENABLED
|
||||
170
Platforms/platform-esp32/private/bluetooth/esp32_ble_internal.h
Normal file
170
Platforms/platform-esp32/private/bluetooth/esp32_ble_internal.h
Normal file
@ -0,0 +1,170 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
#include <sdkconfig.h>
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_BT_NIMBLE_ENABLED)
|
||||
|
||||
#include <tactility/drivers/bluetooth.h>
|
||||
#include <tactility/error.h>
|
||||
// Must be included before any NimBLE header: log_common.h (pulled in by ble_hs.h)
|
||||
// defines LOG_LEVEL_* as macros with the same names as tactility/log.h's LogLevel enum.
|
||||
// Including tactility/log.h first ensures the enum is compiled before the macros shadow it.
|
||||
#include <tactility/log.h>
|
||||
|
||||
#include <host/ble_gap.h>
|
||||
#include <host/ble_gatt.h>
|
||||
#include <host/ble_hs.h>
|
||||
#include <host/ble_uuid.h>
|
||||
|
||||
#include <esp_timer.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
// ---- Per-module headers (structs, accessors, sub-API externs) ----
|
||||
|
||||
#include <bluetooth/esp32_ble_spp.h>
|
||||
#include <bluetooth/esp32_ble_midi.h>
|
||||
#include <bluetooth/esp32_ble_hid.h>
|
||||
|
||||
#define BLE_MAX_CALLBACKS 8
|
||||
|
||||
struct BleCallbackEntry {
|
||||
BtEventCallback fn;
|
||||
void* ctx;
|
||||
};
|
||||
|
||||
struct BleCtx {
|
||||
// Mutexes
|
||||
SemaphoreHandle_t radio_mutex; // guards radio state transitions
|
||||
SemaphoreHandle_t cb_mutex; // guards callbacks array
|
||||
|
||||
// Radio / scan state (atomic — read from multiple tasks)
|
||||
std::atomic<BtRadioState> radio_state;
|
||||
std::atomic<bool> scan_active;
|
||||
// Set by Tactility HID host to prevent simultaneous central connection during name resolution
|
||||
std::atomic<bool> hid_host_active;
|
||||
|
||||
// Event callbacks (guarded by cb_mutex)
|
||||
BleCallbackEntry callbacks[BLE_MAX_CALLBACKS];
|
||||
size_t callback_count;
|
||||
|
||||
// Connection handles + active flags (atomic — accessed from multiple tasks)
|
||||
std::atomic<uint16_t> spp_conn_handle;
|
||||
std::atomic<bool> spp_active;
|
||||
std::atomic<uint16_t> midi_conn_handle;
|
||||
std::atomic<bool> midi_active;
|
||||
std::atomic<bool> midi_use_indicate; // true when client subscribed for INDICATE (e.g. Windows)
|
||||
std::atomic<bool> hid_active;
|
||||
std::atomic<bool> link_encrypted;
|
||||
std::atomic<int> pending_reset_count;
|
||||
|
||||
// Timers
|
||||
esp_timer_handle_t midi_keepalive_timer; // 2-second periodic Active Sensing
|
||||
esp_timer_handle_t adv_restart_timer; // one-shot after connect failure (500 ms)
|
||||
// One-shot timer used to dispatch dispatchDisable off the NimBLE host task.
|
||||
// nimble_port_stop() must not be called from the NimBLE host task itself.
|
||||
esp_timer_handle_t disable_timer;
|
||||
|
||||
// BLE device name (set before or after radio enable; applied in dispatch_enable)
|
||||
char device_name[BLE_DEVICE_NAME_MAX + 1];
|
||||
|
||||
// Scan data (guarded by scan_mutex)
|
||||
SemaphoreHandle_t scan_mutex;
|
||||
BtPeerRecord scan_results[64];
|
||||
ble_addr_t scan_addrs[64];
|
||||
size_t scan_count;
|
||||
|
||||
// Device reference (passed to BtEventCallback)
|
||||
struct Device* device;
|
||||
|
||||
// Child devices (created by esp32_ble_start_device, destroyed by stop_device)
|
||||
struct Device* serial_child;
|
||||
struct Device* midi_child;
|
||||
struct Device* hid_device_child;
|
||||
};
|
||||
|
||||
// Always returns the root BLE device's BleCtx regardless of which device is passed.
|
||||
BleCtx* ble_get_ctx(struct Device* device);
|
||||
|
||||
// ---- General field accessors (defined in esp32_ble.cpp) ----
|
||||
|
||||
BtRadioState ble_get_radio_state(struct Device* device);
|
||||
|
||||
bool ble_hid_get_host_active(struct Device* device);
|
||||
|
||||
bool ble_get_scan_active(struct Device* device);
|
||||
void ble_set_scan_active(struct Device* device, bool v);
|
||||
|
||||
// ---- Scan data management (defined in esp32_ble_scan.cpp) ----
|
||||
void ble_scan_clear_results(struct Device* device);
|
||||
|
||||
// ---- Event publishing ----
|
||||
void ble_publish_event(struct Device* device, struct BtEvent event);
|
||||
|
||||
// ---- Advertising helpers (defined in esp32_ble.cpp) ----
|
||||
void ble_start_advertising(struct Device* device, const ble_uuid128_t* svc_uuid); // svc_uuid=nullptr → name-only
|
||||
void ble_start_advertising_hid(struct Device* device, uint16_t appearance);
|
||||
void ble_schedule_adv_restart(struct Device* device, uint64_t delay_us);
|
||||
|
||||
// ---- GAP scan callback (defined in esp32_ble_scan.cpp) ----
|
||||
int ble_gap_disc_event_handler(struct ble_gap_event* event, void* arg);
|
||||
void ble_resolve_next_unnamed_peer(struct Device* device, size_t start_idx);
|
||||
|
||||
// ---- Child driver definitions (one per profile sub-module) ----
|
||||
extern struct Driver esp32_ble_serial_driver;
|
||||
extern struct Driver esp32_ble_midi_driver;
|
||||
extern struct Driver esp32_ble_hid_device_driver;
|
||||
|
||||
// ---- SPP GATT (defined in esp32_ble_spp.cpp) ----
|
||||
// device must be the serial child Device*.
|
||||
void ble_spp_init_gatt_handles(struct Device* serial_child);
|
||||
error_t ble_spp_start_internal(struct Device* serial_child);
|
||||
|
||||
// ---- MIDI GATT (defined in esp32_ble_midi.cpp) ----
|
||||
// device must be the midi child Device*.
|
||||
void ble_midi_init_gatt_handles(struct Device* midi_child);
|
||||
error_t ble_midi_start_internal(struct Device* midi_child);
|
||||
|
||||
// ---- Cross-module GATT char / service arrays ----
|
||||
// Non-const: the .arg field is set to the child Device* at init time so that
|
||||
// NimBLE access callbacks can retrieve the context without a global pointer.
|
||||
extern struct ble_gatt_chr_def nus_chars_with_handle[]; // esp32_ble_spp.cpp
|
||||
extern struct ble_gatt_chr_def midi_chars[]; // esp32_ble_midi.cpp
|
||||
|
||||
// ---- Cross-module service UUIDs ----
|
||||
extern const ble_uuid128_t NUS_SVC_UUID; // esp32_ble_spp.cpp
|
||||
extern const ble_uuid128_t MIDI_SVC_UUID; // esp32_ble_midi.cpp
|
||||
|
||||
// ---- Cross-module GATT handle variables ----
|
||||
extern uint16_t nus_tx_handle; // esp32_ble_spp.cpp
|
||||
extern uint16_t midi_io_handle; // esp32_ble_midi.cpp
|
||||
extern uint16_t hid_kb_input_handle; // esp32_ble_hid.cpp
|
||||
extern uint16_t hid_consumer_input_handle; // esp32_ble_hid.cpp
|
||||
extern uint16_t hid_mouse_input_handle; // esp32_ble_hid.cpp
|
||||
extern uint16_t hid_gamepad_input_handle; // esp32_ble_hid.cpp
|
||||
|
||||
// ---- HID active report map / appearance ----
|
||||
extern const uint8_t* active_hid_rpt_map; // esp32_ble_hid.cpp
|
||||
extern size_t active_hid_rpt_map_len; // esp32_ble_hid.cpp
|
||||
extern uint16_t hid_appearance; // esp32_ble_hid.cpp
|
||||
extern BleHidProfile current_hid_profile; // esp32_ble_hid.cpp
|
||||
|
||||
// ---- HCI gate (ble_hci_gate.c) — P4/esp-hosted only ----
|
||||
// Controls whether hci_rx_handler forwards packets to NimBLE.
|
||||
// Set true after nimble_port_init(), false before nimble_port_stop().
|
||||
#if defined(CONFIG_ESP_HOSTED_ENABLED)
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
void ble_hci_gate_set_active(bool active);
|
||||
bool ble_hci_gate_wait_idle(int max_ms);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif // CONFIG_ESP_HOSTED_ENABLED
|
||||
|
||||
#endif // CONFIG_BT_NIMBLE_ENABLED
|
||||
38
Platforms/platform-esp32/private/bluetooth/esp32_ble_midi.h
Normal file
38
Platforms/platform-esp32/private/bluetooth/esp32_ble_midi.h
Normal file
@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
#include <sdkconfig.h>
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_BT_NIMBLE_ENABLED)
|
||||
|
||||
#include <tactility/drivers/bluetooth_midi.h>
|
||||
#include <tactility/error.h>
|
||||
|
||||
#include <esp_timer.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct Device;
|
||||
|
||||
bool ble_midi_get_active(struct Device* device);
|
||||
void ble_midi_set_active(struct Device* device, bool v);
|
||||
uint16_t ble_midi_get_conn_handle(struct Device* device);
|
||||
void ble_midi_set_conn_handle(struct Device* device, uint16_t h);
|
||||
bool ble_midi_get_use_indicate(struct Device* device);
|
||||
void ble_midi_set_use_indicate(struct Device* device, bool v);
|
||||
|
||||
// MIDI keepalive timer helpers — timer handle lives in BleCtx.
|
||||
// ble_midi_ensure_keepalive creates the timer if needed and starts it periodically.
|
||||
// ble_midi_stop_keepalive stops (but does not delete) the timer.
|
||||
error_t ble_midi_ensure_keepalive(struct Device* device, esp_timer_cb_t cb, uint64_t period_us);
|
||||
void ble_midi_stop_keepalive(struct Device* device);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // CONFIG_BT_NIMBLE_ENABLED
|
||||
27
Platforms/platform-esp32/private/bluetooth/esp32_ble_spp.h
Normal file
27
Platforms/platform-esp32/private/bluetooth/esp32_ble_spp.h
Normal file
@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
#include <sdkconfig.h>
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_BT_NIMBLE_ENABLED)
|
||||
|
||||
#include <tactility/drivers/bluetooth_serial.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct Device;
|
||||
|
||||
bool ble_spp_get_active(struct Device* device);
|
||||
void ble_spp_set_active(struct Device* device, bool v);
|
||||
uint16_t ble_spp_get_conn_handle(struct Device* device);
|
||||
void ble_spp_set_conn_handle(struct Device* device, uint16_t h);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // CONFIG_BT_NIMBLE_ENABLED
|
||||
142
Platforms/platform-esp32/source/drivers/bluetooth/ble_hci_gate.c
Normal file
142
Platforms/platform-esp32/source/drivers/bluetooth/ble_hci_gate.c
Normal file
@ -0,0 +1,142 @@
|
||||
// ble_hci_gate.c — Strong override of the weak hci_rx_handler from vhci_drv.c.
|
||||
// Gates HCI packet delivery so packets are silently dropped when NimBLE is not
|
||||
// initialised, preventing null-npl_funcs crashes during radio teardown (P4 only).
|
||||
//
|
||||
// On ESP32-P4, sdio_process_rx_task runs independently of the NimBLE host and can
|
||||
// deliver an HCI packet after nimble_port_deinit() has zeroed npl_funcs.
|
||||
// dispatch_disable() calls ble_hci_gate_set_active(false) + ble_hci_gate_wait_idle()
|
||||
// before touching NimBLE, ensuring no in-flight or future hci_rx_handler call can
|
||||
// dereference NimBLE internals.
|
||||
|
||||
#include <sdkconfig.h>
|
||||
#if defined(CONFIG_BT_NIMBLE_ENABLED) && defined(CONFIG_ESP_HOSTED_ENABLED)
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdatomic.h>
|
||||
#include <string.h>
|
||||
#include <esp_log.h>
|
||||
#include <esp_err.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
// NimBLE transport + HCI
|
||||
#include <nimble/transport.h>
|
||||
#include <nimble/hci_common.h>
|
||||
#include <os/os_mbuf.h>
|
||||
|
||||
// H4 packet type indicators (same values used in vhci_drv.c)
|
||||
#define HCI_H4_EVT 0x04
|
||||
#define HCI_H4_ACL 0x02
|
||||
|
||||
// Local defines from vhci_drv.c (not exported by any header)
|
||||
#define BLE_HCI_EVENT_HDR_LEN (2)
|
||||
|
||||
static const char* TAG = "ble_hci_gate";
|
||||
|
||||
// ---- Gate state (zero-init = gate closed until dispatch_enable opens it) ----
|
||||
static atomic_bool s_hci_active = ATOMIC_VAR_INIT(false);
|
||||
static atomic_int s_hci_refcount = ATOMIC_VAR_INIT(0);
|
||||
|
||||
void ble_hci_gate_set_active(bool active) {
|
||||
atomic_store_explicit(&s_hci_active, active, memory_order_seq_cst);
|
||||
}
|
||||
|
||||
// Spin-wait until all in-flight hci_rx_handler calls complete (max_ms timeout).
|
||||
// Returns true if drained within the timeout.
|
||||
bool ble_hci_gate_wait_idle(int max_ms) {
|
||||
for (int elapsed = 0; elapsed < max_ms; elapsed++) {
|
||||
if (atomic_load_explicit(&s_hci_refcount, memory_order_acquire) == 0)
|
||||
return true;
|
||||
vTaskDelay(pdMS_TO_TICKS(1));
|
||||
}
|
||||
return (atomic_load_explicit(&s_hci_refcount, memory_order_acquire) == 0);
|
||||
}
|
||||
|
||||
// Strong override — replaces the H_WEAK_REF hci_rx_handler in vhci_drv.c.
|
||||
// All four esp-hosted transport drivers (SDIO, SPI, SPI-HD, UART) call this symbol.
|
||||
int hci_rx_handler(uint8_t *buf, size_t buf_len) {
|
||||
// Fast path: gate closed — NimBLE not ready, drop silently
|
||||
if (!atomic_load_explicit(&s_hci_active, memory_order_acquire))
|
||||
return ESP_OK;
|
||||
|
||||
// Hold a reference so dispatch_disable() waits for us to finish
|
||||
atomic_fetch_add_explicit(&s_hci_refcount, 1, memory_order_acquire);
|
||||
|
||||
// Double-check: gate may have closed between the first check and the increment
|
||||
if (!atomic_load_explicit(&s_hci_active, memory_order_acquire)) {
|
||||
atomic_fetch_sub_explicit(&s_hci_refcount, 1, memory_order_release);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// ---- Original vhci_drv.c (NimBLE branch) logic ----
|
||||
uint8_t *data = buf;
|
||||
uint32_t len_total_read = buf_len;
|
||||
int rc;
|
||||
|
||||
if (data[0] == HCI_H4_EVT) {
|
||||
uint8_t *evbuf;
|
||||
int totlen = BLE_HCI_EVENT_HDR_LEN + data[2];
|
||||
|
||||
if (totlen > UINT8_MAX + BLE_HCI_EVENT_HDR_LEN) {
|
||||
ESP_LOGE(TAG, "Rx: len[%d] > max INT, drop", totlen);
|
||||
atomic_fetch_sub_explicit(&s_hci_refcount, 1, memory_order_release);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
if (totlen > MYNEWT_VAL(BLE_TRANSPORT_EVT_SIZE)) {
|
||||
ESP_LOGE(TAG, "Rx: len[%d] > max BLE, drop", totlen);
|
||||
atomic_fetch_sub_explicit(&s_hci_refcount, 1, memory_order_release);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
if (data[1] == BLE_HCI_EVCODE_HW_ERROR) {
|
||||
ESP_LOGE(TAG, "Rx: HW_ERROR");
|
||||
atomic_fetch_sub_explicit(&s_hci_refcount, 1, memory_order_release);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
if ((data[1] == BLE_HCI_EVCODE_LE_META) &&
|
||||
(data[3] == BLE_HCI_LE_SUBEV_ADV_RPT ||
|
||||
data[3] == BLE_HCI_LE_SUBEV_EXT_ADV_RPT)) {
|
||||
evbuf = ble_transport_alloc_evt(1);
|
||||
if (!evbuf) {
|
||||
ESP_LOGW(TAG, "Rx: Drop ADV Report: OOM (not fatal)");
|
||||
atomic_fetch_sub_explicit(&s_hci_refcount, 1, memory_order_release);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
} else {
|
||||
evbuf = ble_transport_alloc_evt(0);
|
||||
if (!evbuf) {
|
||||
ESP_LOGE(TAG, "Rx: transport_alloc_evt(0) failed");
|
||||
atomic_fetch_sub_explicit(&s_hci_refcount, 1, memory_order_release);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
memset(evbuf, 0, sizeof *evbuf);
|
||||
memcpy(evbuf, &data[1], totlen);
|
||||
rc = ble_transport_to_hs_evt(evbuf);
|
||||
if (rc) {
|
||||
ESP_LOGE(TAG, "Rx: transport_to_hs_evt failed");
|
||||
atomic_fetch_sub_explicit(&s_hci_refcount, 1, memory_order_release);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
} else if (data[0] == HCI_H4_ACL) {
|
||||
struct os_mbuf *m = ble_transport_alloc_acl_from_ll();
|
||||
if (!m) {
|
||||
ESP_LOGE(TAG, "Rx: alloc_acl_from_ll failed");
|
||||
atomic_fetch_sub_explicit(&s_hci_refcount, 1, memory_order_release);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
if ((rc = os_mbuf_append(m, &data[1], len_total_read - 1)) != 0) {
|
||||
ESP_LOGE(TAG, "Rx: os_mbuf_append failed; rc=%d", rc);
|
||||
os_mbuf_free_chain(m);
|
||||
atomic_fetch_sub_explicit(&s_hci_refcount, 1, memory_order_release);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
ble_transport_to_hs_acl(m);
|
||||
}
|
||||
|
||||
atomic_fetch_sub_explicit(&s_hci_refcount, 1, memory_order_release);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
#endif // CONFIG_BT_NIMBLE_ENABLED && CONFIG_ESP_HOSTED_ENABLED
|
||||
@ -0,0 +1,45 @@
|
||||
@startuml Bluetooth Thread Model
|
||||
|
||||
box "nimble_host task\n(NimBLE internal, 4 KB stack)" #LightBlue
|
||||
participant "gap_event_handler\n(esp32_ble.cpp)" as GAP
|
||||
participant "ble_gatt\ncallbacks" as GATT
|
||||
participant "bt_event_bridge\n(Bluetooth.cpp)" as Bridge
|
||||
participant "hidHostGapCb\n& discovery\n(BluetoothHidHost.cpp)" as HID
|
||||
end box
|
||||
|
||||
box "main_dispatcher task\n(Tactility main task)" #LightGreen
|
||||
participant "settings I/O\n& peer updates" as Dispatch
|
||||
end box
|
||||
|
||||
box "App tasks" #LightYellow
|
||||
participant "BtManage\nBtPeerSettings" as Apps
|
||||
end box
|
||||
|
||||
box "esp_timer task" #LightGray
|
||||
participant "advRestart\nmidiKeepalive\nhidEncRetry" as Timers
|
||||
end box
|
||||
|
||||
box "LVGL task\n(GuiService)" #MistyRose
|
||||
participant "hidHostKeyboard\nReadCb / mouseReadCb" as LVGL
|
||||
end box
|
||||
|
||||
GAP -> Bridge : ble_publish_event() →\nBtEventCallback (bt_event_bridge)
|
||||
Bridge -> Dispatch : getMainDispatcher().dispatch()\n(settings::load/save, autoConnect)
|
||||
Dispatch -> Apps : publishEventCpp → PubSub callbacks
|
||||
GATT -> GAP : GATT access within\nnimble_host task
|
||||
HID -> Dispatch : getMainDispatcher().dispatch()\n(indev cleanup, ProfileStateChanged)
|
||||
Timers -> GAP : advRestartCallback\n(calls ble_start_advertising)
|
||||
LVGL -> HID : lv_indev read callbacks\n(LVGL tick)
|
||||
|
||||
note over GAP, GATT
|
||||
NO file I/O on NimBLE host task —
|
||||
stringstream blows the 4 KB stack
|
||||
end note
|
||||
|
||||
note over GAP, Bridge
|
||||
Driver fires BtEvent via callback array.
|
||||
Bridge (Tactility layer) translates to
|
||||
C++ PubSub<BtEvent> and dispatches I/O.
|
||||
end note
|
||||
|
||||
@enduml
|
||||
1121
Platforms/platform-esp32/source/drivers/bluetooth/esp32_ble.cpp
Normal file
1121
Platforms/platform-esp32/source/drivers/bluetooth/esp32_ble.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,589 @@
|
||||
#ifdef ESP_PLATFORM
|
||||
#include <sdkconfig.h>
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_BT_NIMBLE_ENABLED)
|
||||
|
||||
#include <bluetooth/esp32_ble_internal.h>
|
||||
|
||||
#include <host/ble_gap.h>
|
||||
#include <host/ble_gatt.h>
|
||||
#include <host/ble_hs_mbuf.h>
|
||||
#include <services/gap/ble_svc_gap.h>
|
||||
#include <services/gatt/ble_svc_gatt.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
constexpr auto* TAG = "esp32_ble_hid";
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/driver.h>
|
||||
#include <tactility/log.h>
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
|
||||
|
||||
// ---- HID device context (stored as driver data of the HID device child) ----
|
||||
|
||||
#include <atomic>
|
||||
|
||||
struct BleHidDeviceCtx {
|
||||
std::atomic<uint16_t> hid_conn_handle;
|
||||
};
|
||||
|
||||
// ---- Module globals ----
|
||||
|
||||
BleHidProfile current_hid_profile = BleHidProfile::None;
|
||||
uint16_t hid_appearance = 0x03C1; // Keyboard (default)
|
||||
const uint8_t* active_hid_rpt_map = nullptr;
|
||||
size_t active_hid_rpt_map_len = 0;
|
||||
|
||||
// GATT attribute handles — written by NimBLE via val_handle pointers below.
|
||||
uint16_t hid_kb_input_handle;
|
||||
uint16_t hid_consumer_input_handle;
|
||||
uint16_t hid_mouse_input_handle;
|
||||
uint16_t hid_gamepad_input_handle;
|
||||
|
||||
// ---- Static UUID objects for HID 16-bit UUIDs ----
|
||||
|
||||
static const ble_uuid16_t UUID16_RPT_REF = BLE_UUID16_INIT(0x2908);
|
||||
static const ble_uuid16_t UUID16_HID_INFO = BLE_UUID16_INIT(0x2A4A);
|
||||
static const ble_uuid16_t UUID16_RPT_MAP = BLE_UUID16_INIT(0x2A4B);
|
||||
static const ble_uuid16_t UUID16_HID_CTRL = BLE_UUID16_INIT(0x2A4C);
|
||||
static const ble_uuid16_t UUID16_HID_REPORT = BLE_UUID16_INIT(0x2A4D);
|
||||
static const ble_uuid16_t UUID16_PROTO_MODE = BLE_UUID16_INIT(0x2A4E);
|
||||
static const ble_uuid16_t UUID16_HID_SVC = BLE_UUID16_INIT(0x1812);
|
||||
|
||||
static uint8_t hid_protocol_mode = 0x01; // 0x00=Boot, 0x01=Report
|
||||
|
||||
// ============================================================================
|
||||
// Per-profile HID Report Maps
|
||||
// ============================================================================
|
||||
|
||||
// Keyboard + Consumer (IDs 1 and 2)
|
||||
static const uint8_t hid_rpt_map_kb_consumer[] = {
|
||||
0x05, 0x01, 0x09, 0x06, 0xA1, 0x01,
|
||||
0x85, 0x01,
|
||||
0x05, 0x07, 0x19, 0xE0, 0x29, 0xE7, 0x15, 0x00, 0x25, 0x01,
|
||||
0x75, 0x01, 0x95, 0x08, 0x81, 0x02,
|
||||
0x75, 0x08, 0x95, 0x01, 0x81, 0x01,
|
||||
0x05, 0x08, 0x19, 0x01, 0x29, 0x05, 0x75, 0x01, 0x95, 0x05, 0x91, 0x02,
|
||||
0x75, 0x03, 0x95, 0x01, 0x91, 0x01,
|
||||
0x15, 0x00, 0x25, 0x73, 0x05, 0x07, 0x19, 0x00, 0x29, 0x73,
|
||||
0x75, 0x08, 0x95, 0x06, 0x81, 0x00,
|
||||
0xC0,
|
||||
0x05, 0x0C, 0x09, 0x01, 0xA1, 0x01,
|
||||
0x85, 0x02,
|
||||
0x15, 0x00, 0x26, 0xFF, 0x03, 0x19, 0x00, 0x2A, 0xFF, 0x03,
|
||||
0x75, 0x10, 0x95, 0x01, 0x81, 0x00,
|
||||
0xC0,
|
||||
};
|
||||
|
||||
// Mouse only (ID 1, 4 bytes)
|
||||
static const uint8_t hid_rpt_map_mouse[] = {
|
||||
0x05, 0x01, 0x09, 0x02, 0xA1, 0x01,
|
||||
0x85, 0x01,
|
||||
0x09, 0x01, 0xA1, 0x00,
|
||||
0x05, 0x09, 0x19, 0x01, 0x29, 0x05,
|
||||
0x15, 0x00, 0x25, 0x01, 0x95, 0x05, 0x75, 0x01, 0x81, 0x02,
|
||||
0x95, 0x01, 0x75, 0x03, 0x81, 0x01,
|
||||
0x05, 0x01, 0x09, 0x30, 0x09, 0x31, 0x09, 0x38,
|
||||
0x15, 0x81, 0x25, 0x7F, 0x75, 0x08, 0x95, 0x03, 0x81, 0x06,
|
||||
0xC0, 0xC0,
|
||||
};
|
||||
|
||||
// Keyboard + Consumer + Mouse (IDs 1, 2, 3)
|
||||
static const uint8_t hid_rpt_map_kb_mouse[] = {
|
||||
0x05, 0x01, 0x09, 0x06, 0xA1, 0x01,
|
||||
0x85, 0x01,
|
||||
0x05, 0x07, 0x19, 0xE0, 0x29, 0xE7, 0x15, 0x00, 0x25, 0x01,
|
||||
0x75, 0x01, 0x95, 0x08, 0x81, 0x02,
|
||||
0x75, 0x08, 0x95, 0x01, 0x81, 0x01,
|
||||
0x05, 0x08, 0x19, 0x01, 0x29, 0x05, 0x75, 0x01, 0x95, 0x05, 0x91, 0x02,
|
||||
0x75, 0x03, 0x95, 0x01, 0x91, 0x01,
|
||||
0x15, 0x00, 0x25, 0x73, 0x05, 0x07, 0x19, 0x00, 0x29, 0x73,
|
||||
0x75, 0x08, 0x95, 0x06, 0x81, 0x00,
|
||||
0xC0,
|
||||
0x05, 0x0C, 0x09, 0x01, 0xA1, 0x01,
|
||||
0x85, 0x02,
|
||||
0x15, 0x00, 0x26, 0xFF, 0x03, 0x19, 0x00, 0x2A, 0xFF, 0x03,
|
||||
0x75, 0x10, 0x95, 0x01, 0x81, 0x00,
|
||||
0xC0,
|
||||
0x05, 0x01, 0x09, 0x02, 0xA1, 0x01,
|
||||
0x85, 0x03,
|
||||
0x09, 0x01, 0xA1, 0x00,
|
||||
0x05, 0x09, 0x19, 0x01, 0x29, 0x05,
|
||||
0x15, 0x00, 0x25, 0x01, 0x95, 0x05, 0x75, 0x01, 0x81, 0x02,
|
||||
0x95, 0x01, 0x75, 0x03, 0x81, 0x01,
|
||||
0x05, 0x01, 0x09, 0x30, 0x09, 0x31, 0x09, 0x38,
|
||||
0x15, 0x81, 0x25, 0x7F, 0x75, 0x08, 0x95, 0x03, 0x81, 0x06,
|
||||
0xC0, 0xC0,
|
||||
};
|
||||
|
||||
// Gamepad only (ID 1, 8 bytes)
|
||||
static const uint8_t hid_rpt_map_gamepad[] = {
|
||||
0x05, 0x01, 0x09, 0x05, 0xA1, 0x01,
|
||||
0x85, 0x01,
|
||||
0x05, 0x09, 0x19, 0x01, 0x29, 0x10,
|
||||
0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x10, 0x81, 0x02,
|
||||
0x05, 0x01, 0x09, 0x30, 0x09, 0x31, 0x09, 0x32, 0x09, 0x35, 0x09, 0x33, 0x09, 0x34,
|
||||
0x15, 0x81, 0x25, 0x7F, 0x75, 0x08, 0x95, 0x06, 0x81, 0x02,
|
||||
0xC0,
|
||||
};
|
||||
|
||||
// ---- Per-profile Report Reference descriptor data ----
|
||||
// Format: {Report ID, Report Type} — Type: 1=Input, 2=Output
|
||||
|
||||
static const uint8_t rpt_ref_kbc_kb_in[2] = {1, 1};
|
||||
static const uint8_t rpt_ref_kbc_cs_in[2] = {2, 1};
|
||||
static const uint8_t rpt_ref_kbc_kb_out[2] = {1, 2};
|
||||
static const uint8_t rpt_ref_ms_in[2] = {1, 1};
|
||||
static const uint8_t rpt_ref_kbm_kb_in[2] = {1, 1};
|
||||
static const uint8_t rpt_ref_kbm_cs_in[2] = {2, 1};
|
||||
static const uint8_t rpt_ref_kbm_ms_in[2] = {3, 1};
|
||||
static const uint8_t rpt_ref_kbm_kb_out[2] = {1, 2};
|
||||
static const uint8_t rpt_ref_gp_in[2] = {1, 1};
|
||||
|
||||
// ---- HID GATT callbacks ----
|
||||
|
||||
static int hid_dsc_access(uint16_t /*conn_handle*/, uint16_t /*attr_handle*/,
|
||||
struct ble_gatt_access_ctxt* ctxt, void* arg) {
|
||||
if (ctxt->op == BLE_GATT_ACCESS_OP_READ_DSC) {
|
||||
const uint8_t* data = (const uint8_t*)arg;
|
||||
int rc = os_mbuf_append(ctxt->om, data, 2);
|
||||
return (rc == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
|
||||
}
|
||||
return BLE_ATT_ERR_UNLIKELY;
|
||||
}
|
||||
|
||||
static int hid_chr_access(uint16_t /*conn_handle*/, uint16_t attr_handle,
|
||||
struct ble_gatt_access_ctxt* ctxt, void* /*arg*/) {
|
||||
uint16_t uuid16 = ble_uuid_u16(ctxt->chr->uuid);
|
||||
|
||||
switch (ctxt->op) {
|
||||
case BLE_GATT_ACCESS_OP_READ_CHR: {
|
||||
if (uuid16 == 0x2A4A) {
|
||||
static const uint8_t hid_info[4] = { 0x11, 0x01, 0x00, 0x02 };
|
||||
int rc = os_mbuf_append(ctxt->om, hid_info, sizeof(hid_info));
|
||||
return (rc == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
|
||||
}
|
||||
if (uuid16 == 0x2A4B) {
|
||||
if (active_hid_rpt_map == nullptr || active_hid_rpt_map_len == 0)
|
||||
return BLE_ATT_ERR_UNLIKELY;
|
||||
int rc = os_mbuf_append(ctxt->om, active_hid_rpt_map, active_hid_rpt_map_len);
|
||||
return (rc == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
|
||||
}
|
||||
if (uuid16 == 0x2A4E) {
|
||||
int rc = os_mbuf_append(ctxt->om, &hid_protocol_mode, 1);
|
||||
return (rc == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
|
||||
}
|
||||
if (uuid16 == 0x2A4D) {
|
||||
static const uint8_t zeros[8] = {};
|
||||
size_t report_len = 8;
|
||||
if (attr_handle == hid_consumer_input_handle) report_len = 2;
|
||||
else if (attr_handle == hid_mouse_input_handle) report_len = 4;
|
||||
int rc = os_mbuf_append(ctxt->om, zeros, report_len);
|
||||
return (rc == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
|
||||
}
|
||||
return BLE_ATT_ERR_UNLIKELY;
|
||||
}
|
||||
|
||||
case BLE_GATT_ACCESS_OP_WRITE_CHR: {
|
||||
if (uuid16 == 0x2A4C) return 0; // HID Control Point — no action
|
||||
if (uuid16 == 0x2A4E) {
|
||||
if (OS_MBUF_PKTLEN(ctxt->om) >= 1) {
|
||||
os_mbuf_copydata(ctxt->om, 0, 1, &hid_protocol_mode);
|
||||
LOG_I(TAG, "Protocol Mode -> %u", hid_protocol_mode);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (uuid16 == 0x2A4D) {
|
||||
if (OS_MBUF_PKTLEN(ctxt->om) >= 1) {
|
||||
uint8_t leds = 0;
|
||||
os_mbuf_copydata(ctxt->om, 0, 1, &leds);
|
||||
LOG_I(TAG, "KB LED state: 0x%02x", leds);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return BLE_ATT_ERR_UNLIKELY;
|
||||
}
|
||||
|
||||
default:
|
||||
return BLE_ATT_ERR_UNLIKELY;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Per-profile HID descriptor arrays ----
|
||||
|
||||
static struct ble_gatt_dsc_def hid_kbc_kb_dscs[] = { { .uuid = &UUID16_RPT_REF.u, .att_flags = BLE_ATT_F_READ, .access_cb = hid_dsc_access, .arg = (void*)rpt_ref_kbc_kb_in }, { 0 } };
|
||||
static struct ble_gatt_dsc_def hid_kbc_cs_dscs[] = { { .uuid = &UUID16_RPT_REF.u, .att_flags = BLE_ATT_F_READ, .access_cb = hid_dsc_access, .arg = (void*)rpt_ref_kbc_cs_in }, { 0 } };
|
||||
static struct ble_gatt_dsc_def hid_kbc_out_dscs[] = { { .uuid = &UUID16_RPT_REF.u, .att_flags = BLE_ATT_F_READ, .access_cb = hid_dsc_access, .arg = (void*)rpt_ref_kbc_kb_out }, { 0 } };
|
||||
static struct ble_gatt_dsc_def hid_ms_dscs[] = { { .uuid = &UUID16_RPT_REF.u, .att_flags = BLE_ATT_F_READ, .access_cb = hid_dsc_access, .arg = (void*)rpt_ref_ms_in }, { 0 } };
|
||||
static struct ble_gatt_dsc_def hid_kbm_kb_dscs[] = { { .uuid = &UUID16_RPT_REF.u, .att_flags = BLE_ATT_F_READ, .access_cb = hid_dsc_access, .arg = (void*)rpt_ref_kbm_kb_in }, { 0 } };
|
||||
static struct ble_gatt_dsc_def hid_kbm_cs_dscs[] = { { .uuid = &UUID16_RPT_REF.u, .att_flags = BLE_ATT_F_READ, .access_cb = hid_dsc_access, .arg = (void*)rpt_ref_kbm_cs_in }, { 0 } };
|
||||
static struct ble_gatt_dsc_def hid_kbm_ms_dscs[] = { { .uuid = &UUID16_RPT_REF.u, .att_flags = BLE_ATT_F_READ, .access_cb = hid_dsc_access, .arg = (void*)rpt_ref_kbm_ms_in }, { 0 } };
|
||||
static struct ble_gatt_dsc_def hid_kbm_out_dscs[] = { { .uuid = &UUID16_RPT_REF.u, .att_flags = BLE_ATT_F_READ, .access_cb = hid_dsc_access, .arg = (void*)rpt_ref_kbm_kb_out }, { 0 } };
|
||||
static struct ble_gatt_dsc_def hid_gp_dscs[] = { { .uuid = &UUID16_RPT_REF.u, .att_flags = BLE_ATT_F_READ, .access_cb = hid_dsc_access, .arg = (void*)rpt_ref_gp_in }, { 0 } };
|
||||
|
||||
// ---- Per-profile HID characteristic arrays ----
|
||||
|
||||
static struct ble_gatt_chr_def hid_chars_kb_consumer[] = {
|
||||
{ .uuid = &UUID16_HID_INFO.u, .access_cb = hid_chr_access, .flags = BLE_GATT_CHR_F_READ },
|
||||
{ .uuid = &UUID16_RPT_MAP.u, .access_cb = hid_chr_access, .flags = BLE_GATT_CHR_F_READ },
|
||||
{ .uuid = &UUID16_HID_CTRL.u, .access_cb = hid_chr_access, .flags = BLE_GATT_CHR_F_WRITE_NO_RSP },
|
||||
{ .uuid = &UUID16_PROTO_MODE.u, .access_cb = hid_chr_access, .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE_NO_RSP },
|
||||
{ .uuid = &UUID16_HID_REPORT.u, .access_cb = hid_chr_access, .descriptors = hid_kbc_kb_dscs,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY, .val_handle = &hid_kb_input_handle },
|
||||
{ .uuid = &UUID16_HID_REPORT.u, .access_cb = hid_chr_access, .descriptors = hid_kbc_cs_dscs,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY, .val_handle = &hid_consumer_input_handle },
|
||||
{ .uuid = &UUID16_HID_REPORT.u, .access_cb = hid_chr_access, .descriptors = hid_kbc_out_dscs,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_NO_RSP },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
static struct ble_gatt_chr_def hid_chars_mouse[] = {
|
||||
{ .uuid = &UUID16_HID_INFO.u, .access_cb = hid_chr_access, .flags = BLE_GATT_CHR_F_READ },
|
||||
{ .uuid = &UUID16_RPT_MAP.u, .access_cb = hid_chr_access, .flags = BLE_GATT_CHR_F_READ },
|
||||
{ .uuid = &UUID16_HID_CTRL.u, .access_cb = hid_chr_access, .flags = BLE_GATT_CHR_F_WRITE_NO_RSP },
|
||||
{ .uuid = &UUID16_PROTO_MODE.u, .access_cb = hid_chr_access, .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE_NO_RSP },
|
||||
{ .uuid = &UUID16_HID_REPORT.u, .access_cb = hid_chr_access, .descriptors = hid_ms_dscs,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY, .val_handle = &hid_mouse_input_handle },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
static struct ble_gatt_chr_def hid_chars_kb_mouse[] = {
|
||||
{ .uuid = &UUID16_HID_INFO.u, .access_cb = hid_chr_access, .flags = BLE_GATT_CHR_F_READ },
|
||||
{ .uuid = &UUID16_RPT_MAP.u, .access_cb = hid_chr_access, .flags = BLE_GATT_CHR_F_READ },
|
||||
{ .uuid = &UUID16_HID_CTRL.u, .access_cb = hid_chr_access, .flags = BLE_GATT_CHR_F_WRITE_NO_RSP },
|
||||
{ .uuid = &UUID16_PROTO_MODE.u, .access_cb = hid_chr_access, .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE_NO_RSP },
|
||||
{ .uuid = &UUID16_HID_REPORT.u, .access_cb = hid_chr_access, .descriptors = hid_kbm_kb_dscs,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY, .val_handle = &hid_kb_input_handle },
|
||||
{ .uuid = &UUID16_HID_REPORT.u, .access_cb = hid_chr_access, .descriptors = hid_kbm_cs_dscs,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY, .val_handle = &hid_consumer_input_handle },
|
||||
{ .uuid = &UUID16_HID_REPORT.u, .access_cb = hid_chr_access, .descriptors = hid_kbm_ms_dscs,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY, .val_handle = &hid_mouse_input_handle },
|
||||
{ .uuid = &UUID16_HID_REPORT.u, .access_cb = hid_chr_access, .descriptors = hid_kbm_out_dscs,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_NO_RSP },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
static struct ble_gatt_chr_def hid_chars_gamepad[] = {
|
||||
{ .uuid = &UUID16_HID_INFO.u, .access_cb = hid_chr_access, .flags = BLE_GATT_CHR_F_READ },
|
||||
{ .uuid = &UUID16_RPT_MAP.u, .access_cb = hid_chr_access, .flags = BLE_GATT_CHR_F_READ },
|
||||
{ .uuid = &UUID16_HID_CTRL.u, .access_cb = hid_chr_access, .flags = BLE_GATT_CHR_F_WRITE_NO_RSP },
|
||||
{ .uuid = &UUID16_PROTO_MODE.u, .access_cb = hid_chr_access, .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE_NO_RSP },
|
||||
{ .uuid = &UUID16_HID_REPORT.u, .access_cb = hid_chr_access, .descriptors = hid_gp_dscs,
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY, .val_handle = &hid_gamepad_input_handle },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
// ---- Per-profile GATT service arrays ----
|
||||
|
||||
static const struct ble_gatt_svc_def gatt_svcs_none[] = {
|
||||
{ .type = BLE_GATT_SVC_TYPE_PRIMARY, .uuid = &NUS_SVC_UUID.u, .characteristics = nus_chars_with_handle },
|
||||
{ .type = BLE_GATT_SVC_TYPE_PRIMARY, .uuid = &MIDI_SVC_UUID.u, .characteristics = midi_chars },
|
||||
{ 0 }
|
||||
};
|
||||
static const struct ble_gatt_svc_def gatt_svcs_kb_consumer[] = {
|
||||
{ .type = BLE_GATT_SVC_TYPE_PRIMARY, .uuid = &NUS_SVC_UUID.u, .characteristics = nus_chars_with_handle },
|
||||
{ .type = BLE_GATT_SVC_TYPE_PRIMARY, .uuid = &MIDI_SVC_UUID.u, .characteristics = midi_chars },
|
||||
{ .type = BLE_GATT_SVC_TYPE_PRIMARY, .uuid = &UUID16_HID_SVC.u, .characteristics = hid_chars_kb_consumer },
|
||||
{ 0 }
|
||||
};
|
||||
static const struct ble_gatt_svc_def gatt_svcs_mouse[] = {
|
||||
{ .type = BLE_GATT_SVC_TYPE_PRIMARY, .uuid = &NUS_SVC_UUID.u, .characteristics = nus_chars_with_handle },
|
||||
{ .type = BLE_GATT_SVC_TYPE_PRIMARY, .uuid = &MIDI_SVC_UUID.u, .characteristics = midi_chars },
|
||||
{ .type = BLE_GATT_SVC_TYPE_PRIMARY, .uuid = &UUID16_HID_SVC.u, .characteristics = hid_chars_mouse },
|
||||
{ 0 }
|
||||
};
|
||||
static const struct ble_gatt_svc_def gatt_svcs_kb_mouse[] = {
|
||||
{ .type = BLE_GATT_SVC_TYPE_PRIMARY, .uuid = &NUS_SVC_UUID.u, .characteristics = nus_chars_with_handle },
|
||||
{ .type = BLE_GATT_SVC_TYPE_PRIMARY, .uuid = &MIDI_SVC_UUID.u, .characteristics = midi_chars },
|
||||
{ .type = BLE_GATT_SVC_TYPE_PRIMARY, .uuid = &UUID16_HID_SVC.u, .characteristics = hid_chars_kb_mouse },
|
||||
{ 0 }
|
||||
};
|
||||
static const struct ble_gatt_svc_def gatt_svcs_gamepad[] = {
|
||||
{ .type = BLE_GATT_SVC_TYPE_PRIMARY, .uuid = &NUS_SVC_UUID.u, .characteristics = nus_chars_with_handle },
|
||||
{ .type = BLE_GATT_SVC_TYPE_PRIMARY, .uuid = &MIDI_SVC_UUID.u, .characteristics = midi_chars },
|
||||
{ .type = BLE_GATT_SVC_TYPE_PRIMARY, .uuid = &UUID16_HID_SVC.u, .characteristics = hid_chars_gamepad },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
// ---- HID field accessor implementations ----
|
||||
|
||||
static BleCtx* hid_root_ctx(struct Device* device) {
|
||||
return ble_get_ctx(device);
|
||||
}
|
||||
|
||||
bool ble_hid_get_active(struct Device* device) {
|
||||
return hid_root_ctx(device)->hid_active.load();
|
||||
}
|
||||
void ble_hid_set_active(struct Device* device, bool v) {
|
||||
hid_root_ctx(device)->hid_active.store(v);
|
||||
}
|
||||
uint16_t ble_hid_get_conn_handle(struct Device* device) {
|
||||
if (device == nullptr) return (uint16_t)BLE_HS_CONN_HANDLE_NONE;
|
||||
BleHidDeviceCtx* hid_ctx = (BleHidDeviceCtx*)device_get_driver_data(device);
|
||||
return hid_ctx ? hid_ctx->hid_conn_handle.load() : (uint16_t)BLE_HS_CONN_HANDLE_NONE;
|
||||
}
|
||||
void ble_hid_set_conn_handle(struct Device* device, uint16_t h) {
|
||||
if (device == nullptr) return;
|
||||
BleHidDeviceCtx* hid_ctx = (BleHidDeviceCtx*)device_get_driver_data(device);
|
||||
if (hid_ctx) hid_ctx->hid_conn_handle.store(h);
|
||||
}
|
||||
|
||||
// ---- GATT profile switch ----
|
||||
// device must be the HID device child Device*.
|
||||
|
||||
bool ble_hid_switch_profile(struct Device* device, BleHidProfile profile) {
|
||||
if (profile == current_hid_profile) return true;
|
||||
LOG_I(TAG, "switchGattProfile: %d -> %d", (int)current_hid_profile, (int)profile);
|
||||
|
||||
ble_gap_adv_stop();
|
||||
|
||||
BleHidDeviceCtx* hid_ctx = (BleHidDeviceCtx*)device_get_driver_data(device);
|
||||
if (hid_ctx && hid_ctx->hid_conn_handle.load() != BLE_HS_CONN_HANDLE_NONE) {
|
||||
ble_gap_terminate(hid_ctx->hid_conn_handle.load(), BLE_ERR_REM_USER_CONN_TERM);
|
||||
hid_ctx->hid_conn_handle.store(BLE_HS_CONN_HANDLE_NONE);
|
||||
}
|
||||
|
||||
ble_gatts_reset();
|
||||
ble_svc_gap_init();
|
||||
ble_svc_gatt_init();
|
||||
|
||||
const struct ble_gatt_svc_def* svcs = gatt_svcs_none;
|
||||
const uint8_t* new_rpt_map = nullptr;
|
||||
size_t new_rpt_map_len = 0;
|
||||
switch (profile) {
|
||||
case BleHidProfile::KbConsumer: svcs = gatt_svcs_kb_consumer; new_rpt_map = hid_rpt_map_kb_consumer; new_rpt_map_len = sizeof(hid_rpt_map_kb_consumer); break;
|
||||
case BleHidProfile::Mouse: svcs = gatt_svcs_mouse; new_rpt_map = hid_rpt_map_mouse; new_rpt_map_len = sizeof(hid_rpt_map_mouse); break;
|
||||
case BleHidProfile::KbMouse: svcs = gatt_svcs_kb_mouse; new_rpt_map = hid_rpt_map_kb_mouse; new_rpt_map_len = sizeof(hid_rpt_map_kb_mouse); break;
|
||||
case BleHidProfile::Gamepad: svcs = gatt_svcs_gamepad; new_rpt_map = hid_rpt_map_gamepad; new_rpt_map_len = sizeof(hid_rpt_map_gamepad); break;
|
||||
default: svcs = gatt_svcs_none; break;
|
||||
}
|
||||
|
||||
int rc = ble_gatts_count_cfg(svcs);
|
||||
if (rc == 0) {
|
||||
rc = ble_gatts_add_svcs(svcs);
|
||||
if (rc != 0) {
|
||||
LOG_E(TAG, "switchGattProfile: gatts_add_svcs failed rc=%d", rc);
|
||||
return false; // don't update profile — GATT state is inconsistent
|
||||
}
|
||||
} else {
|
||||
LOG_E(TAG, "switchGattProfile: gatts_count_cfg failed rc=%d", rc);
|
||||
return false;
|
||||
}
|
||||
|
||||
// ble_gatts_add_svcs() only adds definitions to a pending list.
|
||||
// ble_gatts_start() converts them into live ATT entries.
|
||||
// Without this call, all GATT reads return ATT errors and Windows
|
||||
// cannot install the HID driver → Phase 2 reconnect never occurs.
|
||||
rc = ble_gatts_start();
|
||||
if (rc != 0) {
|
||||
LOG_E(TAG, "switchGattProfile: gatts_start failed rc=%d", rc);
|
||||
return false;
|
||||
}
|
||||
|
||||
active_hid_rpt_map = new_rpt_map;
|
||||
active_hid_rpt_map_len = new_rpt_map_len;
|
||||
|
||||
ble_svc_gap_device_name_set(CONFIG_TT_DEVICE_NAME);
|
||||
ble_att_set_preferred_mtu(BLE_ATT_MTU_MAX);
|
||||
// Do NOT call ble_svc_gatt_changed(0, 0xFFFF) here.
|
||||
// switchGattProfile() is always called while disconnected (ble_gatts_mutable()
|
||||
// requires no active connection). ble_gatts_chr_updated() would persist a
|
||||
// "value_changed=1" flag in NVS for every bonded-but-disconnected peer.
|
||||
// When a bonded peer (e.g. Windows HID host) reconnects, NimBLE immediately
|
||||
// sends a Service Changed indication; Windows disconnects to re-discover GATT
|
||||
// without ACKing the indication; NimBLE re-sends on the next reconnect → loop.
|
||||
// GATT handles are deterministic (same registration order each time), so
|
||||
// bonded peers can reuse their cached handles and no indication is needed.
|
||||
|
||||
current_hid_profile = profile;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ble_hid_init_gatt() {
|
||||
current_hid_profile = BleHidProfile::None;
|
||||
active_hid_rpt_map = nullptr;
|
||||
active_hid_rpt_map_len = 0;
|
||||
int rc = ble_gatts_count_cfg(gatt_svcs_none);
|
||||
if (rc != 0) {
|
||||
LOG_E(TAG, "gatts_count_cfg failed (rc=%d)", rc);
|
||||
} else {
|
||||
rc = ble_gatts_add_svcs(gatt_svcs_none);
|
||||
if (rc != 0) {
|
||||
LOG_E(TAG, "gatts_add_svcs failed (rc=%d)", rc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- HID Device sub-API implementations ----
|
||||
// All functions receive the HID device child Device* and operate on BleHidDeviceCtx
|
||||
// stored as its driver data.
|
||||
|
||||
static error_t hid_device_start(struct Device* device, enum BtHidDeviceMode mode) {
|
||||
// Clean up any leftover context from a previous session (e.g. BLE was disabled
|
||||
// while connected: dispatch_disable() skips the normal hid_device_stop cleanup).
|
||||
BleHidDeviceCtx* old_ctx = (BleHidDeviceCtx*)device_get_driver_data(device);
|
||||
delete old_ctx; // no-op if nullptr
|
||||
// Create driver data for this HID session.
|
||||
BleHidDeviceCtx* hid_ctx = new BleHidDeviceCtx();
|
||||
hid_ctx->hid_conn_handle.store(BLE_HS_CONN_HANDLE_NONE);
|
||||
device_set_driver_data(device, hid_ctx);
|
||||
|
||||
BleHidProfile profile;
|
||||
uint16_t appearance;
|
||||
switch (mode) {
|
||||
case BT_HID_DEVICE_MODE_MOUSE:
|
||||
profile = BleHidProfile::Mouse;
|
||||
appearance = 0x03C2;
|
||||
break;
|
||||
case BT_HID_DEVICE_MODE_KEYBOARD_MOUSE:
|
||||
profile = BleHidProfile::KbMouse;
|
||||
appearance = 0x03C0;
|
||||
break;
|
||||
case BT_HID_DEVICE_MODE_GAMEPAD:
|
||||
profile = BleHidProfile::Gamepad;
|
||||
appearance = 0x03C4;
|
||||
break;
|
||||
default: // BT_HID_DEVICE_MODE_KEYBOARD
|
||||
profile = BleHidProfile::KbConsumer;
|
||||
appearance = 0x03C1;
|
||||
break;
|
||||
}
|
||||
|
||||
hid_appearance = appearance;
|
||||
if (!ble_hid_switch_profile(device, profile)) {
|
||||
delete (BleHidDeviceCtx*)device_get_driver_data(device);
|
||||
device_set_driver_data(device, nullptr);
|
||||
return ERROR_INVALID_STATE;
|
||||
}
|
||||
ble_hid_set_active(device, true);
|
||||
ble_start_advertising_hid(device, hid_appearance);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t hid_device_stop(struct Device* device) {
|
||||
ble_hid_set_active(device, false);
|
||||
ble_gap_adv_stop();
|
||||
BleHidDeviceCtx* hid_ctx = (BleHidDeviceCtx*)device_get_driver_data(device);
|
||||
uint16_t conn = hid_ctx ? hid_ctx->hid_conn_handle.load() : (uint16_t)BLE_HS_CONN_HANDLE_NONE;
|
||||
if (conn != BLE_HS_CONN_HANDLE_NONE) {
|
||||
// Connected: terminate and let the DISCONNECT handler switch profile to None.
|
||||
// ble_gatts_mutable() returns false while a connection is live, so calling
|
||||
// switch_profile here would assert inside ble_svc_gap_init().
|
||||
ble_gap_terminate(conn, BLE_ERR_REM_USER_CONN_TERM);
|
||||
// Do NOT clear hid_conn_handle or delete hid_ctx:
|
||||
// the DISCONNECT handler in esp32_ble.cpp uses hid_conn_handle for was_hid detection.
|
||||
// esp32_ble_hid_device_stop_device() (device lifecycle) will free hid_ctx later.
|
||||
} else {
|
||||
// Not connected: GATT is mutable, switch profile immediately.
|
||||
if (current_hid_profile != BleHidProfile::None) {
|
||||
if (!ble_hid_switch_profile(device, BleHidProfile::None)) {
|
||||
LOG_E(TAG, "hid_device_stop: switch to None failed — GATT state inconsistent");
|
||||
}
|
||||
}
|
||||
delete hid_ctx;
|
||||
device_set_driver_data(device, nullptr);
|
||||
}
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t hid_device_send_key(struct Device* device, uint8_t keycode, bool pressed) {
|
||||
BleHidDeviceCtx* hid_ctx = (BleHidDeviceCtx*)device_get_driver_data(device);
|
||||
if (hid_ctx == nullptr || hid_ctx->hid_conn_handle.load() == BLE_HS_CONN_HANDLE_NONE) {
|
||||
return ERROR_INVALID_STATE;
|
||||
}
|
||||
uint8_t report[8] = {};
|
||||
if (pressed) report[2] = keycode;
|
||||
struct os_mbuf* om = ble_hs_mbuf_from_flat(report, sizeof(report));
|
||||
if (om == nullptr) return ERROR_INVALID_STATE;
|
||||
int rc = ble_gatts_notify_custom(hid_ctx->hid_conn_handle.load(), hid_kb_input_handle, om);
|
||||
if (rc != 0) os_mbuf_free_chain(om);
|
||||
return (rc == 0) ? ERROR_NONE : ERROR_INVALID_STATE;
|
||||
}
|
||||
|
||||
static error_t hid_notify(uint16_t conn_handle, uint16_t attr_handle,
|
||||
const uint8_t* data, size_t len) {
|
||||
if (conn_handle == BLE_HS_CONN_HANDLE_NONE) return ERROR_INVALID_STATE;
|
||||
if (attr_handle == 0) return ERROR_INVALID_STATE; // handle not registered for current profile
|
||||
struct os_mbuf* om = ble_hs_mbuf_from_flat(data, len);
|
||||
if (om == nullptr) return ERROR_INVALID_STATE;
|
||||
int rc = ble_gatts_notify_custom(conn_handle, attr_handle, om);
|
||||
if (rc != 0) os_mbuf_free_chain(om);
|
||||
return (rc == 0) ? ERROR_NONE : ERROR_INVALID_STATE;
|
||||
}
|
||||
|
||||
static error_t hid_device_send_keyboard(struct Device* device, const uint8_t* report, size_t len) {
|
||||
BleHidDeviceCtx* hid_ctx = (BleHidDeviceCtx*)device_get_driver_data(device);
|
||||
if (hid_ctx == nullptr) return ERROR_INVALID_STATE;
|
||||
uint8_t buf[8] = {};
|
||||
memcpy(buf, report, len < sizeof(buf) ? len : sizeof(buf));
|
||||
return hid_notify(hid_ctx->hid_conn_handle.load(), hid_kb_input_handle, buf, sizeof(buf));
|
||||
}
|
||||
|
||||
static error_t hid_device_send_consumer(struct Device* device, const uint8_t* report, size_t len) {
|
||||
BleHidDeviceCtx* hid_ctx = (BleHidDeviceCtx*)device_get_driver_data(device);
|
||||
if (hid_ctx == nullptr) return ERROR_INVALID_STATE;
|
||||
uint8_t buf[2] = {};
|
||||
memcpy(buf, report, len < sizeof(buf) ? len : sizeof(buf));
|
||||
return hid_notify(hid_ctx->hid_conn_handle.load(), hid_consumer_input_handle, buf, sizeof(buf));
|
||||
}
|
||||
|
||||
static error_t hid_device_send_mouse(struct Device* device, const uint8_t* report, size_t len) {
|
||||
BleHidDeviceCtx* hid_ctx = (BleHidDeviceCtx*)device_get_driver_data(device);
|
||||
if (hid_ctx == nullptr) return ERROR_INVALID_STATE;
|
||||
uint8_t buf[4] = {};
|
||||
memcpy(buf, report, len < sizeof(buf) ? len : sizeof(buf));
|
||||
return hid_notify(hid_ctx->hid_conn_handle.load(), hid_mouse_input_handle, buf, sizeof(buf));
|
||||
}
|
||||
|
||||
static error_t hid_device_send_gamepad(struct Device* device, const uint8_t* report, size_t len) {
|
||||
BleHidDeviceCtx* hid_ctx = (BleHidDeviceCtx*)device_get_driver_data(device);
|
||||
if (hid_ctx == nullptr) return ERROR_INVALID_STATE;
|
||||
uint8_t buf[8] = {};
|
||||
memcpy(buf, report, len < sizeof(buf) ? len : sizeof(buf));
|
||||
return hid_notify(hid_ctx->hid_conn_handle.load(), hid_gamepad_input_handle, buf, sizeof(buf));
|
||||
}
|
||||
|
||||
static bool hid_device_is_connected(struct Device* device) {
|
||||
BleHidDeviceCtx* hid_ctx = (BleHidDeviceCtx*)device_get_driver_data(device);
|
||||
return hid_ctx != nullptr && hid_ctx->hid_conn_handle.load() != BLE_HS_CONN_HANDLE_NONE;
|
||||
}
|
||||
|
||||
extern const BtHidDeviceApi nimble_hid_device_api = {
|
||||
.start = hid_device_start,
|
||||
.stop = hid_device_stop,
|
||||
.send_key = hid_device_send_key,
|
||||
.send_keyboard = hid_device_send_keyboard,
|
||||
.send_consumer = hid_device_send_consumer,
|
||||
.send_mouse = hid_device_send_mouse,
|
||||
.send_gamepad = hid_device_send_gamepad,
|
||||
.is_connected = hid_device_is_connected,
|
||||
};
|
||||
|
||||
// ---- HID device child driver lifecycle ----
|
||||
|
||||
static error_t esp32_ble_hid_device_stop_device(struct Device* device) {
|
||||
// Safety cleanup: free any BleHidDeviceCtx that was not deleted by hid_device_stop()
|
||||
// (e.g. if the BLE device is stopped while HID is still connected).
|
||||
BleHidDeviceCtx* hid_ctx = (BleHidDeviceCtx*)device_get_driver_data(device);
|
||||
delete hid_ctx; // safe even if nullptr
|
||||
device_set_driver_data(device, nullptr);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
Driver esp32_ble_hid_device_driver = {
|
||||
.name = "esp32_ble_hid_device",
|
||||
.compatible = nullptr,
|
||||
.start_device = nullptr,
|
||||
.stop_device = esp32_ble_hid_device_stop_device,
|
||||
.api = &nimble_hid_device_api,
|
||||
.device_type = &BLUETOOTH_HID_DEVICE_TYPE,
|
||||
.owner = nullptr,
|
||||
.internal = nullptr,
|
||||
};
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
#endif // CONFIG_BT_NIMBLE_ENABLED
|
||||
@ -0,0 +1,263 @@
|
||||
#ifdef ESP_PLATFORM
|
||||
#include <sdkconfig.h>
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_BT_NIMBLE_ENABLED)
|
||||
|
||||
#include <bluetooth/esp32_ble_internal.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
#include <host/ble_gap.h>
|
||||
#include <host/ble_gatt.h>
|
||||
#include <host/ble_hs_mbuf.h>
|
||||
|
||||
constexpr auto* TAG = "esp32_ble_midi";
|
||||
#include <esp_timer.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/driver.h>
|
||||
#include <tactility/log.h>
|
||||
|
||||
// ---- MIDI device context (stored as driver data of the MIDI child device) ----
|
||||
|
||||
struct BleMidiCtx {
|
||||
SemaphoreHandle_t rx_mutex;
|
||||
std::deque<std::vector<uint8_t>> rx_queue;
|
||||
};
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
|
||||
|
||||
// ---- BLE MIDI UUIDs ----
|
||||
|
||||
// 03B80E5A-EDE8-4B33-A751-6CE34EC4C700
|
||||
const ble_uuid128_t MIDI_SVC_UUID = BLE_UUID128_INIT(
|
||||
0x00, 0xC7, 0xC4, 0x4E, 0xE3, 0x6C, 0x51, 0xA7,
|
||||
0x33, 0x4B, 0xE8, 0xED, 0x5A, 0x0E, 0xB8, 0x03
|
||||
);
|
||||
|
||||
// 7772E5DB-3868-4112-A1A9-F2669D106BF3
|
||||
static const ble_uuid128_t MIDI_IO_UUID = BLE_UUID128_INIT(
|
||||
0xF3, 0x6B, 0x10, 0x9D, 0x66, 0xF2, 0xA9, 0xA1,
|
||||
0x12, 0x41, 0x68, 0x38, 0xDB, 0xE5, 0x72, 0x77
|
||||
);
|
||||
|
||||
uint16_t midi_io_handle;
|
||||
|
||||
// ---- MIDI field accessor implementations ----
|
||||
|
||||
static BleCtx* midi_root_ctx(struct Device* device) {
|
||||
return ble_get_ctx(device);
|
||||
}
|
||||
|
||||
bool ble_midi_get_active(struct Device* device) {
|
||||
return midi_root_ctx(device)->midi_active.load();
|
||||
}
|
||||
void ble_midi_set_active(struct Device* device, bool v) {
|
||||
midi_root_ctx(device)->midi_active.store(v);
|
||||
}
|
||||
uint16_t ble_midi_get_conn_handle(struct Device* device) {
|
||||
return midi_root_ctx(device)->midi_conn_handle.load();
|
||||
}
|
||||
void ble_midi_set_conn_handle(struct Device* device, uint16_t h) {
|
||||
midi_root_ctx(device)->midi_conn_handle.store(h);
|
||||
}
|
||||
bool ble_midi_get_use_indicate(struct Device* device) {
|
||||
return midi_root_ctx(device)->midi_use_indicate.load();
|
||||
}
|
||||
void ble_midi_set_use_indicate(struct Device* device, bool v) {
|
||||
midi_root_ctx(device)->midi_use_indicate.store(v);
|
||||
}
|
||||
|
||||
error_t ble_midi_ensure_keepalive(struct Device* device, esp_timer_cb_t cb, uint64_t period_us) {
|
||||
BleCtx* ctx = midi_root_ctx(device);
|
||||
if (ctx->midi_keepalive_timer == nullptr) {
|
||||
esp_timer_create_args_t args = {};
|
||||
args.callback = cb;
|
||||
args.arg = device;
|
||||
args.dispatch_method = ESP_TIMER_TASK;
|
||||
args.name = "ble_midi_as";
|
||||
int rc = esp_timer_create(&args, &ctx->midi_keepalive_timer);
|
||||
if (rc != ESP_OK) {
|
||||
LOG_E(TAG, "midi keepalive timer create failed (rc=%d)", rc);
|
||||
return ERROR_INVALID_STATE;
|
||||
}
|
||||
}
|
||||
// Stop first in case timer is already running
|
||||
esp_timer_stop(ctx->midi_keepalive_timer);
|
||||
int rc = esp_timer_start_periodic(ctx->midi_keepalive_timer, period_us);
|
||||
if (rc != ESP_OK) {
|
||||
LOG_E(TAG, "midi keepalive timer start failed (rc=%d)", rc);
|
||||
return ERROR_INVALID_STATE;
|
||||
}
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
void ble_midi_stop_keepalive(struct Device* device) {
|
||||
BleCtx* ctx = midi_root_ctx(device);
|
||||
if (ctx->midi_keepalive_timer != nullptr) {
|
||||
esp_timer_stop(ctx->midi_keepalive_timer);
|
||||
}
|
||||
}
|
||||
|
||||
// midi_chr_access is called with the midi child Device* (set via ble_midi_init_gatt_handles).
|
||||
static int midi_chr_access(uint16_t conn_handle, uint16_t attr_handle,
|
||||
struct ble_gatt_access_ctxt* ctxt, void* arg) {
|
||||
if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
|
||||
uint16_t len = OS_MBUF_PKTLEN(ctxt->om);
|
||||
LOG_I(TAG, "MIDI RX %u bytes", (unsigned)len);
|
||||
struct Device* device = (struct Device*)arg; // midi child device
|
||||
BleMidiCtx* mctx = (BleMidiCtx*)device_get_driver_data(device);
|
||||
if (mctx != nullptr && len > 0) {
|
||||
std::vector<uint8_t> packet(len);
|
||||
os_mbuf_copydata(ctxt->om, 0, len, packet.data());
|
||||
{
|
||||
xSemaphoreTake(mctx->rx_mutex, portMAX_DELAY);
|
||||
mctx->rx_queue.push_back(std::move(packet));
|
||||
while (mctx->rx_queue.size() > 16) mctx->rx_queue.pop_front();
|
||||
xSemaphoreGive(mctx->rx_mutex);
|
||||
}
|
||||
struct BtEvent e = {};
|
||||
e.type = BT_EVENT_MIDI_DATA_RECEIVED;
|
||||
ble_publish_event(device, e);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct ble_gatt_chr_def midi_chars[] = {
|
||||
{
|
||||
.uuid = &MIDI_IO_UUID.u,
|
||||
.access_cb = midi_chr_access,
|
||||
.arg = nullptr, // set to midi child Device* in ble_midi_init_gatt_handles()
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE_NO_RSP | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_INDICATE,
|
||||
.val_handle = &midi_io_handle,
|
||||
},
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
void ble_midi_init_gatt_handles(struct Device* midi_child) {
|
||||
// Store the midi child Device* as the GATT callback arg so that midi_chr_access
|
||||
// can retrieve BleMidiCtx via device_get_driver_data without a global pointer.
|
||||
// midi_io_handle is written by NimBLE via the val_handle pointer above.
|
||||
midi_chars[0].arg = midi_child;
|
||||
}
|
||||
|
||||
// ---- MIDI Active Sensing keepalive ----
|
||||
// Callback arg is the midi child Device*.
|
||||
|
||||
static void midi_keepalive_cb(void* arg) {
|
||||
struct Device* device = (struct Device*)arg; // midi child device
|
||||
uint16_t conn = ble_midi_get_conn_handle(device);
|
||||
if (conn == BLE_HS_CONN_HANDLE_NONE) return;
|
||||
static const uint8_t as_pkt[3] = { 0x80, 0x80, 0xFE };
|
||||
struct os_mbuf* om = ble_hs_mbuf_from_flat(as_pkt, 3);
|
||||
if (om == nullptr) return;
|
||||
int rc = ble_midi_get_use_indicate(device)
|
||||
? ble_gatts_indicate_custom(conn, midi_io_handle, om)
|
||||
: ble_gatts_notify_custom(conn, midi_io_handle, om);
|
||||
if (rc != 0) os_mbuf_free_chain(om);
|
||||
}
|
||||
|
||||
// ---- MIDI sub-API implementations ----
|
||||
// All functions receive the midi child Device*.
|
||||
|
||||
static error_t midi_start(struct Device* device) {
|
||||
ble_midi_set_active(device, true);
|
||||
// Create 2-second periodic Active Sensing timer to prevent Windows BLE MIDI
|
||||
// driver from declaring the connection idle and disconnecting (~8-10 s timeout).
|
||||
error_t rc = ble_midi_ensure_keepalive(device, midi_keepalive_cb, 2'000'000);
|
||||
if (rc != ERROR_NONE) return rc;
|
||||
ble_start_advertising(device, &MIDI_SVC_UUID);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
error_t ble_midi_start_internal(struct Device* midi_child) {
|
||||
return midi_start(midi_child);
|
||||
}
|
||||
|
||||
static error_t midi_stop(struct Device* device) {
|
||||
ble_midi_set_active(device, false);
|
||||
ble_midi_stop_keepalive(device);
|
||||
uint16_t conn = ble_midi_get_conn_handle(device);
|
||||
if (conn != BLE_HS_CONN_HANDLE_NONE) {
|
||||
ble_gap_terminate(conn, BLE_ERR_REM_USER_CONN_TERM);
|
||||
ble_midi_set_conn_handle(device, BLE_HS_CONN_HANDLE_NONE);
|
||||
}
|
||||
if (!ble_spp_get_active(device) && !ble_hid_get_active(device)) {
|
||||
ble_gap_adv_stop();
|
||||
}
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t midi_send(struct Device* device, const uint8_t* msg, size_t len) {
|
||||
uint16_t conn = ble_midi_get_conn_handle(device);
|
||||
if (conn == BLE_HS_CONN_HANDLE_NONE) return ERROR_INVALID_STATE;
|
||||
// BLE MIDI 2-byte header: [0x80|(ts_high&0x3F)][0x80|(ts_low&0x7F)]
|
||||
uint8_t header[2] = { 0x80, 0x80 };
|
||||
struct os_mbuf* om = ble_hs_mbuf_from_flat(header, 2);
|
||||
if (om == nullptr) return ERROR_INVALID_STATE;
|
||||
if (os_mbuf_append(om, msg, len) != 0) {
|
||||
os_mbuf_free_chain(om);
|
||||
LOG_E(TAG, "midi_send: mbuf append failed");
|
||||
return ERROR_INVALID_STATE;
|
||||
}
|
||||
LOG_I(TAG, "midi_send %u bytes (indicate=%d)", (unsigned)len, (int)ble_midi_get_use_indicate(device));
|
||||
int rc = ble_midi_get_use_indicate(device)
|
||||
? ble_gatts_indicate_custom(conn, midi_io_handle, om)
|
||||
: ble_gatts_notify_custom(conn, midi_io_handle, om);
|
||||
if (rc != 0) {
|
||||
os_mbuf_free_chain(om);
|
||||
LOG_E(TAG, "midi_send failed rc=%d", rc);
|
||||
}
|
||||
return (rc == 0) ? ERROR_NONE : ERROR_INVALID_STATE;
|
||||
}
|
||||
|
||||
static bool midi_is_connected(struct Device* device) {
|
||||
return ble_midi_get_conn_handle(device) != BLE_HS_CONN_HANDLE_NONE;
|
||||
}
|
||||
|
||||
extern const BtMidiApi nimble_midi_api = {
|
||||
.start = midi_start,
|
||||
.stop = midi_stop,
|
||||
.send = midi_send,
|
||||
.is_connected = midi_is_connected,
|
||||
};
|
||||
|
||||
// ---- MIDI child driver lifecycle ----
|
||||
|
||||
static error_t esp32_ble_midi_start_device(struct Device* device) {
|
||||
BleMidiCtx* mctx = new BleMidiCtx();
|
||||
mctx->rx_mutex = xSemaphoreCreateMutex();
|
||||
device_set_driver_data(device, mctx);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t esp32_ble_midi_stop_device(struct Device* device) {
|
||||
BleMidiCtx* mctx = (BleMidiCtx*)device_get_driver_data(device);
|
||||
if (mctx != nullptr) {
|
||||
vSemaphoreDelete(mctx->rx_mutex);
|
||||
delete mctx;
|
||||
device_set_driver_data(device, nullptr);
|
||||
}
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
Driver esp32_ble_midi_driver = {
|
||||
.name = "esp32_ble_midi",
|
||||
.compatible = nullptr,
|
||||
.start_device = esp32_ble_midi_start_device,
|
||||
.stop_device = esp32_ble_midi_stop_device,
|
||||
.api = &nimble_midi_api,
|
||||
.device_type = &BLUETOOTH_MIDI_TYPE,
|
||||
.owner = nullptr,
|
||||
.internal = nullptr,
|
||||
};
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
#endif // CONFIG_BT_NIMBLE_ENABLED
|
||||
@ -0,0 +1,236 @@
|
||||
#ifdef ESP_PLATFORM
|
||||
#include <sdkconfig.h>
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_BT_NIMBLE_ENABLED)
|
||||
|
||||
#include <bluetooth/esp32_ble_internal.h>
|
||||
|
||||
#include <host/ble_gap.h>
|
||||
#include <host/ble_hs_mbuf.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
constexpr auto* TAG = "esp32_ble_scan";
|
||||
#include <tactility/log.h>
|
||||
|
||||
// ---- Module-static scan context ----
|
||||
// Set at the start of ble_resolve_next_unnamed_peer; valid for the duration of
|
||||
// the sequential resolution chain (single-device, single-scan at a time).
|
||||
// Using BleCtx* (not Device*) so we avoid keeping a static Device reference.
|
||||
static BleCtx* s_scan_ctx = nullptr;
|
||||
|
||||
// ---- Scan data helpers ----
|
||||
|
||||
void ble_scan_clear_results(struct Device* device) {
|
||||
BleCtx* ctx = ble_get_ctx(device);
|
||||
xSemaphoreTake(ctx->scan_mutex, portMAX_DELAY);
|
||||
ctx->scan_count = 0;
|
||||
memset(ctx->scan_results, 0, sizeof(ctx->scan_results));
|
||||
xSemaphoreGive(ctx->scan_mutex);
|
||||
}
|
||||
|
||||
// ---- GAP scan callback ----
|
||||
|
||||
int ble_gap_disc_event_handler(struct ble_gap_event* event, void* arg) {
|
||||
struct Device* device = (struct Device*)arg;
|
||||
BleCtx* ctx = ble_get_ctx(device);
|
||||
|
||||
switch (event->type) {
|
||||
case BLE_GAP_EVENT_DISC: {
|
||||
const auto& disc = event->disc;
|
||||
|
||||
BtPeerRecord record = {};
|
||||
memcpy(record.addr, disc.addr.val, BT_ADDR_LEN);
|
||||
record.addr_type = disc.addr.type;
|
||||
record.rssi = disc.rssi;
|
||||
record.paired = false;
|
||||
record.connected = false;
|
||||
|
||||
struct ble_hs_adv_fields fields;
|
||||
if (ble_hs_adv_parse_fields(&fields, disc.data, disc.length_data) == 0) {
|
||||
if (fields.name != nullptr && fields.name_len > 0) {
|
||||
size_t copy_len = std::min<size_t>(fields.name_len, BT_NAME_MAX);
|
||||
memcpy(record.name, fields.name, copy_len);
|
||||
record.name[copy_len] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
xSemaphoreTake(ctx->scan_mutex, portMAX_DELAY);
|
||||
bool found = false;
|
||||
for (size_t i = 0; i < ctx->scan_count; ++i) {
|
||||
if (memcmp(ctx->scan_results[i].addr, record.addr, BT_ADDR_LEN) == 0) {
|
||||
// Deduplicate: merge name from SCAN_RSP without clobbering ADV_IND name
|
||||
if (record.name[0] != '\0') {
|
||||
memcpy(ctx->scan_results[i].name, record.name, BT_NAME_MAX + 1);
|
||||
}
|
||||
ctx->scan_results[i].rssi = record.rssi;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found && ctx->scan_count < 64) {
|
||||
ctx->scan_results[ctx->scan_count] = record;
|
||||
ctx->scan_addrs[ctx->scan_count] = disc.addr; // full addr (type+val)
|
||||
ctx->scan_count++;
|
||||
}
|
||||
xSemaphoreGive(ctx->scan_mutex);
|
||||
}
|
||||
|
||||
struct BtEvent e = {};
|
||||
e.type = BT_EVENT_PEER_FOUND;
|
||||
e.peer = record;
|
||||
ble_publish_event(device, e);
|
||||
break;
|
||||
}
|
||||
|
||||
case BLE_GAP_EVENT_DISC_COMPLETE:
|
||||
LOG_I(TAG, "Scan complete (reason=%d)", event->disc_complete.reason);
|
||||
// Keep scan_active=true; resolveNextUnnamedPeer clears it and fires ScanFinished
|
||||
// once name resolution finishes, so the UI spinner stays active throughout.
|
||||
ble_resolve_next_unnamed_peer(device, 0);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- GATT Device Name resolution ----
|
||||
//
|
||||
// After a scan completes, briefly connect to each device that didn't include its
|
||||
// name in advertising data and read Generic Access Device Name (UUID 0x2A00).
|
||||
//
|
||||
// Resolution is sequential: connect → read → disconnect → next device.
|
||||
// Skip resolution if a profile server or HID host connection is active —
|
||||
// a simultaneous central connection would fail with BLE_HS_EALREADY.
|
||||
|
||||
static int name_read_callback(uint16_t conn_handle, const struct ble_gatt_error* error,
|
||||
struct ble_gatt_attr* attr, void* arg) {
|
||||
BleCtx* ctx = s_scan_ctx;
|
||||
|
||||
if (error->status == 0 && attr != nullptr) {
|
||||
size_t idx = (size_t)(uintptr_t)arg;
|
||||
uint16_t len = OS_MBUF_PKTLEN(attr->om);
|
||||
if (len > 0 && len <= (uint16_t)BT_NAME_MAX) {
|
||||
char name_buf[BT_NAME_MAX + 1] = {};
|
||||
os_mbuf_copydata(attr->om, 0, len, name_buf);
|
||||
{
|
||||
xSemaphoreTake(ctx->scan_mutex, portMAX_DELAY);
|
||||
if (idx < ctx->scan_count && ctx->scan_results[idx].name[0] == '\0') {
|
||||
memcpy(ctx->scan_results[idx].name, name_buf, len);
|
||||
ctx->scan_results[idx].name[len] = '\0';
|
||||
LOG_I(TAG, "Name resolved (idx=%u): %s", (unsigned)idx, name_buf);
|
||||
}
|
||||
BtPeerRecord record = (idx < ctx->scan_count) ? ctx->scan_results[idx] : BtPeerRecord{};
|
||||
xSemaphoreGive(ctx->scan_mutex);
|
||||
|
||||
struct BtEvent e = {};
|
||||
e.type = BT_EVENT_PEER_FOUND;
|
||||
e.peer = record;
|
||||
ble_publish_event(ctx->device, e);
|
||||
}
|
||||
}
|
||||
return 0; // wait for BLE_HS_EDONE
|
||||
}
|
||||
|
||||
// BLE_HS_EDONE, ATT error, or timeout — done with this device
|
||||
ble_gap_terminate(conn_handle, BLE_ERR_REM_USER_CONN_TERM);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int name_res_gap_callback(struct ble_gap_event* event, void* arg) {
|
||||
size_t idx = (size_t)(uintptr_t)arg;
|
||||
BleCtx* ctx = s_scan_ctx;
|
||||
|
||||
switch (event->type) {
|
||||
case BLE_GAP_EVENT_CONNECT:
|
||||
if (event->connect.status == 0) {
|
||||
LOG_I(TAG, "Name resolution: connected (idx=%u handle=%u)", (unsigned)idx, event->connect.conn_handle);
|
||||
static const ble_uuid16_t device_name_uuid = BLE_UUID16_INIT(0x2A00);
|
||||
int rc = ble_gattc_read_by_uuid(event->connect.conn_handle,
|
||||
1, 0xFFFF,
|
||||
&device_name_uuid.u,
|
||||
name_read_callback, arg);
|
||||
if (rc != 0) {
|
||||
LOG_W(TAG, "Name resolution: read_by_uuid failed rc=%d", rc);
|
||||
ble_gap_terminate(event->connect.conn_handle, BLE_ERR_REM_USER_CONN_TERM);
|
||||
}
|
||||
} else {
|
||||
LOG_I(TAG, "Name resolution: connect failed (idx=%u status=%d)", (unsigned)idx, event->connect.status);
|
||||
ble_resolve_next_unnamed_peer(ctx->device, idx + 1);
|
||||
}
|
||||
break;
|
||||
|
||||
case BLE_GAP_EVENT_DISCONNECT:
|
||||
LOG_I(TAG, "Name resolution: disconnected (idx=%u)", (unsigned)idx);
|
||||
ble_resolve_next_unnamed_peer(ctx->device, idx + 1);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ble_resolve_next_unnamed_peer(struct Device* device, size_t start_idx) {
|
||||
BleCtx* ctx = ble_get_ctx(device);
|
||||
s_scan_ctx = ctx;
|
||||
|
||||
// Skip if a profile server or HID host connection attempt is active —
|
||||
// initiating a central connection simultaneously would fail (BLE_HS_EALREADY).
|
||||
if (ble_midi_get_active(device) || ble_spp_get_active(device) ||
|
||||
ble_hid_get_active(device) || ble_hid_get_host_active(device)) {
|
||||
LOG_I(TAG, "Name resolution: skipping (server or HID host active)");
|
||||
ble_set_scan_active(device, false);
|
||||
struct BtEvent e = {};
|
||||
e.type = BT_EVENT_SCAN_FINISHED;
|
||||
ble_publish_event(device, e);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t i = start_idx;
|
||||
while (true) {
|
||||
ble_addr_t addr = {};
|
||||
bool found = false;
|
||||
{
|
||||
xSemaphoreTake(ctx->scan_mutex, portMAX_DELAY);
|
||||
while (i < ctx->scan_count) {
|
||||
if (ctx->scan_results[i].name[0] == '\0') {
|
||||
addr = ctx->scan_addrs[i];
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
xSemaphoreGive(ctx->scan_mutex);
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
LOG_I(TAG, "Name resolution: complete (%u devices)", (unsigned)i);
|
||||
ble_set_scan_active(device, false);
|
||||
struct BtEvent e = {};
|
||||
e.type = BT_EVENT_SCAN_FINISHED;
|
||||
ble_publish_event(device, e);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t own_addr_type;
|
||||
ble_hs_id_infer_auto(0, &own_addr_type);
|
||||
|
||||
void* idx_arg = (void*)(uintptr_t)i;
|
||||
int rc = ble_gap_connect(own_addr_type, &addr, 1500, nullptr,
|
||||
name_res_gap_callback, idx_arg);
|
||||
if (rc == 0) {
|
||||
return; // name_res_gap_callback continues the chain
|
||||
}
|
||||
|
||||
LOG_I(TAG, "Name resolution: ble_gap_connect failed idx=%u rc=%d, skipping", (unsigned)i, rc);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // CONFIG_BT_NIMBLE_ENABLED
|
||||
@ -0,0 +1,239 @@
|
||||
#ifdef ESP_PLATFORM
|
||||
#include <sdkconfig.h>
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_BT_NIMBLE_ENABLED)
|
||||
|
||||
#include <bluetooth/esp32_ble_internal.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
#include <host/ble_gap.h>
|
||||
#include <host/ble_gatt.h>
|
||||
#include <host/ble_hs_mbuf.h>
|
||||
|
||||
constexpr auto* TAG = "esp32_ble_spp";
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/driver.h>
|
||||
#include <tactility/log.h>
|
||||
|
||||
// ---- SPP device context (stored as driver data of the serial child device) ----
|
||||
|
||||
struct BleSppCtx {
|
||||
SemaphoreHandle_t rx_mutex;
|
||||
std::deque<std::vector<uint8_t>> rx_queue;
|
||||
};
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
|
||||
|
||||
// ---- NUS (Nordic UART Service) UUIDs ----
|
||||
|
||||
// 6E400001-B5A3-F393-E0A9-E50E24DCCA9E
|
||||
const ble_uuid128_t NUS_SVC_UUID = BLE_UUID128_INIT(
|
||||
0x9E, 0xCA, 0xDC, 0x24, 0x0E, 0xE5, 0xA9, 0xE0,
|
||||
0x93, 0xF3, 0xA3, 0xB5, 0x01, 0x00, 0x40, 0x6E
|
||||
);
|
||||
|
||||
// 6E400002 RX (write from client → device)
|
||||
static const ble_uuid128_t NUS_RX_UUID = BLE_UUID128_INIT(
|
||||
0x9E, 0xCA, 0xDC, 0x24, 0x0E, 0xE5, 0xA9, 0xE0,
|
||||
0x93, 0xF3, 0xA3, 0xB5, 0x02, 0x00, 0x40, 0x6E
|
||||
);
|
||||
|
||||
// 6E400003 TX (notify device → client)
|
||||
static const ble_uuid128_t NUS_TX_UUID = BLE_UUID128_INIT(
|
||||
0x9E, 0xCA, 0xDC, 0x24, 0x0E, 0xE5, 0xA9, 0xE0,
|
||||
0x93, 0xF3, 0xA3, 0xB5, 0x03, 0x00, 0x40, 0x6E
|
||||
);
|
||||
|
||||
uint16_t nus_tx_handle;
|
||||
|
||||
// nus_chr_access is called with the serial child Device* (set via ble_spp_init_gatt_handles).
|
||||
static int nus_chr_access(uint16_t conn_handle, uint16_t attr_handle,
|
||||
struct ble_gatt_access_ctxt* ctxt, void* arg) {
|
||||
if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
|
||||
uint16_t len = OS_MBUF_PKTLEN(ctxt->om);
|
||||
LOG_I(TAG, "NUS RX %u bytes", (unsigned)len);
|
||||
struct Device* device = (struct Device*)arg; // serial child device
|
||||
BleSppCtx* sctx = (BleSppCtx*)device_get_driver_data(device);
|
||||
if (sctx != nullptr && len > 0) {
|
||||
std::vector<uint8_t> packet(len);
|
||||
os_mbuf_copydata(ctxt->om, 0, len, packet.data());
|
||||
{
|
||||
xSemaphoreTake(sctx->rx_mutex, portMAX_DELAY);
|
||||
sctx->rx_queue.push_back(std::move(packet));
|
||||
while (sctx->rx_queue.size() > 16) sctx->rx_queue.pop_front();
|
||||
xSemaphoreGive(sctx->rx_mutex);
|
||||
}
|
||||
struct BtEvent e = {};
|
||||
e.type = BT_EVENT_SPP_DATA_RECEIVED;
|
||||
ble_publish_event(device, e);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct ble_gatt_chr_def nus_chars_with_handle[] = {
|
||||
{
|
||||
.uuid = &NUS_RX_UUID.u,
|
||||
.access_cb = nus_chr_access,
|
||||
.arg = nullptr, // set to serial child Device* in ble_spp_init_gatt_handles()
|
||||
.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_NO_RSP,
|
||||
},
|
||||
{
|
||||
.uuid = &NUS_TX_UUID.u,
|
||||
.access_cb = nus_chr_access,
|
||||
.arg = nullptr, // set to serial child Device* in ble_spp_init_gatt_handles()
|
||||
.flags = BLE_GATT_CHR_F_NOTIFY,
|
||||
.val_handle = &nus_tx_handle,
|
||||
},
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
void ble_spp_init_gatt_handles(struct Device* serial_child) {
|
||||
// Store the serial child Device* as the GATT callback arg so that nus_chr_access
|
||||
// can retrieve BleSppCtx via device_get_driver_data without a global pointer.
|
||||
// nus_tx_handle is written by NimBLE via the val_handle pointer above.
|
||||
nus_chars_with_handle[0].arg = serial_child;
|
||||
nus_chars_with_handle[1].arg = serial_child;
|
||||
}
|
||||
|
||||
// ---- SPP field accessor implementations ----
|
||||
|
||||
static BleCtx* spp_root_ctx(struct Device* device) {
|
||||
return ble_get_ctx(device);
|
||||
}
|
||||
|
||||
bool ble_spp_get_active(struct Device* device) {
|
||||
return spp_root_ctx(device)->spp_active.load();
|
||||
}
|
||||
void ble_spp_set_active(struct Device* device, bool v) {
|
||||
spp_root_ctx(device)->spp_active.store(v);
|
||||
}
|
||||
uint16_t ble_spp_get_conn_handle(struct Device* device) {
|
||||
return spp_root_ctx(device)->spp_conn_handle.load();
|
||||
}
|
||||
void ble_spp_set_conn_handle(struct Device* device, uint16_t h) {
|
||||
spp_root_ctx(device)->spp_conn_handle.store(h);
|
||||
}
|
||||
|
||||
// ---- SPP sub-API implementations ----
|
||||
// All functions receive the serial child Device*.
|
||||
|
||||
static error_t spp_start(struct Device* device) {
|
||||
ble_spp_set_active(device, true);
|
||||
ble_start_advertising(device, &NUS_SVC_UUID);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
error_t ble_spp_start_internal(struct Device* serial_child) {
|
||||
return spp_start(serial_child);
|
||||
}
|
||||
|
||||
static error_t spp_stop(struct Device* device) {
|
||||
ble_spp_set_active(device, false);
|
||||
uint16_t conn = ble_spp_get_conn_handle(device);
|
||||
if (conn != BLE_HS_CONN_HANDLE_NONE) {
|
||||
ble_gap_terminate(conn, BLE_ERR_REM_USER_CONN_TERM);
|
||||
ble_spp_set_conn_handle(device, BLE_HS_CONN_HANDLE_NONE);
|
||||
}
|
||||
// Do NOT restart advertising after user-initiated stop — restarting name-only
|
||||
// advertising causes bonded Windows hosts to auto-reconnect in a tight loop.
|
||||
if (!ble_midi_get_active(device) && !ble_hid_get_active(device)) {
|
||||
ble_gap_adv_stop();
|
||||
}
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t spp_write(struct Device* device, const uint8_t* data, size_t len, size_t* written) {
|
||||
uint16_t conn = ble_spp_get_conn_handle(device);
|
||||
if (conn == BLE_HS_CONN_HANDLE_NONE) {
|
||||
if (written) *written = 0;
|
||||
return ERROR_INVALID_STATE;
|
||||
}
|
||||
struct os_mbuf* om = ble_hs_mbuf_from_flat(data, len);
|
||||
if (om == nullptr) {
|
||||
if (written) *written = 0;
|
||||
return ERROR_INVALID_STATE;
|
||||
}
|
||||
int rc = ble_gatts_notify_custom(conn, nus_tx_handle, om);
|
||||
if (rc != 0) {
|
||||
os_mbuf_free_chain(om);
|
||||
if (written) *written = 0;
|
||||
return ERROR_INVALID_STATE;
|
||||
}
|
||||
if (written) *written = len;
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t spp_read(struct Device* device, uint8_t* data, size_t max_len, size_t* read_out) {
|
||||
BleSppCtx* sctx = (BleSppCtx*)device_get_driver_data(device);
|
||||
if (sctx == nullptr || data == nullptr || max_len == 0) {
|
||||
if (read_out) *read_out = 0;
|
||||
return ERROR_NONE;
|
||||
}
|
||||
xSemaphoreTake(sctx->rx_mutex, portMAX_DELAY);
|
||||
if (sctx->rx_queue.empty()) {
|
||||
xSemaphoreGive(sctx->rx_mutex);
|
||||
if (read_out) *read_out = 0;
|
||||
return ERROR_NONE;
|
||||
}
|
||||
auto& front = sctx->rx_queue.front();
|
||||
size_t copy_len = std::min(front.size(), max_len);
|
||||
memcpy(data, front.data(), copy_len);
|
||||
sctx->rx_queue.pop_front();
|
||||
xSemaphoreGive(sctx->rx_mutex);
|
||||
if (read_out) *read_out = copy_len;
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static bool spp_is_connected(struct Device* device) {
|
||||
return ble_spp_get_conn_handle(device) != BLE_HS_CONN_HANDLE_NONE;
|
||||
}
|
||||
|
||||
extern const BtSerialApi nimble_serial_api = {
|
||||
.start = spp_start,
|
||||
.stop = spp_stop,
|
||||
.write = spp_write,
|
||||
.read = spp_read,
|
||||
.is_connected = spp_is_connected,
|
||||
};
|
||||
|
||||
// ---- Serial child driver lifecycle ----
|
||||
|
||||
static error_t esp32_ble_serial_start_device(struct Device* device) {
|
||||
BleSppCtx* sctx = new BleSppCtx();
|
||||
sctx->rx_mutex = xSemaphoreCreateMutex();
|
||||
device_set_driver_data(device, sctx);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t esp32_ble_serial_stop_device(struct Device* device) {
|
||||
BleSppCtx* sctx = (BleSppCtx*)device_get_driver_data(device);
|
||||
if (sctx != nullptr) {
|
||||
vSemaphoreDelete(sctx->rx_mutex);
|
||||
delete sctx;
|
||||
device_set_driver_data(device, nullptr);
|
||||
}
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
Driver esp32_ble_serial_driver = {
|
||||
.name = "esp32_ble_serial",
|
||||
.compatible = nullptr,
|
||||
.start_device = esp32_ble_serial_start_device,
|
||||
.stop_device = esp32_ble_serial_stop_device,
|
||||
.api = &nimble_serial_api,
|
||||
.device_type = &BLUETOOTH_SERIAL_TYPE,
|
||||
.owner = nullptr,
|
||||
.internal = nullptr,
|
||||
};
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
#endif // CONFIG_BT_NIMBLE_ENABLED
|
||||
@ -1,4 +1,9 @@
|
||||
#ifdef ESP_PLATFORM
|
||||
#include <sdkconfig.h>
|
||||
#endif
|
||||
|
||||
#include <tactility/check.h>
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/driver.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
@ -14,6 +19,12 @@ extern Driver esp32_sdmmc_driver;
|
||||
#endif
|
||||
extern Driver esp32_spi_driver;
|
||||
extern Driver esp32_uart_driver;
|
||||
#if defined(CONFIG_BT_NIMBLE_ENABLED)
|
||||
extern Driver esp32_bluetooth_driver;
|
||||
extern Driver esp32_ble_serial_driver;
|
||||
extern Driver esp32_ble_midi_driver;
|
||||
extern Driver esp32_ble_hid_device_driver;
|
||||
#endif
|
||||
|
||||
static error_t start() {
|
||||
/* We crash when construct fails, because if a single driver fails to construct,
|
||||
@ -26,12 +37,24 @@ static error_t start() {
|
||||
#endif
|
||||
check(driver_construct_add(&esp32_spi_driver) == ERROR_NONE);
|
||||
check(driver_construct_add(&esp32_uart_driver) == ERROR_NONE);
|
||||
#if defined(CONFIG_BT_NIMBLE_ENABLED)
|
||||
check(driver_construct_add(&esp32_bluetooth_driver) == ERROR_NONE);
|
||||
check(driver_construct_add(&esp32_ble_serial_driver) == ERROR_NONE);
|
||||
check(driver_construct_add(&esp32_ble_midi_driver) == ERROR_NONE);
|
||||
check(driver_construct_add(&esp32_ble_hid_device_driver) == ERROR_NONE);
|
||||
#endif
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t stop() {
|
||||
/* We crash when destruct fails, because if a single driver fails to destruct,
|
||||
* there is no guarantee that the previously destroyed drivers can be recovered */
|
||||
#if defined(CONFIG_BT_NIMBLE_ENABLED)
|
||||
check(driver_remove_destruct(&esp32_ble_hid_device_driver) == ERROR_NONE);
|
||||
check(driver_remove_destruct(&esp32_ble_midi_driver) == ERROR_NONE);
|
||||
check(driver_remove_destruct(&esp32_ble_serial_driver) == ERROR_NONE);
|
||||
check(driver_remove_destruct(&esp32_bluetooth_driver) == ERROR_NONE);
|
||||
#endif
|
||||
check(driver_remove_destruct(&esp32_gpio_driver) == ERROR_NONE);
|
||||
check(driver_remove_destruct(&esp32_i2c_driver) == ERROR_NONE);
|
||||
check(driver_remove_destruct(&esp32_i2s_driver) == ERROR_NONE);
|
||||
|
||||
@ -58,6 +58,8 @@ tactility_add_module(Tactility
|
||||
)
|
||||
|
||||
if (DEFINED ENV{ESP_IDF_VERSION})
|
||||
idf_component_optional_requires(PRIVATE bt)
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
target_compile_options(${COMPONENT_LIB} PUBLIC -Wno-unused-variable)
|
||||
endif ()
|
||||
|
||||
9
Tactility/Include/Tactility/app/btmanage/BtManage.h
Normal file
9
Tactility/Include/Tactility/app/btmanage/BtManage.h
Normal file
@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <Tactility/app/App.h>
|
||||
|
||||
namespace tt::app::btmanage {
|
||||
|
||||
LaunchId start();
|
||||
|
||||
} // namespace tt::app::btmanage
|
||||
99
Tactility/Include/Tactility/bluetooth/Bluetooth.h
Normal file
99
Tactility/Include/Tactility/bluetooth/Bluetooth.h
Normal file
@ -0,0 +1,99 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
|
||||
struct Device;
|
||||
|
||||
namespace tt::bluetooth {
|
||||
|
||||
enum class RadioState {
|
||||
Off,
|
||||
OnPending,
|
||||
On,
|
||||
OffPending,
|
||||
};
|
||||
|
||||
struct PeerRecord {
|
||||
std::array<uint8_t, 6> addr;
|
||||
std::string name;
|
||||
int8_t rssi;
|
||||
bool paired;
|
||||
bool connected;
|
||||
/** Profile used to pair (BtProfileId value). Only meaningful for paired peers. */
|
||||
int profileId = 0;
|
||||
};
|
||||
|
||||
/** Find the first ready BLE device in the kernel device registry. Returns nullptr if unavailable. */
|
||||
struct Device* findFirstDevice();
|
||||
|
||||
/** @return the current radio state */
|
||||
RadioState getRadioState();
|
||||
|
||||
/** For logging purposes */
|
||||
const char* radioStateToString(RadioState state);
|
||||
|
||||
/** @return the peers found during the last scan */
|
||||
std::vector<PeerRecord> getScanResults();
|
||||
|
||||
/** @return the list of currently paired peers */
|
||||
std::vector<PeerRecord> getPairedPeers();
|
||||
|
||||
/**
|
||||
* @brief Initiate pairing with a peer.
|
||||
* Returns immediately; result is delivered via kernel event callback (BT_EVENT_PAIR_RESULT).
|
||||
*/
|
||||
void pair(const std::array<uint8_t, 6>& addr);
|
||||
|
||||
/**
|
||||
* @brief Remove a previously paired peer.
|
||||
* @param[in] addr the peer address
|
||||
*/
|
||||
void unpair(const std::array<uint8_t, 6>& addr);
|
||||
|
||||
/**
|
||||
* @brief Connect to a peer using the specified profile.
|
||||
* @param[in] addr the peer address
|
||||
* @param[in] profileId the BtProfileId value (from bluetooth.h)
|
||||
*/
|
||||
void connect(const std::array<uint8_t, 6>& addr, int profileId);
|
||||
|
||||
/**
|
||||
* @brief Disconnect a peer from the specified profile.
|
||||
* @param[in] addr the peer address
|
||||
* @param[in] profileId the BtProfileId value (from bluetooth.h)
|
||||
*/
|
||||
void disconnect(const std::array<uint8_t, 6>& addr, int profileId);
|
||||
|
||||
/**
|
||||
* @brief Check whether a given profile is supported on this build/SOC.
|
||||
* @param[in] profileId the BtProfileId value to query
|
||||
* @return true when the profile is available
|
||||
*/
|
||||
bool isProfileSupported(int profileId);
|
||||
|
||||
// ---- BLE HID Host (central role — connect to external BLE keyboard/mouse) ----
|
||||
|
||||
/**
|
||||
* @brief Connect to a remote BLE HID device (keyboard, mouse, etc.) as a host.
|
||||
* Discovery, CCCD subscription, and LVGL indev registration happen automatically.
|
||||
* @param[in] addr 6-byte BLE address of the HID peripheral
|
||||
*/
|
||||
void hidHostConnect(const std::array<uint8_t, 6>& addr);
|
||||
|
||||
/** @brief Disconnect from the currently connected BLE HID device. */
|
||||
void hidHostDisconnect();
|
||||
|
||||
/** @return true when a BLE HID peripheral is fully subscribed and acting as LVGL input device */
|
||||
bool hidHostIsConnected();
|
||||
|
||||
/**
|
||||
* @brief Initialize the Bluetooth bridge layer and optionally enable the radio.
|
||||
* Called once from Tactility startup (after kernel drivers are ready).
|
||||
* Reads settings and enables the radio if configured to auto-start.
|
||||
*/
|
||||
void systemStart();
|
||||
|
||||
} // namespace tt::bluetooth
|
||||
@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace tt::bluetooth::settings {
|
||||
|
||||
struct PairedDevice {
|
||||
std::string name;
|
||||
std::array<uint8_t, 6> addr;
|
||||
bool autoConnect = false;
|
||||
/** Profile used to pair (BtProfileId value). Defaults to BT_PROFILE_SPP=2. */
|
||||
int profileId = 2;
|
||||
};
|
||||
|
||||
std::string addrToHex(const std::array<uint8_t, 6>& addr);
|
||||
|
||||
bool hasFileForDevice(const std::string& addr_hex);
|
||||
|
||||
bool load(const std::string& addr_hex, PairedDevice& device);
|
||||
|
||||
bool save(const PairedDevice& device);
|
||||
|
||||
bool remove(const std::string& addr_hex);
|
||||
|
||||
std::vector<PairedDevice> loadAll();
|
||||
|
||||
} // namespace tt::bluetooth::settings
|
||||
14
Tactility/Include/Tactility/bluetooth/BluetoothSettings.h
Normal file
14
Tactility/Include/Tactility/bluetooth/BluetoothSettings.h
Normal file
@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
namespace tt::bluetooth::settings {
|
||||
|
||||
void setEnableOnBoot(bool enable);
|
||||
bool shouldEnableOnBoot();
|
||||
|
||||
void setSppAutoStart(bool enable);
|
||||
bool shouldSppAutoStart();
|
||||
|
||||
void setMidiAutoStart(bool enable);
|
||||
bool shouldMidiAutoStart();
|
||||
|
||||
} // namespace tt::bluetooth::settings
|
||||
24
Tactility/Private/Tactility/app/btmanage/Bindings.h
Normal file
24
Tactility/Private/Tactility/app/btmanage/Bindings.h
Normal file
@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
namespace tt::app::btmanage {
|
||||
|
||||
typedef void (*OnBtToggled)(bool enable);
|
||||
typedef void (*OnScanToggled)(bool enable);
|
||||
typedef void (*OnConnectPeer)(const std::array<uint8_t, 6>& addr, int profileId);
|
||||
typedef void (*OnDisconnectPeer)(const std::array<uint8_t, 6>& addr, int profileId);
|
||||
typedef void (*OnPairPeer)(const std::array<uint8_t, 6>& addr);
|
||||
typedef void (*OnForgetPeer)(const std::array<uint8_t, 6>& addr);
|
||||
|
||||
struct Bindings {
|
||||
OnBtToggled onBtToggled;
|
||||
OnScanToggled onScanToggled;
|
||||
OnConnectPeer onConnectPeer;
|
||||
OnDisconnectPeer onDisconnectPeer;
|
||||
OnPairPeer onPairPeer;
|
||||
OnForgetPeer onForgetPeer;
|
||||
};
|
||||
|
||||
} // namespace tt::app::btmanage
|
||||
40
Tactility/Private/Tactility/app/btmanage/BtManagePrivate.h
Normal file
40
Tactility/Private/Tactility/app/btmanage/BtManagePrivate.h
Normal file
@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include "./View.h"
|
||||
#include "./State.h"
|
||||
|
||||
#include <Tactility/app/App.h>
|
||||
#include <Tactility/Mutex.h>
|
||||
#include <Tactility/bluetooth/Bluetooth.h>
|
||||
#include <tactility/drivers/bluetooth.h>
|
||||
|
||||
namespace tt::app::btmanage {
|
||||
|
||||
class BtManage final : public App {
|
||||
|
||||
Mutex mutex;
|
||||
Bindings bindings = { };
|
||||
State state;
|
||||
View view = View(&bindings, &state);
|
||||
bool isViewEnabled = false;
|
||||
struct Device* btDevice = nullptr;
|
||||
|
||||
public:
|
||||
|
||||
void onBtEvent(const struct BtEvent& event);
|
||||
|
||||
BtManage();
|
||||
|
||||
void lock();
|
||||
void unlock();
|
||||
|
||||
void onShow(AppContext& app, lv_obj_t* parent) override;
|
||||
void onHide(AppContext& app) override;
|
||||
|
||||
Bindings& getBindings() { return bindings; }
|
||||
State& getState() { return state; }
|
||||
|
||||
void requestViewUpdate();
|
||||
};
|
||||
|
||||
} // namespace tt::app::btmanage
|
||||
41
Tactility/Private/Tactility/app/btmanage/State.h
Normal file
41
Tactility/Private/Tactility/app/btmanage/State.h
Normal file
@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include <Tactility/bluetooth/Bluetooth.h>
|
||||
#include <Tactility/RecursiveMutex.h>
|
||||
|
||||
namespace tt::app::btmanage {
|
||||
|
||||
class State final {
|
||||
|
||||
mutable RecursiveMutex mutex;
|
||||
bool scanning = false;
|
||||
bluetooth::RadioState radioState = bluetooth::RadioState::Off;
|
||||
std::vector<bluetooth::PeerRecord> scanResults;
|
||||
std::vector<bluetooth::PeerRecord> pairedPeers;
|
||||
|
||||
public:
|
||||
State() = default;
|
||||
|
||||
void setScanning(bool isScanning);
|
||||
bool isScanning() const;
|
||||
|
||||
void setRadioState(bluetooth::RadioState state);
|
||||
bluetooth::RadioState getRadioState() const;
|
||||
|
||||
void updateScanResults();
|
||||
void updatePairedPeers();
|
||||
|
||||
std::vector<bluetooth::PeerRecord> getScanResults() const {
|
||||
auto lock = mutex.asScopedLock();
|
||||
lock.lock();
|
||||
return scanResults;
|
||||
}
|
||||
|
||||
std::vector<bluetooth::PeerRecord> getPairedPeers() const {
|
||||
auto lock = mutex.asScopedLock();
|
||||
lock.lock();
|
||||
return pairedPeers;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace tt::app::btmanage
|
||||
41
Tactility/Private/Tactility/app/btmanage/View.h
Normal file
41
Tactility/Private/Tactility/app/btmanage/View.h
Normal file
@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include "./Bindings.h"
|
||||
#include "./State.h"
|
||||
|
||||
#include <Tactility/app/AppContext.h>
|
||||
#include <Tactility/app/AppPaths.h>
|
||||
|
||||
#include <lvgl.h>
|
||||
|
||||
namespace tt::app::btmanage {
|
||||
|
||||
class View final {
|
||||
|
||||
Bindings* bindings;
|
||||
State* state;
|
||||
std::unique_ptr<AppPaths> paths;
|
||||
lv_obj_t* root = nullptr;
|
||||
lv_obj_t* enable_switch = nullptr;
|
||||
lv_obj_t* enable_on_boot_switch = nullptr;
|
||||
lv_obj_t* scanning_spinner = nullptr;
|
||||
lv_obj_t* peers_list = nullptr;
|
||||
|
||||
void updateBtToggle();
|
||||
void updateEnableOnBootToggle();
|
||||
void updateScanning();
|
||||
void updatePeerList();
|
||||
|
||||
void createPeerListItem(const bluetooth::PeerRecord& record, bool isPaired, size_t index);
|
||||
|
||||
static void onConnect(lv_event_t* event);
|
||||
|
||||
public:
|
||||
|
||||
View(Bindings* bindings, State* state) : bindings(bindings), state(state) {}
|
||||
|
||||
void init(const AppContext& app, lv_obj_t* parent);
|
||||
void update();
|
||||
};
|
||||
|
||||
} // namespace tt::app::btmanage
|
||||
@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace tt::app::btpeersettings {
|
||||
|
||||
void start(const std::string& addrHex);
|
||||
|
||||
} // namespace tt::app::btpeersettings
|
||||
29
Tactility/Private/Tactility/bluetooth/BluetoothPrivate.h
Normal file
29
Tactility/Private/Tactility/bluetooth/BluetoothPrivate.h
Normal file
@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <Tactility/bluetooth/Bluetooth.h>
|
||||
|
||||
#include <tactility/drivers/bluetooth.h>
|
||||
|
||||
struct Device;
|
||||
|
||||
namespace tt::bluetooth {
|
||||
|
||||
/** Cache a BLE address and its type from a scan result (used by HID host for addr_type lookup). */
|
||||
void cacheScanAddr(const uint8_t addr[6], uint8_t addr_type);
|
||||
|
||||
/**
|
||||
* Look up the ble_addr_t (including address type) for a peer address in the last scan.
|
||||
* Returns false and fills type=0 (PUBLIC) if not found.
|
||||
*/
|
||||
bool getCachedScanAddrType(const uint8_t addr[6], uint8_t* addr_type_out);
|
||||
|
||||
/** Called from BluetoothHidHost.cpp to trigger auto-connect check after scan finishes. */
|
||||
void autoConnectHidHost();
|
||||
|
||||
/**
|
||||
* Returns the BLE address of the currently fully-connected HID host peer (i.e.
|
||||
* all CCCDs subscribed and ready). Returns false if no HID host peer is connected.
|
||||
*/
|
||||
bool hidHostGetConnectedPeer(std::array<uint8_t, 6>& addr_out);
|
||||
|
||||
} // namespace tt::bluetooth
|
||||
@ -38,6 +38,8 @@
|
||||
#include <Tactility/InitEsp.h>
|
||||
#endif
|
||||
|
||||
#include <Tactility/bluetooth/Bluetooth.h>
|
||||
|
||||
namespace tt {
|
||||
|
||||
static auto LOGGER = Logger("Tactility");
|
||||
@ -106,6 +108,8 @@ namespace app {
|
||||
namespace touchcalibration { extern const AppManifest manifest; }
|
||||
namespace timezone { extern const AppManifest manifest; }
|
||||
namespace usbsettings { extern const AppManifest manifest; }
|
||||
namespace btmanage { extern const AppManifest manifest; }
|
||||
namespace btpeersettings { extern const AppManifest manifest; }
|
||||
namespace wifiapsettings { extern const AppManifest manifest; }
|
||||
namespace wificonnect { extern const AppManifest manifest; }
|
||||
namespace wifimanage { extern const AppManifest manifest; }
|
||||
@ -191,6 +195,11 @@ static void registerInternalApps() {
|
||||
if (hal::hasDevice(hal::Device::Type::Power)) {
|
||||
addAppManifest(app::power::manifest);
|
||||
}
|
||||
|
||||
#if defined(CONFIG_BT_ENABLED) && CONFIG_BT_ENABLED
|
||||
addAppManifest(app::btmanage::manifest);
|
||||
addAppManifest(app::btpeersettings::manifest);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void registerInstalledApp(std::string path) {
|
||||
@ -335,6 +344,7 @@ void run(const Configuration& config, Module* dtsModules[], DtsDevice dtsDevices
|
||||
settings::initTimeZone();
|
||||
hal::init(*config.hardware);
|
||||
network::ntp::init();
|
||||
bluetooth::systemStart();
|
||||
|
||||
registerAndStartPrimaryServices();
|
||||
|
||||
|
||||
189
Tactility/Source/app/btmanage/BtManage.cpp
Normal file
189
Tactility/Source/app/btmanage/BtManage.cpp
Normal file
@ -0,0 +1,189 @@
|
||||
#include <Tactility/app/btmanage/BtManagePrivate.h>
|
||||
#include <Tactility/app/btmanage/View.h>
|
||||
|
||||
#include <Tactility/app/AppContext.h>
|
||||
#include <Tactility/app/AppManifest.h>
|
||||
#include <Tactility/Logger.h>
|
||||
#include <Tactility/LogMessages.h>
|
||||
#include <Tactility/lvgl/LvglSync.h>
|
||||
#include <Tactility/Tactility.h>
|
||||
|
||||
#include <tactility/lvgl_icon_shared.h>
|
||||
|
||||
namespace tt::app::btmanage {
|
||||
|
||||
static const auto LOGGER = Logger("BtManage");
|
||||
|
||||
extern const AppManifest manifest;
|
||||
|
||||
static void onBtToggled(bool enabled) {
|
||||
struct Device* dev = bluetooth::findFirstDevice();
|
||||
if (!dev) return;
|
||||
bluetooth_set_radio_enabled(dev, enabled);
|
||||
}
|
||||
|
||||
static void onScanToggled(bool enabled) {
|
||||
struct Device* dev = bluetooth::findFirstDevice();
|
||||
if (!dev) return;
|
||||
if (enabled) {
|
||||
bluetooth_scan_start(dev);
|
||||
} else {
|
||||
bluetooth_scan_stop(dev);
|
||||
}
|
||||
}
|
||||
|
||||
static void onConnectPeer(const std::array<uint8_t, 6>& addr, int profileId) {
|
||||
bluetooth::connect(addr, profileId);
|
||||
}
|
||||
|
||||
static void onDisconnectPeer(const std::array<uint8_t, 6>& addr, int profileId) {
|
||||
bluetooth::disconnect(addr, profileId);
|
||||
}
|
||||
|
||||
static void onPairPeer(const std::array<uint8_t, 6>& addr) {
|
||||
// Clicking an unrecognised scan result initiates a HID host connection.
|
||||
// Bond exchange happens automatically during the first connection.
|
||||
bluetooth::hidHostConnect(addr);
|
||||
}
|
||||
|
||||
static void onForgetPeer(const std::array<uint8_t, 6>& addr) {
|
||||
bluetooth::unpair(addr);
|
||||
}
|
||||
|
||||
BtManage::BtManage() {
|
||||
bindings = (Bindings) {
|
||||
.onBtToggled = onBtToggled,
|
||||
.onScanToggled = onScanToggled,
|
||||
.onConnectPeer = onConnectPeer,
|
||||
.onDisconnectPeer = onDisconnectPeer,
|
||||
.onPairPeer = onPairPeer,
|
||||
.onForgetPeer = onForgetPeer,
|
||||
};
|
||||
}
|
||||
|
||||
void BtManage::lock() {
|
||||
mutex.lock();
|
||||
}
|
||||
|
||||
void BtManage::unlock() {
|
||||
mutex.unlock();
|
||||
}
|
||||
|
||||
void BtManage::requestViewUpdate() {
|
||||
lock();
|
||||
if (isViewEnabled) {
|
||||
if (lvgl::lock(1000)) {
|
||||
view.update();
|
||||
lvgl::unlock();
|
||||
} else {
|
||||
LOGGER.error(LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "LVGL");
|
||||
}
|
||||
}
|
||||
unlock();
|
||||
}
|
||||
|
||||
void BtManage::onBtEvent(const struct BtEvent& event) {
|
||||
auto radio_state = bluetooth::getRadioState();
|
||||
LOGGER.info("Update with state {}", bluetooth::radioStateToString(radio_state));
|
||||
getState().setRadioState(radio_state);
|
||||
switch (event.type) {
|
||||
case BT_EVENT_SCAN_STARTED:
|
||||
getState().setScanning(true);
|
||||
break;
|
||||
case BT_EVENT_SCAN_FINISHED:
|
||||
getState().setScanning(false);
|
||||
getState().updateScanResults();
|
||||
getState().updatePairedPeers();
|
||||
break;
|
||||
case BT_EVENT_PEER_FOUND:
|
||||
getState().updateScanResults();
|
||||
break;
|
||||
case BT_EVENT_PAIR_RESULT:
|
||||
getState().updatePairedPeers();
|
||||
break;
|
||||
case BT_EVENT_PROFILE_STATE_CHANGED:
|
||||
getState().updateScanResults();
|
||||
getState().updatePairedPeers();
|
||||
break;
|
||||
case BT_EVENT_RADIO_STATE_CHANGED:
|
||||
if (event.radio_state == BT_RADIO_STATE_ON) {
|
||||
getState().updatePairedPeers();
|
||||
struct Device* dev = bluetooth::findFirstDevice();
|
||||
if (dev && !bluetooth_is_scanning(dev)) {
|
||||
bluetooth_scan_start(dev);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
requestViewUpdate();
|
||||
}
|
||||
|
||||
static void onKernelBtEvent(struct Device* /*device*/, void* context, struct BtEvent event) {
|
||||
// BT event callbacks can fire from the NimBLE host task (e.g. DISCONNECT during
|
||||
// nimble_port_stop shutdown). Calling onBtEvent() synchronously from the NimBLE
|
||||
// task would block it on the LVGL mutex (held by the LVGL task waiting in
|
||||
// nimble_port_stop), creating a permanent deadlock. Dispatch to the main task so
|
||||
// the NimBLE host task is never blocked by BtManage's state updates or LVGL lock.
|
||||
auto* self = static_cast<BtManage*>(context);
|
||||
getMainDispatcher().dispatch([self, event] {
|
||||
self->onBtEvent(event);
|
||||
});
|
||||
}
|
||||
|
||||
void BtManage::onShow(AppContext& app, lv_obj_t* parent) {
|
||||
// Initialise state and view before subscribing to avoid incoming events
|
||||
// racing with state initialisation.
|
||||
state.setRadioState(bluetooth::getRadioState());
|
||||
struct Device* dev = bluetooth::findFirstDevice();
|
||||
state.setScanning(dev ? bluetooth_is_scanning(dev) : false);
|
||||
state.updateScanResults();
|
||||
state.updatePairedPeers();
|
||||
|
||||
lock();
|
||||
isViewEnabled = true;
|
||||
view.init(app, parent);
|
||||
view.update();
|
||||
unlock();
|
||||
|
||||
btDevice = dev;
|
||||
if (btDevice) {
|
||||
bluetooth_add_event_callback(btDevice, this, onKernelBtEvent);
|
||||
}
|
||||
|
||||
auto radio_state = bluetooth::getRadioState();
|
||||
bool can_scan = radio_state == bluetooth::RadioState::On;
|
||||
LOGGER.info("Radio: {}, Scanning: {}, Can scan: {}",
|
||||
bluetooth::radioStateToString(radio_state),
|
||||
dev ? bluetooth_is_scanning(dev) : false,
|
||||
can_scan);
|
||||
if (can_scan && dev && !bluetooth_is_scanning(dev)) {
|
||||
bluetooth_scan_start(dev);
|
||||
}
|
||||
}
|
||||
|
||||
void BtManage::onHide(AppContext& app) {
|
||||
lock();
|
||||
if (btDevice) {
|
||||
bluetooth_remove_event_callback(btDevice, onKernelBtEvent);
|
||||
btDevice = nullptr;
|
||||
}
|
||||
isViewEnabled = false;
|
||||
unlock();
|
||||
}
|
||||
|
||||
extern const AppManifest manifest = {
|
||||
.appId = "BtManage",
|
||||
.appName = "Bluetooth",
|
||||
.appIcon = LVGL_ICON_SHARED_BLUETOOTH,
|
||||
.appCategory = Category::Settings,
|
||||
.createApp = create<BtManage>
|
||||
};
|
||||
|
||||
LaunchId start() {
|
||||
return app::start(manifest.appId);
|
||||
}
|
||||
|
||||
} // namespace tt::app::btmanage
|
||||
44
Tactility/Source/app/btmanage/State.cpp
Normal file
44
Tactility/Source/app/btmanage/State.cpp
Normal file
@ -0,0 +1,44 @@
|
||||
#include <Tactility/app/btmanage/BtManagePrivate.h>
|
||||
|
||||
namespace tt::app::btmanage {
|
||||
|
||||
void State::setScanning(bool isScanning) {
|
||||
auto lock = mutex.asScopedLock();
|
||||
lock.lock();
|
||||
scanning = isScanning;
|
||||
}
|
||||
|
||||
bool State::isScanning() const {
|
||||
auto lock = mutex.asScopedLock();
|
||||
lock.lock();
|
||||
return scanning;
|
||||
}
|
||||
|
||||
void State::setRadioState(bluetooth::RadioState s) {
|
||||
auto lock = mutex.asScopedLock();
|
||||
lock.lock();
|
||||
radioState = s;
|
||||
}
|
||||
|
||||
bluetooth::RadioState State::getRadioState() const {
|
||||
auto lock = mutex.asScopedLock();
|
||||
lock.lock();
|
||||
return radioState;
|
||||
}
|
||||
|
||||
void State::updateScanResults() {
|
||||
// Fetch outside the lock to avoid holding it during a service call.
|
||||
auto results = bluetooth::getScanResults();
|
||||
auto lock = mutex.asScopedLock();
|
||||
lock.lock();
|
||||
scanResults = std::move(results);
|
||||
}
|
||||
|
||||
void State::updatePairedPeers() {
|
||||
auto peers = bluetooth::getPairedPeers();
|
||||
auto lock = mutex.asScopedLock();
|
||||
lock.lock();
|
||||
pairedPeers = std::move(peers);
|
||||
}
|
||||
|
||||
} // namespace tt::app::btmanage
|
||||
246
Tactility/Source/app/btmanage/View.cpp
Normal file
246
Tactility/Source/app/btmanage/View.cpp
Normal file
@ -0,0 +1,246 @@
|
||||
#include <format>
|
||||
#include <string>
|
||||
|
||||
#include <tactility/lvgl_module.h>
|
||||
|
||||
#include <Tactility/app/btmanage/View.h>
|
||||
#include <Tactility/app/btmanage/BtManagePrivate.h>
|
||||
#include <Tactility/app/btpeersettings/BtPeerSettings.h>
|
||||
#include <Tactility/Logger.h>
|
||||
#include <Tactility/lvgl/Style.h>
|
||||
#include <Tactility/lvgl/Toolbar.h>
|
||||
#include <Tactility/bluetooth/Bluetooth.h>
|
||||
#include <Tactility/bluetooth/BluetoothSettings.h>
|
||||
#include <Tactility/bluetooth/BluetoothPairedDevice.h>
|
||||
#include <Tactility/Tactility.h>
|
||||
|
||||
namespace tt::app::btmanage {
|
||||
|
||||
static const auto LOGGER = Logger("BtManageView");
|
||||
|
||||
static void onEnableSwitchChanged(lv_event_t* event) {
|
||||
auto* enable_switch = static_cast<lv_obj_t*>(lv_event_get_target(event));
|
||||
bool is_on = lv_obj_has_state(enable_switch, LV_STATE_CHECKED);
|
||||
auto bt = std::static_pointer_cast<BtManage>(getCurrentApp());
|
||||
bt->getBindings().onBtToggled(is_on);
|
||||
}
|
||||
|
||||
static void onEnableOnBootSwitchChanged(lv_event_t* event) {
|
||||
auto* enable_switch = static_cast<lv_obj_t*>(lv_event_get_target(event));
|
||||
bool is_on = lv_obj_has_state(enable_switch, LV_STATE_CHECKED);
|
||||
getMainDispatcher().dispatch([is_on] {
|
||||
bluetooth::settings::setEnableOnBoot(is_on);
|
||||
});
|
||||
}
|
||||
|
||||
static void onEnableOnBootParentClicked(lv_event_t* event) {
|
||||
auto* enable_switch = static_cast<lv_obj_t*>(lv_event_get_user_data(event));
|
||||
bool new_state = !lv_obj_has_state(enable_switch, LV_STATE_CHECKED);
|
||||
if (new_state) {
|
||||
lv_obj_add_state(enable_switch, LV_STATE_CHECKED);
|
||||
} else {
|
||||
lv_obj_remove_state(enable_switch, LV_STATE_CHECKED);
|
||||
}
|
||||
// add/remove_state does not fire LV_EVENT_VALUE_CHANGED, so persist here directly.
|
||||
getMainDispatcher().dispatch([new_state] {
|
||||
bluetooth::settings::setEnableOnBoot(new_state);
|
||||
});
|
||||
}
|
||||
|
||||
static void onScanButtonClicked(lv_event_t* event) {
|
||||
auto bt = std::static_pointer_cast<BtManage>(getCurrentApp());
|
||||
struct Device* dev = bluetooth::findFirstDevice();
|
||||
bool scanning = dev ? bluetooth_is_scanning(dev) : false;
|
||||
bt->getBindings().onScanToggled(!scanning);
|
||||
}
|
||||
|
||||
// region Peer list callbacks
|
||||
|
||||
struct PeerListItemData {
|
||||
View* view;
|
||||
size_t index;
|
||||
bool isPaired;
|
||||
};
|
||||
|
||||
void View::onConnect(lv_event_t* event) {
|
||||
auto* data = static_cast<PeerListItemData*>(lv_event_get_user_data(event));
|
||||
auto bt = std::static_pointer_cast<BtManage>(getCurrentApp());
|
||||
auto& state = bt->getState();
|
||||
|
||||
if (data->isPaired) {
|
||||
// Open the per-device settings screen for paired devices
|
||||
auto peers = state.getPairedPeers();
|
||||
if (data->index < peers.size()) {
|
||||
btpeersettings::start(bluetooth::settings::addrToHex(peers[data->index].addr));
|
||||
}
|
||||
} else {
|
||||
// Unrecognised scan result — initiate pairing
|
||||
auto peers = state.getScanResults();
|
||||
if (data->index < peers.size()) {
|
||||
bt->getBindings().onPairPeer(peers[data->index].addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// endregion Peer list callbacks
|
||||
|
||||
static uint8_t mapRssiToPercentage(int8_t rssi) {
|
||||
auto abs_rssi = std::abs(rssi);
|
||||
if (abs_rssi < 30) abs_rssi = 30;
|
||||
if (abs_rssi > 90) abs_rssi = 90;
|
||||
return static_cast<uint8_t>((float)(90 - abs_rssi) / 60.f * 100.f);
|
||||
}
|
||||
|
||||
void View::createPeerListItem(const bluetooth::PeerRecord& record, bool isPaired, size_t index) {
|
||||
const auto percentage = mapRssiToPercentage(record.rssi);
|
||||
const auto label = record.name.empty()
|
||||
? std::format("Unknown ({:02x}{:02x}{:02x}{:02x}{:02x}{:02x}) {}%",
|
||||
record.addr[0], record.addr[1], record.addr[2],
|
||||
record.addr[3], record.addr[4], record.addr[5],
|
||||
percentage)
|
||||
: std::format("{} {}%", record.name, percentage);
|
||||
|
||||
auto* button = lv_list_add_button(peers_list, nullptr, label.c_str());
|
||||
|
||||
auto* item_data = new PeerListItemData { this, index, isPaired };
|
||||
lv_obj_set_user_data(button, item_data);
|
||||
lv_obj_add_event_cb(button, onConnect, LV_EVENT_SHORT_CLICKED, item_data);
|
||||
lv_obj_add_event_cb(button, [](lv_event_t* e) {
|
||||
delete static_cast<PeerListItemData*>(lv_obj_get_user_data(lv_event_get_current_target_obj(e)));
|
||||
}, LV_EVENT_DELETE, nullptr);
|
||||
}
|
||||
|
||||
// region Secondary updates
|
||||
|
||||
void View::updateBtToggle() {
|
||||
lv_obj_clear_state(enable_switch, LV_STATE_ANY);
|
||||
switch (state->getRadioState()) {
|
||||
using enum bluetooth::RadioState;
|
||||
case On:
|
||||
lv_obj_add_state(enable_switch, LV_STATE_CHECKED);
|
||||
break;
|
||||
case OnPending:
|
||||
lv_obj_add_state(enable_switch, LV_STATE_CHECKED);
|
||||
lv_obj_add_state(enable_switch, LV_STATE_DISABLED);
|
||||
break;
|
||||
case Off:
|
||||
lv_obj_remove_state(enable_switch, LV_STATE_CHECKED);
|
||||
lv_obj_remove_state(enable_switch, LV_STATE_DISABLED);
|
||||
break;
|
||||
case OffPending:
|
||||
lv_obj_remove_state(enable_switch, LV_STATE_CHECKED);
|
||||
lv_obj_add_state(enable_switch, LV_STATE_DISABLED);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void View::updateEnableOnBootToggle() {
|
||||
if (enable_on_boot_switch != nullptr) {
|
||||
lv_obj_clear_state(enable_on_boot_switch, LV_STATE_ANY);
|
||||
if (bluetooth::settings::shouldEnableOnBoot()) {
|
||||
lv_obj_add_state(enable_on_boot_switch, LV_STATE_CHECKED);
|
||||
} else {
|
||||
lv_obj_remove_state(enable_on_boot_switch, LV_STATE_CHECKED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void View::updateScanning() {
|
||||
if (state->getRadioState() == bluetooth::RadioState::On && state->isScanning()) {
|
||||
lv_obj_remove_flag(scanning_spinner, LV_OBJ_FLAG_HIDDEN);
|
||||
} else {
|
||||
lv_obj_add_flag(scanning_spinner, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
}
|
||||
|
||||
void View::updatePeerList() {
|
||||
lv_obj_clean(peers_list);
|
||||
|
||||
// Enable on boot row
|
||||
auto* enable_on_boot_wrapper = lv_obj_create(peers_list);
|
||||
lv_obj_set_size(enable_on_boot_wrapper, LV_PCT(100), LV_SIZE_CONTENT);
|
||||
lv_obj_set_style_pad_all(enable_on_boot_wrapper, 0, LV_STATE_DEFAULT);
|
||||
lv_obj_set_style_border_width(enable_on_boot_wrapper, 0, LV_STATE_DEFAULT);
|
||||
|
||||
auto* enable_label = lv_label_create(enable_on_boot_wrapper);
|
||||
lv_label_set_text(enable_label, "Enable on boot");
|
||||
lv_obj_align(enable_label, LV_ALIGN_LEFT_MID, 0, 0);
|
||||
|
||||
enable_on_boot_switch = lv_switch_create(enable_on_boot_wrapper);
|
||||
lv_obj_align(enable_on_boot_switch, LV_ALIGN_RIGHT_MID, 0, 0);
|
||||
lv_obj_add_event_cb(enable_on_boot_switch, onEnableOnBootSwitchChanged, LV_EVENT_VALUE_CHANGED, nullptr);
|
||||
lv_obj_add_event_cb(enable_on_boot_wrapper, onEnableOnBootParentClicked, LV_EVENT_SHORT_CLICKED, enable_on_boot_switch);
|
||||
|
||||
if (lvgl_get_ui_density() == LVGL_UI_DENSITY_COMPACT) {
|
||||
lv_obj_set_style_pad_ver(enable_on_boot_wrapper, 2, LV_STATE_DEFAULT);
|
||||
} else {
|
||||
lv_obj_set_style_pad_ver(enable_on_boot_wrapper, 8, LV_STATE_DEFAULT);
|
||||
}
|
||||
|
||||
updateEnableOnBootToggle();
|
||||
|
||||
using enum bluetooth::RadioState;
|
||||
if (state->getRadioState() == On) {
|
||||
// Paired peers section
|
||||
auto paired = state->getPairedPeers();
|
||||
if (!paired.empty()) {
|
||||
lv_list_add_text(peers_list, "Paired");
|
||||
for (size_t i = 0; i < paired.size(); ++i) {
|
||||
createPeerListItem(paired[i], true, i);
|
||||
}
|
||||
}
|
||||
|
||||
// Scan results section
|
||||
auto scan_results = state->getScanResults();
|
||||
lv_list_add_text(peers_list, "Available");
|
||||
if (!scan_results.empty()) {
|
||||
for (size_t i = 0; i < scan_results.size(); ++i) {
|
||||
createPeerListItem(scan_results[i], false, i);
|
||||
}
|
||||
} else if (!state->isScanning()) {
|
||||
auto* no_devices_label = lv_label_create(peers_list);
|
||||
lv_label_set_text(no_devices_label, "No devices found.");
|
||||
}
|
||||
// Never hide peers_list: it always contains the "Enable on boot" row.
|
||||
// While scanning with no results the spinner in the toolbar provides feedback.
|
||||
|
||||
// Scan button
|
||||
auto* scan_button = lv_button_create(peers_list);
|
||||
lv_obj_set_width(scan_button, LV_PCT(100));
|
||||
lv_obj_set_style_margin_ver(scan_button, 4, LV_STATE_DEFAULT);
|
||||
auto* scan_label = lv_label_create(scan_button);
|
||||
lv_label_set_text(scan_label, state->isScanning() ? "Stop scan" : "Scan");
|
||||
lv_obj_add_event_cb(scan_button, onScanButtonClicked, LV_EVENT_SHORT_CLICKED, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
// endregion Secondary updates
|
||||
|
||||
void View::init(const AppContext& app, lv_obj_t* parent) {
|
||||
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT);
|
||||
|
||||
root = parent;
|
||||
paths = app.getPaths();
|
||||
|
||||
// Toolbar
|
||||
auto* toolbar = lvgl::toolbar_create(parent, app);
|
||||
|
||||
scanning_spinner = lvgl::toolbar_add_spinner_action(toolbar);
|
||||
|
||||
enable_switch = lvgl::toolbar_add_switch_action(toolbar);
|
||||
lv_obj_add_event_cb(enable_switch, onEnableSwitchChanged, LV_EVENT_VALUE_CHANGED, nullptr);
|
||||
|
||||
// Peer list
|
||||
peers_list = lv_list_create(parent);
|
||||
lv_obj_set_flex_grow(peers_list, 1);
|
||||
lv_obj_set_width(peers_list, LV_PCT(100));
|
||||
}
|
||||
|
||||
void View::update() {
|
||||
updateBtToggle();
|
||||
updateScanning();
|
||||
updatePeerList();
|
||||
}
|
||||
|
||||
} // namespace tt::app::btmanage
|
||||
227
Tactility/Source/app/btpeersettings/BtPeerSettings.cpp
Normal file
227
Tactility/Source/app/btpeersettings/BtPeerSettings.cpp
Normal file
@ -0,0 +1,227 @@
|
||||
#include <Tactility/app/btpeersettings/BtPeerSettings.h>
|
||||
|
||||
#include <Tactility/Logger.h>
|
||||
#include <Tactility/LogMessages.h>
|
||||
#include <Tactility/app/App.h>
|
||||
#include <Tactility/app/AppContext.h>
|
||||
#include <Tactility/app/AppManifest.h>
|
||||
#include <Tactility/app/alertdialog/AlertDialog.h>
|
||||
#include <Tactility/lvgl/LvglSync.h>
|
||||
#include <Tactility/lvgl/Style.h>
|
||||
#include <Tactility/lvgl/Toolbar.h>
|
||||
#include <Tactility/bluetooth/Bluetooth.h>
|
||||
#include <Tactility/bluetooth/BluetoothPairedDevice.h>
|
||||
#include <tactility/check.h>
|
||||
#include <tactility/drivers/bluetooth.h>
|
||||
|
||||
#include <lvgl.h>
|
||||
|
||||
namespace tt::app::btpeersettings {
|
||||
|
||||
static const auto LOGGER = Logger("BtPeerSettings");
|
||||
|
||||
extern const AppManifest manifest;
|
||||
|
||||
void start(const std::string& addrHex) {
|
||||
auto bundle = std::make_shared<Bundle>();
|
||||
bundle->putString("addr", addrHex);
|
||||
app::start(manifest.appId, bundle);
|
||||
}
|
||||
|
||||
class BtPeerSettings : public App {
|
||||
|
||||
bool viewEnabled = false;
|
||||
lv_obj_t* connectButton = nullptr;
|
||||
lv_obj_t* disconnectButton = nullptr;
|
||||
std::string addrHex;
|
||||
std::array<uint8_t, 6> addr = {};
|
||||
int profileId = BT_PROFILE_HID_HOST;
|
||||
bool isCurrentlyConnected() const {
|
||||
for (const auto& p : bluetooth::getPairedPeers()) {
|
||||
if (p.addr == addr) return p.connected;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void onPressConnect(lv_event_t* event) {
|
||||
auto* self = static_cast<BtPeerSettings*>(lv_event_get_user_data(event));
|
||||
if (self->profileId == BT_PROFILE_HID_HOST) {
|
||||
bluetooth::hidHostConnect(self->addr);
|
||||
} else {
|
||||
bluetooth::connect(self->addr, self->profileId);
|
||||
}
|
||||
lv_obj_add_state(lv_event_get_target_obj(event), LV_STATE_DISABLED);
|
||||
}
|
||||
|
||||
static void onPressDisconnect(lv_event_t* event) {
|
||||
auto* self = static_cast<BtPeerSettings*>(lv_event_get_user_data(event));
|
||||
if (self->profileId == BT_PROFILE_HID_HOST) {
|
||||
bluetooth::hidHostDisconnect();
|
||||
} else {
|
||||
bluetooth::disconnect(self->addr, self->profileId);
|
||||
}
|
||||
lv_obj_add_state(lv_event_get_target_obj(event), LV_STATE_DISABLED);
|
||||
}
|
||||
|
||||
static void onPressForget(lv_event_t* event) {
|
||||
std::vector<std::string> choices = { "Yes", "No" };
|
||||
alertdialog::start("Confirmation", "Forget this device?", choices);
|
||||
}
|
||||
|
||||
static void onToggleAutoConnect(lv_event_t* event) {
|
||||
auto* self = static_cast<BtPeerSettings*>(lv_event_get_user_data(event));
|
||||
bool is_on = lv_obj_has_state(lv_event_get_target_obj(event), LV_STATE_CHECKED);
|
||||
bluetooth::settings::PairedDevice device;
|
||||
if (bluetooth::settings::load(self->addrHex, device)) {
|
||||
device.autoConnect = is_on;
|
||||
if (!bluetooth::settings::save(device)) {
|
||||
LOGGER.error("Failed to save auto-connect setting");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void requestViewUpdate() const {
|
||||
if (viewEnabled) {
|
||||
if (lvgl::lock(1000)) {
|
||||
updateViews();
|
||||
lvgl::unlock();
|
||||
} else {
|
||||
LOGGER.error(LOG_MESSAGE_MUTEX_LOCK_FAILED_FMT, "LVGL");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void updateViews() const {
|
||||
if (isCurrentlyConnected()) {
|
||||
lv_obj_remove_flag(disconnectButton, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_add_flag(connectButton, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_remove_state(disconnectButton, LV_STATE_DISABLED);
|
||||
} else {
|
||||
lv_obj_add_flag(disconnectButton, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_remove_flag(connectButton, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_remove_state(connectButton, LV_STATE_DISABLED);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
void onCreate(AppContext& app) override {
|
||||
const auto parameters = app.getParameters();
|
||||
check(parameters != nullptr, "Parameters missing");
|
||||
addrHex = parameters->getString("addr");
|
||||
|
||||
// Load addr and profileId from stored settings — avoids manual hex parsing
|
||||
// (std::stoul throws on invalid input and exceptions are disabled).
|
||||
bluetooth::settings::PairedDevice device;
|
||||
if (bluetooth::settings::load(addrHex, device)) {
|
||||
addr = device.addr;
|
||||
profileId = device.profileId;
|
||||
}
|
||||
}
|
||||
|
||||
static void onKernelBtEvent(struct Device* /*device*/, void* context, struct BtEvent /*event*/) {
|
||||
static_cast<BtPeerSettings*>(context)->requestViewUpdate();
|
||||
}
|
||||
|
||||
void onShow(AppContext& app, lv_obj_t* parent) override {
|
||||
if (struct Device* dev = bluetooth::findFirstDevice()) {
|
||||
bluetooth_add_event_callback(dev, this, onKernelBtEvent);
|
||||
}
|
||||
|
||||
// Load stored settings (name, autoConnect)
|
||||
bluetooth::settings::PairedDevice device;
|
||||
bool deviceLoaded = bluetooth::settings::load(addrHex, device);
|
||||
std::string title = (deviceLoaded && !device.name.empty()) ? device.name : addrHex;
|
||||
|
||||
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT);
|
||||
|
||||
lvgl::toolbar_create(parent, title);
|
||||
|
||||
auto* wrapper = lv_obj_create(parent);
|
||||
lv_obj_set_width(wrapper, LV_PCT(100));
|
||||
lv_obj_set_flex_grow(wrapper, 1);
|
||||
lv_obj_set_flex_flow(wrapper, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_border_width(wrapper, 0, LV_STATE_DEFAULT);
|
||||
lvgl::obj_set_style_bg_invisible(wrapper);
|
||||
|
||||
connectButton = lv_button_create(wrapper);
|
||||
lv_obj_set_width(connectButton, LV_PCT(100));
|
||||
lv_obj_add_event_cb(connectButton, onPressConnect, LV_EVENT_SHORT_CLICKED, this);
|
||||
auto* connect_label = lv_label_create(connectButton);
|
||||
lv_obj_align(connect_label, LV_ALIGN_CENTER, 0, 0);
|
||||
lv_label_set_text(connect_label, "Connect");
|
||||
|
||||
disconnectButton = lv_button_create(wrapper);
|
||||
lv_obj_set_width(disconnectButton, LV_PCT(100));
|
||||
lv_obj_add_event_cb(disconnectButton, onPressDisconnect, LV_EVENT_SHORT_CLICKED, this);
|
||||
auto* disconnect_label = lv_label_create(disconnectButton);
|
||||
lv_obj_align(disconnect_label, LV_ALIGN_CENTER, 0, 0);
|
||||
lv_label_set_text(disconnect_label, "Disconnect");
|
||||
|
||||
auto* forget_button = lv_button_create(wrapper);
|
||||
lv_obj_set_width(forget_button, LV_PCT(100));
|
||||
lv_obj_add_event_cb(forget_button, onPressForget, LV_EVENT_SHORT_CLICKED, this);
|
||||
auto* forget_label = lv_label_create(forget_button);
|
||||
lv_obj_align(forget_label, LV_ALIGN_CENTER, 0, 0);
|
||||
lv_label_set_text(forget_label, "Forget");
|
||||
|
||||
// Auto-connect toggle row
|
||||
auto* auto_connect_wrapper = lv_obj_create(wrapper);
|
||||
lv_obj_set_size(auto_connect_wrapper, LV_PCT(100), LV_SIZE_CONTENT);
|
||||
lvgl::obj_set_style_bg_invisible(auto_connect_wrapper);
|
||||
lv_obj_set_style_pad_all(auto_connect_wrapper, 0, LV_STATE_DEFAULT);
|
||||
lv_obj_set_style_border_width(auto_connect_wrapper, 0, LV_STATE_DEFAULT);
|
||||
|
||||
auto* auto_connect_label = lv_label_create(auto_connect_wrapper);
|
||||
lv_label_set_text(auto_connect_label, "Auto-connect");
|
||||
lv_obj_align(auto_connect_label, LV_ALIGN_LEFT_MID, 0, 0);
|
||||
|
||||
auto* auto_connect_switch = lv_switch_create(auto_connect_wrapper);
|
||||
lv_obj_add_event_cb(auto_connect_switch, onToggleAutoConnect, LV_EVENT_VALUE_CHANGED, this);
|
||||
lv_obj_align(auto_connect_switch, LV_ALIGN_RIGHT_MID, 0, 0);
|
||||
|
||||
if (deviceLoaded && device.autoConnect) {
|
||||
lv_obj_add_state(auto_connect_switch, LV_STATE_CHECKED);
|
||||
} else {
|
||||
lv_obj_remove_state(auto_connect_switch, LV_STATE_CHECKED);
|
||||
}
|
||||
|
||||
viewEnabled = true;
|
||||
updateViews();
|
||||
}
|
||||
|
||||
void onHide(AppContext& app) override {
|
||||
if (struct Device* dev = bluetooth::findFirstDevice()) {
|
||||
bluetooth_remove_event_callback(dev, onKernelBtEvent);
|
||||
}
|
||||
viewEnabled = false;
|
||||
}
|
||||
|
||||
void onResult(AppContext& appContext, LaunchId /*launchId*/, Result result, std::unique_ptr<Bundle> bundle) override {
|
||||
if (result != Result::Ok || bundle == nullptr) return;
|
||||
if (alertdialog::getResultIndex(*bundle) != 0) return; // 0 = Yes
|
||||
|
||||
// Disconnect first if connected
|
||||
if (isCurrentlyConnected()) {
|
||||
if (profileId == BT_PROFILE_HID_HOST) {
|
||||
bluetooth::hidHostDisconnect();
|
||||
} else {
|
||||
bluetooth::disconnect(addr, profileId);
|
||||
}
|
||||
}
|
||||
|
||||
bluetooth::unpair(addr);
|
||||
stop();
|
||||
}
|
||||
};
|
||||
|
||||
extern const AppManifest manifest = {
|
||||
.appId = "BtPeerSettings",
|
||||
.appName = "BT Device Settings",
|
||||
.appCategory = Category::System,
|
||||
.appFlags = AppManifest::Flags::Hidden,
|
||||
.createApp = create<BtPeerSettings>
|
||||
};
|
||||
|
||||
} // namespace tt::app::btpeersettings
|
||||
401
Tactility/Source/bluetooth/Bluetooth.cpp
Normal file
401
Tactility/Source/bluetooth/Bluetooth.cpp
Normal file
@ -0,0 +1,401 @@
|
||||
#ifdef ESP_PLATFORM
|
||||
#include <sdkconfig.h>
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_BT_NIMBLE_ENABLED)
|
||||
|
||||
#include <Tactility/bluetooth/Bluetooth.h>
|
||||
#include <Tactility/bluetooth/BluetoothPairedDevice.h>
|
||||
#include <Tactility/bluetooth/BluetoothSettings.h>
|
||||
#include <Tactility/bluetooth/BluetoothPrivate.h>
|
||||
|
||||
#include <Tactility/Logger.h>
|
||||
#include <Tactility/Mutex.h>
|
||||
#include <Tactility/Tactility.h>
|
||||
#include <tactility/check.h>
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/drivers/bluetooth.h>
|
||||
#include <tactility/drivers/bluetooth_hid_device.h>
|
||||
#include <tactility/drivers/bluetooth_midi.h>
|
||||
#include <tactility/drivers/bluetooth_serial.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
namespace tt::bluetooth {
|
||||
|
||||
static const auto LOGGER = Logger("Bluetooth");
|
||||
|
||||
// ---- Scan result cache (C++ PeerRecord list, updated from BT_EVENT_PEER_FOUND) ----
|
||||
|
||||
static Mutex scan_cache_mutex;
|
||||
static std::vector<PeerRecord> scan_results_cache;
|
||||
|
||||
struct CachedAddr {
|
||||
uint8_t addr[6];
|
||||
uint8_t addr_type;
|
||||
};
|
||||
static std::vector<CachedAddr> scan_addr_cache; // parallel to scan_results_cache
|
||||
|
||||
// ---- Device accessor ----
|
||||
|
||||
struct Device* findFirstDevice() {
|
||||
struct Device* found = nullptr;
|
||||
device_for_each_of_type(&BLUETOOTH_TYPE, &found, [](struct Device* dev, void* ctx) -> bool {
|
||||
if (device_is_ready(dev)) {
|
||||
*static_cast<struct Device**>(ctx) = dev;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return found;
|
||||
}
|
||||
|
||||
// ---- Scan cache helpers ----
|
||||
|
||||
void cacheScanAddr(const uint8_t addr[6], uint8_t addr_type) {
|
||||
auto lock = scan_cache_mutex.asScopedLock();
|
||||
lock.lock();
|
||||
for (auto& entry : scan_addr_cache) {
|
||||
if (memcmp(entry.addr, addr, 6) == 0) {
|
||||
entry.addr_type = addr_type;
|
||||
return;
|
||||
}
|
||||
}
|
||||
CachedAddr e = {};
|
||||
memcpy(e.addr, addr, 6);
|
||||
e.addr_type = addr_type;
|
||||
scan_addr_cache.push_back(e);
|
||||
}
|
||||
|
||||
bool getCachedScanAddrType(const uint8_t addr[6], uint8_t* addr_type_out) {
|
||||
auto lock = scan_cache_mutex.asScopedLock();
|
||||
lock.lock();
|
||||
for (const auto& entry : scan_addr_cache) {
|
||||
if (memcmp(entry.addr, addr, 6) == 0) {
|
||||
if (addr_type_out) *addr_type_out = entry.addr_type;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (addr_type_out) *addr_type_out = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
static void cachePeerRecord(const BtPeerRecord& krecord) {
|
||||
PeerRecord rec;
|
||||
memcpy(rec.addr.data(), krecord.addr, 6);
|
||||
rec.name = krecord.name[0] != '\0' ? krecord.name : "";
|
||||
rec.rssi = krecord.rssi;
|
||||
rec.paired = krecord.paired;
|
||||
rec.connected = krecord.connected;
|
||||
rec.profileId = 0;
|
||||
|
||||
cacheScanAddr(krecord.addr, krecord.addr_type);
|
||||
|
||||
auto lock = scan_cache_mutex.asScopedLock();
|
||||
lock.lock();
|
||||
for (auto& existing : scan_results_cache) {
|
||||
if (existing.addr == rec.addr) {
|
||||
if (!rec.name.empty()) existing.name = rec.name;
|
||||
existing.rssi = rec.rssi;
|
||||
return;
|
||||
}
|
||||
}
|
||||
scan_results_cache.push_back(std::move(rec));
|
||||
}
|
||||
|
||||
// ---- Bridge callback (registered with kernel driver) ----
|
||||
// This callback listens to platform driver events to perform auto-start logic
|
||||
// and settings management. Consumers should register their own callbacks via
|
||||
// bluetooth_add_event_callback() to receive events directly.
|
||||
|
||||
static void bt_event_bridge(struct Device* /*device*/, void* /*context*/, struct BtEvent event) {
|
||||
switch (event.type) {
|
||||
case BT_EVENT_RADIO_STATE_CHANGED:
|
||||
switch (event.radio_state) {
|
||||
case BT_RADIO_STATE_ON:
|
||||
getMainDispatcher().dispatch([] {
|
||||
auto peers = settings::loadAll();
|
||||
bool has_hid_host_auto = false;
|
||||
bool has_hid_device_auto = false;
|
||||
for (const auto& p : peers) {
|
||||
if (!p.autoConnect) continue;
|
||||
if (p.profileId == BT_PROFILE_HID_HOST) has_hid_host_auto = true;
|
||||
if (p.profileId == BT_PROFILE_HID_DEVICE) has_hid_device_auto = true;
|
||||
}
|
||||
if (has_hid_host_auto) {
|
||||
LOGGER.info("HID host auto-connect peer found — starting scan");
|
||||
if (struct Device* dev = findFirstDevice()) {
|
||||
bluetooth_scan_start(dev);
|
||||
}
|
||||
} else if (has_hid_device_auto) {
|
||||
LOGGER.info("HID device auto-start (bonded peer found)");
|
||||
if (struct Device* dev = bluetooth_hid_device_get_device()) {
|
||||
bluetooth_hid_device_start(dev, BT_HID_DEVICE_MODE_KEYBOARD);
|
||||
}
|
||||
} else {
|
||||
if (settings::shouldSppAutoStart()) {
|
||||
LOGGER.info("Auto-starting SPP server");
|
||||
if (struct Device* dev = bluetooth_serial_get_device()) {
|
||||
bluetooth_serial_start(dev);
|
||||
}
|
||||
}
|
||||
if (settings::shouldMidiAutoStart()) {
|
||||
LOGGER.info("Auto-starting MIDI server");
|
||||
if (struct Device* dev = bluetooth_midi_get_device()) {
|
||||
bluetooth_midi_start(dev);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case BT_EVENT_SCAN_STARTED:
|
||||
{
|
||||
auto lock = scan_cache_mutex.asScopedLock();
|
||||
lock.lock();
|
||||
scan_results_cache.clear();
|
||||
scan_addr_cache.clear();
|
||||
}
|
||||
break;
|
||||
|
||||
case BT_EVENT_SCAN_FINISHED:
|
||||
getMainDispatcher().dispatch([] { autoConnectHidHost(); });
|
||||
break;
|
||||
|
||||
case BT_EVENT_PEER_FOUND:
|
||||
cachePeerRecord(event.peer);
|
||||
break;
|
||||
|
||||
case BT_EVENT_PAIR_RESULT:
|
||||
if (event.pair_result.result == BT_PAIR_RESULT_SUCCESS) {
|
||||
uint8_t addr_buf[6];
|
||||
int profile_copy = event.pair_result.profile;
|
||||
memcpy(addr_buf, event.pair_result.addr, 6);
|
||||
getMainDispatcher().dispatch([addr_buf, profile_copy]() mutable {
|
||||
std::array<uint8_t, 6> peer_addr;
|
||||
memcpy(peer_addr.data(), addr_buf, 6);
|
||||
const auto hex = settings::addrToHex(peer_addr);
|
||||
if (!settings::hasFileForDevice(hex)) {
|
||||
settings::PairedDevice dev;
|
||||
dev.addr = peer_addr;
|
||||
dev.name = "";
|
||||
dev.autoConnect = true;
|
||||
dev.profileId = profile_copy;
|
||||
if (settings::save(dev)) {
|
||||
LOGGER.info("Saved paired peer {} (profile={})", hex, profile_copy);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (event.pair_result.result == BT_PAIR_RESULT_BOND_LOST) {
|
||||
uint8_t addr_buf[6];
|
||||
memcpy(addr_buf, event.pair_result.addr, 6);
|
||||
getMainDispatcher().dispatch([addr_buf]() mutable {
|
||||
std::array<uint8_t, 6> peer_addr;
|
||||
memcpy(peer_addr.data(), addr_buf, 6);
|
||||
settings::remove(settings::addrToHex(peer_addr));
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case BT_EVENT_PROFILE_STATE_CHANGED:
|
||||
if (event.profile_state.state == BT_PROFILE_STATE_CONNECTED) {
|
||||
uint8_t addr_buf[6];
|
||||
int profile_copy = (int)event.profile_state.profile;
|
||||
memcpy(addr_buf, event.profile_state.addr, 6);
|
||||
getMainDispatcher().dispatch([addr_buf, profile_copy]() mutable {
|
||||
std::array<uint8_t, 6> peer_addr;
|
||||
memcpy(peer_addr.data(), addr_buf, 6);
|
||||
const auto hex = settings::addrToHex(peer_addr);
|
||||
settings::PairedDevice stored;
|
||||
if (settings::load(hex, stored) && stored.profileId != profile_copy) {
|
||||
stored.profileId = profile_copy;
|
||||
settings::save(stored);
|
||||
}
|
||||
});
|
||||
// TODO: Fix auto reconnect if user manually disconnects
|
||||
} else if (event.profile_state.state == BT_PROFILE_STATE_IDLE &&
|
||||
event.profile_state.profile == BT_PROFILE_HID_HOST) {
|
||||
// HID host disconnected — check if any peer has autoConnect and re-scan
|
||||
// so that autoConnectHidHost() fires when the scan finishes.
|
||||
getMainDispatcher().dispatch([] {
|
||||
auto peers = settings::loadAll();
|
||||
bool has_auto = false;
|
||||
for (const auto& p : peers) {
|
||||
if (p.autoConnect && p.profileId == BT_PROFILE_HID_HOST) {
|
||||
has_auto = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (has_auto) {
|
||||
if (struct Device* dev = findFirstDevice()) {
|
||||
if (!bluetooth_is_scanning(dev)) {
|
||||
bluetooth_scan_start(dev);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- systemStart ----
|
||||
|
||||
void systemStart() {
|
||||
struct Device* dev = findFirstDevice();
|
||||
if (dev == nullptr) {
|
||||
LOGGER.warn("systemStart: no BLE device found");
|
||||
return;
|
||||
}
|
||||
bluetooth_add_event_callback(dev, nullptr, bt_event_bridge);
|
||||
|
||||
if (settings::shouldEnableOnBoot()) {
|
||||
LOGGER.info("Auto-enabling Bluetooth on boot");
|
||||
bluetooth_set_radio_enabled(dev, true);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Public API ----
|
||||
|
||||
const char* radioStateToString(RadioState state) {
|
||||
switch (state) {
|
||||
using enum RadioState;
|
||||
case Off: return "Off";
|
||||
case OnPending: return "OnPending";
|
||||
case On: return "On";
|
||||
case OffPending: return "OffPending";
|
||||
}
|
||||
check(false, "not implemented");
|
||||
}
|
||||
|
||||
RadioState getRadioState() {
|
||||
struct Device* dev = findFirstDevice();
|
||||
if (dev == nullptr) return RadioState::Off;
|
||||
BtRadioState state = BT_RADIO_STATE_OFF;
|
||||
bluetooth_get_radio_state(dev, &state);
|
||||
switch (state) {
|
||||
case BT_RADIO_STATE_OFF: return RadioState::Off;
|
||||
case BT_RADIO_STATE_ON_PENDING: return RadioState::OnPending;
|
||||
case BT_RADIO_STATE_ON: return RadioState::On;
|
||||
case BT_RADIO_STATE_OFF_PENDING: return RadioState::OffPending;
|
||||
}
|
||||
return RadioState::Off;
|
||||
}
|
||||
|
||||
std::vector<PeerRecord> getScanResults() {
|
||||
auto lock = scan_cache_mutex.asScopedLock();
|
||||
lock.lock();
|
||||
return scan_results_cache;
|
||||
}
|
||||
|
||||
std::vector<PeerRecord> getPairedPeers() {
|
||||
auto stored = settings::loadAll();
|
||||
std::vector<PeerRecord> result;
|
||||
result.reserve(stored.size());
|
||||
std::array<uint8_t, 6> connected_addr = {};
|
||||
bool hid_host_connected = hidHostGetConnectedPeer(connected_addr);
|
||||
for (const auto& device : stored) {
|
||||
PeerRecord record;
|
||||
record.addr = device.addr;
|
||||
record.name = device.name;
|
||||
record.rssi = 0;
|
||||
record.paired = true;
|
||||
record.profileId = device.profileId;
|
||||
record.connected = hid_host_connected && device.addr == connected_addr;
|
||||
result.push_back(std::move(record));
|
||||
}
|
||||
// Synthesize fallback: LittleFS readdir can lag behind fwrite by one tick, so the
|
||||
// connected peer may not appear in loadAll() yet. Always ensure it is in the list.
|
||||
if (hid_host_connected) {
|
||||
bool found = false;
|
||||
for (const auto& r : result) {
|
||||
if (r.addr == connected_addr) { found = true; break; }
|
||||
}
|
||||
if (!found) {
|
||||
PeerRecord record;
|
||||
record.addr = connected_addr;
|
||||
record.rssi = 0;
|
||||
record.paired = true;
|
||||
record.connected = true;
|
||||
record.profileId = BT_PROFILE_HID_HOST;
|
||||
// Try to get the name from the scan cache.
|
||||
{
|
||||
auto lock = scan_cache_mutex.asScopedLock();
|
||||
lock.lock();
|
||||
for (const auto& sr : scan_results_cache) {
|
||||
if (sr.addr == connected_addr) { record.name = sr.name; break; }
|
||||
}
|
||||
}
|
||||
result.push_back(std::move(record));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void pair(const std::array<uint8_t, 6>& /*addr*/) {
|
||||
// Pairing is handled automatically during connection by NimBLE SM.
|
||||
}
|
||||
|
||||
void unpair(const std::array<uint8_t, 6>& addr) {
|
||||
struct Device* dev = findFirstDevice();
|
||||
if (dev != nullptr) {
|
||||
bluetooth_unpair(dev, addr.data());
|
||||
}
|
||||
settings::remove(settings::addrToHex(addr));
|
||||
}
|
||||
|
||||
void connect(const std::array<uint8_t, 6>& addr, int profileId) {
|
||||
LOGGER.info("connect(profile={})", profileId);
|
||||
if (profileId == BT_PROFILE_HID_HOST) {
|
||||
hidHostConnect(addr);
|
||||
} else if (profileId == BT_PROFILE_HID_DEVICE) {
|
||||
if (struct Device* dev = bluetooth_hid_device_get_device()) {
|
||||
bluetooth_hid_device_start(dev, BT_HID_DEVICE_MODE_KEYBOARD);
|
||||
}
|
||||
} else if (profileId == BT_PROFILE_SPP) {
|
||||
if (struct Device* dev = bluetooth_serial_get_device()) {
|
||||
bluetooth_serial_start(dev);
|
||||
settings::setSppAutoStart(true);
|
||||
}
|
||||
} else if (profileId == BT_PROFILE_MIDI) {
|
||||
if (struct Device* dev = bluetooth_midi_get_device()) {
|
||||
bluetooth_midi_start(dev);
|
||||
settings::setMidiAutoStart(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void disconnect(const std::array<uint8_t, 6>& addr, int profileId) {
|
||||
LOGGER.info("disconnect(profile={})", profileId);
|
||||
if (profileId == BT_PROFILE_HID_HOST) {
|
||||
hidHostDisconnect();
|
||||
} else if (profileId == BT_PROFILE_HID_DEVICE) {
|
||||
if (struct Device* dev = bluetooth_hid_device_get_device()) {
|
||||
bluetooth_hid_device_stop(dev);
|
||||
}
|
||||
} else {
|
||||
struct Device* dev = findFirstDevice();
|
||||
if (dev == nullptr) return;
|
||||
bluetooth_disconnect(dev, addr.data(), (BtProfileId)profileId);
|
||||
}
|
||||
}
|
||||
|
||||
bool isProfileSupported(int profileId) {
|
||||
return profileId == BT_PROFILE_HID_HOST ||
|
||||
profileId == BT_PROFILE_HID_DEVICE ||
|
||||
profileId == BT_PROFILE_SPP ||
|
||||
profileId == BT_PROFILE_MIDI;
|
||||
}
|
||||
|
||||
} // namespace tt::bluetooth
|
||||
|
||||
#endif // CONFIG_BT_NIMBLE_ENABLED
|
||||
885
Tactility/Source/bluetooth/BluetoothHidHost.cpp
Normal file
885
Tactility/Source/bluetooth/BluetoothHidHost.cpp
Normal file
@ -0,0 +1,885 @@
|
||||
#ifdef ESP_PLATFORM
|
||||
#include <sdkconfig.h>
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_BT_NIMBLE_ENABLED)
|
||||
|
||||
#include <Tactility/bluetooth/Bluetooth.h>
|
||||
#include <Tactility/bluetooth/BluetoothPairedDevice.h>
|
||||
#include <Tactility/bluetooth/BluetoothPrivate.h>
|
||||
|
||||
#include <Tactility/Assets.h>
|
||||
#include <Tactility/Logger.h>
|
||||
#include <Tactility/Tactility.h>
|
||||
#include <Tactility/lvgl/Keyboard.h>
|
||||
#include <Tactility/lvgl/LvglSync.h>
|
||||
|
||||
#include <host/ble_gap.h>
|
||||
#include <host/ble_gatt.h>
|
||||
#include <host/ble_hs.h>
|
||||
#include <host/ble_uuid.h>
|
||||
#include <esp_timer.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/queue.h>
|
||||
#include <lvgl.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#define TAG "BtHidHost"
|
||||
#include <esp_log.h>
|
||||
|
||||
namespace tt::bluetooth {
|
||||
|
||||
static const auto LOGGER = Logger("BtHidHost");
|
||||
|
||||
// ---- Report type ----
|
||||
|
||||
enum class HidReportType : uint8_t { Unknown = 0, Keyboard, Mouse, Consumer };
|
||||
|
||||
struct HidHostInputRpt {
|
||||
uint16_t valHandle;
|
||||
uint16_t cccdHandle;
|
||||
uint16_t rptRefHandle;
|
||||
uint8_t reportId;
|
||||
HidReportType type;
|
||||
};
|
||||
|
||||
struct HidHostCtx {
|
||||
uint16_t connHandle = BLE_HS_CONN_HANDLE_NONE;
|
||||
uint16_t hidSvcStart = 0;
|
||||
uint16_t hidSvcEnd = 0;
|
||||
std::vector<HidHostInputRpt> inputRpts;
|
||||
std::vector<uint16_t> allChrDefHandles;
|
||||
int subscribeIdx = 0;
|
||||
int dscDiscIdx = 0;
|
||||
int rptRefReadIdx = 0;
|
||||
uint16_t rptMapHandle = 0;
|
||||
std::vector<uint8_t> rptMap;
|
||||
bool securityInitiated = false;
|
||||
bool typeResolutionDone = false;
|
||||
bool readyBlockFired = false;
|
||||
lv_indev_t* kbIndev = nullptr;
|
||||
lv_indev_t* mouseIndev = nullptr;
|
||||
lv_obj_t* mouseCursor = nullptr;
|
||||
std::array<uint8_t, 6> peerAddr = {};
|
||||
};
|
||||
|
||||
// ---- Globals ----
|
||||
|
||||
static std::unique_ptr<HidHostCtx> hid_host_ctx;
|
||||
static QueueHandle_t hid_host_key_queue = nullptr;
|
||||
static uint8_t hid_host_prev_keys[6] = {};
|
||||
static esp_timer_handle_t hid_enc_retry_timer = nullptr;
|
||||
|
||||
static std::atomic<int32_t> hid_host_mouse_x{0};
|
||||
static std::atomic<int32_t> hid_host_mouse_y{0};
|
||||
static std::atomic<bool> hid_host_mouse_btn{false};
|
||||
static std::atomic<bool> hid_host_mouse_active{false};
|
||||
|
||||
#define HID_HOST_KEY_QUEUE_SIZE 64
|
||||
struct HidHostKeyEvt { uint32_t key; bool pressed; };
|
||||
|
||||
// ---- Forward declarations ----
|
||||
|
||||
static void hidHostSubscribeNext(HidHostCtx& ctx);
|
||||
static void hidHostStartRptRefRead(HidHostCtx& ctx);
|
||||
static void hidHostReadReportMap(HidHostCtx& ctx);
|
||||
static uint16_t getDescEndHandle(const HidHostCtx& ctx, uint16_t valHandle);
|
||||
|
||||
// ---- Keycode mapping ----
|
||||
|
||||
static uint32_t hidHostMapKeycode(uint8_t mod, uint8_t kc) {
|
||||
bool shift = (mod & 0x22) != 0;
|
||||
switch (kc) {
|
||||
case 0x28: return LV_KEY_ENTER;
|
||||
case 0x29: return LV_KEY_ESC;
|
||||
case 0x2A: return LV_KEY_BACKSPACE;
|
||||
case 0x4C: return LV_KEY_DEL;
|
||||
case 0x2B: return shift ? (uint32_t)LV_KEY_PREV : (uint32_t)LV_KEY_NEXT;
|
||||
case 0x52: return LV_KEY_UP;
|
||||
case 0x51: return LV_KEY_DOWN;
|
||||
case 0x50: return LV_KEY_LEFT;
|
||||
case 0x4F: return LV_KEY_RIGHT;
|
||||
case 0x4A: return LV_KEY_HOME;
|
||||
case 0x4D: return LV_KEY_END;
|
||||
default: break;
|
||||
}
|
||||
if (kc >= 0x04 && kc <= 0x1D) {
|
||||
uint32_t c = static_cast<uint32_t>('a' + (kc - 0x04));
|
||||
return shift ? (c - 0x20) : c;
|
||||
}
|
||||
if (kc >= 0x1E && kc <= 0x27) {
|
||||
static const char nums[] = "1234567890";
|
||||
static const char snums[] = "!@#$%^&*()";
|
||||
int i = kc - 0x1E;
|
||||
return shift ? static_cast<uint32_t>(snums[i]) : static_cast<uint32_t>(nums[i]);
|
||||
}
|
||||
if (kc == 0x2C) return ' ';
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void hidHostKeyboardReadCb(lv_indev_t* /*indev*/, lv_indev_data_t* data) {
|
||||
if (!hid_host_key_queue) { data->state = LV_INDEV_STATE_RELEASED; return; }
|
||||
HidHostKeyEvt evt = {};
|
||||
if (xQueueReceive(hid_host_key_queue, &evt, 0) == pdTRUE) {
|
||||
data->key = evt.key;
|
||||
data->state = evt.pressed ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED;
|
||||
data->continue_reading = (uxQueueMessagesWaiting(hid_host_key_queue) > 0);
|
||||
} else {
|
||||
data->state = LV_INDEV_STATE_RELEASED;
|
||||
}
|
||||
}
|
||||
|
||||
static void hidHostHandleKeyboardReport(const uint8_t* data, uint16_t len) {
|
||||
if (len < 3 || !hid_host_key_queue) return;
|
||||
uint8_t mod = data[0];
|
||||
const uint8_t* curr = &data[2];
|
||||
int nkeys = std::min((int)(len - 2), 6);
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
uint8_t kc = hid_host_prev_keys[i];
|
||||
if (kc == 0) continue;
|
||||
bool still = false;
|
||||
for (int j = 0; j < nkeys; j++) { if (curr[j] == kc) { still = true; break; } }
|
||||
if (!still) {
|
||||
uint32_t lv = hidHostMapKeycode(0, kc);
|
||||
if (lv) { HidHostKeyEvt e{lv, false}; xQueueSend(hid_host_key_queue, &e, 0); }
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < nkeys; i++) {
|
||||
uint8_t kc = curr[i];
|
||||
if (kc == 0) continue;
|
||||
bool had = false;
|
||||
for (int j = 0; j < 6; j++) { if (hid_host_prev_keys[j] == kc) { had = true; break; } }
|
||||
if (!had) {
|
||||
uint32_t lv = hidHostMapKeycode(mod, kc);
|
||||
if (lv) { HidHostKeyEvt e{lv, true}; xQueueSend(hid_host_key_queue, &e, 0); }
|
||||
}
|
||||
}
|
||||
std::memcpy(hid_host_prev_keys, curr, nkeys);
|
||||
if (nkeys < 6) std::memset(hid_host_prev_keys + nkeys, 0, 6 - nkeys);
|
||||
}
|
||||
|
||||
static void hidHostMouseReadCb(lv_indev_t* /*indev*/, lv_indev_data_t* data) {
|
||||
int32_t cx = hid_host_mouse_x.load();
|
||||
int32_t cy = hid_host_mouse_y.load();
|
||||
|
||||
lv_display_t* disp = lv_display_get_default();
|
||||
if (disp) {
|
||||
int32_t ow = lv_display_get_original_horizontal_resolution(disp);
|
||||
int32_t oh = lv_display_get_original_vertical_resolution(disp);
|
||||
switch (lv_display_get_rotation(disp)) {
|
||||
case LV_DISPLAY_ROTATION_0:
|
||||
data->point.x = (lv_coord_t)cx;
|
||||
data->point.y = (lv_coord_t)cy;
|
||||
break;
|
||||
case LV_DISPLAY_ROTATION_90:
|
||||
data->point.x = (lv_coord_t)cy;
|
||||
data->point.y = (lv_coord_t)(oh - cx - 1);
|
||||
break;
|
||||
case LV_DISPLAY_ROTATION_180:
|
||||
data->point.x = (lv_coord_t)(ow - cx - 1);
|
||||
data->point.y = (lv_coord_t)(oh - cy - 1);
|
||||
break;
|
||||
case LV_DISPLAY_ROTATION_270:
|
||||
data->point.x = (lv_coord_t)(ow - cy - 1);
|
||||
data->point.y = (lv_coord_t)cx;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
data->point.x = (lv_coord_t)cx;
|
||||
data->point.y = (lv_coord_t)cy;
|
||||
}
|
||||
data->state = hid_host_mouse_btn.load() ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED;
|
||||
|
||||
if (!hid_host_mouse_active.load()) {
|
||||
hid_host_mouse_active.store(true);
|
||||
if (hid_host_ctx && hid_host_ctx->mouseCursor) {
|
||||
lv_obj_remove_flag(hid_host_ctx->mouseCursor, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void hidHostHandleMouseReport(const uint8_t* data, uint16_t len) {
|
||||
if (len < 3) return;
|
||||
bool btn = (data[0] & 0x01) != 0;
|
||||
int8_t dx = (int8_t)data[1];
|
||||
int8_t dy = (int8_t)data[2];
|
||||
|
||||
lv_display_t* disp = lv_display_get_default();
|
||||
int32_t w = disp ? lv_display_get_horizontal_resolution(disp) : 320;
|
||||
int32_t h = disp ? lv_display_get_vertical_resolution(disp) : 240;
|
||||
|
||||
int32_t nx = hid_host_mouse_x.load() + dx;
|
||||
int32_t ny = hid_host_mouse_y.load() + dy;
|
||||
if (nx < 0) nx = 0;
|
||||
if (nx >= w) nx = w - 1;
|
||||
if (ny < 0) ny = 0;
|
||||
if (ny >= h) ny = h - 1;
|
||||
|
||||
hid_host_mouse_x.store(nx);
|
||||
hid_host_mouse_y.store(ny);
|
||||
hid_host_mouse_btn.store(btn);
|
||||
|
||||
if (hid_host_ctx && hid_host_ctx->mouseIndev == nullptr) {
|
||||
getMainDispatcher().dispatch([] {
|
||||
if (!hid_host_ctx || hid_host_ctx->mouseIndev != nullptr) return;
|
||||
if (!tt::lvgl::lock(1000)) { LOGGER.warn("LVGL lock failed for mouse indev"); return; }
|
||||
auto* ms = lv_indev_create();
|
||||
lv_indev_set_type(ms, LV_INDEV_TYPE_POINTER);
|
||||
lv_indev_set_read_cb(ms, hidHostMouseReadCb);
|
||||
auto* cur = lv_image_create(lv_layer_sys());
|
||||
lv_obj_remove_flag(cur, LV_OBJ_FLAG_CLICKABLE);
|
||||
lv_obj_add_flag(cur, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_image_set_src(cur, TT_ASSETS_UI_CURSOR);
|
||||
lv_indev_set_cursor(ms, cur);
|
||||
hid_host_ctx->mouseIndev = ms;
|
||||
hid_host_ctx->mouseCursor = cur;
|
||||
tt::lvgl::unlock();
|
||||
LOGGER.info("Mouse indev registered");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Timer callback for post-encryption CCCD retry ----
|
||||
|
||||
static void hidEncRetryTimerCb(void* /*arg*/) {
|
||||
if (hid_host_ctx) {
|
||||
if (!hid_host_ctx->typeResolutionDone) {
|
||||
LOGGER.warn("Post-encryption delay — type resolution timed out, proceeding");
|
||||
hid_host_ctx->typeResolutionDone = true;
|
||||
hid_host_ctx->subscribeIdx = 0;
|
||||
} else {
|
||||
LOGGER.info("Post-encryption delay complete — starting CCCD subscriptions");
|
||||
}
|
||||
hidHostSubscribeNext(*hid_host_ctx);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Report Map parsing ----
|
||||
|
||||
static void applyReportMapTypes(HidHostCtx& ctx) {
|
||||
const uint8_t* data = ctx.rptMap.data();
|
||||
size_t len = ctx.rptMap.size();
|
||||
|
||||
uint16_t usagePage = 0, usage = 0;
|
||||
uint8_t reportId = 0;
|
||||
int depth = 0;
|
||||
HidReportType collType = HidReportType::Unknown;
|
||||
|
||||
struct Entry { uint8_t id; HidReportType type; };
|
||||
std::vector<Entry> typeMap;
|
||||
std::vector<HidReportType> collOrder;
|
||||
bool collHadInput = false;
|
||||
|
||||
size_t i = 0;
|
||||
while (i < len) {
|
||||
uint8_t prefix = data[i++];
|
||||
if (prefix == 0xFE) {
|
||||
if (i + 1 >= len) break;
|
||||
uint8_t lsz = data[i++]; i++; i += lsz; continue;
|
||||
}
|
||||
uint8_t bSize = prefix & 0x03;
|
||||
uint8_t bType = (prefix >> 2) & 0x03;
|
||||
uint8_t bTag = (prefix >> 4) & 0x0F;
|
||||
uint8_t dataLen = (bSize == 3) ? 4 : bSize;
|
||||
if (i + dataLen > len) break;
|
||||
uint32_t value = 0;
|
||||
for (uint8_t j = 0; j < dataLen; j++) value |= (uint32_t)data[i++] << (8 * j);
|
||||
|
||||
if (bType == 0) {
|
||||
if (bTag == 0xA) {
|
||||
if (depth == 0 && value == 0x01) {
|
||||
if (usagePage == 0x01 && usage == 0x06) collType = HidReportType::Keyboard;
|
||||
else if (usagePage == 0x01 && usage == 0x02) collType = HidReportType::Mouse;
|
||||
else if (usagePage == 0x0C) collType = HidReportType::Consumer;
|
||||
else collType = HidReportType::Unknown;
|
||||
collHadInput = false;
|
||||
}
|
||||
depth++; usage = 0;
|
||||
} else if (bTag == 0xC) {
|
||||
if (depth > 0) depth--;
|
||||
if (depth == 0) { collType = HidReportType::Unknown; collHadInput = false; }
|
||||
usage = 0;
|
||||
} else if (bTag == 0x8) {
|
||||
if (depth > 0 && collType != HidReportType::Unknown) {
|
||||
if (!collHadInput) { collOrder.push_back(collType); collHadInput = true; }
|
||||
if (reportId != 0) {
|
||||
bool found = false;
|
||||
for (const auto& e : typeMap) { if (e.id == reportId) { found = true; break; } }
|
||||
if (!found) typeMap.push_back({reportId, collType});
|
||||
}
|
||||
}
|
||||
usage = 0;
|
||||
} else { usage = 0; }
|
||||
} else if (bType == 1) {
|
||||
if (bTag == 0x0) usagePage = (uint16_t)value;
|
||||
else if (bTag == 0x8) reportId = (uint8_t)value;
|
||||
} else if (bType == 2) {
|
||||
if (bTag == 0x0) usage = (uint16_t)value;
|
||||
}
|
||||
}
|
||||
|
||||
bool anyNonZeroId = false;
|
||||
for (const auto& rpt : ctx.inputRpts) { if (rpt.reportId != 0) { anyNonZeroId = true; break; } }
|
||||
|
||||
size_t zeroRptIdx = 0;
|
||||
for (auto& rpt : ctx.inputRpts) {
|
||||
if (anyNonZeroId) {
|
||||
for (const auto& e : typeMap) { if (e.id == rpt.reportId) { rpt.type = e.type; break; } }
|
||||
} else {
|
||||
if (zeroRptIdx < collOrder.size()) rpt.type = collOrder[zeroRptIdx];
|
||||
zeroRptIdx++;
|
||||
}
|
||||
LOGGER.info("Report val_handle={} reportId={} type={}", rpt.valHandle, rpt.reportId, (int)rpt.type);
|
||||
}
|
||||
ctx.rptMap.clear();
|
||||
}
|
||||
|
||||
// ---- Report Reference read chain ----
|
||||
|
||||
static void hidHostStartRptRefRead(HidHostCtx& ctx) {
|
||||
while (ctx.rptRefReadIdx < (int)ctx.inputRpts.size() &&
|
||||
ctx.inputRpts[ctx.rptRefReadIdx].rptRefHandle == 0) {
|
||||
ctx.rptRefReadIdx++;
|
||||
}
|
||||
if (ctx.rptRefReadIdx >= (int)ctx.inputRpts.size()) {
|
||||
hidHostReadReportMap(ctx);
|
||||
return;
|
||||
}
|
||||
uint16_t handle = ctx.inputRpts[ctx.rptRefReadIdx].rptRefHandle;
|
||||
int rc = ble_gattc_read(ctx.connHandle, handle, [](uint16_t conn_handle,
|
||||
const struct ble_gatt_error* error,
|
||||
struct ble_gatt_attr* attr, void* /*arg*/) -> int {
|
||||
if (!hid_host_ctx) return 0;
|
||||
auto& ctx = *hid_host_ctx;
|
||||
if (conn_handle != ctx.connHandle) return 0;
|
||||
if (error->status == BLE_HS_EDONE) return 0;
|
||||
if (error->status == 0 && attr != nullptr) {
|
||||
if (OS_MBUF_PKTLEN(attr->om) >= 2 && ctx.rptRefReadIdx < (int)ctx.inputRpts.size()) {
|
||||
uint8_t rpt_ref[2] = {};
|
||||
os_mbuf_copydata(attr->om, 0, 2, rpt_ref);
|
||||
ctx.inputRpts[ctx.rptRefReadIdx].reportId = rpt_ref[0];
|
||||
LOGGER.info("Report[{}] val_handle={} reportId={}", ctx.rptRefReadIdx,
|
||||
ctx.inputRpts[ctx.rptRefReadIdx].valHandle, rpt_ref[0]);
|
||||
}
|
||||
}
|
||||
ctx.rptRefReadIdx++;
|
||||
hidHostStartRptRefRead(ctx);
|
||||
return 0;
|
||||
}, nullptr);
|
||||
if (rc != 0) {
|
||||
LOGGER.warn("rptRef read[{}] failed rc={} — skipping", ctx.rptRefReadIdx, rc);
|
||||
ctx.rptRefReadIdx++;
|
||||
hidHostStartRptRefRead(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Report Map read ----
|
||||
|
||||
static void hidHostReadReportMap(HidHostCtx& ctx) {
|
||||
if (ctx.rptMapHandle == 0) {
|
||||
LOGGER.info("No Report Map char — skipping type resolution");
|
||||
ctx.typeResolutionDone = true;
|
||||
ctx.subscribeIdx = 0;
|
||||
hidHostSubscribeNext(ctx);
|
||||
return;
|
||||
}
|
||||
int rc = ble_gattc_read_long(ctx.connHandle, ctx.rptMapHandle, 0,
|
||||
[](uint16_t conn_handle, const struct ble_gatt_error* error,
|
||||
struct ble_gatt_attr* attr, void* /*arg*/) -> int {
|
||||
if (!hid_host_ctx) return 0;
|
||||
auto& ctx = *hid_host_ctx;
|
||||
if (conn_handle != ctx.connHandle) return 0;
|
||||
if (error->status == 0 && attr != nullptr) {
|
||||
uint16_t chunk = OS_MBUF_PKTLEN(attr->om);
|
||||
size_t old_sz = ctx.rptMap.size();
|
||||
ctx.rptMap.resize(old_sz + chunk);
|
||||
os_mbuf_copydata(attr->om, 0, chunk, ctx.rptMap.data() + old_sz);
|
||||
return 0;
|
||||
}
|
||||
if (!ctx.rptMap.empty()) {
|
||||
LOGGER.info("Report map read ({} bytes)", ctx.rptMap.size());
|
||||
applyReportMapTypes(ctx);
|
||||
} else {
|
||||
LOGGER.warn("Report map read failed — types remain Unknown");
|
||||
}
|
||||
ctx.typeResolutionDone = true;
|
||||
ctx.subscribeIdx = 0;
|
||||
hidHostSubscribeNext(ctx);
|
||||
return 0;
|
||||
}, nullptr);
|
||||
if (rc != 0) {
|
||||
LOGGER.warn("Report map read_long failed rc={} — skipping", rc);
|
||||
ctx.typeResolutionDone = true;
|
||||
ctx.subscribeIdx = 0;
|
||||
hidHostSubscribeNext(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- CCCD subscription chain ----
|
||||
|
||||
static int hidHostCccdWriteCb(uint16_t conn_handle, const struct ble_gatt_error* error,
|
||||
struct ble_gatt_attr* /*attr*/, void* /*arg*/) {
|
||||
if (!hid_host_ctx) return 0;
|
||||
auto& ctx = *hid_host_ctx;
|
||||
if (conn_handle != ctx.connHandle) return 0;
|
||||
|
||||
if (error->status != 0 && error->status != BLE_HS_EDONE) {
|
||||
if ((error->status == BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_AUTHEN) ||
|
||||
error->status == BLE_HS_ATT_ERR(BLE_ATT_ERR_INSUFFICIENT_ENC))
|
||||
&& !ctx.securityInitiated) {
|
||||
LOGGER.info("CCCD auth required — initiating security");
|
||||
ctx.securityInitiated = true;
|
||||
ble_gap_security_initiate(conn_handle);
|
||||
return 0;
|
||||
}
|
||||
if (error->status == BLE_HS_ETIMEOUT) {
|
||||
LOGGER.warn("CCCD write timed out for report[{}] — skipping", ctx.subscribeIdx);
|
||||
ctx.subscribeIdx++;
|
||||
hidHostSubscribeNext(ctx);
|
||||
return 0;
|
||||
}
|
||||
if (error->status == BLE_HS_ENOTCONN) {
|
||||
LOGGER.warn("CCCD write failed — not connected");
|
||||
return 0;
|
||||
}
|
||||
LOGGER.warn("CCCD write failed status={}", error->status);
|
||||
}
|
||||
ctx.subscribeIdx++;
|
||||
hidHostSubscribeNext(ctx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void hidHostSubscribeNext(HidHostCtx& ctx) {
|
||||
if (ctx.subscribeIdx >= (int)ctx.inputRpts.size()) {
|
||||
if (ctx.readyBlockFired) {
|
||||
LOGGER.info("Subscribe ready block already ran — ignoring duplicate");
|
||||
return;
|
||||
}
|
||||
ctx.readyBlockFired = true;
|
||||
LOGGER.info("All {} reports subscribed — ready", ctx.inputRpts.size());
|
||||
if (hid_enc_retry_timer) esp_timer_stop(hid_enc_retry_timer);
|
||||
|
||||
if (!hid_host_key_queue) {
|
||||
hid_host_key_queue = xQueueCreate(HID_HOST_KEY_QUEUE_SIZE, sizeof(HidHostKeyEvt));
|
||||
}
|
||||
getMainDispatcher().dispatch([] {
|
||||
if (!hid_host_ctx || hid_host_ctx->kbIndev != nullptr) return;
|
||||
if (!tt::lvgl::lock(1000)) { LOGGER.warn("LVGL lock failed for kb indev"); return; }
|
||||
auto* kb = lv_indev_create();
|
||||
lv_indev_set_type(kb, LV_INDEV_TYPE_KEYPAD);
|
||||
lv_indev_set_read_cb(kb, hidHostKeyboardReadCb);
|
||||
hid_host_ctx->kbIndev = kb;
|
||||
tt::lvgl::hardware_keyboard_set_indev(kb);
|
||||
tt::lvgl::unlock();
|
||||
LOGGER.info("Keyboard indev registered");
|
||||
});
|
||||
|
||||
auto peer_addr = ctx.peerAddr;
|
||||
getMainDispatcher().dispatch([peer_addr] {
|
||||
// Find name from cached scan results
|
||||
std::string name;
|
||||
{
|
||||
auto results = getScanResults();
|
||||
for (const auto& r : results) {
|
||||
if (r.addr == peer_addr) { name = r.name; break; }
|
||||
}
|
||||
}
|
||||
settings::PairedDevice device;
|
||||
device.addr = peer_addr;
|
||||
device.profileId = BT_PROFILE_HID_HOST;
|
||||
device.autoConnect = true;
|
||||
const auto addr_hex = settings::addrToHex(peer_addr);
|
||||
settings::PairedDevice existing;
|
||||
if (settings::load(addr_hex, existing)) {
|
||||
device.autoConnect = existing.autoConnect;
|
||||
}
|
||||
device.name = name;
|
||||
settings::save(device);
|
||||
if (struct Device* dev = findFirstDevice()) {
|
||||
struct BtEvent e = {};
|
||||
e.type = BT_EVENT_PROFILE_STATE_CHANGED;
|
||||
e.profile_state.state = BT_PROFILE_STATE_CONNECTED;
|
||||
e.profile_state.profile = BT_PROFILE_HID_HOST;
|
||||
bluetooth_fire_event(dev, e);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
auto& rpt = ctx.inputRpts[ctx.subscribeIdx];
|
||||
if (rpt.cccdHandle == 0) {
|
||||
ctx.subscribeIdx++;
|
||||
hidHostSubscribeNext(ctx);
|
||||
return;
|
||||
}
|
||||
static const uint16_t notify_val = 0x0001;
|
||||
int rc = ble_gattc_write_flat(ctx.connHandle, rpt.cccdHandle,
|
||||
¬ify_val, sizeof(notify_val),
|
||||
hidHostCccdWriteCb, nullptr);
|
||||
if (rc != 0) {
|
||||
LOGGER.warn("gattc_write_flat CCCD failed rc={}", rc);
|
||||
ctx.subscribeIdx++;
|
||||
hidHostSubscribeNext(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Descriptor discovery ----
|
||||
|
||||
static int hidHostDscDiscCb(uint16_t conn_handle, const struct ble_gatt_error* error,
|
||||
uint16_t chr_val_handle, const struct ble_gatt_dsc* dsc, void* /*arg*/) {
|
||||
if (!hid_host_ctx) return 0;
|
||||
auto& ctx = *hid_host_ctx;
|
||||
if (conn_handle != ctx.connHandle) return 0;
|
||||
|
||||
if (error->status == 0 && dsc != nullptr) {
|
||||
uint16_t dsc_uuid = ble_uuid_u16(&dsc->uuid.u);
|
||||
for (auto& rpt : ctx.inputRpts) {
|
||||
if (rpt.valHandle != chr_val_handle) continue;
|
||||
if (dsc_uuid == 0x2902) { rpt.cccdHandle = dsc->handle; }
|
||||
else if (dsc_uuid == 0x2908) { rpt.rptRefHandle = dsc->handle; }
|
||||
break;
|
||||
}
|
||||
} else if (error->status == BLE_HS_EDONE) {
|
||||
int next_idx = ctx.dscDiscIdx + 1;
|
||||
if (next_idx < (int)ctx.inputRpts.size()) {
|
||||
ctx.dscDiscIdx = next_idx;
|
||||
auto& next_rpt = ctx.inputRpts[next_idx];
|
||||
uint16_t end = getDescEndHandle(ctx, next_rpt.valHandle);
|
||||
int rc = ble_gattc_disc_all_dscs(ctx.connHandle, next_rpt.valHandle, end,
|
||||
hidHostDscDiscCb, nullptr);
|
||||
if (rc != 0) {
|
||||
LOGGER.warn("disc_all_dscs[{}] failed rc={}", next_idx, rc);
|
||||
ctx.rptRefReadIdx = 0;
|
||||
hidHostStartRptRefRead(ctx);
|
||||
}
|
||||
} else {
|
||||
ctx.rptRefReadIdx = 0;
|
||||
hidHostStartRptRefRead(ctx);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint16_t getDescEndHandle(const HidHostCtx& ctx, uint16_t valHandle) {
|
||||
for (uint16_t dh : ctx.allChrDefHandles) {
|
||||
if (dh > valHandle) return dh - 1;
|
||||
}
|
||||
return ctx.hidSvcEnd;
|
||||
}
|
||||
|
||||
// ---- Characteristic discovery ----
|
||||
|
||||
static int hidHostChrDiscCb(uint16_t conn_handle, const struct ble_gatt_error* error,
|
||||
const struct ble_gatt_chr* chr, void* /*arg*/) {
|
||||
if (!hid_host_ctx) return 0;
|
||||
auto& ctx = *hid_host_ctx;
|
||||
if (conn_handle != ctx.connHandle) return 0;
|
||||
|
||||
if (error->status == 0 && chr != nullptr) {
|
||||
ctx.allChrDefHandles.push_back(chr->def_handle);
|
||||
uint16_t uuid16 = ble_uuid_u16(&chr->uuid.u);
|
||||
if (uuid16 == 0x2A4D && (chr->properties & BLE_GATT_CHR_PROP_NOTIFY)) {
|
||||
HidHostInputRpt rpt = {};
|
||||
rpt.valHandle = chr->val_handle;
|
||||
ctx.inputRpts.push_back(rpt);
|
||||
LOGGER.info("Input Report chr val_handle={}", chr->val_handle);
|
||||
} else if (uuid16 == 0x2A4B) {
|
||||
ctx.rptMapHandle = chr->val_handle;
|
||||
}
|
||||
} else if (error->status == BLE_HS_EDONE) {
|
||||
std::sort(ctx.allChrDefHandles.begin(), ctx.allChrDefHandles.end());
|
||||
if (ctx.inputRpts.empty()) {
|
||||
LOGGER.warn("No Input Report chars — disconnecting");
|
||||
ble_gap_terminate(ctx.connHandle, BLE_ERR_REM_USER_CONN_TERM);
|
||||
return 0;
|
||||
}
|
||||
ctx.dscDiscIdx = 0;
|
||||
auto& first = ctx.inputRpts[0];
|
||||
uint16_t end = getDescEndHandle(ctx, first.valHandle);
|
||||
int rc = ble_gattc_disc_all_dscs(ctx.connHandle, first.valHandle, end,
|
||||
hidHostDscDiscCb, nullptr);
|
||||
if (rc != 0) {
|
||||
LOGGER.warn("disc_all_dscs[0] failed rc={}", rc);
|
||||
ctx.rptRefReadIdx = 0;
|
||||
hidHostStartRptRefRead(ctx);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- Service discovery ----
|
||||
|
||||
static int hidHostSvcDiscCb(uint16_t conn_handle, const struct ble_gatt_error* error,
|
||||
const struct ble_gatt_svc* svc, void* /*arg*/) {
|
||||
if (!hid_host_ctx) return 0;
|
||||
auto& ctx = *hid_host_ctx;
|
||||
if (conn_handle != ctx.connHandle) return 0;
|
||||
|
||||
if (error->status == 0 && svc != nullptr) {
|
||||
if (ble_uuid_u16(&svc->uuid.u) == 0x1812) {
|
||||
ctx.hidSvcStart = svc->start_handle;
|
||||
ctx.hidSvcEnd = svc->end_handle;
|
||||
LOGGER.info("HID service start={} end={}", ctx.hidSvcStart, ctx.hidSvcEnd);
|
||||
}
|
||||
} else if (error->status == BLE_HS_EDONE) {
|
||||
if (ctx.hidSvcStart == 0) {
|
||||
LOGGER.warn("No HID service found — disconnecting");
|
||||
ble_gap_terminate(ctx.connHandle, BLE_ERR_REM_USER_CONN_TERM);
|
||||
return 0;
|
||||
}
|
||||
int rc = ble_gattc_disc_all_chrs(ctx.connHandle, ctx.hidSvcStart, ctx.hidSvcEnd,
|
||||
hidHostChrDiscCb, nullptr);
|
||||
if (rc != 0) {
|
||||
LOGGER.warn("disc_all_chrs failed rc={}", rc);
|
||||
ble_gap_terminate(ctx.connHandle, BLE_ERR_REM_USER_CONN_TERM);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- GAP callback for HID host central connection ----
|
||||
|
||||
static int hidHostGapCb(struct ble_gap_event* event, void* /*arg*/) {
|
||||
if (!hid_host_ctx) return 0;
|
||||
auto& ctx = *hid_host_ctx;
|
||||
|
||||
switch (event->type) {
|
||||
case BLE_GAP_EVENT_CONNECT:
|
||||
if (event->connect.status == 0) {
|
||||
ctx.connHandle = event->connect.conn_handle;
|
||||
LOGGER.info("Connected (handle={})", ctx.connHandle);
|
||||
int rc = ble_gattc_disc_all_svcs(ctx.connHandle, hidHostSvcDiscCb, nullptr);
|
||||
if (rc != 0) {
|
||||
LOGGER.warn("disc_all_svcs failed rc={}", rc);
|
||||
ble_gap_terminate(ctx.connHandle, BLE_ERR_REM_USER_CONN_TERM);
|
||||
}
|
||||
} else {
|
||||
LOGGER.warn("Connect failed status={}", event->connect.status);
|
||||
hid_host_ctx.reset();
|
||||
if (struct Device* dev = findFirstDevice()) {
|
||||
bluetooth_set_hid_host_active(dev, false);
|
||||
struct BtEvent e = {};
|
||||
e.type = BT_EVENT_PROFILE_STATE_CHANGED;
|
||||
e.profile_state.state = BT_PROFILE_STATE_IDLE;
|
||||
e.profile_state.profile = BT_PROFILE_HID_HOST;
|
||||
bluetooth_fire_event(dev, e);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case BLE_GAP_EVENT_DISCONNECT: {
|
||||
LOGGER.info("Disconnected reason={}", event->disconnect.reason);
|
||||
lv_indev_t* saved_kb = hid_host_ctx ? hid_host_ctx->kbIndev : nullptr;
|
||||
lv_indev_t* saved_mouse = hid_host_ctx ? hid_host_ctx->mouseIndev : nullptr;
|
||||
lv_obj_t* saved_cursor = hid_host_ctx ? hid_host_ctx->mouseCursor : nullptr;
|
||||
QueueHandle_t saved_queue = hid_host_key_queue;
|
||||
hid_host_ctx.reset();
|
||||
hid_host_key_queue = nullptr;
|
||||
std::memset(hid_host_prev_keys, 0, sizeof(hid_host_prev_keys));
|
||||
hid_host_mouse_x.store(0);
|
||||
hid_host_mouse_y.store(0);
|
||||
hid_host_mouse_btn.store(false);
|
||||
hid_host_mouse_active.store(false);
|
||||
|
||||
if (struct Device* dev = findFirstDevice()) {
|
||||
bluetooth_set_hid_host_active(dev, false);
|
||||
struct BtEvent e = {};
|
||||
e.type = BT_EVENT_PROFILE_STATE_CHANGED;
|
||||
e.profile_state.state = BT_PROFILE_STATE_IDLE;
|
||||
e.profile_state.profile = BT_PROFILE_HID_HOST;
|
||||
bluetooth_fire_event(dev, e);
|
||||
}
|
||||
|
||||
getMainDispatcher().dispatch([saved_kb, saved_mouse, saved_cursor, saved_queue] {
|
||||
if (!tt::lvgl::lock(1000)) {
|
||||
LOGGER.warn("Failed to acquire LVGL lock for indev cleanup");
|
||||
if (saved_queue) vQueueDelete(saved_queue);
|
||||
return;
|
||||
}
|
||||
if (saved_kb) {
|
||||
tt::lvgl::hardware_keyboard_set_indev(nullptr);
|
||||
lv_indev_delete(saved_kb);
|
||||
}
|
||||
if (saved_mouse) lv_indev_delete(saved_mouse);
|
||||
if (saved_cursor) lv_obj_delete(saved_cursor);
|
||||
tt::lvgl::unlock();
|
||||
if (saved_queue) vQueueDelete(saved_queue);
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case BLE_GAP_EVENT_ENC_CHANGE:
|
||||
if (event->enc_change.conn_handle == ctx.connHandle) {
|
||||
if (event->enc_change.status == 0) {
|
||||
LOGGER.info("Encryption established — retrying CCCD in 500ms");
|
||||
ctx.subscribeIdx = 0;
|
||||
if (hid_enc_retry_timer) {
|
||||
esp_timer_stop(hid_enc_retry_timer);
|
||||
esp_timer_start_once(hid_enc_retry_timer, 500 * 1000);
|
||||
} else {
|
||||
hidHostSubscribeNext(ctx);
|
||||
}
|
||||
} else {
|
||||
LOGGER.warn("Encryption failed status={}", event->enc_change.status);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case BLE_GAP_EVENT_NOTIFY_RX:
|
||||
if (event->notify_rx.conn_handle == ctx.connHandle) {
|
||||
uint16_t len = OS_MBUF_PKTLEN(event->notify_rx.om);
|
||||
if (len > 0 && len <= 64) {
|
||||
uint8_t buf[64] = {};
|
||||
os_mbuf_copydata(event->notify_rx.om, 0, len, buf);
|
||||
for (const auto& rpt : ctx.inputRpts) {
|
||||
if (rpt.valHandle != event->notify_rx.attr_handle) continue;
|
||||
switch (rpt.type) {
|
||||
case HidReportType::Keyboard: hidHostHandleKeyboardReport(buf, len); break;
|
||||
case HidReportType::Mouse: hidHostHandleMouseReport(buf, len); break;
|
||||
case HidReportType::Consumer:
|
||||
LOGGER.info("Consumer report len={}", len);
|
||||
break;
|
||||
case HidReportType::Unknown:
|
||||
if (len >= 6) hidHostHandleKeyboardReport(buf, len);
|
||||
else if (len >= 3) hidHostHandleMouseReport(buf, len);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---- Public functions ----
|
||||
|
||||
void hidHostConnect(const std::array<uint8_t, 6>& addr) {
|
||||
if (getRadioState() != RadioState::On) {
|
||||
LOGGER.warn("hidHostConnect: radio not on");
|
||||
return;
|
||||
}
|
||||
if (hid_host_ctx) {
|
||||
LOGGER.warn("hidHostConnect: already connecting/connected");
|
||||
return;
|
||||
}
|
||||
|
||||
hid_host_mouse_x.store(0);
|
||||
hid_host_mouse_y.store(0);
|
||||
hid_host_mouse_btn.store(false);
|
||||
hid_host_mouse_active.store(false);
|
||||
|
||||
hid_host_ctx = std::make_unique<HidHostCtx>();
|
||||
hid_host_ctx->peerAddr = addr;
|
||||
|
||||
// Create enc retry timer lazily
|
||||
if (hid_enc_retry_timer == nullptr) {
|
||||
esp_timer_create_args_t args = {};
|
||||
args.callback = hidEncRetryTimerCb;
|
||||
args.dispatch_method = ESP_TIMER_TASK;
|
||||
args.name = "hid_enc_retry";
|
||||
if (esp_timer_create(&args, &hid_enc_retry_timer) != ESP_OK) {
|
||||
LOGGER.error("Failed to create hid_enc_retry timer");
|
||||
hid_enc_retry_timer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Notify driver that a HID host central connection is starting.
|
||||
if (struct Device* dev = findFirstDevice()) bluetooth_set_hid_host_active(dev, true);
|
||||
|
||||
// Look up the addr_type from the cached scan results.
|
||||
ble_addr_t ble_addr = {};
|
||||
ble_addr.type = BLE_ADDR_PUBLIC;
|
||||
std::memcpy(ble_addr.val, addr.data(), 6);
|
||||
uint8_t addr_type = 0;
|
||||
if (getCachedScanAddrType(addr.data(), &addr_type)) {
|
||||
ble_addr.type = addr_type;
|
||||
}
|
||||
|
||||
uint8_t own_addr_type;
|
||||
if (ble_hs_id_infer_auto(0, &own_addr_type) != 0) {
|
||||
own_addr_type = BLE_OWN_ADDR_PUBLIC;
|
||||
}
|
||||
|
||||
int rc = ble_gap_connect(own_addr_type, &ble_addr, 5000, nullptr, hidHostGapCb, nullptr);
|
||||
if (rc != 0) {
|
||||
LOGGER.warn("ble_gap_connect failed rc={}", rc);
|
||||
hid_host_ctx.reset();
|
||||
if (struct Device* dev = findFirstDevice()) {
|
||||
bluetooth_set_hid_host_active(dev, false);
|
||||
// Fire IDLE so bt_event_bridge can start a new scan and retry.
|
||||
struct BtEvent e = {};
|
||||
e.type = BT_EVENT_PROFILE_STATE_CHANGED;
|
||||
e.profile_state.state = BT_PROFILE_STATE_IDLE;
|
||||
e.profile_state.profile = BT_PROFILE_HID_HOST;
|
||||
bluetooth_fire_event(dev, e);
|
||||
}
|
||||
} else {
|
||||
LOGGER.info("Connecting...");
|
||||
}
|
||||
}
|
||||
|
||||
void hidHostDisconnect() {
|
||||
if (!hid_host_ctx || hid_host_ctx->connHandle == BLE_HS_CONN_HANDLE_NONE) return;
|
||||
ble_gap_terminate(hid_host_ctx->connHandle, BLE_ERR_REM_USER_CONN_TERM);
|
||||
}
|
||||
|
||||
bool hidHostIsConnected() {
|
||||
return hid_host_ctx != nullptr &&
|
||||
hid_host_ctx->connHandle != BLE_HS_CONN_HANDLE_NONE &&
|
||||
!hid_host_ctx->inputRpts.empty() &&
|
||||
hid_host_ctx->subscribeIdx >= (int)hid_host_ctx->inputRpts.size();
|
||||
}
|
||||
|
||||
bool hidHostGetConnectedPeer(std::array<uint8_t, 6>& addr_out) {
|
||||
if (!hidHostIsConnected()) return false;
|
||||
addr_out = hid_host_ctx->peerAddr;
|
||||
return true;
|
||||
}
|
||||
|
||||
void autoConnectHidHost() {
|
||||
if (hidHostIsConnected()) return;
|
||||
|
||||
// Connect to the first saved HID host peer that appeared in the last scan.
|
||||
// cacheScanAddr() is populated during scanning so addr_type is available for ble_gap_connect.
|
||||
auto scan = getScanResults();
|
||||
for (const auto& r : scan) {
|
||||
settings::PairedDevice stored;
|
||||
if (settings::load(settings::addrToHex(r.addr), stored) &&
|
||||
stored.autoConnect &&
|
||||
stored.profileId == BT_PROFILE_HID_HOST) {
|
||||
LOGGER.info("Auto-connecting HID host to {}", settings::addrToHex(r.addr));
|
||||
hidHostConnect(r.addr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Device not in the last scan. If we have an autoConnect HID host peer, restart
|
||||
// scanning so we keep checking until the device powers back on.
|
||||
auto peers = settings::loadAll();
|
||||
for (const auto& peer : peers) {
|
||||
if (peer.autoConnect && peer.profileId == BT_PROFILE_HID_HOST) {
|
||||
if (struct Device* dev = findFirstDevice()) {
|
||||
if (!bluetooth_is_scanning(dev)) {
|
||||
LOGGER.info("Auto-connect HID host: device not in scan, retrying scan");
|
||||
bluetooth_scan_start(dev);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace tt::bluetooth
|
||||
|
||||
#endif // CONFIG_BT_NIMBLE_ENABLED
|
||||
44
Tactility/Source/bluetooth/BluetoothMock.cpp
Normal file
44
Tactility/Source/bluetooth/BluetoothMock.cpp
Normal file
@ -0,0 +1,44 @@
|
||||
#ifdef ESP_PLATFORM
|
||||
#include <sdkconfig.h>
|
||||
#endif
|
||||
|
||||
#if !defined(CONFIG_BT_NIMBLE_ENABLED)
|
||||
|
||||
#include <Tactility/bluetooth/Bluetooth.h>
|
||||
|
||||
namespace tt::bluetooth {
|
||||
|
||||
struct Device* findFirstDevice() { return nullptr; }
|
||||
|
||||
const char* radioStateToString(RadioState state) {
|
||||
switch (state) {
|
||||
using enum RadioState;
|
||||
case Off: return "Off";
|
||||
case OnPending: return "OnPending";
|
||||
case On: return "On";
|
||||
case OffPending: return "OffPending";
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
RadioState getRadioState() { return RadioState::Off; }
|
||||
|
||||
std::vector<PeerRecord> getScanResults() { return {}; }
|
||||
std::vector<PeerRecord> getPairedPeers() { return {}; }
|
||||
|
||||
void pair(const std::array<uint8_t, 6>& /*addr*/) {}
|
||||
void unpair(const std::array<uint8_t, 6>& /*addr*/) {}
|
||||
void connect(const std::array<uint8_t, 6>& /*addr*/, int /*profileId*/) {}
|
||||
void disconnect(const std::array<uint8_t, 6>& /*addr*/, int /*profileId*/) {}
|
||||
|
||||
bool isProfileSupported(int /*profileId*/) { return false; }
|
||||
|
||||
void hidHostConnect(const std::array<uint8_t, 6>& /*addr*/) {}
|
||||
void hidHostDisconnect() {}
|
||||
bool hidHostIsConnected() { return false; }
|
||||
|
||||
void systemStart() {}
|
||||
|
||||
} // namespace tt::bluetooth
|
||||
|
||||
#endif // !CONFIG_BT_NIMBLE_ENABLED
|
||||
118
Tactility/Source/bluetooth/BluetoothPairedDevice.cpp
Normal file
118
Tactility/Source/bluetooth/BluetoothPairedDevice.cpp
Normal file
@ -0,0 +1,118 @@
|
||||
#include <Tactility/bluetooth/BluetoothPairedDevice.h>
|
||||
|
||||
#include <Tactility/file/File.h>
|
||||
#include <Tactility/file/PropertiesFile.h>
|
||||
#include <Tactility/Logger.h>
|
||||
|
||||
#include <dirent.h>
|
||||
#include <format>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <cstdio>
|
||||
|
||||
namespace tt::bluetooth::settings {
|
||||
|
||||
static const auto LOGGER = Logger("BluetoothPairedDevice");
|
||||
|
||||
// Use the same directory as the old service for backward compatibility.
|
||||
constexpr auto* DATA_DIR = "/data/service/bluetooth";
|
||||
constexpr auto* DEVICE_SETTINGS_FORMAT = "{}/{}.device.properties";
|
||||
constexpr auto* KEY_NAME = "name";
|
||||
constexpr auto* KEY_ADDR = "addr";
|
||||
constexpr auto* KEY_AUTO_CONNECT = "autoConnect";
|
||||
constexpr auto* KEY_PROFILE_ID = "profileId";
|
||||
|
||||
std::string addrToHex(const std::array<uint8_t, 6>& addr) {
|
||||
std::stringstream stream;
|
||||
stream << std::hex;
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
stream << std::setw(2) << std::setfill('0') << static_cast<int>(addr[i]);
|
||||
}
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
static bool hexToAddr(const std::string& hex, std::array<uint8_t, 6>& addr) {
|
||||
if (hex.size() != 12) {
|
||||
LOGGER.error("hexToAddr() length mismatch: expected 12, got {}", hex.size());
|
||||
return false;
|
||||
}
|
||||
char buf[3] = { 0 };
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
buf[0] = hex[i * 2];
|
||||
buf[1] = hex[i * 2 + 1];
|
||||
char* endptr = nullptr;
|
||||
addr[i] = static_cast<uint8_t>(strtoul(buf, &endptr, 16));
|
||||
if (endptr != buf + 2) {
|
||||
LOGGER.error("hexToAddr() invalid hex at byte {}: '{}{}'", i, buf[0], buf[1]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::string getFilePath(const std::string& addr_hex) {
|
||||
return std::format(DEVICE_SETTINGS_FORMAT, DATA_DIR, addr_hex);
|
||||
}
|
||||
|
||||
bool hasFileForDevice(const std::string& addr_hex) {
|
||||
return file::isFile(getFilePath(addr_hex));
|
||||
}
|
||||
|
||||
bool load(const std::string& addr_hex, PairedDevice& device) {
|
||||
std::map<std::string, std::string> map;
|
||||
if (!file::loadPropertiesFile(getFilePath(addr_hex), map)) return false;
|
||||
if (!map.contains(KEY_ADDR)) return false;
|
||||
if (!hexToAddr(map[KEY_ADDR], device.addr)) return false;
|
||||
|
||||
device.name = map.contains(KEY_NAME) ? map[KEY_NAME] : "";
|
||||
|
||||
device.autoConnect = !map.contains(KEY_AUTO_CONNECT) || (map[KEY_AUTO_CONNECT] == "true");
|
||||
|
||||
if (map.contains(KEY_PROFILE_ID)) {
|
||||
// TODO: Handle incorrect parsing input
|
||||
device.profileId = std::stoi(map[KEY_PROFILE_ID]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool save(const PairedDevice& device) {
|
||||
const auto addr_hex = addrToHex(device.addr);
|
||||
std::map<std::string, std::string> map;
|
||||
map[KEY_NAME] = device.name;
|
||||
map[KEY_ADDR] = addr_hex;
|
||||
map[KEY_AUTO_CONNECT] = device.autoConnect ? "true" : "false";
|
||||
map[KEY_PROFILE_ID] = std::to_string(device.profileId);
|
||||
return file::savePropertiesFile(getFilePath(addr_hex), map);
|
||||
}
|
||||
|
||||
bool remove(const std::string& addr_hex) {
|
||||
const auto file_path = getFilePath(addr_hex);
|
||||
if (!file::isFile(file_path)) return false;
|
||||
return ::remove(file_path.c_str()) == 0;
|
||||
}
|
||||
|
||||
std::vector<PairedDevice> loadAll() {
|
||||
std::vector<dirent> entries;
|
||||
file::scandir(DATA_DIR, entries, [](const dirent* entry) -> int {
|
||||
if (entry->d_type != file::TT_DT_REG && entry->d_type != file::TT_DT_UNKNOWN) return -1;
|
||||
std::string name = entry->d_name;
|
||||
return name.ends_with(".device.properties") ? 0 : -1;
|
||||
}, nullptr);
|
||||
|
||||
std::vector<PairedDevice> result;
|
||||
result.reserve(entries.size());
|
||||
for (const auto& entry : entries) {
|
||||
std::string filename = entry.d_name;
|
||||
constexpr std::string_view suffix = ".device.properties";
|
||||
if (filename.size() <= suffix.size()) continue;
|
||||
const std::string addr_hex = filename.substr(0, filename.size() - suffix.size());
|
||||
PairedDevice device;
|
||||
if (load(addr_hex, device)) {
|
||||
result.push_back(std::move(device));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace tt::bluetooth::settings
|
||||
103
Tactility/Source/bluetooth/BluetoothSettings.cpp
Normal file
103
Tactility/Source/bluetooth/BluetoothSettings.cpp
Normal file
@ -0,0 +1,103 @@
|
||||
#include <Tactility/bluetooth/BluetoothSettings.h>
|
||||
|
||||
#include <Tactility/file/File.h>
|
||||
#include <Tactility/file/PropertiesFile.h>
|
||||
#include <Tactility/Logger.h>
|
||||
#include <Tactility/Mutex.h>
|
||||
|
||||
namespace tt::bluetooth::settings {
|
||||
|
||||
static const auto LOGGER = Logger("BluetoothSettings");
|
||||
|
||||
// Use the same path as the old service so existing settings survive migration.
|
||||
constexpr auto* SETTINGS_PATH = "/data/service/bluetooth/settings.properties";
|
||||
constexpr auto* KEY_ENABLE_ON_BOOT = "enableOnBoot";
|
||||
constexpr auto* KEY_SPP_AUTO_START = "sppAutoStart";
|
||||
constexpr auto* KEY_MIDI_AUTO_START = "midiAutoStart";
|
||||
|
||||
struct BluetoothSettings {
|
||||
bool enableOnBoot = false;
|
||||
bool sppAutoStart = false;
|
||||
bool midiAutoStart = false;
|
||||
};
|
||||
|
||||
static Mutex settings_mutex;
|
||||
static BluetoothSettings cached;
|
||||
static bool cached_valid = false;
|
||||
|
||||
static bool load(BluetoothSettings& out) {
|
||||
std::map<std::string, std::string> map;
|
||||
if (!file::loadPropertiesFile(SETTINGS_PATH, map)) {
|
||||
return false;
|
||||
}
|
||||
auto it = map.find(KEY_ENABLE_ON_BOOT);
|
||||
if (it == map.end()) return false;
|
||||
out.enableOnBoot = (it->second == "true");
|
||||
|
||||
auto spp_it = map.find(KEY_SPP_AUTO_START);
|
||||
out.sppAutoStart = (spp_it != map.end() && spp_it->second == "true");
|
||||
|
||||
auto midi_it = map.find(KEY_MIDI_AUTO_START);
|
||||
out.midiAutoStart = (midi_it != map.end() && midi_it->second == "true");
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool save(const BluetoothSettings& s) {
|
||||
std::map<std::string, std::string> map;
|
||||
file::loadPropertiesFile(SETTINGS_PATH, map); // ignore failure — may not exist yet
|
||||
map[KEY_ENABLE_ON_BOOT] = s.enableOnBoot ? "true" : "false";
|
||||
map[KEY_SPP_AUTO_START] = s.sppAutoStart ? "true" : "false";
|
||||
map[KEY_MIDI_AUTO_START] = s.midiAutoStart ? "true" : "false";
|
||||
return file::savePropertiesFile(SETTINGS_PATH, map);
|
||||
}
|
||||
|
||||
static BluetoothSettings getCachedOrLoad() {
|
||||
settings_mutex.lock();
|
||||
if (!cached_valid) {
|
||||
if (!load(cached)) {
|
||||
cached = BluetoothSettings{};
|
||||
}
|
||||
cached_valid = true;
|
||||
}
|
||||
auto result = cached;
|
||||
settings_mutex.unlock();
|
||||
return result;
|
||||
}
|
||||
|
||||
void setEnableOnBoot(bool enable) {
|
||||
settings_mutex.lock();
|
||||
cached.enableOnBoot = enable;
|
||||
cached_valid = true;
|
||||
settings_mutex.unlock();
|
||||
if (!save(cached)) LOGGER.error("Failed to save");
|
||||
}
|
||||
|
||||
bool shouldEnableOnBoot() {
|
||||
return getCachedOrLoad().enableOnBoot;
|
||||
}
|
||||
|
||||
void setSppAutoStart(bool enable) {
|
||||
settings_mutex.lock();
|
||||
cached.sppAutoStart = enable;
|
||||
cached_valid = true;
|
||||
settings_mutex.unlock();
|
||||
if (!save(cached)) LOGGER.error("Failed to save (setSppAutoStart)");
|
||||
}
|
||||
|
||||
bool shouldSppAutoStart() {
|
||||
return getCachedOrLoad().sppAutoStart;
|
||||
}
|
||||
|
||||
void setMidiAutoStart(bool enable) {
|
||||
settings_mutex.lock();
|
||||
cached.midiAutoStart = enable;
|
||||
cached_valid = true;
|
||||
settings_mutex.unlock();
|
||||
if (!save(cached)) LOGGER.error("Failed to save (setMidiAutoStart)");
|
||||
}
|
||||
|
||||
bool shouldMidiAutoStart() {
|
||||
return getCachedOrLoad().midiAutoStart;
|
||||
}
|
||||
|
||||
} // namespace tt::bluetooth::settings
|
||||
@ -6,6 +6,7 @@
|
||||
namespace tt::lvgl {
|
||||
|
||||
static lv_indev_t* keyboard_device = nullptr;
|
||||
static lv_group_t* pending_keyboard_group = nullptr;
|
||||
|
||||
void software_keyboard_show(lv_obj_t* textarea) {
|
||||
auto gui_service = service::gui::findService();
|
||||
@ -31,12 +32,14 @@ bool software_keyboard_is_enabled() {
|
||||
}
|
||||
|
||||
void software_keyboard_activate(lv_group_t* group) {
|
||||
pending_keyboard_group = group;
|
||||
if (keyboard_device != nullptr) {
|
||||
lv_indev_set_group(keyboard_device, group);
|
||||
}
|
||||
}
|
||||
|
||||
void software_keyboard_deactivate() {
|
||||
pending_keyboard_group = nullptr;
|
||||
if (keyboard_device != nullptr) {
|
||||
lv_indev_set_group(keyboard_device, nullptr);
|
||||
}
|
||||
@ -48,6 +51,11 @@ bool hardware_keyboard_is_available() {
|
||||
|
||||
void hardware_keyboard_set_indev(lv_indev_t* device) {
|
||||
keyboard_device = device;
|
||||
// If an app already activated a keyboard group while no hardware keyboard was
|
||||
// connected, apply the pending group now that the device is available.
|
||||
if (device != nullptr && pending_keyboard_group != nullptr) {
|
||||
lv_indev_set_group(device, pending_keyboard_group);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -11,6 +11,10 @@
|
||||
#include <Tactility/service/ServiceContext.h>
|
||||
#include <Tactility/service/ServicePaths.h>
|
||||
#include <Tactility/service/ServiceRegistration.h>
|
||||
#include <Tactility/bluetooth/Bluetooth.h>
|
||||
#include <tactility/drivers/bluetooth.h>
|
||||
#include <tactility/drivers/bluetooth_serial.h>
|
||||
#include <tactility/drivers/bluetooth_midi.h>
|
||||
#include <Tactility/service/gps/GpsService.h>
|
||||
#include <Tactility/service/wifi/Wifi.h>
|
||||
#include <tactility/check.h>
|
||||
@ -57,6 +61,21 @@ static const char* getWifiStatusIcon(wifi::RadioState state) {
|
||||
}
|
||||
}
|
||||
|
||||
static const char* getBluetoothStatusIcon(tt::bluetooth::RadioState state, bool scanning, bool connected) {
|
||||
switch (state) {
|
||||
using enum tt::bluetooth::RadioState;
|
||||
case Off:
|
||||
case OffPending:
|
||||
return nullptr; // hidden when off
|
||||
case OnPending:
|
||||
case On:
|
||||
if (connected) return LVGL_ICON_STATUSBAR_BLUETOOTH_CONNECTED;
|
||||
if (scanning) return LVGL_ICON_STATUSBAR_BLUETOOTH_SEARCHING;
|
||||
return LVGL_ICON_STATUSBAR_BLUETOOTH;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static const char* getSdCardStatusIcon(bool mounted) {
|
||||
if (mounted) return LVGL_ICON_STATUSBAR_SD_CARD;
|
||||
return LVGL_ICON_STATUSBAR_SD_CARD_ALERT;
|
||||
@ -107,6 +126,8 @@ class StatusbarService final : public Service {
|
||||
std::unique_ptr<Timer> updateTimer;
|
||||
int8_t gps_icon_id;
|
||||
bool gps_last_state = false;
|
||||
int8_t bt_icon_id;
|
||||
const char* bt_last_icon = nullptr;
|
||||
int8_t wifi_icon_id;
|
||||
const char* wifi_last_icon = nullptr;
|
||||
int8_t sdcard_icon_id;
|
||||
@ -136,6 +157,26 @@ class StatusbarService final : public Service {
|
||||
}
|
||||
}
|
||||
|
||||
void updateBluetoothIcon() {
|
||||
auto radio_state = tt::bluetooth::getRadioState();
|
||||
struct Device* btdev = tt::bluetooth::findFirstDevice();
|
||||
bool scanning = btdev ? bluetooth_is_scanning(btdev) : false;
|
||||
struct Device* serial_dev = bluetooth_serial_get_device();
|
||||
struct Device* midi_dev = bluetooth_midi_get_device();
|
||||
bool connected = (serial_dev && bluetooth_serial_is_connected(serial_dev)) ||
|
||||
(midi_dev && bluetooth_midi_is_connected(midi_dev));
|
||||
const char* desired_icon = getBluetoothStatusIcon(radio_state, scanning, connected);
|
||||
if (bt_last_icon != desired_icon) {
|
||||
if (desired_icon != nullptr) {
|
||||
lvgl::statusbar_icon_set_image(bt_icon_id, desired_icon);
|
||||
lvgl::statusbar_icon_set_visibility(bt_icon_id, true);
|
||||
} else {
|
||||
lvgl::statusbar_icon_set_visibility(bt_icon_id, false);
|
||||
}
|
||||
bt_last_icon = desired_icon;
|
||||
}
|
||||
}
|
||||
|
||||
void updateWifiIcon() {
|
||||
wifi::RadioState radio_state = wifi::getRadioState();
|
||||
const char* desired_icon = getWifiStatusIcon(radio_state);
|
||||
@ -186,6 +227,7 @@ class StatusbarService final : public Service {
|
||||
if (lvgl::isStarted()) {
|
||||
if (lvgl::lock(100)) {
|
||||
updateGpsIcon();
|
||||
updateBluetoothIcon();
|
||||
updateWifiIcon();
|
||||
updateSdCardIcon();
|
||||
updatePowerStatusIcon();
|
||||
@ -198,6 +240,7 @@ public:
|
||||
|
||||
StatusbarService() {
|
||||
gps_icon_id = lvgl::statusbar_icon_add();
|
||||
bt_icon_id = lvgl::statusbar_icon_add();
|
||||
sdcard_icon_id = lvgl::statusbar_icon_add();
|
||||
wifi_icon_id = lvgl::statusbar_icon_add();
|
||||
power_icon_id = lvgl::statusbar_icon_add();
|
||||
@ -206,6 +249,7 @@ public:
|
||||
~StatusbarService() override {
|
||||
lvgl::statusbar_icon_remove(wifi_icon_id);
|
||||
lvgl::statusbar_icon_remove(sdcard_icon_id);
|
||||
lvgl::statusbar_icon_remove(bt_icon_id);
|
||||
lvgl::statusbar_icon_remove(power_icon_id);
|
||||
lvgl::statusbar_icon_remove(gps_icon_id);
|
||||
}
|
||||
|
||||
@ -447,6 +447,8 @@ const esp_elfsym main_symbols[] {
|
||||
ESP_ELFSYM_EXPORT(esp_timer_stop),
|
||||
ESP_ELFSYM_EXPORT(esp_timer_delete),
|
||||
ESP_ELFSYM_EXPORT(esp_timer_start_periodic),
|
||||
ESP_ELFSYM_EXPORT(esp_timer_start_once),
|
||||
ESP_ELFSYM_EXPORT(esp_timer_get_time),
|
||||
// delimiter
|
||||
ESP_ELFSYM_END
|
||||
};
|
||||
|
||||
307
TactilityKernel/include/tactility/drivers/bluetooth.h
Normal file
307
TactilityKernel/include/tactility/drivers/bluetooth.h
Normal file
@ -0,0 +1,307 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <tactility/error.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct Device;
|
||||
struct DeviceType;
|
||||
|
||||
// ---- Device name ----
|
||||
|
||||
/**
|
||||
* Maximum BLE device name length in bytes, excluding the NUL terminator.
|
||||
* Must match CONFIG_BT_NIMBLE_GAP_DEVICE_NAME_MAX_LEN (set in device.py for BT devices).
|
||||
* ble_svc_gap_device_name_set() returns BLE_HS_EINVAL for names longer than this.
|
||||
*/
|
||||
#define BLE_DEVICE_NAME_MAX 64
|
||||
|
||||
// ---- Address ----
|
||||
|
||||
#define BT_ADDR_LEN 6
|
||||
|
||||
typedef uint8_t BtAddr[BT_ADDR_LEN];
|
||||
|
||||
// ---- Radio ----
|
||||
|
||||
enum BtRadioState {
|
||||
BT_RADIO_STATE_OFF,
|
||||
BT_RADIO_STATE_ON_PENDING,
|
||||
BT_RADIO_STATE_ON,
|
||||
BT_RADIO_STATE_OFF_PENDING,
|
||||
};
|
||||
|
||||
// ---- Peer record ----
|
||||
|
||||
#define BT_NAME_MAX 248
|
||||
|
||||
struct BtPeerRecord {
|
||||
BtAddr addr;
|
||||
/** BLE address type (BLE_ADDR_PUBLIC=0, BLE_ADDR_RANDOM=1, etc.) */
|
||||
uint8_t addr_type;
|
||||
char name[BT_NAME_MAX + 1];
|
||||
int8_t rssi;
|
||||
bool paired;
|
||||
bool connected;
|
||||
};
|
||||
|
||||
// ---- Profile identifiers ----
|
||||
|
||||
enum BtProfileId {
|
||||
/** Connect to a BLE HID device (keyboard, mouse, gamepad) */
|
||||
BT_PROFILE_HID_HOST,
|
||||
/** Present this device as a BLE HID peripheral (keyboard, gamepad) */
|
||||
BT_PROFILE_HID_DEVICE,
|
||||
/** BLE SPP serial port (Nordic UART Service / custom GATT) */
|
||||
BT_PROFILE_SPP,
|
||||
/** BLE MIDI (GATT-based) */
|
||||
BT_PROFILE_MIDI,
|
||||
};
|
||||
|
||||
enum BtProfileState {
|
||||
BT_PROFILE_STATE_IDLE,
|
||||
BT_PROFILE_STATE_CONNECTING,
|
||||
BT_PROFILE_STATE_CONNECTED,
|
||||
BT_PROFILE_STATE_DISCONNECTING,
|
||||
};
|
||||
|
||||
// ---- Events ----
|
||||
|
||||
enum BtEventType {
|
||||
/** Radio state changed */
|
||||
BT_EVENT_RADIO_STATE_CHANGED,
|
||||
/** Started scanning for peers */
|
||||
BT_EVENT_SCAN_STARTED,
|
||||
/** Finished scanning for peers */
|
||||
BT_EVENT_SCAN_FINISHED,
|
||||
/** A new peer was found during scan */
|
||||
BT_EVENT_PEER_FOUND,
|
||||
/** Pairing requires user confirmation (passkey displayed or entry required) */
|
||||
BT_EVENT_PAIR_REQUEST,
|
||||
/** Pairing attempt completed */
|
||||
BT_EVENT_PAIR_RESULT,
|
||||
/** A peer's connection state changed */
|
||||
BT_EVENT_CONNECT_STATE_CHANGED,
|
||||
/** A profile's state changed */
|
||||
BT_EVENT_PROFILE_STATE_CHANGED,
|
||||
/** Data was received on the BLE SPP (NUS) RX characteristic */
|
||||
BT_EVENT_SPP_DATA_RECEIVED,
|
||||
/** Data was received on the BLE MIDI I/O characteristic */
|
||||
BT_EVENT_MIDI_DATA_RECEIVED,
|
||||
};
|
||||
|
||||
enum BtPairResult {
|
||||
BT_PAIR_RESULT_SUCCESS,
|
||||
BT_PAIR_RESULT_FAILED,
|
||||
BT_PAIR_RESULT_REJECTED,
|
||||
/** Stale bond detected and removed; fresh pairing will follow */
|
||||
BT_PAIR_RESULT_BOND_LOST,
|
||||
};
|
||||
|
||||
struct BtPairRequestData {
|
||||
BtAddr addr;
|
||||
uint32_t passkey; /**< Passkey to display (0 if not applicable) */
|
||||
bool needs_confirmation; /**< true: just confirm, false: user must enter passkey */
|
||||
};
|
||||
|
||||
struct BtPairResultData {
|
||||
BtAddr addr;
|
||||
enum BtPairResult result;
|
||||
/** Profile active when pairing completed (BtProfileId value) */
|
||||
int profile;
|
||||
};
|
||||
|
||||
struct BtProfileStateData {
|
||||
BtAddr addr;
|
||||
enum BtProfileId profile;
|
||||
enum BtProfileState state;
|
||||
};
|
||||
|
||||
struct BtEvent {
|
||||
enum BtEventType type;
|
||||
union {
|
||||
enum BtRadioState radio_state;
|
||||
struct BtPeerRecord peer;
|
||||
struct BtPairRequestData pair_request;
|
||||
struct BtPairResultData pair_result;
|
||||
struct BtProfileStateData profile_state;
|
||||
};
|
||||
};
|
||||
|
||||
typedef void (*BtEventCallback)(struct Device* device, void* context, struct BtEvent event);
|
||||
|
||||
// ---- Top-level Bluetooth API ----
|
||||
|
||||
struct BluetoothApi {
|
||||
/**
|
||||
* Get the radio state.
|
||||
* @param[in] device the bluetooth device
|
||||
* @param[out] state the current radio state
|
||||
* @return ERROR_NONE on success
|
||||
*/
|
||||
error_t (*get_radio_state)(struct Device* device, enum BtRadioState* state);
|
||||
|
||||
/**
|
||||
* Enable or disable the Bluetooth radio.
|
||||
* @param[in] device the bluetooth device
|
||||
* @param[in] enabled true to enable, false to disable
|
||||
* @return ERROR_NONE on success
|
||||
*/
|
||||
error_t (*set_radio_enabled)(struct Device* device, bool enabled);
|
||||
|
||||
/**
|
||||
* Start scanning for nearby BLE devices.
|
||||
* @param[in] device the bluetooth device
|
||||
* @return ERROR_NONE on success
|
||||
*/
|
||||
error_t (*scan_start)(struct Device* device);
|
||||
|
||||
/**
|
||||
* Stop an active scan.
|
||||
* @param[in] device the bluetooth device
|
||||
* @return ERROR_NONE on success
|
||||
*/
|
||||
error_t (*scan_stop)(struct Device* device);
|
||||
|
||||
/**
|
||||
* @param[in] device the bluetooth device
|
||||
* @return true when a scan is in progress
|
||||
*/
|
||||
bool (*is_scanning)(struct Device* device);
|
||||
|
||||
/**
|
||||
* Initiate pairing with a peer.
|
||||
* @param[in] device the bluetooth device
|
||||
* @param[in] addr the peer address
|
||||
* @return ERROR_NONE on success
|
||||
*/
|
||||
error_t (*pair)(struct Device* device, const BtAddr addr);
|
||||
|
||||
/**
|
||||
* Remove a previously paired peer.
|
||||
* @param[in] device the bluetooth device
|
||||
* @param[in] addr the peer address
|
||||
* @return ERROR_NONE on success
|
||||
*/
|
||||
error_t (*unpair)(struct Device* device, const BtAddr addr);
|
||||
|
||||
/**
|
||||
* Get the list of currently paired peers.
|
||||
* @param[in] device the bluetooth device
|
||||
* @param[out] out the buffer to write records into (may be NULL to query count only)
|
||||
* @param[in, out] count in: capacity of out, out: actual number of paired peers
|
||||
* @return ERROR_NONE on success
|
||||
*/
|
||||
error_t (*get_paired_peers)(struct Device* device, struct BtPeerRecord* out, size_t* count);
|
||||
|
||||
/**
|
||||
* Connect to a peer using the specified profile.
|
||||
* @param[in] device the bluetooth device
|
||||
* @param[in] addr the peer address
|
||||
* @param[in] profile the profile to connect with
|
||||
* @return ERROR_NONE on success
|
||||
*/
|
||||
error_t (*connect)(struct Device* device, const BtAddr addr, enum BtProfileId profile);
|
||||
|
||||
/**
|
||||
* Disconnect a peer from the specified profile.
|
||||
* @param[in] device the bluetooth device
|
||||
* @param[in] addr the peer address
|
||||
* @param[in] profile the profile to disconnect from
|
||||
* @return ERROR_NONE on success
|
||||
*/
|
||||
error_t (*disconnect)(struct Device* device, const BtAddr addr, enum BtProfileId profile);
|
||||
|
||||
/**
|
||||
* Add an event callback.
|
||||
* @param[in] device the bluetooth device
|
||||
* @param[in] context context pointer passed to the callback
|
||||
* @param[in] callback the callback function
|
||||
* @return ERROR_NONE on success
|
||||
*/
|
||||
error_t (*add_event_callback)(struct Device* device, void* context, BtEventCallback callback);
|
||||
|
||||
/**
|
||||
* Remove a previously added event callback.
|
||||
* @param[in] device the bluetooth device
|
||||
* @param[in] callback the callback to remove
|
||||
* @return ERROR_NONE on success
|
||||
*/
|
||||
error_t (*remove_event_callback)(struct Device* device, BtEventCallback callback);
|
||||
|
||||
/**
|
||||
* Set the BLE device name used in advertising and the GAP service.
|
||||
* Can be called before or after the radio is enabled.
|
||||
* If called while advertising is active, advertising restarts with the new name.
|
||||
* @param[in] device the bluetooth device
|
||||
* @param[in] name NUL-terminated name (max BLE_DEVICE_NAME_MAX bytes)
|
||||
* @return ERROR_NONE on success, ERROR_INVALID_ARGUMENT if name is too long or NULL
|
||||
*/
|
||||
error_t (*set_device_name)(struct Device* device, const char* name);
|
||||
|
||||
/**
|
||||
* Get the current BLE device name.
|
||||
* @param[in] device the bluetooth device
|
||||
* @param[out] buf buffer to write the name into
|
||||
* @param[in] buf_len size of buf (must be >= BLE_DEVICE_NAME_MAX + 1)
|
||||
* @return ERROR_NONE on success
|
||||
*/
|
||||
error_t (*get_device_name)(struct Device* device, char* buf, size_t buf_len);
|
||||
|
||||
/**
|
||||
* Notify the driver that a HID host connection is in progress or complete.
|
||||
* Called by the Tactility HID host module to prevent name resolution from
|
||||
* initiating a simultaneous central connection (BLE_HS_EALREADY).
|
||||
* @param[in] device the bluetooth device
|
||||
* @param[in] active true when HID host is connecting/connected, false when idle
|
||||
*/
|
||||
void (*set_hid_host_active)(struct Device* device, bool active);
|
||||
|
||||
/**
|
||||
* Fire an event through all registered event callbacks.
|
||||
* Used by the Tactility HID host module to inject profile-state events that
|
||||
* originate outside the platform driver (e.g. HID host connect/disconnect).
|
||||
*/
|
||||
void (*fire_event)(struct Device* device, struct BtEvent event);
|
||||
};
|
||||
|
||||
extern const struct DeviceType BLUETOOTH_TYPE;
|
||||
|
||||
// ---- Public C API ----
|
||||
// These are the only functions external code should call.
|
||||
// The BluetoothApi struct above is the internal driver interface only.
|
||||
|
||||
/**
|
||||
* Find the first ready Bluetooth device.
|
||||
* Use this instead of referencing BLUETOOTH_TYPE directly from external apps,
|
||||
* since data symbols may not be exported by the ELF loader.
|
||||
* @return the first ready Device of BLUETOOTH_TYPE, or NULL if none found.
|
||||
*/
|
||||
struct Device* bluetooth_find_first_ready_device(void);
|
||||
|
||||
error_t bluetooth_get_radio_state(struct Device* device, enum BtRadioState* state);
|
||||
error_t bluetooth_set_radio_enabled(struct Device* device, bool enabled);
|
||||
error_t bluetooth_scan_start(struct Device* device);
|
||||
error_t bluetooth_scan_stop(struct Device* device);
|
||||
bool bluetooth_is_scanning(struct Device* device);
|
||||
error_t bluetooth_pair(struct Device* device, const BtAddr addr);
|
||||
error_t bluetooth_unpair(struct Device* device, const BtAddr addr);
|
||||
error_t bluetooth_get_paired_peers(struct Device* device, struct BtPeerRecord* out, size_t* count);
|
||||
error_t bluetooth_connect(struct Device* device, const BtAddr addr, enum BtProfileId profile);
|
||||
error_t bluetooth_disconnect(struct Device* device, const BtAddr addr, enum BtProfileId profile);
|
||||
error_t bluetooth_add_event_callback(struct Device* device, void* context, BtEventCallback callback);
|
||||
error_t bluetooth_remove_event_callback(struct Device* device, BtEventCallback callback);
|
||||
error_t bluetooth_set_device_name(struct Device* device, const char* name);
|
||||
error_t bluetooth_get_device_name(struct Device* device, char* buf, size_t buf_len);
|
||||
void bluetooth_set_hid_host_active(struct Device* device, bool active);
|
||||
void bluetooth_fire_event(struct Device* device, struct BtEvent event);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
124
TactilityKernel/include/tactility/drivers/bluetooth_hid_device.h
Normal file
124
TactilityKernel/include/tactility/drivers/bluetooth_hid_device.h
Normal file
@ -0,0 +1,124 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <tactility/error.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct Device;
|
||||
struct DeviceType;
|
||||
|
||||
// ---- HID device mode ----
|
||||
|
||||
/**
|
||||
* Selects the HID report descriptor and appearance used when this device
|
||||
* operates as a BLE HID peripheral.
|
||||
*/
|
||||
enum BtHidDeviceMode {
|
||||
/** Keyboard (report ID 1, 8 bytes) + Consumer (report ID 2, 2 bytes). */
|
||||
BT_HID_DEVICE_MODE_KEYBOARD,
|
||||
/** Mouse only (report ID 1, 4 bytes). */
|
||||
BT_HID_DEVICE_MODE_MOUSE,
|
||||
/** Keyboard + Consumer + Mouse (report IDs 1, 2, 3). */
|
||||
BT_HID_DEVICE_MODE_KEYBOARD_MOUSE,
|
||||
/** Gamepad (report ID 1, 8 bytes: 2-byte buttons + 6-byte axes). */
|
||||
BT_HID_DEVICE_MODE_GAMEPAD,
|
||||
};
|
||||
|
||||
// ---- BLE HID Device child device ----
|
||||
|
||||
/**
|
||||
* BLE HID device profile API (present this device as a BLE HID peripheral).
|
||||
* This API is exposed by a child device of the Bluetooth device.
|
||||
*/
|
||||
struct BtHidDeviceApi {
|
||||
/**
|
||||
* Start advertising as a BLE HID device with the given mode.
|
||||
* Sets up the appropriate GATT report descriptor and starts advertising.
|
||||
* @param[in] device the HID device child device
|
||||
* @param[in] mode the HID device mode (keyboard, mouse, gamepad, etc.)
|
||||
* @return ERROR_NONE on success
|
||||
*/
|
||||
error_t (*start)(struct Device* device, enum BtHidDeviceMode mode);
|
||||
|
||||
/**
|
||||
* Stop advertising as a BLE HID device and close any active connection.
|
||||
* @param[in] device the HID device child device
|
||||
* @return ERROR_NONE on success
|
||||
*/
|
||||
error_t (*stop)(struct Device* device);
|
||||
|
||||
/**
|
||||
* Send a single key event when operating as a HID keyboard device.
|
||||
* @param[in] device the HID device child device
|
||||
* @param[in] keycode the HID keycode
|
||||
* @param[in] pressed true for key down, false for key up
|
||||
* @return ERROR_NONE on success
|
||||
*/
|
||||
error_t (*send_key)(struct Device* device, uint8_t keycode, bool pressed);
|
||||
|
||||
/**
|
||||
* Send a full keyboard HID report (8 bytes: modifier, reserved, keycodes[6]).
|
||||
* @param[in] device the HID device child device
|
||||
* @param[in] report pointer to the 8-byte keyboard report
|
||||
* @param[in] len number of bytes (up to 8)
|
||||
* @return ERROR_NONE on success
|
||||
*/
|
||||
error_t (*send_keyboard)(struct Device* device, const uint8_t* report, size_t len);
|
||||
|
||||
/**
|
||||
* Send a consumer control HID report (2 bytes: 16-bit usage code, little-endian).
|
||||
* @param[in] device the HID device child device
|
||||
* @param[in] report pointer to the 2-byte consumer report
|
||||
* @param[in] len number of bytes (up to 2)
|
||||
* @return ERROR_NONE on success
|
||||
*/
|
||||
error_t (*send_consumer)(struct Device* device, const uint8_t* report, size_t len);
|
||||
|
||||
/**
|
||||
* Send a mouse HID report (4 bytes: buttons, X, Y, wheel).
|
||||
* @param[in] device the HID device child device
|
||||
* @param[in] report pointer to the 4-byte mouse report
|
||||
* @param[in] len number of bytes (up to 4)
|
||||
* @return ERROR_NONE on success
|
||||
*/
|
||||
error_t (*send_mouse)(struct Device* device, const uint8_t* report, size_t len);
|
||||
|
||||
/**
|
||||
* Send a gamepad HID report (8 bytes: buttons[2] + axes[6]).
|
||||
* @param[in] device the HID device child device
|
||||
* @param[in] report pointer to the 8-byte gamepad report
|
||||
* @param[in] len number of bytes (up to 8)
|
||||
* @return ERROR_NONE on success
|
||||
*/
|
||||
error_t (*send_gamepad)(struct Device* device, const uint8_t* report, size_t len);
|
||||
|
||||
/**
|
||||
* @param[in] device the HID device child device
|
||||
* @return true when a remote host is connected to this HID device
|
||||
*/
|
||||
bool (*is_connected)(struct Device* device);
|
||||
};
|
||||
|
||||
extern const struct DeviceType BLUETOOTH_HID_DEVICE_TYPE;
|
||||
|
||||
/** Find the first ready BLE HID device child device. Returns NULL if unavailable. */
|
||||
struct Device* bluetooth_hid_device_get_device(void);
|
||||
|
||||
error_t bluetooth_hid_device_start(struct Device* device, enum BtHidDeviceMode mode);
|
||||
error_t bluetooth_hid_device_stop(struct Device* device);
|
||||
error_t bluetooth_hid_device_send_key(struct Device* device, uint8_t keycode, bool pressed);
|
||||
error_t bluetooth_hid_device_send_keyboard(struct Device* device, const uint8_t* report, size_t len);
|
||||
error_t bluetooth_hid_device_send_consumer(struct Device* device, const uint8_t* report, size_t len);
|
||||
error_t bluetooth_hid_device_send_mouse(struct Device* device, const uint8_t* report, size_t len);
|
||||
error_t bluetooth_hid_device_send_gamepad(struct Device* device, const uint8_t* report, size_t len);
|
||||
bool bluetooth_hid_device_is_connected(struct Device* device);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
65
TactilityKernel/include/tactility/drivers/bluetooth_midi.h
Normal file
65
TactilityKernel/include/tactility/drivers/bluetooth_midi.h
Normal file
@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <tactility/error.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct Device;
|
||||
struct DeviceType;
|
||||
|
||||
// ---- BLE MIDI child device ----
|
||||
|
||||
/**
|
||||
* BLE MIDI profile API (MIDI over Bluetooth Low Energy specification).
|
||||
* This API is exposed by a child device of the Bluetooth device.
|
||||
*/
|
||||
struct BtMidiApi {
|
||||
/**
|
||||
* Start advertising the BLE MIDI service and accept incoming connections.
|
||||
* @param[in] device the MIDI child device
|
||||
* @return ERROR_NONE on success
|
||||
*/
|
||||
error_t (*start)(struct Device* device);
|
||||
|
||||
/**
|
||||
* Stop the BLE MIDI service and close any active connections.
|
||||
* @param[in] device the MIDI child device
|
||||
* @return ERROR_NONE on success
|
||||
*/
|
||||
error_t (*stop)(struct Device* device);
|
||||
|
||||
/**
|
||||
* Send MIDI message bytes over the BLE MIDI connection.
|
||||
* @param[in] device the MIDI child device
|
||||
* @param[in] msg the raw MIDI bytes
|
||||
* @param[in] len the number of bytes
|
||||
* @return ERROR_NONE on success
|
||||
*/
|
||||
error_t (*send)(struct Device* device, const uint8_t* msg, size_t len);
|
||||
|
||||
/**
|
||||
* @param[in] device the MIDI child device
|
||||
* @return true when a remote device is connected
|
||||
*/
|
||||
bool (*is_connected)(struct Device* device);
|
||||
};
|
||||
|
||||
extern const struct DeviceType BLUETOOTH_MIDI_TYPE;
|
||||
|
||||
/** Find the first ready BLE MIDI child device. Returns NULL if unavailable. */
|
||||
struct Device* bluetooth_midi_get_device(void);
|
||||
|
||||
error_t bluetooth_midi_start(struct Device* device);
|
||||
error_t bluetooth_midi_stop(struct Device* device);
|
||||
error_t bluetooth_midi_send(struct Device* device, const uint8_t* msg, size_t len);
|
||||
bool bluetooth_midi_is_connected(struct Device* device);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
77
TactilityKernel/include/tactility/drivers/bluetooth_serial.h
Normal file
77
TactilityKernel/include/tactility/drivers/bluetooth_serial.h
Normal file
@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <tactility/error.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct Device;
|
||||
struct DeviceType;
|
||||
|
||||
// ---- BLE Serial (Nordic UART Service) child device ----
|
||||
|
||||
/**
|
||||
* BLE serial port profile API (Nordic UART Service or equivalent GATT-based SPP).
|
||||
* This API is exposed by a child device of the Bluetooth device.
|
||||
*/
|
||||
struct BtSerialApi {
|
||||
/**
|
||||
* Start advertising the BLE serial service and accept incoming connections.
|
||||
* @param[in] device the serial child device
|
||||
* @return ERROR_NONE on success
|
||||
*/
|
||||
error_t (*start)(struct Device* device);
|
||||
|
||||
/**
|
||||
* Stop the BLE serial service and close any active connections.
|
||||
* @param[in] device the serial child device
|
||||
* @return ERROR_NONE on success
|
||||
*/
|
||||
error_t (*stop)(struct Device* device);
|
||||
|
||||
/**
|
||||
* Write data over the BLE serial connection.
|
||||
* @param[in] device the serial child device
|
||||
* @param[in] data the data to send
|
||||
* @param[in] len the number of bytes to send
|
||||
* @param[out] written the number of bytes actually written
|
||||
* @return ERROR_NONE on success
|
||||
*/
|
||||
error_t (*write)(struct Device* device, const uint8_t* data, size_t len, size_t* written);
|
||||
|
||||
/**
|
||||
* Read data from the BLE serial receive buffer.
|
||||
* @param[in] device the serial child device
|
||||
* @param[out] data the buffer to read into
|
||||
* @param[in] max_len the maximum number of bytes to read
|
||||
* @param[out] read_out the number of bytes actually read
|
||||
* @return ERROR_NONE on success
|
||||
*/
|
||||
error_t (*read)(struct Device* device, uint8_t* data, size_t max_len, size_t* read_out);
|
||||
|
||||
/**
|
||||
* @param[in] device the serial child device
|
||||
* @return true when a remote device is connected
|
||||
*/
|
||||
bool (*is_connected)(struct Device* device);
|
||||
};
|
||||
|
||||
extern const struct DeviceType BLUETOOTH_SERIAL_TYPE;
|
||||
|
||||
/** Find the first ready BLE serial child device. Returns NULL if unavailable. */
|
||||
struct Device* bluetooth_serial_get_device(void);
|
||||
|
||||
error_t bluetooth_serial_start(struct Device* device);
|
||||
error_t bluetooth_serial_stop(struct Device* device);
|
||||
error_t bluetooth_serial_write(struct Device* device, const uint8_t* data, size_t len, size_t* written);
|
||||
error_t bluetooth_serial_read(struct Device* device, uint8_t* data, size_t max_len, size_t* read_out);
|
||||
bool bluetooth_serial_is_connected(struct Device* device);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
103
TactilityKernel/source/drivers/bluetooth.cpp
Normal file
103
TactilityKernel/source/drivers/bluetooth.cpp
Normal file
@ -0,0 +1,103 @@
|
||||
#include <tactility/drivers/bluetooth.h>
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/driver.h>
|
||||
|
||||
#define BT_API(device) ((const struct BluetoothApi*)device_get_driver(device)->api)
|
||||
|
||||
extern "C" {
|
||||
|
||||
// ---- Device lookup ----
|
||||
|
||||
struct Device* bluetooth_find_first_ready_device() {
|
||||
struct Device* found = nullptr;
|
||||
device_for_each_of_type(&BLUETOOTH_TYPE, &found, [](struct Device* dev, void* ctx) -> bool {
|
||||
if (device_is_ready(dev)) {
|
||||
*static_cast<struct Device**>(ctx) = dev;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return found;
|
||||
}
|
||||
|
||||
// ---- Core radio / scan ----
|
||||
|
||||
error_t bluetooth_get_radio_state(struct Device* device, enum BtRadioState* state) {
|
||||
return BT_API(device)->get_radio_state(device, state);
|
||||
}
|
||||
|
||||
error_t bluetooth_set_radio_enabled(struct Device* device, bool enabled) {
|
||||
return BT_API(device)->set_radio_enabled(device, enabled);
|
||||
}
|
||||
|
||||
error_t bluetooth_scan_start(struct Device* device) {
|
||||
return BT_API(device)->scan_start(device);
|
||||
}
|
||||
|
||||
error_t bluetooth_scan_stop(struct Device* device) {
|
||||
return BT_API(device)->scan_stop(device);
|
||||
}
|
||||
|
||||
bool bluetooth_is_scanning(struct Device* device) {
|
||||
return BT_API(device)->is_scanning(device);
|
||||
}
|
||||
|
||||
// ---- Pairing ----
|
||||
|
||||
error_t bluetooth_pair(struct Device* device, const BtAddr addr) {
|
||||
return BT_API(device)->pair(device, addr);
|
||||
}
|
||||
|
||||
error_t bluetooth_unpair(struct Device* device, const BtAddr addr) {
|
||||
return BT_API(device)->unpair(device, addr);
|
||||
}
|
||||
|
||||
error_t bluetooth_get_paired_peers(struct Device* device, struct BtPeerRecord* out, size_t* count) {
|
||||
return BT_API(device)->get_paired_peers(device, out, count);
|
||||
}
|
||||
|
||||
// ---- Connect / disconnect ----
|
||||
|
||||
error_t bluetooth_connect(struct Device* device, const BtAddr addr, enum BtProfileId profile) {
|
||||
return BT_API(device)->connect(device, addr, profile);
|
||||
}
|
||||
|
||||
error_t bluetooth_disconnect(struct Device* device, const BtAddr addr, enum BtProfileId profile) {
|
||||
return BT_API(device)->disconnect(device, addr, profile);
|
||||
}
|
||||
|
||||
// ---- Event callbacks ----
|
||||
|
||||
error_t bluetooth_add_event_callback(struct Device* device, void* context, BtEventCallback callback) {
|
||||
return BT_API(device)->add_event_callback(device, context, callback);
|
||||
}
|
||||
|
||||
error_t bluetooth_remove_event_callback(struct Device* device, BtEventCallback callback) {
|
||||
return BT_API(device)->remove_event_callback(device, callback);
|
||||
}
|
||||
|
||||
error_t bluetooth_set_device_name(struct Device* device, const char* name) {
|
||||
return BT_API(device)->set_device_name(device, name);
|
||||
}
|
||||
|
||||
error_t bluetooth_get_device_name(struct Device* device, char* buf, size_t buf_len) {
|
||||
return BT_API(device)->get_device_name(device, buf, buf_len);
|
||||
}
|
||||
|
||||
// ---- HID host active flag ----
|
||||
|
||||
void bluetooth_set_hid_host_active(struct Device* device, bool active) {
|
||||
BT_API(device)->set_hid_host_active(device, active);
|
||||
}
|
||||
|
||||
void bluetooth_fire_event(struct Device* device, struct BtEvent event) {
|
||||
BT_API(device)->fire_event(device, event);
|
||||
}
|
||||
|
||||
// ---- Device type ----
|
||||
|
||||
const struct DeviceType BLUETOOTH_TYPE = {
|
||||
.name = "bluetooth",
|
||||
};
|
||||
|
||||
} // extern "C"
|
||||
57
TactilityKernel/source/drivers/bluetooth_hid_device.cpp
Normal file
57
TactilityKernel/source/drivers/bluetooth_hid_device.cpp
Normal file
@ -0,0 +1,57 @@
|
||||
#include <tactility/drivers/bluetooth_hid_device.h>
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/driver.h>
|
||||
|
||||
#define BT_HID_DEVICE_API(device) ((const struct BtHidDeviceApi*)device_get_driver(device)->api)
|
||||
|
||||
extern "C" {
|
||||
|
||||
const struct DeviceType BLUETOOTH_HID_DEVICE_TYPE = {
|
||||
.name = "bluetooth-hid-device",
|
||||
};
|
||||
|
||||
struct Device* bluetooth_hid_device_get_device() {
|
||||
struct Device* found = nullptr;
|
||||
device_for_each_of_type(&BLUETOOTH_HID_DEVICE_TYPE, &found, [](struct Device* dev, void* ctx) -> bool {
|
||||
if (device_is_ready(dev)) {
|
||||
*static_cast<struct Device**>(ctx) = dev;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return found;
|
||||
}
|
||||
|
||||
error_t bluetooth_hid_device_start(struct Device* device, enum BtHidDeviceMode mode) {
|
||||
return BT_HID_DEVICE_API(device)->start(device, mode);
|
||||
}
|
||||
|
||||
error_t bluetooth_hid_device_stop(struct Device* device) {
|
||||
return BT_HID_DEVICE_API(device)->stop(device);
|
||||
}
|
||||
|
||||
error_t bluetooth_hid_device_send_key(struct Device* device, uint8_t keycode, bool pressed) {
|
||||
return BT_HID_DEVICE_API(device)->send_key(device, keycode, pressed);
|
||||
}
|
||||
|
||||
error_t bluetooth_hid_device_send_keyboard(struct Device* device, const uint8_t* report, size_t len) {
|
||||
return BT_HID_DEVICE_API(device)->send_keyboard(device, report, len);
|
||||
}
|
||||
|
||||
error_t bluetooth_hid_device_send_consumer(struct Device* device, const uint8_t* report, size_t len) {
|
||||
return BT_HID_DEVICE_API(device)->send_consumer(device, report, len);
|
||||
}
|
||||
|
||||
error_t bluetooth_hid_device_send_mouse(struct Device* device, const uint8_t* report, size_t len) {
|
||||
return BT_HID_DEVICE_API(device)->send_mouse(device, report, len);
|
||||
}
|
||||
|
||||
error_t bluetooth_hid_device_send_gamepad(struct Device* device, const uint8_t* report, size_t len) {
|
||||
return BT_HID_DEVICE_API(device)->send_gamepad(device, report, len);
|
||||
}
|
||||
|
||||
bool bluetooth_hid_device_is_connected(struct Device* device) {
|
||||
return BT_HID_DEVICE_API(device)->is_connected(device);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
41
TactilityKernel/source/drivers/bluetooth_midi.cpp
Normal file
41
TactilityKernel/source/drivers/bluetooth_midi.cpp
Normal file
@ -0,0 +1,41 @@
|
||||
#include <tactility/drivers/bluetooth_midi.h>
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/driver.h>
|
||||
|
||||
#define BT_MIDI_API(device) ((const struct BtMidiApi*)device_get_driver(device)->api)
|
||||
|
||||
extern "C" {
|
||||
|
||||
const struct DeviceType BLUETOOTH_MIDI_TYPE = {
|
||||
.name = "bluetooth-midi",
|
||||
};
|
||||
|
||||
struct Device* bluetooth_midi_get_device() {
|
||||
struct Device* found = nullptr;
|
||||
device_for_each_of_type(&BLUETOOTH_MIDI_TYPE, &found, [](struct Device* dev, void* ctx) -> bool {
|
||||
if (device_is_ready(dev)) {
|
||||
*static_cast<struct Device**>(ctx) = dev;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return found;
|
||||
}
|
||||
|
||||
error_t bluetooth_midi_start(struct Device* device) {
|
||||
return BT_MIDI_API(device)->start(device);
|
||||
}
|
||||
|
||||
error_t bluetooth_midi_stop(struct Device* device) {
|
||||
return BT_MIDI_API(device)->stop(device);
|
||||
}
|
||||
|
||||
error_t bluetooth_midi_send(struct Device* device, const uint8_t* msg, size_t len) {
|
||||
return BT_MIDI_API(device)->send(device, msg, len);
|
||||
}
|
||||
|
||||
bool bluetooth_midi_is_connected(struct Device* device) {
|
||||
return BT_MIDI_API(device)->is_connected(device);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
45
TactilityKernel/source/drivers/bluetooth_serial.cpp
Normal file
45
TactilityKernel/source/drivers/bluetooth_serial.cpp
Normal file
@ -0,0 +1,45 @@
|
||||
#include <tactility/drivers/bluetooth_serial.h>
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/driver.h>
|
||||
|
||||
#define BT_SERIAL_API(device) ((const struct BtSerialApi*)device_get_driver(device)->api)
|
||||
|
||||
extern "C" {
|
||||
|
||||
const struct DeviceType BLUETOOTH_SERIAL_TYPE = {
|
||||
.name = "bluetooth-serial",
|
||||
};
|
||||
|
||||
struct Device* bluetooth_serial_get_device() {
|
||||
struct Device* found = nullptr;
|
||||
device_for_each_of_type(&BLUETOOTH_SERIAL_TYPE, &found, [](struct Device* dev, void* ctx) -> bool {
|
||||
if (device_is_ready(dev)) {
|
||||
*static_cast<struct Device**>(ctx) = dev;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return found;
|
||||
}
|
||||
|
||||
error_t bluetooth_serial_start(struct Device* device) {
|
||||
return BT_SERIAL_API(device)->start(device);
|
||||
}
|
||||
|
||||
error_t bluetooth_serial_stop(struct Device* device) {
|
||||
return BT_SERIAL_API(device)->stop(device);
|
||||
}
|
||||
|
||||
error_t bluetooth_serial_write(struct Device* device, const uint8_t* data, size_t len, size_t* written) {
|
||||
return BT_SERIAL_API(device)->write(device, data, len, written);
|
||||
}
|
||||
|
||||
error_t bluetooth_serial_read(struct Device* device, uint8_t* data, size_t max_len, size_t* read_out) {
|
||||
return BT_SERIAL_API(device)->read(device, data, max_len, read_out);
|
||||
}
|
||||
|
||||
bool bluetooth_serial_is_connected(struct Device* device) {
|
||||
return BT_SERIAL_API(device)->is_connected(device);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
@ -4,6 +4,10 @@
|
||||
#include <tactility/concurrent/timer.h>
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/driver.h>
|
||||
#include <tactility/drivers/bluetooth.h>
|
||||
#include <tactility/drivers/bluetooth_serial.h>
|
||||
#include <tactility/drivers/bluetooth_midi.h>
|
||||
#include <tactility/drivers/bluetooth_hid_device.h>
|
||||
#include <tactility/drivers/gpio_controller.h>
|
||||
#include <tactility/drivers/i2c_controller.h>
|
||||
#include <tactility/drivers/i2s_controller.h>
|
||||
@ -50,6 +54,7 @@ const struct ModuleSymbol KERNEL_SYMBOLS[] = {
|
||||
DEFINE_MODULE_SYMBOL(device_for_each_child),
|
||||
DEFINE_MODULE_SYMBOL(device_for_each_of_type),
|
||||
DEFINE_MODULE_SYMBOL(device_exists_of_type),
|
||||
DEFINE_MODULE_SYMBOL(device_find_by_name),
|
||||
// driver
|
||||
DEFINE_MODULE_SYMBOL(driver_construct),
|
||||
DEFINE_MODULE_SYMBOL(driver_destruct),
|
||||
@ -117,6 +122,51 @@ const struct ModuleSymbol KERNEL_SYMBOLS[] = {
|
||||
DEFINE_MODULE_SYMBOL(uart_controller_get_available),
|
||||
DEFINE_MODULE_SYMBOL(uart_controller_flush_input),
|
||||
DEFINE_MODULE_SYMBOL(UART_CONTROLLER_TYPE),
|
||||
// drivers/bluetooth
|
||||
DEFINE_MODULE_SYMBOL(bluetooth_find_first_ready_device),
|
||||
DEFINE_MODULE_SYMBOL(bluetooth_get_radio_state),
|
||||
DEFINE_MODULE_SYMBOL(bluetooth_set_radio_enabled),
|
||||
DEFINE_MODULE_SYMBOL(bluetooth_scan_start),
|
||||
DEFINE_MODULE_SYMBOL(bluetooth_scan_stop),
|
||||
DEFINE_MODULE_SYMBOL(bluetooth_is_scanning),
|
||||
DEFINE_MODULE_SYMBOL(bluetooth_pair),
|
||||
DEFINE_MODULE_SYMBOL(bluetooth_unpair),
|
||||
DEFINE_MODULE_SYMBOL(bluetooth_get_paired_peers),
|
||||
DEFINE_MODULE_SYMBOL(bluetooth_connect),
|
||||
DEFINE_MODULE_SYMBOL(bluetooth_disconnect),
|
||||
DEFINE_MODULE_SYMBOL(bluetooth_add_event_callback),
|
||||
DEFINE_MODULE_SYMBOL(bluetooth_remove_event_callback),
|
||||
DEFINE_MODULE_SYMBOL(bluetooth_set_device_name),
|
||||
DEFINE_MODULE_SYMBOL(bluetooth_get_device_name),
|
||||
DEFINE_MODULE_SYMBOL(bluetooth_set_hid_host_active),
|
||||
DEFINE_MODULE_SYMBOL(bluetooth_fire_event),
|
||||
DEFINE_MODULE_SYMBOL(BLUETOOTH_TYPE),
|
||||
// drivers/bluetooth_serial
|
||||
DEFINE_MODULE_SYMBOL(bluetooth_serial_get_device),
|
||||
DEFINE_MODULE_SYMBOL(bluetooth_serial_start),
|
||||
DEFINE_MODULE_SYMBOL(bluetooth_serial_stop),
|
||||
DEFINE_MODULE_SYMBOL(bluetooth_serial_write),
|
||||
DEFINE_MODULE_SYMBOL(bluetooth_serial_read),
|
||||
DEFINE_MODULE_SYMBOL(bluetooth_serial_is_connected),
|
||||
DEFINE_MODULE_SYMBOL(BLUETOOTH_SERIAL_TYPE),
|
||||
// drivers/bluetooth_midi
|
||||
DEFINE_MODULE_SYMBOL(bluetooth_midi_get_device),
|
||||
DEFINE_MODULE_SYMBOL(bluetooth_midi_start),
|
||||
DEFINE_MODULE_SYMBOL(bluetooth_midi_stop),
|
||||
DEFINE_MODULE_SYMBOL(bluetooth_midi_send),
|
||||
DEFINE_MODULE_SYMBOL(bluetooth_midi_is_connected),
|
||||
DEFINE_MODULE_SYMBOL(BLUETOOTH_MIDI_TYPE),
|
||||
// drivers/bluetooth_hid_device
|
||||
DEFINE_MODULE_SYMBOL(bluetooth_hid_device_get_device),
|
||||
DEFINE_MODULE_SYMBOL(bluetooth_hid_device_start),
|
||||
DEFINE_MODULE_SYMBOL(bluetooth_hid_device_stop),
|
||||
DEFINE_MODULE_SYMBOL(bluetooth_hid_device_send_key),
|
||||
DEFINE_MODULE_SYMBOL(bluetooth_hid_device_send_keyboard),
|
||||
DEFINE_MODULE_SYMBOL(bluetooth_hid_device_send_consumer),
|
||||
DEFINE_MODULE_SYMBOL(bluetooth_hid_device_send_mouse),
|
||||
DEFINE_MODULE_SYMBOL(bluetooth_hid_device_send_gamepad),
|
||||
DEFINE_MODULE_SYMBOL(bluetooth_hid_device_is_connected),
|
||||
DEFINE_MODULE_SYMBOL(BLUETOOTH_HID_DEVICE_TYPE),
|
||||
// concurrent/dispatcher
|
||||
DEFINE_MODULE_SYMBOL(dispatcher_alloc),
|
||||
DEFINE_MODULE_SYMBOL(dispatcher_free),
|
||||
|
||||
34
device.py
34
device.py
@ -307,6 +307,39 @@ def write_usb_variables(output_file, device_properties: ConfigParser):
|
||||
output_file.write("CONFIG_TINYUSB_MSC_ENABLED=y\n")
|
||||
output_file.write("CONFIG_TINYUSB_MSC_MOUNT_PATH=\"/sdcard\"\n")
|
||||
|
||||
def write_bluetooth_variables(output_file, device_properties: ConfigParser):
|
||||
idf_target = get_property_or_exit(device_properties, "hardware", "target").lower()
|
||||
has_bluetooth = get_boolean_property_or_false(device_properties, "hardware", "bluetooth")
|
||||
if has_bluetooth:
|
||||
output_file.write("# Bluetooth (NimBLE)\n")
|
||||
output_file.write("CONFIG_BT_ENABLED=y\n")
|
||||
output_file.write("CONFIG_BT_NIMBLE_ENABLED=y\n")
|
||||
if idf_target == "esp32p4":
|
||||
output_file.write("CONFIG_BT_NIMBLE_TRANSPORT_UART=n\n")
|
||||
output_file.write("CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y\n")
|
||||
# Move NimBLE host buffers to SPIRAM when available, regardless of target.
|
||||
# The default (INTERNAL) mode causes heap fragmentation after a disable+deinit
|
||||
# cycle, preventing a subsequent nimble_port_init() from allocating its buffers
|
||||
# ("hci inits failed" / rc=-1). EXTERNAL mode uses SPIRAM, which is much larger
|
||||
# and does not suffer from the same fragmentation — enabling reliable re-init.
|
||||
# Also frees significant internal RAM on memory-constrained targets (e.g. S3).
|
||||
# Dependency: CONFIG_SPIRAM_USE_CAPS_ALLOC || CONFIG_SPIRAM_USE_MALLOC (set by write_spiram_variables).
|
||||
has_spiram = get_boolean_property_or_false(device_properties, "hardware", "spiRam")
|
||||
if has_spiram:
|
||||
output_file.write("CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_EXTERNAL=y\n")
|
||||
# Expand NimBLE's GAP device name buffer to match BLE_DEVICE_NAME_MAX.
|
||||
# The default (31) is too short for some device names and leaves no headroom.
|
||||
output_file.write("CONFIG_BT_NIMBLE_GAP_DEVICE_NAME_MAX_LEN=64\n")
|
||||
# Increase NimBLE host task stack from the 4096-byte default.
|
||||
# GAP/GATT event processing + C++ frames push the default over the limit,
|
||||
# causing stack-protection faults on events like BLE_GAP_EVENT_SUBSCRIBE.
|
||||
output_file.write("CONFIG_BT_NIMBLE_HOST_TASK_STACK_SIZE=8192\n")
|
||||
# Persist bond/CCCD data to NVS so that bonds survive BLE disable+re-enable
|
||||
# cycles (nimble_port_deinit clears RAM-only state). Without this, bonded peers
|
||||
# (e.g. Windows HID host) lose their LTK match on re-enable and enter a
|
||||
# rapid connect/disconnect/re-pair loop.
|
||||
output_file.write("CONFIG_BT_NIMBLE_NVS_PERSIST=y\n")
|
||||
|
||||
def write_custom_sdkconfig(output_file, device_properties: ConfigParser):
|
||||
if "sdkconfig" in device_properties.sections():
|
||||
output_file.write("# Custom\n")
|
||||
@ -325,6 +358,7 @@ def write_properties(output_file, device_properties: ConfigParser, device_id: st
|
||||
write_spiram_variables(output_file, device_properties)
|
||||
write_performance_improvements(output_file, device_properties)
|
||||
write_usb_variables(output_file, device_properties)
|
||||
write_bluetooth_variables(output_file, device_properties)
|
||||
write_custom_sdkconfig(output_file, device_properties)
|
||||
write_lvgl_variables(output_file, device_properties)
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user