Edit app code settings directly

Hi-

I'm pretty new to hubitat. Trying to get a custom DTH and App supporting Rheem thermostats and water heaters ported from smartthings. Both the device handler and smart app code loads ok, but there's a preferences drop down in the app that's not being populated correctly.

My question is this, I can see the app settings by clicking the little information "i" next to the app. Is there a way to edit these settings directly? That would certainly help debug, or make the thing work outright.

Thanks-

Unfortunately you cannot edit setting from the "i" page.
I'm assuming the preference dropdown is an enum input. There are differences in HE with how the enum options are coded. See the thread below and read a few posts down. Hopefully that helps.

That thing you linked to in your post is no longer true. Enums can come in three flavors for the options:

  1. A simple list: ["one", "two", "three"]
  2. A list of key:value pairs: ["1": "one", "2": "two", "3": "three"]
  3. A list of maps: [["1": "one"], ["2": "two"], ["3": "three"]]

Each of these will look identical in the ui, and the pull down will have "one", "two", and "three". The input value from the first one would be "one", "two" or "three".

The input value from the second two would be "1", "2", or "3". These second two are vey useful for creating user friendly enum pulldowns to select not so user friendly input values, or to make a simple transformation from the pull down displayed value to a different value to be used in the app code.

1 Like

Yeah it does look like an enum:

input(name: "waterheater", type: "enum", required:false, multiple:true, metadata:[values:waterHeaterList])

OK well this is what i'm seeing in the logs. 2 or 3 errors:

groovy.lang.GroovyRuntimeException: Ambiguous method overloading for method java.lang.Long#minus. Cannot resolve which method to invoke for [null] due to overlapping prototypes between: [class java.lang.Character] [class java.lang.Number] on line 173 (runRefresh)

app:1952018-10-23 07:38:00.192 error groovy.lang.GroovyRuntimeException: Ambiguous method overloading for method java.lang.Long#minus. Cannot resolve which method to invoke for [null] due to overlapping prototypes between: [class java.lang.Character] [class java.lang.Number] on line 173 (runRefresh)

line 173:

log.info "Last refresh was "  + ((now() - state.polling?.last?:0)/60000) + " minutes ago"

and

groovy.lang.MissingMethodException: No signature of method: app15402783467321571891680.canSchedule() is applicable for argument types: () values: [] on line 175 (installed)

line 175:
if ((((state.polling?.last?:0) + (((settings.polling?.toInteger()?:1>0)?:1) * 60000) + 300000) < now()) && canSchedule()) {

This format is not supported in Hubitat. You need to use one of the formats I showed above.

input "waterheater", "enum", multiple: true, options {{this is where one of those formats options goes, either directly or indirectly}}

So what's not supported? I can't supply the list by name, I have to enumerate it by hand? I don' t understand your use of double square brackets in the first example and double curly ones in the second.

Sorry I'm not getting it, I haven't been able to find the actual docs to figure this out.

Currently the list is created dynamically from the rheem api so I'm not quite sure how I'm going to populate it statically...

def waterHeaterList = getWaterHeaterList()

private getWaterHeaterList() { 	 
	def deviceList = [:]
	apiGet("/locations", [] ) { response ->
    	if (response.status == 200) {
          	response.data.equipment[0].each { 
            	if (it.type.equals("Water Heater")) {
                	deviceList["" + it.id]= it.name
                }
            }
        }
    }
    return deviceList
}

It would be helpful if you posted the entire code that you are working on.. regardless we can take a guess at what it looks like.

your existing code:
input(name: "waterheater", type: "enum", required:false, multiple:true, metadata:[values:waterHeaterList])

new code:
input(name: "waterheater", type: "enum", required:false, multiple:true, options:waterHeaterList)

/**
*  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 Tankless",
    namespace: "billm",
    author: "Bill M",
    description: "Connect to Rheem EcoNet",
    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 waterHeaterList = getWaterHeaterList()
    if (waterHeaterList) {
      return dynamicPage(name: "prefListDevice",  title: "Devices", install:true, uninstall:true) {
        section("Select which water heater to use"){
          input(name: "waterheater", type: "enum", required:false, multiple:true, metadata:[[waterHeaterList]])
        }
      }
    } 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 waterHeaterList = getWaterHeaterList()
    def selectedDevices = [] + getSelectedDevices("waterheater")
    selectedDevices.each {
      def dev = getChildDevice(it)
        def name  = waterHeaterList[it]
        if (dev == null) {
          try {
          addChildDevice("bmcgair", "Rheem Econet Tankless", it, null, ["name": "Rheem Tankless: " + 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 water heaters you have in Rheem EcoNet
private getWaterHeaterList() { 	 
  def deviceList = [:]
  apiGet("/locations", [] ) { response ->
      if (response.status == 200) {
            response.data.equipment[0].each { 
              if (it.type.equals("Water Heater")) {
                  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 setDeviceSetPoint(childDevice, setpoint) { 
  log.info "setDeviceSetPoint: $childDevice.deviceNetworkId $setpoint" 
  if (login()) {
      apiPut("/equipment/$childDevice.deviceNetworkId", [
          body: [
                setPoint: setpoint,
            ]
        ])
    }

}
def setDeviceEnabled(childDevice, enabled) {
  log.info "setDeviceEnabled: $childDevice.deviceNetworkId $enabled" 
  if (login()) {
      apiPut("/equipment/$childDevice.deviceNetworkId", [
          body: [
                isEnabled: enabled,
            ]
        ])
    }
}
def setDeviceMode(childDevice, mode) {
  log.info "setDeviceEnabled: $childDevice.deviceNetworkId $enabled" 
  if (login()) {
      apiPut("/equipment/$childDevice.deviceNetworkId", [
          body: [
                mode: mode,
            ]
        ])
    }
}
def setDeviceOnVacation(childDevice, OnVacation) {
  log.info "setDeviceOnVacation: $childDevice.deviceNetworkId $OnVacation" 
  if (login()) {
      apiPut("/equipment/$childDevice.deviceNetworkId", [
          body: [
                isOnVacation: OnVacation,
            ]
        ])
    }
}

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
  log.debug "apiPut: $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
}
1 Like

you can add three back ticks before and after code to format it: ```

ty was trying to remember how to do that. I ended up insert 4 spaces in vi then repasting.

ok, so you can just use my example above then, does that work for you?

Oh sorry... I just saw that example. Uh... yeah that seems like it might work. Formatting looks a bit funny: [{70695=Tankless Water Heater}]

but the list gets populated with something at least. Will try further this evening.

Ah hah. What works is:

inputut(name: "waterheater", type: "enum", required:false, multiple:true, options:[waterHeaterList])

since waterheaterList is already a map that looks like: id=name.

This allows me to select from the dropdown by the rheem device name and populate the Device Network ID with the id.

Seems to be working. Now to get the thermostat/furnace up too!

Thanks for the help guys!

Has anyone ported or tried to create a user app for JUST the Thermostat? Currently using just the heat pump as my water heater was only 1 year old when I upgraded my heat pump and got the new thermostat.

I would check with @april.brandt - I'm 99% sure they have a Rheem thermostat.

Thanks, she is unaware but is checking. I tried one from Github by bmgair (sp?) But I couldn't get it to work correctly.

you're trying to just use a rheem econet thermostat? I thought I was the only person in the world using hubitat with rheem econet, so I basically got this working "good enough for me", which is basically setting the mode and the heat and cooling setpoints. That's all I really need. Any other automation happens elsewhere.

I hadn't updated the github repo in a while but just pushed the versions of the app and dh that I'm currently running.

If there's a better app and DH I'd love to know about it. This is just some old abandoned smartthings code that I munged enough to work on HE.

Yes, I also thought I was alone looking for a connection. I actually use the mobile app and it works fine, just looking if there are some way of tying in some room sensors or rules I to it. Appreciate your update, I'll give it a try. I had the old one working, I thought, but it appeared to have stopped updating. I also looked at the HE schedule program and saw that it allowed adding a 5th schedule period which I would like to use. For our use another time period was something I missed from my old Honeywell I had on my old system. Thanks!

Just read my own readme...

When first installing the app you may see a login error. Just login a second time, it'll work then. The thermostat app refreshes every 10m. The tankless app refreshes ever 30m.

The change I made a few months ago forces the app to login every time it's trying to update an EcoNet setting. It's much more reliable that way.

Working fine, Tile (in photo) appears to be cutting off "Humidity" reading in Thermostat Tile. No problem but thought I'd pass it on, I noticed in a post on FB (Hubitat Group) that the same thing appears in the Water Heater Tile. I assume it's a spacing thing though I no absolutely nothing about Groovy. I'm an aged DOS guy.. LOL Thanks again for your help, I may fool with the Scheduler app but need to enjoy Holiday and let Hubitat settle. Set Alarm off in house this morning as I had "rolled back" to my last backup apparently as Mode changed and it missed the Disarm command. "Joy To The World" is not what we were singing.