-
-
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 11 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,43 @@ | ||
| """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.positive_int, | ||
| }), | ||
| }, extra=vol.ALLOW_EXTRA) | ||
|
|
||
|
|
||
| async def async_setup(hass, config): | ||
| """Try to start embedded Genius Hub broker.""" | ||
| from geniushub.geniushub import GeniusHub | ||
|
|
||
| username = config[DOMAIN].get(CONF_USERNAME) | ||
|
GeoffAtHome marked this conversation as resolved.
Outdated
|
||
| password = config[DOMAIN].get(CONF_PASSWORD) | ||
| host = config[DOMAIN].get(CONF_HOST) | ||
| scan_interval = config[DOMAIN].get(CONF_SCAN_INTERVAL) | ||
| hass.data[GENIUS_HUB] = GeniusHub( | ||
|
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 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:
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. 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.
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. @MartinHjelmare can you give an example of a component that does this? I had a look at hue, but it uses config flow...
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. 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)
)
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.
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 have addressed this: It becomes But, I am getting an occasional 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 responseThe Exception only occurs with methods other than
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.
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. @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? |
||
| host, username, password, scan_interval) | ||
|
|
||
| hass.async_create_task(async_load_platform( | ||
| hass, 'climate', DOMAIN, None, config)) | ||
|
GeoffAtHome marked this conversation as resolved.
Outdated
|
||
|
|
||
| hass.async_create_task(async_load_platform( | ||
| hass, 'switch', DOMAIN, None, config)) | ||
|
|
||
| hass.async_create_task(async_load_platform( | ||
| hass, 'sensor', DOMAIN, None, config)) | ||
|
|
||
| return True | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,204 @@ | ||
| """ | ||
| Supports Genius hub to provide climate controls. | ||
|
|
||
| For more details about this component, please refer to the documentation at | ||
| https://home-assistant.io/components/climate.geniushub/ | ||
|
GeoffAtHome marked this conversation as resolved.
Outdated
|
||
| """ | ||
| 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 homeassistant.components.geniushub import GENIUS_HUB | ||
|
GeoffAtHome marked this conversation as resolved.
Outdated
|
||
| from homeassistant.const import ( | ||
|
MartinHjelmare marked this conversation as resolved.
|
||
| ATTR_TEMPERATURE, STATE_OFF, STATE_ON, TEMP_CELSIUS) | ||
|
|
||
| _LOGGER = logging.getLogger(__name__) | ||
| DOMAIN = 'geniushub' | ||
|
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. Remove this.
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. |
||
|
|
||
|
|
||
|
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] | ||
|
|
||
|
|
||
| async def async_setup_platform(hass, config, | ||
| async_add_entities, discovery_info=None): | ||
| """Set up the Genius hub climate devices.""" | ||
| 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 state(self): | ||
|
GeoffAtHome marked this conversation as resolved.
Outdated
|
||
| """Return the current state.""" | ||
| if self._is_active: | ||
| return STATE_ON | ||
|
|
||
| return STATE_OFF | ||
|
|
||
| @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.""" | ||
| if self._mode == "off": | ||
| return False | ||
|
|
||
| return True | ||
|
|
||
| @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.""" | ||
| mode_map = { | ||
|
GeoffAtHome marked this conversation as resolved.
Outdated
|
||
| 'override': STATE_HEAT, | ||
| 'footprint': STATE_ECO, | ||
| 'timer': STATE_AUTO, | ||
| } | ||
| 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") | ||
|
MartinHjelmare marked this conversation as resolved.
Outdated
|
||
| return | ||
|
|
||
| await GeniusClimate._genius_hub.putjson(self._device_id, data['data']) | ||
|
|
||
| async def async_set_temperature(self, **kwargs): | ||
| """Set new target temperatures.""" | ||
| if kwargs.get(ATTR_TEMPERATURE) is not None: | ||
| await self.async_set_operation_mode(STATE_HEAT) | ||
|
GeoffAtHome marked this conversation as resolved.
Outdated
|
||
|
|
||
| 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
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| """ | ||
| Supports Genius hub to provide room sensor and TRV information. | ||
|
|
||
| For more details about this component, please refer to the documentation at | ||
| https://home-assistant.io/components/sensor.geniushub/ | ||
| """ | ||
| from homeassistant.components.geniushub import GENIUS_HUB | ||
| from homeassistant.const import ( | ||
|
GeoffAtHome marked this conversation as resolved.
Outdated
|
||
| ATTR_BATTERY_LEVEL, ATTR_TEMPERATURE) | ||
| from homeassistant.helpers.entity import Entity | ||
|
|
||
| DOMAIN = 'geniushub' | ||
|
|
||
|
|
||
| async def async_setup_platform(hass, config, | ||
| async_add_entities, discovery_info=None): | ||
| """Set up the Demo climate devices.""" | ||
| genius_hub = hass.data[GENIUS_HUB] | ||
| await genius_hub.getjson('/zones') | ||
|
|
||
| # Get sensors | ||
| for sensor in genius_hub.getSensorList(): | ||
| async_add_entities([GeniusSensor(genius_hub, sensor)]) | ||
|
|
||
| # Get TRVs | ||
| for trv in genius_hub.getTRVList(): | ||
| async_add_entities([GeniusTRV(genius_hub, trv)]) | ||
|
|
||
|
|
||
| class GeniusSensor(Entity): | ||
| """Representation of a Wall Sensor.""" | ||
|
|
||
| _genius_hub = None | ||
|
|
||
| def __init__(self, genius_hub, sensor): | ||
| """Initialize the Wall sensor.""" | ||
| GeniusSensor._genius_hub = genius_hub | ||
| self._name = sensor['name'] + ' sensor ' + str(sensor['index']) | ||
| self._device_id = sensor['iID'] | ||
| self._device_addr = sensor['addr'] | ||
| self._battery = sensor['Battery'] | ||
| self._temperature = sensor['TEMPERATURE'] | ||
| self._luminance = sensor['LUMINANCE'] | ||
| self._motion = sensor['Motion'] | ||
|
|
||
| @property | ||
| def name(self): | ||
| """Return the name of the sensor.""" | ||
| return self._name | ||
|
|
||
| @property | ||
| def state(self): | ||
| """Return the state of the sensor.""" | ||
| return self._temperature | ||
|
|
||
| @property | ||
| def device_class(self): | ||
| """Return the class of this sensor.""" | ||
| return "genius_wall_sensor" | ||
|
|
||
| @property | ||
| def force_update(self): | ||
| """Force update.""" | ||
| return True | ||
|
|
||
| @property | ||
| def device_state_attributes(self): | ||
| """Return the state attributes.""" | ||
| return { | ||
| ATTR_BATTERY_LEVEL: self._battery, | ||
| 'luminance': self._luminance, | ||
| 'motion': self._motion, | ||
| ATTR_TEMPERATURE: self._temperature | ||
| } | ||
|
|
||
| async def async_update(self): | ||
| """Get the latest data.""" | ||
| device = GeniusSensor._genius_hub.getDevice( | ||
| self._device_id, self._device_addr) | ||
| data = GeniusSensor._genius_hub.getSensor(device) | ||
| self._battery = data['Battery'] | ||
| self._temperature = data['TEMPERATURE'] | ||
| self._luminance = data['LUMINANCE'] | ||
| self._motion = data['Motion'] | ||
|
|
||
|
|
||
| class GeniusTRV(Entity): | ||
| """Representation of a TRV Sensor.""" | ||
|
|
||
| _genius_hub = None | ||
|
|
||
| def __init__(self, genius_hub, trv): | ||
| """Initialize the TRV sensor.""" | ||
| GeniusTRV._genius_hub = genius_hub | ||
| self._name = trv['name'] + ' TRV ' + str(trv['index']) | ||
| self._device_id = trv['iID'] | ||
| self._device_addr = trv['addr'] | ||
| self._battery = trv['Battery'] | ||
| self._temperature = trv['TEMPERATURE'] | ||
|
|
||
| @property | ||
| def name(self): | ||
| """Return the name of the sensor.""" | ||
| return self._name | ||
|
|
||
| @property | ||
| def state(self): | ||
| """Return the state of the sensor.""" | ||
| return self._temperature | ||
|
|
||
| @property | ||
| def device_class(self): | ||
| """Return the class of this sensor.""" | ||
| return "genius_trv" | ||
|
|
||
| @property | ||
| def force_update(self): | ||
| """Force update.""" | ||
| return True | ||
|
|
||
| @property | ||
| def device_state_attributes(self): | ||
| """Return the state attributes.""" | ||
| return { | ||
| ATTR_BATTERY_LEVEL: self._battery, | ||
| ATTR_TEMPERATURE: self._temperature | ||
| } | ||
|
|
||
| async def async_update(self): | ||
| """Get the latest data.""" | ||
| device = GeniusTRV._genius_hub.getDevice( | ||
| self._device_id, self._device_addr) | ||
| data = GeniusTRV._genius_hub.getTRV(device) | ||
| self._battery = data['Battery'] | ||
| self._temperature = data['TEMPERATURE'] | ||
Uh oh!
There was an error while loading. Please reload this page.