Heatmiser PRT Wifi Thermostats - Rules, Sitemap, Scripts etc

Yes these are older thermostats, but I managed to get hold of one for about £20 or $28 and needed a new underfloor heating thermostat anyway. This setup is based on Rules and Scripts, works fine with Openhab 2.5.x and should work with Openhab 3 fine.

image

Its based off work people did 3+ years ago, but that was only partially completed/working, so Ive now fully completed it… and whilst these are older devices, ebay seems to have quite a few available and Ive done so much work, I thought I should share. I also believe there are other brands/makes of WiFi Thermostats that use this protocol/communication method (have a hunt on the Openhab forums for posts from around 2015-2018).

All these features work. There are some additional features I didn’t implement (Holiday scheduler etc), but those could be built off the work Ive done.

Im running this on Ubuntu, I would think you could convert it to Windows (its just the scripts you need to convert, not the rules/sitemap) and it looks something like the following:

Front Page


Settings

Scheduling

Feature wise this offers:

  • Change the current temp setting (it will change the temp back next time a scheduled change occurs).
  • Boost the temperature for a period of time.
  • Lock and unlock the Thermostats screen/buttons
  • Turn on holiday mode, which just keeps the thermostat set at Anti-Frost (disables the schedule)
  • Set the heating schedule for Monday-Friday and Saturday-Sunday (5/2 as its known).
  • Get the basic system info
  • Set the Anti-Frost temperature
  • The time on the thermostat will sync once a day with your server (at 3am).
  • The settings will be collected from the Thermostat every 3 minutes, but you can change that if needed in the Time Cron at the top of the Rules file.

To set this up, you will need to:

  1. Configure the IP address/username etc on your Heatmiser PRT thermostat.
  2. Follow this guide https://github.com/thoukydides/heatmiser-wifi/wiki/InstallationBasic from top to bottom (takes about 5 minutes and is copy/paste) NO NEED TO SETUP THE WEB INTERFACE ETC - just get as far as testing/running this line ~/heatmiser-wifi-read-only/bin/heatmiser.pl
  3. Once you’ve followed that guide you will need to edit your HOSTS file on your openhab server and put in a static entry called heatmiser and pointing to the IP address of your thermostat. Typically this file is /etc/hosts and an entry would look like 192.168.1.10 heatmiser
  4. Create a text file /etc/heatmiser.conf. In there, you will add your thermostat hostname and the PIN setup on the thermostat
nginx
HOST        heatmiser
PIN         1234
  1. VERY IMPORTANT NOTE. In the examples I will give below, the OpenHab server’s user account is called openhab. If you wish to or have used a different named user account, you will need to change the scripts/rules to point to the correct HOME folder location, by changing openhab to the name of the account you have used.

  2. Create a folder called openhabscripts in /home/openhab/heatmiser-wifi-read-only/ so that a full path to this folder would be /home/openhab/heatmiser-wifi-read-only/openhabscripts/

  3. In the openhabscripts folder you will create the following files:

hw_frostprotect.sh (contents below)

a='{"frostprotect":{"target":'
b='}}'
echo $a$1$b
/home/openhab/heatmiser-wifi-read-only/bin/heatmiser_json.pl $a$1$b

hw_hold.sh

a='{"heating":{"hold":'
b='}}'
echo $a$1$b
/home/openhab/heatmiser-wifi-read-only/bin/heatmiser_json.pl $a$1$b

hw_holidayaway.sh

/home/openhab/heatmiser-wifi-read-only/bin/heatmiser_json.pl '{"runmode":"frost"}'

hw_holidayhome.sh

/home/openhab/heatmiser-wifi-read-only/bin/heatmiser_json.pl '{"runmode":"heating"}'

hw_keylock.sh

/home/openhab/heatmiser-wifi-read-only/bin/heatmiser_json.pl {\"keylock\":$1}

hw_schedule.sh

a='{"comfort":[[{"target":'
b=',"time":"'
c=':'
d=':00"},{"target":'
f=':00"}],[{"target":'
g=':00"}]]}'
echo $a$1$b$2$c$3$d$4$b$5$c$6$d$7$b$8$c$9$d${10}$b${11}$c${12}$f${13}$b${14}$c${15}$d${16}$b${17}$c${18}$d${19}$b${20}$c${21}$d${22}$b${23}$c${24}$g   
/home/openhab/heatmiser-wifi-read-only/bin/heatmiser_json.pl $a$1$b$2$c$3$d$4$b$5$c$6$d$7$b$8$c$9$d${10}$b${11}$c${12}$f${13}$b${14}$c${15}$d${16}$b${17}$c${18}$d${19}$b${20}$c${21}$d${22}$b${23}$c${24}$g

hw_setback.sh

/home/openhab/heatmiser-wifi-read-only/bin/heatmiser_json.pl  {\"enabled\": $1 }

hw_settemp.sh

a='{"heating":{"target":'
b='}}'
echo $a$1$b
/home/openhab/heatmiser-wifi-read-only/bin/heatmiser_json.pl $a$1$b

hw_status.sh

/home/openhab/heatmiser-wifi-read-only/bin/heatmiser_json.pl

As mentioned, change the word openhab in those files to match your own users home folder location.

  1. For each of those files you have created, you will need to mark them as read/write/executable. So on Linux/Ubuntu you would, at a terminal window in the openhabscripts folder, perform a sudo chmod +rwx filename.sh (for each file)

  2. You will need to copy the following into your Openhab Items file:

//****************
//** Heatmeiser **
//****************
//Type	Item Name				Name on Sitemap						Icon Used		Group
Group	gSched					"Heatmiser Scheduling Group"
String  HW_TemperatureInternal	"Room/Floor Temperature [%s C]" 	<heatmiser>
String  HW_HeatingTarget 		"Set Temperature [%d C]" 			<settemp>
Switch 	HW_Keylock 				"Button/Screen Lock[]"				<screenlock>
String 	HW_KLS					"Button/Screen Lock[%s]"
		//Weekdays Schedule Variables
String 	HW_01_WWDTemp  			"WAKE - Temperature [%d C]"    		<settemp> 		(gSched)
String 	HW_02_WWDHour   		"WAKE - Hour [%d]"             		<time>    		(gSched)
String 	HW_03_WWDMinute 		"WAKE - Minute [%d]"           		<time>    		(gSched)
String 	HW_04_LWDTemp   		"LEAVE - Temperature [%d C]"   		<settemp> 		(gSched)
String 	HW_05_LWDHour   		"LEAVE - Hour [%d]"            		<time>    		(gSched)
String 	HW_06_LWDMinute 		"LEAVE - Minute [%d]"          		<time>    		(gSched)
String 	HW_07_RWDTemp   		"RETURN - Temperature [%d C]"  		<settemp> 		(gSched)
String 	HW_08_RWDHour   		"RETURN - Hour [%d]"          		<time>    		(gSched)
String 	HW_09_RWDMinute			"RETURN - Minute [%d]"        		<time>    		(gSched)
String 	HW_10_SWDTemp   		"SLEEP - Temperature [%d C]"  		<settemp> 		(gSched)
String 	HW_11_SWDHour   		"SLEEP - Hour [%d]"            		<time>    		(gSched)
String 	HW_12_SWDMinute 		"SLEEP - Minute [%d]"          		<time>    		(gSched)
		//Weekend Schedule Variables
String 	HW_13_WWETemp   		"Temperature [%d C]"           		<settemp> 		(gSched)
String 	HW_14_WWEHour   		"Hour [%d]"                    		<time>    		(gSched)
String 	HW_15_WWEMinute 		"Minute [%d]"                  		<time>    		(gSched)
String 	HW_16_LWETemp   		"Temperature [%d C]"           		<settemp> 		(gSched)
String 	HW_17_LWEHour   		"Hour [%d]"                    		<time>    		(gSched)
String 	HW_18_LWEMinute 		"Minute [%d]"                  		<time>    		(gSched)
String 	HW_19_RWETemp   		"Temperature [%d C]"           		<settemp> 		(gSched)
String 	HW_20_RWEHour   		"Hour [%d]"                    		<time>    		(gSched)
String 	HW_21_RWEMinute 		"Minute [%d]"                  		<time>    		(gSched)
String 	HW_22_SWETemp   		"Temperature [%d C]"           		<settemp> 		(gSched)
String 	HW_23_SWEHour   		"Hour [%d]"                    		<time>    		(gSched)
String 	HW_24_SWEMinute 		"Minute [%d]"                  		<time>    		(gSched)

Switch 	HW_SetHoliday 			"Away/Holiday Mode" 				<awaymode>
Number 	HW_TempHoldDuration		"Boost For " 						<time>
String  HW_TempHoldTemperature	"Boost Temperature [%d C]"			<fire>
String	HW_Heatingholdoff 		"Boost Remaining [%s]" 				<fire>

String	HW_FrostprotectEnabled  "Frost protection enabled [%s]" 	<snowflake>
String 	HW_FrostprotectTarget 	"Frost protection Temp [%d C]"		<snowflake>
String 	HW_ProductModel 		"Model [%s]" 						<heatmiser>
String 	HW_ProductVendor 	 	"Vendor [%s]"						<heatmiser>
String 	HW_ProductVersion 		"Firmware Version [%s]"				<heatmiser>
String 	HW_Progmode 			"Day Program Mode [%s]"				<heatmiser>
String 	HW_Sensors	 			"Sensors in use [%s]" 				<heatmiser> 
String 	HW_DegorF 				"Temp in C of F [%s]"				<heatmiser>

String 	HW_MF1					"WAKE - Set Temp/Time [%s]"			<calendar>
String	HW_MF2					"LEAVE - Set Temp/Time [%s]"		<calendar>
String 	HW_MF3					"RETURN - Set Temp/Time [%s]"		<calendar>
String 	HW_MF4					"SLEEP - Set Temp/Time [%s]"		<calendar>
String 	HW_WE1					"WAKE - Set Temp/Time [%s]"			<calendar>
String 	HW_WE2					"LEAVE - Set Temp/Time [%s]"		<calendar>
String 	HW_WE3					"RETURN - Set Temp/Time [%s]"		<calendar>
String 	HW_WE4					"SLEEP - Set Temp/Time [%s]"		<calendar>
  1. Copy the following into your Sitemap:
Frame label="Monday to Friday Schedule" {
					Text item=HW_MF1	valuecolor=["blue"]
					Setpoint item=HW_01_WWDTemp 	minValue=0 maxValue=50 step=1
					Selection item=HW_02_WWDHour 	mappings=[00="00", 01="01", 02="02", 03="03", 04="04", 05="05", 06="06", 07="07", 08="08", 09="09", 10="10", 11="11", 12="12", 13="13", 14="14", 15="15", 16="16", 17="17", 18="18", 19="19", 20="20", 21="21", 22="22", 23="23"]
					Selection item=HW_03_WWDMinute mappings=[00="00", 30="30"]
					Text item=HW_MF2	valuecolor=["blue"]
					Setpoint item=HW_04_LWDTemp 	minValue=0 maxValue=50 step=1
					Selection item=HW_05_LWDHour 	mappings=[00="00", 01="01", 02="02", 03="03", 04="04", 05="05", 06="06", 07="07", 08="08", 09="09", 10="10", 11="11", 12="12", 13="13", 14="14", 15="15", 16="16", 17="17", 18="18", 19="19", 20="20", 21="21", 22="22", 23="23"]
					Selection item=HW_06_LWDMinute mappings=[00="00", 30="30"]
					Text item=HW_MF3	valuecolor=["blue"]				
					Setpoint item=HW_07_RWDTemp 	minValue=0 maxValue=50 step=1
					Selection item=HW_08_RWDHour 	mappings=[00="00", 01="01", 02="02", 03="03", 04="04", 05="05", 06="06", 07="07", 08="08", 09="09", 10="10", 11="11", 12="12", 13="13", 14="14", 15="15", 16="16", 17="17", 18="18", 19="19", 20="20", 21="21", 22="22", 23="23"]
					Selection item=HW_09_RWDMinute	mappings=[00="00", 30="30"]
					Text item=HW_MF4	valuecolor=["blue"]
					Setpoint item=HW_10_SWDTemp 	minValue=0 maxValue=50 step=1
					Selection item=HW_11_SWDHour 	mappings=[00="00", 01="01", 02="02", 03="03", 04="04", 05="05", 06="06", 07="07", 08="08", 09="09", 10="10", 11="11", 12="12", 13="13", 14="14", 15="15", 16="16", 17="17", 18="18", 19="19", 20="20", 21="21", 22="22", 23="23"]
					Selection item=HW_12_SWDMinute mappings=[00="00", 30="30"]
					}
				Frame label="Weekend Schedule" {
					Text item=HW_WE1	valuecolor=["blue"]
					Setpoint item=HW_13_WWETemp 	minValue=0 maxValue=50 step=1
					Selection item=HW_14_WWEHour 	mappings=[00="00", 01="01", 02="02", 03="03", 04="04", 05="05", 06="06", 07="07", 08="08", 09="09", 10="10", 11="11", 12="12", 13="13", 14="14", 15="15", 16="16", 17="17", 18="18", 19="19", 20="20", 21="21", 22="22", 23="23"]
					Selection item=HW_15_WWEMinute mappings=[00="00", 30="30"]
					Text item=HW_WE2	valuecolor=["blue"]
					Setpoint item=HW_16_LWETemp 	minValue=0 maxValue=50 step=1
					Selection item=HW_17_LWEHour 	mappings=[00="00", 01="01", 02="02", 03="03", 04="04", 05="05", 06="06", 07="07", 08="08", 09="09", 10="10", 11="11", 12="12", 13="13", 14="14", 15="15", 16="16", 17="17", 18="18", 19="19", 20="20", 21="21", 22="22", 23="23"]
					Selection item=HW_18_LWEMinute mappings=[00="00", 30="30"]
					Text item=HW_WE3	valuecolor=["blue"]
					Setpoint item=HW_19_RWETemp 	minValue=0 maxValue=50 step=1
					Selection item=HW_20_RWEHour 	mappings=[00="00", 01="01", 02="02", 03="03", 04="04", 05="05", 06="06", 07="07", 08="08", 09="09", 10="10", 11="11", 12="12", 13="13", 14="14", 15="15", 16="16", 17="17", 18="18", 19="19", 20="20", 21="21", 22="22", 23="23"]
					Selection item=HW_21_RWEMinute mappings=[00="00", 30="30"]
					Text item=HW_WE4	valuecolor=["blue"]
					Setpoint item=HW_22_SWETemp 	minValue=0 maxValue=50 step=1
					Selection item=HW_23_SWEHour 	mappings=[00="00", 01="01", 02="02", 03="03", 04="04", 05="05", 06="06", 07="07", 08="08", 09="09", 10="10", 11="11", 12="12", 13="13", 14="14", 15="15", 16="16", 17="17", 18="18", 19="19", 20="20", 21="21", 22="22", 23="23"]
					Selection item=HW_24_SWEMinute mappings=[00="00", 30="30"]
				}

	}
	 
    Text label="System Info" icon="gears" {
		Frame label="System Info" {
			Text item=HW_ProductVendor
			Text item=HW_ProductModel                    
			Text item=HW_ProductVersion
		}		
		Frame {
			Text item=HW_Progmode
			Text item=HW_Sensors                     
			Text item=HW_DegorF
		}
		Frame {
			Text item=HW_FrostprotectEnabled
			Setpoint item=HW_FrostprotectTarget minValue=10 maxValue=20 step=1
		}
	}
	}		
  1. Copy the following into Rules file and if needed, change the word openhab for your correct user account’s home folder path.

NOTE: You may want to edit HW_TemperatureInternal.postUpdate(transform(“JSONPATH”, “$.heatmiser.temperature.internal”, HW_Status)) in the rules file, depending on what temp sensor you are using. internal will monitor the thermostats built in temp sensor, floor will monitor a floor heating sensor and remote will monitor a remote temp probe. Obviously you can build your own items/sitemap/rule to monitor 1 to all 3 as you need. The rule below is set to monitor the internal sensor.

import java.util.concurrent.locks.ReentrantLock
var String strCommand = ""
val ReentrantLock lock1 = new ReentrantLock

//****************************************
//*** Collect Data from the Thermostat ***
//****************************************
rule "Initialize Thermostat and schedule 2 min data collection"
	when 
		System started or
		Time cron "0 0/3 * * * ?"
	then{
	
    // Get Full State of thermostat periodically, every 5 minutes, and initially at system start
    var String HW_Status = executeCommandLine("/home/openhab/heatmiser-wifi-read-only/openhabscripts/hw_status.sh",3000)
   
    // Collect read only settings 
	HW_ProductModel.postUpdate(transform("JSONPATH", "$.heatmiser.product.model", HW_Status))
    HW_ProductVersion.postUpdate(transform("JSONPATH", "$.heatmiser.product.version", HW_Status))
    HW_ProductVendor.postUpdate(transform("JSONPATH", "$.heatmiser.product.vendor", HW_Status))
    HW_FrostprotectEnabled.postUpdate(transform("JSONPATH", "$.heatmiser.frostprotect.enabled", HW_Status))

    // Collect read/write settings
    HW_HeatingTarget.postUpdate(transform("JSONPATH", "$.heatmiser.heating.target", HW_Status))
    HW_TemperatureInternal.postUpdate(transform("JSONPATH", "$.heatmiser.temperature.internal", HW_Status))
    HW_Progmode.postUpdate(transform("JSONPATH", "$.heatmiser.config.progmode", HW_Status))
	HW_Sensors.postUpdate(transform("JSONPATH", "$.heatmiser.config.sensor", HW_Status))
	HW_DegorF.postUpdate(transform("JSONPATH", "$.heatmiser.config.units", HW_Status))
	HW_FrostprotectTarget.postUpdate(transform("JSONPATH", "$.heatmiser.frostprotect.target", HW_Status))
	HW_Heatingholdoff.postUpdate(transform("JSONPATH", "$.heatmiser.heating.hold", HW_Status))
	HW_KLS.postUpdate(transform("JSONPATH", "$.heatmiser.keylock", HW_Status))
 
	//Collect Schedule informaiton for the 5/2 setup (5=Mon-Fri and 2=Sat-Sun)
	//Weekdays - Wake time
    HW_01_WWDTemp.postUpdate(transform("JSONPATH", "$.heatmiser.comfort[0][0].target", HW_Status))
    HW_02_WWDHour.postUpdate(transform("JSONPATH", "$.heatmiser.comfort[0][0].time", HW_Status).substring(0,2)) 
    HW_03_WWDMinute.postUpdate(transform("JSONPATH", "$.heatmiser.comfort[0][0].time", HW_Status).substring(3,5)) 
	
	//Weekdays - Leave time
    HW_04_LWDTemp.postUpdate(transform("JSONPATH", "$.heatmiser.comfort[0][1].target", HW_Status))
    HW_05_LWDHour.postUpdate(transform("JSONPATH", "$.heatmiser.comfort[0][1].time", HW_Status).substring(0,2)) 
	HW_06_LWDMinute.postUpdate(transform("JSONPATH", "$.heatmiser.comfort[0][1].time", HW_Status).substring(3,5)) 

	//Weekdays - Return time
    HW_07_RWDTemp.postUpdate(transform("JSONPATH", "$.heatmiser.comfort[0][2].target", HW_Status))
    HW_08_RWDHour.postUpdate(transform("JSONPATH", "$.heatmiser.comfort[0][2].time", HW_Status).substring(0,2)) 
	HW_09_RWDMinute.postUpdate(transform("JSONPATH", "$.heatmiser.comfort[0][2].time", HW_Status).substring(3,5)) 

	//Weekdays - Sleep time
    HW_10_SWDTemp.postUpdate(transform("JSONPATH", "$.heatmiser.comfort[0][3].target", HW_Status))
    HW_11_SWDHour.postUpdate(transform("JSONPATH", "$.heatmiser.comfort[0][3].time", HW_Status).substring(0,2)) 
	HW_12_SWDMinute.postUpdate(transform("JSONPATH", "$.heatmiser.comfort[0][3].time", HW_Status).substring(3,5)) 

	//Weekends - Wake time
    HW_13_WWETemp.postUpdate(transform("JSONPATH", "$.heatmiser.comfort[1][0].target", HW_Status))
    HW_14_WWEHour.postUpdate(transform("JSONPATH", "$.heatmiser.comfort[1][0].time", HW_Status).substring(0,2)) 
	HW_15_WWEMinute.postUpdate(transform("JSONPATH", "$.heatmiser.comfort[1][0].time", HW_Status).substring(3,5)) 

	//Weekends - Leave time
    HW_16_LWETemp.postUpdate(transform("JSONPATH", "$.heatmiser.comfort[1][1].target", HW_Status))
    HW_17_LWEHour.postUpdate(transform("JSONPATH", "$.heatmiser.comfort[1][1].time", HW_Status).substring(0,2)) 
	HW_18_LWEMinute.postUpdate(transform("JSONPATH", "$.heatmiser.comfort[1][1].time", HW_Status).substring(3,5)) 

	//Weekends - Return time
    HW_19_RWETemp.postUpdate(transform("JSONPATH", "$.heatmiser.comfort[1][2].target", HW_Status))
    HW_20_RWEHour.postUpdate(transform("JSONPATH", "$.heatmiser.comfort[1][2].time", HW_Status).substring(0,2)) 
	HW_21_RWEMinute.postUpdate(transform("JSONPATH", "$.heatmiser.comfort[1][2].time", HW_Status).substring(3,5)) 

	//Weekends - Sleep time
    HW_22_SWETemp.postUpdate(transform("JSONPATH", "$.heatmiser.comfort[1][3].target", HW_Status))
    HW_23_SWEHour.postUpdate(transform("JSONPATH", "$.heatmiser.comfort[1][3].time", HW_Status).substring(0,2)) 
	HW_24_SWEMinute.postUpdate(transform("JSONPATH", "$.heatmiser.comfort[1][3].time", HW_Status).substring(3,5)) 
	}
end

//********************************************************
//*** Rules to post settings/changes to the Thermostat ***
//********************************************************

rule "Sync Thermostat time with Server time"
when 
	System started or
	Time cron "0 0 3 1/1 * ? *"
then
	Thread::sleep(300)
	{executeCommandLine("/home/openhab/heatmiser-wifi-read-only/bin/heatmiser_time.pl")}
end

rule "Set the temperature manually until next scheduled time change"
when
	Item HW_HeatingTarget changed 
then
	lock1.lock()
	try { executeCommandLine("/home/openhab/heatmiser-wifi-read-only/openhabscripts/hw_settemp.sh "+HW_HeatingTarget.state) Thread::sleep(400) } 
	finally  {lock1.unlock()}
end

rule "Lock Thermostat Screen Input"
when
    Item HW_Keylock received command 
then
	lock1.lock()
	try {
    val cmd = if(receivedCommand == ON) "1" else "0" {
    executeCommandLine("/home/openhab/heatmiser-wifi-read-only/openhabscripts/hw_keylock.sh " + cmd)
	}}
	finally {lock1.unlock()}
end

rule "Detect Keylock set at Pad"
when
    Item HW_KLS changed
then
	if (HW_KLS.state==('0')) {HW_Keylock.postUpdate(OFF)}
	if (HW_KLS.state==('1')) {HW_Keylock.postUpdate(ON)}
end

rule "Frost Protect set Temperature"
when
    Item HW_FrostprotectTarget received command 
then
	lock1.lock()
	try { executeCommandLine("/home/openhab/heatmiser-wifi-read-only/openhabscripts/hw_frostprotect.sh "+HW_FrostprotectTarget.state) Thread::sleep(400)} 
	finally  {lock1.unlock() }
end

rule "Turn on/off holiday Mode"
when
    Item HW_SetHoliday received command 
then
	lock1.lock()
	try { switch (receivedCommand) {
		case ON : { executeCommandLine("/home/openhab/heatmiser-wifi-read-only/openhabscripts/hw_holidayaway.sh") Thread::sleep(400)}
		case OFF : { executeCommandLine("/home/openhab/heatmiser-wifi-read-only/openhabscripts/hw_holidayhome.sh") Thread::sleep(400)}
	}} 
	finally {lock1.unlock()}
end

rule "Boost the temperature for X minutes to Boost Temp value"
when
    Item HW_TempHoldDuration received command
then
    if(!(receivedCommand instanceof Number)) return;
    lock1.lock()
    try { 
        if(receivedCommand > 0) {
            executeCommandLine("/home/openhab/heatmiser-wifi-read-only/openhabscripts/hw_hold.sh " + receivedCommand.toString)
            Thread::sleep(500)
			executeCommandLine=("/home/openhab/heatmiser-wifi-read-only/openhabscripts/hw_settemp.sh " + HW_TempHoldTemperature.state.toString)
        }
		else {executeCommandLine=("/home/openhab/heatmiser-wifi-read-only/openhabscripts/hw_hold.sh " + receivedCommand.toString)}
        HW_Heatingholdoff.postUpdate(receivedCommand.toString)
        Thread::sleep(300)
    } 
    finally  {lock1.unlock()}
end

rule "Turn off boost in Sitemap if boost is off"
when
    Item HW_Heatingholdoff changed to "0" 
then
	HW_TempHoldDuration.postUpdate("0")
end

rule "Update schedules and temps"
when
    Member of gSched changed
then
    lock1.lock()
    try { 
        strCommand = "/home/openhab/heatmiser-wifi-read-only/openhabscripts/hw_schedule.sh "
        gSched.members.sortBy[name].forEach[i | strCommand = strCommand + i.state.toString + " "]
        executeCommandLine(strCommand)
		HW_MF1.postUpdate(HW_01_WWDTemp.state + " Deg C at " + HW_02_WWDHour.state + ":" + HW_03_WWDMinute.state)
		HW_MF2.postUpdate(HW_04_LWDTemp.state + " Deg C at " + HW_05_LWDHour.state + ":" + HW_06_LWDMinute.state)
		HW_MF3.postUpdate(HW_07_RWDTemp.state + " Deg C at " + HW_08_RWDHour.state + ":" + HW_09_RWDMinute.state)
		HW_MF4.postUpdate(HW_10_SWDTemp.state + " Deg C at " + HW_11_SWDHour.state + ":" + HW_12_SWDMinute.state)
		HW_WE1.postUpdate(HW_13_WWETemp.state + " Deg C at " + HW_14_WWEHour.state + ":" + HW_15_WWEMinute.state)
		HW_WE2.postUpdate(HW_16_LWETemp.state + " Deg C at " + HW_17_LWEHour.state + ":" + HW_18_LWEMinute.state)
		HW_WE3.postUpdate(HW_19_RWETemp.state + " Deg C at " + HW_20_RWEHour.state + ":" + HW_21_RWEMinute.state)
		HW_WE4.postUpdate(HW_22_SWETemp.state + " Deg C at " + HW_23_SWEHour.state + ":" + HW_24_SWEMinute.state)
        Thread::sleep(400)
    } 
    finally  {lock1.unlock()}
end
  1. The only thing left to do now, will be to setup the icons you wish to use, by changing the names in your Items file… these are the ones from step 9 and the icon names are those in the <>'s

IMPORTANT NOTE: If your thermostat is factory settings, log into it and set something in the blank/empty “Weekend” schedule times/temp, otherwise you will get a bad output on the Openhab web interface.

image

The reason there are thread delays in the rules, is so that we arent firing too many requests at the same time to the thermostat… which ensures it gets each command processed correctly/in a timely manner.

If you wish to read up further or try to develop any other features in these, this work is based off these:

and this

Thanks for posting. Some comments on the Rules.

  • You do not need to import java.lang.String. Everything in java.lang is always available.

  • When you can hard code the name of the Item, it is best to use the method on the Item instead of the Action: e.g. HW_ProductModel.postUpdate(transform("JSONPATH".... There are additional checks performed and it is often more effecient.

  • Long running threads are a bad idea. Why the three second sleep in “Sync Thermostat time with Server time”? Use a Timer instead of the sleep.

  • I notice you have a sleep after all the calls to executeCommandLine. Is this to wait for the script to exit? If so, add a timeout to the call to executeCommandLine and the call will block until the script exits or the timeout time is reached. e.g. executeCommandLine=("/home/openhab/heatmiser-wifi-read-only/openhabscripts/hw_settemp.sh "+HW_HeatingTarget.state, 5000) will wait up to five seconds for the script to exit.

  • what’s with the =( after every executeCommandLine

  • rule “Lock Thermostat Screen Input” could be simplified to:

lock1.lock()
try {
    val cmd = if(receivedCommand == ON) "1" else "2"
    executeCommandLine("/home/openhab/heatmiser-wifi-read-only/openhabscripts/hw_keylock.sh " + cmd, 5000)
finally...
  • The Design Pattern: Gate Keeper would be really helpful here. It would let you eliminate the lock (locks are dangerous) and centralize the enforcement of the delays between calls to all the scripts. It would result in less brittle and simpler overall code in the long run.

Overall the code looks good. Thanks for posting!

1 Like

Hi Rich

Thanks for the comments, I value you taking a look over it!! :slight_smile: The code is a bastardization of someone elses work… to be honest I cant say I understand every single bit of it. The scripts I built from are actually pulled from the OpenHab addons link (but I cant edit there, hence posting here). I did strip quite a bit out…but kept much of it.

You do not need to import java.lang.String. Everything in java.lang is always available.
Pulled from the other persons work… will test without.

*When you can hard code the name of the Item, it is best to use the method on the Item instead of the Action: e.g. HW_ProductModel.postUpdate(transform("JSONPATH"... . There are additional checks performed and it is often more effecient.
Pulled from the other persons work … will test the change and nice to know!!

*Long running threads are a bad idea. Why the three second sleep in “Sync Thermostat time with Server time”? Use a Timer instead of the sleep.
Should have been 300 (my mistake), I will edit.

I notice you have a sleep after all the calls to executeCommandLine. Is this to wait for the script to exit? If so, add a timeout to the call to executeCommandLine and the call will block until the script exits or the timeout time is reached. e.g. executeCommandLine=("/home/openhab/heatmiser-wifi-read-only/openhabscripts/hw_settemp.sh "+HW_HeatingTarget.state, 5000) will wait up to five seconds for the script to exit.
Because its pulling in external scripts which do their own thing… I found that if the external scripts were processed too fast by the Openhab server, and whilst the scripts may work, the actual Thermostat isnt fast enough to keep up with the changes… So if you incremented say the temperature from 20 up to 30, through the Openhab sitemap… the script would send 21, 22, 23, 24, 25, 26, 27, 28, 29, 30 according to the logs… however, the Thermostat doent keep up with all the commands flooding in quickly…As such, you might end up with the temp set at 27, rather than 30. Its processor just cant keep up with a long queue of messages.

So I found a small delay between commands sent to the thermostat worked perfectly, though mind you the 300 I have used is an arbitrary number, but the rules have performed perfectly since I introduced it.

what’s with the =( after every executeCommandLine
The = sign? or both = and (? … for some reason, I thought that was the correct format after pulling someones code a long time ago and thats how Ive always done it. The Rule engine/Logs have never complained at me… What would you suggest?

rule “Lock Thermostat Screen Input” could be simplified to:
Ill take a look at that!

Thanks!

Why using String Items for numbers? e.g.:

Number HW_TempHoldDuration "Boost For [%s Min]" <time>

Use this Widget in sitemap:

Switch item=HW_TempHoldDuration mappings=[0="OFF", 60="1h", 120="2h", 180="3h", 300="5h"]

and this rule:

rule "Boost the temperature for X minutes to Boost Temp value"
when
    Item HW_TempHoldDuration received command
then
    if(!(receivedCommand instanceof Number)) return;
    lock1.lock()
    try { 
        if(receivedCommand > 0) {
            executeCommandLine("/home/openhab/heatmiser-wifi-read-only/openhabscripts/hw_settemp.sh " + HW_TempHoldTemperature.state.toString)
            Thread::sleep(500)
        }
        executeCommandLine=("/home/openhab/heatmiser-wifi-read-only/openhabscripts/hw_hold.sh " + receivedCommand.toString)
        HW_Heatingholdoff.postUpdate(receivedCommand.toString)
        Thread::sleep(300)
    } 
    finally  {lock1.unlock()}
end

Another thing: By using a different naming for some items, it’s possible to write much shorter rules. e.g.:
By changing these:

String HW_WWDTemp   "WAKE - Temperature [%d C]"    <settemp>
String HW_WWDHour   "WAKE - Hour [%d]"             <time>        
...

into this:

Group gSched
Number HW_01_WWDTemp   "WAKE - Temperature [%d C]"    <settemp> (gSched)
Number HW_02_WWDHour   "WAKE - Hour [%d]"             <time>    (gSched)
Number HW_03_WWDMinute "WAKE - Minute [%d]"           <time>    (gSched)
Number HW_04_LWDTemp   "LEAVE - Temperature [%d C]"   <settemp> (gSched)
Number HW_05_LWDHour   "LEAVE - Hour [%d]"            <time>    (gSched)
Number HW_06_LWDMinute "LEAVE - Minute [%d]"          <time>    (gSched)
Number HW_07_RWDTemp   "RETURN - Temperature [%d C]"  <settemp> (gSched)
Number HW_08_RWDHour   "RETURN - Hour [%d]"           <time>    (gSched)
Number HW_09_RWDMinute "RETURN - Minute [%d]"         <time>    (gSched)
Number HW_10_SWDTemp   "SLEEP - Temperature [%d C]"   <settemp> (gSched)
Number HW_11_SWDHour   "SLEEP - Hour [%d]"            <time>    (gSched)
Number HW_12_SWDMinute "SLEEP - Minute [%d]"          <time>    (gSched)

Number HW_13_WWETemp   "Temperature [%d C]"           <settemp> (gSched)
Number HW_14_WWEHour   "Hour [%d]"                    <time>    (gSched)
Number HW_15_WWEMinute "Minute [%d]"                  <time>    (gSched)
Number HW_16_LWETemp   "Temperature [%d C]"           <settemp> (gSched)
Number HW_17_LWEHour   "Hour [%d]"                    <time>    (gSched)
Number HW_18_LWEMinute "Minute [%d]"                  <time>    (gSched)
Number HW_19_RWETemp   "Temperature [%d C]"           <settemp> (gSched)
Number HW_20_RWEHour   "Hour [%d]"                    <time>    (gSched)
Number HW_21_RWEMinute "Minute [%d]"                  <time>    (gSched)
Number HW_22_SWETemp   "Temperature [%d C]"           <settemp> (gSched)
Number HW_23_SWEHour   "Hour [%d]"                    <time>    (gSched)
Number HW_24_SWEMinute "Minute [%d]"                  <time>    (gSched)

the rule is much cleaner:

// global vars are defined on top of file
var String strCommand = ""

rule "Update schedules and temps"
when
    Member of gSched changed
then
    lock1.lock()
    try { 
        strCommand = "/home/openhab/heatmiser-wifi-read-only/openhabscripts/hw_schedule.sh "
        gSched.members.sortBy[name].forEach[i | strCommand = strCommand + i.state.toString + " "]
        executeCommandLine(strCommand)
        Thread::sleep(400)
    } 
    finally  {lock1.unlock()}
end
1 Like

Hi Udo

Im not ignoring you… Im just working my way through some other changes and noticed one thing I missed (pulling the key lock information if set at the keypad its self).

Some of the things Im not sure I can push to a Number item, which I think is something to do with how they are getting transformed from JSON (maybe) Ive not gotten that far as to look at it fully and understand.

Though… once Ive worked my way through this, ill start looking through the bits you’ve sent and let you know how I get on!

Thanks :slight_smile:

@rlkoshak @Udo_Hartmann

Thanks both of you for your input on this!

Ive implemented the changes where possible and will be updating the initial post soon.

Udo, unfortunately I couldn’t use your last tidied up rule (changing Strings to Numbers). The reason being, the underlying scripts require all numbers to be in a 2 digit format e.g. 1 has to be sent to the script as 01. So when I tried to change everything to the script you sent, I got the following output:

…/hw_schedule.sh 27 7 30 16 9 30 27 17 30 16 23 30 27 7 30 16 9 30 27 17 30 18 23 30

Whereas it needs

…/hw_schedule.sh 27 07 30 16 09 30 27 17 30 16 23 30 27 07 30 16 09 30 27 17 30 18 23 30

(all the single digit numbers have a 0 before them).

I didnt write the script that hw_schedule.sh calls upon to parse the JSON format… but it wont accept single digit numbers.

If theres a way of doing that programmatically, then yes that rule you sent is definitely much tidier… but its not something I know how to do and may require a much longer rule writing anyway. You’re welcome to show me how to do it if you wish!

Again, thanks to you both for your input!!

Ah, I see… but you can enforce openHAB to use a specific number format by using String::format:
by changing this line:

        gSched.members.sortBy[name].forEach[i | strCommand = strCommand + i.state.toString + " "]

to this:

        gSched.members.sortBy[name].forEach[i | strCommand = strCommand + String::format("%02d ",i.state as Number)]

it should always use two digits and use a 0 as first digit, if the number is less than 10 :slight_smile:

Aah!! Very nice!! :slight_smile:

I kept the remainder of your suggestion and made those changes, though didnt move the Strings over to Numbers. Ill merge that in, test and amend the rules/items at the top of this post!!

Thanks so much!!

I’m not sure you understand my meaning on this one.

The reason the numbers are processed out of order is because the executeCommandLine without the timeout doesn’t wait for the script to actually finish running before returning. So in all likelihood what is actually happening is that a new call to executeCommandLine is starting and finishing before a previous call to it completed. If you add the timeout to the call to executeCommandLine, that call will block until the script returns which should eliminate the need for the Thread::sleeps, or allow you to greatly reduce the length of the sleeps. Given they are only in the 300 msec range, I’m all but certain that you can eliminate the sleeps entirely.

I’m surprised it works. It’s almost meaningless. The syntax for a function call is function_name(arg1, arg2). No = is involved unless the function returns a value you want to capture into a variable.

val results = executeCommandLine("echo hello world!", 5000)
logInfo("test", "The script returned " + results)