New Python plugin for Logitech Harmony

Python and python framework

Moderator: leecollings

EscApe
Posts: 535
Joined: Thursday 02 April 2015 8:46
Target OS: Linux
Domoticz version: 2020+
Location: The Netherlands
Contact:

New Python plugin for Logitech Harmony

Post by EscApe »

Update 19/02/2018:
Improved (connection) error handling for the alternative version (using Domoticz connection framework)
http://www.domoticz.com/forum/viewtopic ... 86#p171786

Update 17/02/2018:
Alternative version available for testing. If loading urllib crashes your Domoticz you can give this one a try.
http://www.domoticz.com/forum/viewtopic ... 12#p171505

Update 31/01/2018: I think the plugin is now ready for everyday use. I have made it a little more robust. You can set the number of retries before a connection error is logged. The connection errors are quite common with the Harmony Hub. I love the Harmony hub but it tends te be unresponsive for a short while quite frequently.
The timeout for polling the hub can now be configured in the hardware screen. If you use a short interval (e.g. poll interval is 5 seconds) you should also use a short timeout (1 second) te prevent the plugin from blocking the event system. When polling every 5 seconds with a timeout of 3 seconds the plugin could theoretically take up (3/5) 60% of the event system, but only if there are (temporary) connection issues. This would be the case for any python plugin, since the plugin system runs in a single thread.
If you already installed the plugin make sure you set the new parameters on the configuration screen.

Surely there is more room for improvement, like cleaning up the code and constantly adhering to coding standards :-S. Those will stay on my to-do list a little longer because of other priorities. I would welcome any help from the community help on optimizing and enhancing the plugin.

Update 28/01/2018: Issue seems to be fixed. The plugin no longer breaks the hardware configuration page.

Description
The build-in Harmony hardware support in Domoticz frequently hangs my entire Domoticz system. I suspect it is overloading the event system when the Harmony hub becomes unresponsive at times. Since it doesn't seem to be maintained, I have created an alternative. This python plugin connects to the restful-harmony daemon by bwsystems

Restful-harmony does the heavy lifting. My plugin just interfaces with it.
Functionality is very similar to the build in Harmony support for Domoticz:

- a Domoticz device is created for every activity (on startup).
- the current activity is polled every x seconds and the corresponding device will show 'On'. Others will be 'Off'
- if you turn any of the devices 'On' in Domoticz the corresponding Activity is started on Harmony

Solved:So far so good.. this is where te trouble starts ..
If i have this and my other python plugin (iDetect) installed and enabled, the hardware configuration screen gets messed up. Both are shown with the same hardware-type (but they aren't) and configuration parameters are not displayed (initial configuration went fine). Is this a know issue?

I will incude a screenshot of the malformed configuration page and the plugin itself. It might just work on your setup(?)


Comments, tips, enhancements are much appreciated. If anyone wants to take on the challenge to turn this hobby project into a robust solution: feel free ;-)

Installation:
-Make sure you have python3.4 installed and not python3.5. Otherwise some imports may crash Domoticz (seems te be an issue with the plugin system)
-Make sure you have a functioning restful-harmony server running (https://github.com/bwssytems/restful-harmony)
-Unzip attached harmony.zip into a folder under ~/domoticz/plugins (keep the included icon-file zipped) and restart domoticz
-Add hardware 'Logitech Harmony restful connector'

Version 0.0.4 (31/01/2018):
Harmony.zip
(186.47 KiB) Downloaded 169 times
Last edited by EscApe on Monday 19 February 2018 20:17, edited 7 times in total.
User avatar
Dnpwwo
Posts: 820
Joined: Sunday 23 March 2014 9:00
Target OS: Raspberry Pi / ODroid
Domoticz version: Beta
Location: Melbourne, Australia
Contact:

Re: New Python plugin for Logitech Harmony .. technically working, but strange (plugin system?) issue

Post by Dnpwwo »

@EscApe,

I pushed a fix today for an issue with the hardware configuration where some extraneous fields could be left on the page but doesn't sound exactly the same as your problem (but might be). Can you re-test in the next beta (make sure your browser cache refreshes) and if the problem is still there post the details.

If I had to guess I would check to make sure that the plugin 'key' field in the plugin XML definition is different between the two. The key needs to be unique. Its never shown so you can make it something pretty random.
The reasonable man adapts himself to the world; the unreasonable one persists to adapt the world to himself. Therefore all progress depends on the unreasonable man. George Bernard Shaw
EscApe
Posts: 535
Joined: Thursday 02 April 2015 8:46
Target OS: Linux
Domoticz version: 2020+
Location: The Netherlands
Contact:

Re: New Python plugin for Logitech Harmony .. technically working, but strange (plugin system?) issue

Post by EscApe »

@Dnpwwo

Thanks for your input! The key was already unique, but after experimenting with all kinds of naming it turns out the module name was the culprit(!) If the string 'Harmony' is anywhere in de plugin name it will mess up the hardware configuration pages. This seems to be a Domoticz bug.

I have changed the plugin name to "Logitech H-rmony restful plugin" as a work around.
User avatar
Dnpwwo
Posts: 820
Joined: Sunday 23 March 2014 9:00
Target OS: Raspberry Pi / ODroid
Domoticz version: Beta
Location: Melbourne, Australia
Contact:

Re: New Python plugin for Logitech Harmony

Post by Dnpwwo »

@EscApe,

Interesting. Your original plugin worked for me on the Hardware page after the fix I pushed.
Untitled.png
Untitled.png (13.32 KiB) Viewed 7157 times
If you are running the beta version I would suggest taking an update and retrying.
The reasonable man adapts himself to the world; the unreasonable one persists to adapt the world to himself. Therefore all progress depends on the unreasonable man. George Bernard Shaw
EscApe
Posts: 535
Joined: Thursday 02 April 2015 8:46
Target OS: Linux
Domoticz version: 2020+
Location: The Netherlands
Contact:

Re: New Python plugin for Logitech Harmony

Post by EscApe »

@Dnpwwo

I appreciate your taking an interest in my plugin (troubles). Thanks!
First thing i did was testing the Domoticz update that included your commit. Unfortunately it didn't solve this particular problem on my testserver.

Did you have a second python plugin installed? The problem only arose when the Harmony plugin was not the only (enabled) python plugin. In that case the 'hardware type' of the other plugin would also show as the 'hardware type' (name field in xml) of the Harmony plugin.
Even with another plugin installed you could still perform the initial configuration. After that you could no longer reach the existing configuration fields.

The new version of the plugin works just fine with the following xml in plugin.py:
<plugin key="restharmony" name="Logitech H-rmony restful plugin" author="ESCape" version="0.0.2">

If i only change H-rmony to Harmony the problem returns. Maybe you can confirm this with a second python plugin active(?)

The new plugin is running fine on my production system for now, but showing a lot of connect failures in the log. Works just fine though. Because of the very short timeouts i have set they don't affect my domoticz performance. I suspect this is why the 'real' Harmony hardware integration is able to completely hang domoticz. It might just choke on an often unresponsive Harmony hub. Maybe the original Harmony integration has a longer timeout so it backs up the event system(?) I lack the skills to analyse or change the original Harmony integration code, hence the python plugin :-S ;-)
User avatar
remb0
Posts: 499
Joined: Thursday 11 July 2013 22:21
Target OS: Raspberry Pi / ODroid
Domoticz version: Beta
Location: The Netherlands
Contact:

Re: New Python plugin for Logitech Harmony

Post by remb0 »

Great work! but after adding the hardware it looks fine. but when I go to devices domoticz crashes. hardware, devices and settings are empty.
I rebooted and tested it again, and another time with a different hardware name. both the same result.
also with the line in this topic.
EscApe
Posts: 535
Joined: Thursday 02 April 2015 8:46
Target OS: Linux
Domoticz version: 2020+
Location: The Netherlands
Contact:

Re: New Python plugin for Logitech Harmony

Post by EscApe »

@remb0
The only situation in wich Domoticz would crash my Domoticz was when Python3.5 was installed. It would crash on loading the urllib module.
Could you test the folllowing:
Shut down Domoticz
Put a # in front of the line that says import urllib.request (so it will not run) in plugin.py
Restart Domoticz

Obviously the plugin will not function and you will see some errors in the logs. However, if Domoticz doesn’t crash you might have the same python version conflict.

Remove the # from plugin.py
Make share you completely remove everything related to Python3.5 and (re)install Python3.4-dev
Reboot

There’s not much else I can think of without knowing more about your system. There are many factors that can mess up de Domoticz python plugin system (which I think is still in beta). I’m running the plugin successfully on a clean install of the beta version from last Sunday and on my production system wich runs an older beta and has lots of other hardware configured (rfxcom, zwave, hue, serial, python plugins). No issues besides the (actual) harmony hub often being (to) slow to respond.
DAVIZINHO
Posts: 234
Joined: Sunday 27 August 2017 18:00
Target OS: Raspberry Pi / ODroid
Domoticz version: Beta
Location: Spain
Contact:

Re: New Python plugin for Logitech Harmony

Post by DAVIZINHO »

helllo,
Sorry for my ignorance. exactly how works the integration of the harmony hub with domoticz?
i have domoticz and a new harmony elite and i dont know how it works.

I can launch switchs of domoticz using the logitech harmony?
or i can lauch harmony buttons using domoticz?
EscApe
Posts: 535
Joined: Thursday 02 April 2015 8:46
Target OS: Linux
Domoticz version: 2020+
Location: The Netherlands
Contact:

Re: New Python plugin for Logitech Harmony

Post by EscApe »

This plugin will allow you to control the Harmony Activities from Domoticz. Not the other way around.
Like it says in my fist post:
- a Domoticz device is created for every activity (on startup).
- the current activity is polled every x seconds and the corresponding device will show 'On'. Others will be 'Off'
- if you turn any of the devices 'On' in Domoticz the corresponding Activity is started on Harmony

If you want te control Domoticz from the harmony remote you should have a look at HA bridge: https://github.com/bwssytems/ha-bridge. It will emulate a Philips Hue and translate on/off commands into Domoticz api calls. The Harmony Hub can control the (virtual) Hue.
DAVIZINHO
Posts: 234
Joined: Sunday 27 August 2017 18:00
Target OS: Raspberry Pi / ODroid
Domoticz version: Beta
Location: Spain
Contact:

Re: New Python plugin for Logitech Harmony

Post by DAVIZINHO »

Ums, very interesting!!
I will install and work with my configuration.

Thanks a lot!!!
User avatar
heggink
Posts: 979
Joined: Tuesday 08 September 2015 21:44
Target OS: Raspberry Pi / ODroid
Domoticz version: 12451
Location: NL
Contact:

Re: New Python plugin for Logitech Harmony

Post by heggink »

@remb0: do you still have the issues with python 3.5? After installing HA-bridge, I regularly run into harmony hub issues so am looking to give this a go. Unfortunately, I am completely updated so python 3.5 as well.
Docker in Truenas scale, close to latest beta
DASHTICZ 🙃
RFXCOM, zwavejs2mqtt, zigbee2mqtt,
P1 meter & solar panel
Google home, Wifi Cams motion detection
Geofence iCloud, Bluetooth & Wifi ping
Harmony hub, Nest, lots more :-)
User avatar
heggink
Posts: 979
Joined: Tuesday 08 September 2015 21:44
Target OS: Raspberry Pi / ODroid
Domoticz version: 12451
Location: NL
Contact:

Re: New Python plugin for Logitech Harmony

Post by heggink »

Even after uninstalling everything python3.5, only 3.4, it still crashes :-(. Removing the HH plugin allows me to restart Domoticz. Shame as I would love to give this a try.
Bugger: forgot libpython3.5 and dev. Works now!
Unfortunately, fail2ban seems to depend on python3.5 so gets deinstalled as well.
Docker in Truenas scale, close to latest beta
DASHTICZ 🙃
RFXCOM, zwavejs2mqtt, zigbee2mqtt,
P1 meter & solar panel
Google home, Wifi Cams motion detection
Geofence iCloud, Bluetooth & Wifi ping
Harmony hub, Nest, lots more :-)
EscApe
Posts: 535
Joined: Thursday 02 April 2015 8:46
Target OS: Linux
Domoticz version: 2020+
Location: The Netherlands
Contact:

Re: New Python plugin for Logitech Harmony

Post by EscApe »

@Heggink
--- Update: sorry, just noticed in your post that fail2ban gets uninstalled when you uninstall python3.5. In that case the "help" offered in this post won't help you at all. I will post it just the same, for it might benefit others. ---

Glad you like the plugin. It has been working very well for me the last couple of days. The python-version-thing is a bit of a struggle indeed.

Hopefully the python3.4/3.5 issue will be fixed in Domoticz. In the mean time it remains necessary to remove every trace of 3.5 to successfully load some modules in a python plugin. As you found out this can be accomplished by:

sudo apt update
sudo apt autoremove python3.5-dev
sudo apt install3.4-dev

The side effect of this action doesn't matter to domoticz but it might influence other python3 programs. You might have noticed that just running "python3" from the command line results in "-bash: python3: command not found". Somehow the /usr/bin/python3 symlink gets removed but not recreated in the above approach. You could try a couple of things to get fail2ban running again with python3.4:

Manually create a symlink for the python3 command (without the .4)

Code: Select all

sudo ln -s /usr/bin/python3.4 /usr/bin/python3
Or start fail2ban as

Code: Select all

/usr/bin/python3.4 <full path of fail2ban>


Or have a look at the first line in the main fail2ban program. If it says

Code: Select all

#!/usr/bin/python
or

Code: Select all

#!/usr/bin/python3
change it to

Code: Select all

#!/usr/bin/python3.4
You can revert these changes when the python3.5 module loading issue is fixed in Domoticz.

I have no knowledge of fail2ban and have not tested these possible solutions, but they are worth a try. Please let me (and the community) know if any of them worked for you.
User avatar
heggink
Posts: 979
Joined: Tuesday 08 September 2015 21:44
Target OS: Raspberry Pi / ODroid
Domoticz version: 12451
Location: NL
Contact:

Re: New Python plugin for Logitech Harmony

Post by heggink »

Hi @EscApe,
Rather than using urllib, why not use the bultin connections framework? Whilst the system shouldn't crash on importing urllib.request, this seems like a decent workaround...
Herman
Docker in Truenas scale, close to latest beta
DASHTICZ 🙃
RFXCOM, zwavejs2mqtt, zigbee2mqtt,
P1 meter & solar panel
Google home, Wifi Cams motion detection
Geofence iCloud, Bluetooth & Wifi ping
Harmony hub, Nest, lots more :-)
EscApe
Posts: 535
Joined: Thursday 02 April 2015 8:46
Target OS: Linux
Domoticz version: 2020+
Location: The Netherlands
Contact:

Re: New Python plugin for Logitech Harmony

Post by EscApe »

Hi heggink,

I have been experimenting with the build in Domoticz.Connection framework. The request-response principle is working, but i could not get it to give me the needed data.
If you have more knowledge about the build in framework then i could really use your help. I will describe the problem below, but also have a few questions regarding the framework.

1. Can i set a time-out for the connection (every request)? I wrote the plugin to stop the sometimes unresponsive harmony hub from bringing Domoticz to its knees and don't want requests to queue up.
2. I'm using two types of requests: one to get all activities (on startup) and one to get the current activity (on heartbeat) how can i tell them apart in the onMessage function?

The issue i ran into:

According to the journal for restful Harmony the request is received but not processed as expected by the restful Harmony (spark) webserver.
These are two seperate requests. One by my working plugin (at 11:37:29 using urllib.request) and one using the code below (at 11:37:33). The uri and httpMethod are the same, but somehow only the request by urllib is accepted. I could not get the same url to work with the build in Domoticz.Connection.

Code: Select all

Feb 16 11:37:33 domoticz java[331]: 11:37:33.560 [qtp31350797-204] INFO  spark.webserver.MatcherFilter - The requested route [/harmony/show/activity] has not been mapped in Spark
Feb 16 11:37:33 domoticz java[331]: 11:37:33.559 [qtp31350797-204] DEBUG spark.webserver.MatcherFilter - httpMethod:get, uri: /harmony/show/activity
Feb 16 11:37:29 domoticz java[331]: 11:37:29.862 [qtp31350797-218] DEBUG c.b.restfulharmony.api.HarmonyRest - Harmony api current sctivity requested from: 0:0:0:0:0:0:0:1
Feb 16 11:37:29 domoticz java[331]: 11:37:29.862 [qtp31350797-218] DEBUG spark.Request - get splat
Feb 16 11:37:29 domoticz java[331]: 11:37:29.861 [qtp31350797-218] DEBUG spark.Request - get params
Feb 16 11:37:29 domoticz java[331]: 11:37:29.861 [qtp31350797-218] DEBUG spark.webserver.MatcherFilter - httpMethod:get, uri: /harmony/show/activity
From the Domoticz Log:

Code: Select all

2018-02-16 10:38:28.573 (HUB) onConnect called
2018-02-16 10:38:28.577 (HUB) Connection (0) to: 192.168.19.222:8081 with status: Success
2018-02-16 10:38:28.631 (HUB) onMessage called
2018-02-16 10:38:28.632 (HUB) HTTP Details (3):
2018-02-16 10:38:28.632 (HUB) --->'Data':'b'
404 Not found
''
2018-02-16 10:38:28.633 (HUB) --->'Status':'404'
2018-02-16 10:38:28.633 (HUB) --->'Headers (4):
2018-02-16 10:38:28.633 (HUB) ------->'Date':'Fri, 16 Feb 2018 10:38:28 GMT'
2018-02-16 10:38:28.633 (HUB) ------->'Server':'Jetty(9.3.z-SNAPSHOT)'
2018-02-16 10:38:28.634 (HUB) ------->'Content-Type':'text/html;charset=utf-8'
2018-02-16 10:38:28.634 (HUB) ------->'Transfer-Encoding':'chunked'
2018-02-16 10:38:28.634 Error: (HUB) Harmony returned a status: 404
Relevant portions of my experimental plugin.py (only for testing the framework):

Code: Select all

===relevant fragment of onStart function===
		self.httpConn = Domoticz.Connection(Name="JSONTest", Transport="TCP/IP", Protocol="HTTP", Address=Parameters["Address"], Port=Parameters["Port"])
		self.httpConn.Connect()


=== on of many tries (tried different headers en content types)===
	def onConnect(self, Connection, Status, Description):
		Domoticz.Log("onConnect called")
		Domoticz.Log("Connection ("+str(Status)+") to: "+Parameters["Address"]+":"+Parameters["Port"]+" with status: "+Description)
		if (Status == 0):
			Domoticz.Debug("Harmony connected successfully.")
			sendData = { 'Verb' : 'GET',
					 'URL'	: '/harmony/show/activity',
					 'Headers' : { 'Content-Type': 'application/json', \
								   'Accept': 'Content-Type: application/json', \
								   'Host': Parameters["Address"]+":"+Parameters["Port"], \
								   'User-Agent':'Domoticz/1.0' }
				   }
			Connection.Send(sendData)
		else:
			Domoticz.Log("Failed to connect ("+str(Status)+") to: "+Parameters["Address"]+":"+Parameters["Port"]+" with error: "+Description)

	def onMessage(self, Connection, Data):
		Domoticz.Log("onMessage called")
		DumpHTTPResponseToLog(Data)
		
		strData = Data["Data"].decode("utf-8", "ignore")
		Status = int(Data["Status"])
		LogMessage(strData)

		if (Status == 200):
			Domoticz.Log("Good Response received from Harmony.")
			self.httpConn.Disconnect()
		else:
			Domoticz.Error("Harmony returned a status: "+str(Status))
User avatar
heggink
Posts: 979
Joined: Tuesday 08 September 2015 21:44
Target OS: Raspberry Pi / ODroid
Domoticz version: 12451
Location: NL
Contact:

Re: New Python plugin for Logitech Harmony

Post by heggink »

Hi EscApe,

Frankly, I don't know anything about the python framework as I am just an old Unix/C programmer who ended up in sales.
Have you tried to get help from the developers? There are a couple of issues listed on github where you could ask for some support.
H
Docker in Truenas scale, close to latest beta
DASHTICZ 🙃
RFXCOM, zwavejs2mqtt, zigbee2mqtt,
P1 meter & solar panel
Google home, Wifi Cams motion detection
Geofence iCloud, Bluetooth & Wifi ping
Harmony hub, Nest, lots more :-)
User avatar
Dnpwwo
Posts: 820
Joined: Sunday 23 March 2014 9:00
Target OS: Raspberry Pi / ODroid
Domoticz version: Beta
Location: Melbourne, Australia
Contact:

Re: New Python plugin for Logitech Harmony

Post by Dnpwwo »

@EscApe,

Rather than use urllib, can you hit the URL from a web browser? If it works from Chrome or FireFox you can get the browser to show you exactly what it sent. If you send the same verb, headers and data it will work.

If you can't tell the response messages apart by what is in them there are options:
  • Inject and extra header like 'X-Request-ID' (see https://en.wikipedia.org/wiki/List_of_H ... der_fields), a lot of web servers will return it for you or you could try 'Cookie' instead
  • You could assume the 1st response after a connection is established will be the 'all' response and track it using a variable in you plugin
To tell if the hardware is not responding you can count the number of heartbeats since a response and disconnect once a threshold is passed.
The reasonable man adapts himself to the world; the unreasonable one persists to adapt the world to himself. Therefore all progress depends on the unreasonable man. George Bernard Shaw
EscApe
Posts: 535
Joined: Thursday 02 April 2015 8:46
Target OS: Linux
Domoticz version: 2020+
Location: The Netherlands
Contact:

Re: New Python plugin for Logitech Harmony

Post by EscApe »

I have rewritten the plugin to use the Domoticz connection framework instead of urllib. The heavy lifting is still done by restful harmony.

It seems to work quite well. Functionality remains the same. Installation is also the same as described in the top post. If you are already using the previous versions you can just replace the plugin.py file so you will even keep the same Domoticz devices.
For now i will post this new version as an alternative. If it proves to be as stable as the urllib approach i will replace the main version in the top post.

Here it is... the Harmony restful plugin using the Domoticz connection framework:
Harmony.zip
(186.78 KiB) Downloaded 121 times
Superseded!! See newer version below (or always check top post for newest version).

@Dnpwwo: Thanks for jumping in! Your input really helped me to figure this out.
Last edited by EscApe on Monday 19 February 2018 20:19, edited 1 time in total.
EscApe
Posts: 535
Joined: Thursday 02 April 2015 8:46
Target OS: Linux
Domoticz version: 2020+
Location: The Netherlands
Contact:

Re: New Python plugin for Logitech Harmony

Post by EscApe »

Added some more connection error handling to reduce the number of log entries for recoverable errors. We know the Harmony hub occasionally gets unresponsive for a short time... no need to keep logging every temporary blackout.

Also added a automatic reset of the connection if a certain number of poll requests go unanswered. I think this version (using the Domoticz connection framework) is now ready for every day use. Please let me know how it behaves in your setup.

Enhancement: If the Harmony hub switches to an activity the plugin doesn't know (yet) the connection is reset to initiate a refresh of the domoticz devices per activity.

Version 0.1.2 (19/02/2018)
Harmony.zip
(187.77 KiB) Downloaded 197 times
EscApe
Posts: 535
Joined: Thursday 02 April 2015 8:46
Target OS: Linux
Domoticz version: 2020+
Location: The Netherlands
Contact:

Re: New Python plugin for Logitech Harmony

Post by EscApe »

Can anyone, maybe a developer, shed some light on this?

Since i have moved to a new router (Asus ac86u) the connection to the Harmony Hub has become more stable. The plugin however is now locking up every few days. It becomes unresponsive and when i try to stop (disable) it, dozens of these messages appear in the log:

Code: Select all

Error: (HUB) Callback event 'onHeartbeatCallback' (Python call 'onHeartbeat') discarded.
It will stop, but after re-enabling it nothing happens (log says it started, but still unresponsive). Restarting Domoticz does restore functionality.

This is the latest version of plugin.py (some changes in the error handling, to improve the way connection problems are handled. However, that didn't fix this issue)

Code: Select all

# Domoticz Python Plugin alternative to the Logitech Harmony hardware 
# Requires https://github.com/bwssytems/restful-harmony 
#
# Author: ESCape
#
"""
<plugin key="restharmony" name="Logitech H-rmony restful plugin" author="ESCape" version="0.1.3">
	<params>
		<param field="Address" label="Restful-harmony Address" width="200px" required="true" default="localhost"/>
		<param field="Port" label="Restful-harmony port" width="200px" required="true" default="8081"/>
		<param field="Mode1" label="Check current activity every" width="200px" required="true">
			<options>
				<option label="5 seconds" value="5"/>
				<option label="10 seconds" value="10" default="true"/>
				<option label="30 seconds" value="30"/>
			</options>
		</param>
	</params>
</plugin>
"""

import Domoticz
import json

class BasePlugin:
	def __init__(self):
		#some variables to reduce the number of reconnects and log entries on (common) Harmony connection issues
		self.reconnectdelay = 1 #wait X poll cyclus (Heartbeats) before reconnecting
		self.reconnectcountdown = self.reconnectdelay
		self.reqwaiting = 0
		self.maxreqwaiting = 5 #reset the connection if more than X requests have gone without response from restful Harmony
		self.huberrorsallowed = 4 #only log an error every X times the plugin receives a 500 error from restful Harmony
		self.huberrorcount = 0
		self.errorstate = False
		return
	
	def setactivitystatus(self, activity):
		Domoticz.Log("Activity has been changed to: " + Devices[self.harmonyidlist[activity]].Name)
		Devices[self.harmonyidlist[activity]].Update(nValue=1, sValue="On")
		for other in Devices:
			if other != self.harmonyidlist[activity]:
				if Devices[other].nValue == 1:
					Devices[other].Update(nValue=0, sValue="Off")
					
	def managedevices(self, actlist):
		#Select or create icons for devices 
		icon="Restharmony"
		if icon not in Images: Domoticz.Image('restharmony_icons.zip').Create()
		iconid=Images[icon].ID
		#Create device for every Activity
		for actid in actlist:
			if actid not in self.harmonyidlist:
				Domoticz.Device(Name=actlist[actid], Unit=firstfree(), DeviceID=actid, TypeName="Switch", Used=1, Image=iconid).Create()
				Domoticz.Log("Harmony activity " + actlist[actid] + " added.")
		self.harmonyidlist=act2id()
	
		#find and remove obsolete activities
		obsolete=[]
		for check in Devices:
			if Devices[check].DeviceID not in actlist:
				obsolete.append(check)
		for trash in obsolete:
			Domoticz.Log("Removing Harmony activity " + str(Devices[trash].Name) + " (obsolete).")
			Devices[trash].Delete()
		self.harmonyidlist=act2id()

	def onStart(self):
		self.restaddress=Parameters["Address"]
		self.restport=Parameters["Port"]
		Domoticz.Heartbeat(int(Parameters["Mode1"]))
		self.harmonyidlist=act2id()
		
		self.HubConn = Domoticz.Connection(Name="HarmonyHUB", Transport="TCP/IP", Protocol="HTTP", Address=Parameters["Address"], Port=Parameters["Port"])
		self.HubConn.Connect()
		
	def onStop(self):
		Domoticz.Log("onStop called")

	def onConnect(self, Connection, Status, Description):
		Domoticz.Debug("Connection ("+str(Status)+") to: "+Parameters["Address"]+":"+Parameters["Port"]+" with status: "+Description)
		if (Status == 0):
			Domoticz.Debug("Harmony connected successfully.")
			sendData = { 'Verb' : 'GET',
					 'URL'	: '/harmony/list/activities',
					 'Headers' : { 'Host': Parameters["Address"]+":"+Parameters["Port"], \
								'Connection': 'keep-alive', \
								'Cache-Control': 'max-age=0', \
								'Upgrade-Insecure-Requests': '1', \
								'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', \
								'Accept-Encoding': 'gzip, deflate', \
								'Accept-Language': 'nl-NL,nl;q=0.9,en-US;q=0.8,en;q=0.7', \
								'User-Agent':'Domoticz/1.0' }
					}
			self.HubConn.Send(sendData)
			self.reqwaiting += 1
		else:
			Domoticz.Error("Failed to connect ("+str(Status)+") to: "+Parameters["Address"]+":"+Parameters["Port"]+" with error: "+Description)

	def onMessage(self, Connection, Data):
		Domoticz.Debug("onMessage called for " + Connection.Name)
		self.reqwaiting -= 1
		if self.reqwaiting < 0:
			Domoticz.Error("Unexepected response from Harmony. Resetting connection.")
			self.HubConn.Disconnect()
		Status = int(Data["Status"])
		if (Status == 200):
			self.huberrorcount = 0
			if self.errorstate:
				Domoticz.Log("Recovered from error. Received a usable response from restful Harmony.")
				self.errorstate = False
			else:
				Domoticz.Debug("Good Response received from Harmony.")
			if "Data" in Data:
				strData = Data["Data"].decode("utf-8", "ignore")
				Domoticz.Debug("rawdata:" + strData)
				jsonData=json.loads(strData)
				if "status" in jsonData:
					Domoticz.Debug("Response from hub looks like activity switch confirmation")
					if jsonData['status']=="OK":
						Domoticz.Debug("Activity request from domoticz confirmed by restful harmony")
						self.setactivitystatus(encodehubid(self.updateactivity))
					else:
						Domoticz.Error("Failed to set activity from domoticz")
				elif "id" in jsonData:
					Domoticz.Debug("Response from hub looks like current activity")
					curact=int(jsonData["id"])
					Domoticz.Debug("Current: " + str(curact))
					Domoticz.Debug("Current domid: " + str(encodehubid(curact)))
					if encodehubid(curact) in self.harmonyidlist:
						if Devices[self.harmonyidlist[encodehubid(curact)]].nValue != 1:
							self.setactivitystatus(encodehubid(curact))
					else:
						Domoticz.Error("Current Harmony activity unknown to Domoticz... Resetting plugin to update data.")
						self.HubConn.Disconnect()
				else:
					Domoticz.Debug("Response from hub looks like list of activities")
					myacts = {}
					for act in jsonData:
						myacts[encodehubid(act['id'])]= act["label"]
					self.managedevices(myacts)
					Domoticz.Log("Harmony Hub offering the following activities: " + str(myacts))
			else:
				Domoticz.Log("Resonse contains no data")
		elif (Status == 500):
			if self.huberrorcount >= self.huberrorsallowed:
				Domoticz.Error("Harmony returned a status: "+str(Status)+" (restful harmony might not be able to connect to Harmony hub)")
				self.huberrorcount = 0
				self.errorstate = True
			else:
				self.huberrorcount += 1
		else:
			Domoticz.Error("Harmony returned a status: "+str(Status))
			self.errorstate = True

	def onCommand(self, Unit, Command, Level, Hue):
		if str(Command)=='On':
			harmonyact=decodehubid(int(Devices[Unit].DeviceID, 16))
			command = {"activityid":harmonyact} 
			params = json.dumps(command)
			Domoticz.Debug("Sending params:" + str(params))

			sendData = { 'Verb' : 'PUT',
				 'URL'	: '/harmony/start',
				 'Data' : params,
				 'Headers' : { 'Host': Parameters["Address"]+":"+Parameters["Port"], \
							'Connection': 'keep-alive', \
							'Upgrade-Insecure-Requests': '1', \
							'User-Agent':'Domoticz/1.0' }
				}
			self.HubConn.Send(sendData)
			self.reqwaiting += 1
			#store the command we just sent to update the corresponding switch immediately after confirmation is received
			self.updateactivity = harmonyact
		return

	def onNotification(self, Name, Subject, Text, Status, Priority, Sound, ImageFile):
		Domoticz.Log("Notification: " + Name + "," + Subject + "," + Text + "," + Status + "," + str(Priority) + "," + Sound + "," + ImageFile)

	def onDisconnect(self, Connection):
		Domoticz.Debug("onDisconnect called for " + Connection.Name)
		self.reqwaiting = 0

	def onHeartbeat(self):
		Domoticz.Debug("Number of requests waiting for a response: " + str(self.reqwaiting))
		if (self.HubConn.Connecting() or self.HubConn.Connected()):
			if self.reqwaiting > self.maxreqwaiting:
				Domoticz.Error("To many requests waiting. Resetting connection to restful Harmony.")
				self.HubConn.Disconnect()
			else:
				sendData = { 'Verb' : 'GET',
						 'URL'	: '/harmony/show/activity',
						 'Headers' : { 'Host': Parameters["Address"]+":"+Parameters["Port"], \
									'Connection': 'keep-alive', \
									'Cache-Control': 'max-age=0', \
									'Upgrade-Insecure-Requests': '1', \
									'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', \
									'Accept-Encoding': 'gzip, deflate', \
									'Accept-Language': 'nl-NL,nl;q=0.9,en-US;q=0.8,en;q=0.7', \
									'User-Agent':'Domoticz/1.0' }
						}
				self.HubConn.Send(sendData)
				self.reqwaiting += 1
				Domoticz.Debug("onHeartbeat called, Connection is alive.")
		else:
			self.reconnectcountdown -= 1
			if self.reconnectcountdown <= 0:
				Domoticz.Error("Connection lost.... reconnecting")
				self.HubConn.Connect()
				self.reconnectcountdown = self.reconnectdelay
			else:
				Domoticz.Debug("Harmony disconnected. Reconnecting in "+str(self.reconnectcountdown)+" heartbeats.")

global _plugin
_plugin = BasePlugin()

def onStart():
	global _plugin
	_plugin.onStart()

def onStop():
	global _plugin
	_plugin.onStop()

def onConnect(Connection, Status, Description):
	global _plugin
	_plugin.onConnect(Connection, Status, Description)

def onMessage(Connection, Data):
	global _plugin
	_plugin.onMessage(Connection, Data)

def onCommand(Unit, Command, Level, Hue):
	global _plugin
	_plugin.onCommand(Unit, Command, Level, Hue)

def onNotification(Name, Subject, Text, Status, Priority, Sound, ImageFile):
	global _plugin
	_plugin.onNotification(Name, Subject, Text, Status, Priority, Sound, ImageFile)

def onDisconnect(Connection):
	global _plugin
	_plugin.onDisconnect(Connection)

def onHeartbeat():
	global _plugin
	_plugin.onHeartbeat()

	# Generic helper functions

#map Harmony (external) activity ID's to internal Domoticz ID's
def act2id():
	actlist={}
	for thing in Devices:
		actlist[Devices[thing].DeviceID]=thing
	return actlist

#find the first available (internal) unit id
def firstfree():
	for num in range(1,250):
		if num not in Devices:
			return num
	return
	
def encodehubid(realid):
	if int(realid)==-1:
		#results in FFFFFFFF hex
		return format(4294967295, 'X')
	else:
		return format(int(realid), 'X')
		
def decodehubid(fakeid):
	if fakeid==4294967295:
		return -1
	else:
		return fakeid
Post Reply

Who is online

Users browsing this forum: FireWizard and 1 guest