Skip to content

Commit a78951d

Browse files
authored
Merge pull request #132 from puddly/rc
0.10.0 Release
2 parents 3f7898e + a6c2952 commit a78951d

File tree

8 files changed

+140
-104
lines changed

8 files changed

+140
-104
lines changed

.github/workflows/publish-to-pypi.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ jobs:
1010
runs-on: ubuntu-latest
1111
steps:
1212
- uses: actions/checkout@master
13-
- name: Set up Python 3.7
13+
- name: Set up Python 3.8
1414
uses: actions/setup-python@v1
1515
with:
16-
version: 3.7
16+
version: 3.8
1717
- name: Install wheel
1818
run: >-
1919
pip install wheel

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
runs-on: ubuntu-latest
1313
strategy:
1414
matrix:
15-
python-version: ["3.7", "3.8", "3.9", "3.10"]
15+
python-version: ["3.8", "3.9", "3.10"]
1616

1717
steps:
1818
- uses: actions/checkout@v2

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
'pyserial-asyncio>=0.5; platform_system!="Windows"',
2222
'pyserial-asyncio!=0.5; platform_system=="Windows"', # 0.5 broke writes
2323
'pyusb>=1.1.0',
24-
'zigpy>=0.47.0',
24+
'zigpy>=0.51.0',
2525
'gpiozero',
2626
],
2727
tests_require=[

zigpy_zigate/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
MAJOR_VERSION = 0
2-
MINOR_VERSION = 9
3-
PATCH_VERSION = '2'
2+
MINOR_VERSION = 10
3+
PATCH_VERSION = '0'
44
__short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION)
55
__version__ = '{}.{}'.format(__short_version__, PATCH_VERSION)

zigpy_zigate/api.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ class ResponseId(enum.IntEnum):
6767
EXTENDED_ERROR = 0x9999
6868

6969

70+
class SendSecurity(t.uint8_t, enum.Enum):
71+
NETWORK = 0x00
72+
APPLINK = 0x01
73+
TEMP_APPLINK = 0x02
7074

7175

7276
class NonFactoryNewRestartStatus(t.uint8_t, enum.Enum):
@@ -482,11 +486,10 @@ async def remove_device(self, zigate_ieee, ieee):
482486
return await self.command(CommandId.NETWORK_REMOVE_DEVICE, data)
483487

484488
async def raw_aps_data_request(self, addr, src_ep, dst_ep, profile,
485-
cluster, payload, addr_mode=2, security=0):
489+
cluster, payload, addr_mode=t.AddressMode.NWK, security=SendSecurity.NETWORK, radius=0):
486490
'''
487491
Send raw APS Data request
488492
'''
489-
radius = 0
490493
data = t.serialize([addr_mode, addr,
491494
src_ep, dst_ep, cluster, profile,
492495
security, radius, payload], COMMANDS[CommandId.SEND_RAW_APS_DATA_PACKET])

zigpy_zigate/types.py

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -144,9 +144,20 @@ def __str__(self):
144144
class AddressMode(uint8_t, enum.Enum):
145145
# Address modes used in zigate protocol
146146

147+
BOUND = 0x00
147148
GROUP = 0x01
148149
NWK = 0x02
149150
IEEE = 0x03
151+
BROADCAST = 0x04
152+
153+
NO_TRANSMIT = 0x05
154+
155+
BOUND_NO_ACK = 0x06
156+
NWK_NO_ACK = 0x07
157+
IEEE_NO_ACK = 0x08
158+
159+
BOUND_NON_BLOCKING = 0x09
160+
BOUND_NON_BLOCKING_NO_ACK = 0x0A
150161

151162

152163
class Status(uint8_t, enum.Enum):
@@ -259,6 +270,26 @@ def __repr__(self):
259270
return r
260271

261272

273+
ZIGPY_TO_ZIGATE_ADDR_MODE = {
274+
# With ACKs
275+
(zigpy.types.AddrMode.NWK, True): AddressMode.NWK,
276+
(zigpy.types.AddrMode.IEEE, True): AddressMode.IEEE,
277+
(zigpy.types.AddrMode.Broadcast, True): AddressMode.BROADCAST,
278+
(zigpy.types.AddrMode.Group, True): AddressMode.GROUP,
279+
280+
# Without ACKs
281+
(zigpy.types.AddrMode.NWK, False): AddressMode.NWK_NO_ACK,
282+
(zigpy.types.AddrMode.IEEE, False): AddressMode.IEEE_NO_ACK,
283+
(zigpy.types.AddrMode.Broadcast, False): AddressMode.BROADCAST,
284+
(zigpy.types.AddrMode.Group, False): AddressMode.GROUP,
285+
}
286+
287+
ZIGATE_TO_ZIGPY_ADDR_MODE = {
288+
zigate_addr: (zigpy_addr, ack)
289+
for (zigpy_addr, ack), zigate_addr in ZIGPY_TO_ZIGATE_ADDR_MODE.items()
290+
}
291+
292+
262293
class Address(Struct):
263294
_fields = [
264295
('address_mode', AddressMode),
@@ -271,17 +302,23 @@ def __eq__(self, other):
271302
@classmethod
272303
def deserialize(cls, data):
273304
r = cls()
274-
field_name, field_type = cls._fields[0]
275-
mode, data = field_type.deserialize(data)
276-
setattr(r, field_name, mode)
277-
v = None
278-
if mode in [AddressMode.GROUP, AddressMode.NWK]:
279-
v, data = NWK.deserialize(data)
280-
elif mode == AddressMode.IEEE:
281-
v, data = EUI64.deserialize(data)
282-
setattr(r, cls._fields[1][0], v)
305+
r.address_mode, data = AddressMode.deserialize(data)
306+
307+
if r.address_mode in (AddressMode.IEEE, AddressMode.IEEE_NO_ACK):
308+
r.address, data = EUI64.deserialize(data)
309+
else:
310+
r.address, data = NWK.deserialize(data)
311+
283312
return r, data
284313

314+
def to_zigpy_type(self):
315+
zigpy_addr_mode, ack = ZIGATE_TO_ZIGPY_ADDR_MODE[self.address_mode]
316+
317+
return (
318+
zigpy.types.AddrModeAddress(addr_mode=zigpy_addr_mode, address=self.address),
319+
ack,
320+
)
321+
285322

286323
class DeviceEntry(Struct):
287324
_fields = [

zigpy_zigate/uart.py

Lines changed: 19 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@
44
import struct
55
from typing import Any, Dict
66

7-
import serial # noqa
8-
import serial.tools.list_ports
9-
import serial_asyncio
7+
import zigpy.serial
108

119
from .config import CONF_DEVICE_PATH
1210
from . import common as c
@@ -141,38 +139,25 @@ async def connect(device_config: Dict[str, Any], api, loop=None):
141139
if port == 'auto':
142140
port = c.discover_port()
143141

144-
if c.is_zigate_wifi(port):
142+
if c.is_pizigate(port):
143+
LOGGER.debug('PiZiGate detected')
144+
await c.async_set_pizigate_running_mode()
145+
# in case of pizigate:/dev/ttyAMA0 syntax
146+
if port.startswith('pizigate:'):
147+
port = port.replace('pizigate:', '', 1)
148+
elif c.is_zigate_din(port):
149+
LOGGER.debug('ZiGate USB DIN detected')
150+
await c.async_set_zigatedin_running_mode()
151+
elif c.is_zigate_wifi(port):
145152
LOGGER.debug('ZiGate WiFi detected')
146-
port = port.split('socket://', 1)[1]
147-
if ':' in port:
148-
host, port = port.split(':', 1) # 192.168.x.y:9999
149-
port = int(port)
150-
else:
151-
host = port
152-
port = 9999
153-
_, protocol = await loop.create_connection(
154-
lambda: protocol,
155-
host, port)
156-
else:
157-
if c.is_pizigate(port):
158-
LOGGER.debug('PiZiGate detected')
159-
await c.async_set_pizigate_running_mode()
160-
# in case of pizigate:/dev/ttyAMA0 syntax
161-
if port.startswith('pizigate:'):
162-
port = port[9:]
163-
elif c.is_zigate_din(port):
164-
LOGGER.debug('ZiGate USB DIN detected')
165-
await c.async_set_zigatedin_running_mode()
166-
167-
_, protocol = await serial_asyncio.create_serial_connection(
168-
loop,
169-
lambda: protocol,
170-
url=port,
171-
baudrate=ZIGATE_BAUDRATE,
172-
parity=serial.PARITY_NONE,
173-
stopbits=serial.STOPBITS_ONE,
174-
xonxoff=False,
175-
)
153+
154+
_, protocol = await zigpy.serial.create_serial_connection(
155+
loop,
156+
lambda: protocol,
157+
url=port,
158+
baudrate=ZIGATE_BAUDRATE,
159+
xonxoff=False,
160+
)
176161

177162
await connected_future
178163

zigpy_zigate/zigbee/application.py

Lines changed: 64 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,13 @@ async def load_network_info(self, *, load_devices: bool = False):
115115
self.state.network_info.children.append(ieee)
116116
self.state.network_info.nwk_addresses[ieee] = zigpy.types.NWK(device.short_addr)
117117

118+
async def reset_network_info(self):
119+
await self._api.erase_persistent_data()
120+
118121
async def write_network_info(self, *, network_info, node_info):
119122
LOGGER.warning('Setting the pan_id is not supported by ZiGate')
120123

121-
await self._api.erase_persistent_data()
122-
124+
await self.reset_network_info()
123125
await self._api.set_channel(network_info.channel)
124126

125127
epid, _ = zigpy.types.uint64_t.deserialize(network_info.extended_pan_id.serialize())
@@ -179,28 +181,30 @@ def zigate_callback_handler(self, msg, response, lqi):
179181
# LOGGER.debug('Start pairing {} (1st device announce)'.format(nwk))
180182
# self._pending_join.append(nwk)
181183
elif msg == ResponseId.DATA_INDICATION:
182-
if response[1] == 0x0 and response[2] == 0x13:
183-
nwk = zigpy.types.NWK(response[5].address)
184-
ieee = zigpy.types.EUI64(response[7][3:11])
185-
parent_nwk = 0
186-
self.handle_join(nwk, ieee, parent_nwk)
187-
return
188-
try:
189-
if response[5].address_mode == t.AddressMode.NWK:
190-
device = self.get_device(nwk = zigpy.types.NWK(response[5].address))
191-
elif response[5].address_mode == t.AddressMode.IEEE:
192-
device = self.get_device(ieee=zigpy.types.EUI64(response[5].address))
193-
else:
194-
LOGGER.error("No such device %s", response[5].address)
195-
return
196-
except KeyError:
197-
LOGGER.debug("No such device %s", response[5].address)
198-
return
199-
rssi = 0
200-
device.radio_details(lqi, rssi)
201-
self.handle_message(device, response[1],
202-
response[2],
203-
response[3], response[4], response[-1])
184+
(
185+
status,
186+
profile_id,
187+
cluster_id,
188+
src_ep,
189+
dst_ep,
190+
src,
191+
dst,
192+
payload,
193+
) = response
194+
195+
packet = zigpy.types.ZigbeePacket(
196+
src=src.to_zigpy_type()[0],
197+
src_ep=src_ep,
198+
dst=dst.to_zigpy_type()[0],
199+
dst_ep=dst_ep,
200+
profile_id=profile_id,
201+
cluster_id=cluster_id,
202+
data=zigpy.types.SerializableBytes(payload),
203+
lqi=lqi,
204+
rssi=None,
205+
)
206+
207+
self.packet_received(packet)
204208
elif msg == ResponseId.ACK_DATA:
205209
LOGGER.debug('ACK Data received %s %s', response[4], response[0])
206210
# disabled because of https://github.com/fairecasoimeme/ZiGate/issues/324
@@ -228,53 +232,60 @@ def _handle_frame_failure(self, message_tag, status):
228232
except asyncio.futures.InvalidStateError as exc:
229233
LOGGER.debug("Invalid state on future - probably duplicate response: %s", exc)
230234

231-
@zigpy.util.retryable_request
232-
async def request(self, device, profile, cluster, src_ep, dst_ep, sequence, data,
233-
expect_reply=True, use_ieee=False):
234-
return await self._request(device.nwk, profile, cluster, src_ep, dst_ep, sequence, data,
235-
expect_reply, use_ieee)
236-
237-
async def mrequest(self, group_id, profile, cluster, src_ep, sequence, data, *, hops=0, non_member_radius=3):
238-
src_ep = 1
239-
return await self._request(group_id, profile, cluster, src_ep, src_ep, sequence, data, addr_mode=1)
240-
241-
async def _request(self, nwk, profile, cluster, src_ep, dst_ep, sequence, data,
242-
expect_reply=True, use_ieee=False, addr_mode=2):
243-
src_ep = 1 if dst_ep else 0 # ZiGate only support endpoint 1
244-
LOGGER.debug('request %s',
245-
(nwk, profile, cluster, src_ep, dst_ep, sequence, data, expect_reply, use_ieee))
235+
async def send_packet(self, packet):
236+
LOGGER.debug("Sending packet %r", packet)
237+
238+
if packet.dst.addr_mode == zigpy.types.AddrMode.IEEE:
239+
LOGGER.warning("IEEE addressing is not supported, falling back to NWK")
240+
241+
try:
242+
device = self.get_device_with_address(packet.dst)
243+
except (KeyError, ValueError):
244+
raise ValueError(f"Cannot find device with IEEE {packet.dst.address}")
245+
246+
packet = packet.replace(
247+
dst=zigpy.types.AddrModeAddress(
248+
addr_mode=zigpy.types.AddrMode.NWK, address=device.nwk
249+
)
250+
)
251+
252+
ack = (zigpy.types.TransmitOptions.ACK in packet.tx_options)
253+
246254
try:
247-
v, lqi = await self._api.raw_aps_data_request(nwk, src_ep, dst_ep, profile, cluster, data, addr_mode)
255+
(status, tsn, packet_type, _), _ = await self._api.raw_aps_data_request(
256+
addr=packet.dst.address,
257+
src_ep=(1 if packet.dst_ep > 0 else 0), # ZiGate only support endpoint 1
258+
dst_ep=packet.dst_ep,
259+
profile=packet.profile_id,
260+
cluster=packet.cluster_id,
261+
payload=packet.data.serialize(),
262+
addr_mode=t.ZIGPY_TO_ZIGATE_ADDR_MODE[packet.dst.addr_mode, ack],
263+
radius=packet.radius,
264+
)
248265
except NoResponseError:
249-
return 1, "ZiGate doesn't answer to command"
250-
req_id = v[1]
251-
send_fut = asyncio.Future()
252-
self._pending[req_id] = send_fut
266+
raise zigpy.exceptions.DeliveryError("ZiGate did not respond to command")
253267

254-
if v[0] != t.Status.Success:
255-
self._pending.pop(req_id)
256-
return v[0], "Message send failure {}".format(v[0])
268+
if status != t.Status.Success:
269+
self._pending.pop(tsn)
270+
raise zigpy.exceptions.DeliveryError(f"Failed to deliver packet: {status}", status=status)
271+
272+
self._pending[tsn] = asyncio.get_running_loop().create_future()
257273

258274
# disabled because of https://github.com/fairecasoimeme/ZiGate/issues/324
259275
# try:
260276
# v = await asyncio.wait_for(send_fut, 120)
261277
# except asyncio.TimeoutError:
262278
# return 1, "timeout waiting for message %s send ACK" % (sequence, )
263279
# finally:
264-
# self._pending.pop(req_id)
280+
# self._pending.pop(tsn)
265281
# return v, "Message sent"
266-
return 0, "Message sent"
267282

268283
async def permit_ncp(self, time_s=60):
269284
assert 0 <= time_s <= 254
270285
status, lqi = await self._api.permit_join(time_s)
271286
if status[0] != t.Status.Success:
272287
await self._api.reset()
273288

274-
async def broadcast(self, profile, cluster, src_ep, dst_ep, grpid, radius,
275-
sequence, data, broadcast_address):
276-
LOGGER.debug("Broadcast not implemented.")
277-
278289

279290
class ZiGateDevice(zigpy.device.Device):
280291
def __init__(self, application, ieee, nwk):

0 commit comments

Comments
 (0)