Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

0.44.2 Release #957

Merged
merged 16 commits into from Apr 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
26 changes: 23 additions & 3 deletions tests/test_ota_image.py
Expand Up @@ -314,6 +314,11 @@ def test_get_image_block_offset_too_large(raw_header, raw_sub_element):
img.get_image_block(offset, size)


def test_cached_image_wrapping(image):
cached_img = CachedImage(image)
assert cached_img.header is image.header


def wrap_ikea(data):
header = bytearray(100)
header[0:4] = b"NGIS"
Expand Down Expand Up @@ -400,6 +405,21 @@ def test_parse_ota_hue_invalid():
firmware.parse_ota_image(header.replace(manufacturer_id=12).serialize() + rest)


def test_cached_image_wrapping(image):
cached_img = CachedImage(image)
assert cached_img.header is image.header
def test_legrand_container_unwrapping(image):
# Unwrapped size prefix and 1 + 16 byte suffix
data = (
t.uint32_t(len(image.serialize())).serialize()
+ image.serialize()
+ b"\x01"
+ b"abcdabcdabcdabcd"
)

with pytest.raises(ValueError):
firmware.parse_ota_image(data[:-1])

with pytest.raises(ValueError):
firmware.parse_ota_image(b"\xFF" + data[1:])

img, rest = firmware.parse_ota_image(data)
assert not rest
assert img == image
12 changes: 12 additions & 0 deletions tests/test_struct.py
Expand Up @@ -774,3 +774,15 @@ class TestStruct(t.Struct):
s1 = TestStruct(foo=1, bar=2, baz="asd")

assert repr(s1) == "TestStruct(foo=2, bar=bar, baz=baz)"


def test_skip_missing():
class TestStruct(t.Struct):
foo: t.uint8_t
bar: t.uint16_t

assert TestStruct(foo=1).as_dict() == {"foo": 1, "bar": None}
assert TestStruct(foo=1).as_dict(skip_missing=True) == {"foo": 1}

assert TestStruct(foo=1).as_tuple() == (1, None)
assert TestStruct(foo=1).as_tuple(skip_missing=True) == (1,)
2 changes: 1 addition & 1 deletion zigpy/__init__.py
@@ -1,5 +1,5 @@
MAJOR_VERSION = 0
MINOR_VERSION = 44
PATCH_VERSION = "1"
PATCH_VERSION = "2"
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__ = f"{__short_version__}.{PATCH_VERSION}"
87 changes: 87 additions & 0 deletions zigpy/ota/OTA_URLs.md
@@ -0,0 +1,87 @@
# Zigbee OTA source provider sources for these and others

Collection of external Zigbee OTA firmware images from official and unofficial OTA provider sources.

### Koenkk zigbee-OTA repository

Koenkk zigbee-OTA repository host third-party OTA firmware images and external URLs for many third-party Zigbee OTA firmware images.

https://github.com/Koenkk/zigbee-OTA/tree/master/images

https://raw.githubusercontent.com/Koenkk/zigbee-OTA/master/index.json

### Dresden Elektronik

Dresden Elektronik Zigbee OTA firmware images are made publicly available by Dresden Elektronik (first-party) at the following URL:

https://github.com/dresden-elektronik/deconz-rest-plugin/wiki/OTA-Image-Types---Firmware-versions

Dresden Elektronik also provide third-party OTA firmware images and external URLs for many third-party Zigbee OTA firmware images.

### EUROTRONICS

EUROTRONICS Zigbee OTA firmware images are made publicly available by EUROTRONIC Technology (first-party) at the following URL:

https://github.com/EUROTRONIC-Technology/Spirit-ZigBee/releases/download/

### IKEA Trådfri

IKEA Tradfi Zigbee OTA firmware images are made publicly available by IKEA (first-party) at the following URL:

Download-URL:

http://fw.ota.homesmart.ikea.net/feed/version_info.json

Download-URL (Test/Beta-Version):

http://fw.test.ota.homesmart.ikea.net/feed/version_info.json

Release changelogs

https://ww8.ikea.com/ikeahomesmart/releasenotes/releasenotes.html

### LEDVANCE/Sylvania and OSRAM Lightify

LEDVANCE/Sylvania and OSRAM Lightify Zigbee OTA firmware images are made publicly available by LEDVANCE (first-party) at the following URL:

https://update.ledvance.com/firmware-overview

https://api.update.ledvance.com/v1/zigbee/firmwares/download

https://consumer.sylvania.com/our-products/smart/sylvania-smart-zigbee-products-menu/index.jsp

### Legrand/Netatmo

Legrand/Netatmo Zigbee OTA firmware images are made publicly available by Legrand (first-party) at the following URL:

https://developer.legrand.com/documentation/operating-manual/ https://developer.legrand.com/documentation/firmwares-download/

### LiXee

LiXee Zigbee OTA firmware images are made publicly available by Fairecasoimeme / ZiGate (first-party) at the following URL:

https://github.com/fairecasoimeme/Zlinky_TIC/releases

### SALUS/Computime

SALUS/Computime Hue Zigbee OTA firmware images are made publicly available by SALUS (first-party) at the following URL:

https://eu.salusconnect.io/demo/default/status/firmware

### Sengled

Sengled Zigbee OTA firmware images are made publicly available by Dresden Elektronik (third-party) at the following URL:

https://github.com/dresden-elektronik/deconz-rest-plugin/wiki/OTA-Image-Types---Firmware-versions#sengled

### Philips Hue (Signify)

Philips Hue (Signify) Zigbee OTA firmware images are made publicly available by Dresden Elektronik (third-party) at the following URL:

https://github.com/dresden-elektronik/deconz-rest-plugin/wiki/OTA-Image-Types---Firmware-versions#philips-hue

### Ubisys

Ubisys Zigbee OTA firmware images are made publicly available by Ubisys (first-party) at the following URL:

https://www.ubisys.de/en/support/firmware/
8 changes: 6 additions & 2 deletions zigpy/ota/image.py
Expand Up @@ -223,8 +223,12 @@ def parse_ota_image(data: bytes) -> tuple[BaseOTAImage, bytes]:
Attempts to extract any known OTA image type from data. Does not validate firmware.
"""

# IKEA container needs to be unwrapped
if data.startswith(b"NGIS"):
if len(data) > 4 and int.from_bytes(data[0:4], "little") + 21 == len(data):
# Legrand OTA images are prefixed with their unwrapped size and include a 1 + 16
# byte suffix
return OTAImage.deserialize(data[4:-17])
elif data.startswith(b"NGIS"):
# IKEA container needs to be unwrapped
if len(data) <= 24:
raise ValueError(
f"Data too short to contain IKEA container header: {len(data)}"
Expand Down
18 changes: 14 additions & 4 deletions zigpy/types/struct.py
Expand Up @@ -184,11 +184,21 @@ def assigned_fields(self, *, strict=False) -> list[tuple[StructField, typing.Any

return assigned_fields

def as_dict(self) -> dict[str, typing.Any]:
return {f.name: getattr(self, f.name) for f in self.fields}
def as_dict(self, *, skip_missing: bool = False) -> dict[str, typing.Any]:
d = {}

def as_tuple(self) -> tuple:
return tuple(getattr(self, f.name) for f in self.fields)
for f in self.fields:
value = getattr(self, f.name)

if value is None and skip_missing:
continue

d[f.name] = value

return d

def as_tuple(self, *, skip_missing: bool = False) -> tuple:
return tuple(self.as_dict(skip_missing=skip_missing).values())

def serialize(self) -> bytes:
chunks = []
Expand Down
2 changes: 1 addition & 1 deletion zigpy/zcl/__init__.py
Expand Up @@ -207,7 +207,7 @@ def from_id(
cluster.cluster_id = cluster_id
return cluster

LOGGER.warning("Unknown cluster 0x%04X", cluster_id)
LOGGER.debug("Unknown cluster 0x%04X", cluster_id)

cluster = cls(endpoint, is_server)
cluster.cluster_id = cluster_id
Expand Down
2 changes: 1 addition & 1 deletion zigpy/zcl/clusters/closures.py
Expand Up @@ -336,7 +336,7 @@ class EventType(t.enum8):
"set_holiday_schedule",
{
"holiday_schedule_id": t.uint8_t,
"loca_start_time": t.LocalTime,
"local_start_time": t.LocalTime,
"local_end_time": t.LocalTime,
"operating_mode_during_holiday": OperatingMode,
},
Expand Down
2 changes: 1 addition & 1 deletion zigpy/zcl/clusters/general.py
Expand Up @@ -1380,7 +1380,7 @@ class ImageNotifyPayloadType(t.enum8):
"file_offset": t.uint32_t,
"maximum_data_size": t.uint8_t,
"request_node_addr?": t.EUI64,
"minumum_block_period?": t.uint16_t,
"minimum_block_period?": t.uint16_t,
},
False,
),
Expand Down
2 changes: 1 addition & 1 deletion zigpy/zcl/clusters/homeautomation.py
Expand Up @@ -58,7 +58,7 @@ class ApplianceEventAlerts(Cluster):
0x00: ZCLCommandDef("get_alerts", {}, False)
}
client_commands: dict[int, ZCLCommandDef] = {
0x00: ZCLCommandDef("get_alarts_response", {}, True),
0x00: ZCLCommandDef("get_alerts_response", {}, True),
0x01: ZCLCommandDef("alerts_notification", {}, False),
0x02: ZCLCommandDef("event_notification", {}, False),
}
Expand Down
2 changes: 1 addition & 1 deletion zigpy/zcl/clusters/lighting.py
Expand Up @@ -148,7 +148,7 @@ class ColorLoopDirection(t.enum8):
0x02: ZCLCommandDef(
"step_hue",
{
"setp_mode": StepMode,
"step_mode": StepMode,
"step_size": t.uint8_t,
"transition_time": t.uint8_t,
"options_mask?": t.bitmap8,
Expand Down