Skip to content

Commit

Permalink
0.18.0 Release
Browse files Browse the repository at this point in the history
  • Loading branch information
Adminiuga authored Jul 21, 2020
2 parents fbe2d87 + 7504393 commit 73493ed
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 40 deletions.
2 changes: 1 addition & 1 deletion bellows/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
MAJOR_VERSION = 0
MINOR_VERSION = 17
MINOR_VERSION = 18
PATCH_VERSION = "0"
__short_version__ = "{}.{}".format(MAJOR_VERSION, MINOR_VERSION)
__version__ = "{}.{}".format(__short_version__, PATCH_VERSION)
14 changes: 11 additions & 3 deletions bellows/ezsp.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,12 @@ def _ezsp_frame(self, name, *args):
data = t.serialize(args, c[1])
frame = [self._seq & 0xFF, 0, c[0]] # Frame control. TODO. # Frame ID
if self.ezsp_version >= 5:
frame.insert(1, 0xFF) # Legacy Frame ID
frame.insert(1, 0x00) # Ext frame control. TODO.
frame.insert(1, 0xFF) # Legacy Frame
frame.insert(1, 0x00) # Frame control low byte
if self.ezsp_version >= 8:
frame[2] = 0x01 # EZSP v8 - FC High Byte
frame[3] = c[0] & 0x00FF # Extended Frame ID - LSB
frame[4] = (c[0] & 0xFF00) >> 8 # Extended Frame ID - MSB

return bytes(frame) + data

Expand Down Expand Up @@ -219,7 +223,11 @@ def frame_received(self, data):
just have EZSP application stuff here, with all escaping/stuffing and
data randomization removed.
"""
sequence, frame_id, data = data[0], data[2], data[3:]
if self.ezsp_version >= 8:
sequence, frame_id, data = data[0], ((data[4] << 8) | data[3]), data[5:]
else:
sequence, frame_id, data = data[0], data[2], data[3:]

if frame_id == 0xFF:
frame_id = 0
if len(data) > 1:
Expand Down
98 changes: 65 additions & 33 deletions bellows/zigbee/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ def __init__(self, config: Dict):
| t.EmberApsOption.APS_OPTION_ENABLE_ROUTE_DISCOVERY
)
self.use_source_routing = self.config[CONF_PARAM_SRC_RTG]
self._req_lock = asyncio.Lock()

@property
def controller_event(self):
Expand Down Expand Up @@ -263,14 +264,19 @@ def ezsp_callback_handler(self, frame_name, args):
else:
self._handle_frame_sent(*args)
elif frame_name == "trustCenterJoinHandler":
if args[2] == t.EmberDeviceUpdate.DEVICE_LEFT:
self.handle_leave(args[0], args[1])
nwk, ieee, dev_update_status, decision, parent_nwk = args
if dev_update_status == t.EmberDeviceUpdate.DEVICE_LEFT:
self.handle_leave(nwk, ieee)
else:
self.handle_join(nwk, ieee, parent_nwk)
elif frame_name == "incomingRouteRecordHandler":
self.handle_route_record(*args)
elif frame_name == "incomingRouteErrorHandler":
self.handle_route_error(*args)
elif frame_name == "_reset_controller_application":
self._handle_reset_request(*args)
elif frame_name == "idConflictHandler":
self._handle_id_conflict(*args)

def _handle_frame(
self,
Expand Down Expand Up @@ -422,9 +428,10 @@ async def mrequest(

with self._pending.new(message_tag) as req:
async with self._in_flight_msg:
res = await self._ezsp.sendMulticast(
aps_frame, hops, non_member_radius, message_tag, data
)
async with self._req_lock:
res = await self._ezsp.sendMulticast(
aps_frame, hops, non_member_radius, message_tag, data
)
if res[0] != t.EmberStatus.SUCCESS:
return res[0], "EZSP sendMulticast failure: %s" % (res[0],)

Expand Down Expand Up @@ -476,33 +483,43 @@ async def request(
)
with self._pending.new(message_tag) as req:
async with self._in_flight_msg:
if expect_reply and device.node_desc.is_end_device in (True, None):
LOGGER.debug(
"Extending timeout for %s/0x%04x", device.ieee, device.nwk
)
await self._ezsp.setExtendedTimeout(device.ieee, True)
if self.use_source_routing and device.relays is not None:
res = await self._ezsp.setSourceRoute(device.nwk, device.relays)
if res[0] != t.EmberStatus.SUCCESS:
LOGGER.warning(
"Couldn't set source route for %s: %s", device.nwk, res
)
else:
aps_frame.options = t.EmberApsOption(
aps_frame.options
^ t.EmberApsOption.APS_OPTION_ENABLE_ROUTE_DISCOVERY
)
LOGGER.debug(
"Set source route for %s to %s: %s",
device.nwk,
device.relays,
res,
)
delays = [0.5, 1.0, 1.5]
while True:
status, _ = await self._ezsp.sendUnicast(
self.direct, device.nwk, aps_frame, message_tag, data
)
async with self._req_lock:
if expect_reply and device.node_desc.is_end_device in (
True,
None,
):
LOGGER.debug(
"Extending timeout for %s/0x%04x",
device.ieee,
device.nwk,
)
await self._ezsp.setExtendedTimeout(device.ieee, True)
if self.use_source_routing and device.relays is not None:
res = await self._ezsp.setSourceRoute(
device.nwk, device.relays
)
if res[0] != t.EmberStatus.SUCCESS:
LOGGER.warning(
"Couldn't set source route for %s: %s",
device.nwk,
res,
)
else:
aps_frame.options = t.EmberApsOption(
aps_frame.options
^ t.EmberApsOption.APS_OPTION_ENABLE_ROUTE_DISCOVERY
)
LOGGER.debug(
"Set source route for %s to %s: %s",
device.nwk,
device.relays,
res,
)
status, _ = await self._ezsp.sendUnicast(
self.direct, device.nwk, aps_frame, message_tag, data
)
if not (
status == t.EmberStatus.MAX_MESSAGE_LIMIT_REACHED and delays
):
Expand Down Expand Up @@ -548,6 +565,20 @@ async def permit_with_key(self, node, code, time_s=60):

return await self.permit(time_s)

def _handle_id_conflict(self, nwk: t.EmberNodeId) -> None:
LOGGER.warning("NWK conflict is reported for 0x%04x", nwk)
for device in self.devices.values():
if device.nwk != nwk:
continue
LOGGER.warning(
"Found %s device for 0x%04x NWK conflict: %s %s",
device.ieee,
nwk,
device.manufacturer,
device.model,
)
self.handle_leave(nwk, device.ieee)

async def broadcast(
self,
profile,
Expand Down Expand Up @@ -590,9 +621,10 @@ async def broadcast(

with self._pending.new(message_tag) as req:
async with self._in_flight_msg:
res = await self._ezsp.sendBroadcast(
broadcast_address, aps_frame, radius, message_tag, data
)
async with self._req_lock:
res = await self._ezsp.sendBroadcast(
broadcast_address, aps_frame, radius, message_tag, data
)
if res[0] != t.EmberStatus.SUCCESS:
return res[0], "broadcast send failure"

Expand Down
23 changes: 21 additions & 2 deletions tests/test_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,9 +226,14 @@ def test_dup_send_success(app, aps, ieee):
def test_join_handler(app, ieee):
# Calls device.initialize, leaks a task
app.handle_join = mock.MagicMock()
app.ezsp_callback_handler("trustCenterJoinHandler", [1, ieee, None, None, None])
app.ezsp_callback_handler(
"trustCenterJoinHandler", [1, ieee, None, None, mock.sentinel.parent]
)
assert ieee not in app.devices
assert app.handle_join.call_count == 0
assert app.handle_join.call_count == 1
assert app.handle_join.call_args[0][0] == 1
assert app.handle_join.call_args[0][1] == ieee
assert app.handle_join.call_args[0][2] is mock.sentinel.parent


def test_leave_handler(app, ieee):
Expand Down Expand Up @@ -1080,3 +1085,17 @@ async def test_probe_success(mock_connect, mock_reset):
assert mock_connect.await_count == 1
assert mock_reset.call_count == 1
assert mock_connect.return_value.close.call_count == 1


def test_handle_id_conflict(app, ieee):
"""Test handling of an ID confict report."""
nwk = t.EmberNodeId(0x1234)
app.add_device(ieee, nwk)
app.handle_leave = mock.MagicMock()

app.ezsp_callback_handler("idConflictHandler", [nwk + 1])
assert app.handle_leave.call_count == 0

app.ezsp_callback_handler("idConflictHandler", [nwk])
assert app.handle_leave.call_count == 1
assert app.handle_leave.call_args[0][0] == nwk
11 changes: 11 additions & 0 deletions tests/test_ezsp.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,13 @@ def test_receive_protocol_5(ezsp_f):
assert ezsp_f.handle_callback.call_count == 1


def test_receive_protocol_8(ezsp_f):
ezsp_f._ezsp_version = 8
ezsp_f.handle_callback = mock.MagicMock()
ezsp_f.frame_received(b"\x01\x80\x01\x00\x00\x06\x02\x00")
assert ezsp_f.handle_callback.call_count == 1


def test_receive_reply(ezsp_f):
ezsp_f.handle_callback = mock.MagicMock()
callback_mock = mock.MagicMock(spec_set=asyncio.Future)
Expand Down Expand Up @@ -306,6 +313,10 @@ def test_ezsp_frame(ezsp_f):
data = ezsp_f._ezsp_frame("version", 6)
assert data == b"\x22\x00\xff\x00\x00\x06"

ezsp_f._ezsp_version = 8
data = ezsp_f._ezsp_frame("version", 8)
assert data == b"\x22\x00\x01\x00\x00\x08"


@pytest.mark.asyncio
@mock.patch.object(ezsp.EZSP, "reset", new_callable=CoroutineMock)
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ basepython = python3
deps =
flake8==3.5.0
pep8-naming==0.4.1
isort
isort==4.3.21
commands =
flake8
isort --check -rc {toxinidir}/bellows {toxinidir}/tests {toxinidir}/setup.py
Expand Down

0 comments on commit 73493ed

Please sign in to comment.