Files
tactility/Tactility/Source/app/btmanage/BtManage.cpp
T
Ken Van Hoeylandt d3439f6f45 Fixes & memory optimizations (#540)
- Disable BlueTooth driver by default, to ensure it doesn't allocate memory
- Update BlueTooth settings app behaviour to make sure it starts/stops the devices
- Disable SPI RAM usage for ST7789 as it broke App Hub
- Reduce T-Display and T-Deck display buffers from 1/3rd to 1/10th of resolution
- Enabled dynamic buffers for MbedTLS to optimize memory
- TLS 1.3 did not work with dynamic buffers: downgrade to 1.2 for now
- WiFi is now enabled from the Boot app callback to the WiFi service, instead of the WiFi service start() function. This avoids a lockup when WiFi is started by the service while the Boot app is updating the WiFi settings.
2026-07-01 21:11:46 +02:00

197 lines
5.8 KiB
C++

#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 requestOn) {
#if defined(CONFIG_BT_NIMBLE_ENABLED)
Device* dev = device_find_first_by_type(&BLUETOOTH_TYPE);
if (!dev) return;
bool radio_on = bluetooth::isRadioOnOrPending(dev);
if (requestOn && !radio_on) {
bluetooth::start(dev);
} else if (!requestOn && radio_on) {
bluetooth::stop(dev);
}
#endif
}
static void onScanToggled(bool enabled) {
Device* dev = device_find_first_active_by_type(&BLUETOOTH_TYPE);
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();
Device* dev = device_find_first_active_by_type(&BLUETOOTH_TYPE);
if (dev && !bluetooth_is_scanning(dev)) {
bluetooth_scan_start(dev);
}
}
break;
default:
break;
}
requestViewUpdate();
}
static void onKernelBtEvent(Device* /*device*/, void* context, 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());
Device* dev = device_find_first_active_by_type(&BLUETOOTH_TYPE);
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