diff --git a/homeassistant/components/elkm1/__init__.py b/homeassistant/components/elkm1/__init__.py index da322232bdf6da..5c379c7438ba5d 100644 --- a/homeassistant/components/elkm1/__init__.py +++ b/homeassistant/components/elkm1/__init__.py @@ -35,7 +35,13 @@ _LOGGER = logging.getLogger(__name__) -SUPPORTED_DOMAINS = ['alarm_control_panel', 'light', 'scene', 'switch'] +SUPPORTED_DOMAINS = ['alarm_control_panel', 'light', 'scene', 'sensor', + 'switch'] + +SPEAK_SERVICE_SCHEMA = vol.Schema({ + vol.Required('number'): + vol.All(vol.Coerce(int), vol.Range(min=0, max=999)) +}) def _host_validator(config): @@ -135,6 +141,8 @@ def _included(ranges, set_to, values): 'password': conf[CONF_PASSWORD]}) elk.connect() + _create_elk_services(hass, elk) + hass.data[DOMAIN] = {'elk': elk, 'config': config, 'keypads': {}} for component in SUPPORTED_DOMAINS: hass.async_create_task( @@ -143,6 +151,19 @@ def _included(ranges, set_to, values): return True +def _create_elk_services(hass, elk): + def _speak_word_service(service): + elk.panel.speak_word(service.data.get('number')) + + def _speak_phrase_service(service): + elk.panel.speak_phrase(service.data.get('number')) + + hass.services.async_register( + DOMAIN, 'speak_word', _speak_word_service, SPEAK_SERVICE_SCHEMA) + hass.services.async_register( + DOMAIN, 'speak_phrase', _speak_phrase_service, SPEAK_SERVICE_SCHEMA) + + def create_elk_entities(hass, elk_elements, element_type, class_, entities): """Create the ElkM1 devices of a particular class.""" elk_data = hass.data[DOMAIN] diff --git a/homeassistant/components/elkm1/services.yaml b/homeassistant/components/elkm1/services.yaml new file mode 100644 index 00000000000000..405716569630ee --- /dev/null +++ b/homeassistant/components/elkm1/services.yaml @@ -0,0 +1,12 @@ +speak_word: + description: Speak a word. See list of words in ElkM1 ASCII Protocol documentation. + fields: + number: + description: Word number to speak. + example: 142 +speak_phrase: + description: Speak a phrase. See list of phrases in ElkM1 ASCII Protocol documentation. + fields: + number: + description: Phrase number to speak. + example: 42 diff --git a/homeassistant/components/sensor/elkm1.py b/homeassistant/components/sensor/elkm1.py new file mode 100644 index 00000000000000..288f968b2f7103 --- /dev/null +++ b/homeassistant/components/sensor/elkm1.py @@ -0,0 +1,227 @@ +""" +Support for control of ElkM1 sensors. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.elkm1/ +""" +from homeassistant.components.elkm1 import ( + DOMAIN as ELK_DOMAIN, create_elk_entities, ElkEntity) + +DEPENDENCIES = [ELK_DOMAIN] + + +async def async_setup_platform(hass, config, async_add_entities, + discovery_info=None): + """Create the Elk-M1 sensor platform.""" + if discovery_info is None: + return + + elk = hass.data[ELK_DOMAIN]['elk'] + entities = create_elk_entities( + hass, elk.counters, 'counter', ElkCounter, []) + entities = create_elk_entities( + hass, elk.keypads, 'keypad', ElkKeypad, entities) + entities = create_elk_entities( + hass, [elk.panel], 'panel', ElkPanel, entities) + entities = create_elk_entities( + hass, elk.settings, 'setting', ElkSetting, entities) + entities = create_elk_entities( + hass, elk.zones, 'zone', ElkZone, entities) + async_add_entities(entities, True) + + +def temperature_to_state(temperature, undefined_temperature): + """Convert temperature to a state.""" + return temperature if temperature > undefined_temperature else None + + +class ElkSensor(ElkEntity): + """Base representation of Elk-M1 sensor.""" + + def __init__(self, element, elk, elk_data): + """Initialize the base of all Elk sensors.""" + super().__init__(element, elk, elk_data) + self._state = None + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + +class ElkCounter(ElkSensor): + """Representation of an Elk-M1 Counter.""" + + @property + def icon(self): + """Icon to use in the frontend.""" + return 'mdi:numeric' + + def _element_changed(self, element, changeset): + self._state = self._element.value + + +class ElkKeypad(ElkSensor): + """Representation of an Elk-M1 Keypad.""" + + @property + def temperature_unit(self): + """Return the temperature unit.""" + return self._temperature_unit + + @property + def unit_of_measurement(self): + """Return the unit of measurement.""" + return self._temperature_unit + + @property + def icon(self): + """Icon to use in the frontend.""" + return 'mdi:thermometer-lines' + + @property + def device_state_attributes(self): + """Attributes of the sensor.""" + from elkm1_lib.util import username + + attrs = self.initial_attrs() + attrs['area'] = self._element.area + 1 + attrs['temperature'] = self._element.temperature + attrs['last_user_time'] = self._element.last_user_time.isoformat() + attrs['last_user'] = self._element.last_user + 1 + attrs['code'] = self._element.code + attrs['last_user_name'] = username(self._elk, self._element.last_user) + return attrs + + def _element_changed(self, element, changeset): + self._state = temperature_to_state(self._element.temperature, -40) + + async def async_added_to_hass(self): + """Register callback for ElkM1 changes and update entity state.""" + await super().async_added_to_hass() + self.hass.data[ELK_DOMAIN]['keypads'][ + self._element.index] = self.entity_id + + +class ElkPanel(ElkSensor): + """Representation of an Elk-M1 Panel.""" + + @property + def icon(self): + """Icon to use in the frontend.""" + return "mdi:home" + + @property + def device_state_attributes(self): + """Attributes of the sensor.""" + attrs = self.initial_attrs() + attrs['system_trouble_status'] = self._element.system_trouble_status + return attrs + + def _element_changed(self, element, changeset): + if self._elk.is_connected(): + self._state = 'Paused' if self._element.remote_programming_status \ + else 'Connected' + else: + self._state = 'Disconnected' + + +class ElkSetting(ElkSensor): + """Representation of an Elk-M1 Setting.""" + + @property + def icon(self): + """Icon to use in the frontend.""" + return 'mdi:numeric' + + def _element_changed(self, element, changeset): + self._state = self._element.value + + @property + def device_state_attributes(self): + """Attributes of the sensor.""" + from elkm1_lib.const import SettingFormat + attrs = self.initial_attrs() + attrs['value_format'] = SettingFormat( + self._element.value_format).name.lower() + return attrs + + +class ElkZone(ElkSensor): + """Representation of an Elk-M1 Zone.""" + + @property + def icon(self): + """Icon to use in the frontend.""" + from elkm1_lib.const import ZoneType + zone_icons = { + ZoneType.FIRE_ALARM.value: 'fire', + ZoneType.FIRE_VERIFIED.value: 'fire', + ZoneType.FIRE_SUPERVISORY.value: 'fire', + ZoneType.KEYFOB.value: 'key', + ZoneType.NON_ALARM.value: 'alarm-off', + ZoneType.MEDICAL_ALARM.value: 'medical-bag', + ZoneType.POLICE_ALARM.value: 'alarm-light', + ZoneType.POLICE_NO_INDICATION.value: 'alarm-light', + ZoneType.KEY_MOMENTARY_ARM_DISARM.value: 'power', + ZoneType.KEY_MOMENTARY_ARM_AWAY.value: 'power', + ZoneType.KEY_MOMENTARY_ARM_STAY.value: 'power', + ZoneType.KEY_MOMENTARY_DISARM.value: 'power', + ZoneType.KEY_ON_OFF.value: 'toggle-switch', + ZoneType.MUTE_AUDIBLES.value: 'volume-mute', + ZoneType.POWER_SUPERVISORY.value: 'power-plug', + ZoneType.TEMPERATURE.value: 'thermometer-lines', + ZoneType.ANALOG_ZONE.value: 'speedometer', + ZoneType.PHONE_KEY.value: 'phone-classic', + ZoneType.INTERCOM_KEY.value: 'deskphone' + } + return 'mdi:{}'.format( + zone_icons.get(self._element.definition, 'alarm-bell')) + + @property + def device_state_attributes(self): + """Attributes of the sensor.""" + from elkm1_lib.const import ( + ZoneLogicalStatus, ZonePhysicalStatus, ZoneType) + + attrs = self.initial_attrs() + attrs['physical_status'] = ZonePhysicalStatus( + self._element.physical_status).name.lower() + attrs['logical_status'] = ZoneLogicalStatus( + self._element.logical_status).name.lower() + attrs['definition'] = ZoneType( + self._element.definition).name.lower() + attrs['area'] = self._element.area + 1 + attrs['bypassed'] = self._element.bypassed + attrs['triggered_alarm'] = self._element.triggered_alarm + return attrs + + @property + def temperature_unit(self): + """Return the temperature unit.""" + from elkm1_lib.const import ZoneType + if self._element.definition == ZoneType.TEMPERATURE.value: + return self._temperature_unit + return None + + @property + def unit_of_measurement(self): + """Return the unit of measurement.""" + from elkm1_lib.const import ZoneType + if self._element.definition == ZoneType.TEMPERATURE.value: + return self._temperature_unit + if self._element.definition == ZoneType.ANALOG_ZONE.value: + return 'V' + return None + + def _element_changed(self, element, changeset): + from elkm1_lib.const import ZoneLogicalStatus, ZoneType + from elkm1_lib.util import pretty_const + + if self._element.definition == ZoneType.TEMPERATURE.value: + self._state = temperature_to_state(self._element.temperature, -60) + elif self._element.definition == ZoneType.ANALOG_ZONE.value: + self._state = self._element.voltage + else: + self._state = pretty_const(ZoneLogicalStatus( + self._element.logical_status).name)