From 3d23799bcfb88c2a207bc6ad2ab4dcc03b727949 Mon Sep 17 00:00:00 2001 From: David Mulcahey Date: Sun, 18 Aug 2019 10:57:33 -0400 Subject: [PATCH 1/9] add ias warning device support --- homeassistant/components/zha/binary_sensor.py | 62 ++++++++++++ .../components/zha/core/channels/security.py | 98 ++++++++++++++++++- homeassistant/components/zha/core/const.py | 25 +++++ .../components/zha/core/registries.py | 1 + 4 files changed, 184 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/zha/binary_sensor.py b/homeassistant/components/zha/binary_sensor.py index 24c2b92e73927..5d96bc68dc43f 100644 --- a/homeassistant/components/zha/binary_sensor.py +++ b/homeassistant/components/zha/binary_sensor.py @@ -19,6 +19,7 @@ from .core.const import ( CHANNEL_ATTRIBUTE, + CHANNEL_IAS_WD, CHANNEL_ON_OFF, CHANNEL_ZONE, DATA_ZHA, @@ -29,6 +30,11 @@ SENSOR_TYPE, SIGNAL_ATTR_UPDATED, UNKNOWN, + WARNING_DEVICE_MODE_EMERGENCY, + WARNING_DEVICE_SOUND_HIGH, + WARNING_DEVICE_SQUAWK_MODE_ARMED, + WARNING_DEVICE_STROBE_HIGH, + WARNING_DEVICE_STROBE_YES, ZHA_DISCOVERY_NEW, ZONE, ) @@ -177,3 +183,59 @@ async def async_update(self): self._state = await self._attr_channel.get_attribute_value( self._attr_channel.value_attribute ) + + +class WarningDevice(BinarySensor): + """ZHA IAS warning device.""" + + def __init__(self, **kwargs): + """Initialize the ZHA IAS warning sensor.""" + super().__init__(**kwargs) + self._ias_wd_channel = self.cluster_channels.get(CHANNEL_IAS_WD) + + async def squawk( + self, + mode=WARNING_DEVICE_SQUAWK_MODE_ARMED, + strobe=WARNING_DEVICE_STROBE_YES, + squawk_level=WARNING_DEVICE_SOUND_HIGH, + ): + """Issue a squawk command. + + This command uses the WD capabilities to emit a quick audible/visible pulse called a + "squawk". The squawk command has no effect if the WD is currently active + (warning in progress). + """ + await self._ias_wd_channel.squawk( + mode=mode, strobe=strobe, squawk_level=squawk_level + ) + + async def start_warning( + self, + mode=WARNING_DEVICE_MODE_EMERGENCY, + strobe=WARNING_DEVICE_STROBE_YES, + siren_level=WARNING_DEVICE_SOUND_HIGH, + warning_duration=5, # seconds + strobe_duty_cycle=0x00, + strobe_intensity=WARNING_DEVICE_STROBE_HIGH, + ): + """Issue a start warning command. + + This command starts the WD operation. The WD alerts the surrounding area by audible + (siren) and visual (strobe) signals. + + strobe_duty_cycle indicates the length of the flash cycle. This provides a means + of varying the flash duration for different alarm types (e.g., fire, police, burglar). + Valid range is 0-100 in increments of 10. All other values SHALL be rounded to the + nearest valid value. Strobe SHALL calculate duty cycle over a duration of one second. + The ON state SHALL precede the OFF state. For example, if Strobe Duty Cycle Field specifies + “40,” then the strobe SHALL flash ON for 4/10ths of a second and then turn OFF for + 6/10ths of a second. + """ + await self._ias_wd_channel.squawk( + mode=mode, + strobe=strobe, + siren_level=siren_level, + warning_duration=warning_duration, + strobe_duty_cycle=strobe_duty_cycle, + strobe_intensity=strobe_intensity, + ) diff --git a/homeassistant/components/zha/core/channels/security.py b/homeassistant/components/zha/core/channels/security.py index cd407cfc416b6..39dfa040cdccc 100644 --- a/homeassistant/components/zha/core/channels/security.py +++ b/homeassistant/components/zha/core/channels/security.py @@ -13,7 +13,15 @@ from . import ZigbeeChannel from .. import registries -from ..const import SIGNAL_ATTR_UPDATED +from ..const import ( + CLUSTER_COMMAND_SERVER, + SIGNAL_ATTR_UPDATED, + WARNING_DEVICE_MODE_EMERGENCY, + WARNING_DEVICE_SOUND_HIGH, + WARNING_DEVICE_SQUAWK_MODE_ARMED, + WARNING_DEVICE_STROBE_HIGH, + WARNING_DEVICE_STROBE_YES, +) _LOGGER = logging.getLogger(__name__) @@ -25,11 +33,97 @@ class IasAce(ZigbeeChannel): pass +@registries.BINARY_SENSOR_CLUSTERS.register(security.IasWd.cluster_id) @registries.ZIGBEE_CHANNEL_REGISTRY.register(security.IasWd.cluster_id) class IasWd(ZigbeeChannel): """IAS Warning Device channel.""" - pass + @staticmethod + def set_bit(destination_value, destination_bit, source_value, source_bit): + """Set the specified bit in the value.""" + + if IasWd.get_bit(source_value, source_bit): + return destination_value | (1 << destination_bit) + return destination_value + + @staticmethod + def get_bit(value, bit): + """Get the specified bit from the value.""" + return (value & (1 << bit)) != 0 + + async def squawk( + self, + mode=WARNING_DEVICE_SQUAWK_MODE_ARMED, + strobe=WARNING_DEVICE_STROBE_YES, + squawk_level=WARNING_DEVICE_SOUND_HIGH, + ): + """Issue a squawk command. + + This command uses the WD capabilities to emit a quick audible/visible pulse called a + "squawk". The squawk command has no effect if the WD is currently active + (warning in progress). + """ + value = 0 + value = IasWd.set_bit(value, 0, squawk_level, 0) + value = IasWd.set_bit(value, 1, squawk_level, 1) + + value = IasWd.set_bit(value, 3, strobe, 0) + + value = IasWd.set_bit(value, 4, mode, 0) + value = IasWd.set_bit(value, 5, mode, 1) + value = IasWd.set_bit(value, 6, mode, 2) + value = IasWd.set_bit(value, 7, mode, 3) + + self.warning("SQUAWKING!!!!!!!!!!!!!!!!!!!!!!!") + + await self.device.issue_cluster_command( + self.cluster.endpoint.endpoint_id, + self.cluster.cluster_id, + 0x0001, + CLUSTER_COMMAND_SERVER, + [value], + ) + + async def start_warning( + self, + mode=WARNING_DEVICE_MODE_EMERGENCY, + strobe=WARNING_DEVICE_STROBE_YES, + siren_level=WARNING_DEVICE_SOUND_HIGH, + warning_duration=5, # seconds + strobe_duty_cycle=0x00, + strobe_intensity=WARNING_DEVICE_STROBE_HIGH, + ): + """Issue a start warning command. + + This command starts the WD operation. The WD alerts the surrounding area by audible + (siren) and visual (strobe) signals. + + strobe_duty_cycle indicates the length of the flash cycle. This provides a means + of varying the flash duration for different alarm types (e.g., fire, police, burglar). + Valid range is 0-100 in increments of 10. All other values SHALL be rounded to the + nearest valid value. Strobe SHALL calculate duty cycle over a duration of one second. + The ON state SHALL precede the OFF state. For example, if Strobe Duty Cycle Field specifies + “40,” then the strobe SHALL flash ON for 4/10ths of a second and then turn OFF for + 6/10ths of a second. + """ + value = 0 + value = IasWd.set_bit(value, 0, siren_level, 0) + value = IasWd.set_bit(value, 1, siren_level, 1) + + value = IasWd.set_bit(value, 2, strobe, 0) + + value = IasWd.set_bit(value, 4, mode, 0) + value = IasWd.set_bit(value, 5, mode, 1) + value = IasWd.set_bit(value, 6, mode, 2) + value = IasWd.set_bit(value, 7, mode, 3) + + await self.device.issue_cluster_command( + self.cluster.endpoint.endpoint_id, + self.cluster.cluster_id, + 0x0000, + CLUSTER_COMMAND_SERVER, + [value, warning_duration, strobe_duty_cycle, strobe_intensity], + ) @registries.BINARY_SENSOR_CLUSTERS.register(security.IasZone.cluster_id) diff --git a/homeassistant/components/zha/core/const.py b/homeassistant/components/zha/core/const.py index c35cb168fdff3..5305fa88da561 100644 --- a/homeassistant/components/zha/core/const.py +++ b/homeassistant/components/zha/core/const.py @@ -44,6 +44,7 @@ CHANNEL_ELECTRICAL_MEASUREMENT = "electrical_measurement" CHANNEL_EVENT_RELAY = "event_relay" CHANNEL_FAN = "fan" +CHANNEL_IAS_WD = "ias_wd" CHANNEL_LEVEL = ATTR_LEVEL CHANNEL_ON_OFF = "on_off" CHANNEL_POWER_CONFIGURATION = "power" @@ -177,6 +178,30 @@ def list(cls): UNKNOWN_MANUFACTURER = "unk_manufacturer" UNKNOWN_MODEL = "unk_model" +WARNING_DEVICE_MODE_STOP = 0 +WARNING_DEVICE_MODE_BURGLAR = 1 +WARNING_DEVICE_MODE_FIRE = 2 +WARNING_DEVICE_MODE_EMERGENCY = 3 +WARNING_DEVICE_MODE_POLICE_PANIC = 4 +WARNING_DEVICE_MODE_FIRE_PANIC = 5 +WARNING_DEVICE_MODE_EMERGENCY_PANIC = 6 + +WARNING_DEVICE_STROBE_NO = 0 +WARNING_DEVICE_STROBE_YES = 1 + +WARNING_DEVICE_SOUND_LOW = 0 +WARNING_DEVICE_SOUND_MEDIUM = 1 +WARNING_DEVICE_SOUND_HIGH = 2 +WARNING_DEVICE_SOUND_VERY_HIGH = 3 + +WARNING_DEVICE_STROBE_LOW = 0x00 +WARNING_DEVICE_STROBE_MEDIUM = 0x01 +WARNING_DEVICE_STROBE_HIGH = 0x02 +WARNING_DEVICE_STROBE_VERY_HIGH = 0x03 + +WARNING_DEVICE_SQUAWK_MODE_ARMED = 0 +WARNING_DEVICE_SQUAWK_MODE_DISARMED = 1 + ZHA_DISCOVERY_NEW = "zha_discovery_new_{}" ZHA_GW_MSG_RAW_INIT = "raw_device_initialized" ZHA_GW_MSG = "zha_gateway_message" diff --git a/homeassistant/components/zha/core/registries.py b/homeassistant/components/zha/core/registries.py index db7e89dce822c..860ea9660bee5 100644 --- a/homeassistant/components/zha/core/registries.py +++ b/homeassistant/components/zha/core/registries.py @@ -152,6 +152,7 @@ def get_zigate_radio(): zha.DeviceType.ON_OFF_LIGHT_SWITCH: SWITCH, zha.DeviceType.ON_OFF_PLUG_IN_UNIT: SWITCH, zha.DeviceType.SMART_PLUG: SWITCH, + zha.DeviceType.IAS_WARNING_DEVICE: BINARY_SENSOR, } ) From 4f930f6c739adeb4cbbce9d30072486b9202104e Mon Sep 17 00:00:00 2001 From: David Mulcahey Date: Sun, 18 Aug 2019 11:32:30 -0400 Subject: [PATCH 2/9] use channel only clusters for warning devices --- homeassistant/components/zha/binary_sensor.py | 62 ------------------- .../components/zha/core/channels/security.py | 2 +- .../components/zha/core/registries.py | 1 - 3 files changed, 1 insertion(+), 64 deletions(-) diff --git a/homeassistant/components/zha/binary_sensor.py b/homeassistant/components/zha/binary_sensor.py index 5d96bc68dc43f..24c2b92e73927 100644 --- a/homeassistant/components/zha/binary_sensor.py +++ b/homeassistant/components/zha/binary_sensor.py @@ -19,7 +19,6 @@ from .core.const import ( CHANNEL_ATTRIBUTE, - CHANNEL_IAS_WD, CHANNEL_ON_OFF, CHANNEL_ZONE, DATA_ZHA, @@ -30,11 +29,6 @@ SENSOR_TYPE, SIGNAL_ATTR_UPDATED, UNKNOWN, - WARNING_DEVICE_MODE_EMERGENCY, - WARNING_DEVICE_SOUND_HIGH, - WARNING_DEVICE_SQUAWK_MODE_ARMED, - WARNING_DEVICE_STROBE_HIGH, - WARNING_DEVICE_STROBE_YES, ZHA_DISCOVERY_NEW, ZONE, ) @@ -183,59 +177,3 @@ async def async_update(self): self._state = await self._attr_channel.get_attribute_value( self._attr_channel.value_attribute ) - - -class WarningDevice(BinarySensor): - """ZHA IAS warning device.""" - - def __init__(self, **kwargs): - """Initialize the ZHA IAS warning sensor.""" - super().__init__(**kwargs) - self._ias_wd_channel = self.cluster_channels.get(CHANNEL_IAS_WD) - - async def squawk( - self, - mode=WARNING_DEVICE_SQUAWK_MODE_ARMED, - strobe=WARNING_DEVICE_STROBE_YES, - squawk_level=WARNING_DEVICE_SOUND_HIGH, - ): - """Issue a squawk command. - - This command uses the WD capabilities to emit a quick audible/visible pulse called a - "squawk". The squawk command has no effect if the WD is currently active - (warning in progress). - """ - await self._ias_wd_channel.squawk( - mode=mode, strobe=strobe, squawk_level=squawk_level - ) - - async def start_warning( - self, - mode=WARNING_DEVICE_MODE_EMERGENCY, - strobe=WARNING_DEVICE_STROBE_YES, - siren_level=WARNING_DEVICE_SOUND_HIGH, - warning_duration=5, # seconds - strobe_duty_cycle=0x00, - strobe_intensity=WARNING_DEVICE_STROBE_HIGH, - ): - """Issue a start warning command. - - This command starts the WD operation. The WD alerts the surrounding area by audible - (siren) and visual (strobe) signals. - - strobe_duty_cycle indicates the length of the flash cycle. This provides a means - of varying the flash duration for different alarm types (e.g., fire, police, burglar). - Valid range is 0-100 in increments of 10. All other values SHALL be rounded to the - nearest valid value. Strobe SHALL calculate duty cycle over a duration of one second. - The ON state SHALL precede the OFF state. For example, if Strobe Duty Cycle Field specifies - “40,” then the strobe SHALL flash ON for 4/10ths of a second and then turn OFF for - 6/10ths of a second. - """ - await self._ias_wd_channel.squawk( - mode=mode, - strobe=strobe, - siren_level=siren_level, - warning_duration=warning_duration, - strobe_duty_cycle=strobe_duty_cycle, - strobe_intensity=strobe_intensity, - ) diff --git a/homeassistant/components/zha/core/channels/security.py b/homeassistant/components/zha/core/channels/security.py index 39dfa040cdccc..fdb9fa137add7 100644 --- a/homeassistant/components/zha/core/channels/security.py +++ b/homeassistant/components/zha/core/channels/security.py @@ -33,7 +33,7 @@ class IasAce(ZigbeeChannel): pass -@registries.BINARY_SENSOR_CLUSTERS.register(security.IasWd.cluster_id) +@registries.CHANNEL_ONLY_CLUSTERS.register(security.IasWd.cluster_id) @registries.ZIGBEE_CHANNEL_REGISTRY.register(security.IasWd.cluster_id) class IasWd(ZigbeeChannel): """IAS Warning Device channel.""" diff --git a/homeassistant/components/zha/core/registries.py b/homeassistant/components/zha/core/registries.py index 860ea9660bee5..db7e89dce822c 100644 --- a/homeassistant/components/zha/core/registries.py +++ b/homeassistant/components/zha/core/registries.py @@ -152,7 +152,6 @@ def get_zigate_radio(): zha.DeviceType.ON_OFF_LIGHT_SWITCH: SWITCH, zha.DeviceType.ON_OFF_PLUG_IN_UNIT: SWITCH, zha.DeviceType.SMART_PLUG: SWITCH, - zha.DeviceType.IAS_WARNING_DEVICE: BINARY_SENSOR, } ) From 5f174fac615aec9fbc621a44cbdb98a56f7a17b0 Mon Sep 17 00:00:00 2001 From: David Mulcahey Date: Sun, 18 Aug 2019 11:33:28 -0400 Subject: [PATCH 3/9] squawk service --- homeassistant/components/zha/api.py | 50 ++++++++++++++++++++++ homeassistant/components/zha/core/const.py | 2 + 2 files changed, 52 insertions(+) diff --git a/homeassistant/components/zha/api.py b/homeassistant/components/zha/api.py index be079e83fa6bb..31f86ca769d7b 100644 --- a/homeassistant/components/zha/api.py +++ b/homeassistant/components/zha/api.py @@ -19,9 +19,13 @@ ATTR_COMMAND, ATTR_COMMAND_TYPE, ATTR_ENDPOINT_ID, + ATTR_LEVEL, ATTR_MANUFACTURER, ATTR_NAME, ATTR_VALUE, + ATTR_WARNING_DEVICE_MODE, + ATTR_WARNING_DEVICE_STROBE, + CHANNEL_IAS_WD, CLUSTER_COMMAND_SERVER, CLUSTER_COMMANDS_CLIENT, CLUSTER_COMMANDS_SERVER, @@ -31,6 +35,9 @@ DATA_ZHA_GATEWAY, DOMAIN, MFG_CLUSTER_ID_START, + WARNING_DEVICE_SOUND_HIGH, + WARNING_DEVICE_SQUAWK_MODE_ARMED, + WARNING_DEVICE_STROBE_YES, ) from .core.helpers import async_is_bindable_target, convert_ieee, get_matched_clusters @@ -56,6 +63,7 @@ SERVICE_ISSUE_ZIGBEE_CLUSTER_COMMAND = "issue_zigbee_cluster_command" SERVICE_DIRECT_ZIGBEE_BIND = "issue_direct_zigbee_bind" SERVICE_DIRECT_ZIGBEE_UNBIND = "issue_direct_zigbee_unbind" +SERVICE_WARNING_DEVICE_SQUAWK = "warning_device_squawk" SERVICE_ZIGBEE_BIND = "service_zigbee_bind" IEEE_SERVICE = "ieee_based_service" @@ -80,6 +88,20 @@ vol.Optional(ATTR_MANUFACTURER): cv.positive_int, } ), + SERVICE_WARNING_DEVICE_SQUAWK: vol.Schema( + { + vol.Required(ATTR_IEEE): convert_ieee, + vol.Optional( + ATTR_WARNING_DEVICE_MODE, default=WARNING_DEVICE_SQUAWK_MODE_ARMED + ): cv.positive_int, + vol.Optional( + ATTR_WARNING_DEVICE_STROBE, default=WARNING_DEVICE_STROBE_YES + ): cv.positive_int, + vol.Optional( + ATTR_LEVEL, default=WARNING_DEVICE_SOUND_HIGH + ): cv.positive_int, + } + ), SERVICE_ISSUE_ZIGBEE_CLUSTER_COMMAND: vol.Schema( { vol.Required(ATTR_IEEE): convert_ieee, @@ -610,6 +632,33 @@ async def issue_zigbee_cluster_command(service): schema=SERVICE_SCHEMAS[SERVICE_ISSUE_ZIGBEE_CLUSTER_COMMAND], ) + async def warning_device_squawk(service): + """Issue the squawk command for an IAS warning device.""" + ieee = service.data.get(ATTR_IEEE) + mode = service.data.get(ATTR_WARNING_DEVICE_MODE) + strobe = service.data.get(ATTR_WARNING_DEVICE_STROBE) + level = service.data.get(ATTR_LEVEL) + + zha_device = zha_gateway.get_device(ieee) + if zha_device is not None: + channel = zha_device.cluster_channels.get(CHANNEL_IAS_WD) + if channel: + await channel.squawk(mode, strobe, level) + _LOGGER.debug( + "Squawking IASWD: %s %s %s %s", + "{}: [{}]".format(ATTR_IEEE, str(ieee)), + "{}: [{}]".format(ATTR_WARNING_DEVICE_MODE, mode), + "{}: [{}]".format(ATTR_WARNING_DEVICE_STROBE, strobe), + "{}: [{}]".format(ATTR_LEVEL, level), + ) + + hass.helpers.service.async_register_admin_service( + DOMAIN, + SERVICE_WARNING_DEVICE_SQUAWK, + warning_device_squawk, + schema=SERVICE_SCHEMAS[SERVICE_WARNING_DEVICE_SQUAWK], + ) + websocket_api.async_register_command(hass, websocket_permit_devices) websocket_api.async_register_command(hass, websocket_get_devices) websocket_api.async_register_command(hass, websocket_get_device) @@ -629,3 +678,4 @@ def async_unload_api(hass): hass.services.async_remove(DOMAIN, SERVICE_REMOVE) hass.services.async_remove(DOMAIN, SERVICE_SET_ZIGBEE_CLUSTER_ATTRIBUTE) hass.services.async_remove(DOMAIN, SERVICE_ISSUE_ZIGBEE_CLUSTER_COMMAND) + hass.services.async_remove(DOMAIN, SERVICE_WARNING_DEVICE_SQUAWK) diff --git a/homeassistant/components/zha/core/const.py b/homeassistant/components/zha/core/const.py index 5305fa88da561..a6d0e9c9eb853 100644 --- a/homeassistant/components/zha/core/const.py +++ b/homeassistant/components/zha/core/const.py @@ -34,6 +34,8 @@ ATTR_SIGNATURE = "signature" ATTR_TYPE = "type" ATTR_VALUE = "value" +ATTR_WARNING_DEVICE_MODE = "mode" +ATTR_WARNING_DEVICE_STROBE = "strobe" BAUD_RATES = [2400, 4800, 9600, 14400, 19200, 38400, 57600, 115200, 128000, 256000] From 745fcd83e639988356b7bb984d1f909cd3dcfe90 Mon Sep 17 00:00:00 2001 From: David Mulcahey Date: Sun, 18 Aug 2019 12:03:20 -0400 Subject: [PATCH 4/9] add warning device warning service --- homeassistant/components/zha/api.py | 60 ++++++++++++++++++++++ homeassistant/components/zha/core/const.py | 3 ++ 2 files changed, 63 insertions(+) diff --git a/homeassistant/components/zha/api.py b/homeassistant/components/zha/api.py index 31f86ca769d7b..40fa8f4b658d7 100644 --- a/homeassistant/components/zha/api.py +++ b/homeassistant/components/zha/api.py @@ -23,8 +23,11 @@ ATTR_MANUFACTURER, ATTR_NAME, ATTR_VALUE, + ATTR_WARNING_DEVICE_DURTION, ATTR_WARNING_DEVICE_MODE, ATTR_WARNING_DEVICE_STROBE, + ATTR_WARNING_DEVICE_STROBE_DUTY_CYCLE, + ATTR_WARNING_DEVICE_STROBE_INTENSITY, CHANNEL_IAS_WD, CLUSTER_COMMAND_SERVER, CLUSTER_COMMANDS_CLIENT, @@ -35,8 +38,10 @@ DATA_ZHA_GATEWAY, DOMAIN, MFG_CLUSTER_ID_START, + WARNING_DEVICE_MODE_EMERGENCY, WARNING_DEVICE_SOUND_HIGH, WARNING_DEVICE_SQUAWK_MODE_ARMED, + WARNING_DEVICE_STROBE_HIGH, WARNING_DEVICE_STROBE_YES, ) from .core.helpers import async_is_bindable_target, convert_ieee, get_matched_clusters @@ -64,6 +69,7 @@ SERVICE_DIRECT_ZIGBEE_BIND = "issue_direct_zigbee_bind" SERVICE_DIRECT_ZIGBEE_UNBIND = "issue_direct_zigbee_unbind" SERVICE_WARNING_DEVICE_SQUAWK = "warning_device_squawk" +SERVICE_WARNING_DEVICE_WARN = "warning_device_warn" SERVICE_ZIGBEE_BIND = "service_zigbee_bind" IEEE_SERVICE = "ieee_based_service" @@ -102,6 +108,27 @@ ): cv.positive_int, } ), + SERVICE_WARNING_DEVICE_WARN: vol.Schema( + { + vol.Required(ATTR_IEEE): convert_ieee, + vol.Optional( + ATTR_WARNING_DEVICE_MODE, default=WARNING_DEVICE_MODE_EMERGENCY + ): cv.positive_int, + vol.Optional( + ATTR_WARNING_DEVICE_STROBE, default=WARNING_DEVICE_STROBE_YES + ): cv.positive_int, + vol.Optional( + ATTR_LEVEL, default=WARNING_DEVICE_SOUND_HIGH + ): cv.positive_int, + vol.Optional(ATTR_WARNING_DEVICE_DURTION, default=5): cv.positive_int, + vol.Optional( + ATTR_WARNING_DEVICE_STROBE_DUTY_CYCLE, default=0x00 + ): cv.positive_int, + vol.Optional( + ATTR_WARNING_DEVICE_STROBE_INTENSITY, default=WARNING_DEVICE_STROBE_HIGH + ): cv.positive_int, + } + ), SERVICE_ISSUE_ZIGBEE_CLUSTER_COMMAND: vol.Schema( { vol.Required(ATTR_IEEE): convert_ieee, @@ -659,6 +686,38 @@ async def warning_device_squawk(service): schema=SERVICE_SCHEMAS[SERVICE_WARNING_DEVICE_SQUAWK], ) + async def warning_device_warn(service): + """Issue the warning command for an IAS warning device.""" + ieee = service.data.get(ATTR_IEEE) + mode = service.data.get(ATTR_WARNING_DEVICE_MODE) + strobe = service.data.get(ATTR_WARNING_DEVICE_STROBE) + level = service.data.get(ATTR_LEVEL) + duration = service.data.get(ATTR_WARNING_DEVICE_DURTION) + duty_mode = service.data.get(ATTR_WARNING_DEVICE_STROBE_DUTY_CYCLE) + intensity = service.data.get(ATTR_WARNING_DEVICE_STROBE_INTENSITY) + + zha_device = zha_gateway.get_device(ieee) + if zha_device is not None: + channel = zha_device.cluster_channels.get(CHANNEL_IAS_WD) + if channel: + await channel.start_warning( + mode, strobe, level, duration, duty_mode, intensity + ) + _LOGGER.debug( + "Warning IASWD: %s %s %s %s", + "{}: [{}]".format(ATTR_IEEE, str(ieee)), + "{}: [{}]".format(ATTR_WARNING_DEVICE_MODE, mode), + "{}: [{}]".format(ATTR_WARNING_DEVICE_STROBE, strobe), + "{}: [{}]".format(ATTR_LEVEL, level), + ) + + hass.helpers.service.async_register_admin_service( + DOMAIN, + SERVICE_WARNING_DEVICE_WARN, + warning_device_warn, + schema=SERVICE_SCHEMAS[SERVICE_WARNING_DEVICE_WARN], + ) + websocket_api.async_register_command(hass, websocket_permit_devices) websocket_api.async_register_command(hass, websocket_get_devices) websocket_api.async_register_command(hass, websocket_get_device) @@ -679,3 +738,4 @@ def async_unload_api(hass): hass.services.async_remove(DOMAIN, SERVICE_SET_ZIGBEE_CLUSTER_ATTRIBUTE) hass.services.async_remove(DOMAIN, SERVICE_ISSUE_ZIGBEE_CLUSTER_COMMAND) hass.services.async_remove(DOMAIN, SERVICE_WARNING_DEVICE_SQUAWK) + hass.services.async_remove(DOMAIN, SERVICE_WARNING_DEVICE_WARN) diff --git a/homeassistant/components/zha/core/const.py b/homeassistant/components/zha/core/const.py index a6d0e9c9eb853..f674a70cf2725 100644 --- a/homeassistant/components/zha/core/const.py +++ b/homeassistant/components/zha/core/const.py @@ -34,8 +34,11 @@ ATTR_SIGNATURE = "signature" ATTR_TYPE = "type" ATTR_VALUE = "value" +ATTR_WARNING_DEVICE_DURTION = "duration" ATTR_WARNING_DEVICE_MODE = "mode" ATTR_WARNING_DEVICE_STROBE = "strobe" +ATTR_WARNING_DEVICE_STROBE_DUTY_CYCLE = "duty_cycle" +ATTR_WARNING_DEVICE_STROBE_INTENSITY = "intensity" BAUD_RATES = [2400, 4800, 9600, 14400, 19200, 38400, 57600, 115200, 128000, 256000] From ee578e65162d7351bf1c496081cc52fe5f9f9ffa Mon Sep 17 00:00:00 2001 From: David Mulcahey Date: Sun, 18 Aug 2019 12:04:13 -0400 Subject: [PATCH 5/9] update services.yaml --- homeassistant/components/zha/services.yaml | 52 ++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/homeassistant/components/zha/services.yaml b/homeassistant/components/zha/services.yaml index ffd5aa21472c8..d279af46335fe 100644 --- a/homeassistant/components/zha/services.yaml +++ b/homeassistant/components/zha/services.yaml @@ -82,3 +82,55 @@ issue_zigbee_cluster_command: manufacturer: description: manufacturer code example: 0x00FC + +warning_device_squawk: + description: >- + This service uses the WD capabilities to emit a quick audible/visible pulse called a "squawk". The squawk command has no effect if the WD is currently active (warning in progress). + fields: + ieee: + description: IEEE address for the device + example: "00:0d:6f:00:05:7d:2d:34" + mode: + description: >- + The Squawk Mode field is used as a 4-bit enumeration, and can have one of the values shown in Table 8-24 of the ZCL spec - Squawk Mode Field. The exact operation of each mode (how the WD “squawks”) is implementation specific. + example: 1 + strobe: + description: >- + The strobe field is used as a Boolean, and determines if the visual indication is also required in addition to the audible squawk, as shown in Table 8-25 of the ZCL spec - Strobe Bit. + example: 1 + level: + description: >- + The squawk level field is used as a 2-bit enumeration, and determines the intensity of audible squawk sound as shown in Table 8-26 of the ZCL spec - Squawk Level Field Values. + example: 2 + +warning_device_warn: + description: >- + This service starts the WD operation. The WD alerts the surrounding area by audible (siren) and visual (strobe) signals. + fields: + ieee: + description: IEEE address for the device + example: "00:0d:6f:00:05:7d:2d:34" + mode: + description: >- + The Warning Mode field is used as an 4-bit enumeration, can have one of the values defined below in table 8-20 of the ZCL spec. The exact behavior of the WD device in each mode is according to the relevant security standards. + example: 1 + strobe: + description: >- + The Strobe field is used as a 2-bit enumeration, and determines if the visual indication is required in addition to the audible siren, as indicated in Table 8-21 of the ZCL spec. If the strobe field is “1” and the Warning Mode is “0” (“Stop”) then only the strobe is activated. + example: 1 + level: + description: >- + The Siren Level field is used as a 2-bit enumeration, and indicates the intensity of audible squawk sound as shown in Table 8-22 of the ZCL spec. + example: 2 + duration: + description: >- + Requested duration of warning, in seconds. If both Strobe and Warning Mode are "0" this field SHALL be ignored. + example: 2 + duty_cycle: + description: >- + Indicates the length of the flash cycle. This provides a means of varying the flash duration for different alarm types (e.g., fire, police, burglar). Valid range is 0-100 in increments of 10. All other values SHALL be rounded to the nearest valid value. Strobe SHALL calculate duty cycle over a duration of one second. The ON state SHALL precede the OFF state. For example, if Strobe Duty Cycle Field specifies “40,” then the strobe SHALL flash ON for 4/10ths of a second and then turn OFF for 6/10ths of a second. + example: 2 + intensity: + description: >- + Indicates the intensity of the strobe as shown in Table 8-23 of the ZCL spec. This attribute is designed to vary the output of the strobe (i.e., brightness) and not its frequency, which is detailed in section 8.4.2.3.1.6 of the ZCL spec. + example: 2 From ee8e3dedaff4872e3d10f138a8f28398f4c7ad2f Mon Sep 17 00:00:00 2001 From: David Mulcahey Date: Sun, 18 Aug 2019 12:12:40 -0400 Subject: [PATCH 6/9] remove debugging statement --- homeassistant/components/zha/core/channels/security.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/homeassistant/components/zha/core/channels/security.py b/homeassistant/components/zha/core/channels/security.py index fdb9fa137add7..25c11a9fd4f1c 100644 --- a/homeassistant/components/zha/core/channels/security.py +++ b/homeassistant/components/zha/core/channels/security.py @@ -74,8 +74,6 @@ async def squawk( value = IasWd.set_bit(value, 6, mode, 2) value = IasWd.set_bit(value, 7, mode, 3) - self.warning("SQUAWKING!!!!!!!!!!!!!!!!!!!!!!!") - await self.device.issue_cluster_command( self.cluster.endpoint.endpoint_id, self.cluster.cluster_id, From 977ca6fb9c38b0fafc2e2a91e4326b86219d8c07 Mon Sep 17 00:00:00 2001 From: David Mulcahey Date: Tue, 27 Aug 2019 07:10:20 -0400 Subject: [PATCH 7/9] update required attr access --- homeassistant/components/zha/api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/zha/api.py b/homeassistant/components/zha/api.py index 40fa8f4b658d7..52bf438932d17 100644 --- a/homeassistant/components/zha/api.py +++ b/homeassistant/components/zha/api.py @@ -661,7 +661,7 @@ async def issue_zigbee_cluster_command(service): async def warning_device_squawk(service): """Issue the squawk command for an IAS warning device.""" - ieee = service.data.get(ATTR_IEEE) + ieee = service.data[ATTR_IEEE] mode = service.data.get(ATTR_WARNING_DEVICE_MODE) strobe = service.data.get(ATTR_WARNING_DEVICE_STROBE) level = service.data.get(ATTR_LEVEL) @@ -688,7 +688,7 @@ async def warning_device_squawk(service): async def warning_device_warn(service): """Issue the warning command for an IAS warning device.""" - ieee = service.data.get(ATTR_IEEE) + ieee = service.data[ATTR_IEEE] mode = service.data.get(ATTR_WARNING_DEVICE_MODE) strobe = service.data.get(ATTR_WARNING_DEVICE_STROBE) level = service.data.get(ATTR_LEVEL) From 253d307020618c170bf4a0760bea849c958e2980 Mon Sep 17 00:00:00 2001 From: David Mulcahey Date: Mon, 16 Sep 2019 07:18:22 -0400 Subject: [PATCH 8/9] fix constant --- homeassistant/components/zha/api.py | 6 +++--- homeassistant/components/zha/core/const.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/zha/api.py b/homeassistant/components/zha/api.py index 52bf438932d17..3e041fedbc1e8 100644 --- a/homeassistant/components/zha/api.py +++ b/homeassistant/components/zha/api.py @@ -23,7 +23,7 @@ ATTR_MANUFACTURER, ATTR_NAME, ATTR_VALUE, - ATTR_WARNING_DEVICE_DURTION, + ATTR_WARNING_DEVICE_DURATION, ATTR_WARNING_DEVICE_MODE, ATTR_WARNING_DEVICE_STROBE, ATTR_WARNING_DEVICE_STROBE_DUTY_CYCLE, @@ -120,7 +120,7 @@ vol.Optional( ATTR_LEVEL, default=WARNING_DEVICE_SOUND_HIGH ): cv.positive_int, - vol.Optional(ATTR_WARNING_DEVICE_DURTION, default=5): cv.positive_int, + vol.Optional(ATTR_WARNING_DEVICE_DURATION, default=5): cv.positive_int, vol.Optional( ATTR_WARNING_DEVICE_STROBE_DUTY_CYCLE, default=0x00 ): cv.positive_int, @@ -692,7 +692,7 @@ async def warning_device_warn(service): mode = service.data.get(ATTR_WARNING_DEVICE_MODE) strobe = service.data.get(ATTR_WARNING_DEVICE_STROBE) level = service.data.get(ATTR_LEVEL) - duration = service.data.get(ATTR_WARNING_DEVICE_DURTION) + duration = service.data.get(ATTR_WARNING_DEVICE_DURATION) duty_mode = service.data.get(ATTR_WARNING_DEVICE_STROBE_DUTY_CYCLE) intensity = service.data.get(ATTR_WARNING_DEVICE_STROBE_INTENSITY) diff --git a/homeassistant/components/zha/core/const.py b/homeassistant/components/zha/core/const.py index f674a70cf2725..ac83c2cdcd8f4 100644 --- a/homeassistant/components/zha/core/const.py +++ b/homeassistant/components/zha/core/const.py @@ -34,7 +34,7 @@ ATTR_SIGNATURE = "signature" ATTR_TYPE = "type" ATTR_VALUE = "value" -ATTR_WARNING_DEVICE_DURTION = "duration" +ATTR_WARNING_DEVICE_DURATION = "duration" ATTR_WARNING_DEVICE_MODE = "mode" ATTR_WARNING_DEVICE_STROBE = "strobe" ATTR_WARNING_DEVICE_STROBE_DUTY_CYCLE = "duty_cycle" From 0c2f117b6f6086c209afa1cc0e55ed7075c81f0c Mon Sep 17 00:00:00 2001 From: David Mulcahey Date: Fri, 20 Sep 2019 08:29:09 -0400 Subject: [PATCH 9/9] add error logging to IASWD services --- homeassistant/components/zha/api.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/homeassistant/components/zha/api.py b/homeassistant/components/zha/api.py index 3e041fedbc1e8..ff9f27d4843c2 100644 --- a/homeassistant/components/zha/api.py +++ b/homeassistant/components/zha/api.py @@ -671,6 +671,16 @@ async def warning_device_squawk(service): channel = zha_device.cluster_channels.get(CHANNEL_IAS_WD) if channel: await channel.squawk(mode, strobe, level) + else: + _LOGGER.error( + "Squawking IASWD: %s is missing the required IASWD channel!", + "{}: [{}]".format(ATTR_IEEE, str(ieee)), + ) + else: + _LOGGER.error( + "Squawking IASWD: %s could not be found!", + "{}: [{}]".format(ATTR_IEEE, str(ieee)), + ) _LOGGER.debug( "Squawking IASWD: %s %s %s %s", "{}: [{}]".format(ATTR_IEEE, str(ieee)), @@ -703,6 +713,16 @@ async def warning_device_warn(service): await channel.start_warning( mode, strobe, level, duration, duty_mode, intensity ) + else: + _LOGGER.error( + "Warning IASWD: %s is missing the required IASWD channel!", + "{}: [{}]".format(ATTR_IEEE, str(ieee)), + ) + else: + _LOGGER.error( + "Warning IASWD: %s could not be found!", + "{}: [{}]".format(ATTR_IEEE, str(ieee)), + ) _LOGGER.debug( "Warning IASWD: %s %s %s %s", "{}: [{}]".format(ATTR_IEEE, str(ieee)),