Skip to content
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

New Hive Component / Platforms #9804

Merged
merged 34 commits into from
Nov 23, 2017
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
9c3d11d
New Hive Component / Platforms
Rendili Oct 10, 2017
74e10f3
New Hive Component / Platforms
Rendili Nov 1, 2017
b38e659
New Hive Component / Platforms
Rendili Nov 1, 2017
1e9a9c0
New Hive Component / Platforms
Rendili Nov 1, 2017
36e313e
New Hive Component / Platforms
Rendili Nov 1, 2017
b6e2df3
New Hive Component / Platforms
Rendili Nov 1, 2017
a16df0e
New Hive Component / Platforms
Rendili Nov 8, 2017
863f569
New Hive Component / Platforms
Rendili Nov 13, 2017
1f81751
New Hive Component / Platforms
Rendili Nov 16, 2017
6e302a7
New Hive Component / Platforms
Rendili Nov 16, 2017
dca60ef
New Hive Component / Platforms
Rendili Nov 16, 2017
0457a01
New Hive Component / Platforms
Rendili Nov 16, 2017
5e7d872
New Hive Component / Platforms
Rendili Nov 17, 2017
63549cf
New Hive Component / Platforms
Rendili Nov 17, 2017
1595301
Changes
Rendili Nov 18, 2017
6dc23c3
Changes
Rendili Nov 18, 2017
0cc6339
Changes
Rendili Nov 18, 2017
7b54dbd
changes
Rendili Nov 18, 2017
2f4c801
Updates
Rendili Nov 18, 2017
b2ccfc3
Updates
Rendili Nov 18, 2017
0860845
Updates
Rendili Nov 18, 2017
098112a
Updates
Rendili Nov 19, 2017
6aed8d5
Updates
Rendili Nov 19, 2017
03d925c
Updates
Rendili Nov 19, 2017
c69912d
Sensor code updates
Rendili Nov 19, 2017
d4ed607
Sensor code updates
Rendili Nov 19, 2017
fe4cbe3
Move sensors to binary sensors
Rendili Nov 20, 2017
a63fccf
Quack
Rendili Nov 20, 2017
fff1397
Updates - Removed climate related sensors
Rendili Nov 21, 2017
30f5666
sensor fix
Rendili Nov 21, 2017
21c3dc8
binary_sensor updates
Rendili Nov 21, 2017
a80dd4b
New Hive Component / Platforms
Rendili Nov 22, 2017
b5d7053
New Hive Component / Platforms
Rendili Nov 22, 2017
510f777
New Hive Component / Platforms
Rendili Nov 22, 2017
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
3 changes: 3 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ omit =
homeassistant/components/hdmi_cec.py
homeassistant/components/*/hdmi_cec.py

homeassistant/components/hive.py
homeassistant/components/*/hive.py

homeassistant/components/homematic.py
homeassistant/components/*/homematic.py

Expand Down
67 changes: 67 additions & 0 deletions homeassistant/components/binary_sensor/hive.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"""
Support for the Hive devices.

For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.hive/
"""
import logging

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'logging' imported but unused

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed Mr Hound!


from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.hive import DATA_HIVE

DEPENDENCIES = ['hive']

DEVICETYPE_DEVICE_CLASS = {'motionsensor': 'motion',
'contactsensor': 'opening'}


def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up Hive sensor devices."""
if discovery_info is None:
return
session = hass.data.get(DATA_HIVE)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add a check here before continuing with the setup, to make sure discovery_info is not None. Add this for all platforms. These platforms should only be setup via discovery.

if discovery_info is None:
    return
session = hass.data.get(DATA_HIVE)
...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


add_devices([HiveBinarySensorEntity(session, discovery_info)])


class HiveBinarySensorEntity(BinarySensorDevice):
"""Representation of a Hive binary sensor."""

def __init__(self, hivesession, hivedevice):
"""Initialize the hive sensor."""
self.node_id = hivedevice["Hive_NodeID"]
self.node_name = hivedevice["Hive_NodeName"]
self.device_type = hivedevice["HA_DeviceType"]
self.node_device_type = hivedevice["Hive_DeviceType"]
self.session = hivesession
self.data_updatesource = '{}.{}'.format(self.device_type,
self.node_id)

self.session.entities.append(self)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This entity does not add itself to the list of entities that the component stores.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are good, thanks
added.

def handle_update(self, updatesource):
"""Handle the new update request."""
if '{}.{}'.format(self.device_type, self.node_id) not in updatesource:
self.schedule_update_ha_state()

@property
def device_class(self):
"""Return the class of this sensor."""
return DEVICETYPE_DEVICE_CLASS.get(self.node_device_type)

@property
def name(self):
"""Return the name of the binary sensor."""
return self.node_name

@property
def is_on(self):
"""Return true if the binary sensor is on."""
return self.session.sensor.get_state(self.node_id,
self.node_device_type)

def update(self):
"""Update all Node data frome Hive."""
if self.session.core.update_data(self.node_id):
for entity in self.session.entities:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You shouldn't have this here. If we assume all platforms will update at about the same scan interval, with this here, there'll be five state updates per entity instead of just one, when only one is needed.

This is only good to have when the user induces a device update from one entity via a service call, and we want to leverage the new state info for all entities. That is not the case here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is there so that if an update is requested and that then resulted in new hive data it would trigger the update of all the other entities.
the return is true if new data false if cached data
not all values align when on entity does an update and gets new data, the other entities only now about the new data when they next poll, creating a misalignment for a while
It will still only result in a new data fetch from the API every few minutes

entity.handle_update(self.data_updatesource)
136 changes: 136 additions & 0 deletions homeassistant/components/climate/hive.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
"""
Support for the Hive devices.

For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.hive/
"""
import logging

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'logging' imported but unused

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed Mr Hound!


from homeassistant.components.climate import (ClimateDevice,
STATE_AUTO, STATE_HEAT,
STATE_OFF, STATE_ON)
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
from homeassistant.components.hive import DATA_HIVE

DEPENDENCIES = ['hive']
HIVE_TO_HASS_STATE = {'SCHEDULE': STATE_AUTO, 'MANUAL': STATE_HEAT,
'ON': STATE_ON, 'OFF': STATE_OFF}
HASS_TO_HIVE_STATE = {STATE_AUTO: 'SCHEDULE', STATE_HEAT: 'MANUAL',
STATE_ON: 'ON', STATE_OFF: 'OFF'}


def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up Hive climate devices."""
if discovery_info is None:
return
session = hass.data.get(DATA_HIVE)

add_devices([HiveClimateEntity(session, discovery_info)])


class HiveClimateEntity(ClimateDevice):
"""Hive Climate Device."""

def __init__(self, hivesession, hivedevice):
"""Initialize the Climate device."""
self.node_id = hivedevice["Hive_NodeID"]
self.node_name = hivedevice["Hive_NodeName"]
self.device_type = hivedevice["HA_DeviceType"]
self.session = hivesession
self.data_updatesource = '{}.{}'.format(self.device_type,
self.node_id)

if self.device_type == "Heating":
self.modes = [STATE_AUTO, STATE_HEAT, STATE_OFF]
elif self.device_type == "HotWater":
self.modes = [STATE_AUTO, STATE_ON, STATE_OFF]

self.session.entities.append(self)

def handle_update(self, updatesource):
"""Handle the new update request."""
if '{}.{}'.format(self.device_type, self.node_id) not in updatesource:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand what's happening here. Why should all hive entities except the one where the update originated from be scheduled for a state update?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The entity that is not updated in this handle_update is the one that made the request for all entities to be updated. I excluded it as it already had the updated data as it was the one that made the call which resulted in new data

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But why should a change in state of one entity update the state of the other entities? Why should there be that connection?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because, when you set a new temperature for example, the Hive api returns the latest data for all your Hive devices, so to me it makes sense to utilise as soon as possible that latest data by getting all the HA entities to refresh. They wont go out to the Hive api as it has only just been retrieved to HA cache. It is the same for when you set temperature, or turn on a light, each time you do anything with the Hive API it will return the latest data for all devices.

It there any reason why it is not allowed to do this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I hadn't read all the previous discussions. As said in those, the dispatcher could be used for this case. But I think it's ok like this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, great, thanks.

self.schedule_update_ha_state()

@property
def name(self):
"""Return the name of the Climate device."""
friendly_name = "Climate Device"
if self.device_type == "Heating":
friendly_name = "Heating"
if self.node_name is not None:
friendly_name = '{} {}'.format(self.node_name, friendly_name)
elif self.device_type == "HotWater":
friendly_name = "Hot Water"
return friendly_name

@property
def temperature_unit(self):
"""Return the unit of measurement."""
return TEMP_CELSIUS

@property
def current_temperature(self):
"""Return the current temperature."""
if self.device_type == "Heating":
return self.session.heating.current_temperature(self.node_id)

@property
def target_temperature(self):
"""Return the target temperature."""
if self.device_type == "Heating":
return self.session.heating.get_target_temperature(self.node_id)

@property
def min_temp(self):
"""Return minimum temperature."""
if self.device_type == "Heating":
return self.session.heating.min_temperature(self.node_id)

@property
def max_temp(self):
"""Return the maximum temperature."""
if self.device_type == "Heating":
return self.session.heating.max_temperature(self.node_id)

@property
def operation_list(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should translate the values returned by the library into the STATE_* constants from climate/__init__.py. And please use "auto" instead of "schedule".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I don't understand. Please could you give an example of an existing climate component which could help show me what you mean?

The value "schedule" is used as this is what is used in the official Hive application and website.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, have a look at the eq3btsmart platform. In line 64 you can see the mapping of device-internal states to "home assistant operation modes". We're trying to standardize these states as much as possible.

Copy link
Contributor Author

@Rendili Rendili Nov 16, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, thanks, will take a detailed look.

So (after a quick look) I need / can to use the following mapping?
heating : STATE_AUTO= schedule, STATE_HEAT= manual, STATE_OFF = off
hotwater : STATE_AUTO = schedule, STATE_ON= on, STATE_OFF= off

from the eq3btsmart platform it uses STATE_MANUAL but this is not imported from homeassistant.components.climate, so I shouldn't use this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have updated as above.
Is this ok now?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. If you want to use STATE_MANUAL, you should probably define it in climate/hive.py. In some future refactoring, it will be merged with the STATE_MANUAL from eq3btsmart (and others).

Copy link
Contributor Author

@Rendili Rendili Nov 16, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great, thanks.
I will have a think on the STATE_MANUAL vs STATE_HEAT

"""List of the operation modes."""
return self.modes

@property
def current_operation(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See operation_list()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The values used in the operation list in climate are taken directly from the Hive api and is what is displayed and used by the end user in the official Hive application and website. This ensures consistency between the official Hive interfaces and the Home Assistant interface.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see the benefit of that. However, since home assistant tries to provide a standardized interface for lots of devices from lots of manufacturers, we should try to standardize across home assistant first, and between home assistant and the manufacturers second.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok understood

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as above.
I have updated to use the home assistant standards

"""Return current mode."""
if self.device_type == "Heating":
currentmode = self.session.heating.get_mode(self.node_id)
elif self.device_type == "HotWater":
currentmode = self.session.hotwater.get_mode(self.node_id)
return HIVE_TO_HASS_STATE.get(currentmode)

def set_operation_mode(self, operation_mode):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See operation_list()

Copy link
Contributor Author

@Rendili Rendili Nov 16, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my comments as above in operation_list()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as above.
I have updated to use the home assistant standards

"""Set new Heating mode."""
new_mode = HASS_TO_HIVE_STATE.get(operation_mode)
if self.device_type == "Heating":
self.session.heating.set_mode(self.node_id, new_mode)
elif self.device_type == "HotWater":
self.session.hotwater.set_mode(self.node_id, new_mode)

for entity in self.session.entities:
entity.handle_update(self.data_updatesource)

def set_temperature(self, **kwargs):
"""Set new target temperature."""
new_temperature = kwargs.get(ATTR_TEMPERATURE)
if new_temperature is not None:
if self.device_type == "Heating":
self.session.heating.set_target_temperature(self.node_id,
new_temperature)

for entity in self.session.entities:
entity.handle_update(self.data_updatesource)

def update(self):
"""Update all Node data frome Hive."""
if self.session.core.update_data(self.node_id):
for entity in self.session.entities:
entity.handle_update(self.data_updatesource)
80 changes: 80 additions & 0 deletions homeassistant/components/hive.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
"""
Support for the Hive devices.

For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/hive/
"""
import logging
import voluptuous as vol

from homeassistant.const import (CONF_PASSWORD, CONF_SCAN_INTERVAL,
CONF_USERNAME)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.discovery import load_platform
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Import must be ordered according PEP8.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Imports on all files re-ordered using the recommended isort


REQUIREMENTS = ['pyhiveapi==0.2.5']

_LOGGER = logging.getLogger(__name__)
DOMAIN = 'hive'
DATA_HIVE = 'data_hive'
DEVICETYPES = {
'binary_sensor': 'device_list_binary_sensor',
'climate': 'device_list_climate',
'light': 'device_list_light',
'switch': 'device_list_plug',
'sensor': 'device_list_sensor',
}

CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Optional(CONF_SCAN_INTERVAL, default=2): cv.positive_int,
})
}, extra=vol.ALLOW_EXTRA)


class HiveSession:
"""Initiate Hive Session Class."""

entities = []
core = None
heating = None
hotwater = None
light = None
sensor = None
switch = None


def setup(hass, config):
"""Set up the Hive Component."""
from pyhiveapi import Pyhiveapi

session = HiveSession()
session.core = Pyhiveapi()

username = config[DOMAIN][CONF_USERNAME]
password = config[DOMAIN][CONF_PASSWORD]
update_interval = config[DOMAIN][CONF_SCAN_INTERVAL]

devicelist = session.core.initialise_api(username,
password,
update_interval)

if devicelist is None:
_LOGGER.error("Hive API initialization failed")
return False

session.sensor = Pyhiveapi.Sensor()
session.heating = Pyhiveapi.Heating()
session.hotwater = Pyhiveapi.Hotwater()
session.light = Pyhiveapi.Light()
session.switch = Pyhiveapi.Switch()
hass.data[DATA_HIVE] = session

for ha_type, hive_type in DEVICETYPES.items():
for key, devices in devicelist.items():
if key == hive_type:
for hivedevice in devices:
load_platform(hass, ha_type, DOMAIN, hivedevice, config)
return True
Loading