mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
Merge pull request #96 from automicus/master
Added support for ISY994 Insteon and X10 Controller (and other updates)
This commit is contained in:
commit
1dd26fcd73
@ -30,6 +30,11 @@ omit =
|
||||
homeassistant/components/device_tracker/ddwrt.py
|
||||
homeassistant/components/sensor/transmission.py
|
||||
|
||||
homeassistant/components/isy994.py
|
||||
homeassistant/components/light/isy994.py
|
||||
homeassistant/components/switch/isy994.py
|
||||
homeassistant/components/sensor/isy994.py
|
||||
|
||||
|
||||
[report]
|
||||
# Regexes for lines to exclude from consideration
|
||||
|
@ -27,9 +27,25 @@ A state can have several attributes that will help the frontend in displaying yo
|
||||
- `friendly_name`: this name will be used as the name of the device
|
||||
- `entity_picture`: this picture will be shown instead of the domain icon
|
||||
- `unit_of_measurement`: this will be appended to the state in the interface
|
||||
- `hidden`: This is a suggestion to the frontend on if the state should be hidden
|
||||
|
||||
These attributes are defined in [homeassistant.components](https://github.com/balloob/home-assistant/blob/master/homeassistant/components/__init__.py#L25).
|
||||
|
||||
## Proper Visibility Handling ##
|
||||
|
||||
Generally, when creating a new entity for Home Assistant you will want it to be a class that inherits the [homeassistant.helpers.entity.Entity](https://github.com/balloob/home-assistant/blob/master/homeassistant/helpers/entity.py] Class. If this is done, visibility will be handled for you.
|
||||
You can set a suggestion for your entitie's visibility by setting the hidden property by doing something similar to the following.
|
||||
|
||||
```python
|
||||
self.hidden = True
|
||||
```
|
||||
|
||||
This will SUGGEST that the active frontend hide the entity. This requires that the active frontend support hidden cards (the default frontend does) and that the value of hidden be included in your attributes dictionary (see above). The Entity abstract class will take care of this for you.
|
||||
|
||||
Remember: The suggestion set by your component's code will always be overwritten by manual settings in the configuration.yaml file. This is why you may set hidden to be False, but the property may remain True (or vice-versa).
|
||||
|
||||
If you would not like to use the Entity Abstract Class, you may also inherity the Visibility Abstract Class which will include the logic for the hidden property but not automatically add the hidden property to the attributes dictionary. If you use this class, ensure that your class correctly adds the hidden property to the attributes.
|
||||
|
||||
## Working on the frontend
|
||||
|
||||
The frontend is composed of Polymer web-components and compiled into the file `frontend.html`. During development you do not want to work with the compiled version but with the seperate files. To have Home Assistant serve the seperate files, set `development=1` for the http-component in your config.
|
||||
|
@ -20,6 +20,7 @@ import homeassistant
|
||||
import homeassistant.loader as loader
|
||||
import homeassistant.components as core_components
|
||||
import homeassistant.components.group as group
|
||||
from homeassistant.helpers.entity import VisibilityABC
|
||||
from homeassistant.const import (
|
||||
EVENT_COMPONENT_LOADED, CONF_LATITUDE, CONF_LONGITUDE,
|
||||
CONF_TEMPERATURE_UNIT, CONF_NAME, CONF_TIME_ZONE, TEMP_CELCIUS,
|
||||
@ -207,6 +208,8 @@ def process_ha_core_config(hass, config):
|
||||
if key in config:
|
||||
setattr(hass.config, attr, config[key])
|
||||
|
||||
VisibilityABC.visibility.update(config.get('visibility', {}))
|
||||
|
||||
if CONF_TEMPERATURE_UNIT in config:
|
||||
unit = config[CONF_TEMPERATURE_UNIT]
|
||||
|
||||
|
@ -15,8 +15,8 @@
|
||||
<link rel='shortcut icon' href='/static/favicon.ico' />
|
||||
<link rel='icon' type='image/png'
|
||||
href='/static/favicon-192x192.png' sizes='192x192'>
|
||||
<link rel='apple-touch-icon' sizes='180x180'
|
||||
href='/apple-icon-180x180.png'>
|
||||
<link rel='apple-touch-icon' sizes='192x192'
|
||||
href='/static/favicon-192x192.png'>
|
||||
<meta name='theme-color' content='#03a9f4'>
|
||||
</head>
|
||||
<body fullbleed>
|
||||
|
@ -1,2 +1,2 @@
|
||||
""" DO NOT MODIFY. Auto-generated by build_frontend script """
|
||||
VERSION = "b432551a6704deb437aac61cdecef864"
|
||||
VERSION = "e7801905cc2ea1ee349ec199604fb984"
|
||||
|
File diff suppressed because one or more lines are too long
@ -144,7 +144,8 @@
|
||||
});
|
||||
}
|
||||
|
||||
this.states = states.toArray();
|
||||
this.states = states.toArray().filter(
|
||||
function (el) {return !el.attributes.hidden});
|
||||
},
|
||||
|
||||
handleRefreshClick: function() {
|
||||
|
@ -7,10 +7,11 @@ Provides functionality to group devices that can be turned on or off.
|
||||
|
||||
import homeassistant as ha
|
||||
from homeassistant.helpers import generate_entity_id
|
||||
from homeassistant.helpers.entity import VisibilityABC
|
||||
import homeassistant.util as util
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, STATE_ON, STATE_OFF,
|
||||
STATE_HOME, STATE_NOT_HOME, STATE_UNKNOWN)
|
||||
STATE_HOME, STATE_NOT_HOME, STATE_UNKNOWN, ATTR_HIDDEN)
|
||||
|
||||
DOMAIN = "group"
|
||||
DEPENDENCIES = []
|
||||
@ -110,8 +111,9 @@ def setup(hass, config):
|
||||
return True
|
||||
|
||||
|
||||
class Group(object):
|
||||
class Group(VisibilityABC):
|
||||
""" Tracks a group of entity ids. """
|
||||
|
||||
def __init__(self, hass, name, entity_ids=None, user_defined=True):
|
||||
self.hass = hass
|
||||
self.name = name
|
||||
@ -138,7 +140,8 @@ class Group(object):
|
||||
return {
|
||||
ATTR_ENTITY_ID: self.tracking,
|
||||
ATTR_AUTO: not self.user_defined,
|
||||
ATTR_FRIENDLY_NAME: self.name
|
||||
ATTR_FRIENDLY_NAME: self.name,
|
||||
ATTR_HIDDEN: self.hidden
|
||||
}
|
||||
|
||||
def update_tracked_entity_ids(self, entity_ids):
|
||||
|
209
homeassistant/components/isy994.py
Normal file
209
homeassistant/components/isy994.py
Normal file
@ -0,0 +1,209 @@
|
||||
"""
|
||||
Connects to an ISY-994 controller and loads relevant components to control its
|
||||
devices. Also contains the base classes for ISY Sensors, Lights, and Switches.
|
||||
"""
|
||||
# system imports
|
||||
import logging
|
||||
from urllib.parse import urlparse
|
||||
|
||||
# addon library imports
|
||||
import PyISY
|
||||
|
||||
# homeassistant imports
|
||||
from homeassistant import bootstrap
|
||||
from homeassistant.loader import get_component
|
||||
from homeassistant.helpers import validate_config
|
||||
from homeassistant.helpers.entity import ToggleEntity
|
||||
from homeassistant.const import (
|
||||
CONF_HOST, CONF_USERNAME, CONF_PASSWORD, EVENT_PLATFORM_DISCOVERED,
|
||||
ATTR_SERVICE, ATTR_DISCOVERED, ATTR_FRIENDLY_NAME)
|
||||
|
||||
# homeassistant constants
|
||||
DOMAIN = "isy994"
|
||||
DEPENDENCIES = []
|
||||
DISCOVER_LIGHTS = "isy994.lights"
|
||||
DISCOVER_SWITCHES = "isy994.switches"
|
||||
DISCOVER_SENSORS = "isy994.sensors"
|
||||
ISY = None
|
||||
SENSOR_STRING = 'Sensor'
|
||||
HIDDEN_STRING = '{HIDE ME}'
|
||||
|
||||
# setup logger
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def setup(hass, config):
|
||||
"""
|
||||
Setup isy994 component.
|
||||
This will automatically import associated lights, switches, and sensors.
|
||||
"""
|
||||
# pylint: disable=global-statement
|
||||
# check for required values in configuration file
|
||||
if not validate_config(config,
|
||||
{DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]},
|
||||
_LOGGER):
|
||||
return False
|
||||
|
||||
# pull and parse standard configuration
|
||||
user = config[DOMAIN][CONF_USERNAME]
|
||||
password = config[DOMAIN][CONF_PASSWORD]
|
||||
host = urlparse(config[DOMAIN][CONF_HOST])
|
||||
addr = host.geturl()
|
||||
if host.scheme == 'http':
|
||||
addr = addr.replace('http://', '')
|
||||
https = False
|
||||
elif host.scheme == 'https':
|
||||
addr = addr.replace('https://', '')
|
||||
https = True
|
||||
else:
|
||||
_LOGGER.error('isy994 host value in configuration file is invalid.')
|
||||
return False
|
||||
port = host.port
|
||||
addr = addr.replace(':{}'.format(port), '')
|
||||
|
||||
# pull and parse optional configuration
|
||||
global SENSOR_STRING
|
||||
global HIDDEN_STRING
|
||||
SENSOR_STRING = str(config[DOMAIN].get('sensor_string', SENSOR_STRING))
|
||||
HIDDEN_STRING = str(config[DOMAIN].get('hidden_string', HIDDEN_STRING))
|
||||
|
||||
# connect to ISY controller
|
||||
global ISY
|
||||
ISY = PyISY.ISY(addr, port, user, password, use_https=https, log=_LOGGER)
|
||||
if not ISY.connected:
|
||||
return False
|
||||
|
||||
# Load components for the devices in the ISY controller that we support
|
||||
for comp_name, discovery in ((('sensor', DISCOVER_SENSORS),
|
||||
('light', DISCOVER_LIGHTS),
|
||||
('switch', DISCOVER_SWITCHES))):
|
||||
component = get_component(comp_name)
|
||||
bootstrap.setup_component(hass, component.DOMAIN, config)
|
||||
hass.bus.fire(EVENT_PLATFORM_DISCOVERED,
|
||||
{ATTR_SERVICE: discovery,
|
||||
ATTR_DISCOVERED: {}})
|
||||
|
||||
ISY.auto_update = True
|
||||
return True
|
||||
|
||||
|
||||
class ISYDeviceABC(ToggleEntity):
|
||||
""" Abstract Class for an ISY device within home assistant. """
|
||||
|
||||
_attrs = {}
|
||||
_onattrs = []
|
||||
_states = []
|
||||
_dtype = None
|
||||
_domain = None
|
||||
_name = None
|
||||
|
||||
def __init__(self, node):
|
||||
# setup properties
|
||||
self.node = node
|
||||
self.hidden = HIDDEN_STRING in self.raw_name
|
||||
|
||||
# track changes
|
||||
self._change_handler = self.node.status. \
|
||||
subscribe('changed', self.on_update)
|
||||
|
||||
def __del__(self):
|
||||
""" cleanup subscriptions because it is the right thing to do. """
|
||||
self._change_handler.unsubscribe()
|
||||
|
||||
@property
|
||||
def domain(self):
|
||||
""" Returns the domain of the entity. """
|
||||
return self._domain
|
||||
|
||||
@property
|
||||
def dtype(self):
|
||||
""" Returns the data type of the entity (binary or analog). """
|
||||
if self._dtype in ['analog', 'binary']:
|
||||
return self._dtype
|
||||
return 'binary' if self.unit_of_measurement is None else 'analog'
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
""" Tells Home Assistant not to poll this entity. """
|
||||
return False
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
""" returns the unclean value from the controller """
|
||||
# pylint: disable=protected-access
|
||||
return self.node.status._val
|
||||
|
||||
@property
|
||||
def state_attributes(self):
|
||||
""" Returns the state attributes for the node. """
|
||||
attr = {ATTR_FRIENDLY_NAME: self.name}
|
||||
for name, prop in self._attrs.items():
|
||||
attr[name] = getattr(self, prop)
|
||||
return attr
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
""" Returns the id of this isy sensor """
|
||||
# pylint: disable=protected-access
|
||||
return self.node._id
|
||||
|
||||
@property
|
||||
def raw_name(self):
|
||||
""" Returns the unclean node name. """
|
||||
return str(self._name) \
|
||||
if self._name is not None else str(self.node.name)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" Returns the cleaned name of the node. """
|
||||
return self.raw_name.replace(HIDDEN_STRING, '').strip() \
|
||||
.replace('_', ' ')
|
||||
|
||||
def update(self):
|
||||
""" Update state of the sensor. """
|
||||
# ISY objects are automatically updated by the ISY's event stream
|
||||
pass
|
||||
|
||||
def on_update(self, event):
|
||||
""" Handles the update received event. """
|
||||
self.update_ha_state()
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
""" Returns boolean response if the node is on. """
|
||||
return bool(self.value)
|
||||
|
||||
@property
|
||||
def is_open(self):
|
||||
""" Returns boolean respons if the node is open. On = Open. """
|
||||
return self.is_on
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
""" Returns the state of the node. """
|
||||
if len(self._states) > 0:
|
||||
return self._states[0] if self.is_on else self._states[1]
|
||||
return self.value
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
""" turns the device on """
|
||||
if self.domain is not 'sensor':
|
||||
attrs = [kwargs.get(name) for name in self._onattrs]
|
||||
self.node.on(*attrs)
|
||||
else:
|
||||
_LOGGER.error('ISY cannot turn on sensors.')
|
||||
|
||||
def turn_off(self, **kwargs):
|
||||
""" turns the device off """
|
||||
if self.domain is not 'sensor':
|
||||
self.node.off()
|
||||
else:
|
||||
_LOGGER.error('ISY cannot turn off sensors.')
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
""" Returns the defined units of measurement or None. """
|
||||
try:
|
||||
return self.node.units
|
||||
except AttributeError:
|
||||
return None
|
@ -57,7 +57,7 @@ from homeassistant.helpers.entity_component import EntityComponent
|
||||
import homeassistant.util as util
|
||||
from homeassistant.const import (
|
||||
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID)
|
||||
from homeassistant.components import group, discovery, wink
|
||||
from homeassistant.components import group, discovery, wink, isy994
|
||||
|
||||
|
||||
DOMAIN = "light"
|
||||
@ -92,6 +92,7 @@ LIGHT_PROFILES_FILE = "light_profiles.csv"
|
||||
# Maps discovered services to their platforms
|
||||
DISCOVERY_PLATFORMS = {
|
||||
wink.DISCOVER_LIGHTS: 'wink',
|
||||
isy994.DISCOVER_LIGHTS: 'isy994',
|
||||
discovery.services.PHILIPS_HUE: 'hue',
|
||||
}
|
||||
|
||||
|
38
homeassistant/components/light/isy994.py
Normal file
38
homeassistant/components/light/isy994.py
Normal file
@ -0,0 +1,38 @@
|
||||
""" Support for ISY994 lights. """
|
||||
# system imports
|
||||
import logging
|
||||
|
||||
# homeassistant imports
|
||||
from homeassistant.components.isy994 import (ISYDeviceABC, ISY, SENSOR_STRING,
|
||||
HIDDEN_STRING)
|
||||
from homeassistant.components.light import ATTR_BRIGHTNESS
|
||||
from homeassistant.const import STATE_ON, STATE_OFF
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
""" Sets up the isy994 platform. """
|
||||
logger = logging.getLogger(__name__)
|
||||
devs = []
|
||||
# verify connection
|
||||
if ISY is None or not ISY.connected:
|
||||
logger.error('A connection has not been made to the ISY controller.')
|
||||
return False
|
||||
|
||||
# import dimmable nodes
|
||||
for (path, node) in ISY.nodes:
|
||||
if node.dimmable and SENSOR_STRING not in node.name:
|
||||
if HIDDEN_STRING in path:
|
||||
node.name += HIDDEN_STRING
|
||||
devs.append(ISYLightDevice(node))
|
||||
|
||||
add_devices(devs)
|
||||
|
||||
|
||||
class ISYLightDevice(ISYDeviceABC):
|
||||
""" represents as isy light within home assistant. """
|
||||
|
||||
_domain = 'light'
|
||||
_dtype = 'analog'
|
||||
_attrs = {ATTR_BRIGHTNESS: 'value'}
|
||||
_onattrs = [ATTR_BRIGHTNESS]
|
||||
_states = [STATE_ON, STATE_OFF]
|
@ -6,7 +6,7 @@ Component to interface with various sensors that can be monitored.
|
||||
import logging
|
||||
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
from homeassistant.components import wink, zwave
|
||||
from homeassistant.components import wink, zwave, isy994
|
||||
|
||||
DOMAIN = 'sensor'
|
||||
DEPENDENCIES = []
|
||||
@ -18,6 +18,7 @@ ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||
DISCOVERY_PLATFORMS = {
|
||||
wink.DISCOVER_SENSORS: 'wink',
|
||||
zwave.DISCOVER_SENSORS: 'zwave',
|
||||
isy994.DISCOVER_SENSORS: 'isy994'
|
||||
}
|
||||
|
||||
|
||||
|
90
homeassistant/components/sensor/isy994.py
Normal file
90
homeassistant/components/sensor/isy994.py
Normal file
@ -0,0 +1,90 @@
|
||||
""" Support for ISY994 sensors. """
|
||||
# system imports
|
||||
import logging
|
||||
|
||||
# homeassistant imports
|
||||
from homeassistant.components.isy994 import (ISY, ISYDeviceABC, SENSOR_STRING,
|
||||
HIDDEN_STRING)
|
||||
from homeassistant.const import (STATE_OPEN, STATE_CLOSED, STATE_HOME,
|
||||
STATE_NOT_HOME, STATE_ON, STATE_OFF)
|
||||
|
||||
DEFAULT_HIDDEN_WEATHER = ['Temperature_High', 'Temperature_Low', 'Feels_Like',
|
||||
'Temperature_Average', 'Pressure', 'Dew_Point',
|
||||
'Gust_Speed', 'Evapotranspiration',
|
||||
'Irrigation_Requirement', 'Water_Deficit_Yesterday',
|
||||
'Elevation', 'Average_Temperature_Tomorrow',
|
||||
'High_Temperature_Tomorrow',
|
||||
'Low_Temperature_Tomorrow', 'Humidity_Tomorrow',
|
||||
'Wind_Speed_Tomorrow', 'Gust_Speed_Tomorrow',
|
||||
'Rain_Tomorrow', 'Snow_Tomorrow',
|
||||
'Forecast_Average_Temperature',
|
||||
'Forecast_High_Temperature',
|
||||
'Forecast_Low_Temperature', 'Forecast_Humidity',
|
||||
'Forecast_Rain', 'Forecast_Snow']
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
""" Sets up the isy994 platform. """
|
||||
# pylint: disable=protected-access
|
||||
logger = logging.getLogger(__name__)
|
||||
devs = []
|
||||
# verify connection
|
||||
if ISY is None or not ISY.connected:
|
||||
logger.error('A connection has not been made to the ISY controller.')
|
||||
return False
|
||||
|
||||
# import weather
|
||||
if ISY.climate is not None:
|
||||
for prop in ISY.climate._id2name:
|
||||
if prop is not None:
|
||||
prefix = HIDDEN_STRING \
|
||||
if prop in DEFAULT_HIDDEN_WEATHER else ''
|
||||
node = WeatherPseudoNode('ISY.weather.' + prop, prefix + prop,
|
||||
getattr(ISY.climate, prop),
|
||||
getattr(ISY.climate, prop + '_units'))
|
||||
devs.append(ISYSensorDevice(node))
|
||||
|
||||
# import sensor nodes
|
||||
for (path, node) in ISY.nodes:
|
||||
if SENSOR_STRING in node.name:
|
||||
if HIDDEN_STRING in path:
|
||||
node.name += HIDDEN_STRING
|
||||
devs.append(ISYSensorDevice(node, [STATE_ON, STATE_OFF]))
|
||||
|
||||
# import sensor programs
|
||||
for (folder_name, states) in (
|
||||
('HA.locations', [STATE_HOME, STATE_NOT_HOME]),
|
||||
('HA.sensors', [STATE_OPEN, STATE_CLOSED]),
|
||||
('HA.states', [STATE_ON, STATE_OFF])):
|
||||
try:
|
||||
folder = ISY.programs['My Programs'][folder_name]
|
||||
except KeyError:
|
||||
# folder does not exist
|
||||
pass
|
||||
else:
|
||||
for _, _, node_id in folder.children:
|
||||
node = folder[node_id].leaf
|
||||
devs.append(ISYSensorDevice(node, states))
|
||||
|
||||
add_devices(devs)
|
||||
|
||||
|
||||
class WeatherPseudoNode(object):
|
||||
""" This class allows weather variable to act as regular nodes. """
|
||||
# pylint: disable=too-few-public-methods
|
||||
|
||||
def __init__(self, device_id, name, status, units=None):
|
||||
self._id = device_id
|
||||
self.name = name
|
||||
self.status = status
|
||||
self.units = units
|
||||
|
||||
|
||||
class ISYSensorDevice(ISYDeviceABC):
|
||||
""" represents a isy sensor within home assistant. """
|
||||
|
||||
_domain = 'sensor'
|
||||
|
||||
def __init__(self, node, states=None):
|
||||
super().__init__(node)
|
||||
self._states = states or []
|
@ -10,7 +10,7 @@ from homeassistant.helpers.entity_component import EntityComponent
|
||||
|
||||
from homeassistant.const import (
|
||||
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID)
|
||||
from homeassistant.components import group, discovery, wink
|
||||
from homeassistant.components import group, discovery, wink, isy994
|
||||
|
||||
DOMAIN = 'switch'
|
||||
DEPENDENCIES = []
|
||||
@ -30,6 +30,7 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
|
||||
DISCOVERY_PLATFORMS = {
|
||||
discovery.services.BELKIN_WEMO: 'wemo',
|
||||
wink.DISCOVER_SWITCHES: 'wink',
|
||||
isy994.DISCOVER_SWITCHES: 'isy994',
|
||||
}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
82
homeassistant/components/switch/isy994.py
Normal file
82
homeassistant/components/switch/isy994.py
Normal file
@ -0,0 +1,82 @@
|
||||
""" Support for ISY994 switch. """
|
||||
# system imports
|
||||
import logging
|
||||
|
||||
# homeassistant imports
|
||||
from homeassistant.components.isy994 import (ISY, ISYDeviceABC, SENSOR_STRING,
|
||||
HIDDEN_STRING)
|
||||
from homeassistant.const import STATE_ON, STATE_OFF # STATE_OPEN, STATE_CLOSED
|
||||
# The frontend doesn't seem to fully support the open and closed states yet.
|
||||
# Once it does, the HA.doors programs should report open and closed instead of
|
||||
# off and on. It appears that on should be open and off should be closed.
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
""" Sets up the isy994 platform. """
|
||||
# pylint: disable=too-many-locals
|
||||
logger = logging.getLogger(__name__)
|
||||
devs = []
|
||||
# verify connection
|
||||
if ISY is None or not ISY.connected:
|
||||
logger.error('A connection has not been made to the ISY controller.')
|
||||
return False
|
||||
|
||||
# import not dimmable nodes and groups
|
||||
for (path, node) in ISY.nodes:
|
||||
if not node.dimmable and SENSOR_STRING not in node.name:
|
||||
if HIDDEN_STRING in path:
|
||||
node.name += HIDDEN_STRING
|
||||
devs.append(ISYSwitchDevice(node))
|
||||
|
||||
# import ISY doors programs
|
||||
for folder_name, states in (('HA.doors', [STATE_ON, STATE_OFF]),
|
||||
('HA.switches', [STATE_ON, STATE_OFF])):
|
||||
try:
|
||||
folder = ISY.programs['My Programs'][folder_name]
|
||||
except KeyError:
|
||||
# HA.doors folder does not exist
|
||||
pass
|
||||
else:
|
||||
for dtype, name, node_id in folder.children:
|
||||
if dtype is 'folder':
|
||||
custom_switch = folder[node_id]
|
||||
try:
|
||||
actions = custom_switch['actions'].leaf
|
||||
assert actions.dtype == 'program', 'Not a program'
|
||||
node = custom_switch['status'].leaf
|
||||
except (KeyError, AssertionError):
|
||||
pass
|
||||
else:
|
||||
devs.append(ISYProgramDevice(name, node, actions,
|
||||
states))
|
||||
|
||||
add_devices(devs)
|
||||
|
||||
|
||||
class ISYSwitchDevice(ISYDeviceABC):
|
||||
""" represents as isy light within home assistant. """
|
||||
|
||||
_domain = 'switch'
|
||||
_dtype = 'binary'
|
||||
_states = [STATE_ON, STATE_OFF]
|
||||
|
||||
|
||||
class ISYProgramDevice(ISYSwitchDevice):
|
||||
""" represents a door that can be manipulated within home assistant. """
|
||||
|
||||
_domain = 'switch'
|
||||
_dtype = 'binary'
|
||||
|
||||
def __init__(self, name, node, actions, states):
|
||||
super().__init__(node)
|
||||
self._states = states
|
||||
self._name = name
|
||||
self.action_node = actions
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
""" turns the device on/closes the device """
|
||||
self.action_node.runThen()
|
||||
|
||||
def turn_off(self, **kwargs):
|
||||
""" turns the device off/opens the device """
|
||||
self.action_node.runElse()
|
@ -86,6 +86,9 @@ ATTR_TRIPPED = "device_tripped"
|
||||
# time the device was tripped
|
||||
ATTR_LAST_TRIP_TIME = "last_tripped_time"
|
||||
|
||||
# For all entity's, this hold whether or not it should be hidden
|
||||
ATTR_HIDDEN = "hidden"
|
||||
|
||||
# #### SERVICES ####
|
||||
SERVICE_HOMEASSISTANT_STOP = "stop"
|
||||
|
||||
|
2
homeassistant/external/nzbclients
vendored
2
homeassistant/external/nzbclients
vendored
@ -1 +1 @@
|
||||
Subproject commit f01997498fe190d6ac2a2c375a739024843bd44d
|
||||
Subproject commit f9f9ba36934f087b9c4241303b900794a7eb6c08
|
@ -8,16 +8,51 @@ Provides ABC for entities in HA.
|
||||
from homeassistant import NoEntitySpecifiedError
|
||||
|
||||
from homeassistant.const import (
|
||||
ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT, STATE_ON, STATE_OFF,
|
||||
DEVICE_DEFAULT_NAME, TEMP_CELCIUS, TEMP_FAHRENHEIT)
|
||||
ATTR_FRIENDLY_NAME, ATTR_UNIT_OF_MEASUREMENT, ATTR_HIDDEN, STATE_ON,
|
||||
STATE_OFF, DEVICE_DEFAULT_NAME, TEMP_CELCIUS, TEMP_FAHRENHEIT)
|
||||
|
||||
|
||||
class Entity(object):
|
||||
class VisibilityABC(object):
|
||||
"""
|
||||
Abstract Class for including visibility logic. This class includes the
|
||||
necessary methods and properties to consider a visibility suggestion form
|
||||
the component and then determine visibility based on the options in the
|
||||
configuration file. When using this abstract class, the value for the
|
||||
hidden property must still be included in the attributes disctionary. The
|
||||
Entity class takes care of this automatically.
|
||||
"""
|
||||
# pylint: disable=too-few-public-methods
|
||||
|
||||
entity_id = None
|
||||
visibility = {}
|
||||
_hidden = False
|
||||
|
||||
@property
|
||||
def hidden(self):
|
||||
"""
|
||||
Returns the official decision of whether the entity should be hidden.
|
||||
Any value set by the user in the configuration file will overwrite
|
||||
whatever the component sets for visibility.
|
||||
"""
|
||||
if self.entity_id is not None and \
|
||||
self.entity_id.lower() in self.visibility:
|
||||
return self.visibility[self.entity_id.lower()] == 'hide'
|
||||
else:
|
||||
return self._hidden
|
||||
|
||||
@hidden.setter
|
||||
def hidden(self, val):
|
||||
""" Sets the suggestion for visibility. """
|
||||
self._hidden = bool(val)
|
||||
|
||||
|
||||
class Entity(VisibilityABC):
|
||||
""" ABC for Home Assistant entities. """
|
||||
# pylint: disable=no-self-use
|
||||
|
||||
hass = None
|
||||
entity_id = None
|
||||
# SAFE TO OVERWRITE
|
||||
# The properties and methods here are safe to overwrite when inherting this
|
||||
# class. These may be used to customize the behavior of the entity.
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
@ -52,6 +87,10 @@ class Entity(object):
|
||||
""" Unit of measurement of this entity, if any. """
|
||||
return None
|
||||
|
||||
def update(self):
|
||||
""" Retrieve latest state. """
|
||||
pass
|
||||
|
||||
# DEPRECATION NOTICE:
|
||||
# Device is moving from getters to properties.
|
||||
# For now the new properties will call the old functions
|
||||
@ -69,9 +108,13 @@ class Entity(object):
|
||||
""" Returns optional state attributes. """
|
||||
return None
|
||||
|
||||
def update(self):
|
||||
""" Retrieve latest state. """
|
||||
pass
|
||||
# DO NOT OVERWRITE
|
||||
# These properties and methods are either managed by Home Assistant or they
|
||||
# are used to perform a very specific function. Overwriting these may
|
||||
# produce undesirable effects in the entity's operation.
|
||||
|
||||
hass = None
|
||||
entity_id = None
|
||||
|
||||
def update_ha_state(self, force_refresh=False):
|
||||
"""
|
||||
@ -97,6 +140,9 @@ class Entity(object):
|
||||
if ATTR_UNIT_OF_MEASUREMENT not in attr and self.unit_of_measurement:
|
||||
attr[ATTR_UNIT_OF_MEASUREMENT] = self.unit_of_measurement
|
||||
|
||||
if ATTR_HIDDEN not in attr:
|
||||
attr[ATTR_HIDDEN] = bool(self.hidden)
|
||||
|
||||
# Convert temperature if we detect one
|
||||
if attr.get(ATTR_UNIT_OF_MEASUREMENT) in (TEMP_CELCIUS,
|
||||
TEMP_FAHRENHEIT):
|
||||
|
@ -34,6 +34,9 @@ python-nest>=2.1
|
||||
# z-wave
|
||||
pydispatcher>=2.0.5
|
||||
|
||||
# isy994
|
||||
PyISY>=1.0.2
|
||||
|
||||
# sensor.systemmonitor
|
||||
psutil>=2.2.1
|
||||
|
||||
|
@ -34,5 +34,5 @@ if [ $(command -v md5) ]; then
|
||||
elif [ $(command -v md5sum) ]; then
|
||||
echo 'VERSION = "'`md5sum www_static/frontend.html | cut -c-32`'"' >> version.py
|
||||
else
|
||||
echo 'Could not find a MD5 utility'
|
||||
echo 'Could not find an MD5 utility'
|
||||
fi
|
||||
|
97
scripts/get_entities.py
Executable file
97
scripts/get_entities.py
Executable file
@ -0,0 +1,97 @@
|
||||
#! /usr/bin/python
|
||||
"""
|
||||
Query the Home Assistant API for available entities then print them and any
|
||||
desired attributes to the screen.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import getpass
|
||||
import argparse
|
||||
try:
|
||||
from urllib2 import urlopen
|
||||
PYTHON = 2
|
||||
except ImportError:
|
||||
from urllib.request import urlopen
|
||||
PYTHON = 3
|
||||
import json
|
||||
|
||||
|
||||
def main(password, askpass, attrs, address, port):
|
||||
""" fetch Home Assistant api json page and post process """
|
||||
# ask for password
|
||||
if askpass:
|
||||
password = getpass.getpass('Home Assistant API Password: ')
|
||||
|
||||
# fetch API result
|
||||
url = mk_url(address, port, password)
|
||||
response = urlopen(url).read()
|
||||
if PYTHON == 3:
|
||||
response = response.decode('utf-8')
|
||||
data = json.loads(response)
|
||||
|
||||
# parse data
|
||||
output = {'entity_id': []}
|
||||
output.update([(attr, []) for attr in attrs])
|
||||
for item in data:
|
||||
output['entity_id'].append(item['entity_id'])
|
||||
for attr in attrs:
|
||||
output[attr].append(item['attributes'].get(attr, ''))
|
||||
|
||||
# output data
|
||||
print_table(output, ['entity_id'] + attrs)
|
||||
|
||||
|
||||
def print_table(data, columns):
|
||||
""" format and print a table of data from a dictionary """
|
||||
# get column lengths
|
||||
lengths = {}
|
||||
for key, value in data.items():
|
||||
lengths[key] = max([len(str(val)) for val in value] + [len(key)])
|
||||
|
||||
# print header
|
||||
for item in columns:
|
||||
itemup = item.upper()
|
||||
sys.stdout.write(itemup + ' ' * (lengths[item] - len(item) + 4))
|
||||
sys.stdout.write('\n')
|
||||
|
||||
# print body
|
||||
for ind in range(len(data[columns[0]])):
|
||||
for item in columns:
|
||||
val = str(data[item][ind])
|
||||
sys.stdout.write(val + ' ' * (lengths[item] - len(val) + 4))
|
||||
sys.stdout.write("\n")
|
||||
|
||||
|
||||
def mk_url(address, port, password):
|
||||
""" construct the url call for the api states page """
|
||||
url = ''
|
||||
if address.startswith('http://'):
|
||||
url += address
|
||||
else:
|
||||
url += 'http://' + address
|
||||
url += ':' + port + '/api/states?'
|
||||
if password is not None:
|
||||
url += 'api_password=' + password
|
||||
return url
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
all_options = {'password': None, 'askpass': False, 'attrs': [],
|
||||
'address': 'localhost', 'port': '8123'}
|
||||
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('attrs', metavar='ATTRIBUTE', type=str, nargs='*',
|
||||
help='an attribute to read from the state')
|
||||
parser.add_argument('--password', dest='password', default=None,
|
||||
type=str, help='API password for the HA server')
|
||||
parser.add_argument('--ask-password', dest='askpass', default=False,
|
||||
action='store_const', const=True,
|
||||
help='prompt for HA API password')
|
||||
parser.add_argument('--addr', dest='address',
|
||||
default='localhost', type=str,
|
||||
help='address of the HA server')
|
||||
parser.add_argument('--port', dest='port', default='8123',
|
||||
type=str, help='port that HA is hosting on')
|
||||
|
||||
args = parser.parse_args()
|
||||
main(args.password, args.askpass, args.attrs, args.address, args.port)
|
Loading…
x
Reference in New Issue
Block a user