Skip to content

Commit 0921561

Browse files
committed
WIP
1 parent dfa2196 commit 0921561

File tree

2 files changed

+102
-71
lines changed

2 files changed

+102
-71
lines changed

tests/test_application.py

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -259,15 +259,15 @@ async def mock_at_command(cmd, *args):
259259
await app.form_network()
260260
assert app._api._at_command.call_count >= 1
261261
assert app._api._queued_at.call_count >= 7
262-
assert app._nwk == 0x0000
262+
assert app.state.node_info.nwk == 0x0000
263263

264264
app._api._at_command.reset_mock()
265265
app._api._queued_at.reset_mock()
266266
legacy_module = True
267267
await app.form_network()
268268
assert app._api._at_command.call_count >= 1
269269
assert app._api._queued_at.call_count >= 7
270-
assert app._nwk == 0x0000
270+
assert app.state.node_info.nwk == 0x0000
271271

272272

273273
async def _test_startup(
@@ -282,7 +282,7 @@ async def _test_startup(
282282
legacy_module=False,
283283
):
284284
ai_tries = 5
285-
app._nwk = mock.sentinel.nwk
285+
app.state.node_info.nwk = mock.sentinel.nwk
286286

287287
async def _at_command_mock(cmd, *args):
288288
nonlocal ai_tries
@@ -325,53 +325,53 @@ async def init_api_mode_mock():
325325
async def test_startup_ai(app):
326326
auto_form = True
327327
await _test_startup(app, 0x00, auto_form)
328-
assert app._nwk == 0x0000
329-
assert app._ieee == t.EUI64(range(1, 9))
328+
assert app.state.node_info.nwk == 0x0000
329+
assert app.state.node_info.ieee == t.EUI64(range(1, 9))
330330
assert app.form_network.call_count == 0
331331

332332
auto_form = False
333333
await _test_startup(app, 0x00, auto_form)
334-
assert app._nwk == 0x0000
335-
assert app._ieee == t.EUI64(range(1, 9))
334+
assert app.state.node_info.nwk == 0x0000
335+
assert app.state.node_info.ieee == t.EUI64(range(1, 9))
336336
assert app.form_network.call_count == 0
337337

338338
auto_form = True
339339
await _test_startup(app, 0x06, auto_form)
340-
assert app._nwk == 0xFFFE
341-
assert app._ieee == t.EUI64(range(1, 9))
340+
assert app.state.node_info.nwk == 0xFFFE
341+
assert app.state.node_info.ieee == t.EUI64(range(1, 9))
342342
assert app.form_network.call_count == 1
343343

344344
auto_form = False
345345
await _test_startup(app, 0x06, auto_form)
346-
assert app._nwk == 0xFFFE
347-
assert app._ieee == t.EUI64(range(1, 9))
346+
assert app.state.node_info.nwk == 0xFFFE
347+
assert app.state.node_info.ieee == t.EUI64(range(1, 9))
348348
assert app.form_network.call_count == 0
349349

350350
auto_form = True
351351
await _test_startup(app, 0x00, auto_form, zs=1)
352-
assert app._nwk == 0x0000
353-
assert app._ieee == t.EUI64(range(1, 9))
352+
assert app.state.node_info.nwk == 0x0000
353+
assert app.state.node_info.ieee == t.EUI64(range(1, 9))
354354
assert app.form_network.call_count == 1
355355

356356
auto_form = False
357357
await _test_startup(app, 0x06, auto_form, legacy_module=True)
358-
assert app._nwk == 0xFFFE
359-
assert app._ieee == t.EUI64(range(1, 9))
358+
assert app.state.node_info.nwk == 0xFFFE
359+
assert app.state.node_info.ieee == t.EUI64(range(1, 9))
360360
assert app.form_network.call_count == 0
361361

362362
auto_form = True
363363
await _test_startup(app, 0x00, auto_form, zs=1, legacy_module=True)
364-
assert app._nwk == 0x0000
365-
assert app._ieee == t.EUI64(range(1, 9))
364+
assert app.state.node_info.nwk == 0x0000
365+
assert app.state.node_info.ieee == t.EUI64(range(1, 9))
366366
assert app.form_network.call_count == 1
367367

368368

369369
@pytest.mark.asyncio
370370
async def test_startup_no_api_mode(app):
371371
auto_form = True
372372
await _test_startup(app, 0x00, auto_form, api_mode=False)
373-
assert app._nwk == 0x0000
374-
assert app._ieee == t.EUI64(range(1, 9))
373+
assert app.state.node_info.nwk == 0x0000
374+
assert app.state.node_info.ieee == t.EUI64(range(1, 9))
375375
assert app.form_network.call_count == 0
376376
assert app._api.init_api_mode.call_count == 1
377377
assert app._api._at_command.call_count >= 16
@@ -381,8 +381,8 @@ async def test_startup_no_api_mode(app):
381381
async def test_startup_api_mode_config_fails(app):
382382
auto_form = True
383383
await _test_startup(app, 0x00, auto_form, api_mode=False, api_config_succeeds=False)
384-
assert app._nwk == mock.sentinel.nwk
385-
assert app._ieee is None
384+
assert app.state.node_info.nwk == mock.sentinel.nwk
385+
assert app.state.node_info.ieee is None
386386
assert app.form_network.call_count == 0
387387
assert app._api.init_api_mode.call_count == 1
388388
assert app._api._at_command.call_count == 1

zigpy_xbee/zigbee/application.py

Lines changed: 81 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import zigpy.types
1313
import zigpy.util
1414
from zigpy.zcl.clusters.general import Groups
15-
from zigpy.zdo.types import NodeDescriptor, ZDOCmd
15+
from zigpy.zdo.types import LogicalType, NodeDescriptor, ZDOCmd
1616

1717
import zigpy_xbee.api
1818
from zigpy_xbee.config import CONF_DEVICE, CONFIG_SCHEMA, SCHEMA_DEVICE
@@ -40,42 +40,32 @@ class ControllerApplication(zigpy.application.ControllerApplication):
4040
def __init__(self, config: Dict[str, Any]):
4141
super().__init__(config=zigpy.config.ZIGPY_SCHEMA(config))
4242
self._api: Optional[zigpy_xbee.api.XBee] = None
43-
self._nwk = 0
4443

45-
async def shutdown(self):
44+
async def disconnect(self):
4645
"""Shutdown application."""
4746
if self._api:
4847
self._api.close()
4948

50-
async def startup(self, auto_form=False):
51-
"""Perform a complete application startup"""
49+
async def connect(self):
5250
self._api = await zigpy_xbee.api.XBee.new(self, self._config[CONF_DEVICE])
5351
try:
5452
# Ensure we have escaped commands
5553
await self._api._at_command("AP", 2)
5654
except asyncio.TimeoutError:
5755
LOGGER.debug("No response to API frame. Configure API mode")
5856
if not await self._api.init_api_mode():
59-
LOGGER.error("Failed to configure XBee API mode.")
60-
return False
61-
62-
await self._api._at_command("AO", 0x03)
63-
64-
serial_high = await self._api._at_command("SH")
65-
serial_low = await self._api._at_command("SL")
66-
ieee = EUI64.deserialize(
67-
serial_high.to_bytes(4, "big") + serial_low.to_bytes(4, "big")
68-
)[0]
69-
self._ieee = zigpy.types.EUI64(ieee)
70-
LOGGER.debug("Read local IEEE address as %s", self._ieee)
57+
raise zigpy.exceptions.ControllerException(
58+
"Failed to configure XBee API mode."
59+
)
7160

61+
async def start_network(self):
7262
try:
7363
association_state = await asyncio.wait_for(
7464
self._get_association_state(), timeout=4
7565
)
7666
except asyncio.TimeoutError:
7767
association_state = 0xFF
78-
self._nwk = await self._api._at_command("MY")
68+
7969
enc_enabled = await self._api._at_command("EE")
8070
enc_options = await self._api._at_command("EO")
8171
zb_profile = await self._api._at_command("ZS")
@@ -85,62 +75,83 @@ async def startup(self, auto_form=False):
8575
enc_options != 2,
8676
zb_profile != 2,
8777
association_state != 0,
88-
self._nwk != 0,
78+
self.state.node_info.nwk != 0x0000,
8979
)
90-
if auto_form and any(should_form):
91-
await self.form_network()
9280

81+
if should_form:
82+
raise zigpy.exceptions.NetworkNotFormed("Network is not formed")
83+
84+
# Disable joins
9385
await self._api._at_command("NJ", 0)
9486
await self._api._at_command("SP", CONF_CYCLIC_SLEEP_PERIOD)
9587
await self._api._at_command("SN", CONF_POLL_TIMEOUT)
96-
id = await self._api._at_command("ID")
97-
LOGGER.debug("Extended PAN ID: 0x%016x", id)
98-
id = await self._api._at_command("OP")
99-
LOGGER.debug("Operating Extended PAN ID: 0x%016x", id)
100-
id = await self._api._at_command("OI")
101-
LOGGER.debug("PAN ID: 0x%04x", id)
102-
try:
103-
ce = await self._api._at_command("CE")
104-
LOGGER.debug("Coordinator %s", "enabled" if ce else "disabled")
105-
except RuntimeError as exc:
106-
LOGGER.debug("sending CE command: %s", exc)
10788

108-
dev = zigpy.device.Device(self, self.ieee, self.nwk)
89+
dev = zigpy.device.Device(
90+
self, self.state.node_info.ieee, self.state.node_info.nwk
91+
)
10992
dev.status = zigpy.device.Status.ENDPOINTS_INIT
11093
dev.add_endpoint(XBEE_ENDPOINT_ID)
111-
xbee_dev = XBeeCoordinator(self, self.ieee, self.nwk, dev)
94+
95+
xbee_dev = XBeeCoordinator(
96+
self, self.state.node_info.ieee, self.state.node_info.nwk, dev
97+
)
11298
self.listener_event("raw_device_initialized", xbee_dev)
11399
self.devices[dev.ieee] = xbee_dev
114100

115-
async def force_remove(self, dev):
116-
"""Forcibly remove device from NCP."""
117-
pass
101+
async def load_network_info(self, *, load_devices=False):
102+
network_info = self.state.network_info
103+
node_info = self.state.node_info
104+
105+
# Load node info
106+
node_info.nwk = await self._api._at_command("MY")
107+
serial_high = await self._api._at_command("SH")
108+
serial_low = await self._api._at_command("SL")
109+
node_info.ieee = zigpy.types.EUI64(
110+
EUI64.deserialize(
111+
serial_high.to_bytes(4, "big") + serial_low.to_bytes(4, "big")
112+
)[0]
113+
)
114+
115+
if await self._api._at_command("CE") == 0x01:
116+
node_info.logical_type = LogicalType.Coordinator
117+
else:
118+
node_info.logical_type = LogicalType.EndDevice
119+
120+
# Load network info
121+
network_info.pan_id = await self._api._at_command("OI")
122+
network_info.extended_pan_id = await self._api._at_command("ID")
123+
network_info.channel = await self._api._at_command("CH")
124+
125+
async def write_network_info(self, *, network_info, node_info):
126+
scan_bitmask = 1 << (network_info.channel - 11)
118127

119-
async def form_network(self, channel=15, pan_id=None, extended_pan_id=None):
120-
LOGGER.info("Forming network on channel %s", channel)
121-
scan_bitmask = 1 << (channel - 11)
122128
await self._api._queued_at("ZS", 2)
123129
await self._api._queued_at("SC", scan_bitmask)
124130
await self._api._queued_at("EE", 1)
125131
await self._api._queued_at("EO", 2)
126-
await self._api._queued_at("NK", 0)
127-
await self._api._queued_at("KY", b"ZigBeeAlliance09")
132+
133+
key_as_int = int.from_bytes(network_info.network_key.key.serialize(), "big")
134+
await self._api._queued_at("NK", key_as_int)
135+
136+
tclk_as_int = int.from_bytes(network_info.tc_link_key.key.serialize(), "big")
137+
await self._api._queued_at("KY", tclk_as_int)
138+
128139
await self._api._queued_at("NJ", 0)
129140
await self._api._queued_at("SP", CONF_CYCLIC_SLEEP_PERIOD)
130141
await self._api._queued_at("SN", CONF_POLL_TIMEOUT)
131-
try:
132-
await self._api._queued_at("CE", 1)
133-
except RuntimeError:
134-
pass
142+
await self._api._queued_at("SM", 0)
143+
await self._api._queued_at("CE", 1)
135144
await self._api._at_command("WR")
136145

137146
await asyncio.wait_for(self._api.coordinator_started_event.wait(), timeout=10)
138147
association_state = await asyncio.wait_for(
139148
self._get_association_state(), timeout=10
140149
)
141150
LOGGER.debug("Association state: %s", association_state)
142-
self._nwk = await self._api._at_command("MY")
143-
assert self._nwk == 0x0000
151+
152+
async def force_remove(self, dev):
153+
"""Forcibly remove device from NCP."""
154+
pass
144155

145156
async def _get_association_state(self):
146157
"""Wait for Zigbee to start."""
@@ -266,6 +277,9 @@ async def permit_ncp(self, time_s=60):
266277
await self._api._at_command("AC")
267278
await self._api._at_command("CB", 2)
268279

280+
async def permit_with_key(self, node, code, time_s=60):
281+
raise NotImplementedError("XBee does not support install codes")
282+
269283
def handle_modem_status(self, status):
270284
LOGGER.info("Modem status update: %s (%s)", status.name, status.value)
271285

@@ -296,7 +310,7 @@ def handle_rx(
296310
self.handle_join(nwk, ieee, 0)
297311

298312
try:
299-
self.devices[self.ieee].last_seen = time.time()
313+
self.devices[self.state.node_info.ieee].last_seen = time.time()
300314
except KeyError:
301315
pass
302316
try:
@@ -383,7 +397,24 @@ class XBeeGroupResponse(zigpy.quirks.CustomCluster, Groups):
383397
def __init__(self, *args, **kwargs):
384398
super().__init__(*args, **kwargs)
385399
self.node_desc = NodeDescriptor(
386-
0x00, 0x40, 0x8E, 0x101E, 0x52, 0x00FF, 0x2C00, 0x00FF, 0x00
400+
logical_type=NodeDescriptor.LogicalType.Coordinator,
401+
complex_descriptor_available=0,
402+
user_descriptor_available=0,
403+
reserved=0,
404+
aps_flags=0,
405+
frequency_band=NodeDescriptor.FrequencyBand.Freq2400MHz,
406+
mac_capability_flags=(
407+
NodeDescriptor.MACCapabilityFlags.AllocateAddress
408+
| NodeDescriptor.MACCapabilityFlags.RxOnWhenIdle
409+
| NodeDescriptor.MACCapabilityFlags.MainsPowered
410+
| NodeDescriptor.MACCapabilityFlags.FullFunctionDevice
411+
),
412+
manufacturer_code=4126,
413+
maximum_buffer_size=82,
414+
maximum_incoming_transfer_size=255,
415+
server_mask=11264,
416+
maximum_outgoing_transfer_size=255,
417+
descriptor_capability_field=NodeDescriptor.DescriptorCapability.NONE,
387418
)
388419

389420
replacement = {

0 commit comments

Comments
 (0)