From 81ca17590682afc2a47a327617d05125e60c5ca1 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sun, 12 Jun 2016 23:04:45 +0200 Subject: [PATCH] Add mysensors IR switch device and service (#2239) * Add mysensors IR switch device and service * Add MySensorsIRSwitch as child class to MySensorsSwitch. * Add platform specific service mysensors_send_ir_code. Only call device method in service function if device is IR device. * Add service and required attribute to state helper to support scenes. * Move V_IR_SEND type from sensor.mysensors to switch.mysensors platform. * Populate switch.services.yaml with service descriptions. * Fix check of entity_id in service function Since multiple entity_ids can be passed as service data, and the entity_id service attribute is forced to a list by the service validation schema, the check in the service function should iterate over any entity ids. --- homeassistant/components/sensor/mysensors.py | 2 +- homeassistant/components/switch/mysensors.py | 125 +++++++++++++++++- homeassistant/components/switch/services.yaml | 37 ++++++ homeassistant/helpers/state.py | 3 + 4 files changed, 160 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/sensor/mysensors.py b/homeassistant/components/sensor/mysensors.py index c1eaa913535..c6b2e5aa86c 100644 --- a/homeassistant/components/sensor/mysensors.py +++ b/homeassistant/components/sensor/mysensors.py @@ -37,7 +37,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): pres.S_POWER: [set_req.V_WATT, set_req.V_KWH], pres.S_DISTANCE: [set_req.V_DISTANCE], pres.S_LIGHT_LEVEL: [set_req.V_LIGHT_LEVEL], - pres.S_IR: [set_req.V_IR_SEND, set_req.V_IR_RECEIVE], + pres.S_IR: [set_req.V_IR_RECEIVE], pres.S_WATER: [set_req.V_FLOW, set_req.V_VOLUME], pres.S_CUSTOM: [set_req.V_VAR1, set_req.V_VAR2, diff --git a/homeassistant/components/switch/mysensors.py b/homeassistant/components/switch/mysensors.py index 25cf4945d97..102490286f6 100644 --- a/homeassistant/components/switch/mysensors.py +++ b/homeassistant/components/switch/mysensors.py @@ -5,14 +5,27 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/switch.mysensors/ """ import logging +import os +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv from homeassistant.components import mysensors -from homeassistant.components.switch import SwitchDevice -from homeassistant.const import STATE_OFF, STATE_ON +from homeassistant.components.switch import DOMAIN, SwitchDevice +from homeassistant.config import load_yaml_config_file +from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON _LOGGER = logging.getLogger(__name__) DEPENDENCIES = [] +ATTR_IR_CODE = 'V_IR_SEND' +SERVICE_SEND_IR_CODE = 'mysensors_send_ir_code' + +SEND_IR_CODE_SERVICE_SCHEMA = vol.Schema({ + vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Required(ATTR_IR_CODE): cv.string, +}) + def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the mysensors platform for switches.""" @@ -32,6 +45,15 @@ def setup_platform(hass, config, add_devices, discovery_info=None): pres.S_SMOKE: [set_req.V_ARMED], pres.S_LIGHT: [set_req.V_LIGHT], pres.S_LOCK: [set_req.V_LOCK_STATUS], + pres.S_IR: [set_req.V_IR_SEND], + } + device_class_map = { + pres.S_DOOR: MySensorsSwitch, + pres.S_MOTION: MySensorsSwitch, + pres.S_SMOKE: MySensorsSwitch, + pres.S_LIGHT: MySensorsSwitch, + pres.S_LOCK: MySensorsSwitch, + pres.S_IR: MySensorsIRSwitch, } if float(gateway.version) >= 1.5: map_sv_types.update({ @@ -43,15 +65,53 @@ def setup_platform(hass, config, add_devices, discovery_info=None): pres.S_MOISTURE: [set_req.V_ARMED], }) map_sv_types[pres.S_LIGHT].append(set_req.V_STATUS) + device_class_map.update({ + pres.S_BINARY: MySensorsSwitch, + pres.S_SPRINKLER: MySensorsSwitch, + pres.S_WATER_LEAK: MySensorsSwitch, + pres.S_SOUND: MySensorsSwitch, + pres.S_VIBRATION: MySensorsSwitch, + pres.S_MOISTURE: MySensorsSwitch, + }) devices = {} gateway.platform_callbacks.append(mysensors.pf_callback_factory( - map_sv_types, devices, add_devices, MySensorsSwitch)) + map_sv_types, devices, add_devices, device_class_map)) + + def send_ir_code_service(service): + """Set IR code as device state attribute.""" + entity_ids = service.data.get(ATTR_ENTITY_ID) + ir_code = service.data.get(ATTR_IR_CODE) + + if entity_ids: + _devices = [device for device in devices.values() + if isinstance(device, MySensorsIRSwitch) and + device.entity_id in entity_ids] + else: + _devices = [device for device in devices.values() + if isinstance(device, MySensorsIRSwitch)] + + kwargs = {ATTR_IR_CODE: ir_code} + for device in _devices: + device.turn_on(**kwargs) + + descriptions = load_yaml_config_file( + os.path.join(os.path.dirname(__file__), 'services.yaml')) + + hass.services.register(DOMAIN, SERVICE_SEND_IR_CODE, + send_ir_code_service, + descriptions.get(SERVICE_SEND_IR_CODE), + schema=SEND_IR_CODE_SERVICE_SCHEMA) class MySensorsSwitch(mysensors.MySensorsDeviceEntity, SwitchDevice): """Representation of the value of a MySensors Switch child node.""" + @property + def assumed_state(self): + """Return True if unable to access real state of entity.""" + return self.gateway.optimistic + @property def is_on(self): """Return True if switch is on.""" @@ -77,7 +137,60 @@ class MySensorsSwitch(mysensors.MySensorsDeviceEntity, SwitchDevice): self._values[self.value_type] = STATE_OFF self.update_ha_state() + +class MySensorsIRSwitch(MySensorsSwitch): + """IR switch child class to MySensorsSwitch.""" + + def __init__(self, *args): + """Setup instance attributes.""" + MySensorsSwitch.__init__(self, *args) + self._ir_code = None + @property - def assumed_state(self): - """Return True if unable to access real state of entity.""" - return self.gateway.optimistic + def is_on(self): + """Return True if switch is on.""" + set_req = self.gateway.const.SetReq + if set_req.V_LIGHT in self._values: + return self._values[set_req.V_LIGHT] == STATE_ON + return False + + def turn_on(self, **kwargs): + """Turn the IR switch on.""" + set_req = self.gateway.const.SetReq + if set_req.V_LIGHT not in self._values: + _LOGGER.error('missing value_type: %s at node: %s, child: %s', + set_req.V_LIGHT.name, self.node_id, self.child_id) + return + if ATTR_IR_CODE in kwargs: + self._ir_code = kwargs[ATTR_IR_CODE] + self.gateway.set_child_value( + self.node_id, self.child_id, self.value_type, self._ir_code) + self.gateway.set_child_value( + self.node_id, self.child_id, set_req.V_LIGHT, 1) + if self.gateway.optimistic: + # optimistically assume that switch has changed state + self._values[self.value_type] = self._ir_code + self._values[set_req.V_LIGHT] = STATE_ON + self.update_ha_state() + # turn off switch after switch was turned on + self.turn_off() + + def turn_off(self): + """Turn the IR switch off.""" + set_req = self.gateway.const.SetReq + if set_req.V_LIGHT not in self._values: + _LOGGER.error('missing value_type: %s at node: %s, child: %s', + set_req.V_LIGHT.name, self.node_id, self.child_id) + return + self.gateway.set_child_value( + self.node_id, self.child_id, set_req.V_LIGHT, 0) + if self.gateway.optimistic: + # optimistically assume that switch has changed state + self._values[set_req.V_LIGHT] = STATE_OFF + self.update_ha_state() + + def update(self): + """Update the controller with the latest value from a sensor.""" + MySensorsSwitch.update(self) + if self.value_type in self._values: + self._ir_code = self._values[self.value_type] diff --git a/homeassistant/components/switch/services.yaml b/homeassistant/components/switch/services.yaml index e69de29bb2d..00b2abb91a4 100644 --- a/homeassistant/components/switch/services.yaml +++ b/homeassistant/components/switch/services.yaml @@ -0,0 +1,37 @@ +# Describes the format for available switch services + +turn_on: + description: Turn a switch on + + fields: + entity_id: + description: Name(s) of entities to turn on + example: 'switch.living_room' + +turn_off: + description: Turn a switch off + + fields: + entity_id: + description: Name(s) of entities to turn off + example: 'switch.living_room' + +toggle: + description: Toggles a switch state + + fields: + entity_id: + description: Name(s) of entities to toggle + example: 'switch.living_room' + +mysensors_send_ir_code: + description: Set an IR code as a state attribute for a MySensors IR device switch and turn the switch on. + + fields: + entity_id: + description: Name(s) of entites that should have the IR code set and be turned on. Platform dependent. + example: 'switch.living_room_1_1' + + V_IR_SEND: + description: IR code to send + example: '0xC284' diff --git a/homeassistant/helpers/state.py b/homeassistant/helpers/state.py index 7000ea97b43..9c6e797acd1 100644 --- a/homeassistant/helpers/state.py +++ b/homeassistant/helpers/state.py @@ -12,6 +12,8 @@ from homeassistant.components.notify import ( ATTR_MESSAGE, SERVICE_NOTIFY) from homeassistant.components.sun import ( STATE_ABOVE_HORIZON, STATE_BELOW_HORIZON) +from homeassistant.components.switch.mysensors import ( + ATTR_IR_CODE, SERVICE_SEND_IR_CODE) from homeassistant.components.thermostat import ( ATTR_AWAY_MODE, ATTR_FAN, SERVICE_SET_AWAY_MODE, SERVICE_SET_FAN_MODE, SERVICE_SET_TEMPERATURE) @@ -55,6 +57,7 @@ SERVICE_ATTRIBUTES = { SERVICE_SET_OPERATION_MODE: [ATTR_OPERATION_MODE], SERVICE_SET_AUX_HEAT: [ATTR_AUX_HEAT], SERVICE_SELECT_SOURCE: [ATTR_INPUT_SOURCE], + SERVICE_SEND_IR_CODE: [ATTR_IR_CODE] } # Update this dict when new services are added to HA.