Rheem EcoNet

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.

The furnace I have is actually a 5 speed with the setting you listed above. Usually keep it on continuous medium when not heating or cooling to keep air flow through the house. Really work quite well helping to keep things balanced and comfortable.

For the DH and app, I’ll load the updates at some point today. So far they have been working for what I have tested, Including integration into ActionTiles. I have yet to add any full on automation with it, that will come soon and I’ll provide feedback.

I am using the Rheem heat pump water heater and I have been using the ST with the EcoNet but unable to set the water heater to away mode.

Has anyone figured out how to do this with ST?

Great interface! I just got 2 Rheem Econet HVAC systems installed and started using this interface. I was trying to update the current mode (cooling/heating/auto/etc.) through the tiles and a Core piston but it doesn’t seem to be working. I saw in an earlier post that you are working on it. Has it been fixed ? Thank you.

Thanks to jjhuff and copyninja for this. Decided to take a stab at modifying it for a learning experience.
This is purely for the Water Heater (mine is electric).
Vacation mode currently doesn’t work (haven’t tracked that one down) but everything else should.

GitHub: Owner: kramttocs ; Name: ST-RheemEconet ; Branch: master

Hopefully I set all of that up correctly - was a learning experience in all aspects.

Rheem

Are you going to share the code?

I tried to get vacation mode working but have not succeeded either.

Did this not work for you?

I have my smart things hub connected to about 30 light switches in my house along with voice command with google home. i would like to use it for my thermostat ( Rheem EcoNet) along with my tankless water heater also soon to be Rheem Econet. i have been following this thread on and off for a while. was anyone able to really get this to work with smart things yet and how could i get it to work on my phone if so.

Are you referring to the HVAC or WH? Despite being similar they are separately developed (ought to have a individual thread for each really). Can you explain the ‘on my phone’ part? About everything with ST is on the phone so just a little unclear on what you are referring to.

I’m referring to both the water heater and the hvac system. The on the
phone part is be able to control the system it just change temp I get
emails if anything goes wrong.

James Mckeon
Service Manager
Mountain A/C & Heating Corp.
(516) 935-0149