mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-09-01 18:38:17 +00:00
fix contact sync freeze on cold boot
This commit is contained in:
@@ -1156,9 +1156,14 @@ static void tx_drain_work_fn(struct k_work *work)
|
||||
bt_conn_unref(conn);
|
||||
return;
|
||||
} else {
|
||||
/* Other error - drop frame */
|
||||
/* Other error - drop frame. Re-kick rather than returning: on_tx_idle
|
||||
* only fires from the queue-empty path below, and it is the sole re-arm
|
||||
* for the contact dump — bailing out here stranded the dump forever.
|
||||
* This terminates: the frame is already off the queue, so each pass
|
||||
* either drains one or reaches empty and signals idle. */
|
||||
ble_tx_in_progress = false;
|
||||
LOG_WRN("tx_drain[BLE]: send failed err=%d, dropped frame", err);
|
||||
kick_tx_drain();
|
||||
bt_conn_unref(conn);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -178,6 +178,8 @@ CompanionMesh::CompanionMesh(mesh::Radio &radio, mesh::MillisecondClock &ms, mes
|
||||
_pin_change_cb = nullptr;
|
||||
_contact_iter_active = false;
|
||||
_contact_iter_idx = 0;
|
||||
_contact_iter_num = 0;
|
||||
_contact_iter_vc = false;
|
||||
_contact_iter_lastmod = 0;
|
||||
_contact_iter_since = 0;
|
||||
_offline_queue_head = 0;
|
||||
@@ -588,24 +590,14 @@ void CompanionMesh::confirmOfflineMessage()
|
||||
_offline_queue_count--;
|
||||
}
|
||||
|
||||
void CompanionMesh::resetContactIterator()
|
||||
{
|
||||
if (_contact_iter_active) {
|
||||
// Send contact end if interrupted
|
||||
uint8_t rsp[5];
|
||||
rsp[0] = PACKET_CONTACT_END;
|
||||
put_le32(&rsp[1], _contact_iter_lastmod);
|
||||
writeFrame(rsp, sizeof(rsp));
|
||||
}
|
||||
_contact_iter_active = false;
|
||||
}
|
||||
|
||||
bool CompanionMesh::continueContactIteration()
|
||||
{
|
||||
if (!_contact_iter_active) return false;
|
||||
|
||||
if (_contact_iter_idx < getNumContacts()) {
|
||||
if (_contact_iter_idx < _contact_iter_num) {
|
||||
ContactInfo c;
|
||||
/* getContactByIdx bounds-checks against the live table, so a slot that
|
||||
* disappeared under us is skipped rather than read stale. */
|
||||
if (getContactByIdx(_contact_iter_idx, c)) {
|
||||
// Skip transient/anon contacts (ADV_TYPE_NONE) — never synced to the app.
|
||||
// Apply 'since' filter - only send contacts modified after the timestamp
|
||||
@@ -622,11 +614,11 @@ bool CompanionMesh::continueContactIteration()
|
||||
}
|
||||
_contact_iter_idx++;
|
||||
return true;
|
||||
} else if (_contact_iter_idx == getNumContacts()) {
|
||||
} else if (_contact_iter_idx == _contact_iter_num) {
|
||||
// Virtual tail entry: the v-contact (never in the real table).
|
||||
// vcontactReady() implies lastmod != 0 — deferred (clock-invalid)
|
||||
// _contact_iter_vc implies lastmod != 0 — deferred (clock-invalid)
|
||||
// state is excluded so the app never sees a 1970 timestamp.
|
||||
if (vcontactReady() && _vcontact_lastmod > _contact_iter_since) {
|
||||
if (_contact_iter_vc && _vcontact_lastmod > _contact_iter_since) {
|
||||
if (_vcontact_lastmod > _contact_iter_lastmod) {
|
||||
_contact_iter_lastmod = _vcontact_lastmod;
|
||||
}
|
||||
@@ -2018,10 +2010,12 @@ bool CompanionMesh::handleProtocolFrame(const uint8_t *data, size_t len)
|
||||
/* Debug: log all incoming commands */
|
||||
LOG_DBG("CMD: 0x%02x len=%u", data[0], (unsigned)len);
|
||||
|
||||
// Reset contact iterator when any new command is received (except during iteration)
|
||||
if (data[0] != CMD_GET_CONTACTS && _contact_iter_active) {
|
||||
resetContactIterator();
|
||||
}
|
||||
/* An active contact dump survives interleaved commands — the app is free to
|
||||
* talk to us mid-sync and does (it sets the clock on a cold boot, and
|
||||
* pipelines CMD_GET_CHANNEL bursts). Aborting the iterator here truncated
|
||||
* the dump with a premature PACKET_CONTACT_END after whatever had streamed,
|
||||
* so the app waited forever for the rest. Upstream interleaves the same way
|
||||
* (MyMesh::checkSerialInterface); CMD_APP_START remains the only reset. */
|
||||
|
||||
/* V-contact interception — must run before any contact lookup so a frame
|
||||
* addressed to the loopback contact can never create a radio packet. */
|
||||
@@ -2096,12 +2090,14 @@ bool CompanionMesh::handleProtocolFrame(const uint8_t *data, size_t len)
|
||||
|
||||
// Send PACKET_CONTACT_START with total count (unfiltered, but excluding
|
||||
// transient anon slots -- continueContactIteration() never streams those)
|
||||
_contact_iter_num = getNumContacts();
|
||||
_contact_iter_vc = vcontactReady(); /* virtual tail entry (post time sync) */
|
||||
uint32_t total = 0;
|
||||
for (int i = 0; i < getNumContacts(); i++) {
|
||||
for (int i = 0; i < _contact_iter_num; i++) {
|
||||
ContactInfo c;
|
||||
if (getContactByIdx(i, c) && c.type != ADV_TYPE_NONE) total++;
|
||||
}
|
||||
if (vcontactReady()) total++; /* virtual tail entry (post time sync) */
|
||||
if (_contact_iter_vc) total++;
|
||||
uint8_t rsp[5];
|
||||
rsp[0] = PACKET_CONTACT_START;
|
||||
put_le32(&rsp[1], total);
|
||||
|
||||
@@ -214,18 +214,18 @@ public:
|
||||
*/
|
||||
bool continueContactIteration();
|
||||
|
||||
/**
|
||||
* Reset contact iterator (call when new command received).
|
||||
* Sends PACKET_CONTACT_END if iteration was in progress.
|
||||
*/
|
||||
void resetContactIterator();
|
||||
|
||||
/**
|
||||
* Cancel contact iteration silently (no frame sent).
|
||||
* Call on BLE disconnect — there's nobody to send CONTACT_END to.
|
||||
*/
|
||||
void cancelContactIterator() { _contact_iter_active = false; }
|
||||
|
||||
/** True while a contact dump is in progress (drives the stall watchdog). */
|
||||
bool isContactIterActive() const { return _contact_iter_active; }
|
||||
|
||||
/** Dump progress cursor — the watchdog re-kicks only if this stops moving. */
|
||||
int getContactIterIdx() const { return _contact_iter_idx; }
|
||||
|
||||
/**
|
||||
* Cancel pending message sync. Un-ACKed message stays in queue.
|
||||
* Call on BLE disconnect so the message is re-sent on reconnect.
|
||||
@@ -384,6 +384,12 @@ private:
|
||||
/* Contact iteration state */
|
||||
bool _contact_iter_active;
|
||||
int _contact_iter_idx;
|
||||
/* Table bound and v-contact inclusion are snapshotted at PACKET_CONTACT_START
|
||||
* so the dump can never stream more entries than the total it promised: the
|
||||
* table grows from inbound adverts mid-dump, and vcontactReady() flips false
|
||||
* ->true the moment a cold-booted clock goes valid (CMD_SET_DEVICE_TIME). */
|
||||
int _contact_iter_num;
|
||||
bool _contact_iter_vc;
|
||||
uint32_t _contact_iter_lastmod;
|
||||
uint32_t _contact_iter_since; /* Filter: only send contacts with lastmod > this */
|
||||
|
||||
|
||||
@@ -373,10 +373,10 @@ static void process_companion_rx(void)
|
||||
/* Process all queued frames */
|
||||
while (k_msgq_get(zephcore_ble_get_recv_queue(), &f, K_NO_WAIT) == 0) {
|
||||
#ifdef ZEPHCORE_LORA
|
||||
/* handleProtocolFrame() resets the contact iterator internally
|
||||
* (line 1229) for any non-CMD_GET_CONTACTS command — no need
|
||||
* to call resetContactIterator() here. Doing so sent a stale
|
||||
* PACKET_CONTACT_END before the command was even processed. */
|
||||
/* An in-flight contact dump deliberately survives commands parsed
|
||||
* here — it is only cancelled by CMD_APP_START (new session) or by
|
||||
* disconnect. Do not abort it on inbound traffic: the app talks to
|
||||
* us mid-sync, and truncating the dump left it waiting forever. */
|
||||
if (!companion_mesh_ptr->handleProtocolFrame(f.buf, f.len)) {
|
||||
LOG_DBG("rx_process: unknown cmd 0x%02x len=%u", f.buf[0], (unsigned)f.len);
|
||||
uint8_t err_rsp[] = { 0x01, 0x01 }; /* PACKET_ERROR, ERR_UNSUPPORTED */
|
||||
@@ -516,6 +516,24 @@ static void mesh_event_loop(void)
|
||||
companion_mesh_ptr->maintenanceLoop();
|
||||
}
|
||||
|
||||
/* Contact-dump watchdog — the dump is pumped solely by the
|
||||
* BLE/USB tx-idle callback, so a single lost kick strands it
|
||||
* silently and the app waits for contacts that never arrive.
|
||||
* Only re-kick when the cursor has not moved across a whole
|
||||
* tick: a large dump legitimately spans several ticks, and
|
||||
* back-pressure resolves itself. */
|
||||
static int wd_last_iter_idx = -1;
|
||||
if (companion_mesh_ptr && companion_mesh_ptr->isContactIterActive()) {
|
||||
int idx = companion_mesh_ptr->getContactIterIdx();
|
||||
if (idx == wd_last_iter_idx) {
|
||||
LOG_WRN("contact dump watchdog: no progress at %d, resuming", idx);
|
||||
k_event_post(&mesh_events, MESH_EVENT_CONTACT_ITER);
|
||||
}
|
||||
wd_last_iter_idx = idx;
|
||||
} else {
|
||||
wd_last_iter_idx = -1;
|
||||
}
|
||||
|
||||
/* BLE advertising watchdog — if bt_le_adv_start failed
|
||||
* transiently (HCI timeout, controller pacing) the device
|
||||
* would silently stop advertising and be undiscoverable
|
||||
|
||||
Reference in New Issue
Block a user