80bd1c9f20
- Auto-set enableOnBoot=true when BT is enabled or device is paired,
so board restarts with BT on and can reconnect
- Make RADIO_STATE_ON handler non-exclusive: scan for HID Host,
start HID Device, SPP and MIDI all independently (was else-if)
- Improve HID Host auto-connect for RPA: direct addr match, name-match
fallback, and direct connect to stored identity using resolving list
- Persist addrType in PairedDevice file for more reliable reconnection
- Add verbose logging for loadAll/save to debug missing files
Fixes issue where board restart did not reconnect to previously
connected BT devices. Also preserves f94cc160 fullscreen flag feature.
160 lines
5.5 KiB
C++
160 lines
5.5 KiB
C++
#include <Tactility/bluetooth/BluetoothPairedDevice.h>
|
|
|
|
#include "Tactility/Paths.h"
|
|
|
|
#include <Tactility/file/File.h>
|
|
#include <Tactility/file/PropertiesFile.h>
|
|
#include <tactility/log.h>
|
|
|
|
#include <dirent.h>
|
|
#include <format>
|
|
#include <iomanip>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <cstdio>
|
|
|
|
namespace tt::bluetooth::settings {
|
|
|
|
constexpr auto* TAG = "BluetoothPairedDevice";
|
|
|
|
// Use the same directory as the old service for backward compatibility.
|
|
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";
|
|
constexpr auto* KEY_ADDR_TYPE = "addrType";
|
|
|
|
static std::string getSettingsFilePath() {
|
|
return getUserDataPath() + "/service/bluetooth";
|
|
}
|
|
|
|
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) {
|
|
LOG_E(TAG, "hexToAddr() length mismatch: expected 12, got %d", (int)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) {
|
|
LOG_E(TAG, "hexToAddr() invalid hex at byte %d: '%c%c'", i, buf[0], buf[1]);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static std::string getFilePath(const std::string& addr_hex) {
|
|
return std::format(DEVICE_SETTINGS_FORMAT, getSettingsFilePath(), addr_hex);
|
|
}
|
|
|
|
bool hasFileForDevice(const std::string& addr_hex) {
|
|
return file::isFile(getFilePath(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(file_path, 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)) {
|
|
char* endPtr = nullptr;
|
|
long val = std::strtol(map[KEY_PROFILE_ID].c_str(), &endPtr, 10);
|
|
if (endPtr != map[KEY_PROFILE_ID].c_str()) {
|
|
device.profileId = static_cast<int>(val);
|
|
}
|
|
}
|
|
|
|
if (map.contains(KEY_ADDR_TYPE)) {
|
|
char* endPtr = nullptr;
|
|
long val = std::strtol(map[KEY_ADDR_TYPE].c_str(), &endPtr, 10);
|
|
if (endPtr != map[KEY_ADDR_TYPE].c_str() && val >= 0 && val <= 255) {
|
|
device.addrType = static_cast<uint8_t>(val);
|
|
} else {
|
|
device.addrType = 0;
|
|
}
|
|
} else {
|
|
device.addrType = 0; // backward compat: assume PUBLIC
|
|
}
|
|
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);
|
|
map[KEY_ADDR_TYPE] = std::to_string(device.addrType);
|
|
auto file_path = getFilePath(addr_hex);
|
|
LOG_I(TAG, "Saving device file %s profile=%d auto=%d type=%d",
|
|
file_path.c_str(), device.profileId, (int)device.autoConnect, (int)device.addrType);
|
|
if (!file::findOrCreateParentDirectory(file_path, 0755)) {
|
|
LOG_E(TAG, "Failed to create parent dir for %s", file_path.c_str());
|
|
return false;
|
|
}
|
|
bool result = file::savePropertiesFile(file_path, map);
|
|
LOG_I(TAG, "SavePropertiesFile result for %s: %d", file_path.c_str(), (int)result);
|
|
return result;
|
|
}
|
|
|
|
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;
|
|
if (!file::isDirectory(getSettingsFilePath())) {
|
|
LOG_I(TAG, "loadAll: directory %s does not exist", getSettingsFilePath().c_str());
|
|
return {};
|
|
}
|
|
file::scandir(getSettingsFilePath(), 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);
|
|
|
|
LOG_I(TAG, "loadAll: found %d entries in %s", (int)entries.size(), getSettingsFilePath().c_str());
|
|
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));
|
|
} else {
|
|
LOG_W(TAG, "loadAll: failed to load %s", filename.c_str());
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
} // namespace tt::bluetooth::settings
|