Skip to content

Commit

Permalink
Merge branch 'release/0.0.33'
Browse files Browse the repository at this point in the history
  • Loading branch information
dmulcahey committed Feb 19, 2020
2 parents 0f23671 + 033b164 commit ce3e14c
Show file tree
Hide file tree
Showing 32 changed files with 416 additions and 102 deletions.
44 changes: 33 additions & 11 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,42 @@
## Initial Contribution Guidelines - WIP
# Initial Contribution Guidelines - WIP

- Capture the SimpleDescriptor log entries for each endpoint on the device. These can be found in the HA logs after joining a device and they look like this: `<SimpleDescriptor endpoint=1 profile=260 device_type=1026 device_version=0 input_clusters=[0, 1, 3, 32, 1026, 1280, 2821] output_clusters=[25]>`. This information can also be obtained from the zigbee.db if you want to take the time to query the tables and reconstitute the log entry. I find it easier to just remove and rejoin the device. ZHA entity ids are stable for the most part so it *shouldn't* disrupt anything you have configured. These need to match what the device reports EXACTLY or zigpy will not match them when a device joins and the handler will not be used for the device.
- All code is formatted with black. The check format script that runs in CI will ensure that code meets this requirement and that it is correctly formatted with black. Instructions for installing black in many editors can be found here: <https://github.com/psf/black#editor-integration>

- Capture the SimpleDescriptor log entries for each endpoint on the device. These can be found in the HA logs after joining a device and they look like this: `<SimpleDescriptor endpoint=1 profile=260 device_type=1026 device_version=0 input_clusters=[0, 1, 3, 32, 1026, 1280, 2821] output_clusters=[25]>`. This information can also be obtained from the zigbee.db if you want to take the time to query the tables and reconstitute the log entry. I find it easier to just remove and rejoin the device. ZHA entity ids are stable for the most part so it _shouldn't_ disrupt anything you have configured. These need to match what the device reports EXACTLY or zigpy will not match them when a device joins and the handler will not be used for the device.

- Create a device class extending CustomDevice or a derivitave of it

- Use an existing handler as a guide. signature and replacement dicts are required. Include the SimpleDescriptor entry for each endpoint in the signature dict above the definition of the endpoint in this format:
- All custom cluster definitions must extend CustomCluster

- Use an existing handler as a guide. signature and replacement dicts are required. Include the SimpleDescriptor entry for each endpoint in the signature dict above the definition of the endpoint in this format:

```yaml
# <SimpleDescriptor endpoint=1 profile=260 device_type=1026
# device_version=0
# input_clusters=[0, 1, 3, 32, 1026, 1280, 2821]
# output_clusters=[25]>
```

- Use constants for all attribute values referencing the appropriate labels from Zigpy / HA as necessary

- how `device_automation_triggers` work:

Device automation triggers are essentially representations of the events that the devices fire in HA. They allow users to use actions in the UI instead of using the raw events. Ex: For the Hue remote - the on button fires this event:

`<Event zha_event[L]: unique_id=00:17:88:01:04:e7:f9:37:1:0x0006, device_ieee=00:17:88:01:04:e7:f9:37, endpoint_id=1, cluster_id=6, command=on, args=[]>`

and the action defined for this is:

`(SHORT_PRESS, TURN_ON): {COMMAND: COMMAND_ON}`

The first part `(SHORT_PRESS, TURN_ON)` corresponds to the txt the user will see in the UI:

```yaml
<img width="620" alt="image" src="https://user-images.githubusercontent.com/1335687/73609115-76480b80-4598-11ea-97eb-8d8343e2355b.png">

# <SimpleDescriptor endpoint=1 profile=260 device_type=1026
# device_version=0
# input_clusters=[0, 1, 3, 32, 1026, 1280, 2821]
# output_clusters=[25]>
The second part is the event data. You only need to supply enough of the event data to uniquely match the event which in this case is just the command for this event fired by this device: `{COMMAND: COMMAND_ON}`

```
If you look at another example for the same device:

- manufacturer and model are required on EVERY replacement endpoint definition and they NEED to match what the device reports to HA.
`(SHORT_PRESS, DIM_UP): {COMMAND: COMMAND_STEP, CLUSTER_ID: 8, ENDPOINT_ID: 1, ARGS: [0, 30, 9],}`

- Use constants for all attribute values referencing the appropriate labels from Zigpy / HA as necessary
you can see a pattern that illustrates how to match a more complex event. In this case the step command is used for the dim up and dim down buttons so we need to match more of the event data to uniquely match the event.
34 changes: 18 additions & 16 deletions Contributors.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
# Contributors
- [David F. Mulcahey] (https://github.com/dmulcahey)
- [roblandry] (https://github.com/roblandry)
- [presslab-us] (https://github.com/presslab-us)
- [Alexei Chetroi] (https://github.com/Adminiuga)
- [Abílio Costa] (https://github.com/abmantis)
- [Andreas Setterlind] (https://github.com/Gamester17)
- [prairiesnpr] (https://github.com/prairiesnpr)
- [Daniel Lashua] (https://github.com/dlashua)
- [bballwiz5] (https://github.com/bballwiz5)
- [Ross Patterson] (https://github.com/rpatterson)
- [Marc Egli] (https://github.com/frog32)
- [Dinko Bajric] (https://github.com/dbajric)
- [brg468] (https://github.com/brg468)
- [James Riley] (https://github.com/Thalagyrt)
- [Shulyaka] (https://github.com/Shulyaka)
- [Nemesis24] (https://github.com/Nemesis24)

- [David F. Mulcahey](https://github.com/dmulcahey)
- [roblandry](https://github.com/roblandry)
- [presslab-us](https://github.com/presslab-us)
- [Alexei Chetroi](https://github.com/Adminiuga)
- [Abílio Costa](https://github.com/abmantis)
- [Andreas Setterlind](https://github.com/Gamester17)
- [prairiesnpr](https://github.com/prairiesnpr)
- [Daniel Lashua](https://github.com/dlashua)
- [bballwiz5](https://github.com/bballwiz5)
- [Ross Patterson](https://github.com/rpatterson)
- [Marc Egli](https://github.com/frog32)
- [Dinko Bajric](https://github.com/dbajric)
- [brg468](https://github.com/brg468)
- [James Riley](https://github.com/Thalagyrt)
- [Shulyaka](https://github.com/Shulyaka)
- [Nemesis24](https://github.com/Nemesis24)
- [Andy Zickler](https://github.com/andyzickler)
- [Piotr Majkrzak](https://github.com/majkrzak)
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,25 @@ Please refer to [xbee.md](xbee.md) for details on configuration and usage exampl

- All supported devices report battery level

### Thanks
# Thanks

- Special thanks to damarco for the majority of the device tracker code
- Special thanks to Yoda-x for the Xioami attribute parsing code
- Special thanks to damarco and Adminiuga for allowing me to bounce ideas off of them and for listening to me ramble

# Related projects

### Zigpy
**[zigpy](https://github.com/zigpy/zigpy)** is **[Zigbee protocol stack](https://en.wikipedia.org/wiki/Zigbee)** integration project to implement the **[Zigbee Home Automation](https://www.zigbee.org/)** standard as a Python 3 library. Zigbee Home Automation integration with zigpy allows you to connect one of many off-the-shelf Zigbee adapters using one of the available Zigbee radio library modules compatible with zigpy to control Zigbee based devices. There is currently support for controlling Zigbee device types such as binary sensors (e.g., motion and door sensors), sensors (e.g., temperature sensors), lightbulbs, switches, locks, fans, covers (blinds, marquees, and more). A working implementation of zigpy exist in **[Home Assistant](https://www.home-assistant.io)** (Python based open source home automation software) as part of its **[ZHA component](https://www.home-assistant.io/components/zha/)**

### ZHA Map
[zha-map](https://github.com/zha-ng/zha-map) project allow building a Zigbee network topology map for ZHA component in Home Assistant.

### zha-network-visualization-card
[zha-network-visualization-card](https://github.com/dmulcahey/zha-network-visualization-card) is a custom Lovelace element for visualizing the Zigbee network map for ZHA component in Home Assistant.

### ZHA Network Card
[zha-network-card](https://github.com/dmulcahey/zha-network-card) is a custom Lovelace card that displays ZHA network and device information in Home Assistant

### zigpy-deconz-parser
[zigpy-deconz-parser](https://github.com/zha-ng/zigpy-deconz-parser) project can parse Home Assistant ZHA component debug log using `zigpy-deconz` library if you have ConBee or RaspBee hardware.
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from setuptools import find_packages, setup

VERSION = "0.0.32"
VERSION = "0.0.33"


def readme():
Expand Down
11 changes: 11 additions & 0 deletions zhaquirks/centralite/3460L.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,18 @@
from zhaquirks import PowerConfigurationCluster
from zhaquirks.centralite import CENTRALITE
from zhaquirks.const import (
BUTTON_1,
COMMAND,
COMMAND_OFF,
COMMAND_ON,
DEVICE_TYPE,
ENDPOINTS,
INPUT_CLUSTERS,
MODELS_INFO,
OUTPUT_CLUSTERS,
PROFILE_ID,
SHORT_PRESS,
SHORT_RELEASE,
)

DIAGNOSTICS_CLUSTER_ID = 0x0B05 # decimal = 2821
Expand Down Expand Up @@ -85,3 +91,8 @@ class CentraLite3460L(CustomDevice):
}
}
}

device_automation_triggers = {
(SHORT_PRESS, BUTTON_1): {COMMAND: COMMAND_ON},
(SHORT_RELEASE, BUTTON_1): {COMMAND: COMMAND_OFF},
}
2 changes: 2 additions & 0 deletions zhaquirks/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@
RIGHT = "right"
SHAKEN = "device_shaken"
SHORT_PRESS = "remote_button_short_press"
SKIP_CONFIGURATION = "skip_configuration"
SHORT_RELEASE = "remote_button_short_release"
TRIPLE_PRESS = "remote_button_triple_press"
TURN_OFF = "turn_off"
TURN_ON = "turn_on"
Expand Down
10 changes: 8 additions & 2 deletions zhaquirks/ikea/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,13 @@ async def bind(self):
_LOGGER.warning("Aborting - unable to locate required coordinator device.")
return
group_list = await self.get_group_identifiers(0)
group_record = group_list[2]
group_id = group_record[0].group_id
try:
group_record = group_list[2]
group_id = group_record[0].group_id
except IndexError:
_LOGGER.warning(
"unable to locate required group info - falling back to group 0x0000."
)
group_id = 0x0000
status = await coordinator.add_to_group(group_id)
return [status]
18 changes: 2 additions & 16 deletions zhaquirks/ikea/motionzha.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Device handler for IKEA of Sweden TRADFRI remote control."""
from zigpy.profiles import zha
from zigpy.quirks import CustomCluster, CustomDevice
from zigpy.quirks import CustomDevice
from zigpy.zcl.clusters.general import (
Alarms,
Basic,
Expand Down Expand Up @@ -29,20 +29,6 @@
DIAGNOSTICS_CLUSTER_ID = 0x0B05 # decimal = 2821


class LightLinkClusterNew(CustomCluster, LightLink):
"""Ikea LightLink cluster."""

async def bind(self):
"""Bind LightLink cluster to coordinator."""
application = self._endpoint.device.application
try:
coordinator = application.get_device(application.ieee)
except KeyError:
return
status = await coordinator.add_to_group(0x0000)
return [status]


class IkeaTradfriMotion(CustomDevice):
"""Custom device representing IKEA of Sweden TRADFRI remote control."""

Expand Down Expand Up @@ -145,7 +131,7 @@ class IkeaTradfriMotionE1745(CustomDevice):
Identify.cluster_id,
Alarms.cluster_id,
PollControl.cluster_id,
LightLinkClusterNew,
LightLinkCluster,
IKEA_CLUSTER_ID,
],
OUTPUT_CLUSTERS: [
Expand Down
18 changes: 2 additions & 16 deletions zhaquirks/ikea/twobtnremote.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Device handler for IKEA of Sweden TRADFRI remote control."""
from zigpy.profiles import zha, zll
from zigpy.quirks import CustomCluster, CustomDevice
from zigpy.quirks import CustomDevice
from zigpy.zcl.clusters.closures import WindowCovering
from zigpy.zcl.clusters.general import (
Alarms,
Expand All @@ -15,7 +15,7 @@
)
from zigpy.zcl.clusters.lightlink import LightLink

from . import IKEA
from . import IKEA, LightLinkCluster
from .. import DoublingPowerConfigurationCluster
from ..const import (
ARGS,
Expand Down Expand Up @@ -43,20 +43,6 @@
IKEA_CLUSTER_ID = 0xFC7C # decimal = 64636


class LightLinkCluster(CustomCluster, LightLink):
"""Ikea LightLink cluster."""

async def bind(self):
"""Bind LightLink cluster to coordinator."""
application = self._endpoint.device.application
try:
coordinator = application.get_device(application.ieee)
except KeyError:
return
status = await coordinator.add_to_group(0x0000)
return [status]


class IkeaTradfriRemote2Btn(CustomDevice):
"""Custom device representing IKEA of Sweden TRADFRI remote control."""

Expand Down
70 changes: 70 additions & 0 deletions zhaquirks/konke/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
"""Konke sensors."""

import asyncio

from zigpy.quirks import CustomCluster
from zigpy.zcl.clusters.measurement import OccupancySensing
from zigpy.zcl.clusters.security import IasZone

from .. import LocalDataCluster
from ..const import CLUSTER_COMMAND, OFF, ON, ZONE_STATE

KONKE = "Konke"
OCCUPANCY_STATE = 0
OCCUPANCY_EVENT = "occupancy_event"
MOTION_TYPE = 0x000D
ZONE_TYPE = 0x0001

MOTION_TIME = 60
OCCUPANCY_TIME = 600


class OccupancyCluster(LocalDataCluster, OccupancySensing):
"""Occupancy cluster."""

cluster_id = OccupancySensing.cluster_id

def __init__(self, *args, **kwargs):
"""Init."""
super().__init__(*args, **kwargs)
self._timer_handle = None
self.endpoint.device.occupancy_bus.add_listener(self)

def occupancy_event(self):
"""Occupancy event."""
self._update_attribute(OCCUPANCY_STATE, ON)

if self._timer_handle:
self._timer_handle.cancel()

loop = asyncio.get_event_loop()
self._timer_handle = loop.call_later(OCCUPANCY_TIME, self._turn_off)

def _turn_off(self):
self._timer_handle = None
self._update_attribute(OCCUPANCY_STATE, OFF)


class MotionCluster(CustomCluster, IasZone):
"""Motion cluster."""

cluster_id = IasZone.cluster_id

def __init__(self, *args, **kwargs):
"""Init."""
super().__init__(*args, **kwargs)
self._timer_handle = None

def handle_cluster_request(self, tsn, command_id, args):
"""Handle the cluster command."""
if command_id == 0:
if self._timer_handle:
self._timer_handle.cancel()
loop = asyncio.get_event_loop()
self._timer_handle = loop.call_later(MOTION_TIME, self._turn_off)
self.endpoint.device.occupancy_bus.listener_event(OCCUPANCY_EVENT)

def _turn_off(self):
self._timer_handle = None
self.listener_event(CLUSTER_COMMAND, 999, 0, [0, 0, 0, 0])
self._update_attribute(ZONE_STATE, OFF)
70 changes: 70 additions & 0 deletions zhaquirks/konke/motion.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
"""Konke motion sensor."""

from zigpy.profiles import zha
from zigpy.quirks import CustomDevice
from zigpy.zcl.clusters.general import Basic, Identify, PowerConfiguration
from zigpy.zcl.clusters.security import IasZone

from . import KONKE, MotionCluster, OccupancyCluster
from .. import Bus
from ..const import (
DEVICE_TYPE,
ENDPOINTS,
INPUT_CLUSTERS,
MODELS_INFO,
OUTPUT_CLUSTERS,
PROFILE_ID,
)

KONKE_CLUSTER_ID = 0xFCC0


class KonkeMotion(CustomDevice):
"""Custom device representing konke motion sensors."""

def __init__(self, *args, **kwargs):
"""Init."""
self.occupancy_bus = Bus()
super().__init__(*args, **kwargs)

signature = {
# <SimpleDescriptor endpoint=1 profile=260 device_type=1026
# device_version=0
# input_clusters=[0, 1, 3, 1280, 64704]
# output_clusters=[3, 64704]>
MODELS_INFO: [
(KONKE, "3AFE28010402000D"),
(KONKE, "3AFE14010402000D"),
(KONKE, "3AFE27010402000D"),
],
ENDPOINTS: {
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.IAS_ZONE,
INPUT_CLUSTERS: [
Basic.cluster_id,
PowerConfiguration.cluster_id,
Identify.cluster_id,
IasZone.cluster_id,
KONKE_CLUSTER_ID,
],
OUTPUT_CLUSTERS: [Identify.cluster_id, KONKE_CLUSTER_ID],
}
},
}

replacement = {
ENDPOINTS: {
1: {
INPUT_CLUSTERS: [
Basic.cluster_id,
PowerConfiguration.cluster_id,
Identify.cluster_id,
OccupancyCluster,
MotionCluster,
KONKE_CLUSTER_ID,
],
OUTPUT_CLUSTERS: [Identify.cluster_id, KONKE_CLUSTER_ID],
}
}
}
Loading

0 comments on commit ce3e14c

Please sign in to comment.