-
-
Notifications
You must be signed in to change notification settings - Fork 37.8k
Add support for Tahoma smoke sensor, light switch and two awning io components #14931
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
8e3ffe1
0bedc4a
6b86684
a5044e8
7c3bc1f
e02c0b9
30111ea
be4776d
cdc5388
c18033b
6755ae2
89d008d
d65bc00
bb3f0b7
2231a58
24d7ece
9b8346d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| """ | ||
| Support for Tahoma binary sensors. | ||
|
|
||
| For more details about this platform, please refer to the documentation at | ||
| https://home-assistant.io/components/binary_sensor.tahoma/ | ||
| """ | ||
|
|
||
| import logging | ||
| from datetime import timedelta | ||
|
|
||
| from homeassistant.components.binary_sensor import ( | ||
| BinarySensorDevice) | ||
| from homeassistant.components.tahoma import ( | ||
| DOMAIN as TAHOMA_DOMAIN, TahomaDevice) | ||
| from homeassistant.const import (STATE_OFF, STATE_ON, ATTR_BATTERY_LEVEL) | ||
|
|
||
| DEPENDENCIES = ['tahoma'] | ||
|
|
||
| _LOGGER = logging.getLogger(__name__) | ||
|
|
||
| SCAN_INTERVAL = timedelta(seconds=120) | ||
|
|
||
|
|
||
| def setup_platform(hass, config, add_devices, discovery_info=None): | ||
| """Set up Tahoma controller devices.""" | ||
| _LOGGER.debug("Setup Tahoma Binary sensor platform") | ||
| controller = hass.data[TAHOMA_DOMAIN]['controller'] | ||
| devices = [] | ||
| for device in hass.data[TAHOMA_DOMAIN]['devices']['smoke']: | ||
| devices.append(TahomaBinarySensor(device, controller)) | ||
| add_devices(devices, True) | ||
|
|
||
|
|
||
| class TahomaBinarySensor(TahomaDevice, BinarySensorDevice): | ||
| """Representation of a Tahoma Binary Sensor.""" | ||
|
|
||
| def __init__(self, tahoma_device, controller): | ||
| """Initialize the sensor.""" | ||
| super().__init__(tahoma_device, controller) | ||
|
|
||
| self._state = None | ||
| self._icon = None | ||
| self._battery = None | ||
|
|
||
| @property | ||
| def is_on(self): | ||
| """Return the state of the sensor.""" | ||
| return bool(self._state == STATE_ON) | ||
|
|
||
| @property | ||
| def device_class(self): | ||
| """Return the class of the device.""" | ||
| if self.tahoma_device.type == 'rtds:RTDSSmokeSensor': | ||
| return 'smoke' | ||
| return None | ||
|
|
||
| @property | ||
| def icon(self): | ||
| """Icon for device by its type.""" | ||
| return self._icon | ||
|
|
||
| @property | ||
| def state_attributes(self): | ||
| """Return the state attributes of the sensor.""" | ||
| attr = {} | ||
| if self._battery is not None: | ||
| attr[ATTR_BATTERY_LEVEL] = self._battery | ||
| return attr | ||
|
|
||
| def update(self): | ||
| """Update the state.""" | ||
| self.controller.get_states([self.tahoma_device]) | ||
| if self.tahoma_device.type == 'rtds:RTDSSmokeSensor': | ||
| if self.tahoma_device.active_states['core:SmokeState']\ | ||
| == 'notDetected': | ||
| self._state = STATE_OFF | ||
| else: | ||
| self._state = STATE_ON | ||
|
|
||
| if 'core:SensorDefectState' in self.tahoma_device.active_states: | ||
| # Set to 'lowBattery' for low battery warning. | ||
| self._battery = self.tahoma_device.active_states[ | ||
| 'core:SensorDefectState'] | ||
| else: | ||
| self._battery = None | ||
|
|
||
| if self._state == STATE_ON: | ||
| self._icon = "mdi:fire" | ||
| elif self._battery == 'lowBattery': | ||
| self._icon = "mdi:battery-alert" | ||
| else: | ||
| self._icon = None | ||
|
|
||
| _LOGGER.debug("Update %s, state: %s", self._name, self._state) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,6 +14,15 @@ | |
|
|
||
| _LOGGER = logging.getLogger(__name__) | ||
|
|
||
| ATTR_CLOSURE = 'closure' | ||
| ATTR_MEM_POS = 'memorized_position' | ||
| ATTR_RSSI_LEVEL = 'rssi_level' | ||
| ATTR_OPEN_CLOSE = 'open_close' | ||
| ATTR_STATUS = 'status' | ||
| ATTR_LOCK_TIMER = 'priority_lock_timer' | ||
| ATTR_LOCK_LEVEL = 'priority_lock_level' | ||
| ATTR_LOCK_ORIG = 'priority_lock_originator' | ||
|
|
||
|
|
||
| def setup_platform(hass, config, add_devices, discovery_info=None): | ||
| """Set up the Tahoma covers.""" | ||
|
|
@@ -27,27 +36,84 @@ def setup_platform(hass, config, add_devices, discovery_info=None): | |
| class TahomaCover(TahomaDevice, CoverDevice): | ||
| """Representation a Tahoma Cover.""" | ||
|
|
||
| def __init__(self, tahoma_device, controller): | ||
| """Initialize the device.""" | ||
| super().__init__(tahoma_device, controller) | ||
|
|
||
| self._closure = 0 | ||
| # 100 equals open | ||
| self._position = 100 | ||
| self._closed = False | ||
| self._icon = None | ||
| # Can be 0 and bigger | ||
| self._lock_timer = 0 | ||
| # Can be 'comfortLevel1' | ||
| self._lock_level = None | ||
| # Can be 'wind' | ||
| self._lock_originator = None | ||
|
|
||
| def update(self): | ||
| """Update method.""" | ||
| self.controller.get_states([self.tahoma_device]) | ||
|
|
||
| @property | ||
| def current_cover_position(self): | ||
| """ | ||
| Return current position of cover. | ||
| if 'core:ClosureState' in self.tahoma_device.active_states: | ||
| self._closure = \ | ||
| self.tahoma_device.active_states['core:ClosureState'] | ||
| else: | ||
| self._closure = None | ||
| if 'core:PriorityLockTimerState' in self.tahoma_device.active_states: | ||
| self._lock_timer = \ | ||
| self.tahoma_device.active_states['core:PriorityLockTimerState'] | ||
| else: | ||
| self._lock_timer = None | ||
| if 'io:PriorityLockLevelState' in self.tahoma_device.active_states: | ||
| self._lock_level = \ | ||
| self.tahoma_device.active_states['io:PriorityLockLevelState'] | ||
| else: | ||
| self._lock_level = None | ||
| if 'io:PriorityLockOriginatorState' in \ | ||
| self.tahoma_device.active_states: | ||
| self._lock_originator = \ | ||
| self.tahoma_device.active_states[ | ||
| 'io:PriorityLockOriginatorState'] | ||
| else: | ||
| self._lock_originator = None | ||
|
|
||
| # Define which icon to use | ||
| if self._lock_timer > 0: | ||
| if self._lock_originator == 'wind': | ||
| self._icon = 'mdi:weather-windy' | ||
| else: | ||
| self._icon = 'mdi:lock-alert' | ||
| else: | ||
| self._icon = None | ||
|
|
||
| 0 is closed, 100 is fully open. | ||
| """ | ||
| try: | ||
| position = 100 - \ | ||
| # Define current position. | ||
| # _position: 0 is closed, 100 is fully open. | ||
| # 'core:ClosureState': 100 is closed, 0 is fully open. | ||
| if 'core:ClosureState' in self.tahoma_device.active_states: | ||
| self._position = 100 - \ | ||
| self.tahoma_device.active_states['core:ClosureState'] | ||
| if position <= 5: | ||
| return 0 | ||
| if position >= 95: | ||
| return 100 | ||
| return position | ||
| except KeyError: | ||
| return None | ||
| if self._position <= 5: | ||
| self._position = 0 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, I don't know. It was there since the beginning (#10652). So, I kept the code. |
||
| if self._position >= 95: | ||
| self._position = 100 | ||
| self._closed = self._position == 0 | ||
| else: | ||
| self._position = None | ||
| if 'core:OpenClosedState' in self.tahoma_device.active_states: | ||
| self._closed = \ | ||
| self.tahoma_device.active_states['core:OpenClosedState']\ | ||
| == 'closed' | ||
| else: | ||
| self._closed = False | ||
|
|
||
| _LOGGER.debug("Update %s, position: %d", self._name, self._position) | ||
|
|
||
| @property | ||
| def current_cover_position(self): | ||
| """Return current position of cover.""" | ||
| return self._position | ||
|
|
||
| def set_cover_position(self, **kwargs): | ||
| """Move the cover to a specific position.""" | ||
|
|
@@ -56,8 +122,7 @@ def set_cover_position(self, **kwargs): | |
| @property | ||
| def is_closed(self): | ||
| """Return if the cover is closed.""" | ||
| if self.current_cover_position is not None: | ||
| return self.current_cover_position == 0 | ||
| return self._closed | ||
|
|
||
| @property | ||
| def device_class(self): | ||
|
|
@@ -66,13 +131,56 @@ def device_class(self): | |
| return 'window' | ||
| return None | ||
|
|
||
| @property | ||
| def state_attributes(self): | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do not override
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, I'll update it. |
||
| """Return the state attributes of the sensor.""" | ||
| attr = {} | ||
| super_attr = super().state_attributes | ||
| if super_attr is not None: | ||
| attr.update(super_attr) | ||
|
|
||
| if self._closure is not None: | ||
| attr[ATTR_CLOSURE] = self._closure | ||
| if 'core:Memorized1PositionState' in self.tahoma_device.active_states: | ||
| attr[ATTR_MEM_POS] = self.tahoma_device.active_states[ | ||
| 'core:Memorized1PositionState'] | ||
| if 'core:RSSILevelState' in self.tahoma_device.active_states: | ||
| attr[ATTR_RSSI_LEVEL] = self.tahoma_device.active_states[ | ||
| 'core:RSSILevelState'] | ||
| if 'core:OpenClosedState' in self.tahoma_device.active_states: | ||
| attr[ATTR_OPEN_CLOSE] = self.tahoma_device.active_states[ | ||
| 'core:OpenClosedState'] | ||
| if 'core:StatusState' in self.tahoma_device.active_states: | ||
| attr[ATTR_STATUS] = self.tahoma_device.active_states[ | ||
| 'core:StatusState'] | ||
| if self._lock_timer is not None: | ||
| attr[ATTR_LOCK_TIMER] = self._lock_timer | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should store any times in the state machine. Only time stamps are allowed.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I read the docs about the states again and I don't understand, why the lock timer should go into the state machine. It's an attribute of the device for how long it is locked (before it can be used again, e.g. open cover after wind has been detected). It relates directly to lock_level and lock_originator. Could you please point me to an example or the appropriate documentation?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can store timestamps, so store the exact time when it can be used. Don't store relative times that change on every update. |
||
| if self._lock_level is not None: | ||
| attr[ATTR_LOCK_LEVEL] = self._lock_level | ||
| if self._lock_originator is not None: | ||
| attr[ATTR_LOCK_ORIG] = self._lock_originator | ||
| return attr | ||
|
|
||
| @property | ||
| def icon(self): | ||
| """Return the icon to use in the frontend, if any.""" | ||
| return self._icon | ||
|
|
||
| def open_cover(self, **kwargs): | ||
| """Open the cover.""" | ||
| self.apply_action('open') | ||
| if self.tahoma_device.type == 'io:HorizontalAwningIOComponent': | ||
| # The commands open and close seem to be reversed. | ||
| self.apply_action('close') | ||
| else: | ||
| self.apply_action('open') | ||
|
|
||
| def close_cover(self, **kwargs): | ||
| """Close the cover.""" | ||
| self.apply_action('close') | ||
| if self.tahoma_device.type == 'io:HorizontalAwningIOComponent': | ||
| # The commands open and close seem to be reversed. | ||
| self.apply_action('open') | ||
| else: | ||
| self.apply_action('close') | ||
|
|
||
| def stop_cover(self, **kwargs): | ||
| """Stop the cover.""" | ||
|
|
@@ -83,5 +191,9 @@ def stop_cover(self, **kwargs): | |
| ('rts:BlindRTSComponent', | ||
| 'io:ExteriorVenetianBlindIOComponent'): | ||
| self.apply_action('my') | ||
| elif self.tahoma_device.type in \ | ||
| ('io:VerticalExteriorAwningIOComponent', | ||
| 'io:HorizontalAwningIOComponent'): | ||
| self.apply_action('stop') | ||
| else: | ||
| self.apply_action('stopIdentify') | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please limit PRs to 1 feature per PR next time. So changes to binary sensor and cover: 2 prs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will do so in the future. Sorry.