Skip to content

Commit 069abd0

Browse files
committed
Move NVRAM migration into ZNP
1 parent 5b21f3a commit 069abd0

File tree

3 files changed

+72
-69
lines changed

3 files changed

+72
-69
lines changed

tests/application/test_nvram_migration.py renamed to tests/api/test_nvram_migration.py

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ async def test_addrmgr_empty_entries(make_connected_znp, device):
4646

4747

4848
@pytest.mark.parametrize("device", [FormedZStack3CC2531])
49-
async def test_addrmgr_rewrite_fix(device, make_application, mocker):
49+
async def test_addrmgr_rewrite_fix(device, make_connected_znp):
5050
# Keep track of reads
5151
addrmgr_reads = []
5252

@@ -62,7 +62,7 @@ async def test_addrmgr_rewrite_fix(device, make_application, mocker):
6262
extAddr=t.EUI64.convert("FF:FF:FF:FF:FF:FF:FF:FF"),
6363
)
6464

65-
app, znp_server = await make_application(server_cls=device)
65+
znp, znp_server = await make_connected_znp(server_cls=device)
6666
znp_server.callback_for_response(
6767
c.SYS.OSALNVReadExt.Req(Id=OsalNvIds.ADDRMGR, Offset=0), addrmgr_reads.append
6868
)
@@ -83,8 +83,7 @@ async def test_addrmgr_rewrite_fix(device, make_application, mocker):
8383
assert old_addrmgr != nvram[OsalNvIds.ADDRMGR]
8484

8585
assert len(addrmgr_reads) == 0
86-
await app.startup()
87-
await app.shutdown()
86+
await znp.migrate_nvram()
8887
assert len(addrmgr_reads) == 2
8988

9089
# Bad entries have been fixed
@@ -96,9 +95,7 @@ async def test_addrmgr_rewrite_fix(device, make_application, mocker):
9695

9796
# Will not be read again
9897
assert len(addrmgr_reads) == 2
99-
await app.connect()
100-
await app.startup()
101-
await app.shutdown()
98+
await znp.migrate_nvram()
10299
assert len(addrmgr_reads) == 2
103100

104101
# Will be migrated again if the migration NVID is deleted
@@ -107,9 +104,7 @@ async def test_addrmgr_rewrite_fix(device, make_application, mocker):
107104
old_addrmgr2 = nvram[OsalNvIds.ADDRMGR]
108105

109106
assert len(addrmgr_reads) == 2
110-
await app.connect()
111-
await app.startup()
112-
await app.shutdown()
107+
await znp.migrate_nvram()
113108
assert len(addrmgr_reads) == 3
114109

115110
# But nothing will change

zigpy_znp/api.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
STARTUP_TIMEOUT = 15
3939
NETWORK_COMMISSIONING_TIMEOUT = 30
4040

41+
NVRAM_MIGRATION_ID = 1
42+
4143

4244
class ZNP:
4345
def __init__(self, config: conf.ConfigType):
@@ -457,6 +459,11 @@ async def write_network_info(
457459
counter_increment=0,
458460
)
459461

462+
# Prevent an unnecessary NVRAM migration from running
463+
await self.nvram.osal_write(
464+
OsalNvIds.ZIGPY_ZNP_MIGRATION_ID, t.uint8_t(NVRAM_MIGRATION_ID), create=True
465+
)
466+
460467
if self.version == 1.2:
461468
await self.nvram.osal_write(
462469
OsalNvIds.HAS_CONFIGURED_ZSTACK1,
@@ -472,6 +479,65 @@ async def write_network_info(
472479

473480
LOGGER.debug("Done!")
474481

482+
async def migrate_nvram(self) -> bool:
483+
"""
484+
Migrates NVRAM entries using the `ZIGPY_ZNP_MIGRATION_ID` NVRAM item.
485+
Returns `True` if a migration was performed, `False` otherwise.
486+
"""
487+
488+
from zigpy_znp.znp import security
489+
490+
try:
491+
migration_id = await self.nvram.osal_read(
492+
OsalNvIds.ZIGPY_ZNP_MIGRATION_ID, item_type=t.uint8_t
493+
)
494+
except KeyError:
495+
migration_id = 0
496+
497+
initial_migration_id = migration_id
498+
499+
# Migration 1: empty `ADDRMGR` entries are version-dependent and were improperly
500+
# written for CC253x devices.
501+
#
502+
# This migration is stateless and can safely be run more than once:
503+
# the only downside is that startup times increase by 10s on newer
504+
# coordinators, which is why the migration ID is persisted.
505+
if migration_id < 1:
506+
try:
507+
entries = await security.read_addr_manager_entries(self)
508+
except KeyError:
509+
pass
510+
else:
511+
fixed_entries = []
512+
513+
for entry in entries:
514+
if entry.extAddr != t.EUI64.convert("FF:FF:FF:FF:FF:FF:FF:FF"):
515+
fixed_entries.append(entry)
516+
elif self.version == 3.30:
517+
fixed_entries.append(const.EMPTY_ADDR_MGR_ENTRY_ZSTACK3)
518+
else:
519+
fixed_entries.append(const.EMPTY_ADDR_MGR_ENTRY_ZSTACK1)
520+
521+
if entries != fixed_entries:
522+
LOGGER.warning(
523+
"Repairing %d invalid empty address manager entries (total %d)",
524+
sum(i != j for i, j in zip(entries, fixed_entries)),
525+
len(entries),
526+
)
527+
await security.write_addr_manager_entries(self, fixed_entries)
528+
529+
migration_id = 1
530+
531+
if initial_migration_id == migration_id:
532+
return False
533+
534+
await self.nvram.osal_write(
535+
OsalNvIds.ZIGPY_ZNP_MIGRATION_ID, t.uint8_t(migration_id), create=True
536+
)
537+
await self.reset()
538+
539+
return True
540+
475541
async def reset(self) -> None:
476542
"""
477543
Performs a soft reset within Z-Stack.

zigpy_znp/zigbee/application.py

Lines changed: 1 addition & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
import zigpy_znp.config as conf
2929
import zigpy_znp.commands as c
3030
from zigpy_znp.api import ZNP
31-
from zigpy_znp.znp import security
3231
from zigpy_znp.utils import combine_concurrent_calls
3332
from zigpy_znp.exceptions import CommandNotRecognized, InvalidCommandResponse
3433
from zigpy_znp.types.nvids import OsalNvIds
@@ -182,7 +181,7 @@ async def write_network_info(
182181

183182
async def start_network(self, *, read_only=False):
184183
if not read_only:
185-
await self._migrate_nvram()
184+
await self._znp.migrate_nvram()
186185
await self._write_stack_settings(reset_if_changed=True)
187186

188187
if self.znp_config[conf.CONF_TX_POWER] is not None:
@@ -779,63 +778,6 @@ async def _set_led_mode(self, *, led: t.uint8_t, mode: c.util.LEDMode) -> None:
779778
except (asyncio.TimeoutError, CommandNotRecognized):
780779
LOGGER.info("This build of Z-Stack does not appear to support LED control")
781780

782-
async def _migrate_nvram(self) -> bool:
783-
"""
784-
Migrates NVRAM entries using the `ZIGPY_ZNP_MIGRATION_ID` NVRAM item.
785-
Returns `True` if a migration was performed, `False` otherwise.
786-
"""
787-
788-
try:
789-
migration_id = await self._znp.nvram.osal_read(
790-
OsalNvIds.ZIGPY_ZNP_MIGRATION_ID, item_type=t.uint8_t
791-
)
792-
except KeyError:
793-
migration_id = 0
794-
795-
initial_migration_id = migration_id
796-
797-
# Migration 1: empty `ADDRMGR` entries are version-dependent and were improperly
798-
# written for CC253x devices.
799-
#
800-
# This migration is stateless and can safely be run more than once:
801-
# the only downside is that startup times increase by 10s on newer
802-
# coordinators, which is why the migration ID is persisted.
803-
if migration_id < 1:
804-
try:
805-
entries = await security.read_addr_manager_entries(self._znp)
806-
except KeyError:
807-
pass
808-
else:
809-
fixed_entries = []
810-
811-
for entry in entries:
812-
if entry.extAddr != t.EUI64.convert("FF:FF:FF:FF:FF:FF:FF:FF"):
813-
fixed_entries.append(entry)
814-
elif self._znp.version == 3.30:
815-
fixed_entries.append(const.EMPTY_ADDR_MGR_ENTRY_ZSTACK3)
816-
else:
817-
fixed_entries.append(const.EMPTY_ADDR_MGR_ENTRY_ZSTACK1)
818-
819-
if entries != fixed_entries:
820-
LOGGER.warning(
821-
"Repairing %d invalid empty address manager entries (total %d)",
822-
sum(i != j for i, j in zip(entries, fixed_entries)),
823-
len(entries),
824-
)
825-
await security.write_addr_manager_entries(self._znp, fixed_entries)
826-
827-
migration_id = 1
828-
829-
if initial_migration_id == migration_id:
830-
return False
831-
832-
await self._znp.nvram.osal_write(
833-
OsalNvIds.ZIGPY_ZNP_MIGRATION_ID, t.uint8_t(migration_id), create=True
834-
)
835-
await self._znp.reset()
836-
837-
return True
838-
839781
async def _write_stack_settings(self, *, reset_if_changed: bool) -> None:
840782
"""
841783
Writes network-independent Z-Stack settings to NVRAM.

0 commit comments

Comments
 (0)