mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-09-17 09:54:37 +00:00
(greatly) improve our BLE
This commit is contained in:
@@ -53,6 +53,9 @@ LOG_MODULE_REGISTER(zephcore_ble, CONFIG_ZEPHCORE_BLE_LOG_LEVEL);
|
||||
* BLE stack when the link is marginal, fast enough to recover quickly. */
|
||||
#define BLE_TX_OVERFLOW_RETRY_MS 250
|
||||
|
||||
/* App push notifications are 0x80+; protocol response packets are < 0x80. */
|
||||
#define PUSH_CODE_BASE 0x80
|
||||
|
||||
/* Advertising intervals (Apple Accessory Design Guidelines §5.5) */
|
||||
#define BT_ADV_FAST_INTERVAL 32 /* 20ms in 0.625ms units */
|
||||
#define BT_ADV_FAST_DURATION_MS (60 * 1000) /* fast window after boot/disconnect */
|
||||
@@ -139,6 +142,14 @@ static uint32_t ble_passkey = CONFIG_ZEPHCORE_BLE_PASSKEY;
|
||||
/* NUS TX characteristic attribute — resolved at init, avoids hard-coded offset */
|
||||
static const struct bt_gatt_attr *nus_tx_attr;
|
||||
|
||||
static bool is_lossless_protocol_frame(const uint8_t *data, uint16_t len)
|
||||
{
|
||||
if (!data || len == 0) {
|
||||
return false;
|
||||
}
|
||||
return data[0] < PUSH_CODE_BASE;
|
||||
}
|
||||
|
||||
/* ========== Forward declarations ========== */
|
||||
|
||||
static void ble_tx_complete_cb(struct bt_conn *conn, void *user_data);
|
||||
@@ -847,6 +858,13 @@ size_t zephcore_ble_send(const uint8_t *data, uint16_t len)
|
||||
ble_tx_congested = true;
|
||||
}
|
||||
|
||||
/* Protocol response frames are lossless. If queue is full, report
|
||||
* failure so the caller can retry instead of overflow replacement. */
|
||||
if (is_lossless_protocol_frame(data, len)) {
|
||||
LOG_DBG("TX queue full for lossless protocol frame hdr=0x%02x, retry later", data[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Save to overflow — retried at 250ms intervals.
|
||||
* If overflow already pending, replace with newest frame
|
||||
* (push notifications are idempotent MSG_WAITING signals). */
|
||||
|
||||
@@ -182,6 +182,9 @@ CompanionMesh::CompanionMesh(mesh::Radio &radio, mesh::MillisecondClock &ms, mes
|
||||
_pending_telemetry = 0;
|
||||
_pending_discovery = 0;
|
||||
_pending_req = 0;
|
||||
_pending_channel_head = 0;
|
||||
_pending_channel_tail = 0;
|
||||
_pending_channel_count = 0;
|
||||
_app_target_ver = 0;
|
||||
_dirty_contacts_expiry = 0;
|
||||
_dirty_channels_expiry = 0;
|
||||
@@ -278,6 +281,7 @@ bool CompanionMesh::onContactPathRecv(ContactInfo &from, uint8_t *in_path, uint8
|
||||
void CompanionMesh::loop()
|
||||
{
|
||||
BaseChatMesh::loop();
|
||||
drainPendingChannelInfos();
|
||||
|
||||
/* Check for pending lazy contact/channel writes */
|
||||
int64_t now = _ms->getMillis();
|
||||
@@ -300,9 +304,9 @@ void CompanionMesh::markContactsDirty()
|
||||
|
||||
void CompanionMesh::markChannelsDirty()
|
||||
{
|
||||
if (!_dirty_channels_expiry) {
|
||||
_dirty_channels_expiry = _ms->getMillis() + LAZY_WRITE_DELAY_MS;
|
||||
}
|
||||
/* Channels import arrives as a burst of CMD_SET_CHANNEL writes.
|
||||
* Keep pushing the flush deadline so we save once after the burst. */
|
||||
_dirty_channels_expiry = _ms->getMillis() + LAZY_WRITE_DELAY_MS;
|
||||
}
|
||||
|
||||
void CompanionMesh::flushDirtyContacts()
|
||||
@@ -323,12 +327,13 @@ void CompanionMesh::flushDirtyChannels()
|
||||
}
|
||||
}
|
||||
|
||||
void CompanionMesh::writeFrame(const uint8_t *data, size_t len)
|
||||
bool CompanionMesh::writeFrame(const uint8_t *data, size_t len)
|
||||
{
|
||||
LOG_DBG("RSP: 0x%02x len=%u", data[0], (unsigned)len);
|
||||
if (_write_cb) {
|
||||
_write_cb(data, len);
|
||||
return _write_cb(data, len) == len;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void CompanionMesh::sendPacketOk()
|
||||
@@ -378,6 +383,46 @@ static bool isChannelMessage(const uint8_t *buf)
|
||||
return buf[0] == PACKET_CHANNEL_MSG_V3 || buf[0] == PACKET_CHANNEL_MSG_RECV || buf[0] == PACKET_CHANNEL_DATA_RECV;
|
||||
}
|
||||
|
||||
bool CompanionMesh::enqueuePendingChannelInfo(uint8_t idx)
|
||||
{
|
||||
if (_pending_channel_count >= MAX_GROUP_CHANNELS) {
|
||||
return false;
|
||||
}
|
||||
_pending_channel_idx[_pending_channel_tail] = idx;
|
||||
_pending_channel_tail = (uint8_t)((_pending_channel_tail + 1) % MAX_GROUP_CHANNELS);
|
||||
_pending_channel_count++;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CompanionMesh::sendChannelInfoFrame(uint8_t idx)
|
||||
{
|
||||
ChannelDetails ch;
|
||||
if (!getChannel(idx, ch)) {
|
||||
return false;
|
||||
}
|
||||
uint8_t rsp[50];
|
||||
int i = 0;
|
||||
rsp[i++] = PACKET_CHANNEL_INFO;
|
||||
rsp[i++] = idx;
|
||||
memcpy(&rsp[i], ch.name, 32);
|
||||
i += 32;
|
||||
memcpy(&rsp[i], ch.channel.secret, 16);
|
||||
i += 16;
|
||||
return writeFrame(rsp, i);
|
||||
}
|
||||
|
||||
void CompanionMesh::drainPendingChannelInfos()
|
||||
{
|
||||
while (_pending_channel_count > 0) {
|
||||
uint8_t idx = _pending_channel_idx[_pending_channel_head];
|
||||
if (!sendChannelInfoFrame(idx)) {
|
||||
return;
|
||||
}
|
||||
_pending_channel_head = (uint8_t)((_pending_channel_head + 1) % MAX_GROUP_CHANNELS);
|
||||
_pending_channel_count--;
|
||||
}
|
||||
}
|
||||
|
||||
void CompanionMesh::queueOfflineMessage(const uint8_t *data, size_t len)
|
||||
{
|
||||
LOG_DBG("queueOfflineMessage: len=%u type=0x%02x count_before=%d", (unsigned)len, data[0], _offline_queue_count);
|
||||
@@ -465,7 +510,9 @@ bool CompanionMesh::continueContactIteration()
|
||||
}
|
||||
uint8_t rsp[CONTACT_FRAME_SIZE];
|
||||
size_t n = serializeContact(rsp, c, PACKET_CONTACT);
|
||||
writeFrame(rsp, n);
|
||||
if (!writeFrame(rsp, n)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
_contact_iter_idx++;
|
||||
@@ -475,7 +522,9 @@ bool CompanionMesh::continueContactIteration()
|
||||
uint8_t rsp[5];
|
||||
rsp[0] = PACKET_CONTACT_END;
|
||||
put_le32(&rsp[1], _contact_iter_lastmod);
|
||||
writeFrame(rsp, sizeof(rsp));
|
||||
if (!writeFrame(rsp, sizeof(rsp))) {
|
||||
return true;
|
||||
}
|
||||
_contact_iter_active = false;
|
||||
return false;
|
||||
}
|
||||
@@ -1511,20 +1560,16 @@ bool CompanionMesh::handleProtocolFrame(const uint8_t *data, size_t len)
|
||||
// Response: [code][idx][32 name][16 secret] = 50 bytes (matches Arduino)
|
||||
// Arduino returns channel info even if slot is empty (name[0]=='\0')
|
||||
if (len >= 2 && data[1] < MAX_GROUP_CHANNELS) {
|
||||
ChannelDetails ch;
|
||||
if (getChannel(data[1], ch)) {
|
||||
uint8_t rsp[50];
|
||||
int i = 0;
|
||||
rsp[i++] = PACKET_CHANNEL_INFO;
|
||||
rsp[i++] = data[1];
|
||||
memcpy(&rsp[i], ch.name, 32);
|
||||
i += 32;
|
||||
memcpy(&rsp[i], ch.channel.secret, 16); // return 128-bit secret
|
||||
i += 16;
|
||||
writeFrame(rsp, i);
|
||||
} else {
|
||||
sendPacketError(ERR_NOT_FOUND); // Only if index out of range
|
||||
static int64_t last_get_channel_ms;
|
||||
int64_t now_ms = _ms->getMillis();
|
||||
uint32_t dt_ms = last_get_channel_ms ? (uint32_t)(now_ms - last_get_channel_ms) : 0;
|
||||
last_get_channel_ms = now_ms;
|
||||
LOG_DBG("CMD_GET_CHANNEL idx=%u dt=%ums", data[1], dt_ms);
|
||||
if (!enqueuePendingChannelInfo(data[1])) {
|
||||
sendPacketError(ERR_BAD_STATE);
|
||||
return true;
|
||||
}
|
||||
drainPendingChannelInfos();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
@@ -1826,12 +1871,15 @@ bool CompanionMesh::handleProtocolFrame(const uint8_t *data, size_t len)
|
||||
size_t msg_len;
|
||||
if (peekOfflineMessage(buf, msg_len)) {
|
||||
LOG_DBG("CMD_SYNC_NEXT_MESSAGE: peeked msg_len=%u type=0x%02x", (unsigned)msg_len, buf[0]);
|
||||
writeFrame(buf, msg_len);
|
||||
_sync_pending = true; /* will be confirmed on next request or lost on disconnect */
|
||||
if (writeFrame(buf, msg_len)) {
|
||||
_sync_pending = true; /* confirmed on next request or lost on disconnect */
|
||||
}
|
||||
} else {
|
||||
LOG_DBG("CMD_SYNC_NEXT_MESSAGE: queue empty, sending NO_MORE_MSGS");
|
||||
uint8_t rsp[] = { PACKET_NO_MORE_MSGS };
|
||||
writeFrame(rsp, sizeof(rsp));
|
||||
if (!writeFrame(rsp, sizeof(rsp))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Initial sync is done — safe to apply deferred
|
||||
* connection parameters now without disrupting
|
||||
|
||||
@@ -339,7 +339,7 @@ private:
|
||||
_pending_login = _pending_status = _pending_telemetry = _pending_discovery = _pending_req = 0;
|
||||
}
|
||||
|
||||
void writeFrame(const uint8_t *data, size_t len);
|
||||
bool writeFrame(const uint8_t *data, size_t len);
|
||||
void sendPacketOk();
|
||||
void sendPacketError(uint8_t code);
|
||||
void sendPush(uint8_t code, const uint8_t *data = nullptr, size_t len = 0);
|
||||
@@ -352,10 +352,18 @@ private:
|
||||
bool dequeueOfflineMessage(uint8_t *dest, size_t &len);
|
||||
bool peekOfflineMessage(uint8_t *dest, size_t &len);
|
||||
void confirmOfflineMessage();
|
||||
bool enqueuePendingChannelInfo(uint8_t idx);
|
||||
bool sendChannelInfoFrame(uint8_t idx);
|
||||
void drainPendingChannelInfos();
|
||||
|
||||
void queueContactMessage(const ContactInfo &contact, mesh::Packet *pkt,
|
||||
uint8_t txt_type, uint32_t sender_timestamp, const uint8_t *extra, int extra_len, const char *text);
|
||||
|
||||
void addPendingAck(uint32_t expected, int contact_idx);
|
||||
int findAndRemoveAck(uint32_t ack, uint32_t *out_sent_time = nullptr);
|
||||
|
||||
uint8_t _pending_channel_idx[MAX_GROUP_CHANNELS];
|
||||
uint8_t _pending_channel_head;
|
||||
uint8_t _pending_channel_tail;
|
||||
uint8_t _pending_channel_count;
|
||||
};
|
||||
|
||||
@@ -129,6 +129,7 @@ static void ble_on_rx_frame(const uint8_t *data, uint16_t len)
|
||||
static void ble_on_tx_idle(void)
|
||||
{
|
||||
k_work_submit(&contact_iter_work);
|
||||
k_event_post(&mesh_events, MESH_EVENT_TX_DRAIN);
|
||||
}
|
||||
|
||||
/* BLE connected callback — notify UI, clear USB state if needed */
|
||||
|
||||
Reference in New Issue
Block a user