OK I have got something working.
There is still some sort of problem after you do the âConfigure New Deviceâ, as it shows up as a Thing, so I had to change the device to SRT321 Thermostat in the IDE, but it seems to be mostly working.
I have added a helper app to select a switch to be controlled by the thermostat⌠sore point as I maintain that SmartThings are wrong in their design and devices should be able to control / select other devices in their preferencesâŚ
/* SRT321 Device Handler by MeavyDev */
metadata
{
definition (name: âSRT321 Thermostatâ, namespace: âmeavydevâ, author: âMeavyDevâ)
{
capability "Temperature Measurement"
capability "Thermostat"
capability "Configuration"
capability "Polling"
capability âSensorâ
command "switchMode"
command "quickSetHeat"
command "setTemperature"
command "setupDevice"
fingerprint deviceId: "0x0800"
fingerprint inClusters: "0x72, 0x86, 0x80, 0x84, 0x31, 0x43, 0x85, 0x70, 0xEF, 0x40, 0x25"
}
// simulator metadata
simulator
{
}
tiles (scale: 2)
{
multiAttributeTile(name:"heatingSetpoint", type: "thermostat", width: 6, height: 4, canChangeIcon: true)
{
tileAttribute ("device.heatingSetpoint", key: "PRIMARY_CONTROL")
{
attributeState("default", unit:"dC", label:'${currentValue}°')
}
tileAttribute("device.heatingSetpoint", key: "VALUE_CONTROL")
{
attributeState("default", action: "setTemperature")
}
tileAttribute("device.temperature", key: "SECONDARY_CONTROL")
{
attributeState("default", label:'${currentValue}', unit:"dC")
}
tileAttribute("device.thermostatMode", key: "THERMOSTAT_MODE")
{
attributeState("off", label:'${name}')
attributeState("heat", label:'${name}')
}
tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT")
{
attributeState("default", label:'${currentValue}', unit:"dC")
}
tileAttribute("device.thermostatMode", key: "OPERATING_STATE")
{
attributeState("off", backgroundColor:"#44b621")
attributeState("heat", backgroundColor:"#ffa81e")
}
}
valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat", width: 2, height: 2)
{
tileAttribute ("device.battery", key: "PRIMARY_CONTROL")
{
state "battery", label:'${currentValue}% battery', unit:""
}
}
standardTile("refresh", "device.thermostatMode", inactiveLabel: false, decoration: "flat", width: 2, height: 2)
{
state "default", action:"polling.poll", icon:"st.secondary.refresh"
}
standardTile("configure", "device.configure", inactiveLabel: false, decoration: "flat", width: 2, height: 2)
{
state "configure", label:'', action:"configuration.configure", icon:"st.secondary.configure"
}
}
main "heatingSetpoint"
details(["heatingSetpoint", "battery", "refresh", "configure", "temperature", "mode"])
}
preferences
{
section (âWakeup intervalâŚâ)
{
input âuserWakeUpIntervalâ, ânumberâ, title: âWake Up Interval (seconds)â, description: âDefault 3600 sec (10 minutes - 7 days)â, defaultValue: â3600â, required: false, displayDuringSetup: true
}
// This is the "Device Network Id" displayed in the IDE
section ("Associated z-wave switch network Id...")
{
input "userAssociatedDevice", "string", title: "Associated switch ZWave network Id (hex)", required: false, displayDuringSetup: true
}
}
def parse(String description)
{
// log.debug âParse $descriptionâ
def result = zwaveEvent(zwave.parse(description, [0x72:1, 0x86:1, 0x80:1, 0x84:2, 0x31:1, 0x43:1, 0x85:1, 0x70:1, 0xEF:1, 0x40:1, 0x25:1]))
if (!result)
{
log.warn "Parse returned null"
return null
}
// log.debug "Parse returned $result"
result
}
def configure()
{
state.configNeeded = true
}
def zwaveEvent(physicalgraph.zwave.commands.thermostatmodev1.ThermostatModeSet cmd)
{
def map = [:]
switch (cmd.mode) {
case physicalgraph.zwave.commands.thermostatmodev1.ThermostatModeSet.MODE_OFF:
map.value = "off"
break
case physicalgraph.zwave.commands.thermostatmodev1.ThermostatModeSet.MODE_HEAT:
map.value = âheatâ
}
map.name = "thermostatMode"
createEvent(map)
}
// Event Generation
def zwaveEvent(physicalgraph.zwave.commands.thermostatsetpointv1.ThermostatSetpointReport cmd)
{
def map = [:]
map.value = cmd.scaledValue.toString()
map.unit = cmd.scale == 1 ? âFâ : "C"
map.displayed = false
switch (cmd.setpointType) {
case 1:
map.name = "heatingSetpoint"
break;
default:
return [:]
}
// So we can respond with same format
state.size = cmd.size
state.scale = cmd.scale
state.precision = cmd.precision
createEvent(map)
}
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv1.SensorMultilevelReport cmd)
{
def map = [:]
map.value = cmd.scaledSensorValue.toString()
map.unit = cmd.scale == 1 ? âFâ : "C"
map.name = "temperature"
createEvent(map)
}
// Battery powered devices can be configured to periodically wake up and
// check in. They send this command and stay awake long enough to receive
// commands, or until they get a WakeUpNoMoreInformation command that
// instructs them that there are no more commands to receive and they can
// stop listening.
def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpNotification cmd)
{
def map = [name:âthermostatWakeUpâ, value: â${device.displayName} woke upâ, isStateChange: true]
def event = createEvent(map)
def cmds = updateIfNeeded()
cmds << zwave.wakeUpV2.wakeUpNoMoreInformation().format()
log.debug "Wakeup $cmds"
[event, response(delayBetween(cmds, 1000))]
}
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd)
{
def map = [ name: âbatteryâ, unit: â%â ]
if (cmd.batteryLevel == 0xFF)
{ // Special value for low battery alert
map.value = 1
map.descriptionText = "${device.displayName} has a low battery"
map.isStateChange = true
}
else
{
map.value = cmd.batteryLevel
log.debug (âBattery: $cmd.batteryLevelâ)
}
// Store time of last battery update so we donât ask every wakeup, see WakeUpNotification handler
state.lastbatt = new Date().time
createEvent(map)
}
def zwaveEvent(physicalgraph.zwave.commands.thermostatmodev1.ThermostatModeReport cmd)
{
def map = [:]
switch (cmd.mode) {
case physicalgraph.zwave.commands.thermostatmodev1.ThermostatModeReport.MODE_OFF:
map.value = "off"
break
case physicalgraph.zwave.commands.thermostatmodev1.ThermostatModeReport.MODE_HEAT:
map.value = "heat"
break
}
map.name = "thermostatMode"
createEvent(map)
}
def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpIntervalCapabilitiesReport cmd)
{
def map = [ name: âdefaultWakeUpIntervalâ, unit: âsecondsâ ]
map.value = cmd.defaultWakeUpIntervalSeconds
map.displayed = false
state.defaultWakeUpInterval = cmd.defaultWakeUpIntervalSeconds
createEvent(map)
}
def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpIntervalReport cmd)
{
def map = [ name: âreportedWakeUpIntervalâ, unit: âsecondsâ ]
map.value = cmd.seconds
map.displayed = false
createEvent(map)
}
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd)
{
log.debug âZwave event received: $cmdâ
}
def zwaveEvent(physicalgraph.zwave.Command cmd)
{
log.warn âUnexpected zwave command $cmdâ
}
// Command Implementations
def poll()
{
// state.refreshNeeded = true
}
def refresh()
{
state.refreshNeeded = true
}
def quickSetHeat(degrees)
{
setHeatingSetpoint(degrees, 1000)
log.debug(âDegrees at quicksetheat: $degreesâ)
}
def setTempUp()
{
def newtemp = device.currentValue(âheatingSetpointâ).toInteger() + 1
log.debug "Setting temp up: $newtemp"
sendEvent(name: âheatingSetpointâ, value: newtemp)
quickSetHeat(newtemp)
}
def setTempDown()
{
def newtemp = device.currentValue(âheatingSetpointâ).toInteger() - 1
log.debug "Setting temp down: $newtemp"
sendEvent(name: âheatingSetpointâ, value: newtemp)
quickSetHeat(newtemp)
}
def setTemperature(temp)
{
log.debug "setTemperature $temp"
sendEvent(name: âheatingSetpointâ, value: temp)
quickSetHeat(temp)
}
def setHeatingSetpoint(degrees, delay = 30000)
{
setHeatingSetpoint(degrees.toDouble(), delay)
log.debug(âDegrees at setheatpoint: $degreesâ)
}
def setHeatingSetpoint(Double degrees, Integer delay = 30000)
{
log.trace "setHeatingSetpoint($degrees, $delay)"
def deviceScale = state.scale ?: 1
def deviceScaleString = deviceScale == 2 ? âCâ : "F"
def locationScale = getTemperatureScale()
def p = (state.precision == null) ? 1 : state.precision
def convertedDegrees
if (locationScale == "C" && deviceScaleString == "F")
{
convertedDegrees = celsiusToFahrenheit(degrees)
}
else if (locationScale == "F" && deviceScaleString == "C")
{
convertedDegrees = fahrenheitToCelsius(degrees)
}
else
{
convertedDegrees = degrees
}
log.trace "setHeatingSetpoint scale: $deviceScale precision: $p setpoint: $convertedDegrees"
state.deviceScale = deviceScale
state.p = p
state.convertedDegrees = convertedDegrees
state.updateNeeded = true
thermostatMode
}
private getStandardDelay()
{
1000
}
def updateIfNeeded()
{
def cmds = []
// Only ask for battery if we haven't had a BatteryReport in a while
if (!state.lastbatt || (new Date().time) - state.lastbatt > 24*60*60*1000)
{
log.debug "Getting battery state"
cmds << zwave.batteryV1.batteryGet().format()
}
if (state.refreshNeeded)
{
log.debug "Refresh"
cmds << zwave.sensorMultilevelV1.sensorMultilevelGet().format() // current temperature
cmds << zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: physicalgraph.zwave.commands.thermostatsetpointv1.ThermostatSetpointSet.SETPOINT_TYPE_HEATING_1).format()
cmds << zwave.thermostatModeV1.thermostatModeGet().format()
cmds << zwave.thermostatOperatingStateV1.thermostatOperatingStateGet().format()
cmds << zwave.thermostatModeV1.thermostatModeSupportedGet().format()
state.refreshNeeded = false
}
if (state.updateNeeded)
{
log.debug "Update"
cmds << zwave.thermostatSetpointV1.thermostatSetpointSet(setpointType: physicalgraph.zwave.commands.thermostatsetpointv1.ThermostatSetpointSet.SETPOINT_TYPE_HEATING_1, scale: state.deviceScale, precision: state.p, scaledValue: state.convertedDegrees).format()
state.updateNeeded = false
}
if (state.configNeeded)
{
log.debug "Config"
state.configNeeded = false
// Nodes controlled by Thermostat Mode Set - not sure this is needed?
cmds << zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:[zwaveHubNodeId]).format()
// Set hub to get battery reports / warnings
cmds << zwave.associationV1.associationSet(groupingIdentifier:3, nodeId:[zwaveHubNodeId]).format()
// Set hub to get set point reports
cmds << zwave.associationV1.associationSet(groupingIdentifier:4, nodeId:[zwaveHubNodeId]).format()
// Set hub to get multi-level sensor reports (defaults to temperature changes of > 1C)
cmds << zwave.associationV1.associationSet(groupingIdentifier:5, nodeId:[zwaveHubNodeId]).format()
// set the temperature sensor On
cmds << zwave.configurationV1.configurationSet(configurationValue: [0xff], parameterNumber: 1, size: 1).format()
log.debug "association $state.association user: $userAssociatedDevice"
int nodeID = getAssociatedId(state.association)
// If user has changed the switch association, send the new assocation to the device
if (nodeID != -1)
{
log.debug "Setting associated device $nodeID"
cmds << zwave.associationV1.associationSet(groupingIdentifier: 2, nodeId: nodeID).format()
}
def userWake = getUserWakeUp(userWakeUpInterval)
// If user has changed userWakeUpInterval, send the new interval to the device
if (state.wakeUpInterval != userWake)
{
state.wakeUpInterval = userWake
log.debug "Setting New WakeUp Interval to: " + state.wakeUpInterval
cmds << zwave.wakeUpV2.wakeUpIntervalSet(seconds:state.wakeUpInterval, nodeid:zwaveHubNodeId).format()
cmds << zwave.wakeUpV2.wakeUpIntervalGet().format()
}
cmds << zwave.thermostatModeV1.thermostatModeSupportedGet().format()
}
if (cmds.size() > 0)
{
cmds << "delay 2000"
}
cmds
}
private getUserWakeUp(userWake)
{
if (!userWake)
{
userWake = â3600â // set default 1 hr if no user preference
}
// make sure user setting is within valid range for device
if (userWake.toInteger() < 60)
{
userWake = â600â // 10 minutes - Mininum
}
if (userWake.toInteger() > 36000)
{
userWake = â36000â // 10 hours - Maximum
}
return userWake.toInteger()
}
// Get the Z-Wave Id of the binary switch the user wants the thermostat to control
// -1 if no association set
int getAssociatedId(association)
{
int associatedState = -1
int associatedUser = -1
log.debug "getAssociatedId $association"
if (association != null && association != ââ)
{
associatedState = association.toInteger()
log.debug âState $association $associatedStateâ
}
if (userAssociatedDevice != null && userAssociatedDevice != ââ)
{
try
{
associatedUser = Integer.parseInt(userAssociatedDevice, 16)
}
catch (Exception e)
{
}
log.debug âuserDev $userAssociatedDevice $associatedUserâ
}
// Use the app associated switch id if it exists, otherwise the device preference
return associatedState != -1 ? associatedState : associatedUser
}
// Called from the SRT321 App with the Z-Wave switch network ID
// How long before SmartThings realises that having device preferences
// with input â*â âcapability.switchâ is reasonable???
void setupDevice(value)
{
state.association = "$value"
int val = Integer.parseInt(value)
String hex = Integer.toHexString(val)
log.debug "Setting associated device Id $value Hex $hex"
settings.userAssociatedDevice = hex
state.configNeeded = true
}
/* SRT321 Helper App */
definition(
name: âSRT321 Appâ,
namespace: âmeavydevâ,
author: âMeavyDevâ,
description: âAssociates the SRT321 with a switchâ,
category: âMy Appsâ,
iconUrl: âhttps://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.pngâ,
iconX2Url: âhttps://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.pngâ)
preferences
{
input âassociatedSwitchâ, âcapability.switchâ, title: âSwitch?â, multiple: false, required: true
input âthermostatSelectedâ, âcapability.thermostatâ, title: âSRT321 Thermostat?â, multiple: false, required: true
}
def installed()
{
def networkId = " â+associatedSwitch.deviceNetworkId+â â
def devId = Integer.parseInt(networkId.replaceAll(â ", ââ), 16)
log.debug "Installed - Id $devId"
state.association = devId
runIn(1, handler)
}
def updated()
{
def networkId = " â+associatedSwitch.deviceNetworkId+â â
def devId = Integer.parseInt(networkId.replaceAll(â ", ââ), 16)
log.debug "Updated - Id $devId"
state.association = devId.toString()
runIn(1, handler)
}
// No real need for a handler, but this was avoiding crashes
// Send the âDevice Network Idâ to the SRT321 thermostat so it can control the switch
// How long before SmartThings realises that having device preferences
// with input â*â âcapability.switchâ is reasonable???
def handler()
{
log.debug âSetting thermostat associationâ
thermostatSelected.setupDevice(state.association)
}