Nest integration

Great work on a community Nest integration!

Just wanted to update everyone that we continue to make progress on our Official Nest integration. We are in internal testing and although I can’t give release dates, especially since a big part is out of our hands, but we hope to have something official / built in soon.

2 Likes

Working on it (it's just been a busy week). Home/away mode isn't quite as easy to support properly as eco/heat/cool since it's not a thermostat property, and there's a bit of delay between updating it and the Nest itself showing the update (so the driver does some polling). It'd be easier if we could use Nest's streaming updates. Ah well. :slight_smile:

Impatience FTW

1 Like

I totally get it … I also seem to recall something about Hubitat working on official support? idk, i’m hazy on it. Anyway, just wanted to mention my scenario in case it seemed out of left field, Spousal Acceptance Factor can make us to things we did not originally plan :slight_smile:

1 Like

I added a couple due to extra modes and needs to the thermostat:

private updateState(data) {
log.trace 'Updating state with ' + data
sendEvent(name: 'thermostatMode', value: data.hvac_mode)
sendEvent(name: 'humidity', value: data.humidity)
sendEvent(name: 'thermostatOperatingState', value: data.hvac_state)
sendEvent(name: 'temperature', value: data.ambient_temperature_f)
sendEvent(
name: 'thermostatFanMode',
value: data.fan_timer_active ? 'circulate' : 'auto'
)

sendEvent(name: 'sunblockEnabled', value: data.sunlight_correction_enabled)
sendEvent(name: 'sunblockActive', value: data.sunlight_correction_active)

if (data.hvac_mode == 'heat') {
sendEvent(name: 'heatingSetpoint', value: data.target_temperature_low_f)
} else if (data.hvac_mode == 'cool') {
sendEvent(name: 'coolingSetpoint', value: data.target_temperature_high_f)
} else if (data.hvac_mode == 'heat-cool') {
sendEvent(name: 'heatingSetpoint', value: data.target_temperature_low_f)
sendEvent(name: 'coolingSetpoint', value: data.target_temperature_high_f)
} else if (data.hvac_mode == 'eco') {
sendEvent(name: 'heatingSetpoint', value: data.eco_temperature_low_f)
sendEvent(name: 'coolingSetpoint', value: data.eco_temperature_high_f)
}
}

Since the lack of home/away mode in Thermostat seems a sticking point here, as well as with my Sinope thermostats, is there a possibility you could add a home/away mode as part of the standard Thermostat function? I wonder how many thermostat apis directly support a home/away mode.

Specifically, the updated drivers for Sinope’s newer zwave thermostat api refers to it as occupancy, with modes occupy and unoccupy. Seems odd terminology.

//-- Occupancy -------------------------------------------------------------------------------------------

def occupancySts() {
	["unoccupy", "occupy"]
}

def getOccupancyMap() {
	[
		"00": "unoccupy",
		"01": "occupy"
	]
}

def setOccupancyStatus() {
	traceEvent(settings.logFilter, "setOccupancyStatus>switching occupancy status", settings.trace)
	def currentStatus = device.currentState("occupancyStatus")?.value
	traceEvent(settings.logFilter, "setOccupancyStatus>Occupancy :$currentStatus", settings.trace)
	def statusOrder = occupancySts()
	def index = statusOrder.indexOf(currentStatus)
	traceEvent(settings.logFilter, "setOccupancyStatus>Index = $index", settings.trace)
	def next = (index >= 0 && index < (statusOrder.size() - 1)) ? statusOrder[index + 1] : statusOrder[0]
	traceEvent(settings.logFilter, "setOccupancyStatus>switching occupancy from $currentStatus to $next", settings.trace)
	"$next" ()
}
void away() {
	unoccupy()
}

void unoccupy() {
	traceEvent(settings.logFilter, "unoccupy>Set unoccupy", settings.trace)
	sendEvent(name: "occupancyStatus", value: "unoccupy", displayed: true)
	sendEvent(name: "presence", value: "non present", displayed: true)
	state?.previousOccupyTemp = device.currentValue("heatingSetpoint")
	def cmds = []
	cmds += zigbee.writeAttribute(0x0201, 0x0400, 0x30, 0x00, [mfgCode: 0x119C])
	cmds += zigbee.readAttribute(0x0201, 0x0002)
	cmds += zigbee.readAttribute(0x0201, 0x0014)
	sendZigbeeCommands(cmds)

	def scale = state?.scale
	def heatingSetpointRangeLow

	try {
		heatingSetpointRangeLow = device.latestValue("heatingSetpointRangeLow")
	} catch (any) {
		traceEvent(settings.logFilter, "unoccupy>not able to get heatingSetpointRangeLow ($heatingSetpointRangeLow),using default value",
			settings.trace, get_LOG_WARN())
	}
	heatingSetpointRangeLow = (heatingSetpointRangeLow) ?: (scale == 'C') ? 10.0 : 50
	sendEvent(name: "thermostatSetpoint", value: heatingSetpointRangeLow, unit: scale, displayed: true)
	sendEvent(name: "heatingSetpoint", value: heatingSetpointRangeLow, unit: scale, displayed: true)
}

void present() {
	occupy()
}
void occupy() {
	def scale = state?.scale
	traceEvent(settings.logFilter, "occupy>Set occupy", settings.trace)
	sendEvent(name: "occupancyStatus", value: "occupy", displayed: true)
	sendEvent(name: "presence", value: "present", displayed: true)
	def cmds = []
	cmds += zigbee.writeAttribute(0x0201, 0x0400, 0x30, 0x01, [mfgCode: 0x119C])
	cmds += zigbee.readAttribute(0x0201, 0x0002)
	cmds += zigbee.readAttribute(0x0201, 0x0012)
	sendZigbeeCommands(cmds)
	def temp = (state?.previousOccupyTemp) ? state ?.previousOccupyTemp : (scale == 'C') ? 20.0 : 70
	sendEvent(name: "thermostatSetpoint", value: temp, unit: scale, displayed: true)
	sendEvent(name: "heatingSetpoint", value: temp, unit: scale, displayed: true)
}

I think it really belongs as a separate presence device. Or at least the driver should use the presence capability.

I added the presence capability to the existing driver if others would like to use it. It will refresh along with the thermostat updates. It has two custom commands: away() and back() to send presence updates to nest.

/**
 * A simple Nest thermostat driver
 *
 * Author: Jason Cheatham
 * Date: 2018-04-08
 */

metadata {
	definition(name: 'Nest Thermostat', namespace: 'jason0x43', author: 'jason0x43') {
		capability 'Relative Humidity Measurement'
		capability 'Thermostat'
		capability 'Temperature Measurement'
		capability 'Sensor'
		capability 'Refresh'
        capability 'Presence Sensor'

		command 'eco'
		command 'sunblockOn'
		command 'sunblockOff'
        command 'back'
        command 'away'

		attribute 'sunblockEnabled', 'boolean'
		attribute 'sunblockActive', 'boolean'
	}

	preferences {	
		input(
			name: 'pollInterval',
			type: 'enum',
			title: 'Update interval (in minutes)',
			options: ['5', '10', '30'],
			required: true
		)	
	}
}

def auto() {
	log.debug 'auto()'
	nestPut([hvac_mode: 'heat-cool'])
	sendEvent(name: 'thermostatMode', value: 'heat-cool')
}

def eco() {
	log.debug 'eco()'
	nestPut([hvac_mode: 'eco'])
	sendEvent(name: 'thermostatMode', value: 'eco')
}

def sunblockOn() {
	log.debug 'sunblockOn()'
	nestPut([sunlight_correction_enabled: true])
	sendEvent(name: 'sunblockEnabled', value: true)
}

def sunblockOff() {
	log.debug 'sunblockOff()'
	nestPut([sunlight_correction_enabled: false])
	sendEvent(name: 'sunblockEnabled', value: false)
}

def cool() {
	log.debug 'cool()'
	nestPut([hvac_mode: 'cool'])
	sendEvent(name: 'thermostatMode', value: 'cool')
}

def emergencyHeat() {
	log.debug 'emergencyHeat is not implemented'
}

def fanAuto() {
	log.debug 'fanAuto()'
	nestPut([hvac_mode: 'heat'])
	sendEvent(name: 'thermostatMode', value: 'heat')
}

def fanCirculate() {
	log.debug 'fanCirculate()'
	nestPut([
		fan_timer_duration: 15,
		fan_timer_active: true
	])
	sendEvent(name: 'thermostatFanMode', value: 'circulate')
}

def fanOn() {
    fanCirculate()
}

def heat() {
	log.debug 'heat()'
	nestPut([hvac_mode: 'heat'])
	sendEvent(name: 'thermostatMode', value: 'heat')
}

def off() {
	log.debug 'off()'
	nestPut([hvac_mode: 'off'])
	sendEvent(name: 'thermostatMode', value: 'off')
}

def away() {
    nestStructurePut(['away' : 'away'])
    sendEvent(name: 'presence', value: 'not present')
}

def back(){
    nestStructurePut(['away' : 'home'])
    sendEvent(name: 'presence', value: 'present')
}

def setCoolingSetpoint(target) {
	log.debug "setCoolingSetpoint(${target})"
	setTargetTemp(target, 'cool')
}

def setHeatingSetpoint(target) {
	log.debug "setHeatingSetpoint(${target})"
	setTargetTemp(target, 'heat')
}

def setSchedule(schedule) {
	log.debug "setSchedule(${schedule})"
}

def setThermostatFanMode(mode) {
	log.debug "setThermostatFanMode(${mode})"
}

def setThermostatMode(mode) {
	log.debug "setThermostatMode(${mode})"
}

def updated() {
	log.debug 'Updated'

	log.trace('Unscheduling poll timer')
	unschedule()

	if (pollInterval == '5') {
		log.trace "Polling every 5 minutes"
		runEvery5Minutes(refresh)
	} else if (pollInterval == '10') {
		log.trace "Polling every 10 minutes"
		runEvery10Minutes(refresh)
	} else if (pollInterval == '30') {
		log.trace "Polling every 30 minutes"
		runEvery30Minutes(refresh)
	}
}

def parse(description) {
	log.debug 'Received event: ' + description
}

def refresh() {
	log.debug 'Refreshing'
	def id = getDataValue('nestId')
	def data = parent.nestGet("/devices/thermostats/${id}")
	updateState(data)
    refreshStructure()
}

def refreshStructure(){
    def structureId = getDataValue('structureId')
    if(structureId){
        def away = parent.nestGet("/structures/${structureId}/away")
        if(away == "home"){
			sendEvent(name: "presence", value : "present")
        }
        else {
			sendEvent(name: "presence", value: "not present")
        }
    }   
}

private nestPut(data) {
	def id = getDataValue('nestId')
	parent.nestPut("/devices/thermostats/${id}", data)
}

private nestStructurePut(data){
    def id = getDataValue('structureId')
    if(id){
        parent.nestPut("/structures/${id}", data)	    
    } 	    
}

private setTargetTemp(temp, heatOrCool) {
	def id = getDataValue('nestId')

	def mode = device.currentValue('thermostatMode')
	if (mode != heatOrCool && mode != 'heat-cool') {
		log.debug "Not ${heatOrCool}ing"
		return 
	}

	def value = temp.toInteger()
	nestPut([target_temperature_f: value])
	sendEvent(name: "${heatOrCool}ingSetpoint", value: value)
}

private updateState(data) {
	log.trace 'Updating state with ' + data
	sendEvent(name: 'thermostatMode', value: data.hvac_mode)
	sendEvent(name: 'humidity', value: data.humidity)
	sendEvent(
		name: 'thermostatFanMode',
		value: data.fan_timer_active ? 'circulate' : 'auto'
	)

	sendEvent(name: 'sunblockEnabled', value: data.sunlight_correction_enabled)
	sendEvent(name: 'sunblockActive', value: data.sunlight_correction_active)

	if (data.hvac_mode == 'heat') {
		sendEvent(name: 'heatingSetpoint', value: data.target_temperature_f)
	} else if (data.hvac_mode == 'cool') {
		sendEvent(name: 'coolingSetpoint', value: data.target_temperature_f)
	}
    if( (getDataValue('structureId') ?: '') == ''){
         updateDataValue('structureId', data.structure_id)
    }   
}
1 Like

That... actually makes a lot of sense. Does a presence sensor work if it's only externally toggled, though? The "home/away" mode is a manual setting for my thermostats, not automatic.

Not sure the question. A presence capability has away and present states. You can set it via an endpoint via an app or via device subscription.

Thanks for this, but I don't see how I can use this to change the home/away mode using RM unless I don't understand it? I see the command in driver, it works as desired, but I don't see a way to send the command from RM?

I updated my version of the driver to support home/away functionality.

The app is now tied to a specific structure, so when you update it, go into the Nest Integration app and choose a structure and thermostat.

The thermostat driver now has home and away commands. To use these will RM, you’ll first need to create custom commands for home and away in RM (RM -> Create Custom Commands). Choose the Thermostat capability, then your Nest thermostat, then add the command (home or away; you have to add them one at a time). Neither command takes any parameters.

2 Likes

I updated the app and driver code, set the structure and thermostat … refreshing does show accurate state info for “away” when I use Nest’s own app to change between home and away, but the buttons for home and away in the Hubitat device page don’t seem to do anything.

What does the log show for the Nest device when you try running home or away?

No entries relating to either home or away buttons that i can see.
Other functioning buttons do show events (thermostatMode, heatingSetpoint etc).

I’ll give the hub a reboot just in case.

Hmmm…and you updated both the driver and app?

That’s correct. When the hub comes back online i’ll double check the versions in the code.

Code appears up to date … although your source is still saying 4/8 for the date :wink:

I definitely had a Nest permissions issue, I had only allowed Away/Read. I updated it to Away/Read-Write, but still no joy.

I then did a Hubitat App logout, and reauthorized with new PIN … still no joy.

OK, it’s working now … I somewhat randomly discovered the Nest iOS app had notifications waiting for me to approve the Hubitat access … despite generating and using the new PIN. I don’t recall this occurring during the initial setup, but here we are, back in business at least.

And now that this is working with the IFTTT integration for geofencing, ActionTiles is officially the only reason I have a Samsung account anymore.
Today is a pretty good day :+1:

Thank you for this @jason0x43 !