NuHeat WiFi Radiant Floor Controller

I would love to see ST integrate with NuHeat on these radiant floor controllers! Right now I am having to use a relay to trigger the away mode on my stat.

http://www.nuheat.com/floor-heating/signature/

That controller looks nice. I’m currently using a NuHeat Harmony controller for my heated floor. I’m curious, how have you wired the relay to trigger an away mode? What controller are you using? I’d like to get some level of integration to ST, perhaps a motion sensor to detect presence and only heat the floor if there’s activity vs the set timer schedule I have in place now.

I have a SunTouch FloorStat 500650. It has a contact closure “remote” input that mimics the vacation switch. So I have an LFM-20 wired in to that contact input. When the house goes into away or night mode, it triggers the stat to do the same.

REMOTE INPUT
The FloorStat is equipped with a remote input which allows connection
of a telephone controller or any other remote control system. When a
signal is received through this input, the FloorStat will automatically
switch from normal operating mode to Vacation mode ( ), or vice
versa. You can, for example, heat your country house from your office on
Friday, or your house from the airport, or from your car on your way back
from vacation.

If your Harmony doesnt have a relay input under the hood, test to see what the stat does when it loses power. Does it remember its schedule/settings? If so just wire in an Aeon Micro Switch that kills the power to the stat based on your conditions.

We are currently installing this system and would love to see ST integration. I’ll keep this thread updated once I get access to the http://www.mynuheat.com/ website. Hopefully, there will be some way (custom url calling or IFTTT) to connect the system.

1 Like

Has anyone made any progress on integrating the NuHeat Signature Wifi Thermostat into SmartThings? I have a spare Aeon Micro Switch that I will be adding into my ST setup, but would love to see full SmartThings integration.

Was wondering if anyone had success integrating with ST?

Thanks!

Likewise, I’d like to see the NuHeat controller integrated. The device uses their cloud service, which has Nest compatibility. I don’t use any Nest devices, but apparently the NuHeat controller is capable of using the Home/Away settings from Nest.

So there’s at least some kind of network communication going on. Anyone working on more than just Nest interfacing to it?

Just an FYI for you guys…

I have two of these devices. I have been in contact with the product owner and an open API should be released in the next 6 month, but I’m not holding my breathe.

I took a look at the website and attempted to use the api they are using based on the original nest device handler as a template.

@whoismoses Don’t want to upset your nuheat contact. let me know if they object to this usage.

Came up with a POC. Its read only though so limited value.

/**
 *  Nuheat
 *
 *  Author: ed  , based on work by dianoga7@3dgo.net
 *
 *
 * This is intended as a proof of concept only. There is no ability to change your set point - its read only.
 *
 * INSTALLATION
 * =========================================
 * 1) Create a new device type (https://graph.api.smartthings.com/ide/devices)
 *     Copy this into the code section
 * 
 * 2) Create a new device (https://graph.api.smartthings.com/device/list)
 *     Name: Your Choice
 *     Device Network Id: Your Choice
 *     Type: nuheat
 *     Location: Choose the correct location
 *     Hub/Group: Leave blank
 *
 * 3) Update device preferences
 *     Click on the new device to see the details.
 *     Click the edit button next to Preferences
 *     Fill in your information.
 *		ITS VERY BAD PRACTICE TO STORE A PASSWORD THIS WAY
 *
 *  
 * 4) Install pollster and configure it to poll the nuheat device you added
 * 5) That's it, you're done.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this
 * software and associated documentation files (the "Software"), to deal in the Software
 * without restriction, including without limitation the rights to use, copy, modify,
 * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to the following
 * conditions: The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
 * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

preferences {
	input("username", "text", title: "Username", description: "Your Nuheat email")
	input("password", "password", title: "Password", description: "Your Nuheat password")
}

metadata {
	definition (name: "nuheat", namespace: "ed", author: "ed") {
		capability "Polling"
		capability "Thermostat"
		capability "temperatureMeasurement"
	}

	simulator {
	}

	tiles {
		valueTile("temperature", "device.temperature", canChangeIcon: true, canChangeBackground:true) {
			state("temperature", label: '${currentValue}°', backgroundColors: [
				// Celsius Color Range
				[value: 0, color: "#153591"],
				[value: 7, color: "#1e9cbb"],
				[value: 15, color: "#90d2a7"],
				[value: 23, color: "#44b621"],
				[value: 29, color: "#f1d801"],
				[value: 33, color: "#d04e00"],
				[value: 36, color: "#bc2323"],
				// Fahrenheit Color Range
				[value: 40, color: "#153591"],
				[value: 44, color: "#1e9cbb"],
				[value: 59, color: "#90d2a7"],
				[value: 74, color: "#44b621"],
				[value: 84, color: "#f1d801"],
				[value: 92, color: "#d04e00"],
				[value: 96, color: "#bc2323"]
			]
			)
		}


		valueTile("heatingSetpoint", "device.heatingSetpoint") {
			state "default", label:'${currentValue}°', unit:"Heat", backgroundColor:"#bc2323"
		}

		standardTile("refresh", "device.thermostatMode", inactiveLabel: false, decoration: "flat") {
			state "default", action:"polling.poll", icon:"st.secondary.refresh"
		}
	
		main(["temperature", "heatingSetpoint", "refresh"])

	
		details(["temperature", "heatingSetpoint", "refresh"])
	
	}

}

// parse events into attributes
def parse(String description) {

}


def poll() {
	log.debug "Executing 'poll'"
	api('status', []) {
    	try {
     
        //log.debug "it.data.Groups[0].Thermostats[0].SerialNumber ${it.data.Groups[0].Thermostats[0].SerialNumber}"
        log.debug "it.data.Groups[0].Thermostats[0].Temperature ${it.data.Groups[0].Thermostats[0].Temperature }"
        
        sendEvent(name: "temperature" ,  value: ((double)it.data.Groups[0].Thermostats[0].Temperature / 100) ,  unit: "C" )
        sendEvent(name: 'heatingSetpoint', value: ((double)it.data.Groups[0].Thermostats[0].SetPointTemp / 100) , unit: "C" , state: "heat")
    	} catch (Throwable e) {
    		log.error e
        }

	}
}

def api(method, args = [], success = {}) {
	if(!isLoggedIn()) {
		log.debug "Need to login"
		login(method, args, success)
		return
	}

	def methods = [
		'status': [uri: "https://www.mynuheat.com/api/thermostats?sessionid=${data.sessionID}", type: 'get']
	]

	def request = methods.getAt(method)

	log.debug "Logged in"
	doRequest(request.uri, args, request.type, success)
}

// Need to be logged in before this is called. So don't call this. Call api.
def doRequest(uri, args, type, success) {
	log.debug "Calling $type : $uri args: $args type: $type"


	def params = [
		uri: uri,
		headers: [
		"Accept": "application/json, text/javascript, */*; q=0.01",
        "Accept-Encoding": "gzip, deflate, sdch, br",
        "Accept-Language": "en-US,en;q=0.8",
        "Cache-Control": "no-cache",
        "Connection": "keep-alive",
        "Content-Type":"application/json; charset=utf-8",
        "DNT":"1",
        "Pragma":"no-cache",
        "User-Agent": "unofficial Smartthings nuheat app v0.1"	
		],
		body: ""
	]

	def postRequest = { response ->
        
        response.headers.each {
           log.debug "header ${it.name} : ${it.value}"
        }
        
        log.debug "response.getStatus(): ${response.getStatus()}"

        
		if (response.getStatus() == 302) {
        	log.debug "redirecting!!!"
			def locations = response.getHeaders("Location")
			def location = locations[0].getValue()
			log.debug "redirecting to ${location}"
			doRequest(location, args, type, success)
		} else {
			success.call(response)
		}
	}

	try {
		if (type == 'post') {
			httpPostJson(params, postRequest)
		} else if (type == 'get') {
			httpGet(params, postRequest)
		}
	} catch (Throwable e) {
    	log.error e
		login() 
	}
}

def login(method = null, args = [], success = {}) {
	log.debug "login"
	def params = [
		uri: 'https://www.mynuheat.com/api/authenticate/user',
		body: [Email: settings.username , password: settings.password, application: 0]
	] 

	httpPost(params) {resp ->
        resp.headers.each {
           log.debug "header ${it.name} : ${it.value}"
        }
        log.debug "response contentType: ${resp.contentType}"
        log.debug "response data: ${resp.data}"
        log.debug "SessionID: ${resp.data["SessionId"]}"
        data.sessionID = resp.data["SessionId"];
	}
}

def isLoggedIn() {
	if(data.sessionID == null) {
		log.debug "No data.auth"
		return false
	}

	return true;
}

What is the URL for the api documentation?

I was not able to find any api documentation. I used google chrome developer tools (inspect element -> network tab) and was able to observe how the api was functioning on the website.

Clearly there is an api but I am guessing the docs are still private.

The iphone app can change the temperate. Therefore its likely there is another api end point for that. I just have not found it. If I could figure out how to change the temperature in chrome I could likely figure it out.

API wise, what I observed:
GET https://www.mynuheat.com/api/thermostats?sessionid=${data.sessionID}
returns json with the current state of the users system. Pretty much the kitchen sink, all the devices along with schedule and current state

POST https://www.mynuheat.com/api/authenticate/user
params [Email: settings.username , password: settings.password, application: 0]
returns the session id.

I don’t know why I did just do that.

https://www.mynuheat.com/api/thermostat?sessionid=xxxx&serialnumber=YYYYY

{SetPointTemp: “2721”, ScheduleMode: 2, HoldSetPointDateTime: “Wed, 12 Apr 2017 00:15:40 GMT”}

The SetPointTemp is not just the temp.

f(x) = ((x-33)*56)+33

I got it setting the heat point. I’ll start working on a full DH. Mind if I borrow some of your code you already wrote (with credit) unless you want to do it.

1 Like

Eric,

Code was adapted taken from the nest devices controller. Do as you will. Credit would be appreciated, make sure to credit the nest guys too!

Your code looks correct. I was looking at the JS and also the iphone app and came to a similar conclusion.

PUT https://www.mynuheat.com/api/thermostat?sessionid=xxxx&serialnumber=YYYYY
{SetPointTemp: “temp in c x 100”, ScheduleMode: 2, HoldSetPointDateTime: “time to resume schedule”}
not sure what ScheduleMode does but 2 appears to work :slight_smile:

Note: this code is hardcoded to set the temp to 30C until 5:30 today (as in when I posted this today).

/**
 *  Nuheat
 *
 *  Author: ed@tinycall.com , based on work by dianoga7@3dgo.net
 *
 *
 * This is intended as a proof of concept only. There is no ability to change your set point - its read only.
 *
 * INSTALLATION
 * =========================================
 * 1) Create a new device type (https://graph.api.smartthings.com/ide/devices)
 *     Copy this into the code section
 * 
 * 2) Create a new device (https://graph.api.smartthings.com/device/list)
 *     Name: Your Choice
 *     Device Network Id: Your Choice
 *     Type: nuheat
 *     Location: Choose the correct location
 *     Hub/Group: Leave blank
 *
 * 3) Update device preferences
 *     Click on the new device to see the details.
 *     Click the edit button next to Preferences
 *     Fill in your information.
 *		ITS VERY BAD PRACTICE TO STORE A PASSWORD THIS WAY
 *
 *  
 * 4) Install pollster and configure it to poll the nuheat device you added
 * 5) That's it, you're done.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this
 * software and associated documentation files (the "Software"), to deal in the Software
 * without restriction, including without limitation the rights to use, copy, modify,
 * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to the following
 * conditions: The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
 * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */



preferences {
	input("username", "text", title: "Username", description: "Your Nuheat email")
	input("password", "password", title: "Password", description: "Your Nuheat password")
}

metadata {
	definition (name: "nuheat", namespace: "ed", author: "ed") {
		capability "Polling"
		capability "Thermostat"
		capability "temperatureMeasurement"
	}

	simulator {
	}

	tiles {
		valueTile("temperature", "device.temperature", canChangeIcon: true, canChangeBackground:true) {
			state("temperature", label: '${currentValue}°', backgroundColors: [
				// Celsius Color Range
				[value: 0, color: "#153591"],
				[value: 7, color: "#1e9cbb"],
				[value: 15, color: "#90d2a7"],
				[value: 23, color: "#44b621"],
				[value: 29, color: "#f1d801"],
				[value: 33, color: "#d04e00"],
				[value: 36, color: "#bc2323"],
				// Fahrenheit Color Range
				[value: 40, color: "#153591"],
				[value: 44, color: "#1e9cbb"],
				[value: 59, color: "#90d2a7"],
				[value: 74, color: "#44b621"],
				[value: 84, color: "#f1d801"],
				[value: 92, color: "#d04e00"],
				[value: 96, color: "#bc2323"]
			]
			)
		}


		valueTile("heatingSetpoint", "device.heatingSetpoint") {
			state "default", label:'${currentValue}°', unit:"Heat", backgroundColor:"#bc2323"
		}

		standardTile("refresh", "device.thermostatMode", inactiveLabel: false, decoration: "flat") {
			state "default", action:"polling.poll", icon:"st.secondary.refresh"
		}
        
        standardTile("heatingSetpointUp", "device.heatingSetpoint", canChangeIcon: false, inactiveLabel: false, decoration: "flat") {
			state "heatingSetpointDown", label:'  ', action:"heatingSetpointDown", icon:"st.thermostat.thermostat-down", backgroundColor:"#bc2323"
		}
	
    	standardTile("heatingSetpointDown", "device.heatingSetpoint", canChangeIcon: false, inactiveLabel: false, decoration: "flat") {
			state "heatingSetpointDown", label:'  ', action:"heatingSetpointDown", icon:"st.thermostat.thermostat-down", backgroundColor:"#bc2323"
		}

	
    
		main(["temperature", "heatingSetpoint", "refresh", "heatingSetpointUp", "heatingSetpointDown"])

	
		details(["temperature", "heatingSetpoint", "refresh", "heatingSetpointDown"])
	
	}

}

// parse events into attributes
def parse(String description) {

}


def poll() {
	log.debug "Executing 'poll'"
	api('status', []) {
    	try {
     
        //log.debug "it.data.Groups[0].Thermostats[0].SerialNumber ${it.data.Groups[0].Thermostats[0].SerialNumber"
        log.debug "it.data.Groups[0].Thermostats[0].Temperature ${it.data.Groups[0].Thermostats[0].Temperature }, it.data.Groups[0].Thermostats[0].SerialNumber ${it.data.Groups[0].Thermostats[0].SerialNumber }"
        
        data.serialNumber = it.data.Groups[0].Thermostats[0].SerialNumber
        
        sendEvent(name: "temperature" ,  value: ((double)it.data.Groups[0].Thermostats[0].Temperature / 100) ,  unit: "C" )
        sendEvent(name: 'heatingSetpoint', value: ((double)it.data.Groups[0].Thermostats[0].SetPointTemp / 100) , unit: "C" , state: "heat")
    	} catch (Throwable e) {
    		log.error e
        }

	}
}

def api(method, args = [], success = {}) {
	if(!isLoggedIn()) {
		log.debug "Need to login"
		login(method, args, success)
		return
	}

	def methods = [
		'status': [uri: "https://www.mynuheat.com/api/thermostats?sessionid=${data.sessionID}", type: 'get'],
        'setTemp': [uri: "https://www.mynuheat.com/api/thermostat?sessionid=${data.sessionID}&serialnumber=${data.serialNumber}", type: 'put']
	]

	def request = methods.getAt(method)

	log.debug "Logged in"
	doRequest(request.uri, args, request.type, success)
}

// Need to be logged in before this is called. So don't call this. Call api.
def doRequest(uri, args, type, success) {
	log.debug "Calling $type : $uri args: $args type: $type"


	def params = [
		uri: uri,
		headers: [
		"Accept": "application/json, text/javascript, */*; q=0.01",
        "Accept-Encoding": "gzip, deflate, sdch, br",
        "Accept-Language": "en-US,en;q=0.8",
        "Cache-Control": "no-cache",
        "Connection": "keep-alive",
        "Content-Type":"application/json; charset=utf-8",
        "DNT":"1",
        "Pragma":"no-cache",
        "User-Agent": "unofficial Smartthings nuheat app v0.2"	
		],
		body: args
	]

	def postRequest = { response ->
        
        response.headers.each {
           log.debug "header ${it.name} : ${it.value}"
        }
        
        log.debug "response.getStatus(): ${response.getStatus()}"

        
		if (response.getStatus() == 302) {
        	log.debug "redirecting!!!"
			def locations = response.getHeaders("Location")
			def location = locations[0].getValue()
			log.debug "redirecting to ${location}"
			doRequest(location, args, type, success)
		} else {
			success.call(response)
		}
	}

	try {
		if (type == 'post') {
			httpPostJson(params, postRequest)
        } else if (type == 'put') {
			httpPutJson(params, postRequest)
		} else if (type == 'get') {
			httpGet(params, postRequest)
		}
	} catch (Throwable e) {
    	log.error e
		login() 
	}
}

def login(method = null, args = [], success = {}) {
	log.debug "login"
	def params = [
		uri: 'https://www.mynuheat.com/api/authenticate/user',
		body: [Email: settings.username , password: settings.password, application: 0]

    ] 

	httpPost(params) {resp ->
        resp.headers.each {
           log.debug "header ${it.name} : ${it.value}"
        }
        log.debug "response contentType: ${resp.contentType}"
        log.debug "response data: ${resp.data}"
        log.debug "SessionID: ${resp.data["SessionId"]}"
        data.sessionID = resp.data["SessionId"];
	}
    poll();
}

def isLoggedIn() {
	if(data.sessionID == null) {
		log.debug "No data.auth"
		return false
	}

	return true;
}

	
// handle commands
def setHeatingSetpoint(temp) {

	log.debug("Set heat to $temp");
	def latestThermostatMode = device.latestState('thermostatMode')
	def temperatureUnit = device.latestValue('temperatureUnit')
    def args = 
    [
    		SetPointTemp: 3000,
            ScheduleMode: 2,
        	HoldSetPointDateTime: "2017-04-13T11:30:00+00:00"
    ]

	api('setTemp', args) {
    	try {
     
        //log.debug "it.data.Groups[0].Thermostats[0].SerialNumber ${it.data.Groups[0].Thermostats[0].SerialNumber}"
        log.debug "returned with $it.data "
        
    	} catch (Throwable e) {
    		log.error e
        }

	}
    
    
	poll()
}
1 Like

I have the DH about 90% done, just need to add the logic to set how long to set the hold time instead of forever. Other than that everything else works.

1 - Resume Schedule
2 - Hold until…
3 - Hold forever.

1 Like

I noticed that the thermostat isn’t respecting daylight savings time when it comes to schedules.

Have you made any more progress with the DH for NuHeat? I’m looking at radiant floors for my basement.

This is great, been looking to integrate Nuheat for a long time, thanks for all your work. Trying to use this with webcore and have been unable to
get very far any advice on what can be done, such as setting temp, hold etc. Any help would be great.