-
-
Notifications
You must be signed in to change notification settings - Fork 38.2k
Genius hub #21598
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
Genius hub #21598
Changes from 17 commits
9a191d4
57fc2d4
2a9c8d5
1077de9
24931b4
01cfbe0
c361be3
77a9cb1
6a20776
70c6e92
98d71c3
acb0823
56e74fd
f34e345
dfc2c7c
dc17eb7
5887e93
358cd22
48818ba
2cd9424
efd16a1
3907737
df7e4d0
299d8e2
16f306f
9552c5b
9d01726
6195e6c
cc31566
9b9245a
1388095
96073c5
8324789
1228d7f
42110f4
532bca9
5f1e0ac
6bbc964
d09dfa0
d5bf592
6d40237
1135c69
d0a3593
13fc061
6a2d272
17cb347
3800679
e6623c5
e3729d0
33084a4
4328fbe
b668f31
8bc1783
c092f3b
2e9ef6a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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( | ||
| host, username, password, scan_interval) | ||
|
|
||
| hass.async_create_task(async_load_platform( | ||
| hass, 'climate', DOMAIN, {}, config)) | ||
|
|
||
| return True | ||
| 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 ( | ||
|
MartinHjelmare marked this conversation as resolved.
Outdated
|
||
| DOMAIN, GENIUS_HUB) | ||
| from homeassistant.const import ( | ||
|
MartinHjelmare marked this conversation as resolved.
|
||
| ATTR_TEMPERATURE, TEMP_CELSIUS) | ||
|
|
||
| _LOGGER = logging.getLogger(__name__) | ||
|
|
||
|
|
||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please remove one blank line.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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] | ||
|
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)]) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Collect all entities in a list and call
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Set it as an instance attribute instead.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 = { | ||
|
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" | ||
|
GeoffAtHome marked this conversation as resolved.
Outdated
|
||
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, I found:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tibber uses our session:
https://github.com/home-assistant/home-assistant/blob/59476ab475122bb02ddbdd2abcd4930cdfece849/homeassistant/components/tibber/__init__.py#L32-L33
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
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._sessionin my client library (see below).But, I am getting an occasional
ServerDisconnectedErrorerror in the client library. The API is undocumented, so I have done this:The Exception only occurs with methods other than
GET, such asPOST. So it uses HA's session most of the time, but is creating/destroying sessions for setting temps, operating modes, etc..There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see: https://github.com/zxdavb/geniushub-client/blob/6e7b038e33de7b4a84b74b4a243c4c0d2ab0d3df/geniushubclient/__init__.py#L255-L302
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@MartinHjelmare, the new client:
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?