janky notifications

This commit is contained in:
2025-10-21 00:42:35 +02:00
parent fc633d7c90
commit 12e8a0e098
19 changed files with 1012 additions and 2835 deletions

View File

@@ -1,5 +1,9 @@
#pragma once
namespace cardboy::sdk {
class INotificationCenter;
} // namespace cardboy::sdk
namespace cardboy::backend::esp {
/**
@@ -16,5 +20,10 @@ void ensure_time_sync_service_started();
*/
void shutdown_time_sync_service();
} // namespace cardboy::backend::esp
/**
* Provide a notification sink that receives mirrored notifications from iOS.
* Passing nullptr disables mirroring.
*/
void set_notification_center(cardboy::sdk::INotificationCenter* center);
} // namespace cardboy::backend::esp

View File

@@ -61,6 +61,7 @@ private:
class HighResClockService;
class FilesystemService;
class LoopHooksService;
class NotificationService;
std::unique_ptr<BuzzerService> buzzerService;
std::unique_ptr<BatteryService> batteryService;
@@ -70,6 +71,7 @@ private:
std::unique_ptr<FilesystemService> filesystemService;
std::unique_ptr<EventBus> eventBus;
std::unique_ptr<LoopHooksService> loopHooksService;
std::unique_ptr<NotificationService> notificationService;
cardboy::sdk::Services services{};
};

View File

@@ -24,8 +24,11 @@
#include <algorithm>
#include <cstdint>
#include <ctime>
#include <mutex>
#include <string>
#include <string_view>
#include <vector>
namespace cardboy::backend::esp {
@@ -37,6 +40,7 @@ void ensureNvsInit() {
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
printf("Erasing flash!\n");
ESP_ERROR_CHECK(nvs_flash_erase());
err = nvs_flash_init();
}
@@ -127,6 +131,80 @@ public:
void onLoopIteration() override { vTaskDelay(1); }
};
class EspRuntime::NotificationService final : public cardboy::sdk::INotificationCenter {
public:
void pushNotification(Notification notification) override {
if (notification.timestamp == 0) {
notification.timestamp = static_cast<std::uint64_t>(std::time(nullptr));
}
capLengths(notification);
std::lock_guard<std::mutex> lock(mutex);
notification.id = nextId++;
notification.unread = true;
entries.push_back(std::move(notification));
if (entries.size() > kMaxEntries)
entries.erase(entries.begin());
++revisionCounter;
}
[[nodiscard]] std::uint32_t revision() const override {
std::lock_guard<std::mutex> lock(mutex);
return revisionCounter;
}
[[nodiscard]] std::vector<Notification> recent(std::size_t limit) const override {
std::lock_guard<std::mutex> lock(mutex);
std::vector<Notification> out;
const std::size_t count = std::min<std::size_t>(limit, entries.size());
out.reserve(count);
for (std::size_t i = 0; i < count; ++i) {
out.push_back(entries[entries.size() - 1 - i]);
}
return out;
}
void markAllRead() override {
std::lock_guard<std::mutex> lock(mutex);
bool changed = false;
for (auto& entry: entries) {
if (entry.unread) {
entry.unread = false;
changed = true;
}
}
if (changed)
++revisionCounter;
}
void clear() override {
std::lock_guard<std::mutex> lock(mutex);
if (entries.empty())
return;
entries.clear();
++revisionCounter;
}
private:
static constexpr std::size_t kMaxEntries = 8;
static constexpr std::size_t kMaxTitleBytes = 96;
static constexpr std::size_t kMaxBodyBytes = 256;
static void capLengths(Notification& notification) {
if (notification.title.size() > kMaxTitleBytes)
notification.title.resize(kMaxTitleBytes);
if (notification.body.size() > kMaxBodyBytes)
notification.body.resize(kMaxBodyBytes);
}
mutable std::mutex mutex;
std::vector<Notification> entries;
std::uint64_t nextId = 1;
std::uint32_t revisionCounter = 0;
};
EspRuntime::EspRuntime() : framebuffer(), input(), clock() {
initializeHardware();
@@ -138,20 +216,26 @@ EspRuntime::EspRuntime() : framebuffer(), input(), clock() {
filesystemService = std::make_unique<FilesystemService>();
eventBus = std::make_unique<EventBus>();
loopHooksService = std::make_unique<LoopHooksService>();
notificationService = std::make_unique<NotificationService>();
services.buzzer = buzzerService.get();
services.battery = batteryService.get();
services.storage = storageService.get();
services.random = randomService.get();
services.highResClock = highResClockService.get();
services.filesystem = filesystemService.get();
services.eventBus = eventBus.get();
services.loopHooks = loopHooksService.get();
services.buzzer = buzzerService.get();
services.battery = batteryService.get();
services.storage = storageService.get();
services.random = randomService.get();
services.highResClock = highResClockService.get();
services.filesystem = filesystemService.get();
services.eventBus = eventBus.get();
services.loopHooks = loopHooksService.get();
services.notifications = notificationService.get();
Buttons::get().setEventBus(eventBus.get());
set_notification_center(notificationService.get());
}
EspRuntime::~EspRuntime() { shutdown_time_sync_service(); }
EspRuntime::~EspRuntime() {
set_notification_center(nullptr);
shutdown_time_sync_service();
}
cardboy::sdk::Services& EspRuntime::serviceRegistry() { return services; }

View File

@@ -15,6 +15,7 @@
#include "host/ble_gatt.h"
#include "host/ble_hs.h"
#include "host/ble_hs_mbuf.h"
#include "host/ble_store.h"
#include "host/util/util.h"
#include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h"
@@ -29,6 +30,7 @@
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <dirent.h>
#include <esp_bt.h>
#include <esp_err.h>
@@ -37,6 +39,10 @@
#include <sys/stat.h>
#include <vector>
#include "cardboy/sdk/services.hpp"
extern "C" void ble_store_config_init(void);
namespace cardboy::backend::esp {
namespace {
@@ -44,9 +50,9 @@ namespace {
constexpr char kLogTag[] = "TimeSyncBLE";
constexpr char kDeviceName[] = "Cardboy";
constexpr std::uint16_t kPreferredConnIntervalMin = BLE_GAP_CONN_ITVL_MS(80); // 80 ms
constexpr std::uint16_t kPreferredConnIntervalMax = BLE_GAP_CONN_ITVL_MS(150); // 150 ms
constexpr std::uint16_t kPreferredConnLatency = 2;
constexpr std::uint16_t kPreferredConnIntervalMin = BLE_GAP_CONN_ITVL_MS(200); // 80 ms
constexpr std::uint16_t kPreferredConnIntervalMax = BLE_GAP_CONN_ITVL_MS(300); // 150 ms
constexpr std::uint16_t kPreferredConnLatency = 3;
constexpr std::uint16_t kPreferredSupervisionTimeout = BLE_GAP_SUPERVISION_TIMEOUT_MS(5000); // 5 s
constexpr float connIntervalUnitsToMs(std::uint16_t units) { return static_cast<float>(units) * 1.25f; }
@@ -75,10 +81,12 @@ struct [[gnu::packed]] TimeSyncPayload {
static_assert(sizeof(TimeSyncPayload) == 12, "Unexpected payload size");
static bool g_started = false;
static uint8_t g_ownAddrType = BLE_OWN_ADDR_PUBLIC;
static TaskHandle_t g_hostTaskHandle = nullptr;
static uint16_t g_activeConnHandle = BLE_HS_CONN_HANDLE_NONE;
static bool g_started = false;
static uint8_t g_ownAddrType = BLE_OWN_ADDR_PUBLIC;
static TaskHandle_t g_hostTaskHandle = nullptr;
static uint16_t g_activeConnHandle = BLE_HS_CONN_HANDLE_NONE;
static cardboy::sdk::INotificationCenter* g_notificationCenter = nullptr;
static bool g_securityRequested = false;
struct ResponseMessage {
uint8_t opcode;
@@ -112,6 +120,86 @@ struct FileDownloadContext {
static FileUploadContext g_uploadCtx{};
static FileDownloadContext g_downloadCtx{};
static const ble_uuid128_t kAncsServiceUuid = BLE_UUID128_INIT(0xD0, 0x00, 0x2D, 0x12, 0x1E, 0x4B, 0x0F, 0xA4, 0x99,
0x4E, 0xCE, 0xB5, 0x31, 0xF4, 0x05, 0x79);
static const ble_uuid128_t kAncsNotificationSourceUuid = BLE_UUID128_INIT(
0xBD, 0x1D, 0xA2, 0x99, 0xE6, 0x25, 0x58, 0x8C, 0xD9, 0x42, 0x01, 0x63, 0x0D, 0x12, 0xBF, 0x9F);
static const ble_uuid128_t kAncsDataSourceUuid = BLE_UUID128_INIT(0xFB, 0x7B, 0x7C, 0xCE, 0x6A, 0xB3, 0x44, 0xBE, 0xB5,
0x4B, 0xD6, 0x24, 0xE9, 0xC6, 0xEA, 0x22);
static const ble_uuid128_t kAncsControlPointUuid = BLE_UUID128_INIT(0xD9, 0xD9, 0xAA, 0xFD, 0xBD, 0x9B, 0x21, 0x98,
0xA8, 0x49, 0xE1, 0x45, 0xF3, 0xD8, 0xD1, 0x69);
static uint16_t g_ancsServiceEndHandle = 0;
static uint16_t g_ancsNotificationSourceHandle = 0;
static uint16_t g_ancsDataSourceHandle = 0;
static uint16_t g_ancsControlPointHandle = 0;
static uint16_t g_mtuSize = 23;
struct PendingNotification {
uint32_t uid = 0;
uint8_t category = 0;
uint8_t flags = 0;
std::string appIdentifier;
std::string title;
std::string message;
};
static std::vector<PendingNotification> g_pendingNotifications;
static std::vector<uint8_t> g_dataSourceBuffer;
static const ble_uuid16_t kClientConfigUuid = BLE_UUID16_INIT(BLE_GATT_DSC_CLT_CFG_UUID16);
void resetAncsState() {
g_ancsServiceEndHandle = 0;
g_ancsNotificationSourceHandle = 0;
g_ancsDataSourceHandle = 0;
g_ancsControlPointHandle = 0;
g_mtuSize = 23;
g_dataSourceBuffer.clear();
g_pendingNotifications.clear();
}
PendingNotification* findPending(uint32_t uid) {
for (auto& entry: g_pendingNotifications) {
if (entry.uid == uid)
return &entry;
}
return nullptr;
}
PendingNotification& ensurePending(uint32_t uid) {
if (auto* existing = findPending(uid))
return *existing;
g_pendingNotifications.push_back({});
auto& pending = g_pendingNotifications.back();
pending.uid = uid;
return pending;
}
void finalizePending(uint32_t uid) {
if (!g_notificationCenter)
return;
for (auto it = g_pendingNotifications.begin(); it != g_pendingNotifications.end(); ++it) {
if (it->uid != uid)
continue;
cardboy::sdk::INotificationCenter::Notification note{};
note.timestamp = static_cast<std::uint64_t>(time(nullptr));
if (!it->title.empty()) {
note.title = it->title;
} else if (!it->appIdentifier.empty()) {
note.title = it->appIdentifier;
} else {
note.title = "Notification";
}
note.body = it->message;
g_notificationCenter->pushNotification(std::move(note));
ESP_LOGI(kLogTag, "Stored notification uid=%" PRIu32 " title='%s' body='%s'", uid, it->title.c_str(),
it->message.c_str());
g_pendingNotifications.erase(it);
break;
}
}
enum class FileCommandCode : uint8_t {
ListDirectory = 0x01,
UploadBegin = 0x02,
@@ -156,6 +244,15 @@ bool sendFileResponseNow(const ResponseMessage& msg);
void notificationTask(void* param);
bool scheduleDownloadChunk();
void processDownloadChunk();
void handleAncsNotificationSource(uint16_t connHandle, const uint8_t* data, uint16_t length);
bool handleAncsDataSource(const uint8_t* data, uint16_t length);
void requestAncsAttributes(uint16_t connHandle, uint32_t uid);
void applyPreferredConnectionParams(uint16_t connHandle);
int ancsServiceDiscoveredCb(uint16_t connHandle, const ble_gatt_error* error, const ble_gatt_svc* svc, void* arg);
int ancsCharacteristicDiscoveredCb(uint16_t connHandle, const ble_gatt_error* error, const ble_gatt_chr* chr,
void* arg);
int ancsDescriptorDiscoveredCb(uint16_t connHandle, const ble_gatt_error* error, uint16_t chrValHandle,
const ble_gatt_dsc* dsc, void* arg);
static const ble_gatt_chr_def kTimeServiceCharacteristics[] = {
{
@@ -811,6 +908,215 @@ void handleRename(const uint8_t* payload, std::size_t length) {
}
}
void requestAncsAttributes(uint16_t connHandle, uint32_t uid) {
if (!g_notificationCenter || g_ancsControlPointHandle == 0)
return;
static constexpr uint16_t kMaxTitle = 96;
static constexpr uint16_t kMaxMessage = 256;
uint8_t buffer[32];
std::size_t index = 0;
buffer[index++] = 0x00; // CommandIDGetNotificationAttributes
buffer[index++] = static_cast<uint8_t>(uid & 0xFF);
buffer[index++] = static_cast<uint8_t>((uid >> 8) & 0xFF);
buffer[index++] = static_cast<uint8_t>((uid >> 16) & 0xFF);
buffer[index++] = static_cast<uint8_t>((uid >> 24) & 0xFF);
buffer[index++] = 0x00; // App Identifier
buffer[index++] = 0x01; // Title
buffer[index++] = static_cast<uint8_t>(kMaxTitle & 0xFF);
buffer[index++] = static_cast<uint8_t>((kMaxTitle >> 8) & 0xFF);
buffer[index++] = 0x03; // Message
buffer[index++] = static_cast<uint8_t>(kMaxMessage & 0xFF);
buffer[index++] = static_cast<uint8_t>((kMaxMessage >> 8) & 0xFF);
const int rc = ble_gattc_write_flat(connHandle, g_ancsControlPointHandle, buffer, index, nullptr, nullptr);
if (rc != 0) {
ESP_LOGW(kLogTag, "ANCS attribute request failed: rc=%d uid=%" PRIu32, rc, uid);
} else {
ESP_LOGI(kLogTag, "Requested ANCS attributes for uid=%" PRIu32, uid);
}
}
void applyPreferredConnectionParams(uint16_t connHandle) {
ble_gap_upd_params params{
.itvl_min = kPreferredConnIntervalMin,
.itvl_max = kPreferredConnIntervalMax,
.latency = kPreferredConnLatency,
.supervision_timeout = kPreferredSupervisionTimeout,
.min_ce_len = 0,
.max_ce_len = 0,
};
const int rc = ble_gap_update_params(connHandle, &params);
if (rc != 0) {
ESP_LOGW(kLogTag, "ble_gap_update_params failed (rc=%d)", rc);
} else {
ESP_LOGI(kLogTag, "Requested conn params: %.1f-%.1f ms latency %u timeout %.0f ms",
connIntervalUnitsToMs(params.itvl_min), connIntervalUnitsToMs(params.itvl_max), params.latency,
supervisionUnitsToMs(params.supervision_timeout));
}
}
void handleAncsNotificationSource(uint16_t connHandle, const uint8_t* data, uint16_t length) {
if (!g_notificationCenter || !data || length < 8)
return;
const uint8_t eventId = data[0];
const uint8_t eventFlags = data[1];
const uint8_t category = data[2];
const uint32_t uid = static_cast<uint32_t>(data[4]) | (static_cast<uint32_t>(data[5]) << 8) |
(static_cast<uint32_t>(data[6]) << 16) | (static_cast<uint32_t>(data[7]) << 24);
ESP_LOGI(kLogTag, "ANCS notification event=%u flags=0x%02x category=%u uid=%" PRIu32, eventId, eventFlags, category,
uid);
if (eventId == 2) { // Removed
finalizePending(uid);
return;
}
auto& pending = ensurePending(uid);
pending.flags = eventFlags;
pending.category = category;
requestAncsAttributes(connHandle, uid);
}
bool handleAncsDataSource(const uint8_t* data, uint16_t length) {
if (!g_notificationCenter || !data || length == 0)
return false;
g_dataSourceBuffer.insert(g_dataSourceBuffer.end(), data, data + length);
if (g_dataSourceBuffer.size() > 2048) {
ESP_LOGW(kLogTag, "Dropping oversized ANCS data buffer (%u bytes)",
static_cast<unsigned>(g_dataSourceBuffer.size()));
g_dataSourceBuffer.clear();
return false;
}
const uint8_t* buffer = g_dataSourceBuffer.data();
const uint16_t total = static_cast<uint16_t>(g_dataSourceBuffer.size());
if (total < 5)
return false;
if (buffer[0] != 0x00)
return false;
const uint32_t uid = static_cast<uint32_t>(buffer[1]) | (static_cast<uint32_t>(buffer[2]) << 8) |
(static_cast<uint32_t>(buffer[3]) << 16) | (static_cast<uint32_t>(buffer[4]) << 24);
PendingNotification* pending = findPending(uid);
if (!pending)
pending = &ensurePending(uid);
std::size_t offset = 5;
while (offset + 3 <= total) {
const uint8_t attrId = buffer[offset];
const uint16_t attrLen =
static_cast<uint16_t>(buffer[offset + 1]) | (static_cast<uint16_t>(buffer[offset + 2]) << 8);
offset += 3;
if (offset + attrLen > total)
return false;
const char* valuePtr = reinterpret_cast<const char*>(buffer + offset);
const std::string value(valuePtr, valuePtr + attrLen);
switch (attrId) {
case 0x00:
pending->appIdentifier = value;
ESP_LOGD(kLogTag, "ANCS uid=%" PRIu32 " appId=%.*s", uid, static_cast<int>(attrLen), valuePtr);
break;
case 0x01:
pending->title = value;
ESP_LOGD(kLogTag, "ANCS uid=%" PRIu32 " title=%.*s", uid, static_cast<int>(attrLen), valuePtr);
break;
case 0x03:
pending->message = value;
ESP_LOGD(kLogTag, "ANCS uid=%" PRIu32 " message=%.*s", uid, static_cast<int>(attrLen), valuePtr);
break;
default:
break;
}
offset += attrLen;
}
if (offset != total)
return false;
ESP_LOGI(kLogTag, "ANCS data complete uid=%" PRIu32, uid);
finalizePending(uid);
g_dataSourceBuffer.clear();
return true;
}
int ancsDescriptorDiscoveredCb(uint16_t connHandle, const ble_gatt_error* error, uint16_t /*chr_val_handle*/,
const ble_gatt_dsc* dsc, void* /*arg*/) {
if (error->status == 0 && dsc) {
if (ble_uuid_cmp(&dsc->uuid.u, &kClientConfigUuid.u) == 0) {
const uint8_t enable[2] = {0x01, 0x00};
const int rc = ble_gattc_write_flat(connHandle, dsc->handle, enable, sizeof(enable), nullptr, nullptr);
if (rc != 0)
ESP_LOGW(kLogTag, "Failed to enable ANCS notifications (rc=%d) handle=%u", rc, dsc->handle);
else
ESP_LOGI(kLogTag, "Subscribed ANCS descriptor handle=%u", dsc->handle);
}
return 0;
}
if (error->status == BLE_HS_EDONE)
return 0;
return error->status;
}
int ancsCharacteristicDiscoveredCb(uint16_t connHandle, const ble_gatt_error* error, const ble_gatt_chr* chr,
void* /*arg*/) {
if (error->status == BLE_HS_EDONE)
return 0;
if (error->status != 0)
return error->status;
if (!chr)
return 0;
if ((chr->properties & BLE_GATT_CHR_PROP_NOTIFY) &&
ble_uuid_cmp(&chr->uuid.u, &kAncsNotificationSourceUuid.u) == 0) {
g_ancsNotificationSourceHandle = chr->val_handle;
ESP_LOGI(kLogTag, "ANCS notification source handle=%u", g_ancsNotificationSourceHandle);
ble_gattc_disc_all_dscs(connHandle, chr->val_handle, g_ancsServiceEndHandle, ancsDescriptorDiscoveredCb,
nullptr);
} else if ((chr->properties & BLE_GATT_CHR_PROP_NOTIFY) &&
ble_uuid_cmp(&chr->uuid.u, &kAncsDataSourceUuid.u) == 0) {
g_ancsDataSourceHandle = chr->val_handle;
ESP_LOGI(kLogTag, "ANCS data source handle=%u", g_ancsDataSourceHandle);
ble_gattc_disc_all_dscs(connHandle, chr->val_handle, g_ancsServiceEndHandle, ancsDescriptorDiscoveredCb,
nullptr);
} else if ((chr->properties & BLE_GATT_CHR_PROP_WRITE) &&
ble_uuid_cmp(&chr->uuid.u, &kAncsControlPointUuid.u) == 0) {
g_ancsControlPointHandle = chr->val_handle;
ESP_LOGI(kLogTag, "ANCS control point handle=%u", g_ancsControlPointHandle);
}
return 0;
}
int ancsServiceDiscoveredCb(uint16_t connHandle, const ble_gatt_error* error, const ble_gatt_svc* svc, void* /*arg*/) {
if (error->status == BLE_HS_EDONE)
return 0;
if (error->status != 0)
return error->status;
if (!svc) {
ESP_LOGW(kLogTag, "ANCS service missing");
return 0;
}
g_ancsServiceEndHandle = svc->end_handle;
ESP_LOGI(kLogTag, "ANCS service discovered: start=%u end=%u", svc->start_handle, svc->end_handle);
return ble_gattc_disc_all_chrs(connHandle, svc->start_handle, svc->end_handle, ancsCharacteristicDiscoveredCb,
nullptr);
}
void handleGattsRegister(ble_gatt_register_ctxt* ctxt, void* /*arg*/) {
if (ctxt->op == BLE_GATT_REGISTER_OP_CHR) {
if (ble_uuid_cmp(ctxt->chr.chr_def->uuid, &kFileCommandCharUuid.u) == 0) {
@@ -969,27 +1275,6 @@ void logConnectionParams(uint16_t connHandle, const char* context) {
static_cast<unsigned>(desc.conn_latency), timeoutMs);
}
void applyPreferredConnectionParams(uint16_t connHandle) {
ble_gap_upd_params params{
.itvl_min = kPreferredConnIntervalMin,
.itvl_max = kPreferredConnIntervalMax,
.latency = kPreferredConnLatency,
.supervision_timeout = kPreferredSupervisionTimeout,
.min_ce_len = 0,
.max_ce_len = 0,
};
const int rc = ble_gap_update_params(connHandle, &params);
if (rc != 0) {
ESP_LOGW(kLogTag, "Requesting preferred conn params failed (rc=%d)", rc);
return;
}
ESP_LOGI(kLogTag, "Requested conn params: interval=%.0f-%.0f ms latency=%u supervision=%.0f ms",
connIntervalUnitsToMs(kPreferredConnIntervalMin), connIntervalUnitsToMs(kPreferredConnIntervalMax),
kPreferredConnLatency, supervisionUnitsToMs(kPreferredSupervisionTimeout));
}
void startAdvertising() {
ble_hs_adv_fields fields{};
std::memset(&fields, 0, sizeof(fields));
@@ -1075,7 +1360,22 @@ int gapEventHandler(struct ble_gap_event* event, void* /*arg*/) {
ESP_LOGI(kLogTag, "Connected; handle=%d", event->connect.conn_handle);
g_activeConnHandle = event->connect.conn_handle;
logConnectionParams(event->connect.conn_handle, "Initial");
applyPreferredConnectionParams(event->connect.conn_handle);
ble_gap_conn_desc desc{};
if (ble_gap_conn_find(event->connect.conn_handle, &desc) == 0) {
ESP_LOGI(kLogTag, "Security state on connect: bonded=%d encrypted=%d authenticated=%d key_size=%u",
desc.sec_state.bonded, desc.sec_state.encrypted, desc.sec_state.authenticated,
static_cast<unsigned>(desc.sec_state.key_size));
if (!desc.sec_state.encrypted && !desc.sec_state.bonded && !g_securityRequested) {
const int src = ble_gap_security_initiate(event->connect.conn_handle);
if (src == 0) {
g_securityRequested = true;
ESP_LOGI(kLogTag, "Security procedure initiated");
} else {
ESP_LOGW(kLogTag, "Failed to initiate security (rc=%d)", src);
}
}
}
} else {
ESP_LOGW(kLogTag, "Connection attempt failed; status=%d", event->connect.status);
startAdvertising();
@@ -1084,7 +1384,9 @@ int gapEventHandler(struct ble_gap_event* event, void* /*arg*/) {
case BLE_GAP_EVENT_DISCONNECT:
ESP_LOGI(kLogTag, "Disconnected; reason=%d", event->disconnect.reason);
g_activeConnHandle = BLE_HS_CONN_HANDLE_NONE;
g_activeConnHandle = BLE_HS_CONN_HANDLE_NONE;
g_securityRequested = false;
resetAncsState();
resetUploadContext();
resetDownloadContext();
if (g_responseQueue)
@@ -1092,6 +1394,20 @@ int gapEventHandler(struct ble_gap_event* event, void* /*arg*/) {
startAdvertising();
break;
case BLE_GAP_EVENT_ENC_CHANGE:
if (event->enc_change.status == 0) {
ESP_LOGI(kLogTag, "Link encrypted; discovering ANCS");
resetAncsState();
g_securityRequested = false;
ble_gattc_disc_svc_by_uuid(event->enc_change.conn_handle, &kAncsServiceUuid.u, ancsServiceDiscoveredCb,
nullptr);
applyPreferredConnectionParams(event->enc_change.conn_handle);
} else {
ESP_LOGW(kLogTag, "Encryption change failed; status=%d", event->enc_change.status);
g_securityRequested = false;
}
break;
case BLE_GAP_EVENT_ADV_COMPLETE:
ESP_LOGI(kLogTag, "Advertising complete; restarting");
startAdvertising();
@@ -1108,24 +1424,39 @@ int gapEventHandler(struct ble_gap_event* event, void* /*arg*/) {
case BLE_GAP_EVENT_CONN_UPDATE_REQ:
if (event->conn_update_req.self_params) {
auto& params = *event->conn_update_req.self_params;
if (params.itvl_max > kPreferredConnIntervalMax)
params.itvl_max = kPreferredConnIntervalMax;
if (params.itvl_min > params.itvl_max)
params.itvl_min = params.itvl_max;
if (params.latency > kPreferredConnLatency)
params.latency = kPreferredConnLatency;
if (params.supervision_timeout > kPreferredSupervisionTimeout)
params.supervision_timeout = kPreferredSupervisionTimeout;
params.min_ce_len = 0;
params.max_ce_len = 0;
const auto& params = *event->conn_update_req.self_params;
ESP_LOGI(kLogTag, "Peer update request -> interval %.1f-%.1f ms latency %u timeout %.0f ms",
connIntervalUnitsToMs(params.itvl_min), connIntervalUnitsToMs(params.itvl_max), params.latency,
supervisionUnitsToMs(params.supervision_timeout));
}
break;
case BLE_GAP_EVENT_NOTIFY_RX:
if (event->notify_rx.attr_handle == g_ancsNotificationSourceHandle) {
handleAncsNotificationSource(event->notify_rx.conn_handle, event->notify_rx.om->om_data,
event->notify_rx.om->om_len);
} else if (event->notify_rx.attr_handle == g_ancsDataSourceHandle) {
const uint16_t len = event->notify_rx.om->om_len;
ESP_LOGD(kLogTag, "ANCS data chunk len=%u", static_cast<unsigned>(len));
handleAncsDataSource(event->notify_rx.om->om_data, len);
}
break;
case BLE_GAP_EVENT_MTU:
g_mtuSize = event->mtu.value;
ESP_LOGI(kLogTag, "MTU updated to %u", g_mtuSize);
break;
case BLE_GAP_EVENT_REPEAT_PAIRING: {
ble_gap_conn_desc desc{};
if (ble_gap_conn_find(event->repeat_pairing.conn_handle, &desc) == 0) {
ESP_LOGI(kLogTag, "Repeat pairing requested by %02X:%02X:%02X:%02X:%02X:%02X; keeping existing bond",
desc.peer_id_addr.val[0], desc.peer_id_addr.val[1], desc.peer_id_addr.val[2],
desc.peer_id_addr.val[3], desc.peer_id_addr.val[4], desc.peer_id_addr.val[5]);
}
return BLE_GAP_REPEAT_PAIRING_IGNORE;
}
default:
break;
}
@@ -1153,12 +1484,15 @@ bool initController() {
ble_hs_cfg.gatts_register_cb = handleGattsRegister;
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
ble_hs_cfg.sm_io_cap = BLE_HS_IO_NO_INPUT_OUTPUT;
ble_hs_cfg.sm_bonding = 0;
ble_hs_cfg.sm_bonding = 1;
ble_hs_cfg.sm_mitm = 0;
ble_hs_cfg.sm_sc = 0;
ble_hs_cfg.sm_our_key_dist = BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID;
ble_hs_cfg.sm_their_key_dist = BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID;
ESP_ERROR_CHECK(nimble_port_init());
configureGap();
ble_store_config_init();
int gattRc = ble_gatts_count_cfg(kGattServices);
if (gattRc != 0) {
@@ -1182,6 +1516,8 @@ void ensure_time_sync_service_started() {
return;
}
resetAncsState();
if (!initController()) {
ESP_LOGE(kLogTag, "Unable to initialise BLE time sync service");
return;
@@ -1254,6 +1590,10 @@ void shutdown_time_sync_service() {
vQueueDelete(g_responseQueue);
g_responseQueue = nullptr;
}
resetAncsState();
}
void set_notification_center(cardboy::sdk::INotificationCenter* center) { g_notificationCenter = center; }
} // namespace cardboy::backend::esp