Ken Van Hoeylandt 7ea8c346b0
Various improvements (#673)
Graphics & rendering

- Implement `binfont-module` to generate bitmap fonts on the device itself
- Remove lodepng support from LVGL library and create `Modules/lodepng-module` to make the feature re-usable in non-LVGL apps too. Add binding functions for LVGL support.
- Implement large icons for boot screen (Tactility and USB logos)
- Remove unused asset files in `Data/`

Boot

- Split up `Tactility.cpp` and its `run()` function into various `Init*.cpp` files.
- Re-implement `Boot.cpp` app as non-LVGL app
- Boot app now has a `BootSequence.cpp` for the init code.
- LVGL is now started when the Boot app finishes
- Move more init logic into Boot app

BLE

- Don't allocate scan result buffers unless BLE is on
- Allocate scan results in external memory if possible

UX & UI

- USB Settings app: remove border from the button container
2026-10-04 18:09:21 +02:00

192 lines
8.4 KiB
C++

#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>
constexpr size_t BLE_SCAN_RESULTS_MAX = 64;
// ---- 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>
struct BleCtx {
// Mutexes
SemaphoreHandle_t radio_mutex; // guards radio state transitions
// Guards GAP/GATT advertising & profile-switch calls (on_sync()'s advertising decision vs.
// hid/spp/midi's _start()/_stop()). Deliberately separate from radio_mutex: radio_mutex can
// be held for the duration of nimble_port_stop(), which blocks waiting for the NimBLE host
// task to exit its run loop - taking that same mutex from code that can run ON the host task
// (on_sync()) would deadlock disable against it. Nothing held under gap_mutex ever waits on
// host-task progress, so it's safe to take from the host task itself.
SemaphoreHandle_t gap_mutex;
// 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 subscriptions (guarded by subscriptionsMutex)
Mutex subscriptionsMutex;
BtEventSubscription* subscriptions;
// 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;
// Given by host_task() right after nimble_port_run() returns (i.e. the host
// task has fully drained g_eventq_dflt and will process no further NimBLE
// events). dispatch_disable() must wait on this before nimble_port_deinit(),
// otherwise a reset event still executing on the host task can dereference
// timer/callout state that deinit just freed. See on_reset()/dispatch_disable().
SemaphoreHandle_t host_task_done_sem;
// Posted to NimBLE's own default event queue (nimble_port_get_dflt_eventq())
// from on_reset() instead of kicking disable_timer directly. ble_hs_reset()
// calls reset_cb (on_reset) synchronously, then unconditionally falls through
// to ble_hs_sync() -> ble_hs_timer_sched(), which touches the global static
// ble_hs_timer with no locking. Firing disable_timer (and thus
// nimble_port_stop() -> ble_hs_stop() -> ble_npl_callout_deinit(&ble_hs_timer))
// from on_reset() races that in-flight ble_hs_sync() call on the SAME host
// task from a DIFFERENT task (esp_timer task), nulling out ble_hs_timer's
// callout mid-use -> null deref crash in ble_npl_callout_is_active(). Posting
// this event instead defers the giveup until the host task's event loop is
// idle again (FIFO-after the in-flight reset), so by the time disable_timer
// fires, ble_hs_sync() for this reset has already returned and there is no
// concurrent user of ble_hs_timer left on the host task.
struct ble_npl_event giveup_event;
// 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), allocated only while the radio is on
SemaphoreHandle_t scan_mutex;
BtPeerRecord* scan_results;
ble_addr_t* scan_addrs;
size_t scan_count;
// Device reference (passed to subscribers via BtEvent delivery)
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