2026-07-14 08:34:29 +02:00

191 lines
7.9 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>
// ---- 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;
// 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)
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