Insteon local (#5088)

* platform set-up begin components

* lights seem to be getting set up properly, not sure why they aren't being added...

* typo

* Dependencies line

* toggle working

* toggle working

* added the switch to insteon_local

First commit hope to test tonight or in the morning

* Update insteon_local.py

* Update insteon_local.py

* Update insteon_local.py

* Update insteon_local.py

* Update insteon_local.py

* Update insteon_local.py

* Update insteon_local.py

* Update insteon_local.py

* Update insteon_local.py

* Update insteon_local.py

* Update insteon_local.py

* move dependency declaration before import?

* Move dependencies in Switch

* Update insteon_local.py

* wait for response

* switched the while to an if

switched the while 'cmd2' not in resp: to an if 'cmd2' not in resp: this seems to have the updater working

* Switched the while sleep loop to an if

switched the wile cmd2 not ins resp to be if cmd2 not in resp seems to be working.

* Update insteon_local.py

* import statement

Updated the import statement to import the instance of the insteon_local component not the hub Instance.

* updated import and the device assignment

update the import to import the instance of the insteon_local component not the hub.

* more changes to support the import change

* more changes to support the import change

* change to hass.data and add loop logic

* &&

* Update insteon_local.py

* Update insteon_local.py

* logic fixes and throttle

* reduce polling time

* brightness support

* import util

* hound fixes

* requirements file

* more hound fixes

* newline

* newline weirdness

* lint fixes

* more lint fixes

* switch state

* Update insteon_local.py

* log cmd2 for debugging

* assume success

* remove check for none

* fix comments

* fix comments again

* fix comments, add fixed version of lib, add support for timeout, add support for port, handle invalid login and connection problems

* fix logging exception

* fix hounceci-bot errors

* fix hounceci-bot errors

* requirements fix

* unique-id changes

* make dimmer off use saved ramp rate

* configurator working for lights

* configurator working for switches?

* configurator working for switches?

* include model names and fix lint errors

* lint fix

* fix exception order

* lint fixes

* fix lint errors

* update to use insteon local 0.38

* fix device id

* move status check to library

* move status check to library

* add SKU to setup

* lint fixes

* requirements

* linting
This commit is contained in:
Craig J. Ward 2017-01-08 17:33:35 -06:00 committed by Paulus Schoutsen
parent 2b14d407c0
commit a3971d7ad1
6 changed files with 447 additions and 0 deletions

View File

@ -37,6 +37,9 @@ omit =
homeassistant/components/insteon_hub.py
homeassistant/components/*/insteon_hub.py
homeassistant/components/insteon_local.py
homeassistant/components/*/insteon_local.py
homeassistant/components/ios.py
homeassistant/components/*/ios.py

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -0,0 +1,74 @@
"""
Local Support for Insteon.
Based on the insteonlocal library
https://github.com/phareous/insteonlocal
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/insteon_local/
"""
import logging
import voluptuous as vol
import requests
from homeassistant.const import (CONF_PASSWORD, CONF_USERNAME, CONF_HOST,
CONF_PORT, CONF_TIMEOUT)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['insteonlocal==0.39']
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'insteon_local'
DEFAULT_PORT = 25105
DEFAULT_TIMEOUT = 10
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int
})
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
"""Setup Insteon Hub component.
This will automatically import associated lights.
"""
from insteonlocal.Hub import Hub
conf = config[DOMAIN]
username = conf.get(CONF_USERNAME)
password = conf.get(CONF_PASSWORD)
host = conf.get(CONF_HOST)
port = conf.get(CONF_PORT)
timeout = conf.get(CONF_TIMEOUT)
try:
insteonhub = Hub(host, username, password, port, timeout, _LOGGER)
# check for successful connection
insteonhub.get_buffer_status()
except requests.exceptions.ConnectTimeout:
_LOGGER.error("Error on insteon_local."
"Could not connect. Check config", exc_info=True)
return False
except requests.exceptions.ConnectionError:
_LOGGER.error("Error on insteon_local. Could not connect."
"Check config", exc_info=True)
return False
except requests.exceptions.RequestException:
if insteonhub.http_code == 401:
_LOGGER.error("Bad user/pass for insteon_local hub")
return False
else:
_LOGGER.error("Error on insteon_local hub check", exc_info=True)
return False
hass.data['insteon_local'] = insteonhub
return True

View File

@ -0,0 +1,191 @@
"""
Support for Insteon dimmers via local hub control.
Based on the insteonlocal library
https://github.com/phareous/insteonlocal
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/light.insteon_local/
--
Example platform config
--
insteon_local:
host: YOUR HUB IP
username: YOUR HUB USERNAME
password: YOUR HUB PASSWORD
timeout: 10
port: 25105
"""
import json
import logging
import os
from datetime import timedelta
from homeassistant.components.light import (ATTR_BRIGHTNESS,
SUPPORT_BRIGHTNESS, Light)
from homeassistant.loader import get_component
import homeassistant.util as util
INSTEON_LOCAL_LIGHTS_CONF = 'insteon_local_lights.conf'
DEPENDENCIES = ['insteon_local']
SUPPORT_INSTEON_LOCAL = SUPPORT_BRIGHTNESS
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100)
DOMAIN = "light"
_LOGGER = logging.getLogger(__name__)
_CONFIGURING = {}
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Insteon local light platform."""
insteonhub = hass.data['insteon_local']
conf_lights = config_from_file(hass.config.path(INSTEON_LOCAL_LIGHTS_CONF))
if len(conf_lights):
for device_id in conf_lights:
setup_light(device_id, conf_lights[device_id], insteonhub, hass,
add_devices)
linked = insteonhub.get_linked()
for device_id in linked:
if (linked[device_id]['cat_type'] == 'dimmer' and
device_id not in conf_lights):
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_light_config_callback(data):
"""The actions to do when our configuration callback is called."""
setup_light(device_id, data.get('name'), insteonhub, hass,
add_devices_callback)
_CONFIGURING[device_id] = configurator.request_config(
hass, 'Insteon ' + model + ' addr: ' + device_id,
insteon_light_config_callback,
description=('Enter a name for ' + model + ' addr: ' + device_id),
entity_picture='/static/images/config_insteon.png',
submit_caption='Confirm',
fields=[{'id': 'name', 'name': 'Name', 'type': ''}]
)
def setup_light(device_id, name, insteonhub, hass, add_devices_callback):
"""Setup light."""
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_lights = config_from_file(hass.config.path(INSTEON_LOCAL_LIGHTS_CONF))
if device_id not in conf_lights:
conf_lights[device_id] = name
if not config_from_file(
hass.config.path(INSTEON_LOCAL_LIGHTS_CONF),
conf_lights):
_LOGGER.error('failed to save config file')
device = insteonhub.dimmer(device_id)
add_devices_callback([InsteonLocalDimmerDevice(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 config file failed: %s', error)
# This won't work yet
return False
else:
return {}
class InsteonLocalDimmerDevice(Light):
"""An abstract Class for an Insteon node."""
def __init__(self, node, name):
"""Initialize the device."""
self.node = node
self.node.deviceName = name
self._value = 0
@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_' + self.node.device_id
@property
def brightness(self):
"""Return the brightness of this light between 0..255."""
return self._value
@util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
def update(self):
"""Update state of the light."""
resp = self.node.status(0)
if 'cmd2' in resp:
self._value = int(resp['cmd2'], 16)
@property
def is_on(self):
"""Return the boolean response if the node is on."""
return self._value != 0
@property
def supported_features(self):
"""Flag supported features."""
return SUPPORT_INSTEON_LOCAL
def turn_on(self, **kwargs):
"""Turn device on."""
brightness = 100
if ATTR_BRIGHTNESS in kwargs:
brightness = int(kwargs[ATTR_BRIGHTNESS]) / 255 * 100
self.node.on(brightness)
def turn_off(self, **kwargs):
"""Turn device off."""
self.node.off()

View File

@ -0,0 +1,176 @@
"""
Support for Insteon switch devices via local hub support.
Based on the insteonlocal library
https://github.com/phareous/insteonlocal
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/switch.insteon_local/
--
Example platform config
--
insteon_local:
host: YOUR HUB IP
username: YOUR HUB USERNAME
password: YOUR HUB PASSWORD
timeout: 10
port: 25105
"""
import json
import logging
import os
from datetime import timedelta
from homeassistant.components.switch import SwitchDevice
from homeassistant.loader import get_component
import homeassistant.util as util
INSTEON_LOCAL_SWITCH_CONF = 'insteon_local_switch.conf'
DEPENDENCIES = ['insteon_local']
_LOGGER = logging.getLogger(__name__)
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5)
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100)
DOMAIN = "switch"
_LOGGER = logging.getLogger(__name__)
_CONFIGURING = {}
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the Insteon local switch platform."""
insteonhub = hass.data['insteon_local']
conf_switches = config_from_file(hass.config.path(
INSTEON_LOCAL_SWITCH_CONF))
if len(conf_switches):
for device_id in conf_switches:
setup_switch(device_id, conf_switches[device_id], insteonhub,
hass, add_devices)
linked = insteonhub.get_inked()
for device_id in linked:
if linked[device_id]['cat_type'] == 'switch'\
and device_id not in conf_switches:
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_switch_config_callback(data):
"""The actions to do when our configuration callback is called."""
setup_switch(device_id, data.get('name'), insteonhub, hass,
add_devices_callback)
_CONFIGURING[device_id] = configurator.request_config(
hass, 'Insteon Switch ' + model + ' addr: ' + device_id,
insteon_switch_config_callback,
description=('Enter a name for ' + model + ' addr: ' + device_id),
entity_picture='/static/images/config_insteon.png',
submit_caption='Confirm',
fields=[{'id': 'name', 'name': 'Name', 'type': ''}]
)
def setup_switch(device_id, name, insteonhub, hass, add_devices_callback):
"""Setup switch."""
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_switch = config_from_file(hass.config.path(INSTEON_LOCAL_SWITCH_CONF))
if device_id not in conf_switch:
conf_switch[device_id] = name
if not config_from_file(
hass.config.path(INSTEON_LOCAL_SWITCH_CONF), conf_switch):
_LOGGER.error('failed to save config file')
device = insteonhub.switch(device_id)
add_devices_callback([InsteonLocalSwitchDevice(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 config file failed: %s', error)
# This won't work yet
return False
else:
return {}
class InsteonLocalSwitchDevice(SwitchDevice):
"""An abstract Class for an Insteon node."""
def __init__(self, node, name):
"""Initialize the device."""
self.node = node
self.node.deviceName = name
self._state = False
@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_' + self.node.device_id
@util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
def update(self):
"""Get the updated status of the switch."""
resp = self.node.status(0)
if 'cmd2' in resp:
self._state = int(resp['cmd2'], 16) > 0
@property
def is_on(self):
"""Return the boolean response if the node is on."""
return self._state
def turn_on(self, **kwargs):
"""Turn device on."""
self.node.on()
self._state = True
def turn_off(self, **kwargs):
"""Turn device off."""
self.node.off()
self._state = False

View File

@ -271,6 +271,9 @@ influxdb==3.0.0
# homeassistant.components.insteon_hub
insteon_hub==0.4.5
# homeassistant.components.insteon_local
insteonlocal==0.39
# homeassistant.components.media_player.kodi
jsonrpc-async==0.1