Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion homeassistant/components/elkm1/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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(
Expand All @@ -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]
Expand Down
228 changes: 228 additions & 0 deletions homeassistant/components/sensor/elkm1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
"""
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/
"""

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove blank line.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is still left.

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(
Comment thread
MartinHjelmare marked this conversation as resolved.
self._element.logical_status).name)
14 changes: 14 additions & 0 deletions homeassistant/components/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -601,3 +601,17 @@ nest:
trip_id:
description: Optional identity of a trip. Using the same trip_ID will update the estimation.
example: trip_back_home

elkm1:
sensor_speak_word:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move these to a new file services.yaml in the elkm1 package. See other components that are packages for example.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do. Don't understand why though. What is different about elkm1 compared to the other services in where they were put?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't want to add more service descriptions to the base component package file.

The descriptions in the base component file are always loaded and cached the first time we need all the service descriptions of registered services, eg in the frontend, so the longer that file is the longer time it will take to do that, and the more information, potentially not needed info, will be stored in memory.

That's why we want to split this file up and put the descriptions in the other child components so that we only load the necessary information.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, that helps. Sounds like that the services will be eventually moved. Makes a lot of sense. Would it make sense to leave a "trail" in that services.yaml that says what you said in your comment? It could help point people in the right direction.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make the suggestion in a PR. It should be documented in our dev docs at minimum.

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