Merge remote-tracking branch 'origin/main' into main
This commit is contained in:
@@ -40,18 +40,16 @@ static std::vector<CachedAddr> scan_addr_cache; // parallel to scan_results_cach
|
||||
|
||||
// ---- 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;
|
||||
}
|
||||
Device* findFirstRegisteredDevice() {
|
||||
Device* found = nullptr;
|
||||
device_for_each_of_type(&BLUETOOTH_TYPE, &found, [](Device* dev, void* ctx) -> bool {
|
||||
*static_cast<Device**>(ctx) = dev;
|
||||
return true;
|
||||
});
|
||||
return found;
|
||||
}
|
||||
|
||||
|
||||
// ---- Scan cache helpers ----
|
||||
|
||||
void cacheScanAddr(const uint8_t addr[6], uint8_t addr_type) {
|
||||
@@ -110,7 +108,7 @@ static void cachePeerRecord(const BtPeerRecord& krecord) {
|
||||
// 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) {
|
||||
static void bt_event_bridge(Device*, void* /*context*/, BtEvent event) {
|
||||
switch (event.type) {
|
||||
case BT_EVENT_RADIO_STATE_CHANGED:
|
||||
switch (event.radio_state) {
|
||||
@@ -126,24 +124,24 @@ static void bt_event_bridge(struct Device* /*device*/, void* /*context*/, struct
|
||||
}
|
||||
if (has_hid_host_auto) {
|
||||
LOGGER.info("HID host auto-connect peer found — starting scan");
|
||||
if (struct Device* dev = findFirstDevice()) {
|
||||
if (Device* dev = device_find_first_active_by_type(&BLUETOOTH_TYPE)) {
|
||||
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()) {
|
||||
if (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()) {
|
||||
if (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()) {
|
||||
if (Device* dev = bluetooth_midi_get_device()) {
|
||||
bluetooth_midi_start(dev);
|
||||
}
|
||||
}
|
||||
@@ -233,7 +231,7 @@ static void bt_event_bridge(struct Device* /*device*/, void* /*context*/, struct
|
||||
}
|
||||
}
|
||||
if (has_auto) {
|
||||
if (struct Device* dev = findFirstDevice()) {
|
||||
if (Device* dev = device_find_first_active_by_type(&BLUETOOTH_TYPE)) {
|
||||
if (!bluetooth_is_scanning(dev)) {
|
||||
bluetooth_scan_start(dev);
|
||||
}
|
||||
@@ -251,19 +249,80 @@ static void bt_event_bridge(struct Device* /*device*/, void* /*context*/, struct
|
||||
// ---- systemStart ----
|
||||
|
||||
void systemStart() {
|
||||
struct Device* dev = findFirstDevice();
|
||||
Device* dev = findFirstRegisteredDevice();
|
||||
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);
|
||||
start(dev);
|
||||
}
|
||||
}
|
||||
|
||||
bool isRadioOnOrPending(Device* dev) {
|
||||
if (!device_is_ready(dev)) return false;
|
||||
BtRadioState state;
|
||||
if (bluetooth_get_radio_state(dev, &state) != ERROR_NONE) return false;
|
||||
return state == BT_RADIO_STATE_ON || state == BT_RADIO_STATE_ON_PENDING;
|
||||
}
|
||||
|
||||
bool start(Device* dev) {
|
||||
LOGGER.info("Auto-enabling BLE on boot");
|
||||
if (!device_is_ready(dev)) {
|
||||
LOGGER.info("Starting BLE device");
|
||||
if (device_start(dev) != ERROR_NONE) {
|
||||
LOGGER.error("Failed to start BLE device");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Fix bug where repeatedly calling start would add this callback multiple times
|
||||
if (bluetooth_add_event_callback(dev, nullptr, bt_event_bridge) != ERROR_NONE) {
|
||||
LOGGER.error("Failed to set BLE callback");
|
||||
}
|
||||
|
||||
LOGGER.info("Enabling BT radio");
|
||||
if (bluetooth_set_radio_enabled(dev, true) != ERROR_NONE) {
|
||||
LOGGER.error("Failed to enable BLE radio");
|
||||
// Add bridge again
|
||||
bluetooth_remove_event_callback(dev, bt_event_bridge);
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGGER.info("BT enabled");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool stop(Device* dev) {
|
||||
BtRadioState state;
|
||||
if (bluetooth_get_radio_state(dev, &state) != ERROR_NONE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (state == BT_RADIO_STATE_OFF || state == BT_RADIO_STATE_OFF_PENDING) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (bluetooth_remove_event_callback(dev, bt_event_bridge) != ERROR_NONE) {
|
||||
LOGGER.error("Failed to remove BLE callback");
|
||||
}
|
||||
|
||||
if (bluetooth_set_radio_enabled(dev, false) != ERROR_NONE) {
|
||||
LOGGER.error("Failed to disable BT radio");
|
||||
// Re-register bridge
|
||||
bluetooth_add_event_callback(dev, nullptr, bt_event_bridge);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (device_stop(dev) != ERROR_NONE) {
|
||||
LOGGER.error("Failed to stop BT device");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- Public API ----
|
||||
|
||||
const char* radioStateToString(RadioState state) {
|
||||
@@ -278,7 +337,7 @@ const char* radioStateToString(RadioState state) {
|
||||
}
|
||||
|
||||
RadioState getRadioState() {
|
||||
struct Device* dev = findFirstDevice();
|
||||
Device* dev = device_find_first_active_by_type(&BLUETOOTH_TYPE);
|
||||
if (dev == nullptr) return RadioState::Off;
|
||||
BtRadioState state = BT_RADIO_STATE_OFF;
|
||||
bluetooth_get_radio_state(dev, &state);
|
||||
@@ -346,7 +405,7 @@ void pair(const std::array<uint8_t, 6>& /*addr*/) {
|
||||
}
|
||||
|
||||
void unpair(const std::array<uint8_t, 6>& addr) {
|
||||
struct Device* dev = findFirstDevice();
|
||||
Device* dev = device_find_first_active_by_type(&BLUETOOTH_TYPE);
|
||||
if (dev != nullptr) {
|
||||
bluetooth_unpair(dev, addr.data());
|
||||
}
|
||||
@@ -358,16 +417,16 @@ void connect(const std::array<uint8_t, 6>& addr, int 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()) {
|
||||
if (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()) {
|
||||
if (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()) {
|
||||
if (Device* dev = bluetooth_midi_get_device()) {
|
||||
bluetooth_midi_start(dev);
|
||||
settings::setMidiAutoStart(true);
|
||||
}
|
||||
@@ -379,11 +438,11 @@ void disconnect(const std::array<uint8_t, 6>& addr, int profileId) {
|
||||
if (profileId == BT_PROFILE_HID_HOST) {
|
||||
hidHostDisconnect();
|
||||
} else if (profileId == BT_PROFILE_HID_DEVICE) {
|
||||
if (struct Device* dev = bluetooth_hid_device_get_device()) {
|
||||
if (Device* dev = bluetooth_hid_device_get_device()) {
|
||||
bluetooth_hid_device_stop(dev);
|
||||
}
|
||||
} else {
|
||||
struct Device* dev = findFirstDevice();
|
||||
Device* dev = device_find_first_active_by_type(&BLUETOOTH_TYPE);
|
||||
if (dev == nullptr) return;
|
||||
bluetooth_disconnect(dev, addr.data(), (BtProfileId)profileId);
|
||||
}
|
||||
|
||||
@@ -576,8 +576,8 @@ static void hidHostSubscribeNext(HidHostCtx& ctx) {
|
||||
}
|
||||
device.name = name;
|
||||
settings::save(device);
|
||||
if (struct Device* dev = findFirstDevice()) {
|
||||
struct BtEvent e = {};
|
||||
if (Device* dev = device_find_first_active_by_type(&BLUETOOTH_TYPE)) {
|
||||
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;
|
||||
@@ -738,7 +738,7 @@ static int hidHostGapCb(struct ble_gap_event* event, void* /*arg*/) {
|
||||
} else {
|
||||
LOGGER.warn("Connect failed status={}", event->connect.status);
|
||||
hid_host_ctx.reset();
|
||||
if (struct Device* dev = findFirstDevice()) {
|
||||
if (Device* dev = device_find_first_active_by_type(&BLUETOOTH_TYPE)) {
|
||||
bluetooth_set_hid_host_active(dev, false);
|
||||
struct BtEvent e = {};
|
||||
e.type = BT_EVENT_PROFILE_STATE_CHANGED;
|
||||
@@ -763,7 +763,7 @@ static int hidHostGapCb(struct ble_gap_event* event, void* /*arg*/) {
|
||||
hid_host_mouse_btn.store(false);
|
||||
hid_host_mouse_active.store(false);
|
||||
|
||||
if (struct Device* dev = findFirstDevice()) {
|
||||
if (Device* dev = device_find_first_active_by_type(&BLUETOOTH_TYPE)) {
|
||||
bluetooth_set_hid_host_active(dev, false);
|
||||
struct BtEvent e = {};
|
||||
e.type = BT_EVENT_PROFILE_STATE_CHANGED;
|
||||
@@ -865,7 +865,7 @@ void hidHostConnect(const std::array<uint8_t, 6>& addr) {
|
||||
}
|
||||
|
||||
// Notify driver that a HID host central connection is starting.
|
||||
if (struct Device* dev = findFirstDevice()) bluetooth_set_hid_host_active(dev, true);
|
||||
if (Device* dev = device_find_first_active_by_type(&BLUETOOTH_TYPE)) bluetooth_set_hid_host_active(dev, true);
|
||||
|
||||
// Look up the addr_type from the cached scan results.
|
||||
ble_addr_t ble_addr = {};
|
||||
@@ -885,10 +885,10 @@ void hidHostConnect(const std::array<uint8_t, 6>& addr) {
|
||||
if (rc != 0) {
|
||||
LOGGER.warn("ble_gap_connect failed rc={}", rc);
|
||||
hid_host_ctx.reset();
|
||||
if (struct Device* dev = findFirstDevice()) {
|
||||
if (Device* dev = device_find_first_active_by_type(&BLUETOOTH_TYPE)) {
|
||||
bluetooth_set_hid_host_active(dev, false);
|
||||
// Fire IDLE so bt_event_bridge can start a new scan and retry.
|
||||
struct BtEvent e = {};
|
||||
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;
|
||||
@@ -939,7 +939,7 @@ void autoConnectHidHost() {
|
||||
auto peers = settings::loadAll();
|
||||
for (const auto& peer : peers) {
|
||||
if (peer.autoConnect && peer.profileId == BT_PROFILE_HID_HOST) {
|
||||
if (struct Device* dev = findFirstDevice()) {
|
||||
if (Device* dev = device_find_first_active_by_type(&BLUETOOTH_TYPE)) {
|
||||
if (!bluetooth_is_scanning(dev)) {
|
||||
LOGGER.info("Auto-connect HID host: device not in scan, retrying scan");
|
||||
bluetooth_scan_start(dev);
|
||||
|
||||
@@ -60,8 +60,11 @@ bool hasFileForDevice(const std::string& addr_hex) {
|
||||
}
|
||||
|
||||
bool load(const std::string& addr_hex, PairedDevice& device) {
|
||||
auto file_path = getFilePath(addr_hex);
|
||||
if (!file::isFile(file_path)) return false;
|
||||
|
||||
std::map<std::string, std::string> map;
|
||||
if (!file::loadPropertiesFile(getFilePath(addr_hex), map)) return false;
|
||||
if (!file::loadPropertiesFile(file_path, map)) return false;
|
||||
if (!map.contains(KEY_ADDR)) return false;
|
||||
if (!hexToAddr(map[KEY_ADDR], device.addr)) return false;
|
||||
|
||||
@@ -83,7 +86,12 @@ bool save(const PairedDevice& device) {
|
||||
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);
|
||||
auto file_path = getFilePath(addr_hex);
|
||||
if (!file::findOrCreateParentDirectory(file_path, 0755)) {
|
||||
LOGGER.error("Failed to create parent dir for {}", file_path);
|
||||
return false;
|
||||
}
|
||||
return file::savePropertiesFile(file_path, map);
|
||||
}
|
||||
|
||||
bool remove(const std::string& addr_hex) {
|
||||
|
||||
@@ -4,13 +4,16 @@
|
||||
#include <Tactility/file/PropertiesFile.h>
|
||||
#include <Tactility/Logger.h>
|
||||
#include <Tactility/Mutex.h>
|
||||
#include <Tactility/Paths.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";
|
||||
static std::string getSettingsPath() {
|
||||
return getUserDataPath() + "/settings/bluetooth.settings";
|
||||
}
|
||||
|
||||
constexpr auto* KEY_ENABLE_ON_BOOT = "enableOnBoot";
|
||||
constexpr auto* KEY_SPP_AUTO_START = "sppAutoStart";
|
||||
constexpr auto* KEY_MIDI_AUTO_START = "midiAutoStart";
|
||||
@@ -26,8 +29,13 @@ static BluetoothSettings cached;
|
||||
static bool cached_valid = false;
|
||||
|
||||
static bool load(BluetoothSettings& out) {
|
||||
auto settings_path = getSettingsPath();
|
||||
if (!file::isFile(settings_path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> map;
|
||||
if (!file::loadPropertiesFile(SETTINGS_PATH, map)) {
|
||||
if (!file::loadPropertiesFile(settings_path, map)) {
|
||||
return false;
|
||||
}
|
||||
auto it = map.find(KEY_ENABLE_ON_BOOT);
|
||||
@@ -44,11 +52,18 @@ static bool load(BluetoothSettings& out) {
|
||||
|
||||
static bool save(const BluetoothSettings& s) {
|
||||
std::map<std::string, std::string> map;
|
||||
file::loadPropertiesFile(SETTINGS_PATH, map); // ignore failure — may not exist yet
|
||||
if (file::isFile(getSettingsPath())) {
|
||||
file::loadPropertiesFile(getSettingsPath(), map);
|
||||
}
|
||||
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);
|
||||
auto settings_path = getSettingsPath();
|
||||
if (!file::findOrCreateParentDirectory(settings_path, 0755)) {
|
||||
LOGGER.error("Failed to create parent dir for {}", settings_path);
|
||||
return false;
|
||||
}
|
||||
return file::savePropertiesFile(settings_path, map);
|
||||
}
|
||||
|
||||
static BluetoothSettings getCachedOrLoad() {
|
||||
|
||||
Reference in New Issue
Block a user