Skip to content

Commit

Permalink
0.12.0 Release.
Browse files Browse the repository at this point in the history
  • Loading branch information
Adminiuga committed Dec 26, 2019
2 parents ca55a67 + bcf3d9e commit 4f314a3
Show file tree
Hide file tree
Showing 23 changed files with 171 additions and 543 deletions.
22 changes: 22 additions & 0 deletions Contributors.md
@@ -0,0 +1,22 @@
# Contributors
- [Russell Cloran] (https://github.com/rcloran)
- [Alexei Chetroi] (https://github.com/Adminiuga)
- [damarco] (https://github.com/damarco)
- [Andreas Bomholtz] (https://github.com/AndreasBomholtz)
- [puddly] (https://github.com/puddly)
- [presslab-us] (https://github.com/presslab-us)
- [Igor Bernstein] (https://github.com/igorbernstein2)
- [David F. Mulcahey] (https://github.com/dmulcahey)
- [Yoda-x] (https://github.com/Yoda-x)
- [Solomon_M] (https://github.com/zalke)
- [Pascal Vizeli] (https://github.com/pvizeli)
- [prairiesnpr] (https://github.com/prairiesnpr)
- [Jurriaan Pruis] (https://github.com/jurriaan)
- [Marcel Hoppe] (https://github.com/hobbypunk90)
- [felixstorm] (https://github.com/felixstorm)
- [Dinko Bajric] (https://github.com/dbajric)
- [Abílio Costa] (https://github.com/abmantis)
- [https://github.com/SchaumburgM] (https://github.com/SchaumburgM)
- [https://github.com/Nemesis24] (https://github.com/Nemesis24)
- [Hedda] (https://github.com/Hedda)
- [Andreas Setterlind] (https://github.com/Gamester17)
14 changes: 14 additions & 0 deletions README.md
Expand Up @@ -30,6 +30,7 @@ zigpy works with separate radio libraries which can each interface with multiple
- [RaspBee](https://www.dresden-elektronik.de/raspbee/) GPIO radio adapter from [Dresden-Elektronik](https://www.dresden-elektronik.de)

### Experimental Zigbee radio modules

- ZiGate based radios (via the [zigpy-zigate](https://github.com/doudz/zigpy-zigate) library for zigpy)
- [ZiGate open source ZigBee adapter hardware](https://zigate.fr/)

Expand All @@ -42,6 +43,19 @@ Packages of tagged versions are also released via PyPI
- https://pypi.org/project/zigpy-deconz/
- https://pypi.org/project/zigpy-zigate/

## How to contribute

If you are looking to make a contribution to this project we suggest that you follow the steps in these guides:
- https://github.com/firstcontributions/first-contributions/blob/master/README.md
- https://github.com/firstcontributions/first-contributions/blob/master/github-desktop-tutorial.md

Some developers might also be interested in receiving donations in the form of hardware such as Zigbee modules or devices, and even if such donations are most often donated with no strings attached it could in many cases help the developers motivation and indirect improve the development of this project.

## Developer references

Silicon Labs video playlist of ZigBee Concepts: Architecture basics, MAC/PHY, node types, and application profiles
- https://www.youtube.com/playlist?list=PL-awFRrdECXvAs1mN2t2xaI0_bQRh2AqD

## Related projects

### ZHA Device Handlers
Expand Down
14 changes: 3 additions & 11 deletions setup.py
Expand Up @@ -11,15 +11,7 @@
author="Russell Cloran",
author_email="rcloran@gmail.com",
license="GPL-3.0",
packages=find_packages(exclude=['*.tests']),
install_requires=[
'aiohttp',
'pycryptodome',
'crccheck',
],
tests_require=[
'asynctest',
'pytest',
'pytest-asyncio',
],
packages=find_packages(exclude=["*.tests"]),
install_requires=["aiohttp", "crccheck", "pycryptodome", "voluptuous"],
tests_require=["asynctest", "pytest", "pytest-asyncio"],
)
15 changes: 13 additions & 2 deletions tests/test_appdb.py
Expand Up @@ -53,6 +53,8 @@ async def test_database(tmpdir, monkeypatch):
app = make_app(db)
# TODO: Leaks a task on dev.initialize, I think?
ieee = make_ieee()
relays_1 = [t.NWK(0x1234), t.NWK(0x2345)]
relays_2 = [t.NWK(0x3456), t.NWK(0x4567)]
app.handle_join(99, ieee, 0)
app.handle_join(99, ieee, 0)

Expand All @@ -75,6 +77,7 @@ async def test_database(tmpdir, monkeypatch):
clus._update_attribute(5, bytes("Model", "ascii"))
clus.listener_event("cluster_command", 0)
clus.listener_event("general_command")
dev.relays = relays_1

# Test a CustomDevice
custom_ieee = make_ieee(1)
Expand All @@ -89,6 +92,7 @@ async def test_database(tmpdir, monkeypatch):
assert isinstance(app.get_device(custom_ieee), CustomDevice)
assert ep.endpoint_id in dev.get_signature()
app.device_initialized(app.get_device(custom_ieee))
dev.relays = relays_2

# Everything should've been saved - check that it re-loads
with mock.patch("zigpy.quirks.get_device", fake_get_device):
Expand All @@ -103,7 +107,11 @@ async def test_database(tmpdir, monkeypatch):
assert dev.endpoints[2].model == "Model"
assert dev.endpoints[2].out_clusters[1].cluster_id == 1
assert dev.endpoints[3].device_type == profiles.zll.DeviceType.COLOR_LIGHT
assert dev.relays == relays_1

dev = app2.get_device(custom_ieee)
assert dev.relays == relays_2
dev.relays = None

app.handle_leave(99, ieee)

Expand All @@ -119,6 +127,8 @@ async def mockleave(*args, **kwargs):

app3 = make_app(db)
assert ieee not in app3.devices
dev = app2.get_device(custom_ieee)
assert dev.relays is None

os.unlink(db)

Expand All @@ -128,8 +138,9 @@ def _test_null_padded(tmpdir, test_manufacturer=None, test_model=None):
app = make_app(db)
# TODO: Leaks a task on dev.initialize, I think?
ieee = make_ieee()
app.handle_join(99, ieee, 0)
app.handle_join(99, ieee, 0)
with mock.patch("zigpy.device.Device.schedule_initialize"):
app.handle_join(99, ieee, 0)
app.handle_join(99, ieee, 0)

dev = app.get_device(ieee)
ep = dev.add_endpoint(3)
Expand Down
5 changes: 4 additions & 1 deletion tests/test_application.py
Expand Up @@ -67,7 +67,6 @@ async def test_permit(app, ieee):
await app.permit(node=ncp_ieee)
assert app.devices[ieee].zdo.permit.call_count == 1
assert app.permit_ncp.call_count == 1
print("{}".format(asyncio.Task.all_tasks()))


@pytest.mark.asyncio
Expand Down Expand Up @@ -195,6 +194,10 @@ def test_nwk(app):
assert app.nwk == app._nwk


def test_config(app):
assert app.config == app._config


def test_deserialize(app, ieee):
dev = mock.MagicMock()
app.deserialize(dev, 1, 1, b"")
Expand Down
12 changes: 12 additions & 0 deletions tests/test_endpoint.py
Expand Up @@ -36,6 +36,18 @@ async def mockrequest(nwk, epid, tries=None, delay=None):
assert 6 in ep.out_clusters


@pytest.mark.asyncio
async def test_inactive_initialize(ep):
async def mockrequest(nwk, epid, tries=None, delay=None):
sd = types.SimpleDescriptor()
sd.endpoint = 2
return [131, None, sd]

ep._device.zdo.Simple_Desc_req = mockrequest
await ep.initialize()
assert ep.status == endpoint.Status.ENDPOINT_INACTIVE


@pytest.mark.asyncio
async def test_initialize_zha(ep):
return await _test_initialize(ep, 260)
Expand Down
8 changes: 4 additions & 4 deletions tests/test_group.py
Expand Up @@ -83,15 +83,15 @@ def test_add_group_no_evt(groups, monkeypatch):
def test_pop_group_id(groups, endpoint):
group = groups[FIXTURE_GRP_ID]
group.add_member(endpoint)
group.remove_member = mock.MagicMock()
group.remove_member = mock.MagicMock(side_effect=group.remove_member)
groups.listener_event.reset_mock()

assert FIXTURE_GRP_ID in groups
grp = groups.pop(FIXTURE_GRP_ID)

assert isinstance(grp, zigpy.group.Group)
assert FIXTURE_GRP_ID not in groups
assert groups.listener_event.call_count == 1
assert groups.listener_event.call_count == 2
assert group.remove_member.call_count == 1
assert group.remove_member.call_args[0][0] is endpoint

Expand All @@ -103,13 +103,13 @@ def test_pop_group(groups, endpoint):
assert FIXTURE_GRP_ID in groups
group = groups[FIXTURE_GRP_ID]
group.add_member(endpoint)
group.remove_member = mock.MagicMock()
group.remove_member = mock.MagicMock(side_effect=group.remove_member)
groups.listener_event.reset_mock()

grp = groups.pop(group)
assert isinstance(grp, zigpy.group.Group)
assert FIXTURE_GRP_ID not in groups
assert groups.listener_event.call_count == 1
assert groups.listener_event.call_count == 2
assert group.remove_member.call_count == 1
assert group.remove_member.call_args[0][0] is endpoint

Expand Down
13 changes: 9 additions & 4 deletions tests/test_ota_provider.py
Expand Up @@ -324,18 +324,23 @@ async def test_ikea_refresh_list_locked(mock_get, ikea_prov, image_with_version)
@pytest.mark.asyncio
@patch("aiohttp.ClientSession.get")
async def test_ikea_fetch_image(mock_get, image_with_version):
prefix = b"\x00This is extra data\x00\x55\xaa"
data = (
data = bytes.fromhex(
"1ef1ee0b0001380000007c11012178563412020054657374204f544120496d61"
"676500000000000000000000000000000000000042000000"
)
data = binascii.unhexlify(data)
sub_el = b"\x00\x00\x04\x00\x00\x00abcd"

container = bytearray(b"\x00This is extra data\x00\x55\xaa" * 100)
container[0:4] = b'NGIS'
container[16:20] = (512).to_bytes(4, 'little') # offset
container[20:24] = len(data + sub_el).to_bytes(4, 'little') # size
container[512:512 + len(data) + len(sub_el)] = data + sub_el

img = image_with_version(image_type=0x2101)
img.url = mock.sentinel.url

mock_get.return_value.__aenter__.return_value.read = CoroutineMock(
side_effect=[prefix + data + sub_el]
side_effect=[container]
)

r = await img.fetch_image()
Expand Down
38 changes: 0 additions & 38 deletions tests/test_quirks.py
Expand Up @@ -283,44 +283,6 @@ class MyCluster(zigpy.quirks.CustomCluster):
assert Device not in zigpy.quirks._DEVICE_REGISTRY


def test_kof_no_reply():
class TestCluster(zigpy.quirks.kof.NoReplyMixin, zigpy.quirks.CustomCluster):
cluster_id = 0x1234
void_input_commands = [0x0002]
server_commands = {
0x0001: ("noop", (), False),
0x0002: ("noop_noreply", (), False),
}
client_commands = {}

ep = mock.MagicMock()
cluster = TestCluster(ep)

cluster.command(0x0001)
ep.request.assert_called_with(
mock.ANY, mock.ANY, mock.ANY, expect_reply=True, command_id=mock.ANY
)
ep.reset_mock()

cluster.command(0x0001, expect_reply=False)
ep.request.assert_called_with(
mock.ANY, mock.ANY, mock.ANY, expect_reply=False, command_id=mock.ANY
)
ep.reset_mock()

cluster.command(0x0002)
ep.request.assert_called_with(
mock.ANY, mock.ANY, mock.ANY, expect_reply=False, command_id=mock.ANY
)
ep.reset_mock()

cluster.command(0x0002, expect_reply=True)
ep.request.assert_called_with(
mock.ANY, mock.ANY, mock.ANY, expect_reply=True, command_id=mock.ANY
)
ep.reset_mock()


def test_custom_cluster_idx():
class TestClusterIdx(zigpy.quirks.CustomCluster):
cluster_Id = 0x1234
Expand Down
2 changes: 1 addition & 1 deletion zigpy/__init__.py
@@ -1,6 +1,6 @@
# coding: utf-8
MAJOR_VERSION = 0
MINOR_VERSION = 11
MINOR_VERSION = 12
PATCH_VERSION = "0"
__short_version__ = "{}.{}".format(MAJOR_VERSION, MINOR_VERSION)
__version__ = "{}.{}".format(__short_version__, PATCH_VERSION)
34 changes: 34 additions & 0 deletions zigpy/appdb.py
Expand Up @@ -43,6 +43,7 @@ def __init__(self, database_file, application):
self._create_table_attributes()
self._create_table_groups()
self._create_table_group_members()
self._create_table_relays()

self._application = application

Expand All @@ -64,6 +65,14 @@ def device_left(self, device):
def device_removed(self, device):
self._remove_device(device)

def device_relays_updated(self, device, relays):
"""Device relay list is updated."""
if relays is None:
self._save_device_relays_clear(device.ieee)
return

self._save_device_relays_update(device.ieee, t.Relays(relays).serialize())

def attribute_updated(self, cluster, attrid, value):
self._save_attribute(
cluster.endpoint.device.ieee,
Expand Down Expand Up @@ -161,6 +170,14 @@ def _create_table_group_members(self):
"group_members_idx", "group_members", "group_id, ieee, endpoint_id"
)

def _create_table_relays(self):
self._create_table(
"relays",
"""(ieee ieee, relays,
FOREIGN KEY(ieee) REFERENCES devices(ieee) ON DELETE CASCADE)""",
)
self._create_index("relays_idx", "relays", "ieee")

def _enable_foreign_keys(self):
self.execute("PRAGMA foreign_keys = ON")

Expand Down Expand Up @@ -237,6 +254,15 @@ def _save_attribute(self, ieee, endpoint_id, cluster_id, attrid, value):
self.execute(q, (ieee, endpoint_id, cluster_id, attrid, value))
self._db.commit()

def _save_device_relays_update(self, ieee, value):
q = "INSERT OR REPLACE INTO relays VALUES (?, ?)"
self.execute(q, (ieee, value))
self._db.commit()

def _save_device_relays_clear(self, ieee):
self.execute("DELETE FROM relays WHERE ieee = ?", (ieee,))
self._db.commit()

def _scan(self, table, filter=None):
if filter is None:
return self.execute("SELECT * FROM %s" % (table,))
Expand Down Expand Up @@ -282,6 +308,7 @@ def _load_attributes(filter=None):
_load_attributes()
self._load_groups()
self._load_group_members()
self._load_relays()

def _load_devices(self):
for (ieee, nwk, status) in self._scan("devices"):
Expand Down Expand Up @@ -329,3 +356,10 @@ def _load_group_members(self):
group.add_member(
self._application.get_device(ieee).endpoints[ep_id], suppress_event=True
)

def _load_relays(self):
for (ieee, value) in self._scan("relays"):
dev = self._application.get_device(ieee)
dev.relays = t.Relays.deserialize(value)[0]
for dev in self._application.devices.values():
dev.add_context_listener(self)

0 comments on commit 4f314a3

Please sign in to comment.