Rheem EcoNet

Hey all!

Sorry for being absent in this thread.

Yeah, the ‘new’ econet API situation is a mess…and that’s putting it nicely. I unraveled it by wading through their code and watching requests – which is why supporting other device types would be hard for me.

I am more than happy to add additional committers to the GitHub repo, accept pull requests, or transfer responsibility.

As for adding HVAC support, the original code was reasonably modular, and I tried to keep most of that. Thus, it should be reasonable to add a new device handler to the existing codebase.

ah cool. I’m on a mac, I’ll have to find something similar.

I’m of two minds about integrating hvac into the existing waterheater app. On the one hand it seems like the right thing to do. On the other hand, the seeming differences in the way the API content-type parameters work for each device might make it needlessly complicated.

If we have to have unique methods for communicating with each type of device anyway, is there any advantage of putting it all together in one app?

Looks like it’s all application/json now. I don’t recall what the old API was doing.

Also, wow, that code is formatted really bad:(

hmm… well maybe that will work. I’ll have a look at it tonight. Can you add me as a commiter? bmcgair.

Guys I am using a rheem heat pump water heater and currently setting the temp set point. I have made small tweaks to the DH, but an interested in expanding it.

I am willing to help or test.

I’m thrilled to work on this but it doesn’t look like I’m going to be able to get to it with any seriousness for a week or 2 due to other real life commitments. Just FYI.

I think we basically have everything we need to get a working DH going, so its just a matter of doing it at this point. I don’t see any huge unknowns.

I can’t wait to retire my absurd HamBridge scripts that were remote controlling the “Fake” web browser on the mac, to modify my thermostat settings via the EcoNet website. Such a lame hack!

@Justin_Huff thanks for adding me to the repo!

Of course, you guys need to just do whatever works best for you, the coders, but as a water-heater-user, in the selfish interest of benefiting from any potential enhancements, corrections, modifications, etc to the water-heater code from the two projects being related, I’d like to see it all be kept within the same, current version of the app (i.e. where @Justin_Huff has it at this point)…to whatever degree that’s possible. :slight_smile:

Thanks for working on this, guys!

Hi everyone. Last year I tried creating a thread on the Rheem EcoNet for HVAC when we had our new furnace and AC unit installed, but really no luck back then. I came across this post (which I followed a while back when it was Copyninjas), and it looks like you guys are making progress outside of the water heater, on the HVAC system. I would love to be able to integrate the EcoNet with smarthings, thought am not much of a programmer. I would defiantly help where I can, including with testing. Our furnace is the fully modulating version, so the EcoNet was really our only choice for thermostat.

I cannot seem to find the code for the HVAC system (maybe not public?).

Let me know if there is any way that I can help/ provide testing feedback! Thanks!

Quick update:

I have dedicated the few hours I could so far and have some progress on the HVAC side;

I created a new smartapp. It’s probably best to have a shared smartapp but it was just quicker to create a new one. Somebody smarter than me can probably integrate the 2 smartapps. I have not had time to integrate with github so I will just post the code below for the smartapp.

I have been struggling more with the device handler. At this point I am hesitant to post the code as there is so much not working and the code contains a lot of stuff that is in some form of flux. At this point I have the following working:
-Display current temp and humidity in a multi attribute tile. But the android tile is broken and the tile in general is deficient in a few ways.
-Display heat/cool setpoints.
-Adjust heat/cool setpoints
-Display outside temp.
-Display alert status (but this is broken on the iOS app)
-Tile for displaying/setting current mode (cooling/heating/auto/etc.). But currently it is not able to change the mode.
-A couple of tile for fan mode and status that I have not decided how I want to deal with.

I have also disabled the setpoint buttons on the multi attribute tile. I felt that in auto mode there would be no good way to use a single set of up/down buttons so I opted for separate tiles to deal with cool/heat setpoints separately. I’m open to suggestions!

ToDo:
-Get the mode tile working
-Get the operating state working (this is where I have wasted all my time - I have a separate thread on the developers forum on this)
-Figure out how to display and update fan status (gimme your thoughts!)
-My HVAC is currently set to allow override of the schedule for 2 hrs. I probably need to also allow for smartthings to override this setting.
-Check min/max setpoints from the API when adjusting the setpoints. Don’t allow exceeding the min/max values.
-Probably more that I don’t remember now.
-I want to play with a generic multi attribute tile to see if I can avoid some of the bugs in the thermostat multi attribute tile.

SmartApp:

/**
 *  Rheem EcoNet (Connect)
 *
 *  Copyright 2017 Justin Huff
 *
 *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 *  in compliance with the License. You may obtain a copy of the License at:
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
 *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
 *  for the specific language governing permissions and limitations under the License.
 *
 *  Last Updated : 1/1/17
 *
 *  Based on https://github.com/copy-ninja/SmartThings_RheemEcoNet
 *
 */
definition(
    name: "Rheem EcoNet (Connect) HVAC",
    namespace: "jjhuff",
    author: "Justin Huff",
    description: "Connect to Rheem EcoNet HVAC",
    category: "SmartThings Labs",
    iconUrl: "http://smartthings.copyninja.net/icons/Rheem_EcoNet@1x.png",
    iconX2Url: "http://smartthings.copyninja.net/icons/Rheem_EcoNet@2x.png",
    iconX3Url: "http://smartthings.copyninja.net/icons/Rheem_EcoNet@3x.png")


preferences {
	page(name: "prefLogIn", title: "Rheem EcoNet")    
	page(name: "prefListDevice", title: "Rheem EcoNet")
}

/* Preferences */
def prefLogIn() {
	def showUninstall = username != null && password != null 
	return dynamicPage(name: "prefLogIn", title: "Connect to Rheem EcoNet", nextPage:"prefListDevice", uninstall:showUninstall, install: false) {
		section("Login Credentials"){
			input("username", "email", title: "Username", description: "Rheem EcoNet Email")
			input("password", "password", title: "Password", description: "Rheem EcoNet password (case sensitive)")
		} 
		section("Advanced Options"){
			input(name: "polling", title: "Server Polling (in Minutes)", type: "int", description: "in minutes", defaultValue: "5" )
		}
	}
}

def prefListDevice() {	
	if (login()) {
		def hvaclist = gethvaclist()
		if (hvaclist) {
			return dynamicPage(name: "prefListDevice",  title: "Devices", install:true, uninstall:true) {
				section("Select which HVAC to use"){
					input(name: "hvac", type: "enum", required:false, multiple:true, metadata:[values:hvaclist])
				}
			}
		} else {
			return dynamicPage(name: "prefListDevice",  title: "Error!", install:false, uninstall:true) {
				section(""){ paragraph "Could not find any devices"  }
			}
		}
	} else {
		return dynamicPage(name: "prefListDevice",  title: "Error!", install:false, uninstall:true) {
			section(""){ paragraph "The username or password you entered is incorrect. Try again. " }
		}  
	}
}


/* Initialization */
def installed() { initialize() }
def updated() { 
	unsubscribe()
	initialize() 
}
def uninstalled() {
	unschedule()
    unsubscribe()
	getAllChildDevices().each { deleteChildDevice(it) }
}	

def initialize() {
	// Set initial states
	state.polling = [ last: 0, rescheduler: now() ]  
	    
	// Create selected devices
	def hvaclist = gethvaclist()
    def selectedDevices = [] + getSelectedDevices("hvac")
    selectedDevices.each {
    	def dev = getChildDevice(it)
        def name  = hvaclist[it]
        if (dev == null) {
	        try {
    			addChildDevice("jjhuff", "Rheem Econet HVAC", it, null, ["name": "Rheem Econet: " + name])
    	    } catch (e)	{
				log.debug "addChildDevice Error: $e"
          	}
        }
    }
    
	// Remove unselected devices
	/*def deleteDevices = (selectedDevices) ? (getChildDevices().findAll { !selectedDevices.contains(it.deviceNetworkId) }) : getAllChildDevices()
	deleteDevices.each { deleteChildDevice(it.deviceNetworkId) } */
	
	//Subscribes to sunrise and sunset event to trigger refreshes
	subscribe(location, "sunrise", runRefresh)
	subscribe(location, "sunset", runRefresh)
	subscribe(location, "mode", runRefresh)
	subscribe(location, "sunriseTime", runRefresh)
	subscribe(location, "sunsetTime", runRefresh)
	    
	//Refresh devices
	runRefresh()
}

def getSelectedDevices( settingsName ) {
	def selectedDevices = []
	(!settings.get(settingsName))?:((settings.get(settingsName)?.getAt(0)?.size() > 1)  ? settings.get(settingsName)?.each { selectedDevices.add(it) } : selectedDevices.add(settings.get(settingsName)))
	return selectedDevices
}


/* Data Management */
// Listing all the HVAC Units you have in Rheem EcoNet
private gethvaclist() { 	 
	def deviceList = [:]
	apiGet("/locations", [] ) { response ->
    	if (response.status == 200) {
          	response.data.equipment[0].each { 
            	if (it.type.equals("HVAC")) {
                	deviceList["" + it.id]= it.name
                }
            }
        }
    }
    return deviceList
}

// Refresh data
def refresh() {
	if (!login()) {
    	return
    }
    
	log.info "Refreshing data..."
    // update last refresh
	state.polling?.last = now()

	// get all the children and send updates
	getAllChildDevices().each {
    	def id = it.deviceNetworkId
    	apiGet("/equipment/$id", [] ) { response ->
    		if (response.status == 200) {
            	log.debug "Got data: $response.data"
            	it.updateDeviceData(response.data)
            }
        }

    }
    
	//schedule the rescheduler to schedule refresh ;)
	if ((state.polling?.rescheduler?:0) + 2400000 < now()) {
		log.info "Scheduling Auto Rescheduler.."
		runEvery30Minutes(runRefresh)
		state.polling?.rescheduler = now()
	}
}

// Schedule refresh
def runRefresh(evt) {
	log.info "Last refresh was "  + ((now() - state.polling?.last?:0)/60000) + " minutes ago"
	// Reschedule if  didn't update for more than 5 minutes plus specified polling
	if ((((state.polling?.last?:0) + (((settings.polling?.toInteger()?:1>0)?:1) * 60000) + 300000) < now()) && canSchedule()) {
		log.info "Scheduling Auto Refresh.."
		schedule("* */" + ((settings.polling?.toInteger()?:1>0)?:1) + " * * * ?", refresh)
	}
    
	// Force Refresh NOWWW!!!!
	refresh()
    
	//Update rescheduler's last run
	if (!evt) state.polling?.rescheduler = now()
}

def setCoolSetPoint(childDevice, coolsetpoint) { 
	log.info "setDeviceSetPoint: $childDevice.deviceNetworkId $coolsetpoint" 
	if (login()) {
    	apiPut("/equipment/$childDevice.deviceNetworkId", [
        	body: [
                coolSetPoint: coolsetpoint,
            ]
        ])
    }
}
def setHeatSetPoint(childDevice, heatsetpoint) { 
	log.info "setDeviceSetPoint: $childDevice.deviceNetworkId $heatsetpoint" 
	if (login()) {
    	apiPut("/equipment/$childDevice.deviceNetworkId", [
        	body: [
                heatSetPoint: heatsetpoint,
            ]
        ])
    }
}
// available values are Heating, Cooling, Auto, Fan Only, Off, Emergency Heat
def setDeviceMode(childDevice, mode) {
	log.info "setDeviceMode: $childDevice.deviceNetworkId $mode" 
	if (login()) {
    	apiPut("/equipment/$childDevice.deviceNetworkId", [
        	body: [
                mode: mode,
            ]
        ])
    }
}
// available values are Auto, Low, Med.Lo, Medium, Med.Hi, High
def setFanMode(childDevice, fanMode) {
	log.info "setFanMode: $childDevice.deviceNetworkId $fanMode" 
	if (login()) {
    	apiPut("/equipment/$childDevice.deviceNetworkId", [
        	body: [
                fanMode: fanmode,
            ]
        ])
    }
}

private login() {
	def apiParams = [
    	uri: getApiURL(),
        path: "/auth/token",
        headers: ["Authorization": "Basic Y29tLnJoZWVtLmVjb25ldF9hcGk6c3RhYmxla2VybmVs"],
        requestContentType: "application/x-www-form-urlencoded",
        body: [
        	username: settings.username,
        	password: settings.password,
        	"grant_type": "password"
        ],
    ]
    if (state.session?.expiration < now()) {
    	try {
			httpPost(apiParams) { response -> 
            	if (response.status == 200) {
                	log.debug "Login good!"
                	state.session = [ 
                    	accessToken: response.data.access_token,
                    	refreshToken: response.data.refresh_token,
                    	expiration: now() + 150000
                	]
                	return true
            	} else {
                	return false
            	} 	
        	}
		}	catch (e)	{
			log.debug "API Error: $e"
        	return false
		}
	} else { 
    	// TODO: do a refresh 
		return true
	}
}

/* API Management */
// HTTP GET call
private apiGet(apiPath, apiParams = [], callback = {}) {	
	// set up parameters
	apiParams = [ 
		uri: getApiURL(),
		path: apiPath,
        headers: ["Authorization": getApiAuth()],
        requestContentType: "application/json",
	] + apiParams
	log.debug "GET: $apiParams"
	try {
		httpGet(apiParams) { response -> 
        	callback(response)
        }
	}	catch (e)	{
		log.debug "API Error: $e"
	}
}

// HTTP PUT call
private apiPut(apiPath, apiParams = [], callback = {}) {	
	// set up parameters
	apiParams = [ 
		uri: getApiURL(),
		path: apiPath,
        headers: ["Authorization": getApiAuth()],
        requestContentType: "application/json",
	] + apiParams
	
	try {
		httpPut(apiParams) { response -> 
        	callback(response)
        }
	}	catch (e)	{
		log.debug "API Error: $e"
	}
}

private getApiURL() { 
	return "https://econet-api.rheemcert.com"
}
    
private getApiAuth() {
	return "Bearer " + state.session?.accessToken
}

Device Handler (no guarantees! and very much in flux)

/**
 *  Rheem Econet HVAC
 *
 *  Copyright 2017 Justin Huff
 *
 *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 *  in compliance with the License. You may obtain a copy of the License at:
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
 *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
 *  for the specific language governing permissions and limitations under the License.
 *
 *  Last Updated : 2017-01-04
 *
 *  Based on https://github.com/copy-ninja/SmartThings_RheemEcoNet
 */
metadata {
	definition (name: "Rheem Econet HVAC", namespace: "jjhuff", author: "Justin Huff") {
		capability "Actuator"
		capability "Thermostat"
        capability "Sensor"
		capability "Refresh"
		capability "Relative Humidity Measurement"
		capability "Temperature Measurement"
        capability "Thermostat Cooling Setpoint"
		capability "Thermostat Fan Mode"
		capability "Thermostat Heating Setpoint"
		capability "Thermostat Mode"
		capability "Thermostat Operating State"
		capability "Thermostat Setpoint"
		
		command "heatLevelUp"
		command "heatLevelDown"
		command "coolLevelUp"
		command "coolLevelDown"
		command "updateDeviceData", ["string"]
	}

	simulator { }

	tiles(scale: 2) {      
              
		multiAttributeTile(name:"tempSummary", type:"thermostat", width:6, height:4) {
			tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
				attributeState("default", label:'${currentValue}°'/*, unit:"dF", defaultState: true*/
							  )
			}

			/*tileAttribute("device.temperature", key: "VALUE_CONTROL") {
                attributeState("default", action: "setTemperature")
			}*/
            tileAttribute("device.humidity", key: "SECONDARY_CONTROL") {
				attributeState("default", label:'${currentValue}%', unit:"%")
			}

			tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE") {
				attributeState("idle", backgroundColor:"#44b621")
				attributeState("heating", backgroundColor:"#ffa81e")
				attributeState("cooling", backgroundColor:"#269bd2")
			}
			tileAttribute("device.thermostatMode", key: "THERMOSTAT_MODE") {
				attributeState("Off", label:'${name}')
				attributeState("Heating", label:'${name}')
				attributeState("Cooling", label:'${name}')
                attributeState("Auto", label:'${name}')
				attributeState("Fan Only", label:'${name}')
				attributeState("Emergency Heat", label:'${name}')
			}
            tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT") {
            	attributeState("default", label:'${currentValue}', unit:"dF")
            }
			tileAttribute("device.coolingSetpoint", key: "COOLING_SETPOINT") {
				attributeState("default", label:'${currentValue}', unit:"dF")
			}

        } // End multiAttributeTile
	
	
		valueTile("heatingSetpoint", "device.heatingSetpoint", inactiveLabel: false, width: 2, height: 2) {
			state("heatingSetpoint", label:'${currentValue}°', backgroundColor:"#d04e00"
			)
		}
        
        valueTile("coolingSetpoint", "device.coolingSetpoint", inactiveLabel: false, width: 2, height: 2) {
			state("coolingSetpoint", label:'${currentValue}°', backgroundColor:"#1e9cbb"
			)
		}
        
		standardTile("heatLevelUp", "device.switch", canChangeIcon: false, decoration: "flat" ) {
			state("heatLevelUp",   action:"heatLevelUp",  label:"Heat", icon:"st.thermostat.thermostat-up")
		}  
		standardTile("heatLevelDown", "device.switch", canChangeIcon: false, decoration: "flat") {
			state("heatLevelDown", action:"heatLevelDown", label:"Heat",icon:"st.thermostat.thermostat-down")
		}
		
		standardTile("coolLevelUp", "device.switch", canChangeIcon: false, decoration: "flat" ) {
			state("coolLevelUp",   action:"coolLevelUp",  label:"Cool", icon:"st.thermostat.thermostat-up")
		}  
		standardTile("coolLevelDown", "device.switch", canChangeIcon: false, decoration: "flat") {
			state("coolLevelDown", action:"coolLevelDown", label:"Cool", icon:"st.thermostat.thermostat-down")
		}

		standardTile("switch", "device.switch", canChangeIcon: false, decoration: "flat" ) {
       		state "on", label: 'On', action: "switch.off",
          		icon: "st.switches.switch.on", backgroundColor: "#79b821"
       		state("off", label: 'Off', action: "switch.on",
          		icon: "st.switches.switch.off", backgroundColor: "#ffffff")
		}
        
		standardTile("refresh", "device.switch", decoration: "flat") {
			state("default", action:"refresh.refresh",        icon:"st.secondary.refresh")
		}
		
		standardTile("mode", "device.thermostatMode", inactiveLabel:true, decoration: "flat", width: 2, height: 2) {
            //state "default", label:'[Mode]'
            state "Off", label:'', icon:"st.thermostat.heating-cooling-off", backgroundColor:"#FFFFFF", action:"thermostat.Heating", nextState: "updating"
            state "Heating", label:'', icon:"st.thermostat.heat", backgroundColor:"#FFCC99", action:"thermostat.Cooling", nextState: "updating"
            state "Cooling", label:'', icon:"st.thermostat.cool", backgroundColor:"#99CCFF", action:"thermostat.Auto", nextState: "updating"
            state "Auto", label:'', icon:"st.thermostat.auto", backgroundColor:"#99FF99", action:"thermostat.Fan Only", nextState: "updating"
			state "Fan Only", label:'', icon:"st.thermostat.fan-on", backgroundColor:"#99FF99", action:"thermostat.Off", nextState: "updating"
			state "Emergency Heat", label:'', icon:"st.thermostat.emergency-heat", backgroundColor:"#FFCC99", action:"thermostat.Off"
			state "updating", label:"Working", icon: "st.secondary.secondary"
        }
		
		standardTile("fanMode", "device.thermostatFanMode", inactiveLabel: false, decoration: "flat") {
			state "Auto", icon: "st.thermostat.fan-auto", action:"thermostat.Low" 
			state "Low", label:'Low', icon: "st.thermostat.fan-on", action:"thermostat.Medium" 
			state "Med.Lo", label:'M-Low', icon: "st.thermostat.fan-on", action:"thermostat.Medium" 
			state "Medium", label:'Med', icon: "st.thermostat.fan-on", action:"thermostat.High" 
			state "Med.Hi", label:'M-High', icon: "st.thermostat.fan-on", action:"thermostat.High" 
			state "High", label:'High', icon: "st.thermostat.fan-on", action:"thermostat.Auto" 
		}
		
		valueTile("fanSpeed", "device.fanSpeed", inactiveLabel: false, decoration: "flat") {
			state("Off", label:'${currentValue}', icon: "st.thermostat.fan-off"
			)
		}
		
		valueTile("outdoorTemperature", "device.outdoorTemperature", inactiveLabel: false, width: 2, height: 1) {
			state("outdoorTemperature", label:'Outside: ${currentValue}°'
			)
		}
		
		valueTile("alert", "device.alert",  decoration: "flat", canChangeIcon: true) {
			state "true", label: 'Alert!', icon: "st.alarm.water.wet"
			state "false", label: 'No Alert', icon: "st.alarm.water.dry"
		}
		
		standardTile("hiddenIconTile", "device.temperature", decoration: "flat", width: 2, height: 2, canChangeIcon: true, canChangeBackground: true) {
 		state "default", label:'${currentValue}°', icon: "st.Weather.weather2", backgroundColor: "#79B821"
 		}
		
		standardTile("operatingState", "device.thermostatOperatingState", inactiveLabel:false, decoration:"flat") {
            state "idle", label:'${currentValue}'
            state "heating", label:'${currentValue}'
            state "cooling", label:'${currentValue}'
        }
        
		main (["hiddenIconTile"])
		details(["tempSummary", "coolLevelDown", "coolLevelUp", "outdoorTemperature", "heatLevelDown", "heatLevelUp",
		"coolingSetpoint", "mode", "heatingSetpoint",    "alert", "fanMode", "fanSpeed", "refresh","operatingState"
		])
	}
}

def parse(String description) { }

def refresh() {
	log.debug "refresh"
	parent.refresh()
}

def fanAuto() {
    sendEvent(name: "thermostatFanMode", value: "Auto")
}
def Low() {
    sendEvent(name: "thermostatFanMode", value: "Low")
}

def setHeatingSetpoint(Number heatSetPoint) {
   	sendEvent(name: "heatingSetpoint", value: heatSetPoint, unit: "F")
	parent.setHeatSetPoint(this.device, heatSetPoint)
}

def setCoolingSetpoint(Number coolSetPoint) {
   	sendEvent(name: "coolingSetpoint", value: coolSetPoint, unit: "F")
	parent.setCoolSetPoint(this.device, coolSetPoint)
}

def heatLevelUp() { 
	def heatSetPoint = device.currentValue("heatingSetpoint")
    heatSetPoint = heatSetPoint + 1
	setHeatingSetpoint(heatSetPoint)
}	

def heatLevelDown() { 
	def heatSetPoint = device.currentValue("heatingSetpoint")
    heatSetPoint = heatSetPoint - 1
    setHeatingSetpoint(heatSetPoint)
}

def coolLevelUp() { 
	def coolSetPoint = device.currentValue("coolingSetpoint")
    coolSetPoint = coolSetPoint + 1
	setCoolingSetpoint(coolSetPoint)
}	

def coolLevelDown() { 
	def coolSetPoint = device.currentValue("coolingSetpoint")
    coolSetPoint = coolSetPoint - 1
    setCoolingSetpoint(coolSetPoint)
}

def getThermostatOperatingState() {
	def active = device.currentValue("inUse")
	def modes = device.currentValue("thermostatMode")
	if (active == "false") {
				sendEvent(name: 'thermostatOperatingState', value: "idle")
							}
	else if (modes == "Cooling") {
				sendEvent(name: 'thermostatOperatingState', value: "cooling")
							}
	else if (modes == "Heating") {
				sendEvent(name: 'thermostatOperatingState', value: "heating")
							}
}

def updateDeviceData(data) {
	sendEvent(name: "heatingSetpoint", value: data.heatSetPoint, unit: "F")
	sendEvent(name: "coolingSetpoint", value: data.coolSetPoint, unit: "F")
    sendEvent(name: "switch", value: data.isEnabled ? "on" : "off")
	sendEvent(name: "humidity", value: (String.format("%3.0f",data.indoorHumidityPercentage)), unit: "%")
	sendEvent(name: "temperature", value: data.indoorTemperature, unit: "F")
	sendEvent(name: "outdoorTemperature", value: data.outdoorTemperature, unit: "F")
	sendEvent(name: "thermostatMode", value: data.mode)
	sendEvent(name: "thermostatFanMode", value: data.fanMode)
	sendEvent(name: "fanSpeed", value: data.fanSpeed)
	sendEvent(name: "inUse", value: data.inUse)
	sendEvent(name: "alert", value: data.hasCriticalAlert)
	
	
}

Awesome, thanks for posting this. I just installed now and will play around with it a bit. I’ll let you know any feedback from a performance, usage, etc. perspective.

have you figured anything out with the vacation mode?
I have a water heater and I have not been able to set it to vacation mode…
The lowest setpoint it will allow is 110 degrees F. I would like to basically shut it off when gone.

Hi fstr-

Hmm… very cool. I was planning on working on this shortly too. I’m glad you made some progress.

I’m having a problem however. I don’t get anything but a blank page back from the sign in when adding the smart app. I’ve gotten further than that before, so I’m not sure whats up yet. My credentials are correct and are working through postman.

The ver. of the smartapp posted above is getting hvac equipment for you?

Do you have the code in github somewhere?

UPDATE: welp. nevermind. I went through the code and found I didn’t grab the last line when cutting/pasting out of the chat window. Github would really help!

Not sure why…are you on iPhone or Android?
I am now testing on Android phone and iPad as I am finding many differences.
The DH I posted previously is crap, I just didn’t want anybody to reinvent the wheel. I developed it by just reverse engineering. Once I hit the wall I had to go look for info. Since then I have figured out most things. Below I will post a new DH and a new smart app (one 1 minor change to the smart app to update settings endpoint).

I was hoping to have most things done on the DH by today. But life never works out that way…
I still need to work on switching the mode (cool/heat/auto/off/etc). I know what to do I just have not done it. But I will post the code for now just to see if your issue is resolved. It’s nowhere on github yet as I have not reached a point where I fee it can be added there. But it is close!

Todo:
-Switch mode
-Incorporate min/max cool/heat points. As in: don’t exceed the min/max setpoints.
-I added the ability to override or set the schedule. But strangely when I override the schedule from the Rheem website I cannot resume from Smartthings. This is big in terms of my automation needs. I’ll have to do some proxy tracing to figure this one out.
-When the HVAC is idle it displays the mode (e.g. “cooling”) instead of idle on the multitile. On a simple tile it displays correct. Not sure if this is my bug or smartthings’.
-Tiles on iPad looks like crap. While on Android everything is formatted correctly. I’ll have to dig in the forums on this.
-Work on the refresh
-Build more logging.

I also need input on “fanSpeed”. Whatever I set my HVAC to it always shows “off”. I left the tile at the bottom to that others can provide me input. I can only code to the statuses I know about.

SmartApp:

/**
 *  Rheem EcoNet (Connect)
 *
 *  Copyright 2017 Justin Huff
 *
 *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 *  in compliance with the License. You may obtain a copy of the License at:
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
 *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
 *  for the specific language governing permissions and limitations under the License.
 *
 *  Last Updated : 1/1/17
 *
 *  Based on https://github.com/copy-ninja/SmartThings_RheemEcoNet
 *
 */
definition(
    name: "Rheem EcoNet (Connect) HVAC",
    namespace: "jjhuff",
    author: "Justin Huff",
    description: "Connect to Rheem EcoNet HVAC",
    category: "SmartThings Labs",
    iconUrl: "http://smartthings.copyninja.net/icons/Rheem_EcoNet@1x.png",
    iconX2Url: "http://smartthings.copyninja.net/icons/Rheem_EcoNet@2x.png",
    iconX3Url: "http://smartthings.copyninja.net/icons/Rheem_EcoNet@3x.png")


preferences {
	page(name: "prefLogIn", title: "Rheem EcoNet")    
	page(name: "prefListDevice", title: "Rheem EcoNet")
}

/* Preferences */
def prefLogIn() {
	def showUninstall = username != null && password != null 
	return dynamicPage(name: "prefLogIn", title: "Connect to Rheem EcoNet", nextPage:"prefListDevice", uninstall:showUninstall, install: false) {
		section("Login Credentials"){
			input("username", "email", title: "Username", description: "Rheem EcoNet Email")
			input("password", "password", title: "Password", description: "Rheem EcoNet password (case sensitive)")
		} 
		section("Advanced Options"){
			input(name: "polling", title: "Server Polling (in Minutes)", type: "int", description: "in minutes", defaultValue: "5" )
		}
	}
}

def prefListDevice() {	
	if (login()) {
		def hvaclist = gethvaclist()
		if (hvaclist) {
			return dynamicPage(name: "prefListDevice",  title: "Devices", install:true, uninstall:true) {
				section("Select which HVAC to use"){
					input(name: "hvac", type: "enum", required:false, multiple:true, metadata:[values:hvaclist])
				}
			}
		} else {
			return dynamicPage(name: "prefListDevice",  title: "Error!", install:false, uninstall:true) {
				section(""){ paragraph "Could not find any devices"  }
			}
		}
	} else {
		return dynamicPage(name: "prefListDevice",  title: "Error!", install:false, uninstall:true) {
			section(""){ paragraph "The username or password you entered is incorrect. Try again. " }
		}  
	}
}


/* Initialization */
def installed() { initialize() }
def updated() { 
	unsubscribe()
	initialize() 
}
def uninstalled() {
	unschedule()
    unsubscribe()
	getAllChildDevices().each { deleteChildDevice(it) }
}	

def initialize() {
	// Set initial states
	state.polling = [ last: 0, rescheduler: now() ]  
	    
	// Create selected devices
	def hvaclist = gethvaclist()
    def selectedDevices = [] + getSelectedDevices("hvac")
    selectedDevices.each {
    	def dev = getChildDevice(it)
        def name  = hvaclist[it]
        if (dev == null) {
	        try {
    			addChildDevice("jjhuff", "Rheem Econet HVAC", it, null, ["name": "Rheem Econet: " + name])
    	    } catch (e)	{
				log.debug "addChildDevice Error: $e"
          	}
        }
    }
    
	// Remove unselected devices
	/*def deleteDevices = (selectedDevices) ? (getChildDevices().findAll { !selectedDevices.contains(it.deviceNetworkId) }) : getAllChildDevices()
	deleteDevices.each { deleteChildDevice(it.deviceNetworkId) } */
	
	//Subscribes to sunrise and sunset event to trigger refreshes
	subscribe(location, "sunrise", runRefresh)
	subscribe(location, "sunset", runRefresh)
	subscribe(location, "mode", runRefresh)
	subscribe(location, "sunriseTime", runRefresh)
	subscribe(location, "sunsetTime", runRefresh)
	    
	//Refresh devices
	runRefresh()
}

def getSelectedDevices( settingsName ) {
	def selectedDevices = []
	(!settings.get(settingsName))?:((settings.get(settingsName)?.getAt(0)?.size() > 1)  ? settings.get(settingsName)?.each { selectedDevices.add(it) } : selectedDevices.add(settings.get(settingsName)))
	return selectedDevices
}


/* Data Management */
// Listing all the HVAC Units you have in Rheem EcoNet
private gethvaclist() { 	 
	def deviceList = [:]
	apiGet("/locations", [] ) { response ->
    	if (response.status == 200) {
          	response.data.equipment[0].each { 
            	if (it.type.equals("HVAC")) {
                	deviceList["" + it.id]= it.name
                }
            }
        }
    }
    return deviceList
}

// Refresh data
def refresh() {
	if (!login()) {
    	return
    }
    
	log.info "Refreshing data..."
    // update last refresh
	state.polling?.last = now()

	// get all the children and send updates
	getAllChildDevices().each {
    	def id = it.deviceNetworkId
    	apiGet("/equipment/$id", [] ) { response ->
    		if (response.status == 200) {
            	log.debug "Got data: $response.data"
            	it.updateDeviceData(response.data)
            }
        }

    }
    
	//schedule the rescheduler to schedule refresh ;)
	if ((state.polling?.rescheduler?:0) + 2400000 < now()) {
		log.info "Scheduling Auto Rescheduler.."
		runEvery30Minutes(runRefresh)
		state.polling?.rescheduler = now()
	}
}

// Schedule refresh
def runRefresh(evt) {
	log.info "Last refresh was "  + ((now() - state.polling?.last?:0)/60000) + " minutes ago"
	// Reschedule if  didn't update for more than 5 minutes plus specified polling
	if ((((state.polling?.last?:0) + (((settings.polling?.toInteger()?:1>0)?:1) * 60000) + 300000) < now()) && canSchedule()) {
		log.info "Scheduling Auto Refresh.."
		schedule("* */" + ((settings.polling?.toInteger()?:1>0)?:1) + " * * * ?", refresh)
	}
    
	// Force Refresh NOWWW!!!!
	refresh()
    
	//Update rescheduler's last run
	if (!evt) state.polling?.rescheduler = now()
}

def setCoolSetPoint(childDevice, coolsetpoint) { 
	log.info "setDeviceSetPoint: $childDevice.deviceNetworkId $coolsetpoint" 
	if (login()) {
    	apiPut("/equipment/$childDevice.deviceNetworkId", [
        	body: [
                coolSetPoint: coolsetpoint,
            ]
        ])
    }
}
def setHeatSetPoint(childDevice, heatsetpoint) { 
	log.info "setDeviceSetPoint: $childDevice.deviceNetworkId $heatsetpoint" 
	if (login()) {
    	apiPut("/equipment/$childDevice.deviceNetworkId", [
        	body: [
                heatSetPoint: heatsetpoint,
            ]
        ])
    }
}
// available values are Heating, Cooling, Auto, Fan Only, Off, Emergency Heat
def setDeviceMode(childDevice, mode) {
	log.info "setDeviceMode: $childDevice.deviceNetworkId $mode" 
	if (login()) {
    	apiPut("/equipment/$childDevice.deviceNetworkId", [
        	body: [
                mode: mode,
            ]
        ])
    }
}
// available values are Auto, Low, Med.Lo, Medium, Med.Hi, High
def setFanMode(childDevice, fanMode) {
	log.info "setFanMode: $childDevice.deviceNetworkId $fanMode" 
	if (login()) {
    	apiPut("/equipment/$childDevice.deviceNetworkId", [
        	body: [
                fanMode: fanMode,
            ]
        ])
    }
}

def setFollowSchedule (childDevice, followSchedule) {
	log.info "setFollowSchedule: $childDevice.deviceNetworkId $followSchedule"
	if (login()) {
    	apiPut("/equipment/$childDevice.deviceNetworkId/settings", [
        	body: [
                name: "followSchedules",
				value: followSchedule
            ]
        ])
    }
}

private login() {
	def apiParams = [
    	uri: getApiURL(),
        path: "/auth/token",
        headers: ["Authorization": "Basic Y29tLnJoZWVtLmVjb25ldF9hcGk6c3RhYmxla2VybmVs"],
        requestContentType: "application/x-www-form-urlencoded",
        body: [
        	username: settings.username,
        	password: settings.password,
        	"grant_type": "password"
        ],
    ]
    if (state.session?.expiration < now()) {
    	try {
			httpPost(apiParams) { response -> 
            	if (response.status == 200) {
                	log.debug "Login good!"
                	state.session = [ 
                    	accessToken: response.data.access_token,
                    	refreshToken: response.data.refresh_token,
                    	expiration: now() + 150000
                	]
                	return true
            	} else {
                	return false
            	} 	
        	}
		}	catch (e)	{
			log.debug "API Error: $e"
        	return false
		}
	} else { 
    	// TODO: do a refresh 
		return true
	}
}

/* API Management */
// HTTP GET call
private apiGet(apiPath, apiParams = [], callback = {}) {	
	// set up parameters
	apiParams = [ 
		uri: getApiURL(),
		path: apiPath,
        headers: ["Authorization": getApiAuth()],
        requestContentType: "application/json",
	] + apiParams
	log.debug "GET: $apiParams"
	try {
		httpGet(apiParams) { response -> 
        	callback(response)
        }
	}	catch (e)	{
		log.debug "API Error: $e"
	}
}

// HTTP PUT call
private apiPut(apiPath, apiParams = [], callback = {}) {	
	// set up parameters
	apiParams = [ 
		uri: getApiURL(),
		path: apiPath,
        headers: ["Authorization": getApiAuth()],
        requestContentType: "application/json",
	] + apiParams
	
	try {
		httpPut(apiParams) { response -> 
        	callback(response)
        }
	}	catch (e)	{
		log.debug "API Error: $e"
	}
}

private getApiURL() { 
	return "https://econet-api.rheemcert.com"
}
    
private getApiAuth() {
	return "Bearer " + state.session?.accessToken
}

Device Handler:

/**
 *  Rheem Econet HVAC
 *
 *  Copyright 2017 Justin Huff
 *
 *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 *  in compliance with the License. You may obtain a copy of the License at:
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
 *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
 *  for the specific language governing permissions and limitations under the License.
 *
 *  Last Updated : 2017-01-04
 *
 *  Based on https://github.com/copy-ninja/SmartThings_RheemEcoNet
 */
metadata {
	definition (name: "Rheem Econet HVAC", namespace: "jjhuff", author: "Justin Huff") {
		capability "Actuator"
		capability "Thermostat"
        capability "Sensor"
        capability "Polling"
		capability "Refresh"
		capability "Relative Humidity Measurement"
		capability "Temperature Measurement"
        capability "Thermostat Cooling Setpoint"
		capability "Thermostat Fan Mode"
		capability "Thermostat Heating Setpoint"
		capability "Thermostat Mode"
		capability "Thermostat Operating State"
		capability "Thermostat Setpoint"
		
		command "heatLevelUp"
		command "heatLevelDown"
		command "coolLevelUp"
		command "coolLevelDown"
		command "updateDeviceData", ["string"]
        command "thermostatOperatingState", ["string"]
		command "setFollowingSchedule"
		command "setCancelSchedule"
		command "setFanAuto"
		command "setFanLow"
		command "setFanMedium"
		command "setFanHigh"
        
        attribute "thermostatOperatingState", "string"
		attribute "alert", "string"

	}

	simulator { }

	tiles(scale: 2) {      
              
		multiAttributeTile(name:"tempSummary", type:"thermostat", width:6, height:4) {
			tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
				attributeState("default", label:'${currentValue}°'/*, unit:"dF", defaultState: true*/
							  )
			}

			/*tileAttribute("device.temperature", key: "VALUE_CONTROL") {
                attributeState("default", action: "setTemperature")
			}*/
            tileAttribute("device.humidity", key: "SECONDARY_CONTROL") {
				attributeState("default", label:'${currentValue}%', unit:"%")
			}

			tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE") {
				attributeState("idle", backgroundColor:"#44b621")
				attributeState("heating", backgroundColor:"#ffa81e")
				attributeState("cooling", backgroundColor:"#269bd2")
			}
			tileAttribute("device.thermostatMode", key: "THERMOSTAT_MODE") {
				attributeState("Off", label:'${name}')
				attributeState("Heating", label:'${name}')
				attributeState("Cooling", label:'${name}')
                attributeState("Auto", label:'${name}')
				attributeState("Fan Only", label:'${name}')
				attributeState("Emergency Heat", label:'${name}')
			}
            tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT") {
            	attributeState("default", label:'${currentValue}', unit:"dF")
            }
			tileAttribute("device.coolingSetpoint", key: "COOLING_SETPOINT") {
				attributeState("default", label:'${currentValue}', unit:"dF")
			}

        } // End multiAttributeTile
	
	
		valueTile("heatingSetpoint", "device.heatingSetpoint", inactiveLabel: false, width: 2, height: 2) {
			state("heatingSetpoint", label:'${currentValue}°', backgroundColor:"#d04e00"
			)
		}
        
        valueTile("coolingSetpoint", "device.coolingSetpoint", inactiveLabel: false, width: 2, height: 2) {
			state("coolingSetpoint", label:'${currentValue}°', backgroundColor:"#1e9cbb"
			)
		}
        
		standardTile("heatLevelUp", "device.switch", canChangeIcon: false, decoration: "flat" ) {
			state("heatLevelUp",   action:"heatLevelUp",  label:"Heat", icon:"st.thermostat.thermostat-up")
		}  
		standardTile("heatLevelDown", "device.switch", canChangeIcon: false, decoration: "flat") {
			state("heatLevelDown", action:"heatLevelDown", label:"Heat",icon:"st.thermostat.thermostat-down")
		}
		
		standardTile("coolLevelUp", "device.switch", canChangeIcon: false, decoration: "flat" ) {
			state("coolLevelUp",   action:"coolLevelUp",  label:"Cool", icon:"st.thermostat.thermostat-up")
		}  
		standardTile("coolLevelDown", "device.switch", canChangeIcon: false, decoration: "flat") {
			state("coolLevelDown", action:"coolLevelDown", label:"Cool", icon:"st.thermostat.thermostat-down")
		}

		standardTile("switch", "device.switch", canChangeIcon: false, decoration: "flat" ) {
       		state "on", label: 'On', action: "switch.off",
          		icon: "st.switches.switch.on", backgroundColor: "#79b821"
       		state("off", label: 'Off', action: "switch.on",
          		icon: "st.switches.switch.off", backgroundColor: "#ffffff")
		}
        
		standardTile("refresh", "device.switch", decoration: "flat", width: 2, height: 2) {
			state("default", action:"refresh.refresh",        icon:"st.secondary.refresh")
		}
		
		standardTile("mode", "device.thermostatMode", inactiveLabel:true, decoration: "flat", width: 2, height: 2) {
            //state "default", label:'[Mode]'
            state "Off", label:'', icon:"st.thermostat.heating-cooling-off", action:"thermostat.Heating"//, nextState: "updating", backgroundColor:"#FFFFFF"
            state "Heating", label:'', icon:"st.thermostat.heat", action:"thermostat.Cooling"//, nextState: "updating", backgroundColor:"#FFCC99"
            state "Cooling", label:'', icon:"st.thermostat.cool", action:"thermostat.Auto"//, nextState: "updating", backgroundColor:"#99CCFF"
            state "Auto", label:'', icon:"st.thermostat.auto", action:"thermostat.Fan Only"//, nextState: "updating", backgroundColor:"#99FF99"
			state "Fan Only", label:'', icon:"st.thermostat.fan-on", action:"thermostat.Off"//, nextState: "updating", backgroundColor:"#99FF99"
			state "Emergency Heat", label:'', icon:"st.thermostat.emergency-heat", action:"thermostat.Off"//, backgroundColor:"#FFCC99"
			state "updating", label:"Working", icon: "st.secondary.secondary"
        }
		
		valueTile("fanSpeed", "device.fanSpeed", inactiveLabel: false, decoration: "flat", width: 2, height: 1) {
			state("default", label:'fanspeed: ${currentValue}'//, icon: "st.thermostat.fan-off"
			)
		}
		
		valueTile("outdoorTemperature", "device.outdoorTemperature", inactiveLabel: false, width: 1, height: 1) {
			state("outdoorTemperature", label:'${currentValue}°',icon: "st.Outdoor.outdoor21"
			)
		}
		
		standardTile("alert", "device.alert",  decoration: "flat", inactiveLabel: false) {
			state "true", label: 'Alert!', icon: "st.alarm.water.wet"
			state "false", label: 'No Alert', icon: "st.alarm.water.dry"
		}
		
		standardTile("iconTile", "device.temperature", decoration: "flat", width: 2, height: 2, canChangeIcon: true) {
 		state "default", label:'${currentValue}°', icon: "st.Weather.weather2", backgroundColor: "#79B821"
 		}
		
		standardTile("operatingState", "device.thermostatOperatingState", inactiveLabel:false, decoration:"flat", width: 2, height: 1) {
            state "heating", label:'${currentValue}', action:"polling.poll"
            state "cooling", label:'${currentValue}', action:"polling.poll"
			state "idle", label:'${currentValue}', action:"polling.poll"
        }
        
        standardTile("State", "device.inUse", inactiveLabel:false, decoration:"flat", width: 2, height: 1) {
            state "off", label:'${currentValue}'
            state "on", label:'${currentValue}'
        }
		
		standardTile("isFollowingSchedule", "device.isFollowingSchedule", inactiveLabel: false, decoration:"flat", width: 2, height: 1) {
            state "Following Schedule", label:'${currentValue}', action:"setCancelSchedule"
            state "Resume Schedule", label:'${currentValue}', action:"setFollowingSchedule"
        }
		
		
		standardTile("thermostatFanMode", "device.thermostatFanMode", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
			state "Auto", icon: "st.thermostat.fan-auto", action:"setFanLow" 
			state "Low", label:'Low', icon: "st.thermostat.fan-on", action:"setFanMedium" 
			//state "Med.Lo", label:'M-Low', icon: "st.thermostat.fan-on", action:"setFanMedium" 
			state "Medium", label:'Med', icon: "st.thermostat.fan-on", action:"setFanHigh" 
			//state "Med.Hi", label:'M-High', icon: "st.thermostat.fan-on", action:"setFanHigh" 
			state "High", label:'High', icon: "st.thermostat.fan-on", action:"setFanAuto" 
		}
        
		main (["iconTile"])
		details(["tempSummary", "outdoorTemperature", "operatingState", "isFollowingSchedule", "alert", "coolLevelUp", "coolingSetpoint", "heatingSetpoint", "heatLevelUp", "coolLevelDown", "heatLevelDown",  
		 "mode",      "thermostatFanMode",  "refresh", "fanSpeed"
		])
	}
}

def parse(String description) { }

def refresh() {
	log.debug "refresh"
	parent.refresh()
    poll()
}

def setFanAuto() {
    sendEvent(name: "thermostatFanMode", value: "Auto")
	parent.setFanMode(this.device, "Auto")
}
def setFanLow() {
    sendEvent(name: "thermostatFanMode", value: "Low")
	parent.setFanMode(this.device, "Low")
}
def setFanMedium() {
    sendEvent(name: "thermostatFanMode", value: "Medium")
	parent.setFanMode(this.device, "Medium")
}
def setFanHigh() {
    sendEvent(name: "thermostatFanMode", value: "High")
	parent.setFanMode(this.device, "High")
}

def setHeatingSetpoint(Number heatSetPoint) {
   	sendEvent(name: "heatingSetpoint", value: heatSetPoint, unit: "F")
	parent.setHeatSetPoint(this.device, heatSetPoint)
}

def setCoolingSetpoint(Number coolSetPoint) {
   	sendEvent(name: "coolingSetpoint", value: coolSetPoint, unit: "F")
	parent.setCoolSetPoint(this.device, coolSetPoint)
}

def heatLevelUp() { 
	def heatSetPoint = device.currentValue("heatingSetpoint")
    heatSetPoint = heatSetPoint + 1
	setHeatingSetpoint(heatSetPoint)
}	

def heatLevelDown() { 
	def heatSetPoint = device.currentValue("heatingSetpoint")
    heatSetPoint = heatSetPoint - 1
    setHeatingSetpoint(heatSetPoint)
}

def coolLevelUp() { 
	def coolSetPoint = device.currentValue("coolingSetpoint")
    coolSetPoint = coolSetPoint + 1
	setCoolingSetpoint(coolSetPoint)
}	

def coolLevelDown() { 
	def coolSetPoint = device.currentValue("coolingSetpoint")
    coolSetPoint = coolSetPoint - 1
    setCoolingSetpoint(coolSetPoint)
}

def setFollowingSchedule() {
	parent.setFollowSchedule(this.device, true)
	sendEvent(name: "isFollowingSchedule", value: "Following Schedule")
}

def setCancelSchedule() {
	parent.setFollowSchedule(this.device, false)
	sendEvent(name: "isFollowingSchedule", value: "Resume Schedule")
}

def poll() {
	
    log.info "Polling..."
    
    def active = device.currentValue("inUse")
	def modes = device.currentValue("thermostatMode")
	
	if (active == "off") {
				sendEvent(name: "thermostatOperatingState", value: "idle")
							}
    else if (modes == "Cooling") {
				sendEvent(name: "thermostatOperatingState", value: "cooling")
							}
	else if (modes == "Auto") {
				sendEvent(name: "thermostatOperatingState", value: "auto")
							}
	else if (modes == "Fan Only") {
				sendEvent(name: "thermostatOperatingState", value: "fan only")
							}
	else  {     sendEvent(name: "thermostatOperatingState", value: "heating")
							}
							
}

def updateDeviceData(data) {
	sendEvent(name: "heatingSetpoint", value: data.heatSetPoint, unit: "F")
	sendEvent(name: "minHeatSetPoint", value: data.minHeatSetPoint, unit: "F")
	sendEvent(name: "maxHeatSetPoint", value: data.maxHeatSetPoint, unit: "F")
	sendEvent(name: "coolingSetpoint", value: data.coolSetPoint, unit: "F")
	sendEvent(name: "minCoolSetPoint", value: data.minCoolSetPoint, unit: "F")
	sendEvent(name: "maxCoolSetPoint", value: data.maxCoolSetPoint, unit: "F")
    sendEvent(name: "switch", value: data.isEnabled ? "on" : "off")
	sendEvent(name: "humidity", value: (String.format("%3.0f",data.indoorHumidityPercentage)), unit: "%")
	sendEvent(name: "temperature", value: data.indoorTemperature, unit: "F")
	sendEvent(name: "outdoorTemperature", value: data.outdoorTemperature, unit: "F")
	sendEvent(name: "thermostatMode", value: data.mode)
	sendEvent(name: "thermostatFanMode", value: data.fanMode)
	sendEvent(name: "fanSpeed", value: data.fanSpeed)
	sendEvent(name: "inUse", value: data.inUse ? "on" : "off")
	sendEvent(name: "alert", value: data.hasCriticalAlert ? "true" : "false")
	sendEvent(name: "isFollowingSchedule", value: data.isFollowingSchedule ? "Following Schedule" : "Resume Schedule")
	
}

heh. I updated my original post. It was broken because I didn’t grab the last line of code when I cut/pasted. Works now.

Regarding fan speed, does you hvac have an ecm blower? Does it show multiple fan speeds in the gui on the econet? I can find a list of what the values are from the gui on mine.

Yes, my unit is ecm, not inverter. It’s a 2 speed. Another example of where Rheem messed up (not just in the API) is that the econet thermostat is 5 speed. So when I have my unit in fan-only the 5 speed options do not line up with my 2 speed air handler. So the fan speed is always low when I’m in fan-only regardless of what I choose.

BTW, there are 2 “settings” for fan in the API:
“fanMode”: “Low”, --available values are [Auto, Low, Med.Lo, Medium, Med.Hi, High]"
“fanSpeed”: “Off”,

So don’t confuse these 2. The tile for fan controls the fan mode. Fan speed is not update able in the API.

Regarding github. That’s another piece I have to learn first…

I think fan speed is what’s being reported by the furnace, so yeah its not settable.