Skip to content

Commit 09ccd74

Browse files
committed
Attempt to fix corrupted coordinator NVRAM during startup
1 parent 0adeb9b commit 09ccd74

File tree

3 files changed

+97
-2
lines changed

3 files changed

+97
-2
lines changed

tests/application/test_startup.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ async def test_auto_form_unnecessary(device, make_application, mocker):
216216

217217

218218
@pytest.mark.parametrize("device", EMPTY_DEVICES)
219-
async def test_auto_form_necessary(device, make_application, mocker):
219+
async def test_auto_form_necessary(device, make_application):
220220
app, znp_server = make_application(server_cls=device)
221221

222222
assert app.channel is None
@@ -238,3 +238,35 @@ async def test_auto_form_necessary(device, make_application, mocker):
238238
assert nvram[OsalNvIds.ZDO_DIRECT_CB] == t.Bool(True).serialize()
239239

240240
await app.shutdown()
241+
242+
243+
@pytest.mark.parametrize("device", [FormedLaunchpadCC26X2R1])
244+
async def test_corrupted_nvram_startup_fixes(device, make_application, caplog):
245+
app, znp_server = make_application(server_cls=device)
246+
247+
# NIB has a weird NWK update ID and is 110 bytes instead of 116??
248+
znp_server._nvram[ExNvIds.LEGACY][OsalNvIds.NIB] = bytes.fromhex(
249+
"100502330f33001e0000000105018f000700020d1e0000000b00000000000000000000005a9608"
250+
"00000800000f0f04000100000001000000001dbb9f21004b120001000000000000000000000000"
251+
"0000000000000000000000000000000000000000000000000f050001780a0100"
252+
)
253+
254+
# One byte too short: should be 20 bytes, not 19
255+
znp_server._nvram[ExNvIds.TCLK_TABLE][0x0000] = bytes.fromhex(
256+
"21000000000000000000000000000000ff0000"
257+
)
258+
259+
await app.startup()
260+
261+
assert "Correcting invalid NIB" in caplog.text
262+
assert "Correcting invalid TCLK_TABLE" in caplog.text
263+
264+
# Correct settings are loaded from NIB. Confirmed with Wireshark.
265+
assert app.nwk_update_id == 0
266+
assert app.channel == 11
267+
assert app.channels == t.Channels.from_channel_list([11])
268+
assert app.extended_pan_id == t.EUI64.convert("00:12:4b:00:21:9f:bb:1d")
269+
assert app.nwk == 0x0000
270+
assert app.pan_id == 0x965A
271+
272+
await app.shutdown()

zigpy_znp/zigbee/application.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import zigpy_znp.config as conf
2727
import zigpy_znp.commands as c
2828
from zigpy_znp.api import ZNP
29+
from zigpy_znp.znp.utils import fix_misaligned_coordinator_nvram
2930
from zigpy_znp.exceptions import CommandNotRecognized, InvalidCommandResponse
3031
from zigpy_znp.types.nvids import OsalNvIds
3132
from zigpy_znp.zigbee.zdo_converters import ZDO_CONVERTERS
@@ -313,7 +314,15 @@ async def _startup(self, auto_form=False, force_form=False, read_only=False):
313314
if self.znp_config[conf.CONF_LED_MODE] is not None:
314315
await self._set_led_mode(led=0xFF, mode=self.znp_config[conf.CONF_LED_MODE])
315316

316-
await self._load_network_info()
317+
try:
318+
await self._load_network_info()
319+
except ValueError:
320+
LOGGER.warning(
321+
"Failed to read network information. Attempting to fix NVRAM.",
322+
exc_info=True,
323+
)
324+
await fix_misaligned_coordinator_nvram(self._znp)
325+
await self._load_network_info()
317326

318327
# Add the coordinator as a zigpy device. We do this up here because
319328
# `self._register_endpoint()` adds endpoints to this device object.

zigpy_znp/znp/utils.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
import logging
12
import dataclasses
23

34
import zigpy_znp.types as t
45
from zigpy_znp.exceptions import CommandNotRecognized
56
from zigpy_znp.types.nvids import ExNvIds, OsalNvIds
67

8+
LOGGER = logging.getLogger(__name__)
9+
710

811
@dataclasses.dataclass(frozen=True)
912
class NetworkInfo:
@@ -22,6 +25,57 @@ def replace(self, **kwargs):
2225
return dataclasses.replace(self, **kwargs)
2326

2427

28+
async def fix_misaligned_coordinator_nvram(znp) -> None:
29+
"""
30+
Some users have coordinators with broken alignment in NVRAM:
31+
32+
"TCLK_TABLE": {
33+
"0x0000": "21000000000000000000000000000000ff0000", <-- missing a byte??
34+
"0x0001": "00000000000000000000000000000000ff000000",
35+
36+
These issues need to be corrected before zigpy-znp can continue working with NVRAM.
37+
"""
38+
39+
if znp.version < 3.30:
40+
return
41+
42+
try:
43+
nib_data = await znp.nvram.osal_read(OsalNvIds.NIB, item_type=t.Bytes)
44+
znp.nvram.deserialize(nib_data, item_type=t.NIB)
45+
except KeyError:
46+
pass
47+
except ValueError:
48+
LOGGER.warning("Correcting invalid NIB alignment: %s", nib_data)
49+
50+
nib = znp.nvram.deserialize(nib_data + b"\xFF" * 6, item_type=t.NIB)
51+
52+
if nib.nwkUpdateId == 0xFF:
53+
nib.nwkUpdateId = 0
54+
55+
await znp.nvram.osal_write(OsalNvIds.NIB, nib, create=True)
56+
57+
offset = 0
58+
59+
async for data in znp.nvram.read_table(
60+
item_id=ExNvIds.TCLK_TABLE,
61+
item_type=t.Bytes,
62+
):
63+
try:
64+
znp.nvram.deserialize(data, item_type=t.TCLKDevEntry)
65+
except ValueError:
66+
LOGGER.warning(
67+
"Correcting invalid TCLK_TABLE[0x%04X] entry: %s", offset, data
68+
)
69+
70+
entry = znp.nvram.deserialize(data + b"\x00", item_type=t.TCLKDevEntry)
71+
72+
await znp.nvram.write(
73+
item_id=ExNvIds.TCLK_TABLE, sub_id=offset, value=entry, create=True
74+
)
75+
76+
offset += 1
77+
78+
2579
async def load_network_info(znp) -> NetworkInfo:
2680
"""
2781
Loads low-level network information from NVRAM.

0 commit comments

Comments
 (0)