mirror of
https://github.com/usatiuk/cardboy.git
synced 2025-10-28 23:27:49 +01:00
lower power consumption bluetooth
This commit is contained in:
@@ -13,6 +13,7 @@
|
|||||||
#include "host/ble_gap.h"
|
#include "host/ble_gap.h"
|
||||||
#include "host/ble_gatt.h"
|
#include "host/ble_gatt.h"
|
||||||
#include "host/ble_hs.h"
|
#include "host/ble_hs.h"
|
||||||
|
#include "host/ble_att.h"
|
||||||
#include "host/ble_hs_mbuf.h"
|
#include "host/ble_hs_mbuf.h"
|
||||||
#include "host/util/util.h"
|
#include "host/util/util.h"
|
||||||
#include "nimble/nimble_port.h"
|
#include "nimble/nimble_port.h"
|
||||||
@@ -43,6 +44,19 @@ namespace {
|
|||||||
constexpr char kLogTag[] = "TimeSyncBLE";
|
constexpr char kLogTag[] = "TimeSyncBLE";
|
||||||
constexpr char kDeviceName[] = "Cardboy";
|
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 kPreferredSupervisionTimeout = BLE_GAP_SUPERVISION_TIMEOUT_MS(5000); // 5 s
|
||||||
|
|
||||||
|
constexpr float connIntervalUnitsToMs(std::uint16_t units) {
|
||||||
|
return static_cast<float>(units) * 1.25f;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr float supervisionUnitsToMs(std::uint16_t units) {
|
||||||
|
return static_cast<float>(units) * 10.0f;
|
||||||
|
}
|
||||||
|
|
||||||
// 128-bit UUIDs (little-endian order for NimBLE macros)
|
// 128-bit UUIDs (little-endian order for NimBLE macros)
|
||||||
static const ble_uuid128_t kTimeServiceUuid = BLE_UUID128_INIT(0x30, 0xF2, 0xD3, 0xF4, 0xC3, 0x10, 0xA6, 0xB5, 0xFD,
|
static const ble_uuid128_t kTimeServiceUuid = BLE_UUID128_INIT(0x30, 0xF2, 0xD3, 0xF4, 0xC3, 0x10, 0xA6, 0xB5, 0xFD,
|
||||||
0x4E, 0x7B, 0xCA, 0x01, 0x00, 0x00, 0x00);
|
0x4E, 0x7B, 0xCA, 0x01, 0x00, 0x00, 0x00);
|
||||||
@@ -75,6 +89,7 @@ struct ResponseMessage {
|
|||||||
uint8_t status;
|
uint8_t status;
|
||||||
uint16_t length;
|
uint16_t length;
|
||||||
uint8_t* data;
|
uint8_t* data;
|
||||||
|
bool streamDownload;
|
||||||
};
|
};
|
||||||
|
|
||||||
static QueueHandle_t g_responseQueue = nullptr;
|
static QueueHandle_t g_responseQueue = nullptr;
|
||||||
@@ -95,6 +110,7 @@ struct FileDownloadContext {
|
|||||||
FILE* file = nullptr;
|
FILE* file = nullptr;
|
||||||
std::size_t remaining = 0;
|
std::size_t remaining = 0;
|
||||||
bool active = false;
|
bool active = false;
|
||||||
|
bool chunkScheduled = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
static FileUploadContext g_uploadCtx{};
|
static FileUploadContext g_uploadCtx{};
|
||||||
@@ -142,6 +158,8 @@ void handleRename(const uint8_t* payload, std::size_t length);
|
|||||||
bool enqueueFileResponse(uint8_t opcode, uint8_t status, const uint8_t* data, std::size_t length);
|
bool enqueueFileResponse(uint8_t opcode, uint8_t status, const uint8_t* data, std::size_t length);
|
||||||
bool sendFileResponseNow(const ResponseMessage& msg);
|
bool sendFileResponseNow(const ResponseMessage& msg);
|
||||||
void notificationTask(void* param);
|
void notificationTask(void* param);
|
||||||
|
bool scheduleDownloadChunk();
|
||||||
|
void processDownloadChunk();
|
||||||
|
|
||||||
static const ble_gatt_chr_def kTimeServiceCharacteristics[] = {
|
static const ble_gatt_chr_def kTimeServiceCharacteristics[] = {
|
||||||
{
|
{
|
||||||
@@ -345,6 +363,29 @@ bool sendFileResponse(uint8_t opcode, uint8_t status, const uint8_t* data, std::
|
|||||||
return enqueueFileResponse(opcode, status, data, length);
|
return enqueueFileResponse(opcode, status, data, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool scheduleDownloadChunk() {
|
||||||
|
if (!g_downloadCtx.active || !g_responseQueue || g_activeConnHandle == BLE_HS_CONN_HANDLE_NONE)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (g_downloadCtx.chunkScheduled)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
ResponseMessage msg{};
|
||||||
|
msg.opcode = static_cast<uint8_t>(FileCommandCode::DownloadRequest);
|
||||||
|
msg.status = 0;
|
||||||
|
msg.length = 0;
|
||||||
|
msg.data = nullptr;
|
||||||
|
msg.streamDownload = true;
|
||||||
|
|
||||||
|
if (xQueueSend(g_responseQueue, &msg, pdMS_TO_TICKS(20)) != pdPASS) {
|
||||||
|
ESP_LOGW(kLogTag, "Failed to schedule download chunk; response queue full");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_downloadCtx.chunkScheduled = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool sendFileError(uint8_t opcode, int err, const char* message) {
|
bool sendFileError(uint8_t opcode, int err, const char* message) {
|
||||||
const uint8_t status = static_cast<uint8_t>(std::min(err, 0x7F)) | kResponseFlagComplete;
|
const uint8_t status = static_cast<uint8_t>(std::min(err, 0x7F)) | kResponseFlagComplete;
|
||||||
if (message && *message != '\0') {
|
if (message && *message != '\0') {
|
||||||
@@ -371,6 +412,78 @@ void resetDownloadContext() {
|
|||||||
}
|
}
|
||||||
g_downloadCtx.remaining = 0;
|
g_downloadCtx.remaining = 0;
|
||||||
g_downloadCtx.active = false;
|
g_downloadCtx.active = false;
|
||||||
|
g_downloadCtx.chunkScheduled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void processDownloadChunk() {
|
||||||
|
if (!g_downloadCtx.active || !g_downloadCtx.file) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_activeConnHandle == BLE_HS_CONN_HANDLE_NONE) {
|
||||||
|
resetDownloadContext();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr std::size_t kMaxChunkBuffer = 244;
|
||||||
|
std::array<uint8_t, kMaxChunkBuffer> buffer{};
|
||||||
|
|
||||||
|
std::size_t maxPayload = buffer.size();
|
||||||
|
if (g_activeConnHandle != BLE_HS_CONN_HANDLE_NONE) {
|
||||||
|
const uint16_t mtu = ble_att_mtu(g_activeConnHandle);
|
||||||
|
if (mtu > sizeof(PacketHeader)) {
|
||||||
|
maxPayload = std::min<std::size_t>(buffer.size(), static_cast<std::size_t>(mtu - sizeof(PacketHeader)));
|
||||||
|
} else {
|
||||||
|
maxPayload = std::min<std::size_t>(buffer.size(), static_cast<std::size_t>(20));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::size_t toRead = std::min<std::size_t>(maxPayload, g_downloadCtx.remaining);
|
||||||
|
if (toRead == 0) {
|
||||||
|
resetDownloadContext();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::size_t read = std::fread(buffer.data(), 1, toRead, g_downloadCtx.file);
|
||||||
|
if (read == 0) {
|
||||||
|
const int err = ferror(g_downloadCtx.file) ? errno : EIO;
|
||||||
|
resetDownloadContext();
|
||||||
|
sendFileError(static_cast<uint8_t>(FileCommandCode::DownloadRequest), err, "Read failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_downloadCtx.remaining -= read;
|
||||||
|
|
||||||
|
ResponseMessage chunk{};
|
||||||
|
chunk.opcode = static_cast<uint8_t>(FileCommandCode::DownloadRequest);
|
||||||
|
chunk.status = (g_downloadCtx.remaining == 0) ? kResponseFlagComplete : 0;
|
||||||
|
chunk.length = static_cast<uint16_t>(read);
|
||||||
|
chunk.data = buffer.data();
|
||||||
|
|
||||||
|
bool sent = false;
|
||||||
|
while (g_activeConnHandle != BLE_HS_CONN_HANDLE_NONE) {
|
||||||
|
if (sendFileResponseNow(chunk)) {
|
||||||
|
sent = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(100));
|
||||||
|
}
|
||||||
|
if (!sent) {
|
||||||
|
resetDownloadContext();
|
||||||
|
sendFileError(static_cast<uint8_t>(FileCommandCode::DownloadRequest), EIO, "Notify failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_downloadCtx.remaining == 0) {
|
||||||
|
resetDownloadContext();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(3));
|
||||||
|
if (!scheduleDownloadChunk()) {
|
||||||
|
resetDownloadContext();
|
||||||
|
sendFileError(static_cast<uint8_t>(FileCommandCode::DownloadRequest), EAGAIN, "Queue busy");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleListDirectory(const uint8_t* payload, std::size_t length) {
|
void handleListDirectory(const uint8_t* payload, std::size_t length) {
|
||||||
@@ -597,28 +710,10 @@ void handleDownloadRequest(const uint8_t* payload, std::size_t length) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::array<uint8_t, 128> chunk{};
|
if (!scheduleDownloadChunk()) {
|
||||||
while (g_downloadCtx.remaining > 0) {
|
sendFileError(opcode, EAGAIN, "Queue busy");
|
||||||
const std::size_t toRead = std::min<std::size_t>(chunk.size(), g_downloadCtx.remaining);
|
resetDownloadContext();
|
||||||
const std::size_t read = std::fread(chunk.data(), 1, toRead, g_downloadCtx.file);
|
|
||||||
if (read == 0) {
|
|
||||||
const int err = ferror(g_downloadCtx.file) ? errno : EIO;
|
|
||||||
resetDownloadContext();
|
|
||||||
sendFileError(opcode, err, "Read failed");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
g_downloadCtx.remaining -= read;
|
|
||||||
const uint8_t status = (g_downloadCtx.remaining == 0) ? kResponseFlagComplete : 0;
|
|
||||||
if (!sendFileResponse(opcode, status, chunk.data(), read)) {
|
|
||||||
resetDownloadContext();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (g_downloadCtx.remaining > 0)
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(5));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resetDownloadContext();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleDeletePath(const uint8_t* payload, std::size_t length, bool directory) {
|
void handleDeletePath(const uint8_t* payload, std::size_t length, bool directory) {
|
||||||
@@ -829,12 +924,20 @@ int timeSyncWriteAccess(uint16_t /*conn_handle*/, uint16_t /*attr_handle*/, ble_
|
|||||||
void notificationTask(void* /*param*/) {
|
void notificationTask(void* /*param*/) {
|
||||||
ResponseMessage msg{};
|
ResponseMessage msg{};
|
||||||
while (xQueueReceive(g_responseQueue, &msg, portMAX_DELAY) == pdTRUE) {
|
while (xQueueReceive(g_responseQueue, &msg, portMAX_DELAY) == pdTRUE) {
|
||||||
if (msg.opcode == kResponseOpcodeShutdown && msg.length == 0) {
|
if (msg.opcode == kResponseOpcodeShutdown && msg.length == 0 && !msg.streamDownload) {
|
||||||
if (msg.data)
|
if (msg.data)
|
||||||
vPortFree(msg.data);
|
vPortFree(msg.data);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (msg.streamDownload) {
|
||||||
|
g_downloadCtx.chunkScheduled = false;
|
||||||
|
processDownloadChunk();
|
||||||
|
if (msg.data)
|
||||||
|
vPortFree(msg.data);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
bool sent = false;
|
bool sent = false;
|
||||||
while (g_activeConnHandle != BLE_HS_CONN_HANDLE_NONE) {
|
while (g_activeConnHandle != BLE_HS_CONN_HANDLE_NONE) {
|
||||||
if (sendFileResponseNow(msg)) {
|
if (sendFileResponseNow(msg)) {
|
||||||
@@ -856,6 +959,48 @@ void notificationTask(void* /*param*/) {
|
|||||||
vTaskDelete(nullptr);
|
vTaskDelete(nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void logConnectionParams(uint16_t connHandle, const char* context) {
|
||||||
|
ble_gap_conn_desc desc{};
|
||||||
|
if (ble_gap_conn_find(connHandle, &desc) != 0) {
|
||||||
|
ESP_LOGW(kLogTag, "%s: unable to read conn params for handle=%u", context, static_cast<unsigned>(connHandle));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float intervalMs = connIntervalUnitsToMs(desc.conn_itvl);
|
||||||
|
const float timeoutMs = supervisionUnitsToMs(desc.supervision_timeout);
|
||||||
|
|
||||||
|
ESP_LOGI(kLogTag,
|
||||||
|
"%s params: interval=%.1f ms latency=%u supervision=%.0f ms",
|
||||||
|
context,
|
||||||
|
intervalMs,
|
||||||
|
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, ¶ms);
|
||||||
|
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() {
|
void startAdvertising() {
|
||||||
ble_hs_adv_fields fields{};
|
ble_hs_adv_fields fields{};
|
||||||
std::memset(&fields, 0, sizeof(fields));
|
std::memset(&fields, 0, sizeof(fields));
|
||||||
@@ -940,6 +1085,8 @@ int gapEventHandler(struct ble_gap_event* event, void* /*arg*/) {
|
|||||||
if (event->connect.status == 0) {
|
if (event->connect.status == 0) {
|
||||||
ESP_LOGI(kLogTag, "Connected; handle=%d", event->connect.conn_handle);
|
ESP_LOGI(kLogTag, "Connected; handle=%d", event->connect.conn_handle);
|
||||||
g_activeConnHandle = event->connect.conn_handle;
|
g_activeConnHandle = event->connect.conn_handle;
|
||||||
|
logConnectionParams(event->connect.conn_handle, "Initial");
|
||||||
|
applyPreferredConnectionParams(event->connect.conn_handle);
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGW(kLogTag, "Connection attempt failed; status=%d", event->connect.status);
|
ESP_LOGW(kLogTag, "Connection attempt failed; status=%d", event->connect.status);
|
||||||
startAdvertising();
|
startAdvertising();
|
||||||
@@ -961,6 +1108,40 @@ int gapEventHandler(struct ble_gap_event* event, void* /*arg*/) {
|
|||||||
startAdvertising();
|
startAdvertising();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case BLE_GAP_EVENT_CONN_UPDATE:
|
||||||
|
if (event->conn_update.status == 0) {
|
||||||
|
logConnectionParams(event->conn_update.conn_handle, "Updated");
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(kLogTag,
|
||||||
|
"Connection update failed; status=%d handle=%u",
|
||||||
|
event->conn_update.status,
|
||||||
|
static_cast<unsigned>(event->conn_update.conn_handle));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
#include "cardboy/apps/clock_app.hpp"
|
#include "cardboy/apps/clock_app.hpp"
|
||||||
#include "cardboy/apps/gameboy_app.hpp"
|
#include "cardboy/apps/gameboy_app.hpp"
|
||||||
#include "cardboy/apps/menu_app.hpp"
|
|
||||||
#include "cardboy/apps/lockscreen_app.hpp"
|
#include "cardboy/apps/lockscreen_app.hpp"
|
||||||
|
#include "cardboy/apps/menu_app.hpp"
|
||||||
#include "cardboy/apps/settings_app.hpp"
|
#include "cardboy/apps/settings_app.hpp"
|
||||||
#include "cardboy/apps/snake_app.hpp"
|
#include "cardboy/apps/snake_app.hpp"
|
||||||
#include "cardboy/apps/tetris_app.hpp"
|
#include "cardboy/apps/tetris_app.hpp"
|
||||||
@@ -241,7 +241,7 @@ extern "C" void app_main() {
|
|||||||
system.registerApp(apps::createTetrisAppFactory());
|
system.registerApp(apps::createTetrisAppFactory());
|
||||||
system.registerApp(apps::createGameboyAppFactory());
|
system.registerApp(apps::createGameboyAppFactory());
|
||||||
|
|
||||||
start_task_usage_monitor();
|
// start_task_usage_monitor();
|
||||||
|
|
||||||
system.run();
|
system.run();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user