mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-08 05:31:56 +00:00
Merge pull request #2192 from SmartThingsCommunity/staging
Rolling up staging to production
This commit is contained in:
@@ -73,7 +73,7 @@ metadata {
|
||||
[value: 64, color: "#44B621"],
|
||||
[value: 80, color: "#3D79D9"],
|
||||
[value: 96, color: "#0A50C2"]
|
||||
]
|
||||
], icon:"st.Weather.weather12"
|
||||
}
|
||||
|
||||
valueTile("maxHum", "device.maxHum", canChangeIcon: false, canChangeBackground: false) {
|
||||
|
||||
@@ -27,13 +27,9 @@ Works with:
|
||||
## Device Health
|
||||
|
||||
Aeon Labs MultiSensor (Gen 5) is polled by the hub.
|
||||
As of hubCore version 0.14.38 the hub sends up reports every 15 minutes regardless of whether the state changed.
|
||||
Device-Watch allows 2 check-in misses from device plus some lag time. So Check-in interval = (2*15 + 2)mins = 32 mins.
|
||||
Not to mention after going OFFLINE when the device is plugged back in, it might take a considerable amount of time for
|
||||
the device to appear as ONLINE again. This is because if this listening device does not respond to two poll requests in a row,
|
||||
it is not polled for 5 minutes by the hub. This can delay up the process of being marked ONLINE by quite some time.
|
||||
Aeon MultiSensor Gen5 reports in once every hour.
|
||||
|
||||
* __32min__ checkInterval
|
||||
* __122min__ checkInterval
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
||||
@@ -100,12 +100,12 @@ metadata {
|
||||
|
||||
def installed(){
|
||||
// Device-Watch simply pings if no device events received for 32min(checkInterval)
|
||||
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
|
||||
sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
|
||||
}
|
||||
|
||||
def updated(){
|
||||
// Device-Watch simply pings if no device events received for 32min(checkInterval)
|
||||
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
|
||||
sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
|
||||
}
|
||||
|
||||
def parse(String description)
|
||||
|
||||
@@ -28,13 +28,9 @@ Works with:
|
||||
## Device Health
|
||||
|
||||
Aeon Labs MultiSensor is polled by the hub.
|
||||
As of hubCore version 0.14.38 the hub sends up reports every 15 minutes regardless of whether the state changed.
|
||||
Device-Watch allows 2 check-in misses from device plus some lag time. So Check-in interval = (2*15 + 2)mins = 32 mins.
|
||||
Not to mention after going OFFLINE when the device is plugged back in, it might take a considerable amount of time for
|
||||
the device to appear as ONLINE again. This is because if this listening device does not respond to two poll requests in a row,
|
||||
it is not polled for 5 minutes by the hub. This can delay up the process of being marked ONLINE by quite some time.
|
||||
Aeon MultiSensor reports in once every hour.
|
||||
|
||||
* __32min__ checkInterval
|
||||
* __122min__ checkInterval
|
||||
|
||||
## Battery Specification
|
||||
|
||||
|
||||
@@ -96,12 +96,12 @@ metadata {
|
||||
|
||||
def installed(){
|
||||
// Device-Watch simply pings if no device events received for 32min(checkInterval)
|
||||
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
|
||||
sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
|
||||
}
|
||||
|
||||
def updated(){
|
||||
// Device-Watch simply pings if no device events received for 32min(checkInterval)
|
||||
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
|
||||
sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
|
||||
}
|
||||
|
||||
// Parse incoming device messages to generate events
|
||||
|
||||
@@ -6,7 +6,6 @@ metadata {
|
||||
capability "Relative Humidity Measurement"
|
||||
capability "Thermostat"
|
||||
capability "Battery"
|
||||
capability "Configuration"
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
capability "Health Check"
|
||||
@@ -15,161 +14,173 @@ metadata {
|
||||
|
||||
command "switchMode"
|
||||
command "switchFanMode"
|
||||
command "quickSetCool"
|
||||
command "quickSetHeat"
|
||||
command "lowerHeatingSetpoint"
|
||||
command "raiseHeatingSetpoint"
|
||||
command "lowerCoolSetpoint"
|
||||
command "raiseCoolSetpoint"
|
||||
|
||||
fingerprint deviceId: "0x08", inClusters: "0x43,0x40,0x44,0x31,0x80,0x85,0x60"
|
||||
fingerprint mfr:"0098", prod:"6401", model:"0107", deviceJoinName: "2Gig CT100 Programmable Thermostat"
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
simulator {
|
||||
status "off" : "command: 4003, payload: 00"
|
||||
status "heat" : "command: 4003, payload: 01"
|
||||
status "cool" : "command: 4003, payload: 02"
|
||||
status "auto" : "command: 4003, payload: 03"
|
||||
status "emergencyHeat" : "command: 4003, payload: 04"
|
||||
|
||||
status "fanAuto" : "command: 4403, payload: 00"
|
||||
status "fanOn" : "command: 4403, payload: 01"
|
||||
status "fanCirculate" : "command: 4403, payload: 06"
|
||||
|
||||
status "heat 60" : "command: 4303, payload: 01 09 3C"
|
||||
status "heat 72" : "command: 4303, payload: 01 09 48"
|
||||
|
||||
status "cool 76" : "command: 4303, payload: 02 09 4C"
|
||||
status "cool 80" : "command: 4303, payload: 02 09 50"
|
||||
|
||||
status "temp 58" : "command: 3105, payload: 01 2A 02 44"
|
||||
status "temp 62" : "command: 3105, payload: 01 2A 02 6C"
|
||||
status "temp 78" : "command: 3105, payload: 01 2A 03 0C"
|
||||
status "temp 86" : "command: 3105, payload: 01 2A 03 34"
|
||||
|
||||
status "idle" : "command: 4203, payload: 00"
|
||||
status "heating" : "command: 4203, payload: 01"
|
||||
status "cooling" : "command: 4203, payload: 02"
|
||||
|
||||
// reply messages
|
||||
reply "2502": "command: 2503, payload: FF"
|
||||
}
|
||||
|
||||
tiles {
|
||||
valueTile("temperature", "device.temperature", width: 2, height: 2) {
|
||||
state("temperature", label:'${currentValue}°',
|
||||
backgroundColors:[
|
||||
[value: 32, color: "#153591"],
|
||||
[value: 44, color: "#1e9cbb"],
|
||||
[value: 59, color: "#90d2a7"],
|
||||
[value: 74, color: "#44b621"],
|
||||
[value: 84, color: "#f1d801"],
|
||||
[value: 92, color: "#d04e00"],
|
||||
[value: 98, color: "#bc2323"]
|
||||
]
|
||||
)
|
||||
multiAttributeTile(name:"temperature", type:"generic", width:3, height:2, canChangeIcon: true) {
|
||||
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
|
||||
attributeState("temperature", label:'${currentValue}°', icon: "st.alarm.temperature.normal",
|
||||
backgroundColors:[
|
||||
// Celsius
|
||||
[value: 0, color: "#153591"],
|
||||
[value: 7, color: "#1e9cbb"],
|
||||
[value: 15, color: "#90d2a7"],
|
||||
[value: 23, color: "#44b621"],
|
||||
[value: 28, color: "#f1d801"],
|
||||
[value: 35, color: "#d04e00"],
|
||||
[value: 37, color: "#bc2323"],
|
||||
// Fahrenheit
|
||||
[value: 40, 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"]
|
||||
]
|
||||
)
|
||||
}
|
||||
tileAttribute("device.batteryIcon", key: "SECONDARY_CONTROL") {
|
||||
attributeState "ok_battery", label:'${currentValue}%', icon:"st.arlo.sensor_battery_4"
|
||||
attributeState "low_battery", label:'Low Battery', icon:"st.arlo.sensor_battery_0"
|
||||
}
|
||||
}
|
||||
standardTile("mode", "device.thermostatMode", inactiveLabel: false, decoration: "flat") {
|
||||
state "off", label:'${name}', action:"switchMode", nextState:"to_heat"
|
||||
state "heat", label:'${name}', action:"switchMode", nextState:"to_cool"
|
||||
state "cool", label:'${name}', action:"switchMode", nextState:"..."
|
||||
state "auto", label:'${name}', action:"switchMode", nextState:"..."
|
||||
state "emergency heat", label:'${name}', action:"switchMode", nextState:"..."
|
||||
state "to_heat", label: "heat", action:"switchMode", nextState:"to_cool"
|
||||
state "to_cool", label: "cool", action:"switchMode", nextState:"..."
|
||||
state "...", label: "...", action:"off", nextState:"off"
|
||||
standardTile("mode", "device.thermostatMode", width:2, height:2, inactiveLabel: false, decoration: "flat") {
|
||||
state "off", action:"switchMode", nextState:"to_heat", icon: "st.thermostat.heating-cooling-off"
|
||||
state "heat", action:"switchMode", nextState:"to_cool", icon: "st.thermostat.heat"
|
||||
state "cool", action:"switchMode", nextState:"...", icon: "st.thermostat.cool"
|
||||
state "auto", action:"switchMode", nextState:"...", icon: "st.thermostat.auto"
|
||||
state "emergency heat", action:"switchMode", nextState:"...", icon: "st.thermostat.emergency-heat"
|
||||
state "to_heat", action:"switchMode", nextState:"to_cool", icon: "st.secondary.secondary"
|
||||
state "to_cool", action:"switchMode", nextState:"...", icon: "st.secondary.secondary"
|
||||
state "...", label: "...", action:"off", nextState:"off", icon: "st.secondary.secondary"
|
||||
}
|
||||
standardTile("fanMode", "device.thermostatFanMode", inactiveLabel: false, decoration: "flat") {
|
||||
state "fanAuto", label:'${name}', action:"switchFanMode"
|
||||
state "fanOn", label:'${name}', action:"switchFanMode"
|
||||
state "fanCirculate", label:'${name}', action:"switchFanMode"
|
||||
standardTile("fanMode", "device.thermostatFanMode", width:2, height:2, inactiveLabel: false, decoration: "flat") {
|
||||
state "auto", action:"switchFanMode", icon: "st.thermostat.fan-auto"
|
||||
state "on", action:"switchFanMode", icon: "st.thermostat.fan-on"
|
||||
state "circulate", action:"switchFanMode", icon: "st.thermostat.fan-circulate"
|
||||
}
|
||||
controlTile("heatSliderControl", "device.heatingSetpoint", "slider", height: 1, width: 2, inactiveLabel: false) {
|
||||
state "setHeatingSetpoint", action:"quickSetHeat", backgroundColor:"#e86d13"
|
||||
valueTile("humidity", "device.humidity", width:2, height:2, inactiveLabel: false, decoration: "flat") {
|
||||
state "humidity", label:'${currentValue}%', icon:"st.Weather.weather12"
|
||||
}
|
||||
valueTile("heatingSetpoint", "device.heatingSetpoint", inactiveLabel: false, decoration: "flat") {
|
||||
state "heat", label:'${currentValue}° heat', backgroundColor:"#ffffff"
|
||||
standardTile("lowerHeatingSetpoint", "device.heatingSetpoint", width:2, height:1, inactiveLabel: false, decoration: "flat") {
|
||||
state "heatingSetpoint", action:"lowerHeatingSetpoint", icon:"st.thermostat.thermostat-left"
|
||||
}
|
||||
controlTile("coolSliderControl", "device.coolingSetpoint", "slider", height: 1, width: 2, inactiveLabel: false) {
|
||||
state "setCoolingSetpoint", action:"quickSetCool", backgroundColor: "#00a0dc"
|
||||
valueTile("heatingSetpoint", "device.heatingSetpoint", width:2, height:1, inactiveLabel: false, decoration: "flat") {
|
||||
state "heatingSetpoint", label:'${currentValue}° heat', backgroundColor:"#ffffff"
|
||||
}
|
||||
valueTile("coolingSetpoint", "device.coolingSetpoint", inactiveLabel: false, decoration: "flat") {
|
||||
state "cool", label:'${currentValue}° cool', backgroundColor:"#ffffff"
|
||||
standardTile("raiseHeatingSetpoint", "device.heatingSetpoint", width:2, height:1, inactiveLabel: false, decoration: "flat") {
|
||||
state "heatingSetpoint", action:"raiseHeatingSetpoint", icon:"st.thermostat.thermostat-right"
|
||||
}
|
||||
valueTile("humidity", "device.humidity", inactiveLabel: false, decoration: "flat") {
|
||||
state "humidity", label:'${currentValue}% humidity', unit:""
|
||||
standardTile("lowerCoolSetpoint", "device.coolingSetpoint", width:2, height:1, inactiveLabel: false, decoration: "flat") {
|
||||
state "coolingSetpoint", action:"lowerCoolSetpoint", icon:"st.thermostat.thermostat-left"
|
||||
}
|
||||
valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat") {
|
||||
state "battery", label:'${currentValue}% battery', unit:""
|
||||
valueTile("coolingSetpoint", "device.coolingSetpoint", width:2, height:1, inactiveLabel: false, decoration: "flat") {
|
||||
state "coolingSetpoint", label:'${currentValue}° cool', backgroundColor:"#ffffff"
|
||||
}
|
||||
standardTile("refresh", "device.thermostatMode", inactiveLabel: false, decoration: "flat") {
|
||||
standardTile("raiseCoolSetpoint", "device.heatingSetpoint", width:2, height:1, inactiveLabel: false, decoration: "flat") {
|
||||
state "heatingSetpoint", action:"raiseCoolSetpoint", icon:"st.thermostat.thermostat-right"
|
||||
}
|
||||
|
||||
standardTile("refresh", "device.thermostatMode", width:2, height:2, inactiveLabel: false, decoration: "flat") {
|
||||
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
main "temperature"
|
||||
details(["temperature", "mode", "fanMode", "heatSliderControl", "heatingSetpoint", "coolSliderControl", "coolingSetpoint", "refresh", "humidity", "battery"])
|
||||
details(["temperature", "mode", "fanMode", "humidity", "lowerHeatingSetpoint", "heatingSetpoint", "raiseHeatingSetpoint", "lowerCoolSetpoint", "coolingSetpoint", "raiseCoolSetpoint", "refresh"])
|
||||
}
|
||||
}
|
||||
|
||||
def updated() {
|
||||
// Device-Watch simply pings if no device events received for 32min(checkInterval)
|
||||
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
|
||||
// If not set update ManufacturerSpecific data
|
||||
if (!getDataValue("manufacturer")) {
|
||||
sendHubCommand(new physicalgraph.device.HubAction(zwave.manufacturerSpecificV2.manufacturerSpecificGet().format()))
|
||||
}
|
||||
initialize()
|
||||
}
|
||||
|
||||
def installed() {
|
||||
// Configure device
|
||||
def cmds = []
|
||||
cmds << new physicalgraph.device.HubAction(zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:[zwaveHubNodeId]).format())
|
||||
cmds << new physicalgraph.device.HubAction(zwave.manufacturerSpecificV2.manufacturerSpecificGet().format())
|
||||
sendHubCommand(cmds)
|
||||
initialize()
|
||||
}
|
||||
|
||||
def initialize() {
|
||||
// Device-Watch simply pings if no device events received for 32min(checkInterval)
|
||||
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
|
||||
// Poll device for additional data that will be updated by refresh tile
|
||||
refresh()
|
||||
}
|
||||
|
||||
def parse(String description)
|
||||
{
|
||||
def result = []
|
||||
def result = null
|
||||
if (description == "updated") {
|
||||
} else {
|
||||
def zwcmd = zwave.parse(description, [0x42:2, 0x43:2, 0x31: 2, 0x60: 3])
|
||||
if (zwcmd) {
|
||||
result += zwaveEvent(zwcmd)
|
||||
result = zwaveEvent(zwcmd)
|
||||
// Check battery level at least once every 2 days
|
||||
if (!state.lastbatt || now() - state.lastbatt > 48*60*60*1000) {
|
||||
sendHubCommand(new physicalgraph.device.HubAction(zwave.batteryV1.batteryGet().format()))
|
||||
}
|
||||
} else {
|
||||
log.debug "$device.displayName couldn't parse $description"
|
||||
}
|
||||
}
|
||||
if (!result) {
|
||||
return null
|
||||
return []
|
||||
}
|
||||
if (result.size() == 1 && (!state.lastbatt || now() - state.lastbatt > 48*60*60*1000)) {
|
||||
result << response(zwave.batteryV1.batteryGet().format())
|
||||
}
|
||||
log.debug "$device.displayName parsed '$description' to $result"
|
||||
result
|
||||
return [result]
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.multichannelv3.MultiChannelCmdEncap cmd) {
|
||||
def result = null
|
||||
def encapsulatedCommand = cmd.encapsulatedCommand([0x42:2, 0x43:2, 0x31: 2])
|
||||
log.debug ("Command from endpoint ${cmd.sourceEndPoint}: ${encapsulatedCommand}")
|
||||
def zwaveEvent(physicalgraph.zwave.commands.multichannelv3.MultiInstanceCmdEncap cmd) {
|
||||
def encapsulatedCommand = cmd.encapsulatedCommand([0x31: 3])
|
||||
log.debug ("multiinstancev1.MultiInstanceCmdEncap: command from instance ${cmd.instance}: ${encapsulatedCommand}")
|
||||
if (encapsulatedCommand) {
|
||||
result = zwaveEvent(encapsulatedCommand)
|
||||
if (cmd.sourceEndPoint == 1) { // indicates a response to refresh() vs an unrequested update
|
||||
def event = ([] + result)[0] // in case zwaveEvent returns a list
|
||||
def resp = nextRefreshQuery(event?.name)
|
||||
if (resp) {
|
||||
log.debug("sending next refresh query: $resp")
|
||||
result = [] + result + response(["delay 200", resp])
|
||||
}
|
||||
}
|
||||
zwaveEvent(encapsulatedCommand)
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.thermostatsetpointv2.ThermostatSetpointReport cmd)
|
||||
{
|
||||
def cmdScale = cmd.scale == 1 ? "F" : "C"
|
||||
def temp = convertTemperatureIfNeeded(cmd.scaledValue, cmdScale, cmd.precision)
|
||||
def sendCmd = []
|
||||
def unit = getTemperatureScale()
|
||||
def map1 = [ value: temp, unit: unit, displayed: false ]
|
||||
def cmdScale = cmd.scale == 1 ? "F" : "C"
|
||||
def setpoint = getTempInLocalScale(cmd.scaledValue, cmdScale)
|
||||
switch (cmd.setpointType) {
|
||||
case 1:
|
||||
map1.name = "heatingSetpoint"
|
||||
//map1.name = "heatingSetpoint"
|
||||
sendEvent(name: "heatingSetpoint", value: setpoint, unit: unit, displayed: false)
|
||||
updateThermostatSetpoint("heatingSetpoint", setpoint)
|
||||
// Enforce coolingSetpoint limits, as device doesn't
|
||||
if (setpoint > getTempInLocalScale("coolingSetpoint")) {
|
||||
sendCmd << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointSet(
|
||||
setpointType: 2, scale: cmd.scale, precision: cmd.precision, scaledValue: cmd.scaledValue).format())
|
||||
sendCmd << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 2).format())
|
||||
sendHubCommand(sendCmd)
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
map1.name = "coolingSetpoint"
|
||||
//map1.name = "coolingSetpoint"
|
||||
sendEvent(name: "coolingSetpoint", value: setpoint, unit: unit, displayed: false)
|
||||
updateThermostatSetpoint("coolingSetpoint", setpoint)
|
||||
// Enforce heatingSetpoint limits, as device doesn't
|
||||
if (setpoint < getTempInLocalScale("heatingSetpoint")) {
|
||||
sendCmd << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointSet(
|
||||
setpointType: 1, scale: cmd.scale, precision: cmd.precision, scaledValue: cmd.scaledValue).format())
|
||||
sendCmd << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 1).format())
|
||||
sendHubCommand(sendCmd)
|
||||
}
|
||||
break;
|
||||
default:
|
||||
log.debug "unknown setpointType $cmd.setpointType"
|
||||
@@ -180,33 +191,55 @@ def zwaveEvent(physicalgraph.zwave.commands.thermostatsetpointv2.ThermostatSetpo
|
||||
state.size = cmd.size
|
||||
state.scale = cmd.scale
|
||||
state.precision = cmd.precision
|
||||
|
||||
def mode = device.latestValue("thermostatMode")
|
||||
if (mode && map1.name.startsWith(mode) || (mode == "emergency heat" && map1.name == "heatingSetpoint")) {
|
||||
def map2 = [ name: "thermostatSetpoint", value: temp, unit: unit ]
|
||||
[ createEvent(map1), createEvent(map2) ]
|
||||
} else {
|
||||
createEvent(map1)
|
||||
}
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv2.SensorMultilevelReport cmd)
|
||||
{
|
||||
// thermostatSetpoint is not displayed by any tile as it can't be predictable calculated due to
|
||||
// the device's quirkiness but it is defined by the capability so it must be set, set it to the most likely value
|
||||
def updateThermostatSetpoint(setpoint, value) {
|
||||
def scale = getTemperatureScale()
|
||||
def heatingSetpoint = (setpoint == "heatingSetpoint") ? value : getTempInLocalScale("heatingSetpoint")
|
||||
def coolingSetpoint = (setpoint == "coolingSetpoint") ? value : getTempInLocalScale("coolingSetpoint")
|
||||
def mode = device.currentValue("thermostatMode")
|
||||
def thermostatSetpoint = heatingSetpoint // corresponds to (mode == "heat" || mode == "emergency heat")
|
||||
if (mode == "cool") {
|
||||
thermostatSetpoint = coolingSetpoint
|
||||
}
|
||||
// Just set to average of heating + cooling for mode off and auto
|
||||
if (mode == "off" || mode == "auto") {
|
||||
thermostatSetpoint = getTempInLocalScale((heatingSetpoint + coolingSetpoint)/2, scale)
|
||||
}
|
||||
sendEvent(name: "thermostatSetpoint", value: thermostatSetpoint, unit: scale)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv2.SensorMultilevelReport cmd) {
|
||||
def map = [:]
|
||||
if (cmd.sensorType == 1) {
|
||||
map.name = "temperature"
|
||||
map.unit = getTemperatureScale()
|
||||
map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmd.scale == 1 ? "F" : "C", cmd.precision)
|
||||
map.value = getTempInLocalScale(cmd.scaledSensorValue, (cmd.scale == 1 ? "F" : "C"))
|
||||
} else if (cmd.sensorType == 5) {
|
||||
map.name = "humidity"
|
||||
map.unit = "%"
|
||||
map.value = cmd.scaledSensorValue
|
||||
}
|
||||
createEvent(map)
|
||||
sendEvent(map)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.thermostatoperatingstatev2.ThermostatOperatingStateReport cmd)
|
||||
{
|
||||
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv3.SensorMultilevelReport cmd) {
|
||||
def map = [:]
|
||||
if (cmd.sensorType == 1) {
|
||||
map.name = "temperature"
|
||||
map.unit = getTemperatureScale()
|
||||
map.value = getTempInLocalScale(cmd.scaledSensorValue, (cmd.scale == 1 ? "F" : "C"))
|
||||
} else if (cmd.sensorType == 5) {
|
||||
map.value = cmd.scaledSensorValue
|
||||
map.unit = "%"
|
||||
map.name = "humidity"
|
||||
}
|
||||
sendEvent(map)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.thermostatoperatingstatev2.ThermostatOperatingStateReport cmd) {
|
||||
def map = [name: "thermostatOperatingState" ]
|
||||
switch (cmd.operatingState) {
|
||||
case physicalgraph.zwave.commands.thermostatoperatingstatev2.ThermostatOperatingStateReport.OPERATING_STATE_IDLE:
|
||||
@@ -231,12 +264,7 @@ def zwaveEvent(physicalgraph.zwave.commands.thermostatoperatingstatev2.Thermosta
|
||||
map.value = "vent economizer"
|
||||
break
|
||||
}
|
||||
def result = createEvent(map)
|
||||
if (result.isStateChange && device.latestValue("thermostatMode") == "auto" && (result.value == "heating" || result.value == "cooling")) {
|
||||
def thermostatSetpoint = device.latestValue("${result.value}Setpoint")
|
||||
result = [result, createEvent(name: "thermostatSetpoint", value: thermostatSetpoint, unit: getTemperatureScale())]
|
||||
}
|
||||
result
|
||||
sendEvent(map)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.thermostatfanstatev1.ThermostatFanStateReport cmd) {
|
||||
@@ -252,203 +280,256 @@ def zwaveEvent(physicalgraph.zwave.commands.thermostatfanstatev1.ThermostatFanSt
|
||||
map.value = "running high"
|
||||
break
|
||||
}
|
||||
createEvent(map)
|
||||
sendEvent(map)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport cmd) {
|
||||
def map = [name: "thermostatMode"]
|
||||
def thermostatSetpoint = null
|
||||
def map = [name: "thermostatMode", data:[supportedThermostatModes: state.supportedModes]]
|
||||
switch (cmd.mode) {
|
||||
case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_OFF:
|
||||
map.value = "off"
|
||||
break
|
||||
case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_HEAT:
|
||||
map.value = "heat"
|
||||
thermostatSetpoint = device.latestValue("heatingSetpoint")
|
||||
break
|
||||
case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_AUXILIARY_HEAT:
|
||||
map.value = "emergency heat"
|
||||
thermostatSetpoint = device.latestValue("heatingSetpoint")
|
||||
break
|
||||
case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_COOL:
|
||||
map.value = "cool"
|
||||
thermostatSetpoint = device.latestValue("coolingSetpoint")
|
||||
break
|
||||
case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_AUTO:
|
||||
map.value = "auto"
|
||||
def temp = device.latestValue("temperature")
|
||||
def heatingSetpoint = device.latestValue("heatingSetpoint")
|
||||
def coolingSetpoint = device.latestValue("coolingSetpoint")
|
||||
if (temp && heatingSetpoint && coolingSetpoint) {
|
||||
if (temp < (heatingSetpoint + coolingSetpoint) / 2.0) {
|
||||
thermostatSetpoint = heatingSetpoint
|
||||
} else {
|
||||
thermostatSetpoint = coolingSetpoint
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
state.lastTriedMode = map.value
|
||||
if (thermostatSetpoint) {
|
||||
[ createEvent(map), createEvent(name: "thermostatSetpoint", value: thermostatSetpoint, unit: getTemperatureScale()) ]
|
||||
} else {
|
||||
createEvent(map)
|
||||
}
|
||||
sendEvent(map)
|
||||
updateThermostatSetpoint(null, null)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport cmd) {
|
||||
def map = [name: "thermostatFanMode", displayed: false]
|
||||
def map = [name: "thermostatFanMode", data:[supportedThermostatFanModes: state.supportedFanModes]]
|
||||
switch (cmd.fanMode) {
|
||||
case physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport.FAN_MODE_AUTO_LOW:
|
||||
map.value = "fanAuto"
|
||||
case physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport.FAN_MODE_AUTO_LOW:
|
||||
map.value = "auto"
|
||||
break
|
||||
case physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport.FAN_MODE_LOW:
|
||||
map.value = "fanOn"
|
||||
map.value = "on"
|
||||
break
|
||||
case physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeReport.FAN_MODE_CIRCULATION:
|
||||
map.value = "fanCirculate"
|
||||
map.value = "circulate"
|
||||
break
|
||||
}
|
||||
state.lastTriedFanMode = map.value
|
||||
createEvent(map)
|
||||
sendEvent(map)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeSupportedReport cmd) {
|
||||
def supportedModes = ""
|
||||
if(cmd.off) { supportedModes += "off " }
|
||||
if(cmd.heat) { supportedModes += "heat " }
|
||||
if(cmd.auxiliaryemergencyHeat) { supportedModes += "emergency heat " }
|
||||
if(cmd.cool) { supportedModes += "cool " }
|
||||
if(cmd.auto) { supportedModes += "auto " }
|
||||
def supportedModes = []
|
||||
if(cmd.heat) { supportedModes << "heat" }
|
||||
if(cmd.cool) { supportedModes << "cool" }
|
||||
// Make sure off is before auto, this ensures the right setpoint is used based on current temperature when auto is set
|
||||
if(cmd.off) { supportedModes << "off" }
|
||||
if(cmd.auto) { supportedModes << "auto" }
|
||||
if(cmd.auxiliaryemergencyHeat) { supportedModes << "emergency heat" }
|
||||
|
||||
state.supportedModes = supportedModes
|
||||
[ createEvent(name:"supportedModes", value: supportedModes, displayed: false),
|
||||
response(zwave.thermostatFanModeV3.thermostatFanModeSupportedGet()) ]
|
||||
sendEvent(name: "supportedThermostatModes", value: supportedModes, isStateChange: true, displayed: false)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.thermostatfanmodev3.ThermostatFanModeSupportedReport cmd) {
|
||||
def supportedFanModes = ""
|
||||
if(cmd.auto) { supportedFanModes += "fanAuto " }
|
||||
if(cmd.low) { supportedFanModes += "fanOn " }
|
||||
if(cmd.circulation) { supportedFanModes += "fanCirculate " }
|
||||
def supportedFanModes = []
|
||||
if(cmd.auto) { supportedFanModes << "auto" }
|
||||
if(cmd.low) { supportedFanModes << "on" }
|
||||
if(cmd.circulation) { supportedFanModes << "circulate" }
|
||||
|
||||
state.supportedFanModes = supportedFanModes
|
||||
[ createEvent(name:"supportedFanModes", value: supportedModes, displayed: false),
|
||||
response(refresh()) ]
|
||||
sendEvent(name: "supportedThermostatFanModes", value: supportedFanModes, isStateChange: true, displayed: false)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
|
||||
log.debug "Zwave event received: $cmd"
|
||||
log.debug "Zwave BasicReport: $cmd"
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
|
||||
def map = [ name: "battery", unit: "%" ]
|
||||
if (cmd.batteryLevel == 0xFF) {
|
||||
def batteryState = cmd.batteryLevel
|
||||
def map = [name: "battery", unit: "%", value: cmd.batteryLevel]
|
||||
if ((cmd.batteryLevel == 0xFF) || (cmd.batteryLevel == 0x00)) { // Special value for low battery alert
|
||||
map.value = 1
|
||||
map.descriptionText = "${device.displayName} battery is low"
|
||||
map.isStateChange = true
|
||||
} else {
|
||||
map.value = cmd.batteryLevel
|
||||
batteryState = "low_battery"
|
||||
}
|
||||
state.lastbatt = now()
|
||||
createEvent(map)
|
||||
sendEvent(name: "batteryIcon", value: batteryState, displayed: false)
|
||||
sendEvent(map)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
||||
log.warn "Unexpected zwave command $cmd"
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
|
||||
log.debug "ManufacturerSpecificReport ${cmd}: value:${cmd}"
|
||||
if (cmd.manufacturerName) {
|
||||
updateDataValue("manufacturer", cmd.manufacturerName)
|
||||
}
|
||||
if (cmd.productTypeId) {
|
||||
updateDataValue("productTypeId", cmd.productTypeId.toString())
|
||||
}
|
||||
if (cmd.productId) {
|
||||
updateDataValue("productId", cmd.productId.toString())
|
||||
}
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
// Use encapsulation to differentiate refresh cmds from what the thermostat sends proactively on change
|
||||
def cmd = zwave.sensorMultilevelV2.sensorMultilevelGet()
|
||||
zwave.multiChannelV3.multiChannelCmdEncap(destinationEndPoint:1).encapsulate(cmd).format()
|
||||
}
|
||||
|
||||
def nextRefreshQuery(name) {
|
||||
def cmd = null
|
||||
switch (name) {
|
||||
case "temperature":
|
||||
cmd = zwave.thermostatModeV2.thermostatModeGet()
|
||||
break
|
||||
case "thermostatMode":
|
||||
cmd = zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 1)
|
||||
break
|
||||
case "heatingSetpoint":
|
||||
cmd = zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 2)
|
||||
break
|
||||
case "coolingSetpoint":
|
||||
cmd = zwave.thermostatFanModeV3.thermostatFanModeGet()
|
||||
break
|
||||
case "thermostatFanMode":
|
||||
cmd = zwave.thermostatOperatingStateV2.thermostatOperatingStateGet()
|
||||
break
|
||||
case "thermostatOperatingState":
|
||||
// get humidity, multilevel sensor get to endpoint 2
|
||||
cmd = zwave.sensorMultilevelV2.sensorMultilevelGet()
|
||||
return zwave.multiChannelV3.multiChannelCmdEncap(destinationEndPoint:2).encapsulate(cmd).format()
|
||||
default: return null
|
||||
// Only allow refresh every 2 minutes to prevent flooding the Zwave network
|
||||
def timeNow = now()
|
||||
if (!state.refreshTriggeredAt || (2 * 60 * 1000 < (timeNow - state.refreshTriggeredAt))) {
|
||||
state.refreshTriggeredAt = timeNow
|
||||
// refresh will request battery, prevent multiple request by setting lastbatt now
|
||||
state.lastbatt = timeNow
|
||||
// use runIn with overwrite to prevent multiple DTH instances run before state.refreshTriggeredAt has been saved
|
||||
runIn(2, "poll", [overwrite: true])
|
||||
}
|
||||
zwave.multiChannelV3.multiChannelCmdEncap(destinationEndPoint:1).encapsulate(cmd).format()
|
||||
}
|
||||
|
||||
def quickSetHeat(degrees) {
|
||||
setHeatingSetpoint(degrees, 1000)
|
||||
def poll() {
|
||||
def cmds = []
|
||||
cmds << new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeSupportedGet().format())
|
||||
cmds << new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeSupportedGet().format())
|
||||
cmds << new physicalgraph.device.HubAction(zwave.multiChannelV3.multiInstanceCmdEncap(instance: 1).encapsulate(zwave.sensorMultilevelV3.sensorMultilevelGet()).format()) // temperature
|
||||
cmds << new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeGet().format())
|
||||
cmds << new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeGet().format())
|
||||
cmds << new physicalgraph.device.HubAction(zwave.thermostatOperatingStateV1.thermostatOperatingStateGet().format())
|
||||
cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 1).format()) // HeatingSetpoint
|
||||
cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 2).format()) // CoolingSetpoint
|
||||
cmds << new physicalgraph.device.HubAction(zwave.batteryV1.batteryGet().format())
|
||||
cmds << new physicalgraph.device.HubAction(zwave.multiChannelV3.multiInstanceCmdEncap(instance: 2).encapsulate(zwave.sensorMultilevelV3.sensorMultilevelGet()).format()) // humidity
|
||||
def time = getTimeAndDay()
|
||||
log.debug "time: $time"
|
||||
if (time) {
|
||||
cmds << new physicalgraph.device.HubAction(zwave.clockV1.clockSet(time).format())
|
||||
}
|
||||
// Add 3 seconds delay between each command to avoid flooding the Z-Wave network choking the hub
|
||||
sendHubCommand(cmds, 3000)
|
||||
}
|
||||
|
||||
def setHeatingSetpoint(degrees, delay = 30000) {
|
||||
setHeatingSetpoint(degrees.toDouble(), delay)
|
||||
def raiseHeatingSetpoint() {
|
||||
alterSetpoint(null, true, "heatingSetpoint")
|
||||
}
|
||||
|
||||
def setHeatingSetpoint(Double degrees, Integer delay = 30000) {
|
||||
log.trace "setHeatingSetpoint($degrees, $delay)"
|
||||
def deviceScale = state.scale ?: 1
|
||||
def deviceScaleString = deviceScale == 2 ? "C" : "F"
|
||||
def lowerHeatingSetpoint() {
|
||||
alterSetpoint(null, false, "heatingSetpoint")
|
||||
}
|
||||
|
||||
def raiseCoolSetpoint() {
|
||||
alterSetpoint(null, true, "coolingSetpoint")
|
||||
}
|
||||
|
||||
def lowerCoolSetpoint() {
|
||||
alterSetpoint(null, false, "coolingSetpoint")
|
||||
}
|
||||
|
||||
// Adjusts nextHeatingSetpoint either .5° C/1° F) if raise true/false
|
||||
def alterSetpoint(degrees, raise, setpoint) {
|
||||
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)
|
||||
def heatingSetpoint = getTempInLocalScale("heatingSetpoint")
|
||||
def coolingSetpoint = getTempInLocalScale("coolingSetpoint")
|
||||
def targetvalue = (setpoint == "heatingSetpoint") ? heatingSetpoint : coolingSetpoint
|
||||
def delta = (locationScale == "F") ? 1 : 0.5
|
||||
if (raise != null) {
|
||||
targetvalue += raise ? delta : - delta
|
||||
} else if (degrees) {
|
||||
targetvalue = degrees
|
||||
} else {
|
||||
convertedDegrees = degrees
|
||||
log.warn "alterSetpoint called with neither up/down/degree information"
|
||||
return
|
||||
}
|
||||
def data = enforceSetpointLimits(setpoint, [targetvalue: targetvalue, heatingSetpoint: heatingSetpoint, coolingSetpoint: coolingSetpoint])
|
||||
// update UI without waiting for the device to respond, this to give user a smoother UI experience
|
||||
// also, as runIn's have to overwrite and user can change heating/cooling setpoint separately separate runIn's have to be used
|
||||
if (data.targetHeatingSetpoint) {
|
||||
sendEvent("name": "heatingSetpoint", "value": data.targetHeatingSetpoint, unit: locationScale, eventType: "ENTITY_UPDATE")//, displayed: false)
|
||||
runIn(4, "updateHeatingSetpoint", [data: data, overwrite: true])
|
||||
}
|
||||
if (data.targetCoolingSetpoint) {
|
||||
sendEvent("name": "coolingSetpoint", "value": data.targetCoolingSetpoint, unit: locationScale, eventType: "ENTITY_UPDATE")//, displayed: false)
|
||||
runIn(4, "updateCoolingSetpoint", [data: data, overwrite: true])
|
||||
}
|
||||
}
|
||||
|
||||
def updateHeatingSetpoint(data) {
|
||||
updateSetpoints(data)
|
||||
}
|
||||
|
||||
def updateCoolingSetpoint(data) {
|
||||
updateSetpoints(data)
|
||||
}
|
||||
|
||||
def enforceSetpointLimits(setpoint, data) {
|
||||
// Enforce max/min for setpoints
|
||||
def maxSetpoint = getTempInLocalScale(95, "F")
|
||||
def minSetpoint = getTempInLocalScale(35, "F")
|
||||
def targetvalue = data.targetvalue
|
||||
def heatingSetpoint = null
|
||||
def coolingSetpoint = null
|
||||
|
||||
if (targetvalue > maxSetpoint) {
|
||||
targetvalue = maxSetpoint
|
||||
} else if (targetvalue < minSetpoint) {
|
||||
targetvalue = minSetpoint
|
||||
}
|
||||
// Enforce limits, for now make sure heating <= cooling, and cooling >= heating
|
||||
if (setpoint == "heatingSetpoint") {
|
||||
heatingSetpoint = targetvalue
|
||||
coolingSetpoint = (heatingSetpoint > data.coolingSetpoint) ? heatingSetpoint : null
|
||||
}
|
||||
if (setpoint == "coolingSetpoint") {
|
||||
coolingSetpoint = targetvalue
|
||||
heatingSetpoint = (coolingSetpoint < data.heatingSetpoint) ? coolingSetpoint : null
|
||||
}
|
||||
return [targetHeatingSetpoint: heatingSetpoint, targetCoolingSetpoint: coolingSetpoint]
|
||||
}
|
||||
|
||||
def setHeatingSetpoint(degrees) {
|
||||
if (degrees) {
|
||||
def data = enforceSetpointLimits("heatingSetpoint",
|
||||
[targetvalue: degrees.toDouble(), heatingSetpoint: getTempInLocalScale("heatingSetpoint"), coolingSetpoint: getTempInLocalScale("coolingSetpoint")])
|
||||
updateSetpoints(data)
|
||||
}
|
||||
}
|
||||
|
||||
def setCoolingSetpoint(degrees) {
|
||||
if (degrees) {
|
||||
def data = enforceSetpointLimits("coolingSetpoint",
|
||||
[targetvalue: degrees.toDouble(), heatingSetpoint: getTempInLocalScale("heatingSetpoint"), coolingSetpoint: getTempInLocalScale("coolingSetpoint")])
|
||||
updateSetpoints(data)
|
||||
}
|
||||
}
|
||||
|
||||
def updateSetpoints(data) {
|
||||
def cmds = []
|
||||
if (data.targetHeatingSetpoint) {
|
||||
cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointSet(
|
||||
setpointType: 1, scale: state.scale, precision: state.precision, scaledValue: convertToDeviceScale(data.targetHeatingSetpoint)).format())
|
||||
}
|
||||
if (data.targetCoolingSetpoint) {
|
||||
cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointSet(
|
||||
setpointType: 2, scale: state.scale, precision: state.precision, scaledValue: convertToDeviceScale(data.targetCoolingSetpoint)).format())
|
||||
}
|
||||
|
||||
delayBetween([
|
||||
zwave.thermostatSetpointV1.thermostatSetpointSet(setpointType: 1, scale: deviceScale, precision: p, scaledValue: convertedDegrees).format(),
|
||||
zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 1).format()
|
||||
], delay)
|
||||
// Always request both setpoints in case thermostat changed both
|
||||
cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 1).format())
|
||||
cmds << new physicalgraph.device.HubAction(zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 2).format())
|
||||
sendHubCommand(cmds)
|
||||
}
|
||||
|
||||
def quickSetCool(degrees) {
|
||||
setCoolingSetpoint(degrees, 1000)
|
||||
}
|
||||
|
||||
def setCoolingSetpoint(degrees, delay = 30000) {
|
||||
setCoolingSetpoint(degrees.toDouble(), delay)
|
||||
}
|
||||
|
||||
def setCoolingSetpoint(Double degrees, Integer delay = 30000) {
|
||||
log.trace "setCoolingSetpoint($degrees, $delay)"
|
||||
def deviceScale = state.scale ?: 1
|
||||
def deviceScaleString = deviceScale == 2 ? "C" : "F"
|
||||
def convertToDeviceScale(setpoint) {
|
||||
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
|
||||
}
|
||||
|
||||
delayBetween([
|
||||
zwave.thermostatSetpointV1.thermostatSetpointSet(setpointType: 2, scale: deviceScale, precision: p, scaledValue: convertedDegrees).format(),
|
||||
zwave.thermostatSetpointV1.thermostatSetpointGet(setpointType: 2).format()
|
||||
], delay)
|
||||
def deviceScale = (state.scale == 1) ? "F" : "C"
|
||||
return (deviceScale == locationScale) ? setpoint :
|
||||
(deviceScale == "F" ? celsiusToFahrenheit(setpoint.toBigDecimal()) : roundC(fahrenheitToCelsius(setpoint.toBigDecimal())))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -456,78 +537,56 @@ def setCoolingSetpoint(Double degrees, Integer delay = 30000) {
|
||||
* */
|
||||
def ping() {
|
||||
log.debug "ping() called"
|
||||
refresh()
|
||||
}
|
||||
|
||||
def configure() {
|
||||
delayBetween([
|
||||
zwave.thermostatModeV2.thermostatModeSupportedGet().format(),
|
||||
], 2300)
|
||||
}
|
||||
|
||||
def modes() {
|
||||
["off", "heat", "cool", "auto", "emergency heat"]
|
||||
// Just get Operating State as it is not reported when it chnages and there's no need to flood more commands
|
||||
sendHubCommand(new physicalgraph.device.HubAction(zwave.thermostatOperatingStateV1.thermostatOperatingStateGet().format()))
|
||||
}
|
||||
|
||||
def switchMode() {
|
||||
def currentMode = device.currentState("thermostatMode")?.value
|
||||
def currentMode = device.currentValue("thermostatMode")
|
||||
def lastTriedMode = state.lastTriedMode ?: currentMode ?: "off"
|
||||
def supportedModes = getDataByName("supportedModes")
|
||||
def modeOrder = modes()
|
||||
def next = { modeOrder[modeOrder.indexOf(it) + 1] ?: modeOrder[0] }
|
||||
def nextMode = next(lastTriedMode)
|
||||
if (supportedModes?.contains(currentMode)) {
|
||||
while (!supportedModes.contains(nextMode) && nextMode != "off") {
|
||||
nextMode = next(nextMode)
|
||||
}
|
||||
def supportedModes = state.supportedModes
|
||||
if (supportedModes) {
|
||||
def next = { supportedModes[supportedModes.indexOf(it) + 1] ?: supportedModes[0] }
|
||||
def nextMode = next(lastTriedMode)
|
||||
setThermostatMode(nextMode)
|
||||
state.lastTriedMode = nextMode
|
||||
} else {
|
||||
log.warn "supportedModes not defined"
|
||||
}
|
||||
state.lastTriedMode = nextMode
|
||||
delayBetween([
|
||||
zwave.thermostatModeV2.thermostatModeSet(mode: modeMap[nextMode]).format(),
|
||||
zwave.thermostatModeV2.thermostatModeGet().format()
|
||||
], 1000)
|
||||
}
|
||||
|
||||
def switchToMode(nextMode) {
|
||||
def supportedModes = getDataByName("supportedModes")
|
||||
if(supportedModes && !supportedModes.contains(nextMode)) log.warn "thermostat mode '$nextMode' is not supported"
|
||||
if (nextMode in modes()) {
|
||||
def supportedModes = state.supportedModes
|
||||
if (supportedModes && supportedModes.contains(nextMode)) {
|
||||
setThermostatMode(nextMode)
|
||||
state.lastTriedMode = nextMode
|
||||
"$nextMode"()
|
||||
} else {
|
||||
log.debug("no mode method '$nextMode'")
|
||||
log.debug("ThermostatMode $nextMode is not supported by ${device.displayName}")
|
||||
}
|
||||
}
|
||||
|
||||
def switchFanMode() {
|
||||
def currentMode = device.currentState("thermostatFanMode")?.value
|
||||
def lastTriedMode = state.lastTriedFanMode ?: currentMode ?: "off"
|
||||
def supportedModes = getDataByName("supportedFanModes") ?: "fanAuto fanOn"
|
||||
def modeOrder = ["fanAuto", "fanCirculate", "fanOn"]
|
||||
def next = { modeOrder[modeOrder.indexOf(it) + 1] ?: modeOrder[0] }
|
||||
def nextMode = next(lastTriedMode)
|
||||
while (!supportedModes?.contains(nextMode) && nextMode != "fanAuto") {
|
||||
nextMode = next(nextMode)
|
||||
def supportedFanModes = state.supportedFanModes
|
||||
if (supportedFanModes) {
|
||||
def next = { supportedFanModes[supportedFanModes.indexOf(it) + 1] ?: supportedFanModes[0] }
|
||||
def nextMode = next(lastTriedMode)
|
||||
setThermostatFanMode(nextMode)
|
||||
state.lastTriedFanMode = nextMode
|
||||
} else {
|
||||
log.warn "supportedFanModes not defined"
|
||||
}
|
||||
switchToFanMode(nextMode)
|
||||
}
|
||||
|
||||
def switchToFanMode(nextMode) {
|
||||
def supportedFanModes = getDataByName("supportedFanModes")
|
||||
if(supportedFanModes && !supportedFanModes.contains(nextMode)) log.warn "thermostat mode '$nextMode' is not supported"
|
||||
|
||||
def returnCommand
|
||||
if (nextMode == "fanAuto") {
|
||||
returnCommand = fanAuto()
|
||||
} else if (nextMode == "fanOn") {
|
||||
returnCommand = fanOn()
|
||||
} else if (nextMode == "fanCirculate") {
|
||||
returnCommand = fanCirculate()
|
||||
def supportedFanModes = state.supportedFanModes
|
||||
if (supportedFanModes && supportedFanModes.contains(nextMode)) {
|
||||
setThermostatFanMode(nextMode)
|
||||
state.lastTriedFanMode = nextMode
|
||||
} else {
|
||||
log.debug("no fan mode '$nextMode'")
|
||||
log.debug("FanMode $nextMode is not supported by ${device.displayName}")
|
||||
}
|
||||
if(returnCommand) state.lastTriedFanMode = nextMode
|
||||
returnCommand
|
||||
}
|
||||
|
||||
def getDataByName(String name) {
|
||||
@@ -543,10 +602,10 @@ def getModeMap() { [
|
||||
]}
|
||||
|
||||
def setThermostatMode(String value) {
|
||||
delayBetween([
|
||||
zwave.thermostatModeV2.thermostatModeSet(mode: modeMap[value]).format(),
|
||||
zwave.thermostatModeV2.thermostatModeGet().format()
|
||||
], standardDelay)
|
||||
def cmds = []
|
||||
cmds << new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeSet(mode: modeMap[value]).format())
|
||||
cmds << new physicalgraph.device.HubAction(zwave.thermostatModeV2.thermostatModeGet().format())
|
||||
sendHubCommand(cmds)
|
||||
}
|
||||
|
||||
def getFanModeMap() { [
|
||||
@@ -556,69 +615,70 @@ def getFanModeMap() { [
|
||||
]}
|
||||
|
||||
def setThermostatFanMode(String value) {
|
||||
delayBetween([
|
||||
zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: fanModeMap[value]).format(),
|
||||
zwave.thermostatFanModeV3.thermostatFanModeGet().format()
|
||||
], standardDelay)
|
||||
def cmds = []
|
||||
cmds << new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: fanModeMap[value]).format())
|
||||
cmds << new physicalgraph.device.HubAction(zwave.thermostatFanModeV3.thermostatFanModeGet().format())
|
||||
sendHubCommand(cmds)
|
||||
}
|
||||
|
||||
def off() {
|
||||
delayBetween([
|
||||
zwave.thermostatModeV2.thermostatModeSet(mode: 0).format(),
|
||||
zwave.thermostatModeV2.thermostatModeGet().format()
|
||||
], standardDelay)
|
||||
switchToMode("off")
|
||||
}
|
||||
|
||||
def heat() {
|
||||
delayBetween([
|
||||
zwave.thermostatModeV2.thermostatModeSet(mode: 1).format(),
|
||||
zwave.thermostatModeV2.thermostatModeGet().format()
|
||||
], standardDelay)
|
||||
switchToMode("heat")
|
||||
}
|
||||
|
||||
def emergencyHeat() {
|
||||
delayBetween([
|
||||
zwave.thermostatModeV2.thermostatModeSet(mode: 4).format(),
|
||||
zwave.thermostatModeV2.thermostatModeGet().format()
|
||||
], standardDelay)
|
||||
switchToMode("emergency heat")
|
||||
}
|
||||
|
||||
def cool() {
|
||||
delayBetween([
|
||||
zwave.thermostatModeV2.thermostatModeSet(mode: 2).format(),
|
||||
zwave.thermostatModeV2.thermostatModeGet().format()
|
||||
], standardDelay)
|
||||
switchToMode("cool")
|
||||
}
|
||||
|
||||
def auto() {
|
||||
delayBetween([
|
||||
zwave.thermostatModeV2.thermostatModeSet(mode: 3).format(),
|
||||
zwave.thermostatModeV2.thermostatModeGet().format()
|
||||
], standardDelay)
|
||||
switchToMode("auto")
|
||||
}
|
||||
|
||||
def fanOn() {
|
||||
delayBetween([
|
||||
zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: 1).format(),
|
||||
zwave.thermostatFanModeV3.thermostatFanModeGet().format()
|
||||
], standardDelay)
|
||||
switchToFanMode("on")
|
||||
}
|
||||
|
||||
def fanAuto() {
|
||||
delayBetween([
|
||||
zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: 0).format(),
|
||||
zwave.thermostatFanModeV3.thermostatFanModeGet().format()
|
||||
], standardDelay)
|
||||
switchToFanMode("auto")
|
||||
}
|
||||
|
||||
def fanCirculate() {
|
||||
delayBetween([
|
||||
zwave.thermostatFanModeV3.thermostatFanModeSet(fanMode: 6).format(),
|
||||
zwave.thermostatFanModeV3.thermostatFanModeGet().format()
|
||||
], standardDelay)
|
||||
switchToFanMode("circulate")
|
||||
}
|
||||
|
||||
private getStandardDelay() {
|
||||
1000
|
||||
private getTimeAndDay() {
|
||||
def timeNow = now()
|
||||
// Need to check that location have timeZone as SC may have created the location without setting it
|
||||
// Don't update clock more than once a day
|
||||
if (location.timeZone && (!state.timeClockSet || (24 * 60 * 60 * 1000 < (timeNow - state.timeClockSet)))) {
|
||||
def currentDate = Calendar.getInstance(location.timeZone)
|
||||
state.timeClockSet = timeNow
|
||||
return [hour: currentDate.get(Calendar.HOUR_OF_DAY), minute: currentDate.get(Calendar.MINUTE), weekday: currentDate.get(Calendar.DAY_OF_WEEK)]
|
||||
}
|
||||
}
|
||||
|
||||
// Get stored temperature from currentState in current local scale
|
||||
def getTempInLocalScale(state) {
|
||||
def temp = device.currentState(state)
|
||||
if (temp && temp.value && temp.unit) {
|
||||
return getTempInLocalScale(temp.value.toBigDecimal(), temp.unit)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// get/convert temperature to current local scale
|
||||
def getTempInLocalScale(temp, scale) {
|
||||
def scaledTemp = convertTemperatureIfNeeded(temp.toBigDecimal(), scale).toDouble()
|
||||
return (getTemperatureScale() == "F" ? scaledTemp.round(0).toInteger() : roundC(scaledTemp))
|
||||
}
|
||||
|
||||
def roundC (tempC) {
|
||||
return (Math.round(tempC.toDouble() * 2))/2
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
.st-ignore
|
||||
README.md
|
||||
@@ -0,0 +1,39 @@
|
||||
# Z-wave Basic Smoke Alarm
|
||||
|
||||
Cloud Execution
|
||||
|
||||
Works with:
|
||||
|
||||
* [First Alert Smoke Detector (ZSMOKE)](https://www.smartthings.com/products/first-alert-smoke-detector)
|
||||
|
||||
## Table of contents
|
||||
|
||||
* [Capabilities](#capabilities)
|
||||
* [Health](#device-health)
|
||||
* [Battery](#battery-specification)
|
||||
* [Troubleshooting](#troubleshooting)
|
||||
|
||||
## Capabilities
|
||||
|
||||
* **Smoke Detector** - measure smoke and optionally carbon monoxide levels
|
||||
* **Sensor** - detects sensor events
|
||||
* **Battery** - defines device uses a battery
|
||||
* **Health Check** - indicates ability to get device health notifications
|
||||
|
||||
## Device Health
|
||||
|
||||
First Alert Smoke Detector (ZSMOKE) is a Z-wave sleepy device and checks in every 1 hour.
|
||||
Device-Watch allows 2 check-in misses from device plus some lag time. So Check-in interval = (2*60 + 2)mins = 122 mins.
|
||||
|
||||
* __122min__ checkInterval
|
||||
|
||||
## Battery Specification
|
||||
|
||||
Two AA 1.5V batteries are required.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
|
||||
Pairing needs to be tried again by placing the device closer to the hub.
|
||||
Instructions related to pairing, resetting and removing the device from SmartThings can be found in the following link:
|
||||
* [First Alert Smoke Detector (ZSMOKE) Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/207150556-First-Alert-Smoke-Detector-ZSMOKE-)
|
||||
@@ -0,0 +1,181 @@
|
||||
/**
|
||||
* Copyright 2015 SmartThings
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
metadata {
|
||||
definition (name: "Z-Wave Basic Smoke Alarm", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Smoke Detector"
|
||||
capability "Sensor"
|
||||
capability "Battery"
|
||||
capability "Health Check"
|
||||
|
||||
fingerprint deviceId: "0xA100", inClusters: "0x20,0x80,0x70,0x85,0x71,0x72,0x86"
|
||||
fingerprint mfr:"0138", prod:"0001", model:"0001", deviceJoinName: "First Alert Smoke Detector"
|
||||
}
|
||||
|
||||
simulator {
|
||||
status "smoke": "command: 7105, payload: 01 FF"
|
||||
status "clear": "command: 7105, payload: 01 00"
|
||||
status "test": "command: 7105, payload: 0C FF"
|
||||
status "battery 100%": "command: 8003, payload: 64"
|
||||
status "battery 5%": "command: 8003, payload: 05"
|
||||
}
|
||||
|
||||
tiles (scale: 2){
|
||||
multiAttributeTile(name:"smoke", type: "lighting", width: 6, height: 4){
|
||||
tileAttribute ("device.smoke", key: "PRIMARY_CONTROL") {
|
||||
attributeState("clear", label:"clear", icon:"st.alarm.smoke.clear", backgroundColor:"#ffffff")
|
||||
attributeState("detected", label:"SMOKE", icon:"st.alarm.smoke.smoke", backgroundColor:"#e86d13")
|
||||
attributeState("tested", label:"TEST", icon:"st.alarm.smoke.test", backgroundColor:"#e86d13")
|
||||
}
|
||||
}
|
||||
valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "battery", label:'${currentValue}% battery', unit:""
|
||||
}
|
||||
|
||||
main "smoke"
|
||||
details(["smoke", "battery"])
|
||||
}
|
||||
}
|
||||
|
||||
def installed() {
|
||||
// Device checks in every hour, this interval allows us to miss one check-in notification before marking offline
|
||||
sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
|
||||
|
||||
def cmds = []
|
||||
createSmokeEvents("smokeClear", cmds)
|
||||
cmds.each { cmd -> sendEvent(cmd) }
|
||||
}
|
||||
|
||||
def updated() {
|
||||
// Device checks in every hour, this interval allows us to miss one check-in notification before marking offline
|
||||
sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
|
||||
}
|
||||
|
||||
def parse(String description) {
|
||||
def results = []
|
||||
if (description.startsWith("Err")) {
|
||||
results << createEvent(descriptionText:description, displayed:true)
|
||||
} else {
|
||||
def cmd = zwave.parse(description, [ 0x80: 1, 0x84: 1, 0x71: 2, 0x72: 1 ])
|
||||
if (cmd) {
|
||||
zwaveEvent(cmd, results)
|
||||
}
|
||||
}
|
||||
log.debug "'$description' parsed to ${results.inspect()}"
|
||||
return results
|
||||
}
|
||||
|
||||
def createSmokeEvents(name, results) {
|
||||
def text = null
|
||||
switch (name) {
|
||||
case "smoke":
|
||||
text = "$device.displayName smoke was detected!"
|
||||
// these are displayed:false because the composite event is the one we want to see in the app
|
||||
results << createEvent(name: "smoke", value: "detected", descriptionText: text)
|
||||
break
|
||||
case "tested":
|
||||
text = "$device.displayName was tested"
|
||||
results << createEvent(name: "smoke", value: "tested", descriptionText: text)
|
||||
break
|
||||
case "smokeClear":
|
||||
text = "$device.displayName smoke is clear"
|
||||
results << createEvent(name: "smoke", value: "clear", descriptionText: text)
|
||||
name = "clear"
|
||||
break
|
||||
case "testClear":
|
||||
text = "$device.displayName test cleared"
|
||||
results << createEvent(name: "smoke", value: "clear", descriptionText: text)
|
||||
name = "clear"
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd, results) {
|
||||
if (cmd.zwaveAlarmType == physicalgraph.zwave.commands.alarmv2.AlarmReport.ZWAVE_ALARM_TYPE_SMOKE) {
|
||||
if (cmd.zwaveAlarmEvent == 3) {
|
||||
createSmokeEvents("tested", results)
|
||||
} else {
|
||||
createSmokeEvents((cmd.zwaveAlarmEvent == 1 || cmd.zwaveAlarmEvent == 2) ? "smoke" : "smokeClear", results)
|
||||
}
|
||||
} else switch(cmd.alarmType) {
|
||||
case 1:
|
||||
createSmokeEvents(cmd.alarmLevel ? "smoke" : "smokeClear", results)
|
||||
break
|
||||
case 12: // test button pressed
|
||||
createSmokeEvents(cmd.alarmLevel ? "tested" : "testClear", results)
|
||||
break
|
||||
case 13: // sent every hour -- not sure what this means, just a wake up notification?
|
||||
if (cmd.alarmLevel == 255) {
|
||||
results << createEvent(descriptionText: "$device.displayName checked in", isStateChange: false)
|
||||
} else {
|
||||
results << createEvent(descriptionText: "$device.displayName code 13 is $cmd.alarmLevel", isStateChange:true, displayed:false)
|
||||
}
|
||||
|
||||
// Clear smoke in case they pulled batteries and we missed the clear msg
|
||||
if(device.currentValue("smoke") != "clear") {
|
||||
createSmokeEvents("smokeClear", results)
|
||||
}
|
||||
|
||||
// Check battery if we don't have a recent battery event
|
||||
if (!state.lastbatt || (now() - state.lastbatt) >= 48*60*60*1000) {
|
||||
results << response(zwave.batteryV1.batteryGet())
|
||||
}
|
||||
break
|
||||
default:
|
||||
results << createEvent(displayed: true, descriptionText: "Alarm $cmd.alarmType ${cmd.alarmLevel == 255 ? 'activated' : cmd.alarmLevel ?: 'deactivated'}".toString())
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// SensorBinary and SensorAlarm aren't tested, but included to preemptively support future smoke alarms
|
||||
//
|
||||
def zwaveEvent(physicalgraph.zwave.commands.sensorbinaryv2.SensorBinaryReport cmd, results) {
|
||||
if (cmd.sensorType == physicalgraph.zwave.commandclasses.SensorBinaryV2.SENSOR_TYPE_SMOKE) {
|
||||
createSmokeEvents(cmd.sensorValue ? "smoke" : "smokeClear", results)
|
||||
}
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.sensoralarmv1.SensorAlarmReport cmd, results) {
|
||||
if (cmd.sensorType == 1) {
|
||||
createSmokeEvents(cmd.sensorState ? "smoke" : "smokeClear", results)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd, results) {
|
||||
results << createEvent(descriptionText: "$device.displayName woke up", isStateChange: false)
|
||||
if (!state.lastbatt || (now() - state.lastbatt) >= 56*60*60*1000) {
|
||||
results << response(zwave.batteryV1.batteryGet(), "delay 2000", zwave.wakeUpV1.wakeUpNoMoreInformation())
|
||||
} else {
|
||||
results << response(zwave.wakeUpV1.wakeUpNoMoreInformation())
|
||||
}
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd, results) {
|
||||
def map = [ name: "battery", unit: "%", isStateChange: true ]
|
||||
state.lastbatt = now()
|
||||
if (cmd.batteryLevel == 0xFF) {
|
||||
map.value = 1
|
||||
map.descriptionText = "$device.displayName battery is low!"
|
||||
} else {
|
||||
map.value = cmd.batteryLevel
|
||||
}
|
||||
results << createEvent(map)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.Command cmd, results) {
|
||||
def event = [ displayed: false ]
|
||||
event.linkText = device.label ?: device.name
|
||||
event.descriptionText = "$event.linkText: $cmd"
|
||||
results << createEvent(event)
|
||||
}
|
||||
@@ -21,8 +21,6 @@ metadata {
|
||||
|
||||
attribute "alarmState", "string"
|
||||
|
||||
fingerprint deviceId: "0xA100", inClusters: "0x20,0x80,0x70,0x85,0x71,0x72,0x86"
|
||||
fingerprint mfr:"0138", prod:"0001", model:"0001", deviceJoinName: "First Alert Smoke Detector"
|
||||
fingerprint mfr:"0138", prod:"0001", model:"0002", deviceJoinName: "First Alert Smoke Detector and Carbon Monoxide Alarm (ZCOMBO)"
|
||||
}
|
||||
|
||||
@@ -57,6 +55,10 @@ metadata {
|
||||
def installed() {
|
||||
// Device checks in every hour, this interval allows us to miss one check-in notification before marking offline
|
||||
sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
|
||||
|
||||
def cmds = []
|
||||
createSmokeOrCOEvents("allClear", cmds) // allClear to set inital states for smoke and CO
|
||||
cmds.each { cmd -> sendEvent(cmd) }
|
||||
}
|
||||
|
||||
def updated() {
|
||||
@@ -105,6 +107,12 @@ def createSmokeOrCOEvents(name, results) {
|
||||
results << createEvent(name: "carbonMonoxide", value: "clear", descriptionText: text, displayed: false)
|
||||
name = "clear"
|
||||
break
|
||||
case "allClear":
|
||||
text = "$device.displayName all clear"
|
||||
results << createEvent(name: "smoke", value: "clear", descriptionText: text, displayed: false)
|
||||
results << createEvent(name: "carbonMonoxide", value: "clear", displayed: false)
|
||||
name = "clear"
|
||||
break
|
||||
case "testClear":
|
||||
text = "$device.displayName test cleared"
|
||||
results << createEvent(name: "smoke", value: "clear", descriptionText: text, displayed: false)
|
||||
|
||||
@@ -2,26 +2,11 @@ import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.security.InvalidKeyException;
|
||||
|
||||
/**
|
||||
* OpenT2T SmartApp Test
|
||||
*
|
||||
* Copyright 2016 OpenT2T
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
definition(
|
||||
name: "OpenT2T SmartApp Test",
|
||||
namespace: "opent2t",
|
||||
author: "OpenT2T",
|
||||
description: "Test app to test end to end SmartThings scenarios via OpenT2T",
|
||||
author: "Microsoft",
|
||||
description: "SmartApp for end to end SmartThings scenarios via OpenT2T",
|
||||
category: "SmartThings Labs",
|
||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
|
||||
@@ -55,16 +40,16 @@ definition(
|
||||
|
||||
//Device Inputs
|
||||
preferences {
|
||||
section("Allow OpenT2T to control these things...") {
|
||||
input "contactSensors", "capability.contactSensor", title: "Which Contact Sensors", multiple: true, required: false, hideWhenEmpty: true
|
||||
input "garageDoors", "capability.garageDoorControl", title: "Which Garage Doors?", multiple: true, required: false, hideWhenEmpty: true
|
||||
input "locks", "capability.lock", title: "Which Locks?", multiple: true, required: false, hideWhenEmpty: true
|
||||
input "cameras", "capability.videoCapture", title: "Which Cameras?", multiple: true, required: false, hideWhenEmpty: true
|
||||
input "motionSensors", "capability.motionSensor", title: "Which Motion Sensors?", multiple: true, required: false, hideWhenEmpty: true
|
||||
input "presenceSensors", "capability.presenceSensor", title: "Which Presence Sensors", multiple: true, required: false, hideWhenEmpty: true
|
||||
section("Allow Microsoft to control these things...") {
|
||||
// input "contactSensors", "capability.contactSensor", title: "Which Contact Sensors", multiple: true, required: false, hideWhenEmpty: true
|
||||
// input "garageDoors", "capability.garageDoorControl", title: "Which Garage Doors?", multiple: true, required: false, hideWhenEmpty: true
|
||||
// input "locks", "capability.lock", title: "Which Locks?", multiple: true, required: false, hideWhenEmpty: true
|
||||
// input "cameras", "capability.videoCapture", title: "Which Cameras?", multiple: true, required: false, hideWhenEmpty: true
|
||||
// input "motionSensors", "capability.motionSensor", title: "Which Motion Sensors?", multiple: true, required: false, hideWhenEmpty: true
|
||||
// input "presenceSensors", "capability.presenceSensor", title: "Which Presence Sensors", multiple: true, required: false, hideWhenEmpty: true
|
||||
input "switches", "capability.switch", title: "Which Switches and Lights?", multiple: true, required: false, hideWhenEmpty: true
|
||||
input "thermostats", "capability.thermostat", title: "Which Thermostat?", multiple: true, required: false, hideWhenEmpty: true
|
||||
input "waterSensors", "capability.waterSensor", title: "Which Water Leak Sensors?", multiple: true, required: false, hideWhenEmpty: true
|
||||
// input "waterSensors", "capability.waterSensor", title: "Which Water Leak Sensors?", multiple: true, required: false, hideWhenEmpty: true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,36 +67,32 @@ def getInputs() {
|
||||
return inputList
|
||||
}
|
||||
|
||||
|
||||
//API external Endpoints
|
||||
mappings {
|
||||
path("/devices") {
|
||||
action:
|
||||
[
|
||||
action: [
|
||||
GET: "getDevices"
|
||||
]
|
||||
}
|
||||
path("/devices/:id") {
|
||||
action:
|
||||
[
|
||||
action: [
|
||||
GET: "getDevice"
|
||||
]
|
||||
}
|
||||
path("/update/:id") {
|
||||
action:
|
||||
[
|
||||
action: [
|
||||
PUT: "updateDevice"
|
||||
]
|
||||
}
|
||||
path("/deviceSubscription") {
|
||||
action:
|
||||
[
|
||||
action: [
|
||||
POST : "registerDeviceChange",
|
||||
DELETE: "unregisterDeviceChange"
|
||||
]
|
||||
}
|
||||
path("/locationSubscription") {
|
||||
action:
|
||||
[
|
||||
action: [
|
||||
POST : "registerDeviceGraph",
|
||||
DELETE: "unregisterDeviceGraph"
|
||||
]
|
||||
@@ -196,7 +177,7 @@ def registerDeviceChange() {
|
||||
log.info "Added subscription URL: ${subscriptionEndpt} for ${myDevice.displayName}"
|
||||
} else if (!state.deviceSubscriptionMap[deviceId].contains(subscriptionEndpt)) {
|
||||
// state.deviceSubscriptionMap[deviceId] << subscriptionEndpt
|
||||
// For now, we will only have one subscription endpoint per device
|
||||
// For now, we will only have one subscription endpoint per device
|
||||
state.deviceSubscriptionMap.remove(deviceId)
|
||||
state.deviceSubscriptionMap.put(deviceId, [subscriptionEndpt])
|
||||
log.info "Added subscription URL: ${subscriptionEndpt} for ${myDevice.displayName}"
|
||||
@@ -311,16 +292,16 @@ def deviceEventHandler(evt) {
|
||||
def evtDeviceType = getDeviceType(evtDevice)
|
||||
def deviceData = [];
|
||||
|
||||
if (evt.data != null) {
|
||||
def evtData = parseJson(evt.data)
|
||||
log.info "Received event for ${evtDevice.displayName}, data: ${evtData}, description: ${evt.descriptionText}"
|
||||
}
|
||||
|
||||
if (evtDeviceType == "thermostat") {
|
||||
deviceData = [name: evtDevice.displayName, id: evtDevice.id, status: evtDevice.status, deviceType: evtDeviceType, manufacturer: evtDevice.manufacturerName, model: evtDevice.modelName, attributes: deviceAttributeList(evtDevice, evtDeviceType), locationMode: getLocationModeInfo(), locationId: location.id]
|
||||
} else {
|
||||
deviceData = [name: evtDevice.displayName, id: evtDevice.id, status: evtDevice.status, deviceType: evtDeviceType, manufacturer: evtDevice.manufacturerName, model: evtDevice.modelName, attributes: deviceAttributeList(evtDevice, evtDeviceType), locationId: location.id]
|
||||
}
|
||||
|
||||
if(evt.data != null){
|
||||
def evtData = parseJson(evt.data)
|
||||
log.info "Received event for ${evtDevice.displayName}, data: ${evtData}, description: ${evt.descriptionText}"
|
||||
}
|
||||
|
||||
def params = [body: deviceData]
|
||||
|
||||
@@ -330,10 +311,10 @@ def deviceEventHandler(evt) {
|
||||
params.uri = "${it}"
|
||||
if (state.verificationKeyMap[it] != null) {
|
||||
def key = state.verificationKeyMap[it]
|
||||
params.header = [Signature: ComputHMACValue(key, groovy.json.JsonOutput.toJson(params.body))]
|
||||
params.headers = [Signature: ComputHMACValue(key, groovy.json.JsonOutput.toJson(params.body))]
|
||||
}
|
||||
log.trace "POST URI: ${params.uri}"
|
||||
log.trace "Header: ${params.header}"
|
||||
log.trace "Headers: ${params.headers}"
|
||||
log.trace "Payload: ${params.body}"
|
||||
try {
|
||||
httpPostJson(params) { resp ->
|
||||
@@ -363,10 +344,10 @@ def locationEventHandler(evt) {
|
||||
params.uri = "${it}"
|
||||
if (state.verificationKeyMap[it] != null) {
|
||||
def key = state.verificationKeyMap[it]
|
||||
params.header = [Signature: ComputHMACValue(key, groovy.json.JsonOutput.toJson(params.body))]
|
||||
params.headers = [Signature: ComputHMACValue(key, groovy.json.JsonOutput.toJson(params.body))]
|
||||
}
|
||||
log.trace "POST URI: ${params.uri}"
|
||||
log.trace "Header: ${params.header}"
|
||||
log.trace "Headers: ${params.headers}"
|
||||
log.trace "Payload: ${params.body}"
|
||||
try {
|
||||
httpPostJson(params) { resp ->
|
||||
@@ -385,6 +366,7 @@ def locationEventHandler(evt) {
|
||||
|
||||
private ComputHMACValue(key, data) {
|
||||
try {
|
||||
log.debug "data hased: ${data}"
|
||||
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA1")
|
||||
Mac mac = Mac.getInstance("HmacSHA1")
|
||||
mac.init(secretKeySpec)
|
||||
@@ -507,7 +489,8 @@ private getDeviceType(device) {
|
||||
|
||||
//Loop through the device capability list to determine the device type.
|
||||
capabilities.each { capability ->
|
||||
switch (capability.name.toLowerCase()) {
|
||||
switch(capability.name.toLowerCase())
|
||||
{
|
||||
case "switch":
|
||||
deviceType = "switch"
|
||||
|
||||
@@ -652,7 +635,8 @@ private mapDeviceCommands(command, value) {
|
||||
if (value == 1 || value == "1" || value == "lock") {
|
||||
resultCommand = "lock"
|
||||
resultValue = ""
|
||||
} else if (value == 0 || value == "0" || value == "unlock") {
|
||||
}
|
||||
else if (value == 0 || value == "0" || value == "unlock") {
|
||||
resultCommand = "unlock"
|
||||
resultValue = ""
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user