Skip to content
Merged
Changes from all 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
195 changes: 195 additions & 0 deletions homeassistant/components/fan/insteon_local.py
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
Copy link
Copy Markdown

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

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(
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 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.

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.

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 light with fan).

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.

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