-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathpatch_nimble.py
More file actions
156 lines (139 loc) · 6.35 KB
/
Copy pathpatch_nimble.py
File metadata and controls
156 lines (139 loc) · 6.35 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
"""
PlatformIO pre-build script: Patch NimBLE stability issues.
Patch 1 — ble_hs.c: Remove assert(0) in BLE_HS_SYNC_STATE_BRINGUP timer handler.
Timer can fire during host re-sync due to a race condition. Harmless — just ignore it.
Patch 2 — NimBLEClient.cpp: Add null checks in PHY update event handler.
If a client is deleted while events are queued, the callback arg becomes a dangling
pointer. Guard against null pClient and null m_pClientCallbacks.
Patch 3 — ble_gap.c: Handle BLE_ERR_CONN_ESTABLISHMENT (574) in conn complete handler.
NimBLE only handles 574 when BLE_PERIODIC_ADV_WITH_RESPONSES is enabled. Without this
patch, 574 falls through to assert(0) and the master GAP state is never cleaned up,
leaving scan/advertise permanently broken. The ESP32-S3 controller returns 574 when
connection establishment fails (peer disappeared, RF interference, etc.).
Patch 4 — NimBLEDevice.cpp: Expose host reset reason via global volatile variable.
NimBLE's onReset callback logs reason to ESP_LOG (serial only). This patch adds a
global volatile int that application code can poll to capture the reason in UDP logs.
"""
Import("env")
import os
NIMBLE_BASE = os.path.join(
env.get("PROJECT_DIR", "."),
".pio", "libdeps", "tdeck", "NimBLE-Arduino", "src"
)
def apply_patch(filepath, old, new, label):
if not os.path.exists(filepath):
print(f"PATCH: {os.path.basename(filepath)} not found, skipping {label}")
return
with open(filepath, "r") as f:
content = f.read()
if old in content:
content = content.replace(old, new)
with open(filepath, "w") as f:
f.write(content)
print(f"PATCH: {label}")
elif new in content:
print(f"PATCH: {label} (already applied)")
else:
print(f"PATCH: WARNING -- {label}: expected code not found")
# Patch 1: ble_hs.c timer assert
apply_patch(
os.path.join(NIMBLE_BASE, "nimble", "nimble", "host", "src", "ble_hs.c"),
""" case BLE_HS_SYNC_STATE_BRINGUP:
default:
/* The timer should not be set in this state. */
assert(0);
break;""",
""" case BLE_HS_SYNC_STATE_BRINGUP:
default:
/* Timer can fire during bringup due to race with host reset.
* This is harmless — bringup will reschedule when ready. */
break;""",
"ble_hs.c: removed assert(0) in BRINGUP timer handler"
)
# Patch 2: NimBLEClient.cpp PHY update null guard
apply_patch(
os.path.join(NIMBLE_BASE, "NimBLEClient.cpp"),
""" case BLE_GAP_EVENT_PHY_UPDATE_COMPLETE: {
NimBLEConnInfo peerInfo;
rc = ble_gap_conn_find(event->phy_updated.conn_handle, &peerInfo.m_desc);
if (rc != 0) {
return BLE_ATT_ERR_INVALID_HANDLE;
}
pClient->m_pClientCallbacks->onPhyUpdate(pClient, event->phy_updated.tx_phy, event->phy_updated.rx_phy);
return 0;
} // BLE_GAP_EVENT_PHY_UPDATE_COMPLETE""",
""" case BLE_GAP_EVENT_PHY_UPDATE_COMPLETE: {
if (pClient == nullptr || pClient->m_pClientCallbacks == nullptr) {
return 0;
}
NimBLEConnInfo peerInfo;
rc = ble_gap_conn_find(event->phy_updated.conn_handle, &peerInfo.m_desc);
if (rc != 0) {
return BLE_ATT_ERR_INVALID_HANDLE;
}
pClient->m_pClientCallbacks->onPhyUpdate(pClient, event->phy_updated.tx_phy, event->phy_updated.rx_phy);
return 0;
} // BLE_GAP_EVENT_PHY_UPDATE_COMPLETE""",
"NimBLEClient.cpp: added null guard in PHY update handler"
)
# Patch 3: ble_gap.c — handle BLE_ERR_CONN_ESTABLISHMENT (574) without PAwR
# The ESP32-S3 controller returns error 574 when connection establishment fails.
# NimBLE only handles this in the BLE_PERIODIC_ADV_WITH_RESPONSES path. Without it,
# 574 hits the default case (assert(0) + no cleanup), leaving master GAP state stuck
# in BLE_GAP_OP_M_CONN — permanently blocking scan and advertising.
apply_patch(
os.path.join(NIMBLE_BASE, "nimble", "nimble", "host", "src", "ble_gap.c"),
"""#if MYNEWT_VAL(BLE_PERIODIC_ADV_WITH_RESPONSES)
case BLE_ERR_CONN_ESTABLISHMENT:
if (!v1_evt) {
ble_gap_rx_conn_comp_failed(evt);
}
break;
#endif // MYNEWT_VAL(BLE_PERIODIC_ADV_WITH_RESPONSES)
default:
/* this should never happen, unless controller is broken */
BLE_HS_LOG(INFO, "controller reported invalid error code in conn"
"complete event: %u", evt->status);
assert(0);
break;""",
""" case BLE_ERR_CONN_ESTABLISHMENT:
/* Connection establishment failed (e.g. peer disappeared).
* Clean up master GAP state so scan/advertise can resume.
* Without this, the master state stays stuck in BLE_GAP_OP_M_CONN. */
if (ble_gap_master_in_progress()) {
ble_gap_master_failed(BLE_HS_ECONTROLLER);
}
break;
default:
/* this should never happen, unless controller is broken */
BLE_HS_LOG(INFO, "controller reported invalid error code in conn"
"complete event: %u", evt->status);
if (ble_gap_master_in_progress()) {
ble_gap_master_failed(BLE_HS_ECONTROLLER);
}
break;""",
"ble_gap.c: handle BLE_ERR_CONN_ESTABLISHMENT (574) to prevent stuck GAP state"
)
# Patch 4: NimBLEDevice.cpp — expose host reset reason for application-level logging
# NimBLE's onReset callback logs via NIMBLE_LOGE which only goes to serial UART.
# This patch adds a global volatile int that our BLE loop can poll and log via UDP.
apply_patch(
os.path.join(NIMBLE_BASE, "NimBLEDevice.cpp"),
"""void NimBLEDevice::onReset(int reason) {
if (!m_synced) {
return;
}
m_synced = false;
NIMBLE_LOGE(LOG_TAG, "Host reset; reason=%d, %s", reason, NimBLEUtils::returnCodeToString(reason));
} // onReset""",
"""volatile int nimble_host_reset_reason = 0;
void NimBLEDevice::onReset(int reason) {
if (!m_synced) {
return;
}
m_synced = false;
nimble_host_reset_reason = reason;
NIMBLE_LOGE(LOG_TAG, "Host reset; reason=%d, %s", reason, NimBLEUtils::returnCodeToString(reason));
} // onReset""",
"NimBLEDevice.cpp: expose host reset reason for application-level logging"
)