Skip to content

Commit e6d78b2

Browse files
committed
Test and fix from-scratch network formation and startup
1 parent d3e7b32 commit e6d78b2

File tree

6 files changed

+194
-102
lines changed

6 files changed

+194
-102
lines changed

tests/test_api.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -294,11 +294,11 @@ def test_command_deduplication():
294294
partial=True,
295295
Status=c.app_config.BDBCommissioningStatus.InProgress,
296296
Mode=c.app_config.BDBCommissioningMode.NwkFormation,
297-
RemainingModes=c.app_config.BDBRemainingCommissioningModes.InitiatorTl,
297+
RemainingModes=c.app_config.BDBCommissioningMode.InitiatorTouchLink,
298298
),
299299
c.AppConfig.BDBCommissioningNotification.Callback(
300300
partial=True,
301-
RemainingModes=c.app_config.BDBRemainingCommissioningModes.InitiatorTl,
301+
RemainingModes=c.app_config.BDBCommissioningMode.InitiatorTouchLink,
302302
),
303303
]
304304
)
@@ -312,7 +312,7 @@ def test_command_deduplication():
312312
),
313313
c.AppConfig.BDBCommissioningNotification.Callback(
314314
partial=True,
315-
RemainingModes=c.app_config.BDBRemainingCommissioningModes.InitiatorTl,
315+
RemainingModes=c.app_config.BDBCommissioningMode.InitiatorTouchLink,
316316
),
317317
}
318318

tests/test_application.py

Lines changed: 109 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ async def callback(request):
6666
callback.called = True
6767

6868
for response in responses:
69-
await asyncio.sleep(0.1)
69+
await asyncio.sleep(0.01)
7070
LOGGER.debug("Replying to %s with %s", request, response)
7171

7272
try:
@@ -86,7 +86,7 @@ async def callback(request):
8686
callback.call_count += 1
8787

8888
for response in responses:
89-
await asyncio.sleep(0.1)
89+
await asyncio.sleep(0.01)
9090
LOGGER.debug("Replying to %s with %s", request, response)
9191

9292
try:
@@ -159,19 +159,55 @@ def application(znp_server):
159159
],
160160
)
161161

162+
active_eps = [100, 12, 11, 8, 1]
163+
162164
znp_server.reply_to(
163165
request=c.ZDO.ActiveEpReq.Req(DstAddr=0x0000, NWKAddrOfInterest=0x0000),
164166
responses=[
165167
c.ZDO.ActiveEpReq.Rsp(Status=t.Status.Success),
166168
c.ZDO.ActiveEpRsp.Callback(
167-
Src=0x0000, Status=t.ZDOStatus.SUCCESS, NWK=0x0000, ActiveEndpoints=[]
169+
Src=0x0000,
170+
Status=t.ZDOStatus.SUCCESS,
171+
NWK=0x0000,
172+
ActiveEndpoints=active_eps,
168173
),
169174
],
170175
)
171176

172177
znp_server.reply_to(
173-
request=c.AF.Register.Req(partial=True),
174-
responses=[c.AF.Register.Rsp(Status=t.Status.Success)],
178+
request=c.ZDO.ActiveEpReq.Req(DstAddr=0x0000, NWKAddrOfInterest=0x0000),
179+
responses=[
180+
c.ZDO.ActiveEpReq.Rsp(Status=t.Status.Success),
181+
c.ZDO.ActiveEpRsp.Callback(
182+
Src=0x0000,
183+
Status=t.ZDOStatus.SUCCESS,
184+
NWK=0x0000,
185+
ActiveEndpoints=active_eps,
186+
),
187+
],
188+
)
189+
190+
def on_endpoint_registration(req):
191+
assert req.Endpoint not in active_eps
192+
193+
active_eps.append(req.Endpoint)
194+
active_eps.sort(reverse=True)
195+
196+
return c.AF.Register.Rsp(Status=t.Status.Success)
197+
198+
znp_server.reply_to(
199+
request=c.AF.Register.Req(partial=True), responses=[on_endpoint_registration],
200+
)
201+
202+
def on_endpoint_deletion(req):
203+
assert req.Endpoint in active_eps
204+
205+
active_eps.remove(req.Endpoint)
206+
207+
return c.AF.Delete.Rsp(Status=t.Status.Success)
208+
209+
znp_server.reply_to(
210+
request=c.AF.Delete.Req(partial=True), responses=[on_endpoint_deletion],
175211
)
176212

177213
znp_server.reply_to(
@@ -183,12 +219,7 @@ def application(znp_server):
183219
c.AppConfig.BDBCommissioningNotification.Callback(
184220
Status=c.app_config.BDBCommissioningStatus.Success,
185221
Mode=c.app_config.BDBCommissioningMode.NwkSteering,
186-
RemainingModes=c.app_config.BDBRemainingCommissioningModes.NONE,
187-
),
188-
c.AppConfig.BDBCommissioningNotification.Callback(
189-
Status=c.app_config.BDBCommissioningStatus.NoNetwork,
190-
Mode=c.app_config.BDBCommissioningMode.NwkSteering,
191-
RemainingModes=c.app_config.BDBRemainingCommissioningModes.NONE,
222+
RemainingModes=c.app_config.BDBCommissioningMode.NONE,
192223
),
193224
],
194225
)
@@ -206,30 +237,71 @@ def application(znp_server):
206237
responses=[c.Sys.OSALNVWrite.Rsp(Status=t.Status.Success)],
207238
)
208239

240+
znp_server.reply_to(
241+
request=c.Sys.OSALNVRead.Req(Id=NwkNvIds.HAS_CONFIGURED_ZSTACK3, Offset=0),
242+
responses=[c.Sys.OSALNVRead.Rsp(Status=t.Status.Success, Value=b"\x55")],
243+
)
244+
245+
znp_server.reply_to(
246+
request=c.Util.GetDeviceInfo.Req(),
247+
responses=[
248+
c.Util.GetDeviceInfo.Rsp(
249+
Status=t.Status.Success,
250+
IEEE=t.EUI64([0x00, 0x12, 0x4B, 0x00, 0x1C, 0xAA, 0xAC, 0x5C]),
251+
NWK=t.NWK(0xFFFE),
252+
DeviceType=t.DeviceLogicalType(7),
253+
DeviceState=t.DeviceState.InitializedNotStarted,
254+
AssociatedDevices=[],
255+
)
256+
],
257+
)
258+
259+
znp_server.reply_to(
260+
request=c.ZDO.StartupFromApp.Req(partial=True),
261+
responses=[
262+
c.ZDO.StartupFromApp.Rsp(State=c.zdo.StartupState.RestoredNetworkState),
263+
c.ZDO.StateChangeInd.Callback(State=t.DeviceState.StartedAsCoordinator),
264+
],
265+
)
266+
209267
return app, znp_server
210268

211269

212270
@pytest_mark_asyncio_timeout(seconds=5)
213-
async def test_application_startup(application):
271+
async def test_application_startup_endpoints(application):
214272
app, znp_server = application
215273

216-
num_endpoints = 5
217274
endpoints = []
275+
znp_server.callback_for_response(c.AF.Register.Req(partial=True), endpoints.append)
218276

219-
def register_endpoint(request):
220-
nonlocal num_endpoints
221-
num_endpoints -= 1
277+
await app.startup(auto_form=False)
222278

223-
endpoints.append(request)
279+
assert len(endpoints) == 5
224280

225-
if num_endpoints < 0:
226-
raise RuntimeError("Too many endpoints registered")
227281

228-
znp_server.callback_for_response(c.AF.Register.Req(partial=True), register_endpoint)
282+
@pytest_mark_asyncio_timeout(seconds=5)
283+
async def test_application_startup_failure(application):
284+
app, znp_server = application
229285

230-
await app.startup(auto_form=False)
286+
# Prevent the fixture's default response
287+
znp_server._response_listeners[c.Sys.OSALNVRead.Req.header].clear()
231288

232-
assert len(endpoints) == 5
289+
znp_server.reply_once_to(
290+
request=c.Sys.OSALNVRead.Req(Id=NwkNvIds.HAS_CONFIGURED_ZSTACK3, Offset=0),
291+
responses=[c.Sys.OSALNVRead.Rsp(Status=t.Status.InvalidParameter, Value=b"")],
292+
)
293+
294+
# We cannot start the application if Z-Stack is not configured and without auto_form
295+
with pytest.raises(RuntimeError):
296+
await app.startup(auto_form=False)
297+
298+
znp_server.reply_once_to(
299+
request=c.Sys.OSALNVRead.Req(Id=NwkNvIds.HAS_CONFIGURED_ZSTACK3, Offset=0),
300+
responses=[c.Sys.OSALNVRead.Rsp(Status=t.Status.Success, Value=b"\x00")],
301+
)
302+
303+
with pytest.raises(RuntimeError):
304+
await app.startup(auto_form=False)
233305

234306

235307
@pytest_mark_asyncio_timeout(seconds=3)
@@ -858,17 +930,9 @@ async def test_force_remove(application, mocker):
858930
async def test_auto_form_unnecessary(application, mocker):
859931
app, znp_server = application
860932

861-
# b"\x55" means that Z-Stack is already configured?
862-
read_zstack_configured = znp_server.reply_once_to(
863-
request=c.Sys.OSALNVRead.Req(Id=NwkNvIds.HAS_CONFIGURED_ZSTACK3, Offset=0),
864-
responses=[c.Sys.OSALNVRead.Rsp(Status=t.Status.Success, Value=b"\x55")],
865-
)
866-
867933
mocker.patch.object(app, "form_network")
868934

869935
await app.startup(auto_form=True)
870-
871-
await read_zstack_configured
872936
assert app.form_network.call_count == 0
873937

874938

@@ -881,22 +945,31 @@ async def test_auto_form_necessary(application, mocker):
881945
mocker.patch.object(app, "_reset", new=CoroutineMock())
882946

883947
def nvram_writer(req):
884-
if req.Id in nvram:
885-
raise ValueError("Unexpected overwrite")
886-
887948
nvram[req.Id] = req.Value
888949

889950
return c.Sys.OSALNVWrite.Rsp(Status=t.Status.Success)
890951

952+
def nvram_init(req):
953+
nvram[req.Id] = req.Value
954+
955+
return c.Sys.OSALNVItemInit.Rsp(Status=t.Status.Success)
956+
957+
# Prevent the fixture's default response
958+
znp_server._response_listeners[c.Sys.OSALNVRead.Req.header].clear()
959+
891960
read_zstack_configured = znp_server.reply_once_to(
892961
request=c.Sys.OSALNVRead.Req(Id=NwkNvIds.HAS_CONFIGURED_ZSTACK3, Offset=0),
893-
responses=[c.Sys.OSALNVRead.Rsp(Status=t.Status.Success, Value=b"\x00")],
962+
responses=[c.Sys.OSALNVRead.Rsp(Status=t.Status.InvalidParameter, Value=b"")],
894963
)
895964

896965
znp_server.reply_to(
897966
request=c.Sys.OSALNVWrite.Req(Offset=0, partial=True), responses=[nvram_writer]
898967
)
899968

969+
znp_server.reply_to(
970+
request=c.Sys.OSALNVItemInit.Req(partial=True), responses=[nvram_init]
971+
)
972+
900973
znp_server.reply_to(
901974
request=c.AppConfig.BDBStartCommissioning.Req(
902975
Mode=c.app_config.BDBCommissioningMode.NwkFormation
@@ -919,8 +992,9 @@ def nvram_writer(req):
919992
await read_zstack_configured
920993

921994
assert app.update_network.call_count == 1
922-
assert app._reset.call_count == 2
995+
assert app._reset.call_count == 1
923996

997+
assert nvram[NwkNvIds.HAS_CONFIGURED_ZSTACK3] == b"\x55"
924998
assert nvram[NwkNvIds.STARTUP_OPTION] == t.StartupOptions.ClearState.serialize()
925999
assert nvram[NwkNvIds.LOGICAL_TYPE] == t.DeviceLogicalType.Coordinator.serialize()
9261000
assert nvram[NwkNvIds.ZDO_DIRECT_CB] == t.Bool(True).serialize()

zigpy_znp/commands/app_config.py

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -49,23 +49,14 @@ class BDBCommissioningStatus(t.enum_uint8):
4949
Failure = 0x0E
5050

5151

52-
class BDBCommissioningMode(t.enum_uint8):
53-
Initialization = 0x00
54-
NwkSteering = 0x01
55-
NwkFormation = 0x02
56-
FindingBinding = 0x03
57-
Touchlink = 0x04
58-
ParentLost = 0x05
59-
60-
61-
class BDBRemainingCommissioningModes(t.enum_flag_uint8):
52+
class BDBCommissioningMode(t.enum_flag_uint8):
6253
NONE = 0
6354

64-
InitiatorTl = 1 << 0
55+
InitiatorTouchLink = 1 << 0
6556
NwkSteering = 1 << 1
6657
NwkFormation = 1 << 2
6758
FindingBinding = 1 << 3
68-
Initialization = 1 << 4
59+
Touchlink = 1 << 4
6960
ParentLost = 1 << 5
7061

7162

@@ -245,7 +236,7 @@ class AppConfig(t.CommandsBase, subsystem=t.Subsystem.APPConfig):
245236
),
246237
t.Param(
247238
"RemainingModes",
248-
BDBRemainingCommissioningModes,
239+
BDBCommissioningMode,
249240
(
250241
"Bitmask of the remaining commissioning modes after "
251242
"this notification"

zigpy_znp/commands/sys.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class Sys(t.CommandsBase, subsystem=t.Subsystem.SYS):
1616
(
1717
t.Param(
1818
"Type",
19-
t.uint8_t,
19+
t.ResetType,
2020
(
2121
"This command will reset the device by using a hardware reset "
2222
"(i.e. watchdog reset) if ‘Type’ is zero. Otherwise a soft "

zigpy_znp/types/named.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -205,9 +205,9 @@ class KeySource(basic.FixedList, item_type=basic.uint8_t, length=8):
205205

206206

207207
class StartupOptions(basic.enum_flag_uint8):
208-
ClearConfig = 1 << 1
209-
ClearState = 1 << 2
210-
AutoStart = 1 << 3
208+
ClearConfig = 1 << 0
209+
ClearState = 1 << 1
210+
AutoStart = 1 << 2
211211

212212

213213
class DeviceLogicalType(basic.enum_flag_uint8):

0 commit comments

Comments
 (0)