[Deprecated] Harmony hub instant status updates using node.js app

Oh cool! Sounds like exactly what I'm trying to do. Will give it a shot, thanks.

I'm getting the following error when trying to install as a global package, any ideas? Did you have to change anything before doing this?

image

sudo npm -g

1 Like

You also may need to do a su and run truly as root. That is what I had to do due to node.js v11 restrictions.

I couldn't get the global service thing to work, not sure what I'm doing wrong but I didn't spend a lot of time on it.

I also experimented a bit with using refresh instead of on/off commands, and I wasn't getting good results, although I just now am realizing that I did not add the 5 second sleep. Either way, it's been working fine for me with the change made earlier this week. Basically, when the hub turns on, send an on command to the HE switch. When the hub turns off, send an off command to the HE switch. When the hub changes activities (this is what was causing problems earlier), I only send an on command to the new activity's HE switch (rather than an on for the new switch plus an off for the old switch), which appears to cause the HE app to call a hub refresh, which turns off the old HE activity switch. I did not add any type of delay, seems that waiting to send the commands when the activity finishes turning on is enough of a delay.

I rewrote the client app to be cleaner and accept a command line argument to direct it to a certain hub, rather than having separate files for each hub. This way there's only one code base, rather than having to make updates in multiple places. It now takes Activity and Switch names in the cross-reference instead of ID numbers (it cross-references those to ID numbers later). I also added something for the client to restart itself when it loses connection to the hub.

Here's my updated instructions, these don't necessarily follow the path that you were working with @aaron, I apologize for this but this is a better fit for my skillset.

  • Install the harmonyhubjs-client from the swissmanu github link above
  • Create a blank file in the root folder called "client.js"
  • Populate the client.js file with the code template below
  • Fill in your Maker API URL as described in my first post
  • Populate the hubXref variable with your desired hub nickname and associated IP address (the nickname is what you will enter as a command line argument)
  • Populate the activityXref variable with your hub nickname, activity names, and switch names
  • Open a command prompt, navigate to the install folder, and type node client.js hub_nickname

Example of my client.js file populated:

My hub nicknames are MBR for master bedroom and LR for living room. So when I launch this app, I type
node client.js MBR
or
node client.js LR

Code to paste into client.js file:

var harmony = require('./index.js')
var request = require('request')
var hubName = process.argv[2]
var harmonyIP
const makerLink = ''
var hubXref = //create an array variable to associate command line arguments to IP addresses
				[
					//['HUB_NICKNAME','IP_ADDRESS']
					['',''],
					['','']
				]
var activityXref = //create an array variable to associate activity names from Harmony to switch names from Hubitat
					[
						//['HUB_NICKNAME','ACTIVITY_NAME','SWITCH_NAME']
						['','',''],
						['','','']
					]
//----------------------------------------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------------------------------------

cleanXref()

function cleanXref() {
	var i
	var list = []
	for (i = 0; i < activityXref.length; i++) {
		if(activityXref[i][0] != hubName) {
			list.push(i)
		}
	}
	for (i = list.length - 1; i >= 0; i--) {
		activityXref.splice(list[i],1)
	}
	getIP()
}
					
function getIP () {
	for(i = 0; i < hubXref.length; i++) {
		if (hubXref[i][0] == hubName) {
			harmonyIP = hubXref[i][1]
			console.log('Found IP adress ' + harmonyIP + ' for hub ' + hubName)
		} 
	}
	if(harmonyIP == null)  {
		console.log('Could not find a match for hub ' + hubName + ', please try again')
	} else {
		getSwitchIds()
	}
}

function getSwitchIds () {
	var options = {
		uri: makerLink,
		method: 'GET',
		json: true
	}
	request(options, function(error, response, body) {
		if (!error && response.statusCode == 200) {
			var i
			var j 
			for(i = 0; i < body.length; i++) {
				for (j = 0; j < activityXref.length; j++) {
					if(activityXref[j][2] == body[i].label) {
						activityXref[j][2] = body[i].id
					} 
				}
			}		
			startClient()
		} else {
			console.log('no response received from Hubitat - check that Maker API is enabled and URL is correct')
		}
 })
}
 
function startClient () {
	harmony(harmonyIP).then(function(harmonyClient){
		harmonyClient.getActivities().then(function(activities) {
			var i
			var j
			for(i = 0; i < activities.length; i++) {
				for (j = 0; j < activityXref.length; j++) {
					if(activities[i].label == activityXref[j][1]) {
						activityXref[j][1] = activities[i].id
					}
				}
			}
			console.log('----------------------------------------------------------')
			console.log('Using cross-reference list:')
			console.log(activityXref)
			console.log('----------------------------------------------------------')
			console.log('Listening for state digest...')
		})
		console.log('Starting Client...')
		
		
		! function keepAlive(){
			harmonyClient.request('getCurrentActivity').timeout(5000).then(function(response) {
				setTimeout(keepAlive, 45000);
			}).catch(function(e){
				console.log('Lost connection to hub  (' + new Date() + ')')
				console.log('Restarting...')
				harmonyClient.end()
				startClient()
			})
		}()
		
		
		harmonyClient.on('stateDigest', function(digest) {
			var statusCode = digest.activityStatus
			var currentlyOn = digest.runningActivityList
			var targetActivity = digest.activityId
			
			switch(String(statusCode)) {
				case '0': //Hub is off
					if(currentlyOn == '' && targetActivity == '-1') {
						console.log('- Hub is off')
					} else {
						console.log('- Activity ' + currentlyOn + ' is off')
						changeSwitch(currentlyOn, 'off')
					}
					break;
				case '1': //Activity is starting
					console.log('')
					console.log('Received state digest (' + new Date() + ')')
					console.log('- Activity is starting...')
					break;
				case '2': //Activity is started
					if(currentlyOn == targetActivity) {
						console.log('- Activity is started')
					} else {
						if(currentlyOn == '') {
							console.log('- Activity ' + targetActivity + ' is on')
							changeSwitch(targetActivity, 'on')
						}
						else {
							console.log('- Activity ' + targetActivity + ' is on')
							console.log('- Activity ' + currentlyOn + ' is off')	
							changeSwitch(targetActivity,'on')
						}
					}
					break;
				case '3': //Hub is turning off
					console.log('')
					console.log('Received state digest (' + new Date() + ')')
					console.log('- Hub is turning off...')
					break;
			}
		})
	}).catch(function(e){
		console.log('error')
	})
}

function changeSwitch (activityId, changeTo) {
	var activityCount = activityXref.length
	var i
	var switchId = ""
	var tokenLabelPos = makerLink.indexOf('access_token')
	var makerURL = makerLink.substring(0,tokenLabelPos - 1)
	var makerToken = makerLink.replace(makerURL,'')
	
	for(i = 0; i < activityCount; i++) {
		if (activityXref[i][1] == activityId) {
			switchId = activityXref[i][2]			
			console.log('- Cross-referencing activity ' + activityId + ' to switch ' + switchId)
		}
	}
	
	var options = {
		uri: makerURL + "/" + switchId + "/" + changeTo + makerToken,
		method: 'GET',
		json: true
		}
		
	request(options, function(error, response, body) {
		if (!error && response.statusCode == 200) {
			console.log("- Sending " + changeTo + " command to switch " + switchId)
		} 
	})
}

Today's nerd alert btw...

2 Likes

Instead of running this as a service, I'm using the autostart file in /home/pi/.config/lxsession/LXDE-pi folder:
image

Then have these two lines added to the bottom
image

Well crap.

On Friday I added harmonyClient.end() to the keepAlive "catch" section to see if it would do a better job of resetting the connection after it dropped. I've been hoping to get a good test of this, but both Harmony hubs have remained connected since mid-day Friday without dropping. I guess that's a good problem to have. Added the line to my template in post 26. If this ends up working, I'll be happy with the functionality of this app.

@mike.maxwell does the Hubitat hub have any capability to act as a XMPP client? If so, the answer to getting Hubitat to talk to Harmony without copying authentication tokens from ST is here:

1 Like

not as it sits currently, no.

Possibility for the future?

Ahhhhh got a good restart! Woohoo!

I mean it looks like it tried a few times before it got a good connection, but hey, it worked without having to manually restart.

2 Likes

I haven't touched either instance of this app on my Pi and they are still running after 8 days. I think I'm going to mark this as a success :sunglasses:

1 Like

Awesome! I haven’t had any issues since changing to refresh.

1 Like

I'm seeing TONS of disconnect and reconnects on my console, but just for one of my two hubs. Really weird. I'm sure there's a better way to handle these, but at least it's doing it on it's own. Might move this to Beta and see if I can get more people in here to help.

1 Like

Hoping for some assistance on the install. NPM is installed already. I've used it for Homebridge. Here is what I'm seeing when I run the command to install:

npm install harmonyhubjs-client --save

npm WARN saveError ENOENT: no such file or directory, open '/home/pi/package.json'

npm WARN enoent ENOENT: no such file or directory, open '/home/pi/package.json'

npm WARN pi No description

npm WARN pi No repository field.

npm WARN pi No README data

npm WARN pi No license field.

  • harmonyhubjs-client@1.1.10

removed 154 packages and updated 1 package in 8.731s

What version of node.js?

Sudo node —version

It was on an old version, so I updated it. It is now running 11.2.0. The errors I posted above were after I had updated it, in case that was the issue.

EDIT: In case it helps, I'm doing this all via command line, no GUI. This is on a raspberry pi.

No worries I don't use a GUI either.

So with 10.X and above Node.JS doesn't allow you do "sudo" to update anything. I had to do a "su" for superuser as root and then everything installed correctly.

I'll try that now and let you know!