Help with encoding password

Hey there fellow Hubitators,

Just got my shiny new hub. I'm trying to bring across my alarm system into Hubitat (previously working well in SmartThings). Can someone give me a pointer as to this line.

Error in logs:
:error groovy.lang.MissingMethodException: No signature of method: java.lang.String.decodeAsBase64() is applicable for argument types: () values: []
Possible solutions: decodeBase64() on line 741 (ZoneCreateZoneDevices)

Line 741 in the Device Code is as follows:
def userpass = "Basic " + userpassascii.encodeAsBase64().toString()

Any thoughts?

Those questions are often best posed to @chuck.schwer. He’s the API guru for Hubitat. Hopefully he’ll answer shortly.

Sweet, this is the thing that is holding up my transition across. The alarm has a stack of motion sensors that report and run Bengali's Room Manager. Since it's not working, the family are wondering why!.

Groovy has some baked in helper methods on byte[] that will help. Here's what I'm using in one of my apps:

def authHeader(){
	return "Basic " + (settings.username + ":" + settings.password).bytes.encodeBase64()
}
2 Likes

Yep, that's the way to do it. encodeAsBase64 and decodeAsBase64 are Grails extensions that we do not support. Here is a good example of encode and decode:

1 Like

Thanks, so do i replace the whole line 741 with the line from putnamjwp?

Like so:

def userpass = "Basic " + userpassascii.bytes.encodeBase64().toString()

Also your error message and line of code do not match up, is it possible there is an error somewhere else as well?

1 Like

Chuck(@chuck.schwer)
I'm trying to port Lennox Icomfort app from ST to HE and I was getting the same error as OP in the log. I've tried to use the solution offered, but now the login doesn't work with a message:

Lennox iComfort (Connect)

Error!
The username or password you entered is incorrect. Try again.

This is only line of code that was changed from the original app and login is working in ST.
What else could be the problem?

It sounds like you need help with basic troubleshooting, hopefully some others can jump in to help you.

Have you gotten the Lennox iComfort ST app ported to HE?

No. I have no idea how to troubleshoot basic authentication, so I'm still keeping my thermostat on Smartthings

@aryvin
Here is my smart App for Icomfort

/**
 *  Lennox iComfort (Connect)
 *
 *  Copyright 2015 Jason Mok
 *
 *  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 : 7/15/2015
 *
 */
definition(
	name: "Lennox iComfort (Connect)",
	namespace: "copy-ninja",
	author: "Jason Mok",
	description: "Connect Lennox iComfort to control your thermostats",
	category: "SmartThings Labs",
	iconUrl:   "http://smartthings.copyninja.net/icons/iComfort@1x.png",
	iconX2Url: "http://smartthings.copyninja.net/icons/iComfort@2x.png",
	iconX3Url: "http://smartthings.copyninja.net/icons/iComfort@3x.png"
)

preferences {
	page(name: "prefLogIn", title: "Lennox iComfort")    
	page(name: "prefListDevice", title: "Lennox iComfort")
}

/* Preferences */
def prefLogIn() {
	def showUninstall = username != null && password != null 
	return dynamicPage(name: "prefLogIn", title: "Connect to Lennox iComfort", nextPage:"prefListDevice", uninstall:showUninstall, install: false) {
		section("Login Credentials"){
			input("username", "text", title: "Username", description: "iComfort Username (case sensitive)")
			input("password", "password", title: "Password", description: "iComfort password (case sensitive)")
		} 
		section("Advanced Options"){
			input(name: "polling", title: "Server Polling (in Minutes)", type: "int", description: "in minutes", defaultValue: "5" )
			//paragraph "This option enables author to troubleshoot if you have problem adding devices. It allows the app to send information exchanged with iComfort server to the author. DO NOT ENABLE unless you have contacted author at jason@copyninja.net"
			//input(name:"troubleshoot", title: "Troubleshoot", type: "boolean")
		}            
	}
}

def prefListDevice() {
	LoginResult = loginCheck()
	if (LoginResult) 
	{
		def thermostatList = getThermostatList()
		if (thermostatList) {
			return dynamicPage(name: "prefListDevice",  title: "Thermostats", install:true, uninstall:true) {
				section("Select which thermostat/zones to use"){
					input(name: "thermostat", type: "enum", required:false, multiple:true, options:[thermostatList])
				}
			}
		} 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()
	getAllChildDevices().each { deleteChildDevice(it.deviceNetworkId) }
}	

def initialize() {    
	// Get initial polling state
	state.polling = [ last: 0, rescheduler: now() ]
    state.troubleshoot = null
    
	// Create selected devices
	def thermostatList = getThermostatList()
	def selectedDevices = [] + getSelectedDevices("thermostat")
	selectedDevices.each { (getChildDevice(it))?:addChildDevice("copy-ninja", "Lennox iComfort Thermostat", it, null, ["name": "Lennox iComfort: " + thermostatList[it]]) }

	// 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 device
	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 
} 

/* Access Management */
private loginCheck() { 
	def returnval = 0
	apiPut("/DBAcessService.svc/ValidateUser", [query: [UserName: settings.username, lang_nbr: "1"]] ) { response ->
		if (response.status == 200) {
			if (response.data.msg_code == "SUCCESS")
			{
				returnval = 1
			}
			else
			{
				returnval =  0
			}
		} else {
			returnval = 0
		}
	}
	return returnval
}

// Listing all the thermostats you have in iComfort
private getThermostatList() { 	    
	def thermostatList = [:]
	def gatewayList = [:]
	state.data = [:]
	state.lookup = [
		thermostatOperatingState: [:],
		thermostatFanMode: [:],
		thermostatMode: [:],
		program: [:],
		coolingSetPointHigh: [:],
		coolingSetPointLow: [:],
		heatingSetPointHigh: [:],
		heatingSetPointLow: [:],
		differenceSetPoint: [:],
		temperatureRangeF: [:]
	]
	state.list = [
		temperatureRangeC: [],
		program: [:]
	]
    
	//Get Thermostat Mode lookups
	apiGet("/DBAcessService.svc/GetTstatLookupInfo", [query: [name: "Operation_Mode", langnumber: 0]]) { response ->
		response.data.tStatlookupInfo.each {
			state.lookup.thermostatMode.putAt(it.value.toString(), translateDesc(it.description))
		}
	}
	
	//Get Fan Modes lookups
	apiGet("/DBAcessService.svc/GetTstatLookupInfo", [query: [name: "Fan_Mode", langnumber: 0]]) { response ->
		response.data.tStatlookupInfo.each {
			state.lookup.thermostatFanMode.putAt(it.value.toString(), translateDesc(it.description))
		}
	}
	
	//Get System Status lookups
	apiGet("/DBAcessService.svc/GetTstatLookupInfo", [query: [name: "System_Status", langnumber: 0]]) { response ->
		response.data.tStatlookupInfo.each {
			state.lookup.thermostatOperatingState.putAt(it.value.toString(), translateDesc(it.description))
		}
	}    

	//Get Temperature lookups
	apiGet("/DBAcessService.svc/GetTemperatureRange", [query: [highpoint: 40, lowpoint: 0]]) { response ->
		response.data.each {
			def temperatureLookup = it.Value.split("\\|")
			state.lookup.temperatureRangeF.putAt(temperatureLookup[1].toString(), temperatureLookup[0].toString())
			state.list.temperatureRangeC.add(temperatureLookup[0].toString())
		}
	}    

	//Retrieve all the gateways
	apiGet("/DBAcessService.svc/GetSystemsInfo", [query: [userID: settings.username]]) { response ->
		if (response.status == 200) {
			response.data.Systems.each { device ->
				gatewayList.putAt(device.Gateway_SN,device.System_Name)
			}
		}
	}   
	//Retrieve all the Zones
	gatewayList.each { gatewaySN, gatewayName ->		
		apiGet("/DBAcessService.svc/GetTStatInfoList", [query: [GatewaySN: gatewaySN, TempUnit: (getTemperatureScale()=="F")?0:1, Cancel_Away: "-1"]]) { response ->
			if (response.status == 200) {
				response.data.tStatInfo.each { 
					def dni = [ app.id, gatewaySN, it.Zone_Number ].join('|')
					thermostatList[dni] = ( it.Zones_Installed > 1 )? gatewayName + ": " + it.Zone_Name : gatewayName
					
					//Get the state of each device
					state.data[dni] = [
						temperature: it.Indoor_Temp,
						humidity: it.Indoor_Humidity,
						coolingSetpoint: it.Cool_Set_Point,
						heatingSetpoint: it.Heat_Set_Point,
						thermostatMode: lookupInfo( "thermostatMode", it.Operation_Mode.toString(), true ),
						thermostatFanMode: lookupInfo( "thermostatFanMode", it.Fan_Mode.toString(), true ),
						thermostatOperatingState: lookupInfo( "thermostatOperatingState", it.System_Status.toString(), true ),
						thermostatProgramMode: it.Program_Schedule_Mode,
						thermostatProgramSelection: it.Program_Schedule_Selection,
						awayMode: it.Away_Mode.toString()
					]
					
					//Get Devices Program lookups
					state.lookup.program.putAt(dni, [:])
					state.list.program.putAt(dni, [])
					apiGet("/DBAcessService.svc/GetTStatScheduleInfo", [query: [GatewaySN: gatewaySN]]) { response2 ->
						if (response2.status == 200) {
							response2.data.tStatScheduleInfo.each {
								state.lookup.program[dni].putAt(it.Schedule_Number.toString(), "Program " + (it.Schedule_Number + 1) + ":\n" + it.Schedule_Name)
								state.list.program[dni].add("Program " + (it.Schedule_Number + 1) + ":\n" + it.Schedule_Name)
							}      
						}
					}
                    
					//Get Devices Limit Lookups
					apiGet("/DBAcessService.svc/GetGatewayInfo", [query: [GatewaySN: gatewaySN, TempUnit: "0"]]) { response2 ->
						if (response2.status == 200) {
							state.lookup.coolingSetPointHigh.putAt(dni, response2.data.Cool_Set_Point_High_Limit)
							state.lookup.coolingSetPointLow.putAt(dni, response2.data.Cool_Set_Point_Low_Limit)
							state.lookup.heatingSetPointHigh.putAt(dni, response2.data.Heat_Set_Point_High_Limit)
							state.lookup.heatingSetPointLow.putAt(dni, response2.data.Heat_Set_Point_Low_Limit)
							state.lookup.differenceSetPoint.putAt(dni, response2.data.Heat_Cool_Dead_Band)
						}
					}
				}
			}
		}
	}

	return thermostatList
}

/* api connection */

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

// HTTP PUT call
private apiPut(apiPath, apiParams = [], callback = {}) {    
	// set up final parameters
	apiParams = [ uri: getApiURL(), path: apiPath, headers: [Authorization: getApiAuth()] ] + apiParams
	log.debug "apiParams: $apiParams"

	try {
		httpPut(apiParams) { response -> callback(response) }
	} catch (Error e) {
		log.debug "API Error: $e"
	}
}

// update child device data
private updateDeviceChildData(device) {
	apiGet("/DBAcessService.svc/GetTStatInfoList", [query: [GatewaySN: getDeviceGatewaySN(device), TempUnit: (getTemperatureScale()=="F")?0:1, Cancel_Away: "-1"]]) { response ->
		if (response.status == 200) {
			response.data.tStatInfo.each { 
				def dni = [ app.id, it.GatewaySN, it.Zone_Number ].join('|')
				state.data[dni] = [
					temperature: it.Indoor_Temp,
					humidity: it.Indoor_Humidity,
					coolingSetpoint: it.Cool_Set_Point,
					heatingSetpoint: it.Heat_Set_Point,
					thermostatMode: lookupInfo( "thermostatMode", it.Operation_Mode.toString(), true ),
					thermostatFanMode: lookupInfo( "thermostatFanMode", it.Fan_Mode.toString(), true ),
					thermostatOperatingState: lookupInfo( "thermostatOperatingState", it.System_Status.toString(), true ),
					thermostatProgramMode: it.Program_Schedule_Mode,
					thermostatProgramSelection: it.Program_Schedule_Selection,
					awayMode: it.Away_Mode.toString()
				]
			}
		}
	}
	return true
}

// lookup value translation
def lookupInfo( lookupName, lookupValue, lookupMode ) {
	if (lookupName == "thermostatFanMode") {
		if (lookupMode) {
			return state.lookup.thermostatFanMode.getAt(lookupValue.toString())
		} else {
			return state.lookup.thermostatFanMode.find{it.value==lookupValue.toString()}?.key
		}
	}
	if (lookupName == "thermostatMode") {
		if (lookupMode) {
			return state.lookup.thermostatMode.getAt(lookupValue.toString())
		} else {
			return state.lookup.thermostatMode.find{it.value==lookupValue.toString()}?.key
		}	
	}
	if (lookupName == "thermostatOperatingState") {
		if (lookupMode) {
			return state.lookup.thermostatOperatingState.getAt(lookupValue.toString())
		} else {
			return state.lookup.thermostatOperatingState.find{it.value==lookupValue.toString()}?.key
		}	
	}
}

/* for SmartDevice to call */
// Refresh data
def refresh() {
	log.info "Refreshing data..."
	// set polling states
	state.polling?.last = now()
		
	// update data for child devices
	getAllChildDevices().each { (!updateDeviceChildData(it))?:it.updateThermostatData(state.data[it.deviceNetworkId.toString()]) }
    
	//schedule the rescheduler to schedule refresh ;)
	if ((state.polling?.rescheduler?:0) + 300000 < now()) {  //BES - Changed to every 5 minutes.
		log.info "Scheduling Auto Rescheduler.."
		runEvery30Minutes(runRefresh)
		state.polling?.rescheduler = now()
	}
}

// Get Device Gateway SN
def getDeviceGatewaySN(childDevice) { return childDevice.deviceNetworkId.toString().split("\\|")[1] }

// Get Device Zone
def getDeviceZone(childDevice) { return childDevice.deviceNetworkId.toString().split("\\|")[2] }

// Get single device status
def getDeviceStatus(childDevice) { return state.data[childDevice.deviceNetworkId.toString()] }

// Send thermostat
def setThermostat(childDevice, thermostatData = []) {
	thermostatData.each { key, value -> 
		if (key=="coolingSetpoint") { state.data[childDevice.deviceNetworkId].coolingSetpoint = value }
		if (key=="heatingSetpoint") { state.data[childDevice.deviceNetworkId].heatingSetpoint = value }
		if (key=="thermostatFanMode") { state.data[childDevice.deviceNetworkId].thermostatFanMode = value }
		if (key=="thermostatMode") { state.data[childDevice.deviceNetworkId].thermostatMode = value }
	}
	
	// set up final parameters
	def apiBody = [ 
		Cool_Set_Point: state.data[childDevice.deviceNetworkId].coolingSetpoint,
		Heat_Set_Point: state.data[childDevice.deviceNetworkId].heatingSetpoint,
		Fan_Mode: lookupInfo("thermostatFanMode",state.data[childDevice.deviceNetworkId].thermostatFanMode.toString(),false),
		Operation_Mode: lookupInfo("thermostatMode",state.data[childDevice.deviceNetworkId].thermostatMode.toString(),false),
		Pref_Temp_Units: (getTemperatureScale()=="F")?0:1,
		Zone_Number: getDeviceZone(childDevice),
		GatewaySN: getDeviceGatewaySN(childDevice) 
	]
	
    apiPut("/DBAcessService.svc/SetTStatInfo", [contentType: "application/x-www-form-urlencoded", requestContentType: "application/json; charset=utf-8", body: apiBody]) 
    
    return state.data[childDevice.deviceNetworkId]
}

// Set program
def setProgram(childDevice, scheduleMode, scheduleSelection) {
	def apiBody = []
	def thermostatData = []
	
	//Retrieve program info
	state.data[childDevice.deviceNetworkId].thermostatProgramMode = scheduleMode
	if (scheduleMode == "1") {
		state.data[childDevice.deviceNetworkId].thermostatProgramSelection = scheduleSelection
		apiGet("/DBAcessService.svc/GetProgramInfo", [query: [GatewaySN: getDeviceGatewaySN(childDevice), ScheduleNum: scheduleSelection, TempUnit: (getTemperatureScale()=="F")?0:1]]) { response ->
			if (response.status == 200) {
				state.data[childDevice.deviceNetworkId].coolingSetpoint = response.data.Cool_Set_Point
				state.data[childDevice.deviceNetworkId].heatingSetpoint = response.data.Heat_Set_Point
				state.data[childDevice.deviceNetworkId].thermostatFanMode = lookupInfo("thermostatFanMode",response.data.Fan_Mode.toString(),true)
			}
		}
	}
		
	// set up final parameters for program
	apiBody = [ 
		Cool_Set_Point: state.data[childDevice.deviceNetworkId].coolingSetpoint,
		Heat_Set_Point: state.data[childDevice.deviceNetworkId].heatingSetpoint,
		Fan_Mode: lookupInfo("thermostatFanMode",state.data[childDevice.deviceNetworkId].thermostatFanMode.toString(),false),
		Operation_Mode: lookupInfo("thermostatMode",state.data[childDevice.deviceNetworkId].thermostatMode.toString(),false),
		Pref_Temp_Units: (getTemperatureScale()=="F")?0:1,
		Program_Schedule_Mode: scheduleMode,
		Program_Schedule_Selection: scheduleSelection,
		Zone_Number: getDeviceZone(childDevice),
		GatewaySN: getDeviceGatewaySN(childDevice) 
	]
	
	//Set Thermostat Program
	apiPut("/DBAcessService.svc/SetProgramInfoNew", [contentType: "application/x-www-form-urlencoded", requestContentType: "application/json; charset=utf-8", body: apiBody]) { response ->
		if (response.status == 200) {
			response.data.tStatInfo.each { 
				state.data[device.deviceNetworkId] = [
					temperature: it.Indoor_Temp,
					humidity: it.Indoor_Humidity,
					coolingSetpoint: it.Cool_Set_Point,
					heatingSetpoint: it.Heat_Set_Point,
					thermostatMode: lookupInfo( "thermostatMode", it.Operation_Mode.toString(), true ),
					thermostatFanMode: lookupInfo( "thermostatFanMode", it.Fan_Mode.toString(), true ),
					thermostatOperatingState: lookupInfo( "thermostatOperatingState", it.System_Status.toString(), true ),
					thermostatProgramMode: it.Program_Schedule_Mode,
					thermostatProgramSelection: it.Program_Schedule_Selection,
					awayMode: it.Away_Mode.toString()
				]
				thermostatData = [
					coolingSetpoint: it.Cool_Set_Point,
					heatingSetpoint: it.Heat_Set_Point,
					thermostatMode: lookupInfo( "thermostatMode", it.Operation_Mode.toString(), true ),
					thermostatFanMode: lookupInfo( "thermostatFanMode", it.Fan_Mode.toString(), true ),
				]
			}
		}
	}
	   
	//Set Thermostat Values
	return setThermostat(childDevice, thermostatData)
}


def translateDesc(value) {
	switch (value) {
		case "cool only"     : return "cool"
		case "heat only"     : return "heat"
		case "heat or cool"  : return "auto"
		default: return value
	}
}


def getThermostatProgramName(childDevice, thermostatProgramSelection) {
	def thermostatProgramSelectionName = state?.lookup?.program[childDevice.deviceNetworkId]?.getAt(thermostatProgramSelection.toString())
	return thermostatProgramSelectionName?thermostatProgramSelectionName:"Unknown"
}

def getThermostatProgramNext(childDevice, value) {
	def sizeProgramIndex = state.list.program[childDevice.deviceNetworkId].size() - 1
	def currentProgramIndex = (state?.list?.program[childDevice.deviceNetworkId]?.findIndexOf { it == value })?state?.list?.program[childDevice.deviceNetworkId]?.findIndexOf { it == value } : 0
	def nextProgramIndex = ((currentProgramIndex + 1) <= sizeProgramIndex)? (currentProgramIndex + 1) : 0
	def nextProgramName = state?.list?.program[childDevice.deviceNetworkId]?.getAt(nextProgramIndex)
	return state?.lookup?.program[childDevice.deviceNetworkId]?.find{it.value==nextProgramName}?.key
}

def getTemperatureNext(value, diffIndex) {
	if (getTemperatureScale()=="F") {
		return (value + diffIndex)
	} else {
		def currentTemperatureIndex = state?.list?.temperatureRangeC?.findIndexOf { it == value.toString() }.toInteger()
		def nextTemperature = new BigDecimal(state?.list?.temperatureRangeC[currentTemperatureIndex + diffIndex])
		return nextTemperature
	}
}

def getSetPointLimit( childDevice, limitType ) { 
	if (getTemperatureScale() == "F") {
		return  state?.lookup?.getAt(limitType)?.getAt(childDevice.deviceNetworkId) 
	} else {
		if (limitType == "differenceSetPoint") {
			return  state?.lookup?.getAt(limitType)?.getAt(childDevice.deviceNetworkId)
		} else {
			def limitTemperatureF = state?.lookup?.getAt(limitType)?.getAt(childDevice.deviceNetworkId)
			def limitTemperatureC = new BigDecimal(state?.lookup?.temperatureRangeF?.getAt(limitTemperatureF.toInteger().toString()))
			return limitTemperatureC
		}
	}
}

// Set Away Mode
def setAway(childDevice, awayStatus) {    
	def awayMode = ((awayStatus.toString().equals("away"))?"1":"0")
	//Retrieve program info
	state.data[childDevice.deviceNetworkId].awayMode = awayMode.toString()
	
	def apiQuery = [ 
		awayMode: awayMode.toString(),
		ZoneNumber: getDeviceZone(childDevice),
		TempScale: (getTemperatureScale()=="F")?0:1,
		GatewaySN: getDeviceGatewaySN(childDevice) 
	]
	
	//Set Thermostat Program
	apiPut("/DBAcessService.svc/SetAwayModeNew", [contentType: "application/json; charset=utf-8", requestContentType: "application/json; charset=utf-8", query: apiQuery]) { response ->
		if (response.status == 200) {
			response.data.tStatInfo.each { 
				state.data[childDevice.deviceNetworkId] = [
					temperature: it.Indoor_Temp,
					humidity: it.Indoor_Humidity,
					coolingSetpoint: it.Cool_Set_Point,
					heatingSetpoint: it.Heat_Set_Point,
					thermostatMode: lookupInfo( "thermostatMode", it.Operation_Mode.toString(), true ),
					thermostatFanMode: lookupInfo( "thermostatFanMode", it.Fan_Mode.toString(), true ),
					thermostatOperatingState: lookupInfo( "thermostatOperatingState", it.System_Status.toString(), true ),
					thermostatProgramMode: it.Program_Schedule_Mode,
					thermostatProgramSelection: it.Program_Schedule_Selection,
					awayMode: it.Away_Mode.toString()
				]
			}
		}
	}
	return state.data[childDevice.deviceNetworkId]
}

//API URL
def getApiURL() { 
	def troubleshoot = "false"
	if (settings.troubleshoot == "true") {
		if (!(state.troubleshoot)) state.troubleshoot = now() + 3600000 
		troubleshoot = (state.troubleshoot > now()) ? "true" : "false"
	}
	if (troubleshoot == "true") {
		return "https://services-myicomfort-com-xjor6gavxo3b.runscope.net"
	} else {
		return "https://services.myicomfort.com"
	}
}

//API Authorization header
def getApiAuth() {
	return "Basic " + (settings.username + ":" + settings.password).bytes.encodeBase64()
}

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() > 0 )? settings.polling.toInteger() : 1) * 60000) + 300000) < now()) && canSchedule()) {
		log.info "Scheduling Auto Refresh.."
		schedule("* */" + ((settings.polling.toInteger() > 0 )? settings.polling.toInteger() : 1) + " * * * ?", refresh)
	}
    
	// Force Refresh NOWWW!!!!
	refresh()
    
	//Update rescheduler's last run
	if (!evt) state.polling?.rescheduler = now()
}

Here is my Device Driver:

/**
 *  Lennox iComfort Thermostat
 *
 *  Copyright 2015 Jason Mok
 *
 *  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 : 7/15/2015
 * 
 */
 metadata {
	definition (name: "Lennox iComfort Thermostat", namespace: "copy-ninja", author: "Jason Mok") {
		capability "Thermostat"
		capability "Relative Humidity Measurement"
		capability "Polling"
		capability "Refresh"
		capability "Sensor"
		capability "Temperature Measurement"
		capability "Actuator"
	        
		attribute "thermostatProgram", "string"
	        
		command "heatLevelUp"
		command "heatLevelDown"
		command "coolLevelUp"
		command "coolLevelDown"
		command "switchMode"
		command "switchFanMode"
		command "switchProgram"
		command "setThermostatProgram"
		command "away"
		command "present"
		command "setPresence"
		command "updateThermostatData", ["string"]
	}

	simulator { }

	tiles {
		valueTile("temperature", "device.temperature", width: 2, height: 2) {
			state("temperature", label:'${currentValue}°', 
				backgroundColors:[
					[value: 31, color: "#153591"],
					[value: 44, color: "#1e9cbb"],
					[value: 59, color: "#90d2a7"],
					[value: 74, color: "#44b621"],
					[value: 84, color: "#f1d801"],
					[value: 95, color: "#d04e00"],
					[value: 96, color: "#bc2323"]
				]		
			)
		}
		valueTile("humidity", "device.humidity", inactiveLabel: false) {
			state("humidity", label:'${currentValue}% \nHumidity' , unit: "Humidity",
				backgroundColors:[
					[value: 20, color: "#b2d3f9"],
					[value: 30, color: "#99c5f8"],
					[value: 35, color: "#7fb6f6"],
					[value: 40, color: "#66a8f4"],
					[value: 45, color: "#4c99f3"],
					[value: 50, color: "#328bf1"],
					[value: 55, color: "#197cef"],
					[value: 60, color: "#006eee"],
					[value: 70, color: "#0063d6"],
				]
			)
		}
		standardTile("thermostatOperatingState", "device.thermostatOperatingState", canChangeIcon: false, decoration: "flat") {
			state("idle",            icon: "st.thermostat.ac.air-conditioning", label: "Idle")
			state("waiting",         icon: "st.thermostat.ac.air-conditioning", label: "Waiting")
			state("heating",         icon: "st.thermostat.heating")
			state("cooling",         icon: "st.thermostat.cooling")
			state("emergency heat",  icon: "st.thermostat.emergency-heat")
			
		}
		standardTile("thermostatMode", "device.thermostatMode", canChangeIcon: false, inactiveLabel: false, decoration: "flat") {
			state("auto",      action:"switchMode",    nextState: "auto",      icon: "st.thermostat.auto")
			state("heat",      action:"switchMode",    nextState: "heat",      icon: "st.thermostat.heat")
			state("cool",      action:"switchMode",    nextState: "cool",      icon: "st.thermostat.cool")
			state("off",       action:"switchMode",    nextState: "off",       icon: "st.thermostat.heating-cooling-off")
			state("emergency heat",       action:"switchMode",    nextState: "emergency heat",       icon: "st.thermostat.emergency-heat")
			state("program",   action:"switchMode",    nextState: "program",   icon: "st.thermostat.ac.air-conditioning", label: "Program")
		}
		standardTile("thermostatFanMode", "device.thermostatFanMode", canChangeIcon: false, inactiveLabel: false, decoration: "flat") {
			state("auto",      action:"switchFanMode", nextState: "auto",       icon: "st.thermostat.fan-auto")
			state("on",        action:"switchFanMode", nextState: "on",         icon: "st.thermostat.fan-on")
			state("circulate", action:"switchFanMode", nextState: "circulate",  icon: "st.thermostat.fan-circulate")
			state("off",       action:"switchFanMode", nextState: "off",        icon: "st.thermostat.fan-off")
		}
		standardTile("heatLevelUp", "device.switch", canChangeIcon: false, inactiveLabel: true, decoration: "flat" ) {
			state("heatLevelUp",   action:"heatLevelUp",   icon:"st.thermostat.thermostat-up", backgroundColor:"#F7C4BA")
		}        
		valueTile("heatingSetpoint", "device.heatingSetpoint", inactiveLabel: false) {
			state("heat", label:'${currentValue}°',
				backgroundColors:[
					[value: 40, color: "#f49b88"],
					[value: 50, color: "#f28770"],
					[value: 60, color: "#f07358"],
					[value: 70, color: "#ee5f40"],
					[value: 80, color: "#ec4b28"],
					[value: 90, color: "#ea3811"]
				]
			)
		}
		standardTile("heatLevelDown", "device.switch", canChangeIcon: false, inactiveLabel: true, decoration: "flat") {
			state("heatLevelDown", action:"heatLevelDown", icon:"st.thermostat.thermostat-down", backgroundColor:"#F7C4BA")
		}        
		standardTile("coolLevelUp", "device.switch", canChangeIcon: false, inactiveLabel: true, decoration: "flat" ) {
			state("coolLevelUp",   action:"coolLevelUp",   icon:"st.thermostat.thermostat-up" , backgroundColor:"#BAEDF7")
		}
		valueTile("coolingSetpoint", "device.coolingSetpoint", inactiveLabel: false) {
			state("cool", label:'${currentValue}°',
				backgroundColors:[
					[value: 40, color: "#88e1f4"],
					[value: 50, color: "#70dbf2"],
					[value: 60, color: "#58d5f0"],
					[value: 70, color: "#40cfee"],
					[value: 80, color: "#28c9ec"],
					[value: 90, color: "#11c3ea"]
				]
			)
		}
		standardTile("coolLevelDown", "device.switch", canChangeIcon: false, inactiveLabel: true, decoration: "flat") {
			state("coolLevelDown", action:"coolLevelDown", icon:"st.thermostat.thermostat-down", backgroundColor:"#BAEDF7")
		}
		valueTile("thermostatProgram", "device.thermostatProgram", inactiveLabel: false, decoration: "flat") {
			state("program", action:"switchProgram", label: '${currentValue}')
		}
		standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
			state("default", action:"refresh.refresh",        icon:"st.secondary.refresh")
		}
		standardTile("presence", "device.presence", inactiveLabel: false, decoration: "flat") { 
			state("present", label:"present", action:"switchPresenceMode", nextState: "present", icon: "st.Home.home2")
			state("away", label:"away", action:"switchPresenceMode", nextState: "away", icon: "st.Transportation.transportation5")
		} 
    
   	main "temperature"
		details(["temperature", "humidity", "thermostatOperatingState",  "heatLevelUp", "coolLevelUp", "thermostatFanMode", "heatingSetpoint", "coolingSetpoint", "thermostatMode", "heatLevelDown", "coolLevelDown", "thermostatProgram", "presence", "refresh" ])
	}
}

def parse(String description) { }

def refresh() { parent.refresh() }

def poll() { updateThermostatData(parent.getDeviceStatus(this.device)) }

def updateThermostatData(thermostatData) {		
def ValChanged = false
	def thermostatProgramSelection
	def thermostatProgramMode = (device.currentValue("thermostatProgram") == "Manual")?"0":"1"
	def thermostatMode = (device.currentState("thermostatMode")?.value)?device.currentState("thermostatMode")?.value:"auto"
log.debug "UpdateThermostatData: " + thermostatData
	thermostatData.each { name, value -> 
		if (name == "temperature" || name == "coolingSetpoint" || name == "heatingSetpoint") {
			sendEvent(name: name, value: value , unit: getTemperatureScale())
			log.debug "Sending Event: " + [name, value, getTemperatureScale()]
		} else if (name == "thermostatProgramMode") {
			thermostatProgramMode = value
        ValChanged = true
		} else if (name == "thermostatProgramSelection") {
			thermostatProgramSelection = value
        ValChanged = true
		} else if (name == "thermostatMode") {
			thermostatMode = value
        ValChanged = true
		} else if (name == "awayMode") {
			if (value == "1") {
				sendEvent(name: "presence", value: "away")
            log.debug "Sending Event: " + ["presence", "away"]
			} else {
				sendEvent(name: "presence", value: "present")
            log.debug "Sending Event: " + ["presence", "present"]
			}
		} else {
			sendEvent(name: name, value: value, displayed: false)
			log.debug "Sending Misc Event: " + [name, value]
		}
	}

	if (true == ValChanged){
    if (thermostatProgramMode == "0") {
        sendEvent(name: "thermostatMode", value: thermostatMode)
        sendEvent(name: "thermostatProgram", value: "Manual")
        log.debug "Sending Event: " + ["thermostatMode", thermostatMode]
        log.debug "Sending Event: " + ["thermostatProgram", "Manual"]			
    } else {
        sendEvent(name: "thermostatMode", value: "program") 
        log.debug "Sending Event: " + ["thermostatMode", "program"]
        if (thermostatProgramSelection) {
            sendEvent(name: "thermostatProgram", value: parent.getThermostatProgramName(this.device, thermostatProgramSelection).toString().replaceAll("\r","").replaceAll("\n",""))
            log.debug "Sending Event: " + ["thermostatProgram", parent.getThermostatProgramName(this.device, thermostatProgramSelection).replaceAll("\r","").replaceAll("\n","")]
        }
    }
}
}

def setHeatingSetpoint(Number heatingSetpoint) {
	// define maximum & minimum for heating setpoint 
	def minHeat = parent.getSetPointLimit(this.device, "heatingSetPointLow")
	def maxHeat = parent.getSetPointLimit(this.device, "heatingSetPointHigh")
	def diffHeat = parent.getSetPointLimit(this.device, "differenceSetPoint").toInteger()
	heatingSetpoint = (heatingSetpoint < minHeat)? minHeat : heatingSetpoint
	heatingSetpoint = (heatingSetpoint > maxHeat)? maxHeat : heatingSetpoint

	// check cooling setpoint 
	def heatSetpointDiff = parent.getTemperatureNext(heatingSetpoint, diffHeat)
	def coolingSetpoint = device.currentValue("coolingSetpoint")
	coolingSetpoint = (heatSetpointDiff > coolingSetpoint)? heatSetpointDiff : coolingSetpoint   
	setThermostatData([coolingSetpoint: coolingSetpoint, heatingSetpoint: heatingSetpoint])
}

def setCoolingSetpoint(Number coolingSetpoint) { 
	// define maximum & minimum for cooling setpoint 
	def minCool = parent.getSetPointLimit(this.device, "coolingSetPointLow")
	def maxCool = parent.getSetPointLimit(this.device, "coolingSetPointHigh")
	def diffHeat = parent.getSetPointLimit(this.device, "differenceSetPoint").toInteger()
	coolingSetpoint = (coolingSetpoint < minCool)? minCool : coolingSetpoint
	coolingSetpoint = (coolingSetpoint > maxCool)? maxCool : coolingSetpoint
	
	// check heating setpoint 
	def coolSetpointDiff = parent.getTemperatureNext(coolingSetpoint, (diffHeat * -1))
	def heatingSetpoint = device.currentValue("heatingSetpoint")
	heatingSetpoint = (coolSetpointDiff < heatingSetpoint)? coolSetpointDiff : heatingSetpoint
	setThermostatData([coolingSetpoint: coolingSetpoint, heatingSetpoint: heatingSetpoint])
}

def switchMode() {
	def currentMode = device.currentState("thermostatMode")?.value
	switch (currentMode) {
		case "off":
			setThermostatMode("heat")
			break
		case "heat":
			setThermostatMode("cool")
			break
		case "cool":
			setThermostatMode("auto")
			break
		case "auto":
			setThermostatMode("program")
			break
		case "program":
			setThermostatMode("off")
			break
		default:
			setThermostatMode("auto")
	}
	if(!currentMode) { setThermostatMode("auto") }
}

def off()           { setThermostatMode("off") }
def heat()          { setThermostatMode("heat") }
def emergencyHeat() { setThermostatMode("emergency heat") }
def cool()          { setThermostatMode("cool") }
def auto()          { setThermostatMode("auto") }

def switchFanMode() {
	def currentFanMode = device.currentState("thermostatFanMode")?.value
	switch (currentFanMode) {
		case "auto":
			setThermostatFanMode("on")
			break
		case "on":
			setThermostatFanMode("off")
			break
    case "off":
			setThermostatFanMode("circulate")
			break
		case "circulate":
			setThermostatFanMode("auto")
			break
		default:
			setThermostatFanMode("auto")
	}
	if(!currentFanMode) { setThermostatFanMode("auto") }
}

def setThermostatMode(mode) { 
	def thermostatProgramMode = (device.currentValue("thermostatProgram") == "Manual")?"0":"1"
	if (thermostatProgramMode != "0") {
		parent.setProgram(this.device, "0", state.thermostatProgramSelection)
		setThermostatData([ thermostatProgramMode: "0", thermostatMode: mode ])
	} else {
		setThermostatData([ thermostatMode: mode ])
	}
}
def fanOff()       { setThermostatFanMode("off")}
def fanOn()        { setThermostatFanMode("on") }
def fanAuto()      { setThermostatFanMode("auto") }
def fanCirculate() { setThermostatFanMode("circulate") }
def setThermostatFanMode(fanMode) {	setThermostatData([ thermostatFanMode: fanMode ]) }
def heatLevelUp() {	
	def heatingSetpoint = device.currentValue("heatingSetpoint")
	setHeatingSetpoint(parent.getTemperatureNext(heatingSetpoint, 1))   
}
def heatLevelDown() { 
	def heatingSetpoint = device.currentValue("heatingSetpoint")
	setHeatingSetpoint(parent.getTemperatureNext(heatingSetpoint, -1))  
}
def coolLevelUp() { 
	def coolingSetpoint = device.currentValue("coolingSetpoint")
	setCoolingSetpoint(parent.getTemperatureNext(coolingSetpoint, 1))  
}
def coolLevelDown() { 
	def coolingSetpoint = device.currentValue("coolingSetpoint")
	setCoolingSetpoint(parent.getTemperatureNext(coolingSetpoint, -1))  
}

def switchProgram() {
	def currentProgram = device.currentValue("thermostatProgram")
	def nextProgramID = parent.getThermostatProgramNext(this.device, currentProgram)
	setThermostatProgram(nextProgramID)
}

def setThermostatProgram(programID) {
	updateThermostatData([thermostatProgramMode: "1", thermostatProgramSelection: programID])
	def thermostatResult = parent.setProgram(this.device, "1", programID)
	updateThermostatData(thermostatResult)
}

def setThermostatData(thermostatData) {
	updateThermostatData(thermostatData)
	def thermostatResult = parent.setThermostat(this.device, thermostatData)
	updateThermostatData(thermostatResult)
}

def away() { setPresence("away") } 
def present() { setPresence("present") } 
def switchPresenceMode() {
	def currentPresenceMode = device.currentState("awayMode")?.value
	switch (currentPresenceMode) {
		case "present":
			setPresence("away")
			break
		case "away":
			setPresence("present")
			break
		default:
			setPresence("present")
	}
}

def setPresence(awayStatus) {
	def awayMode = (awayStatus.toString().equals("away"))?1:0
	updateThermostatData([awayMode: awayMode.toString()])
log.debug "Calling setAway:" + this.device + " " + awayStatus + " " + awayMode.toString()
	def thermostatResult = parent.setAway(this.device, awayStatus)
	updateThermostatData(thermostatResult)   
}
1 Like

When I try it I get the following error? I don't know what it means but I'm willing to learn. Thanks

app:9432019-01-04 11:31:40.280 am errorgroovy.lang.MissingMethodException: No signature of method: java.lang.String.encodeAsBase64() is applicable for argument types: () values: [] Possible solutions: decodeBase64() on line 524 (prefListDevice)

Try what HE suggests: change encode to decode on line 524, and check result.

Use the code provided by @brianspranger above. The problem with encoding is fixed in it already.

@aryvin thanks for the info. I believe it is working. Thanks I having had time to really look at it, but I will later today. Thanks @brianspranger also.

It's working. Thank you @brianspranger for your help porting the app.

@aryvin @leeonestop

I have found that the driver/app works but it doesn't refresh the status.

I have fixed that and when I get back on a PC I will get the code up on github and start a new thread for people to follow.

@aryvin @leeonestop

Here is my latest Lennox iComfort Driver and App.

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.