Page 1 of 1

Heatmiser NeoHub Integration

Posted: Monday 17 April 2017 6:12
by ddahya
Hi Guys,
A while back people were asking for NeoHub integration, I have a working solution for getting the current temp, checking if heating is on/off and setting the target temp “Setpoint”. It’s all manual setup at first but once it working it works well. I haven’t had any issues with it since i got it working a few months ago.
I’m doing this with a python script and dzvents. I don’t have the programming skills to port this across to the python plugin. Anyone wanting to help it would be much appreciated.
Ill attach my script if anyone is interested in getting Neohub and Domoticz working.
Here’s how to get my solution working;
Copy my python script to the \Domoticz\scripts\python\NeoHubInterface.py folder
• Edit the script with your “Domoticz”:”Port” and “NeoHub IP” details
• Create a dummy hardware device, Call it Neohub for example
oThen create three devices from the NeoHub Hardware per Thermostat.
• Temperature
• Switch
• Thermostat Setpoint
• Now note down all 3 IDX numbers for the Thermostat; Switch, Thermostat & Setpoint
e.g Here is my naming for the devices
Dining Underfloor – Switch Device Name “IDX 1”
Dining Temperature – Temperature Device Name “IDX 2”
Dining - Thermostat Setpoint Name “This Name needs to match the Thermostat name in your neohub app” “IDX 3”

Copy my dzvents LUA script to the \Domoticz\scripts\lua\scripts\ Folder
• Edit the script with your IDX number for the Setpoint Device
• Create a new LUA script for each Thermostat
o The LUA script is a one to one. One script to one Thermostat
• If you have 5 Thermostats then you will have 5 LUA scrpits
Now create a schedule task ie. crontab –e
Add the following
* * * * * /usr/bin/python /home/pi/domoticz/scripts/python/NeoHub.py --StatName '< Thermostat Name>' --Mode http --TempIDX 2 --STempIDX 3 --SwitchIDX 1

This is where we require the 3 IDX numbers noted down earlier;
TempIDX = Temperature
STempIDX = Thermostat Setpoint
SwitchIDX = Switch


NeoHubInterface.py

Code: Select all

#!/usr/bin/python2.7

import json
import socket
import logging
import sys
import getopt
import time
import urllib2
from ConfigParser import SafeConfigParser
import os

#Domoticz IP Address and Port
domoticz = "<DomoticzIP>:<Port>"

#NeoHub IP Address and Port
host = "<NeoHub IP>"
port = "4242"

log = None

def initLogger(name):
    global log
    logging.basicConfig(filename=os.path.normpath(os.path.dirname(os.path.realpath(__file__)) + "/NeohubInterface" + name + ".log"), level=logging.ERROR, format="%(asctime)s [%(levelname)s] %(message)s")
    log = logging.getLogger(__name__)
    soh = logging.StreamHandler(sys.stdout)
    soh.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(message)s"))
    log.addHandler(soh)
    log.setLevel(logging.ERROR)

class HeatmiserNeostat:
    """ Represents a Heatmiser Neostat thermostat. """
    def __init__(self,  host, port, name):
        self._name = name
        self._host = host
        self._port = port
        self._operation = "Null"

    @property
    def standby(self):
        return self._standby

    @property
    def deviceResponse(self):
        return self._deviceResponse

    @property
    def should_poll(self):
        """ No polling needed for a demo thermostat. """
        return True

    @property
    def name(self):
        """ Returns the name. """
        return self._name

    @property
    def operation(self):
        """ Returns current operation. heat, cool idle """
        return self._operation

    @property
    def unit_of_measurement(self):
        """ Returns the unit of measurement. """
        return self._unit_of_measurement

    @property
    def current_temperature(self):
        """ Returns the current temperature. """
        return self._current_temperature

    @property
    def target_temperature(self):
        """ Returns the temperature we try to reach. """
        return self._target_temperature

    @property
    def is_away_mode_on(self):
        """ Returns if away mode is on. """
        return self._away

    def set_temperature(self, temperature):
        """ Set new target temperature. """
        response = self.json_request({"SET_TEMP": [int(temperature), self._name]})
        if response:
            log.info("set_temperature response: %s " % response)
            # Need check for sucsess here
            # {'result': 'temperature was set'}

    def turn_away_mode_on(self):
        """ Turns away mode on. """
        log.debug("Entered turn_away_mode_on for device: %s" % self._name)
        response = self.json_request({"AWAY_ON":self._name})
        if response:
            log.info("turn_away_mode_on request: %s " % response)
            # Need check for success here
            # {"result":"away on"}
            # {"error":"Could not complete away on"}
            # {"error":"Invalid argument to AWAY_OFF, should be a valid device array of valid devices"}

    def turn_away_mode_off(self):
        """ Turns away mode off. """
        log.info("Entered turn_away_mode_off for device: %s" % self._name)
        response = self.json_request({"AWAY_OFF":self._name})
        if response:
            log.info("turn_away_mode_off response: %s " % response)
            # Need check for success here
            # {"result":"away off"}
            # {"error":"Could not complete away off"}
            # {"error":"Invalid argument to AWAY_OFF, should be a valid device or


    def turn_frost_mode_off(self):
        """ Turns frost mode off. """
        log.info("Entered turn_away_mode_off for device: %s" % self._name)
        response = self.json_request({"FROST_OFF":self._name})
        if response:
            log.info("turn_frost_mode_off response: %s " % response)
            # Need check for success here
            # {"result":"frost off"}
            # {"error":"Could not complete frost off"}
            # {"error":"Invalid argument to FROST_OFF, should be a valid device or


    def turn_frost_mode_on(self):
        """ Turns frost mode om. """
        log.info("Entered turn_frost_mode_on for device: %s" % self._name)
        response = self.json_request({"FROST_ON":self._name})
        if response:
            log.info("turn_frost_mode_on response: %s " % response)
            # Need check for success here
            # {"result":"frost on"}
            # {"error":"Could not complete frost on"}
            # {"error":"Invalid argument to FROST_ON, should be a valid device or

    def update(self):
        """ Get Updated Info. """
        log.debug("Entered update(self)")
        response = self.json_request({"INFO": "0"})
        if response:
            # Add handling for multiple thermostats here
            log.debug("update() json response: %s " % response)

            counter = 0
            deviceResponse = ""

            log.debug("Neostats Found:")
            for devices in response['devices']:
                deviceName = devices['device']
                log.debug(str(counter) + ": " + deviceName)
                if (deviceName == self.name):
                    deviceResponse = devices
                counter += 1


            if (deviceResponse):
                #self._name = device['device']
                log.info("Neostat Found = " + self.name)
                tmptempfmt = deviceResponse["TEMPERATURE_FORMAT"]

                if (tmptempfmt is False) or (tmptempfmt.upper() == "C"):
                    self._unit_of_measurement = "TEMP_CELCIUS"
                else:
                    self._unit_of_measurement = "TEMP_FAHRENHEIT"

                log.debug("Temperature Format = " + self.unit_of_measurement)

                self.standby = deviceResponse["STANDBY"]
                self._away = deviceResponse['AWAY']
                self._target_temperature = round(float(deviceResponse["CURRENT_SET_TEMPERATURE"]), 2)
                self._current_temperature = round(float(deviceResponse["CURRENT_TEMPERATURE"]), 2)

                if (deviceResponse["HEATING"]):
                    self._operation = "Heating"
                elif (deviceResponse["COOLING"]):
                    self._operation = "Cooling"
                else:
                    self._operation = "Idle"

        return False

    def printThermostatStatus(self):
        log.info("Printing Thermostat Status")
        log.info("Name: " + self.name)
        log.info("Temperature Format: " + str(self.unit_of_measurement))
        log.info("Away: " + str(self.is_away_mode_on))
        log.info("Target Temperature: " + str(self.target_temperature))
        log.info("Current Temperature: " + str(self.current_temperature))
        log.info("Standby: " + str(self.standby))
        log.info("Operation: " + self.operation)

    def json_request(self, request=None, wait_for_response=False):
        """ Communicate with the json server. """
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(5)

        try:
            sock.connect((self._host, self._port))
        except OSError:
            log.error("Error connecting to Neohub")
            sock.close()
            return False

        if not request:
            # no communication needed, simple presence detection returns True
            sock.close()
            return True

        log.debug("json_request: %s " % request)

        sock.send(bytearray(json.dumps(request) + "\0\r", "utf-8"))
        try:
            buf = sock.recv(4096)
        except socket.timeout:
            # something is wrong, assume it's offline
            log.error("Timeout error")
            sock.close()
            return False

        # read until a newline or timeout
        buffering = True
        while buffering:
            if "\n" in str(buf):
                response = str(buf).split("\n")[0]
                buffering = False
            else:
                try:
                    more = sock.recv(4096)
                except socket.timeout:
                    more = None
                if not more:
                    buffering = False
                    response = str(buf)
                else:
                    buf += more

        sock.close()

        response = response.rstrip('\0')

        log.debug("json_response: %s " % response)

        return json.loads(response, strict=False)

def updateDomoticzHttp(domoticzUrl, switchidx, tempidx, stempidx, NeoStat, updateInterval):
    while True:
        NeoStat.update()
        time.sleep(updateInterval)
        try:
            url = domoticzUrl + "/json.htm?type=command&param=udevice&idx=" + str(tempidx) + "&nvalue=0&svalue=" + str(
                NeoStat.current_temperature)
            urllib2.urlopen(url)
            log.info("Temperature updated: " + url)
            url = domoticzUrl + "/json.htm?type=command&param=setsetpoint&idx=" + str(stempidx) + "&setpoint=" + str(
                NeoStat.target_temperature)
            urllib2.urlopen(url)
            log.info("SetTemperature updated: " + url)
	    switchUrl = domoticzUrl + "/json.htm?type=devices&rid=" + switchidx
            log.debug("SwitchURL: " + switchUrl)
            status = json.load(urllib2.urlopen(switchUrl))['result'][0]['Status']
            log.debug("Switch Status: " + status + " - NeoStat Status: " + NeoStat.operation)
            if status == "Off" and NeoStat.operation == "Heating":
                switchOnUrl = domoticzUrl + "/json.htm?type=command&param=switchlight&idx=" + str(switchidx) + "&switchcmd=On"
                log.info("Turn Switch " + str(switchidx) + " On")
                urllib2.urlopen(switchOnUrl)
            elif status == "On" and NeoStat.operation == "Idle":
                switchOffUrl = domoticzUrl + "/json.htm?type=command&param=switchlight&idx=" + str(
                    switchidx) + "&switchcmd=Off"
                log.info("Turn Switch " + str(switchidx) + " Off")
                urllib2.urlopen(switchOffUrl)
            if (updateInterval == 0):
                break
        except socket.timeout, e:
            log.error("Timeout error occurred retrieving NeoStat data...")
        except urllib2.HTTPError, e:
            log.error("HTTP error - " + e)
        except urllib2.URLError, e:
            log.error("URL Error - " + e)
        except:
            log.error("Error Occurred...")

def main(argv):
    try:
        opts, args = getopt.getopt(argv, "hsfix:", ["StatName=", "Mode=", "TempIDX=", "STempIDX=", "SwitchIDX="])
    except getopt.GetoptError:
        print("NeoHubInterface.py -h -s -f -i -x --StatName --Mode --TempIDX --STempIDX --SwitchIDX")
        sys.exit(1)

    updateMode = True
    updateInterval = 0

    for opt, arg in opts:
        if opt in '-h':
            print("NeoHubInterface.py -h -s -f -i -x --StatName --Mode --TempIDX --STempIDX --SwitchIDX")
            sys.exit(2)
        elif (opt == "--StatName"):
            statName = arg
        elif (opt == "--TempIDX"):
            tempidx = int(arg)
	elif (opt == "--STempIDX"):
            stempidx = int(arg)
        elif (opt == "--SwitchIDX"):
            switchidx = arg
        elif (opt == "--Mode"):
            mode = arg
        elif (opt == "-i"):
            updateInterval = float(arg)
        elif (opt == "-s"):
            updateMode = False
        elif (opt == "-x"):
            set = float(arg)

    initLogger(statName)

    log.info("NeoHub connection: " + host + ":" + str(port))

    NeoStat = HeatmiserNeostat(host, int(port), statName)

    if updateMode:
        if mode == "http":
            log.debug("HTTP Mode")
            updateDomoticzHttp(domoticz, switchidx, tempidx, stempidx, NeoStat, updateInterval)
    elif not updateMode:
       if (set):
           NeoStat.set_temperature(int(set))
    log.info("Exiting...")

main(sys.argv[1:])
neohub-Dining.lua

Code: Select all

return {
   active = true,
   on = {
      3  -- Setpoint Device IDX Number
   },
   data = {
      previousTemp = { initial = 20 }
   },
   execute = function(domoticz, device)
      local previousTemp = domoticz.data['previousTemp']
      local TargetTemp = device.setPoint;
      
      if (TargetTemp ~= previousTemp) then
         -- send the TargetTemp to NeoHub
	os.execute('python /home/pi/domoticz/scripts/python/NeoHubInterface.py -s -x '..TargetTemp..' --StatName '.."'"..device.name.."'")
     
      end
--	domoticz.log(device.name, domoticz_LOG_INFO)
--	domoticz.log(device.deviceType, domoticz_LOG_INFO)
--	domoticz.log(TargetTemp, domoticz_LOG_INFO)
--	domoticz.log(previousTemp, domoticz_LOG_INFO)
--	domoticz.log('python /home/pi/domoticz/scripts/python/NeoHubInterface.py -s -x  '..tonumber(TargetTemp)..' --StatName '..  "'"..device.name.."'")

       domoticz.data['previousTemp'] = TargetTemp
--     domoticz.logDevice(device)
   end
}

Re: Heatmiser NeoHub Integration

Posted: Sunday 09 July 2017 22:20
by Gambit
Has anyone else tried this and got working?
getting various errors

line 258
indentationError

spaced that in thinking might have been that but then get same further down, lines 300, 324
tried to adjust those but get errors of TypeErrors, 328, 322, 278

Re: Heatmiser NeoHub Integration

Posted: Monday 10 July 2017 8:49
by ddahya
Could you please try this script, its the one i use on my system. Not sure why the one posted in the first post isn't working. Just remember to update IP addresses for Domoticz server and Neohub IP in the script.

Also rename file extension from txt to py.

Re: Heatmiser NeoHub Integration

Posted: Tuesday 11 July 2017 20:17
by Gambit
That script worked for reading the temps/switch/setpoint but having some issues with setting the setpoint and on/off.
have added the index into the lua file and using same name as the stat in the neohub

just had to change the two IP address and it ran first time.

Done a file compare and this highlighted a difference between the two in the post and the same 4 lines I had issues with flagged up, looks to have been tabs vs spaces.
Slight naming difference between NeoHub vs NeoHubIterface in new vs old
Was a difference between the files in that the org script shows CR/LF and the new script only showed LF.

Brilliant work, now to add in the various zones.

Re: Heatmiser NeoHub Integration

Posted: Wednesday 12 July 2017 2:45
by ddahya
Hi Gambit,

Can you please check the name of the python script in the LUA file? in the original post my script was calling NeoHubInterface.py, my new posted script is called neohub.py. You might need to update you LUA file with the correct name.

Old
os.execute('python /home/pi/domoticz/scripts/python/NeoHubInterface.py -s -x '..TargetTemp..' --StatName '.."'"..device.name.."'")

new
os.execute('python /home/pi/domoticz/scripts/python/NeoHub.py -s -x '..TargetTemp..' --StatName '.."'"..device.name.."'")

What kind of issues are you having with the On/Off switch?

If you are running the latest beta, dzVents 2.0 is now integrated. you will need to make a small change to the LUA script to make it work with the integrated version.

Re: Heatmiser NeoHub Integration

Posted: Wednesday 12 July 2017 15:00
by Gambit
Noticed the slight changes when was setting up the zones and made suer everything in the scripts all pointed to the same name Neohub as well as the cron jobs.

If have this right the on/off is read only and not what I initially thought it was, was thinking something around Holiday mode on/off

This is a new installation, standard image and the quick installation Domoticz method, updated to the beta channel.

I have a NeoHW stat and noticed it doesn't seem to update the switch status in the same way the standard NeoStat and NeoUltra but need to check everything to make sure everything is correct. Odd that the HW stat doesn't display a Temp on its screen yet the script is pulling back Temp data and set point of around 12 until switched it on and its now up at 238 degrees, not sure if has a sensor attached someplace in the thermal store tank

Re: Heatmiser NeoHub Integration

Posted: Sunday 16 July 2017 23:53
by ddahya
Yes you are correct the on/off switch is read only, the switch is an indicator for what zone is currently on “heating”.

If you are using anything above beta 3.8023 you can use the integrated dzVents,
Just update the start of the LUA script with

return {
active = true,
on = {
devices = {
107 -- Thermostat IDX Number
}
},

Not sure about the NeoHW, I’ve only got NeoStat devices.

The script has the ability to set the “away mode” to on/off. You will just need to modify the bottom part of the script for this functionality.

would be really nice to turn this into a python plugin, if someone has the scripting skills to assist.

Re: Heatmiser NeoHub Integration

Posted: Saturday 24 February 2018 13:22
by nickgosling
Hi.

I have 10 thermostats in my house and stumbled across this forum post. Well done for making this work. I just wondered if you have found any other way of integration with domoticz. Creating 30 devices seems pretty time consuming and I'm not a genius at programming. I've got about 10 dzvents scripts running but they are pretty basic.

Nick.

Sent from my SM-G935F using Tapatalk


Re: Heatmiser NeoHub Integration

Posted: Thursday 01 March 2018 21:27
by Mrrodz
hi I'm getting

Traceback (most recent call last):
File "./NeoHub.py", line 328, in <module>
main(sys.argv[1:])
File "./NeoHub.py", line 322, in main
updateDomoticzHttp(domoticz, switchidx, tempidx, stempidx, NeoStat, updateInterval)
File "./NeoHub.py", line 251, in updateDomoticzHttp
NeoStat.current_temperature)
File "./NeoHub.py", line 70, in current_temperature
return self._current_temperature
AttributeError: HeatmiserNeostat instance has no attribute '_current_temperature'

when i try to run from command line. any suggestions ?

thanks

Re: Heatmiser NeoHub Integration

Posted: Friday 02 March 2018 10:27
by ddahya
I found somewhere that said Heatmiser have updated the NeoHub API. I had this issue awhile back but you seem to be the only other person that is having the same issue i had

Could you please try this
Edit Line 139
response = self.json_request({"INFO": "0"})

to
response = self.json_request({"INFO": 0})

Heatmiser NeoHub Integration

Posted: Friday 02 March 2018 19:46
by Mrrodz
Hi ddahya, that worked . Thank you. I have a first gen heatmiser hub. Maybe that has something to do with it.

So my temp and switch are updating. But my setpoint is not setting the stat. Any ideas?

Regards


If i run the command in the lua script from command line, adding in the temp and device name, it works. I cant tell if the lua script is running.

Regards

Sent from my iPhone using Tapatalk

Re: Heatmiser NeoHub Integration

Posted: Saturday 03 March 2018 16:46
by Mrrodz
Hi, I've got it working now. i had to add 127.0.0.1 to Local Networks (no username/password) in settings, Enable DzVents in Settings and put the lua scripts in domoticz/scripts/dvents/scripts/

its all working great now for 4 stats.


thank you

Re: Heatmiser NeoHub Integration

Posted: Saturday 28 April 2018 17:53
by Arkie
Now create a schedule task ie. crontab –e
Add the following
* * * * * /usr/bin/python /home/pi/domoticz/scripts/python/NeoHub.py --StatName '< Thermostat Name>' --Mode http --TempIDX 2 --STempIDX 3 --SwitchIDX 1

This is where we require the 3 IDX numbers noted down earlier;
TempIDX = Temperature
STempIDX = Thermostat Setpoint
SwitchIDX = Switch
I'm not sure how to do this, can you please explain me (i'm running domotiz on my synology)
regards

Re: Heatmiser NeoHub Integration

Posted: Sunday 29 April 2018 11:23
by Arkie
Just find the solution for change the crontab file.
But i still get the errors with the first script and the same in the second script.
same errors as Gambit
Has anyone else tried this and got working?
getting various errors

line 258
indentationError]
I've tried to put the scripts with putty, but i also tried to put the scripts in with domoticz events.
Can anyone tell in which map the domoticz events are stored? Can't find them with putty?

regards

Re: Heatmiser NeoHub Integration

Posted: Friday 01 June 2018 15:21
by hopalong
Hi.

Is it possible to set the Heating on / off switch so it can actually control the heating. I see from an earlier reply it is currently read only.

Thanks