manzanotti / geniushub-client Goto Github PK
View Code? Open in Web Editor NEWPython library to provide direct connectivity to Genius Hub.
License: MIT License
Python library to provide direct connectivity to Genius Hub.
License: MIT License
Good to see this being used in HA - but has broken what was previously working for me.
Not sure why some many warning are disabled as these are there for a good reason. To stop crashes like this:
Traceback (most recent call last):
File "/home/geoff/home-assistant/homeassistant/setup.py", line 153, in _async_setup_component
hass, processed_config)
File "/home/geoff/home-assistant/homeassistant/components/geniushub/__init__.py", line 50, in async_setup
await data._client.hub.update() # pylint: disable=protected-access
File "/home/geoff/home-assistant/venv/lib/python3.6/site-packages/geniushubclient/__init__.py", line 644, in update
[_populate_zone(z) for z in await self._get_zones_raw]
File "/home/geoff/home-assistant/venv/lib/python3.6/site-packages/geniushubclient/__init__.py", line 644, in <listcomp>
[_populate_zone(z) for z in await self._get_zones_raw]
File "/home/geoff/home-assistant/venv/lib/python3.6/site-packages/geniushubclient/__init__.py", line 557, in _populate_zone
zone_dict = self._convert_zone(zone_raw)
File "/home/geoff/home-assistant/venv/lib/python3.6/site-packages/geniushubclient/__init__.py", line 262, in _convert_zone
elif setpoint_temp != default_temp: # noqa: disable=F821; pylint: disable=used-before-assignment
UnboundLocalError: local variable 'setpoint_temp' referenced before assignment
2019-05-19 15:24:29 DEBUG (MainThread) [homeassistant.core] Bus:Handling <Event call_service[L]: domain=persistent_notification, service=create, service_data=title=Invalid config, message=The following components and platforms could not be set up:
- introduction
- [geniushub](https://home-assistant.io/components/geniushub/)
Please check your config., notification_id=invalid_config>
2019-05-19 15:24:29 DEBUG (MainThread) [homeassistant.core] Bus:Handling <Event state_changed[L]: entity_id=persistent_notification.invalid_config, old_state=<state persistent_notification.invalid_config=notifying; title=Invalid config, message=The following components and platforms could not be set up:
Still no time to help on the project but hopefully in July.
This is the initial state of the On/Off zone, Override/On:
(venv) dbonnes@vm-builder:~$ python ghclient.py ${HUB_ADDRESS} -u ${USERNAME} -p ${PASSWORD} -z17 -vv
{"id": 17, "name": "Test On / Off", "type": "on / off", "mode": "override", "setpoint": true, "override": {"duration": 549, "setpoint": true}, "_state": {"bIsActive": true, "bOutRequestHeat": true}}
... then we instruct the zone to Override/Off (0 is off) for 10 minutes:
(venv) dbonnes@vm-builder:~$ python ghclient.py ${HUB_ADDRESS} -u ${USERNAME} -p ${PASSWORD} -z17 -t0 -s600
However, it didn't take effect (setpoint
is still true):
(venv) dbonnes@vm-builder:~$ python ghclient.py ${HUB_ADDRESS} -u ${USERNAME} -p ${PASSWORD} -z17 -vv
{"id": 17, "name": "Test On / Off", "type": "on / off", "mode": "override", "setpoint": true, "override": {"duration": 529, "setpoint": true}, "_state": {"bIsActive": true, "bOutRequestHeat": true}}
... let's try it again, but via curl:
(venv) dbonnes@vm-builder:~$ curl --user ${USERNAME}:${HASH} -X PATCH http://${HUB_ADDRESS}:1223/v3/zone/17 -d '{"iMode": 16, "fBoostSP": 0, "iBoostTimeRemaining": 600}' > /dev/null
That did work (setpoint
is true, and duration
has increased):
(venv) dbonnes@vm-builder:~$ python ghclient.py ${HUB_ADDRESS} -u ${USERNAME} -p ${PASSWORD} -z17 -vv
{"id": 17, "name": "Test On / Off", "type": "on / off", "mode": "override", "setpoint": false, "override": {"duration": 589, "setpoint": false}, "_state": {"bIsActive": true, "bOutRequestHeat": false}}
I don't know if other zone types/mode are affected.
As mentioned in home-assistant/core#93554, the ESW w/temp doesn't have a type object, which then causes HomeAssistant to fail to load any binary sensors. Happy to test code locally as needed!
I am trying to write an automation in HA for a switch.
The switch exists in its own zone in Genius Hub.
In HA the switch appears the following two entities:
binary_sensor.smart_plug_4 service data
assigned_zone: zone_name socket
last_comms: '2021-01-21T15:30:59+00:00'
state: {}
friendly_name: Smart Plug 4
switch.zone_name service data
entity_id: switch.zone_name
I have tried turning the switch off and on calling the service switch.turn_off and switch.turn_on
I have tried using the services geniushub.set_zone_mode and geniushub.set_zone_override
The HA documentation for the GH states:
Individual smart plugs are not yet exposed as switches - you can create one zone per smart plug as a work-around.
Am I missing something or a setup/user error on my part, but how do I toggle the switch on and off from HA?
Happy to extend the code to support this if needed but just wanted to check here first incase it is user error on my part or not.
If the code needs to be extended I would expose smart sockets as a switch. This would toggle on and off. On would be the default timer period. Also create a service geniushub.set_switch_override to allow the override duration to be changed.
Let me know your views.
The integration between Home Assistant and Genius Hub suddenly stopped working last week. I have done nothing to the integration in Home Assistant and I have done nothing to my Genius Hub system. I have rebooted my GH system three times and restarted HA countless times. Genius Hub is showing no issues or errors.
I have removed the configuration from configuration.yaml and rebooted, replaced it and rebooted again and still I see the following error:
Logger: homeassistant.setup
Source: components/geniushub/__init__.py:124
First occurred: 10:26:05 (1 occurrences)
Last logged: 10:26:05
Error during setup of component geniushub
Traceback (most recent call last):
File "/usr/src/homeassistant/homeassistant/setup.py", line 253, in _async_setup_component
result = await task
File "/usr/src/homeassistant/homeassistant/components/geniushub/__init__.py", line 124, in async_setup
await client.update()
File "/usr/local/lib/python3.10/site-packages/geniushubclient/__init__.py", line 336, in update
await self._update() # now convert all the raw JSON
File "/usr/local/lib/python3.10/site-packages/geniushubclient/__init__.py", line 303, in _update
self.issues = [convert_issue(raw_json) for raw_json in self._issues]
File "/usr/local/lib/python3.10/site-packages/geniushubclient/__init__.py", line 303, in <listcomp>
self.issues = [convert_issue(raw_json) for raw_json in self._issues]
File "/usr/local/lib/python3.10/site-packages/geniushubclient/__init__.py", line 262, in convert_issue
device_type = self.device_by_id[raw_json["data"]["nodeID"]].data["type"]
KeyError: '29'
I have tried:
Error is still seen. I repeated the above twice.
What version of Home Assistant Core has the issue?
2023.1.6
What type of installation are you running?
Home Assistant OS
Integration causing the issue
Genius Hub
Link to integration documentation on our website
https://www.home-assistant.io/integrations/geniushub/
Also raised here, with no responses to date
home-assistant/core#86277
The state of plug (on or off) is not updated until the switch is used.
In switch.py we have:
@property
def is_on(self) -> bool:
"""Return the current state of the on/off zone.
The zone is considered 'on' if & only if it is override/on (e.g. timer/on is 'off').
"""
return self._zone.data["mode"] == "override" and self._zone.data["setpoint"]
The switch can on if the mode is "override" or "timer". I am not sure if or why this test is required.
The return statement could be:
return and self._zone.data["setpoint"]
or if we what the check on the mode include "timer"
return self._zone.data["mode"] in ["override", "timer"] and self._zone.data["setpoint"]
Both appear to resolve the issue and reflect the same state as in the Genius App.
While attempting to build on a new system (Windows) I am capturing here some issues I have run into.
python .\setup.py installTraceback (most recent call last):
File "setup.py", line 8, in <module>
from setuptools import find_packages, setup
ModuleNotFoundError: No module named 'setuptools'
Fix: pip install setuptools
python .\setup.py install
Traceback (most recent call last):
setup.py", line 15, in <module>
VERSION = os.environ["GITHUB_REF_NAME"]
~~~~~~~~~~^^^^^^^^^^^^^^^^^^^
File "<frozen os>", line 685, in __getitem__
KeyError: 'GITHUB_REF_NAME'
Could not solve this using Windows POWERSHELL. Ended up using pip install aiohttp
. This allowed me to run the client locally.
The battery warning e.g. 'The battery for the Genius Valve is low' doesn't provide zone information, and therefore isn't localisable to an individual device. I think this this because it is missing {zone_name} from the error message.
If we are saying Smart Plugs should be switches and not Binary Sensors, then Smart Plugs should be filtered out of Binary Sensors. To do this make the following two changes to binary_sensor.py:
Add
GH_TYPE = "Receiver"
to
GH_STATE_ATTR = "outputOnOff"
GH_TYPE = "Receiver"
And change:
async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the Genius Hub sensor entities."""
if discovery_info is None:
return
broker = hass.data[DOMAIN]["broker"]
switches = [
GeniusBinarySensor(broker, d, GH_STATE_ATTR)
for d in broker.client.device_objs
if GH_STATE_ATTR in d.data["state"]
]
async_add_entities(switches, update_before_add=True)
to
async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
"""Set up the Genius Hub sensor entities."""
if discovery_info is None:
return
broker = hass.data[DOMAIN]["broker"]
switches = [
GeniusBinarySensor(broker, d, GH_STATE_ATTR)
for d in broker.client.device_objs
if GH_TYPE in d.data["type"]
]
async_add_entities(switches, update_before_add=True)```
Whilst I am happy to maintain the client's current v1-compatible JSON output, I would like to expose the v3 API too.
To make the distinction more obvious to existing users, I propose adding classes to contain the properties and statuses of a zone, with obviously named properties on the classes (e.g. _has_room_sensor rather than has_Pir).
The current data property that returns the v1-compatible JSON can then be generated from the properties on these classes.
Not really sure why the schedule is required to be processed by HA. HA just needs to reflect the state of each device.
2019-11-13 19:08:06 ERROR (MainThread) [geniushubclient] Failed to convert Zone 23, message: list index out of range.
Traceback (most recent call last):
File "/usr/local/lib/python3.7/site-packages/geniushubclient/__init__.py", line 546, in _convert
result["schedule"]["timer"] = _timer_schedule(raw_json)
File "/usr/local/lib/python3.7/site-packages/geniushubclient/__init__.py", line 463, in _timer_schedule
tm_last = setpoints[idx + 1]["iTm"]
IndexError: list index out of range
It would be far easier, for HA, if during initialisation this code was not executed unless I am missing the point as to why it is needed. In HA schedules cannot, currently, be viewed or edited.
I have been away sometime. Finally back home, moved, and get HA up and running in our new house.
Originally posted by @sftgunner in home-assistant/core#78799 (comment)
Hi @manzanotti, just to add to your todo list (sorry!)...
I've done the same as @rbubley and have replaced the client library with the current master branch which solves some of the error messages, but I'm still getting the error
Failed to convert Zone 0. Traceback (most recent call last): File "/config/custom_components/geniushub/geniushubclient/zone.py", line 150, in data result["mode"] = IMODE_TO_MODE[raw_json["iMode"]] KeyError: 0
.
From inspection of the raw v3 /zones API endpoint, it looks like I have two zones - one for the whole house and a zone which appears in the GeniusHub web GUI as my test room. I have a powered room thermostat in the test room, which has iMode 2. However, the "whole house" zone has iMode 0 (which helpfully isn't documented), which doesn't exist in the const IMODE_TO_MODE and thus causes the whole thing to fail.
Any ideas?
Currently sensors such as battery levels just reflect the id of the device. If this included the zone name it would make it more useful.
In sensor.py
I suggest the code is changed from:
class GeniusBattery(GeniusDevice, SensorEntity):
"""Representation of a Genius Hub sensor."""
def __init__(self, broker, device, state_attr) -> None:
"""Initialize the sensor."""
super().__init__(broker, device)
self._state_attr = state_attr
self._attr_name = f"{device.type} {device.id}"
to:
class GeniusBattery(GeniusDevice, SensorEntity):
"""Representation of a Genius Hub sensor."""
def __init__(self, broker, device, state_attr) -> None:
"""Initialize the sensor."""
super().__init__(broker, device)
self._state_attr = state_attr
self._attr_name = f"{device.type} ({device.id}) in {device.assigned_zone.name}"
from
Hi - first of all many thanks for all the work here, I came by way of Home Assistant where I wanted to override a Smart Plug at Sunset for a period of time, I can't get it to work there so wanted to check the underlying solution.
Here's what I have found:
There is no error and the zone does come on but the override time is whatever it was before (when trying this in Home Assistant I get an error (see image)
There are a couple of things in ghclient.py that confuse me and this may be the root of my issue:
Examples:
ghclient.py HUB_ID [Makes sense]
Display information about the Hub.
ghclient.py HUB_ID zones -v [Makes sense]
Display detailed information about all Zones.
ghclient.py HUB_ID -z 3 off [Assume this needs to be "-m off"]
Turn Zone 3 off.
ghclient.py HUB_ID -z 12 -d 3600 -t 19.5 [Assume this needs to be -s 3600 and it would override for 3600 seconds]
Set the override temperature for Zone 12 to 19.5C for 1 hour.
I can't get the override to work with Power Zone or Heating Zone - the mode changes OK to Override but the time and the temperature are not applied.
So in summary this is either user error or a bug, override works but the time and temperature settings don't seem to (for me).
I'm very happy to provide more information or try things out if it helps.
Regards,
Gav
PS The Temperature Override in HA appears OK, but the duration does not seem to work.
For what it's worth, I have observed that the iOS client can request a Z-Wave ping by a making a POST to this route: /v3/hg-zwave/ping/{deviceId:int}
. This route isn't in the published API description.
(Incidentally GETs to /v3/hg-zwave
give a variety of information, including all the information on the 'Device List' page of the app and quite a lot more - it might be interesting in due course to be able to access this info from geniushub-client).
Hi there!
I have been getting a very similar failure to the one found here: #74 only my issue complains because it can't find 'type' in the dict its found. The stack trace fails in exactly the same place however.
With a bit of investigation, I established your library gets 'friendly device names' and so forth from data that is hardcoded in const.py
- this of course means that if Genius Hub release new devices, or even update the firmware in such a way that device ids change, things break.
With a bit more investigation, I've established that you are getting your mapping data structures from the Genius Hub frontend client. A data object that appears to be completely identical in shape to the one that is causing the problem for me can be found in https://www.geniushub.co.uk/app/js/bower.js
.
To make your library more resilient, I think you need to find some way of automatically fetching that data, otherwise as the maintainer you are faced with the task of continually updating the package to match any updates the GH team do. To do it at runtime would be slow, but it might not be a bad idea to do this either at install or publish time. With that in mind, I've written a python script this evening that essentially does just this: downloads the bower.js
file, parses it, locates the dict containing the AST for the 'devicesModel', generates a python AST from it, then writes that to disk as a python source file.
It's not perfect (and parsing that JS file is particularly slow), but I'd like to work this into something that you can use in your package to make sure that its always up to date. If this works, would you accept it as a PR? Script is as follows:
import aiohttp
import json
import asyncio
import ast
import astor
from pyjsparser import parse
def find_symbol_with_name(name, my_dict, parent):
"""
Recursively iterate through the generated JS AST
until you find an object with a 'name' that matches
the passed in name argument. Once found, return its parent
"""
if (isinstance(my_dict, dict) and "name" in my_dict and my_dict['name'] == name):
return parent
if (isinstance(my_dict, dict)):
for next_key in my_dict:
if (type(my_dict[next_key]) == list):
for item in my_dict[next_key]:
result = find_symbol_with_name(name, item, my_dict)
if (result != None):
return result
if (isinstance(my_dict[next_key], dict)):
result = find_symbol_with_name(name, my_dict[next_key], my_dict)
if (result != None):
return result
return None
def transform_value(value):
if value['type'] == 'Literal':
return ast.Constant(value['value'])
if value['type'] == 'ArrayExpression':
values = []
for element in value['elements']:
values.append(transform_value(element))
return ast.List(elts=values)
def process_devices_model(data):
"""
Generate a python AST from the found JS AST
"""
elements = data['value']['elements']
keys = []
values = []
for element in elements:
for property in element['properties']:
keys.append(ast.Constant(value=property['key']['value']))
values.append(transform_value(property['value']))
ast_elements = ast.Dict(
keys=keys,
values=values
)
return ast.Module(body=[
ast.Assign(targets=[ast.Name(id="DEVICES_MODEL")],
value=ast.List(
elts=[ast_elements]
)
)
])
async def main():
async with aiohttp.ClientSession() as session:
print('Downloading bower.js')
async with session.get('https://www.geniushub.co.uk/app/js/bower.js') as response:
javascript = await response.text()
print('Parsing JavaScript')
ast = parse(javascript)
print('Extracting devicesModel')
devices = find_symbol_with_name('devicesModel', ast, None)
print('Generating source')
devices_ast = process_devices_model(devices)
source = astor.to_source(devices_ast)
print('Writing to disk')
with open('output.py', 'w') as file:
file.write(source)
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
I had an issue that has now gone away so this is more for information.
As part of trying to address the problem with switches not being in a zone I tried to create that situation on my HG hub. I did this by deleting a zone, before removing the device (smart plug) from the zone. The smart plug got orphaned. I could see it in the list of devices but this led to two errors.
The first:
Without this information geniushub-client fails to log in and reports a 503 error as it cannot retrieve the version information.
The second, once the first was fixed by power cycling the hub leads to this device_id = raw_json["data"]["nodeID"]
line throwing a key error "nodeID" missing.
I have strengthen the code for testing like this.
if "{device_type}" in description:
# don't use nodeHash, it won't pick up (e.g. DCR - Channel 1)
# vice_type = DEVICE_HASH_TO_TYPE[raw_json["data"]["nodeHash"]]
if "nodeID" in raw_json["data"]:
device_id = raw_json["data"]["nodeID"]
if device_id in self._device_by_id.keys():
device = self._device_by_id[device_id]
device_type = device.data["type"]
else:
device_type = "Unknown device"
else:
device_type = "Unknown device"
My hub is now in a good state and I cannot reproduce this problem or capture the JSON returned from the hub.
I think the support team at HG did something as the orphaned smart plug is now back where it should have been.
Feel free to close this issue as if it can't be reproduced it can't be fixed. You may want make the code more robust first.
All was working fine with HA until the latest update. I can no longer connect to my Genius Hub.
I am testing standalone with ghclient.py with correct local IP address, username and password. The follow error is produced:
Exception has occurred: ClientConnectorError
Cannot connect to host http:80 ssl:default [getaddrinfo failed]
File "D:\demos\geniushub-client\geniushubclient\session.py", line 45, in request
async with http_method(
File "D:\demos\geniushub-client\geniushubclient\__init__.py", line 248, in update
zones, data_manager, auth = await asyncio.gather(
File "D:\demos\geniushub-client\ghclient.py", line 191, in main
await hub.update() # initialise: enumerate all zones, devices & issues
File "D:\demos\geniushub-client\ghclient.py", line 262, in <module>
LOOP.run_until_complete(main(LOOP))
Doing the same (local IP address, username and password) from https://pypi.org/project/geniushub/ => https://github.com/GeoffAtHome/geniushub connects fine.
I am wondering if anyone else is seeing this issue or is it my system and setup?
Will investigate more later... just reaching out incase someone else has see the same issue.
I get this error for 2 zones in my list whenever I run ghclient:
ERROR: Failed to convert Zone 3, message: list index out of range.
Traceback (most recent call last):
File "./geniushubclient/__init__.py", line 246, in populate_objects
entity = obj_by_id[raw_json[key]]
KeyError: 3
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "./geniushubclient/__init__.py", line 556, in _convert
result["schedule"]["timer"] = _timer_schedule(raw_json)
File "./geniushubclient/__init__.py", line 473, in _timer_schedule
tm_last = setpoints[idx + 1]["iTm"]
IndexError: list index out of range
Which is actually taking place in _timer_schedule(raw_json) -> Dict
, when the last item in objTimer has the same temp value as the "defaultSetpoint"
I stopped it complaining like this (the error was in the setpoints[idx + 1]["iTm"]
:
- elif sp_next != node["defaultSetpoint"]:
+ elif sp_next != node["defaultSetpoint"] and idx != len(setpoints)-1:
Though I'm not quite sure what the comparison to defaultSetPoint is for, only collect times when the temp is different to the default?
The data:
{
"fSP": 21,
"iDay": 6,
"iTm": -1
},
{
"fSP": 15,
"iDay": 6,
"iTm": 0
},
{
"fSP": 21,
"iDay": 6,
"iTm": 300
}
I have a Genius Hub with an added OpenTherm Board (a beta trial they ran). That has added an extra zone for the boiler but without a type key returned from the api. I have emailed Genius Hub asking them to add one but no response as yet.
Would you accept a PR to check that the type key exists? I am currently getting the following error:
Logger: geniushubclient.zone
Source: /usr/local/lib/python3.10/site-packages/geniushubclient/zone.py:184
First occurred: 12:18:41 (4 occurrences)
Last logged: 12:21:41
Failed to convert Zone 1.
Traceback (most recent call last):
File "/usr/local/lib/python3.10/site-packages/geniushubclient/zone.py", line 145, in data
result["type"] = ITYPE_TO_TYPE[raw_json["iType"]]
KeyError: 7
The relevant part returned from /zones of the api:
{
"id": 1,
"name": "Ideal Vogue Max",
"output": 0,
"mode": "linked",
"schedule": {
"timer": {},
"footprint": {}
}
},
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.