zigpy-deconz 0.25.2__tar.gz → 0.25.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. {zigpy_deconz-0.25.2/zigpy_deconz.egg-info → zigpy_deconz-0.25.4}/PKG-INFO +1 -1
  2. {zigpy_deconz-0.25.2 → zigpy_deconz-0.25.4}/tests/test_application.py +12 -2
  3. {zigpy_deconz-0.25.2 → zigpy_deconz-0.25.4}/tests/test_network_state.py +111 -6
  4. {zigpy_deconz-0.25.2 → zigpy_deconz-0.25.4}/zigpy_deconz/zigbee/application.py +55 -10
  5. {zigpy_deconz-0.25.2 → zigpy_deconz-0.25.4/zigpy_deconz.egg-info}/PKG-INFO +1 -1
  6. {zigpy_deconz-0.25.2 → zigpy_deconz-0.25.4}/COPYING +0 -0
  7. {zigpy_deconz-0.25.2 → zigpy_deconz-0.25.4}/LICENSE +0 -0
  8. {zigpy_deconz-0.25.2 → zigpy_deconz-0.25.4}/README.md +0 -0
  9. {zigpy_deconz-0.25.2 → zigpy_deconz-0.25.4}/pyproject.toml +0 -0
  10. {zigpy_deconz-0.25.2 → zigpy_deconz-0.25.4}/setup.cfg +0 -0
  11. {zigpy_deconz-0.25.2 → zigpy_deconz-0.25.4}/setup.py +0 -0
  12. {zigpy_deconz-0.25.2 → zigpy_deconz-0.25.4}/tests/test_api.py +0 -0
  13. {zigpy_deconz-0.25.2 → zigpy_deconz-0.25.4}/tests/test_exception.py +0 -0
  14. {zigpy_deconz-0.25.2 → zigpy_deconz-0.25.4}/tests/test_send_receive.py +0 -0
  15. {zigpy_deconz-0.25.2 → zigpy_deconz-0.25.4}/tests/test_types.py +0 -0
  16. {zigpy_deconz-0.25.2 → zigpy_deconz-0.25.4}/tests/test_uart.py +0 -0
  17. {zigpy_deconz-0.25.2 → zigpy_deconz-0.25.4}/tests/test_utils.py +0 -0
  18. {zigpy_deconz-0.25.2 → zigpy_deconz-0.25.4}/zigpy_deconz/__init__.py +0 -0
  19. {zigpy_deconz-0.25.2 → zigpy_deconz-0.25.4}/zigpy_deconz/api.py +0 -0
  20. {zigpy_deconz-0.25.2 → zigpy_deconz-0.25.4}/zigpy_deconz/config.py +0 -0
  21. {zigpy_deconz-0.25.2 → zigpy_deconz-0.25.4}/zigpy_deconz/exception.py +0 -0
  22. {zigpy_deconz-0.25.2 → zigpy_deconz-0.25.4}/zigpy_deconz/types.py +0 -0
  23. {zigpy_deconz-0.25.2 → zigpy_deconz-0.25.4}/zigpy_deconz/uart.py +0 -0
  24. {zigpy_deconz-0.25.2 → zigpy_deconz-0.25.4}/zigpy_deconz/utils.py +0 -0
  25. {zigpy_deconz-0.25.2 → zigpy_deconz-0.25.4}/zigpy_deconz/zigbee/__init__.py +0 -0
  26. {zigpy_deconz-0.25.2 → zigpy_deconz-0.25.4}/zigpy_deconz.egg-info/SOURCES.txt +0 -0
  27. {zigpy_deconz-0.25.2 → zigpy_deconz-0.25.4}/zigpy_deconz.egg-info/dependency_links.txt +0 -0
  28. {zigpy_deconz-0.25.2 → zigpy_deconz-0.25.4}/zigpy_deconz.egg-info/requires.txt +0 -0
  29. {zigpy_deconz-0.25.2 → zigpy_deconz-0.25.4}/zigpy_deconz.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: zigpy-deconz
3
- Version: 0.25.2
3
+ Version: 0.25.4
4
4
  Summary: A library which communicates with Deconz radios for zigpy
5
5
  Author-email: Daniel Schmidt <schmidt.d@aon.at>
6
6
  License: GPL-3.0
@@ -553,10 +553,20 @@ async def test_add_endpoint_no_unnecessary_writes(app):
553
553
 
554
554
 
555
555
  async def test_reset_network_info(app):
556
- app.form_network = AsyncMock()
556
+ app.write_network_info = AsyncMock()
557
557
  await app.reset_network_info()
558
558
 
559
- app.form_network.assert_called_once()
559
+ assert len(app.write_network_info.mock_calls) == 1
560
+ network_info = app.write_network_info.mock_calls[0].kwargs["network_info"]
561
+ node_info = app.write_network_info.mock_calls[0].kwargs["node_info"]
562
+
563
+ # Verify invalid network settings are written
564
+ assert network_info.pan_id == 0xFFFF
565
+ assert network_info.extended_pan_id == zigpy.types.EUI64.convert(
566
+ "FF:FF:FF:FF:FF:FF:FF:FF"
567
+ )
568
+ assert network_info.channel is None
569
+ assert node_info.nwk == 0xFFFF
560
570
 
561
571
 
562
572
  async def test_energy_scan_conbee_2(app):
@@ -1,9 +1,10 @@
1
1
  """Test `load_network_info` and `write_network_info` methods."""
2
2
 
3
3
  import importlib.metadata
4
+ from unittest.mock import ANY, call
4
5
 
5
6
  import pytest
6
- from zigpy.exceptions import ControllerException, NetworkNotFormed
7
+ from zigpy.exceptions import CannotWriteNetworkSettings, NetworkNotFormed
7
8
  import zigpy.state as app_state
8
9
  import zigpy.types as t
9
10
  import zigpy.zdo.types as zdo_t
@@ -11,6 +12,7 @@ import zigpy.zdo.types as zdo_t
11
12
  import zigpy_deconz
12
13
  import zigpy_deconz.api
13
14
  import zigpy_deconz.exception
15
+ import zigpy_deconz.types
14
16
  import zigpy_deconz.zigbee.application as application
15
17
 
16
18
  from tests.async_mock import AsyncMock, patch
@@ -70,28 +72,55 @@ def network_info(node_info):
70
72
  @patch.object(application, "CHANGE_NETWORK_POLL_TIME", 0.001)
71
73
  @patch.object(application, "CHANGE_NETWORK_STATE_DELAY", 0.001)
72
74
  @pytest.mark.parametrize(
73
- "channel_mask, channel, security_level, fw_supports_fc, logical_type",
75
+ (
76
+ "channel_mask",
77
+ "channel",
78
+ "security_level",
79
+ "fw_supports_fc",
80
+ "logical_type",
81
+ "tx_counter",
82
+ "should_error",
83
+ ),
74
84
  [
85
+ # FW supports frame counter
75
86
  (
76
87
  t.Channels.from_channel_list([15]),
77
88
  15,
78
89
  0,
79
90
  True,
80
91
  zdo_t.LogicalType.Coordinator,
92
+ 39009277,
93
+ False,
94
+ ),
95
+ # FW doesn't support but we're writing blank settings (tx_counter == 0)
96
+ (
97
+ t.Channels.from_channel_list([15]),
98
+ 15,
99
+ 5,
100
+ False,
101
+ zdo_t.LogicalType.Coordinator,
102
+ 0,
103
+ False,
81
104
  ),
105
+ # FW doesn't support and we're writing real settings (should error)
82
106
  (
83
107
  t.Channels.from_channel_list([15]),
84
108
  15,
85
109
  0,
86
110
  False,
87
111
  zdo_t.LogicalType.Coordinator,
112
+ 39009277,
113
+ True,
88
114
  ),
115
+ # Additional test cases with FW support
89
116
  (
90
117
  t.Channels.from_channel_list([15, 20]),
91
118
  15,
92
119
  5,
93
120
  True,
94
121
  zdo_t.LogicalType.Coordinator,
122
+ 39009277,
123
+ False,
95
124
  ),
96
125
  (
97
126
  t.Channels.from_channel_list([15, 20, 25]),
@@ -99,8 +128,18 @@ def network_info(node_info):
99
128
  5,
100
129
  True,
101
130
  zdo_t.LogicalType.Router,
131
+ 39009277,
132
+ False,
133
+ ),
134
+ (
135
+ None,
136
+ 15,
137
+ 5,
138
+ True,
139
+ zdo_t.LogicalType.Coordinator,
140
+ 39009277,
141
+ False,
102
142
  ),
103
- (None, 15, 5, True, zdo_t.LogicalType.Coordinator),
104
143
  ],
105
144
  )
106
145
  async def test_write_network_info(
@@ -112,6 +151,8 @@ async def test_write_network_info(
112
151
  security_level,
113
152
  fw_supports_fc,
114
153
  logical_type,
154
+ tx_counter,
155
+ should_error,
115
156
  ):
116
157
  """Test that network info is correctly written."""
117
158
 
@@ -137,13 +178,14 @@ async def test_write_network_info(
137
178
  channel=channel,
138
179
  channel_mask=channel_mask,
139
180
  security_level=security_level,
181
+ network_key=network_info.network_key.replace(tx_counter=tx_counter),
140
182
  )
141
183
 
142
184
  node_info = node_info.replace(logical_type=logical_type)
143
185
 
144
- if not fw_supports_fc:
186
+ if should_error:
145
187
  with pytest.raises(
146
- ControllerException,
188
+ CannotWriteNetworkSettings,
147
189
  match=(
148
190
  "Please upgrade your adapter firmware. Firmware version 0x26580700 does"
149
191
  " not support writing the network key frame counter, which is required"
@@ -166,7 +208,9 @@ async def test_write_network_info(
166
208
  for call in app._api.write_parameter.await_args_list
167
209
  }
168
210
 
169
- assert params["nwk_frame_counter"] == (network_info.network_key.tx_counter,)
211
+ # Only check frame counter if firmware supports it
212
+ if fw_supports_fc:
213
+ assert params["nwk_frame_counter"] == (network_info.network_key.tx_counter,)
170
214
 
171
215
  if node_info.logical_type == zdo_t.LogicalType.Coordinator:
172
216
  assert params["aps_designed_coordinator"] == (1,)
@@ -328,3 +372,64 @@ async def test_load_network_info(
328
372
  assert app.state.network_info == network_info
329
373
 
330
374
  assert app.state.node_info == node_info.replace(**node_state_changes)
375
+
376
+
377
+ @patch.object(application, "CHANGE_NETWORK_POLL_TIME", 0.001)
378
+ @patch.object(application, "CHANGE_NETWORK_STATE_DELAY", 0.001)
379
+ async def test_form_network_fast_without_frame_counter_support(app): # noqa: F811
380
+ """Test that form_network(fast=True) works when FW doesn't support frame counter."""
381
+
382
+ async def write_parameter(param, *args):
383
+ if param == zigpy_deconz.api.NetworkParameter.nwk_frame_counter:
384
+ raise zigpy_deconz.exception.CommandError(
385
+ "Command is unsupported",
386
+ status=zigpy_deconz.api.Status.UNSUPPORTED,
387
+ command=None,
388
+ )
389
+
390
+ app._change_network_state = AsyncMock()
391
+ app._api.write_parameter = AsyncMock(side_effect=write_parameter)
392
+ app.backups = AsyncMock()
393
+ app.backups.restore_backup = AsyncMock()
394
+
395
+ # This should not raise an error because fast=True sets form_quickly
396
+ await app.form_network(fast=True)
397
+
398
+ # Verify that restore_backup was called with create_new=False (due to fast=True)
399
+ assert app.backups.restore_backup.mock_calls == [
400
+ call(backup=ANY, counter_increment=0, allow_incomplete=True, create_new=False)
401
+ ]
402
+
403
+
404
+ @patch.object(application, "CHANGE_NETWORK_POLL_TIME", 0.001)
405
+ @patch.object(application, "CHANGE_NETWORK_STATE_DELAY", 0.001)
406
+ async def test_reset_network_info_without_frame_counter_support(app): # noqa: F811
407
+ """Test that reset_network_info works even when FW doesn't support frame counter."""
408
+
409
+ async def write_parameter(param, *args):
410
+ if param == zigpy_deconz.api.NetworkParameter.nwk_frame_counter:
411
+ raise zigpy_deconz.exception.CommandError(
412
+ "Command is unsupported",
413
+ status=zigpy_deconz.api.Status.UNSUPPORTED,
414
+ command=None,
415
+ )
416
+
417
+ app._change_network_state = AsyncMock()
418
+ app._api.write_parameter = AsyncMock(side_effect=write_parameter)
419
+ app._api.read_parameter = AsyncMock(
420
+ return_value=t.EUI64.convert("00:11:22:33:44:55:66:77")
421
+ )
422
+
423
+ # Should not raise an error despite frame counter not being supported
424
+ await app.reset_network_info()
425
+
426
+ # Verify that write_parameter was called (including the frame counter attempt)
427
+ assert any(
428
+ mock_call.args[0] == zigpy_deconz.api.NetworkParameter.nwk_frame_counter
429
+ for mock_call in app._api.write_parameter.mock_calls
430
+ )
431
+
432
+ # Verify network state changes were awaited
433
+ assert app._change_network_state.mock_calls == [
434
+ call(zigpy_deconz.api.NetworkState.OFFLINE)
435
+ ]
@@ -180,7 +180,29 @@ class ControllerApplication(zigpy.application.ControllerApplication):
180
180
 
181
181
  async def reset_network_info(self):
182
182
  # TODO: There does not appear to be a way to factory reset a Conbee
183
- await self.form_network()
183
+ await self.write_network_info(
184
+ network_info=zigpy.state.NetworkInfo(
185
+ pan_id=0xFFFF,
186
+ extended_pan_id=zigpy.types.EUI64.convert("FF:FF:FF:FF:FF:FF:FF:FF"),
187
+ channel=None,
188
+ channel_mask=zigpy.types.Channels(0),
189
+ nwk_update_id=0,
190
+ network_key=zigpy.state.Key(
191
+ key=zigpy.types.KeyData.convert(
192
+ "FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF"
193
+ )
194
+ ),
195
+ tc_link_key=zigpy.state.Key(
196
+ key=zigpy.types.KeyData.convert(b"ZigBeeAlliance09".hex())
197
+ ),
198
+ security_level=0x05,
199
+ ),
200
+ node_info=zigpy.state.NodeInfo(
201
+ logical_type=zdo_t.LogicalType.Coordinator,
202
+ ieee=zigpy.types.EUI64.UNKNOWN,
203
+ nwk=0xFFFF,
204
+ ),
205
+ )
184
206
 
185
207
  async def write_network_info(self, *, network_info, node_info):
186
208
  try:
@@ -189,12 +211,19 @@ class ControllerApplication(zigpy.application.ControllerApplication):
189
211
  )
190
212
  except zigpy_deconz.exception.CommandError as ex:
191
213
  assert ex.status == Status.UNSUPPORTED
192
- fw_version = f"{int(self._api.firmware_version):#010x}"
193
- raise zigpy.exceptions.ControllerException(
194
- f"Please upgrade your adapter firmware. Firmware version {fw_version}"
195
- f" does not support writing the network key frame counter, which is"
196
- f" required for migration to succeed."
197
- )
214
+
215
+ # If we are resetting the adapter or forming a brand new network, we can
216
+ # skip this check
217
+ if not (
218
+ network_info.stack_specific.get("form_quickly", False)
219
+ or network_info.network_key.tx_counter == 0
220
+ ):
221
+ fw_version = f"{int(self._api.firmware_version):#010x}"
222
+ raise zigpy.exceptions.CannotWriteNetworkSettings(
223
+ f"Please upgrade your adapter firmware. Firmware version"
224
+ f" {fw_version} does not support writing the network key frame"
225
+ f" counter, which is required for migration to succeed."
226
+ )
198
227
 
199
228
  if node_info.logical_type == zdo_t.LogicalType.Coordinator:
200
229
  await self._api.write_parameter(
@@ -284,6 +313,14 @@ class ControllerApplication(zigpy.application.ControllerApplication):
284
313
  # Note: Changed network configuration parameters become only affective after
285
314
  # sending a Leave Network Request followed by a Create or Join Network Request
286
315
  await self._change_network_state(NetworkState.OFFLINE)
316
+
317
+ if (
318
+ network_info.pan_id == 0xFFFF
319
+ or network_info.channel_mask == zigpy.types.Channels(0)
320
+ ):
321
+ # Network is being reset, it will never enter the CONNECTED state
322
+ return
323
+
287
324
  await asyncio.sleep(CHANGE_NETWORK_STATE_DELAY)
288
325
  await self._change_network_state(NetworkState.CONNECTED)
289
326
 
@@ -334,7 +371,7 @@ class ControllerApplication(zigpy.application.ControllerApplication):
334
371
  NetworkParameter.aps_extended_panid
335
372
  )
336
373
 
337
- if network_info.extended_pan_id == zigpy.types.EUI64.convert(
374
+ if network_info.extended_pan_id == zigpy.types.ExtendedPanId.convert(
338
375
  "00:00:00:00:00:00:00:00"
339
376
  ):
340
377
  network_info.extended_pan_id = await self._api.read_parameter(
@@ -351,8 +388,16 @@ class ControllerApplication(zigpy.application.ControllerApplication):
351
388
  NetworkParameter.nwk_update_id
352
389
  )
353
390
 
354
- if network_info.channel == 0:
355
- raise NetworkNotFormed("Network channel is zero")
391
+ if (
392
+ node_info.nwk == 0xFFFF
393
+ or network_info.pan_id == 0xFFFF
394
+ or (
395
+ network_info.extended_pan_id
396
+ == zigpy.types.ExtendedPanId.convert("FF:FF:FF:FF:FF:FF:FF:FF")
397
+ )
398
+ or network_info.channel == 0
399
+ ):
400
+ raise NetworkNotFormed("Network is not formed")
356
401
 
357
402
  indexed_key = await self._api.read_parameter(NetworkParameter.network_key, 0)
358
403
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: zigpy-deconz
3
- Version: 0.25.2
3
+ Version: 0.25.4
4
4
  Summary: A library which communicates with Deconz radios for zigpy
5
5
  Author-email: Daniel Schmidt <schmidt.d@aon.at>
6
6
  License: GPL-3.0
File without changes
File without changes
File without changes
File without changes
File without changes