Skip to content

Commit

Permalink
Merge branch 'release/0.0.40'
Browse files Browse the repository at this point in the history
  • Loading branch information
dmulcahey committed Jun 14, 2020
2 parents 63f7209 + b363b79 commit 1fbb273
Show file tree
Hide file tree
Showing 8 changed files with 515 additions and 5 deletions.
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.39"
VERSION = "0.0.40"


def readme():
Expand Down
16 changes: 13 additions & 3 deletions zhaquirks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,18 @@ def __init__(self, *args, **kwargs):
class LocalDataCluster(CustomCluster):
"""Cluster meant to prevent remote calls."""

async def bind(self):
"""Prevent bind."""
return (foundation.Status.SUCCESS,)

async def unbind(self):
"""Prevent unbind."""
return (foundation.Status.SUCCESS,)

async def _configure_reporting(self, *args, **kwargs):
"""Prevent remote configure reporting."""
return foundation.ConfigureReportingResponse.deserialize(b"\x00")[0]

async def read_attributes_raw(self, attributes, manufacturer=None):
"""Prevent remote reads."""
records = [
Expand Down Expand Up @@ -142,15 +154,13 @@ class PowerConfigurationCluster(CustomCluster, PowerConfiguration):

def _update_attribute(self, attrid, value):
super()._update_attribute(attrid, value)
if attrid == self.BATTERY_VOLTAGE_ATTR:
if attrid == self.BATTERY_VOLTAGE_ATTR and value not in (0, 255):
super()._update_attribute(
self.BATTERY_PERCENTAGE_REMAINING,
self._calculate_battery_percentage(value),
)

def _calculate_battery_percentage(self, raw_value):
if raw_value in (0, 255):
return -1
volts = raw_value / 10
volts = max(volts, self.MIN_VOLTS)
volts = min(volts, self.MAX_VOLTS)
Expand Down
148 changes: 148 additions & 0 deletions zhaquirks/eurotronic/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
"""Eurotronic devices."""

import logging

import zigpy.types as types
from zigpy.quirks import CustomCluster
from zigpy.zcl import foundation
from zigpy.zcl.clusters.hvac import Thermostat


EUROTRONIC = "Eurotronic"

THERMOSTAT_CHANNEL = "thermostat"

MANUFACTURER = 0x1037 # 4151

OCCUPIED_HEATING_SETPOINT_ATTR = 0x0012
CTRL_SEQ_OF_OPER_ATTR = 0x001B
SYSTEM_MODE_ATTR = 0x001C

TRV_MODE_ATTR = 0x4000
SET_VALVE_POS_ATTR = 0x4001
ERRORS_ATTR = 0x4002
CURRENT_TEMP_SETPOINT_ATTR = 0x4003
HOST_FLAGS_ATTR = 0x4008


# Host Flags
# unknown (defaults to 1) = 0b00000001 # 1
MIRROR_SCREEN_FLAG = 0b00000010 # 2
BOOST_FLAG = 0b00000100 # 4
# unknown = 0b00001000 # 8
CLR_OFF_MODE_FLAG = 0b00010000 # 16
SET_OFF_MODE_FLAG = 0b00100000 # 32, reported back as 16
# unknown = 0b01000000 # 64
CHILD_LOCK_FLAG = 0b10000000 # 128


_LOGGER = logging.getLogger(__name__)


class ThermostatCluster(CustomCluster, Thermostat):
"""Thermostat cluster."""

cluster_id = Thermostat.cluster_id

attributes = {
TRV_MODE_ATTR: ("trv_mode", types.enum8),
SET_VALVE_POS_ATTR: ("set_valve_position", types.uint8_t),
ERRORS_ATTR: ("errors", types.uint8_t),
CURRENT_TEMP_SETPOINT_ATTR: ("current_temperature_setpoint", types.int16s),
HOST_FLAGS_ATTR: ("host_flags", types.uint24_t),
}
attributes.update(Thermostat.attributes)

def _update_attribute(self, attrid, value):
_LOGGER.debug("update attribute %04x to %s... ", attrid, value)

if attrid == CURRENT_TEMP_SETPOINT_ATTR:
super()._update_attribute(OCCUPIED_HEATING_SETPOINT_ATTR, value)
elif attrid == HOST_FLAGS_ATTR:
if value & CLR_OFF_MODE_FLAG == CLR_OFF_MODE_FLAG:
super()._update_attribute(SYSTEM_MODE_ATTR, 0x0)
_LOGGER.debug("set system_mode to [off ]")
else:
super()._update_attribute(SYSTEM_MODE_ATTR, 0x4)
_LOGGER.debug("set system_mode to [heat]")

_LOGGER.debug("update attribute %04x to %s... [ ok ]", attrid, value)
super()._update_attribute(attrid, value)

async def read_attributes_raw(self, attributes, manufacturer=None):
"""Override wrong attribute reports from the thermostat."""
success = []
error = []

if CTRL_SEQ_OF_OPER_ATTR in attributes:
rar = foundation.ReadAttributeRecord(
CTRL_SEQ_OF_OPER_ATTR, foundation.Status.SUCCESS, foundation.TypeValue()
)
rar.value.value = 0x2
success.append(rar)

if SYSTEM_MODE_ATTR in attributes:
rar = foundation.ReadAttributeRecord(
SYSTEM_MODE_ATTR, foundation.Status.SUCCESS, foundation.TypeValue()
)
rar.value.value = 0x4
success.append(rar)

if OCCUPIED_HEATING_SETPOINT_ATTR in attributes:

_LOGGER.debug("intercepting OCC_HS")

values = await super().read_attributes_raw(
[CURRENT_TEMP_SETPOINT_ATTR], manufacturer=MANUFACTURER
)

if len(values) == 2:
current_temp_setpoint = values[1][0]
current_temp_setpoint.attrid = OCCUPIED_HEATING_SETPOINT_ATTR

error.extend(values[1])
else:
current_temp_setpoint = values[0][0]
current_temp_setpoint.attrid = OCCUPIED_HEATING_SETPOINT_ATTR

success.extend(values[0])

attributes = list(
filter(
lambda x: x
not in (
CTRL_SEQ_OF_OPER_ATTR,
SYSTEM_MODE_ATTR,
OCCUPIED_HEATING_SETPOINT_ATTR,
),
attributes,
)
)

if attributes:
values = await super().read_attributes_raw(attributes, manufacturer)

success.extend(values[0])

if len(values) == 2:
error.extend(values[1])

return success, error

def write_attributes(self, attributes, manufacturer=None):
"""Override wrong writes to thermostat attributes."""
if "system_mode" in attributes:

host_flags = self._attr_cache.get(HOST_FLAGS_ATTR, 1)
_LOGGER.debug("current host_flags: %s", host_flags)

if attributes.get("system_mode") == 0x0:
return super().write_attributes(
{"host_flags": host_flags | SET_OFF_MODE_FLAG}, MANUFACTURER
)
if attributes.get("system_mode") == 0x4:
return super().write_attributes(
{"host_flags": host_flags | CLR_OFF_MODE_FLAG}, MANUFACTURER
)

return super().write_attributes(attributes, manufacturer)
85 changes: 85 additions & 0 deletions zhaquirks/eurotronic/spzb0001.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
"""Eurotronic Spirit Zigbee quirk."""

from zigpy.profiles import zha
from zigpy.quirks import CustomDevice
from zigpy.zcl.clusters.general import (
Basic,
Groups,
Identify,
Ota,
PowerConfiguration,
Time,
)
from zigpy.zcl.clusters.hvac import Thermostat

from zhaquirks.const import (
DEVICE_TYPE,
ENDPOINTS,
INPUT_CLUSTERS,
MODELS_INFO,
OUTPUT_CLUSTERS,
PROFILE_ID,
)

from . import EUROTRONIC, ThermostatCluster


class SPZB0001(CustomDevice):
"""Eurotronic Spirit Zigbee device."""

signature = {
# <SimpleDescriptor endpoint=1 profile=260 device_type=769
# device_version=1
# input_clusters=[0, 1, 3, 513, 25, 10]
# output_clusters=[0, 1, 3, 4, 513, 25, 10]>
MODELS_INFO: [(EUROTRONIC, "SPZB0001")],
ENDPOINTS: {
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.THERMOSTAT,
INPUT_CLUSTERS: [
Basic.cluster_id,
PowerConfiguration.cluster_id,
Identify.cluster_id,
Thermostat.cluster_id,
Ota.cluster_id,
Time.cluster_id,
],
OUTPUT_CLUSTERS: [
Basic.cluster_id,
PowerConfiguration.cluster_id,
Identify.cluster_id,
Groups.cluster_id,
Thermostat.cluster_id,
Ota.cluster_id,
Time.cluster_id,
],
}
},
}

replacement = {
ENDPOINTS: {
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.THERMOSTAT,
INPUT_CLUSTERS: [
Basic.cluster_id,
PowerConfiguration.cluster_id,
Identify.cluster_id,
ThermostatCluster,
Ota.cluster_id,
Time.cluster_id,
],
OUTPUT_CLUSTERS: [
Basic.cluster_id,
PowerConfiguration.cluster_id,
Identify.cluster_id,
Groups.cluster_id,
ThermostatCluster,
Ota.cluster_id,
Time.cluster_id,
],
}
}
}
61 changes: 61 additions & 0 deletions zhaquirks/ikea/fivebtnremotezha.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from zigpy.profiles import zha
from zigpy.quirks import CustomDevice
from zigpy.zcl.clusters.general import (
Alarms,
Basic,
Groups,
Identify,
Expand All @@ -11,6 +12,7 @@
PollControl,
PowerConfiguration,
)
from zigpy.zcl.clusters.homeautomation import Diagnostic
from zigpy.zcl.clusters.lightlink import LightLink

from .. import DoublingPowerConfigurationCluster
Expand Down Expand Up @@ -166,3 +168,62 @@ class IkeaTradfriRemote(CustomDevice):
ARGS: [3328, 0],
},
}


class IkeaTradfriRemote2(IkeaTradfriRemote):
"""Custom device representing IKEA of Sweden TRADFRI 5 button remote control."""

signature = {
# <SimpleDescriptor endpoint = 1 profile = 260 device_type = 2064
# device_version = 2 input_clusters = [0, 1, 3, 9, 2821, 4096]
# output_clusters = [3, 4, 5, 6, 8, 25, 4096]
MODELS_INFO: [(IKEA, "TRADFRI remote control")],
ENDPOINTS: {
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.COLOR_SCENE_CONTROLLER,
INPUT_CLUSTERS: [
Basic.cluster_id,
PowerConfiguration.cluster_id,
Identify.cluster_id,
Alarms.cluster_id,
Diagnostic.cluster_id,
LightLink.cluster_id,
],
OUTPUT_CLUSTERS: [
Identify.cluster_id,
Groups.cluster_id,
ScenesCluster.cluster_id,
OnOff.cluster_id,
LevelControl.cluster_id,
Ota.cluster_id,
LightLink.cluster_id,
],
}
},
}

replacement = {
ENDPOINTS: {
1: {
PROFILE_ID: zha.PROFILE_ID,
DEVICE_TYPE: zha.DeviceType.COLOR_SCENE_CONTROLLER,
INPUT_CLUSTERS: [
Basic.cluster_id,
DoublingPowerConfigurationCluster,
Identify.cluster_id,
Alarms.cluster_id,
LightLinkCluster,
],
OUTPUT_CLUSTERS: [
Identify.cluster_id,
Groups.cluster_id,
ScenesCluster,
OnOff.cluster_id,
LevelControl.cluster_id,
Ota.cluster_id,
LightLink.cluster_id,
],
}
}
}
Loading

0 comments on commit 1fbb273

Please sign in to comment.