-
-
Notifications
You must be signed in to change notification settings - Fork 37.5k
Add support for Insteon FanLinc fan #6959
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 all commits
590b3d0
8c0abfd
b917d37
6f6312d
7b8278d
04d1f7f
dce769f
15adaef
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,195 @@ | ||
| """ | ||
| Support for Insteon fans via local hub control. | ||
|
|
||
| For more details about this component, please refer to the documentation at | ||
| https://home-assistant.io/components/fan.insteon_local/ | ||
| """ | ||
| import json | ||
| import logging | ||
| import os | ||
| from datetime import timedelta | ||
|
|
||
| from homeassistant.components.fan import ( | ||
| ATTR_SPEED, SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH, | ||
| SUPPORT_SET_SPEED, FanEntity) | ||
| from homeassistant.helpers.entity import ToggleEntity | ||
| from homeassistant.loader import get_component | ||
| import homeassistant.util as util | ||
|
|
||
| _CONFIGURING = {} | ||
| _LOGGER = logging.getLogger(__name__) | ||
|
|
||
| DEPENDENCIES = ['insteon_local'] | ||
| DOMAIN = 'fan' | ||
|
|
||
| INSTEON_LOCAL_FANS_CONF = 'insteon_local_fans.conf' | ||
|
|
||
| MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100) | ||
| MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) | ||
|
|
||
| SUPPORT_INSTEON_LOCAL = SUPPORT_SET_SPEED | ||
|
|
||
|
|
||
| def setup_platform(hass, config, add_devices, discovery_info=None): | ||
| """Set up the Insteon local fan platform.""" | ||
| insteonhub = hass.data['insteon_local'] | ||
|
|
||
| conf_fans = config_from_file(hass.config.path(INSTEON_LOCAL_FANS_CONF)) | ||
| if len(conf_fans): | ||
| for device_id in conf_fans: | ||
| setup_fan(device_id, conf_fans[device_id], insteonhub, hass, | ||
| add_devices) | ||
|
|
||
| else: | ||
| linked = insteonhub.get_linked() | ||
|
|
||
| for device_id in linked: | ||
| if (linked[device_id]['cat_type'] == 'dimmer' and | ||
| linked[device_id]['sku'] == '2475F' and | ||
| device_id not in conf_fans): | ||
| request_configuration(device_id, | ||
| insteonhub, | ||
| linked[device_id]['model_name'] + ' ' + | ||
| linked[device_id]['sku'], | ||
| hass, add_devices) | ||
|
|
||
|
|
||
| def request_configuration(device_id, insteonhub, model, hass, | ||
| add_devices_callback): | ||
| """Request configuration steps from the user.""" | ||
| configurator = get_component('configurator') | ||
|
|
||
| # We got an error if this method is called while we are configuring | ||
| if device_id in _CONFIGURING: | ||
| configurator.notify_errors( | ||
| _CONFIGURING[device_id], 'Failed to register, please try again.') | ||
|
|
||
| return | ||
|
|
||
| def insteon_fan_config_callback(data): | ||
| """The actions to do when our configuration callback is called.""" | ||
| setup_fan(device_id, data.get('name'), insteonhub, hass, | ||
| add_devices_callback) | ||
|
|
||
| _CONFIGURING[device_id] = configurator.request_config( | ||
|
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. This is a very weird use case of the configurator. We shouldn't have people configure it like this, they can much better rename the devices via customize later. Let's remove this functionality.
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. Fine with me. Just as a heads up, this was directly copied from Insteon Local Light (this entire implementation is basically just a copy replacing
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. Yeah we should remove it there too, for a future PR. Let's just make sure we don't introduce new instances for now. |
||
| hass, 'Insteon ' + model + ' addr: ' + device_id, | ||
| insteon_fan_config_callback, | ||
| description=('Enter a name for ' + model + ' Fan addr: ' + device_id), | ||
| entity_picture='/static/images/config_insteon.png', | ||
| submit_caption='Confirm', | ||
| fields=[{'id': 'name', 'name': 'Name', 'type': ''}] | ||
| ) | ||
|
|
||
|
|
||
| def setup_fan(device_id, name, insteonhub, hass, add_devices_callback): | ||
| """Set up the fan.""" | ||
| if device_id in _CONFIGURING: | ||
| request_id = _CONFIGURING.pop(device_id) | ||
| configurator = get_component('configurator') | ||
| configurator.request_done(request_id) | ||
| _LOGGER.info("Device configuration done!") | ||
|
|
||
| conf_fans = config_from_file(hass.config.path(INSTEON_LOCAL_FANS_CONF)) | ||
| if device_id not in conf_fans: | ||
| conf_fans[device_id] = name | ||
|
|
||
| if not config_from_file( | ||
| hass.config.path(INSTEON_LOCAL_FANS_CONF), | ||
| conf_fans): | ||
| _LOGGER.error("Failed to save configuration file") | ||
|
|
||
| device = insteonhub.fan(device_id) | ||
| add_devices_callback([InsteonLocalFanDevice(device, name)]) | ||
|
|
||
|
|
||
| def config_from_file(filename, config=None): | ||
| """Small configuration file management function.""" | ||
| if config: | ||
| # We're writing configuration | ||
| try: | ||
| with open(filename, 'w') as fdesc: | ||
| fdesc.write(json.dumps(config)) | ||
| except IOError as error: | ||
| _LOGGER.error('Saving config file failed: %s', error) | ||
| return False | ||
| return True | ||
| else: | ||
| # We're reading config | ||
| if os.path.isfile(filename): | ||
| try: | ||
| with open(filename, 'r') as fdesc: | ||
| return json.loads(fdesc.read()) | ||
| except IOError as error: | ||
| _LOGGER.error("Reading configuration file failed: %s", error) | ||
| # This won't work yet | ||
| return False | ||
| else: | ||
| return {} | ||
|
|
||
|
|
||
| class InsteonLocalFanDevice(FanEntity): | ||
| """An abstract Class for an Insteon node.""" | ||
|
|
||
| def __init__(self, node, name): | ||
| """Initialize the device.""" | ||
| self.node = node | ||
| self.node.deviceName = name | ||
| self._speed = SPEED_OFF | ||
|
|
||
| @property | ||
| def name(self): | ||
| """Return the the name of the node.""" | ||
| return self.node.deviceName | ||
|
|
||
| @property | ||
| def unique_id(self): | ||
| """Return the ID of this Insteon node.""" | ||
| return 'insteon_local_{}_fan'.format(self.node.device_id) | ||
|
|
||
| @property | ||
| def speed(self) -> str: | ||
| """Return the current speed.""" | ||
| return self._speed | ||
|
|
||
| @property | ||
| def speed_list(self: ToggleEntity) -> list: | ||
| """Get the list of available speeds.""" | ||
| return [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH] | ||
|
|
||
| @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS) | ||
| def update(self): | ||
| """Update state of the fan.""" | ||
| resp = self.node.status() | ||
| if 'cmd2' in resp: | ||
| if resp['cmd2'] == '00': | ||
| self._speed = SPEED_OFF | ||
| elif resp['cmd2'] == '55': | ||
| self._speed = SPEED_LOW | ||
| elif resp['cmd2'] == 'AA': | ||
| self._speed = SPEED_MEDIUM | ||
| elif resp['cmd2'] == 'FF': | ||
| self._speed = SPEED_HIGH | ||
|
|
||
| @property | ||
| def supported_features(self): | ||
| """Flag supported features.""" | ||
| return SUPPORT_INSTEON_LOCAL | ||
|
|
||
| def turn_on(self: ToggleEntity, speed: str=None, **kwargs) -> None: | ||
| """Turn device on.""" | ||
| if speed is None: | ||
| if ATTR_SPEED in kwargs: | ||
| speed = kwargs[ATTR_SPEED] | ||
| else: | ||
| speed = SPEED_MEDIUM | ||
|
|
||
| self.set_speed(speed) | ||
|
|
||
| def turn_off(self: ToggleEntity, **kwargs) -> None: | ||
| """Turn device off.""" | ||
| self.node.off() | ||
|
|
||
| def set_speed(self: ToggleEntity, speed: str) -> None: | ||
| """Set the speed of the fan.""" | ||
| if self.node.on(speed): | ||
| self._speed = speed | ||
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.
'homeassistant.loader.get_component' imported but unused