Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
9a191d4
Adding Genius Hub
GeoffAtHome Mar 2, 2019
57fc2d4
Added Genius hub
Mar 2, 2019
2a9c8d5
Correct hound errors
Mar 2, 2019
1077de9
Correct hound errors.
GeoffAtHome Mar 3, 2019
24931b4
Merge
GeoffAtHome Mar 3, 2019
01cfbe0
Correct tox errors.
GeoffAtHome Mar 3, 2019
c361be3
Fix travis errors
GeoffAtHome Mar 3, 2019
77a9cb1
Correct sensor names
GeoffAtHome Mar 3, 2019
6a20776
Correct travis errors
GeoffAtHome Mar 4, 2019
70c6e92
Correct travis errors
GeoffAtHome Mar 4, 2019
98d71c3
Correct hound errors
GeoffAtHome Mar 4, 2019
acb0823
Follow up from code review by Martin Hjelmare
GeoffAtHome Mar 24, 2019
56e74fd
More changes from code review.
GeoffAtHome Mar 25, 2019
f34e345
Attempt to resolve conflicts in requirements_all
GeoffAtHome Mar 25, 2019
dfc2c7c
rebase with latest upstream changes
zxdavb Mar 28, 2019
dc17eb7
de-lint for the houndci-bot
zxdavb Mar 28, 2019
5887e93
better logging message, and small tidy-up
zxdavb Mar 28, 2019
358cd22
minor refactor and de-lint
zxdavb Mar 28, 2019
48818ba
domain name should be the same as the component name
zxdavb Mar 28, 2019
2cd9424
use self where appropriate
zxdavb Mar 28, 2019
efd16a1
minor de-lint
zxdavb Mar 28, 2019
3907737
add entities as a single call
zxdavb Mar 28, 2019
df7e4d0
de-lint
zxdavb Mar 28, 2019
299d8e2
Merge branch 'dev' of https://github.com/home-assistant/home-assistan…
zxdavb Mar 29, 2019
16f306f
Merge branch 'dev' of https://github.com/home-assistant/home-assistan…
zxdavb Apr 3, 2019
9552c5b
latest changes
zxdavb Apr 6, 2019
9d01726
all read-only attrs almost done
zxdavb Apr 6, 2019
6195e6c
refactor - near the end
zxdavb Apr 6, 2019
cc31566
change state/,ode mapping
zxdavb Apr 6, 2019
9b9245a
override temp from curr_temp
zxdavb Apr 7, 2019
1388095
all read-only properties working
zxdavb Apr 12, 2019
96073c5
working now
zxdavb Apr 13, 2019
8324789
ready for PR, but need to remove logging
zxdavb Apr 13, 2019
1228d7f
Merge branch 'dev' of https://github.com/home-assistant/home-assistan…
zxdavb Apr 13, 2019
42110f4
de-lint
zxdavb Apr 13, 2019
532bca9
de-linted, ready for merge
zxdavb Apr 13, 2019
5f1e0ac
Merge branch 'dev' of https://github.com/home-assistant/home-assistan…
zxdavb Apr 13, 2019
6bbc964
de-linted, ready for merge 2
zxdavb Apr 13, 2019
d09dfa0
Merge pull request #1 from GeoffAtHome/use-geniushub-client
zxdavb Apr 13, 2019
d5bf592
up to date
zxdavb Apr 13, 2019
6d40237
fix merge hell
zxdavb Apr 13, 2019
1135c69
didn't like import in climate/__init__
zxdavb Apr 13, 2019
d0a3593
improve footprint logic
zxdavb Apr 13, 2019
13fc061
add manifest.json
zxdavb Apr 13, 2019
6a2d272
Merge branch 'dev' of https://github.com/home-assistant/home-assistan…
zxdavb Apr 13, 2019
17cb347
add manifest.json 2
zxdavb Apr 13, 2019
3800679
correct a regression
zxdavb Apr 13, 2019
e6623c5
fix regression with device.is_on()
zxdavb Apr 13, 2019
e3729d0
Merge branch 'dev' of https://github.com/home-assistant/home-assistan…
zxdavb Apr 14, 2019
33084a4
Merge remote-tracking branch 'upstream/dev' into genius-hub
zxdavb Apr 15, 2019
4328fbe
Merge remote-tracking branch 'upstream/dev' into genius-hub
zxdavb Apr 15, 2019
b668f31
use latest client library
zxdavb Apr 15, 2019
8bc1783
Merge remote-tracking branch 'upstream/dev' into genius-hub
zxdavb Apr 16, 2019
c092f3b
update to latest client library, 3.3.6
zxdavb Apr 16, 2019
2e9ef6a
delint and shoudl be OK to go
zxdavb Apr 16, 2019
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
1 change: 1 addition & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ omit =
homeassistant/components/futurenow/light.py
homeassistant/components/garadget/cover.py
homeassistant/components/gc100/*
homeassistant/components/geniushub/*
homeassistant/components/gearbest/sensor.py
homeassistant/components/geizhals/sensor.py
homeassistant/components/github/sensor.py
Expand Down
38 changes: 38 additions & 0 deletions homeassistant/components/geniushub/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""This module connects to the Genius hub and shares the data."""
import voluptuous as vol

from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_SCAN_INTERVAL)
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.discovery import async_load_platform

REQUIREMENTS = ['geniushub==0.1']
GENIUS_HUB = 'genius_hub'
DOMAIN = 'geniushub'

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


async def async_setup(hass, config):
"""Try to start embedded Genius Hub broker."""
from geniushub.geniushub import GeniusHub

username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
host = config.get(CONF_HOST)
scan_interval = config.get(CONF_SCAN_INTERVAL)

hass.data[GENIUS_HUB] = GeniusHub(

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.

Please have the client accept an (optional) aiohttp session. We want all integrations that use aiohttp in home assistant to use the aiohttp session of home assistant.

Use our aiohttp helper to get the session:
https://github.com/home-assistant/home-assistant/blob/59476ab475122bb02ddbdd2abcd4930cdfece849/homeassistant/helpers/aiohttp_client.py#L30

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.

Also, please have the client (optionally) accept an asyncio event loop to use. We want all asyncio integrations to use the home assistant event loop.

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.

@MartinHjelmare can you give an example of a component that does this? I had a look at hue, but it uses config flow...

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.

Actually, I found:

async def get_bridge(hass, host, username=None):
    """Create a bridge object and verify authentication."""
    import aiohue

    bridge = aiohue.Bridge(
        host, username=username,
        websession=aiohttp_client.async_get_clientsession(hass)
    )

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.

@zxdavb zxdavb Apr 13, 2019

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.

I have addressed this:
https://github.com/home-assistant/home-assistant/blob/17cb347a844c6eadbc029307b2b0afe15be54979/homeassistant/components/geniushub/__init__.py#L36-L39

It becomes self._client._session in my client library (see below).

But, I am getting an occasional ServerDisconnectedError error in the client library. The API is undocumented, so I have done this:

    async def _request(self, method, url, data=None):
        http_method = {
            "GET": self._client._session.get,
            "PATCH": self._client._session.patch,
            "POST": self._client._session.post,
            "PUT": self._client._session.put,
        }.get(method)

        try:
            _LOGGER.warning("_request(): 1st try: %s %s %s", method, url, data)
            async with http_method(
    ...    ...
            ) as resp:
                assert resp.status == HTTP_OK
                response = await resp.json()
            return response

        except aiohttp.client_exceptions.ServerDisconnectedError as err:
            _LOGGER.warning(
                "_request(): 2nd try: %s %s %s - as ServerDisconnected. "
                "Message was: %s", method, url, data, err)
            _session = aiohttp.ClientSession()
            async with http_method(
    ...     ...
            ) as resp:
                assert resp.status == HTTP_OK
                response = await resp.json()
            await _session.close()
            return response

The Exception only occurs with methods other than GET, such as POST. So it uses HA's session most of the time, but is creating/destroying sessions for setting temps, operating modes, etc..

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.

@zxdavb zxdavb Apr 16, 2019

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.

@MartinHjelmare, the new client:

  • "accept(s) an (optional) aiohttp session" - yes, but with the above caveat
  • "(optionally) accept an asyncio event loop to use" - no, as it doesn't use a loop internally

AFAICT, the PR has passed all tests, and only requires your sign-off on this requested change. I cannot mark anything as resolved, because I did not submit the original PR.

Would you have a look at it for me?

host, username, password, scan_interval)

hass.async_create_task(async_load_platform(
hass, 'climate', DOMAIN, {}, config))

return True
200 changes: 200 additions & 0 deletions homeassistant/components/geniushub/climate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
"""
Supports Genius hub to provide climate controls.
"""
import logging

from homeassistant.components.climate import ClimateDevice
from homeassistant.components.climate.const import (
STATE_ECO, STATE_HEAT, STATE_AUTO, STATE_IDLE,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE,
SUPPORT_ON_OFF, SUPPORT_AWAY_MODE)
from . import (
Comment thread
MartinHjelmare marked this conversation as resolved.
Outdated
DOMAIN, GENIUS_HUB)
from homeassistant.const import (
Comment thread
MartinHjelmare marked this conversation as resolved.
ATTR_TEMPERATURE, TEMP_CELSIUS)

_LOGGER = logging.getLogger(__name__)


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.

Please remove one blank line.

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.

Resolved.

SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE \
| SUPPORT_ON_OFF | SUPPORT_AWAY_MODE
# Genius supports the operation modes: Off, Override, Footprint and Timer
# To work with Alexa these MUST BE
#
# climate.STATE_HEAT: 'HEAT',
# climate.STATE_COOL: 'COOL',
# climate.STATE_AUTO: 'AUTO',
# climate.STATE_ECO: 'ECO',
# climate.STATE_IDLE: 'OFF',
# climate.STATE_FAN_ONLY: 'OFF',
# climate.STATE_DRY: 'OFF',

# These needed to be mapped into HA modes:
# Off => OFF => STATE_IDLE # Mode_Off: 1,
# Override => HEAT => STATE_HEAT # Mode_Boost: 16,
# Footprint => ECO => STATE_ECO # Mode_Footprint: 4,
# Timer => AUTO => STATE_AUTO # Mode_Timer: 2,
# Away # Mode_Away: 8,
#
OPERATION_LIST = [STATE_IDLE, STATE_HEAT, STATE_ECO, STATE_AUTO]

"""Map between GeniusHub and Home Assistant"""
MODE_MAP = {
'override': STATE_HEAT,
'footprint': STATE_ECO,
'timer': STATE_AUTO,
}


async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):

"""Set up the Genius hub climate devices."""
if discovery_info is None:
return

genius_hub = hass.data[GENIUS_HUB]
Comment thread
GeoffAtHome marked this conversation as resolved.
Outdated
await genius_hub.getjson('/zones')

# Get the zones with a temperature
climate_list = genius_hub.getClimateList()

for zone in climate_list:
async_add_entities([GeniusClimate(genius_hub, zone)])

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.

Collect all entities in a list and call async_add_entities only once with all entities.

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.

Done - Resolved.



class GeniusClimate(ClimateDevice):
"""Representation of a Genius Hub climate device."""

def __init__(self, genius_hub, zone):
"""Initialize the climate device."""
GeniusClimate._genius_hub = genius_hub

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.

Set it as an instance attribute instead.

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.

I think this is resolved.

self._name = zone['name']
self._device_id = zone['iID']
self._current_temperature = zone['current_temperature']
self._target_temperature = zone['target_temperature']
self._mode = zone['mode']
self._is_active = zone['is_active']

@property
def name(self):
"""Return the name of the climate device."""
return self._name

@property
def current_temperature(self):
"""Return the current temperature."""
return self._current_temperature

@property
def target_temperature(self):
"""Return the temperature we try to reach."""
return self._target_temperature

@property
def min_temp(self):
"""Return max valid temperature that can be set."""
return 4.0

@property
def max_temp(self):
"""Return max valid temperature that can be set."""
return 28.0

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

@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS

@property
def operation_list(self):
"""Return the list of available operation modes."""
return OPERATION_LIST

@property
def is_on(self):
"""Return true if the device is on."""
return self._mode != "off"

@property
def is_away_mode_on(self):
"""Return true if away mode is on."""
if self._mode == "away":
return True

return False

@property
def current_operation(self):
"""Return the current operation mode."""
return self.get_current_operation_mode(self._mode)

@staticmethod
def get_current_operation_mode(mode):
"""Return the current operational mode."""
return MODE_MAP.get(mode, STATE_IDLE)

def get_operation_mode(self, operation_mode):
"""Coverts operation mode from Home Assistant to Genius Hub."""
# These needed to be mapped into HA modes:
# Off => OFF => STATE_IDLE # Mode_Off: 1,
# Override => HEAT => STATE_HEAT # Mode_Boost: 16,
# Footprint => ECO => STATE_ECO # Mode_Footprint: 4,
# Timer => AUTO => STATE_AUTO # Mode_Timer: 2,
# Away # Mode_Away: 8,
#
# OPERATION_LIST = [STATE_IDLE, STATE_HEAT, STATE_ECO, STATE_AUTO]
operation_mode_map = {
Comment thread
MartinHjelmare marked this conversation as resolved.
Outdated
STATE_IDLE: {'mode': 'off', 'data': {'iMode': 1}},
STATE_HEAT: {'mode': 'override', 'data':
{'iBoostTimeRemaining': 3600,
'iMode': 16,
'fBoostSP': self._target_temperature}},
STATE_ECO: {'mode': 'footprint', 'data': {'iMode': 4}},
STATE_AUTO: {'mode': 'timer', 'data': {'iMode': 2}}, }
return operation_mode_map.get(operation_mode,
{'mode': 'off', 'data': None})

async def async_set_operation_mode(self, operation_mode):
"""Set new operation mode."""
data = self.get_operation_mode(operation_mode)
self._mode = data['mode']
if data['data'] is None:
_LOGGER.error("Unknown mode %s", operation_mode)
return

await GeniusClimate._genius_hub.putjson(self._device_id, data['data'])

async def async_set_temperature(self, **kwargs):
"""Set new target temperatures."""
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
return

self._target_temperature = temperature
await self.async_set_operation_mode(STATE_HEAT)

async def async_update(self):
"""Get the latest data from the hub."""
what_zone = GeniusClimate._genius_hub.getZone(self._device_id)
if what_zone:
zone = GeniusClimate._genius_hub.GET_CLIMATE(what_zone)
self._current_temperature = zone['current_temperature']
self._target_temperature = zone['target_temperature']
self._mode = zone['mode']
self._is_active = zone['is_active']

async def async_turn_on(self):
"""Turn on."""
await self.async_set_operation_mode(STATE_AUTO)
self._mode = "on"

async def async_turn_off(self):
"""Turn off."""
await self.async_set_operation_mode(STATE_IDLE)
self._mode = "off"
Comment thread
GeoffAtHome marked this conversation as resolved.
Outdated
3 changes: 3 additions & 0 deletions requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,9 @@ gearbest_parser==1.0.7
# homeassistant.components.geizhals.sensor
geizhals==0.0.9

# homeassistant.components.geniushub
geniushub==0.1

# homeassistant.components.geo_json_events.geo_location
# homeassistant.components.nsw_rural_fire_service_feed.geo_location
# homeassistant.components.usgs_earthquakes_feed.geo_location
Expand Down