Compare commits

...

17 Commits

Author SHA1 Message Date
Jean-François Many
da708c5430 MSA-2027: Removed setpoint value tile since the tile does not support text on multiple lines anymore 2017-06-08 08:01:41 -07:00
Vinay Rao
320c8918f8 Merge pull request #2075 from lymanepp/master
MSA-2076: Fix debug message
2017-06-07 15:32:43 -07:00
Lyman Epp
0f3656cd12 Fix debug message 2017-06-07 12:22:33 -05:00
Vinay Rao
c391ce4b43 Merge pull request #2073 from SmartThingsCommunity/staging
Rolling down staging to master
2017-06-06 11:55:16 -07:00
Vinay Rao
073bac8dac Merge pull request #2069 from larsfinander/DVCSMP-2688_Remove_sending_turningOn_Off_events_staging
DVCSMP-2688 Remove sending turningOn/Off events
2017-06-06 11:50:59 -07:00
Vinay Rao
3c83bfb53b Merge pull request #2071 from SmartThingsCommunity/production
Rolling down production to staging
2017-06-06 11:34:24 -07:00
Lars Finander
945a972082 DVCSMP-2688 Remove sending turningOn/Off events
-Removed createSwitchEvent()
2017-06-05 16:50:03 -06:00
Tom Manley
535fc112de Merge pull request #2055 from jmagnuson/DVCSMP-2537
DVCSMP-2537 Add CRC16 un-encapsulation to Z-Wave switches
2017-06-02 16:36:50 -05:00
Jon Magnuson
a844e85c47 Remove extra comment slash 2017-06-02 16:25:04 -05:00
Tom Manley
b65acdb91f Merge pull request #2060 from varzac/aeon-multisensor-gen5
[DVCSMP-2680] Correctly configure Aeon Multisensor Gen5
2017-06-02 12:02:59 -05:00
Vinay Rao
4067e6e7e2 Merge pull request #2058 from juano2310/jumping_fix
ICP-1124 - Sylvania SMART BR30 RGBW ' s color ticker moving randomly
2017-06-01 17:40:42 -07:00
Zach Varberg
4c6d1eddb2 Correctly configure Aeon Multisensor Gen5
Previously this device had been changed to use configureAfterSecure to
work around the secure inclusion for Z-Wave.  However, support was added
hub side for handling this so it needed to be changed back to using the
normal configure process.

This resolves: https://smartthings.atlassian.net/browse/DVCSMP-2680
2017-06-01 08:28:12 -05:00
Vinay Rao
10d2097ebe Merge pull request #2065 from bflorian/MSA-1951-1-production-fingerprint
MSA-1951 added second Inovelli fingerprint
2017-05-31 17:35:00 -07:00
Bob Florian
d3728d95b2 MSA-1951 added second Inovelli fingerprint 2017-05-31 16:45:55 -07:00
Vinay Rao
b645872d8f Merge pull request #2063 from SmartThingsCommunity/staging
Rolling up staging to production
2017-05-31 13:42:12 -07:00
juano2310
74a35eeddb ICP-1124 - Sylvania SMART BR30 RGBW ' s color ticker moving randomly
state

overwrite: true
2017-05-30 18:18:22 -04:00
Jon Magnuson
8aceef9be4 Add CRC16 un-encapsulation to Z-Wave switches
Fixes DVCSMP-2663 and DVCSMP-2537.
2017-05-25 17:07:12 -05:00
13 changed files with 1362 additions and 92 deletions

View File

@@ -26,7 +26,9 @@ metadata {
capability "Polling"
capability "Refresh"
capability "Health Check"
fingerprint manufacturer: "015D", prod: "0221", model: "251C"
fingerprint manufacturer: "015D", prod: "0221", model: "251C", deviceJoinName: "Show Home 2-Channel Smart Plug"
fingerprint manufacturer: "0312", prod: "B221", model: "251C", deviceJoinName: "Inovelli 2-Channel Smart Plug"
}
simulator {}
preferences {}

View File

@@ -22,8 +22,6 @@ metadata {
capability "Battery"
capability "Health Check"
command "configureAfterSecure"
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x86,0x72,0x59,0x85,0x73,0x71,0x84,0x80,0x30,0x31,0x70,0x98,0x7A", outClusters:"0x5A"
fingerprint mfr:"0086", prod:"0102", model:"004A", deviceJoinName: "Aeon Labs MultiSensor (Gen 5)"
}
@@ -91,12 +89,12 @@ metadata {
valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "battery", label:'${currentValue}% battery', unit:""
}
standardTile("configureAfterSecure", "device.configure", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "configure", label:'', action:"configureAfterSecure", icon:"st.secondary.configure"
standardTile("configure", "device.configure", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "configure", label:'', action:"configure", icon:"st.secondary.configure"
}
main(["motion", "temperature", "humidity", "illuminance"])
details(["motion", "temperature", "humidity", "illuminance", "battery", "configureAfterSecure"])
details(["motion", "temperature", "humidity", "illuminance", "battery", "configure"])
}
}
@@ -131,8 +129,8 @@ def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd)
if (!isConfigured()) {
// we're still in the process of configuring a newly joined device
log.debug("not sending wakeUpNoMoreInformation yet")
result += response(configureAfterSecure())
log.debug("not sending wakeUpNoMoreInformation yet: late configure")
result += response(configure())
} else {
result += response(zwave.wakeUpV1.wakeUpNoMoreInformation())
}
@@ -150,11 +148,6 @@ def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulat
}
}
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityCommandsSupportedReport cmd) {
// log.debug "Received SecurityCommandsSupportedReport"
response(configureAfterSecure())
}
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
def map = [ name: "battery", unit: "%" ]
if (cmd.batteryLevel == 0xFF) {
@@ -226,36 +219,6 @@ def zwaveEvent(physicalgraph.zwave.Command cmd) {
createEvent(descriptionText: cmd.toString(), isStateChange: false)
}
def configureAfterSecure() {
// log.debug "configureAfterSecure()"
def request = [
// send temperature, humidity, and illuminance every 8 minutes
zwave.configurationV1.configurationSet(parameterNumber: 101, size: 4, scaledConfigurationValue: 128|64|32),
zwave.configurationV1.configurationSet(parameterNumber: 111, size: 4, scaledConfigurationValue: 8*60),
// send battery every 20 hours
zwave.configurationV1.configurationSet(parameterNumber: 102, size: 4, scaledConfigurationValue: 1),
zwave.configurationV1.configurationSet(parameterNumber: 112, size: 4, scaledConfigurationValue: 20*60*60),
// send no-motion report 60 seconds after motion stops
zwave.configurationV1.configurationSet(parameterNumber: 3, size: 2, scaledConfigurationValue: 60),
// send binary sensor report instead of basic set for motion
zwave.configurationV1.configurationSet(parameterNumber: 5, size: 1, scaledConfigurationValue: 2),
// disable notification-style motion events
zwave.notificationV3.notificationSet(notificationType: 7, notificationStatus: 0),
zwave.batteryV1.batteryGet(),
zwave.sensorBinaryV2.sensorBinaryGet(sensorType:0x0C)
]
setConfigured()
secureSequence(request) + ["delay 20000", zwave.wakeUpV1.wakeUpNoMoreInformation().format()]
}
/**
* PING is used by Device-Watch in attempt to reach the Device
* */
@@ -265,7 +228,36 @@ def ping() {
def configure() {
// log.debug "configure()"
//["delay 30000"] + secure(zwave.securityV1.securityCommandsSupportedGet())
def request = []
// send temperature, humidity, and illuminance every 8 minutes
request << zwave.configurationV1.configurationSet(parameterNumber: 101, size: 4, scaledConfigurationValue: 128|64|32)
request << zwave.configurationV1.configurationSet(parameterNumber: 111, size: 4, scaledConfigurationValue: 8*60)
// send battery every 20 hours
request << zwave.configurationV1.configurationSet(parameterNumber: 102, size: 4, scaledConfigurationValue: 1)
request << zwave.configurationV1.configurationSet(parameterNumber: 112, size: 4, scaledConfigurationValue: 20*60*60)
// send no-motion report 60 seconds after motion stops
request << zwave.configurationV1.configurationSet(parameterNumber: 3, size: 2, scaledConfigurationValue: 60)
// send binary sensor report instead of basic set for motion
request << zwave.configurationV1.configurationSet(parameterNumber: 5, size: 1, scaledConfigurationValue: 2)
// Turn on the Multisensor Gen5 PIR sensor
request << zwave.configurationV1.configurationSet(parameterNumber: 4, size: 1, scaledConfigurationValue: 1)
// disable notification-style motion events
request << zwave.notificationV3.notificationSet(notificationType: 7, notificationStatus: 0)
request << zwave.batteryV1.batteryGet()
request << zwave.sensorBinaryV2.sensorBinaryGet(sensorType: 0x0C) //motion
request << zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 0x01) //temperature
request << zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 0x03) //illuminance
request << zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType: 0x05) //humidity
setConfigured()
secureSequence(request) + ["delay 20000", zwave.wakeUpV1.wakeUpNoMoreInformation().format()]
}
private setConfigured() {

View File

@@ -108,11 +108,20 @@ def updated(){
}
}
def getCommandClassVersions() {
[
0x20: 1, // Basic
0x26: 1, // SwitchMultilevel
0x56: 1, // Crc16Encap
0x70: 1, // Configuration
]
}
def parse(String description) {
def result = null
if (description != "updated") {
log.debug "parse() >> zwave.parse($description)"
def cmd = zwave.parse(description, [0x20: 1, 0x26: 1, 0x70: 1])
def cmd = zwave.parse(description, commandClassVersions)
if (cmd) {
result = zwaveEvent(cmd)
}
@@ -179,6 +188,16 @@ def zwaveEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelS
[createEvent(name:"switch", value:"on"), response(zwave.switchMultilevelV1.switchMultilevelGet().format())]
}
def zwaveEvent(physicalgraph.zwave.commands.crc16encapv1.Crc16Encap cmd) {
def versions = commandClassVersions
def version = versions[cmd.commandClass as Integer]
def ccObj = version ? zwave.commandClass(cmd.commandClass, version) : zwave.commandClass(cmd.commandClass)
def encapsulatedCommand = ccObj?.command(cmd.command)?.parse(cmd.data)
if (encapsulatedCommand) {
zwaveEvent(encapsulatedCommand)
}
}
def zwaveEvent(physicalgraph.zwave.Command cmd) {
// Handles all Z-Wave commands we aren't interested in
[:]

View File

@@ -49,6 +49,6 @@ def arrived() {
def departed() {
log.trace "Executing 'arrived'"
log.trace "Executing 'departed'"
sendEvent(name: "presence", value: "not present")
}

View File

@@ -103,12 +103,12 @@ def parse(String description) {
if (zigbeeMap?.clusterInt == COLOR_CONTROL_CLUSTER) {
if(zigbeeMap.attrInt == ATTRIBUTE_HUE){ //Hue Attribute
def hueValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 0xfe * 100)
sendEvent(name: "hue", value: hueValue, descriptionText: "Color has changed")
state.hueValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 0xfe * 100)
runIn(5, updateColor, [overwrite: true])
}
else if(zigbeeMap.attrInt == ATTRIBUTE_SATURATION){ //Saturation Attribute
def saturationValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 0xfe * 100)
sendEvent(name: "saturation", value: saturationValue, descriptionText: "Color has changed", displayed: false)
state.saturationValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 0xfe * 100)
runIn(5, updateColor, [overwrite: true])
}
}
else if (cluster && cluster.clusterId == 0x0006 && cluster.command == 0x07) {
@@ -127,6 +127,11 @@ def parse(String description) {
}
}
def updateColor() {
sendEvent(name: "hue", value: state.hueValue, descriptionText: "Color has changed")
sendEvent(name: "saturation", value: state.saturationValue, descriptionText: "Color has changed", displayed: false)
}
def on() {
zigbee.on()
}
@@ -204,9 +209,9 @@ def setColor(value){
log.trace "setColor($value)"
zigbee.on() +
zigbee.command(COLOR_CONTROL_CLUSTER, MOVE_TO_HUE_AND_SATURATION_COMMAND,
getScaledHue(value.hue), getScaledSaturation(value.saturation), "0000") +
zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE) +
zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION)
getScaledHue(value.hue), getScaledSaturation(value.saturation), "0000") +
zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_SATURATION) +
zigbee.readAttribute(COLOR_CONTROL_CLUSTER, ATTRIBUTE_HUE)
}
def setHue(value) {

View File

@@ -88,11 +88,20 @@ def updated(){
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
}
def getCommandClassVersions() {
[
0x20: 1, // Basic
0x26: 1, // SwitchMultilevel
0x56: 1, // Crc16Encap
0x70: 1, // Configuration
]
}
def parse(String description) {
def result = null
if (description != "updated") {
log.debug "parse() >> zwave.parse($description)"
def cmd = zwave.parse(description, [0x20: 1, 0x26: 1, 0x70: 1])
def cmd = zwave.parse(description, commandClassVersions)
if (cmd) {
result = zwaveEvent(cmd)
}
@@ -159,6 +168,16 @@ def zwaveEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelS
[createEvent(name:"switch", value:"on"), response(zwave.switchMultilevelV1.switchMultilevelGet().format())]
}
def zwaveEvent(physicalgraph.zwave.commands.crc16encapv1.Crc16Encap cmd) {
def versions = commandClassVersions
def version = versions[cmd.commandClass as Integer]
def ccObj = version ? zwave.commandClass(cmd.commandClass, version) : zwave.commandClass(cmd.commandClass)
def encapsulatedCommand = ccObj?.command(cmd.command)?.parse(cmd.data)
if (encapsulatedCommand) {
zwaveEvent(encapsulatedCommand)
}
}
def zwaveEvent(physicalgraph.zwave.Command cmd) {
// Handles all Z-Wave commands we aren't interested in
[:]

View File

@@ -85,11 +85,21 @@ metadata {
details(["switch", "power", "energy", "levelSliderControl", "refresh", "reset"])
}
def getCommandClassVersions() {
[
0x20: 1, // Basic
0x26: 3, // SwitchMultilevel
0x56: 1, // Crc16Encap
0x70: 1, // Configuration
0x32: 3, // Meter
]
}
// parse events into attributes
def parse(String description) {
def result = null
if (description != "updated") {
def cmd = zwave.parse(description, [0x20: 1, 0x26: 3, 0x70: 1, 0x32:3])
def cmd = zwave.parse(description, commandClassVersions)
if (cmd) {
result = zwaveEvent(cmd)
log.debug("'$description' parsed to $result")
@@ -124,6 +134,21 @@ def zwaveEvent(physicalgraph.zwave.commands.switchmultilevelv3.SwitchMultilevelR
dimmerEvents(cmd)
}
def zwaveEvent(physicalgraph.zwave.commands.crc16encapv1.Crc16Encap cmd) {
def versions = commandClassVersions
def version = versions[cmd.commandClass as Integer]
def ccObj = version ? zwave.commandClass(cmd.commandClass, version) : zwave.commandClass(cmd.commandClass)
def encapsulatedCommand = ccObj?.command(cmd.command)?.parse(cmd.data)
if (encapsulatedCommand) {
zwaveEvent(encapsulatedCommand)
}
}
def zwaveEvent(physicalgraph.zwave.Command cmd) {
// Handles all Z-Wave commands we aren't interested in
[:]
}
def dimmerEvents(physicalgraph.zwave.Command cmd) {
def result = []
def value = (cmd.value ? "on" : "off")

View File

@@ -89,10 +89,19 @@ def updated() {
} catch (e) { log.debug e }
}
def getCommandClassVersions() {
[
0x20: 1, // Basic
0x32: 1, // SwitchMultilevel
0x56: 1, // Crc16Encap
0x72: 2, // ManufacturerSpecific
]
}
def parse(String description) {
def result = null
if(description == "updated") return
def cmd = zwave.parse(description, [0x20: 1, 0x32: 1, 0x72: 2])
def cmd = zwave.parse(description, commandClassVersions)
if (cmd) {
result = zwaveEvent(cmd)
}
@@ -157,6 +166,16 @@ def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerS
result
}
def zwaveEvent(physicalgraph.zwave.commands.crc16encapv1.Crc16Encap cmd) {
def versions = commandClassVersions
def version = versions[cmd.commandClass as Integer]
def ccObj = version ? zwave.commandClass(cmd.commandClass, version) : zwave.commandClass(cmd.commandClass)
def encapsulatedCommand = ccObj?.command(cmd.command)?.parse(cmd.data)
if (encapsulatedCommand) {
zwaveEvent(encapsulatedCommand)
}
}
def zwaveEvent(physicalgraph.zwave.Command cmd) {
log.debug "$device.displayName: Unhandled: $cmd"
[:]

View File

@@ -71,9 +71,17 @@ def updated(){
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
}
def getCommandClassVersions() {
[
0x20: 1, // Basic
0x56: 1, // Crc16Encap
0x70: 1, // Configuration
]
}
def parse(String description) {
def result = null
def cmd = zwave.parse(description, [0x20: 1, 0x70: 1])
def cmd = zwave.parse(description, commandClassVersions)
if (cmd) {
result = createEvent(zwaveEvent(cmd))
}
@@ -120,6 +128,15 @@ def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerS
createEvent([descriptionText: "$device.displayName MSR: $msr", isStateChange: false])
}
def zwaveEvent(physicalgraph.zwave.commands.crc16encapv1.Crc16Encap cmd) {
def versions = commandClassVersions
def version = versions[cmd.commandClass as Integer]
def ccObj = version ? zwave.commandClass(cmd.commandClass, version) : zwave.commandClass(cmd.commandClass)
def encapsulatedCommand = ccObj?.command(cmd.command)?.parse(cmd.data)
if (encapsulatedCommand) {
zwaveEvent(encapsulatedCommand)
}
}
def zwaveEvent(physicalgraph.zwave.Command cmd) {
// Handles all Z-Wave commands we aren't interested in

View File

@@ -89,9 +89,17 @@ def updated(){
}
}
def getCommandClassVersions() {
[
0x20: 1, // Basic
0x56: 1, // Crc16Encap
0x70: 1, // Configuration
]
}
def parse(String description) {
def result = null
def cmd = zwave.parse(description, [0x20: 1, 0x70: 1])
def cmd = zwave.parse(description, commandClassVersions)
if (cmd) {
result = createEvent(zwaveEvent(cmd))
}
@@ -138,6 +146,16 @@ def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerS
createEvent([descriptionText: "$device.displayName MSR: $msr", isStateChange: false])
}
def zwaveEvent(physicalgraph.zwave.commands.crc16encapv1.Crc16Encap cmd) {
def versions = commandClassVersions
def version = versions[cmd.commandClass as Integer]
def ccObj = version ? zwave.commandClass(cmd.commandClass, version) : zwave.commandClass(cmd.commandClass)
def encapsulatedCommand = ccObj?.command(cmd.command)?.parse(cmd.data)
if (encapsulatedCommand) {
zwaveEvent(encapsulatedCommand)
}
}
def zwaveEvent(physicalgraph.zwave.Command cmd) {
// Handles all Z-Wave commands we aren't interested in

View File

@@ -0,0 +1,537 @@
/**
* Copyright 2017 Stelpro
*
* 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.
*
* Stelpro Ki Thermostat
*
* Author: Stelpro
*
* Date: 2017-06-08
*/
preferences {
input("zipcode", "text", title: "ZipCode (Outdoor Temperature)", description: "[Do not use space](Blank = No Forecast)")
input("heatdetails", "enum", title: "Do you want a detailed operating state notification?", options: ["No", "Yes"], defaultValue: "No", required: false, displayDuringSetup: true)
}
metadata {
definition (name: "Stelpro Ki Thermostat", namespace: "stelpro", author: "Stelpro") {
capability "Thermostat"
capability "Temperature Measurement"
capability "Actuator"
capability "Polling"
capability "Refresh"
capability "Sensor"
capability "Configuration"
attribute "outsideTemp", "number"
command "switchMode"
command "quickSetHeat"
command "quickSetOutTemp"
command "increaseHeatSetpoint"
command "decreaseHeatSetpoint"
command "setCustomThermostatMode"
command "eco"
command "applyNow"
fingerprint deviceId: "0x0806", inClusters: "0x5E,0x86,0x72,0x40,0x43,0x31,0x85,0x59,0x5A,0x73,0x20,0x42"
}
// simulator metadata
simulator {
//Add test code here
}
tiles(scale : 2) {
multiAttributeTile(name:"thermostatMulti", type:"thermostat", width:6, height:4) {
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
attributeState("temp", label:'${currentValue}')
attributeState("high", label:'HIGH')
attributeState("low", label:'LOW')
attributeState("--", label:'--')
}
tileAttribute("device.heatingSetpoint", key: "VALUE_CONTROL") {
attributeState("VALUE_UP", action: "increaseHeatSetpoint")
attributeState("VALUE_DOWN", action: "decreaseHeatSetpoint")
}
tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE") {
attributeState("idle", backgroundColor:"#44b621")
attributeState("heating", backgroundColor:"#ffa81e")
}
tileAttribute("device.thermostatMode", key: "THERMOSTAT_MODE") {
attributeState("off", label:'Off')
attributeState("comfort", label:'Comfort')
attributeState("eco", label:'Eco')
}
tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT")
{
attributeState("heatingSetpoint", label:'${currentValue}')
}
}
standardTile("mode", "device.thermostatMode", width: 2, height: 2) {
state "off", label:'${name}', action:"switchMode", nextState:"to_comfort", icon:"st.thermostat.off"
state "comfort", label:'${name}', action:"switchMode", nextState:"to_eco", icon:"http://cdn.device-icons.smartthings.com/Home/home29-icn@2x.png"
state "eco", label:'${name}', action:"switchMode", nextState:"...", icon:"http://cdn.device-icons.smartthings.com/Outdoor/outdoor3-icn@2x.png"
state "to_comfort", label: "comfort", action:"switchMode", nextState:"to_eco"
state "to_eco", label: "eco", action:"switchMode", nextState:"..."
state "...", label: "...", action:"heat", nextState:"comfort"
}
standardTile("refresh", "device.refresh", decoration: "flat", width: 2, height: 2) {
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main ("thermostatMulti")
details(["thermostatMulti", "mode", "refresh"])
}
}
def parse(String description)
{
if (description == "updated")
return []
def unitScale = getTemperatureScale()
//Class, version
def map = createEvent(zwaveEvent(zwave.parse(description, [0x40:2, 0x43:2, 0x31:3, 0x42:1])))
if (!map) {
return null
}
def result = [map]
if (map.name in ["heatingSetpoint","thermostatMode"]) {
def map2 = [
name: "thermostatSetpoint",
unit: getTemperatureScale()
]
if (map.name == "thermostatMode") {
state.lastTriedMode = map.value
}
else {
def mode = device.latestValue("thermostatMode")
log.info "THERMOSTAT, latest mode = ${mode}"
if (map.name == "heatingSetpoint") {
map2.value = map.value
map2.unit = map.unit
}
}
if (map2.value != null) {
log.debug "THERMOSTAT, adding setpoint event: $map"
result << createEvent(map2)
}
}
log.debug "Parse returned $result"
result
}
// Event Generation
def zwaveEvent(physicalgraph.zwave.commands.thermostatsetpointv2.ThermostatSetpointReport cmd)
{
def cmdScale = cmd.scale == 1 ? "F" : "C"
def temp;
float tempfloat;
def map = [:]
if (cmd.scaledValue >= 327)
{
map.value = "--"
}
else
{
temp = convertTemperatureIfNeeded(cmd.scaledValue, cmdScale, cmd.precision)
tempfloat = (Math.round(temp.toFloat() * 2)) / 2
map.value = tempfloat
}
map.unit = getTemperatureScale()
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
sendEvent(name:"heatingSetpoint", value:map.value)
map
}
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv3.SensorMultilevelReport cmd)
{
def temp;
float tempfloat;
def format;
def map = [:]
if (cmd.sensorType == 1) {
map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmd.scale == 1 ? "F" : "C", cmd.precision)
map.unit = getTemperatureScale()
map.name = "temperature"
temp = map.value
if (temp == "32765") //0x7FFD
{
map.value = "low"
}
else if (temp == "32767") //0x7FFF
{
map.value = "high"
}
else if (temp == "-32768") //0x8000
{
map.value = "--"
}
else
{
tempfloat = (Math.round(temp.toFloat() * 2)) / 2
map.value = tempfloat
}
} else if (cmd.sensorType == 5) {
map.value = cmd.scaledSensorValue
map.unit = "%"
map.name = "humidity"
}
sendEvent(name:"temperature", value:map.value)
map
}
def zwaveEvent(physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport cmd)
{
def map = [:]
switch (cmd.operatingState) {
case physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport.OPERATING_STATE_IDLE:
map.value = "idle"
break
case physicalgraph.zwave.commands.thermostatoperatingstatev1.ThermostatOperatingStateReport.OPERATING_STATE_HEATING:
map.value = "heating"
break
}
map.name = "thermostatOperatingState"
if (settings.heatdetails == "No") {
map.displayed = false
}
map
}
def zwaveEvent(physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport cmd) {
def map = [:]
switch (cmd.mode) {
case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_HEAT:
map.value = "comfort"
break
case physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeReport.MODE_OFF:
map.value = "off"
break
default:
map.value = "eco"
break
}
map.name = "thermostatMode"
sendEvent(name:"thermostatMode", value:map.value)
map
}
def zwaveEvent(physicalgraph.zwave.commands.associationv2.AssociationReport cmd) {
delayBetween([
zwave.associationV1.associationRemove(groupingIdentifier:1, nodeId:0).format(),
zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:[zwaveHubNodeId]).format(),
poll()
], 2300)
}
def zwaveEvent(physicalgraph.zwave.commands.thermostatmodev2.ThermostatModeSupportedReport cmd) {
log.debug "Zwave event received: $cmd"
}
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() {
def weather
// If there is a zipcode defined, weather forecast will be sent. Otherwise, no weather forecast.
if (settings.zipcode) {
log.debug "ZipCode: ${settings.zipcode}"
weather = getWeatherFeature( "conditions", settings.zipcode )
// Check if the variable is populated, otherwise return.
if (!weather) {
log.debug( "Something went wrong, no data found." )
return false
}
// Set the tiles
def locationScale = getTemperatureScale()
def tempToSend
if (locationScale == "C")
{
log.debug( "Outdoor Temperature: ${weather.current_observation.temp_c}ºC" )
sendEvent( name: 'outsideTemp', value: weather.current_observation.temp_c )
tempToSend = weather.current_observation.temp_c
}
else
{
log.debug( "Outdoor Temperature: ${weather.current_observation.temp_f}ºF" )
sendEvent( name: 'outsideTemp', value: weather.current_observation.temp_f )
tempToSend = weather.current_observation.temp_f
}
delayBetween([
quickSetOutTemp(tempToSend),
zwave.thermostatOperatingStateV1.thermostatOperatingStateGet().format(),
zwave.thermostatModeV2.thermostatModeGet().format(),
zwave.thermostatSetpointV2.thermostatSetpointGet(setpointType: 1).format(),
zwave.sensorMultilevelV3.sensorMultilevelGet().format(), // current temperature
sendEvent( name: 'change', value: 0 )
], 100)
} else {
delayBetween([
zwave.thermostatOperatingStateV1.thermostatOperatingStateGet().format(),
zwave.thermostatModeV2.thermostatModeGet().format(),
zwave.thermostatSetpointV2.thermostatSetpointGet(setpointType: 1).format(),
zwave.sensorMultilevelV3.sensorMultilevelGet().format(), // current temperature
sendEvent( name: 'change', value: 0 )
], 100)
}
}
def refresh() {
poll()
}
def configure() {
poll()
}
def applyNow() {
float currentHeatSetpoint = device.currentValue("heatingSetpoint")
def deviceScale
def locationScale = getTemperatureScale()
sendEvent( name: 'change', value: 0 )
log.debug("currentHeatSetpoint $currentHeatSetpoint")
if (locationScale == "C")
{
deviceScale = 0
}
else
{
deviceScale = 1
}
delayBetween([
zwave.thermostatSetpointV2.thermostatSetpointSet(setpointType: 1, scale: deviceScale, precision: state.precision, scaledValue: currentHeatSetpoint).format(),
poll()
], 1000)
}
def quickSetHeat(degrees) {
setHeatingSetpoint(degrees, 0)
}
def setHeatingSetpoint(degrees, delay = 0) {
sendEvent(name:"heatingSetpoint", value:degrees)
applyNow()
}
def quickSetOutTemp(degrees) {
setOutdoorTemperature(degrees, 0)
}
def setOutdoorTemperature(degrees, delay = 0) {
setOutdoorTemperature(degrees.toDouble(), delay)
}
def setOutdoorTemperature(Double degrees, Integer delay = 0) {
def deviceScale
def locationScale = getTemperatureScale()
def p = (state.precision == null) ? 1 : state.precision
if (locationScale == "C")
{
deviceScale = 0
}
else
{
deviceScale = 1
}
log.info "setOutdoorTemperature: ${degrees}"
zwave.sensorMultilevelV3.sensorMultilevelReport(sensorType: 1, scale: deviceScale, precision: p, scaledSensorValue: degrees).format()
}
def increaseHeatSetpoint()
{
def currentMode = device.currentState("thermostatMode")?.value
if (currentMode != "off")
{
float currentSetpoint = device.currentValue("heatingSetpoint")
def locationScale = getTemperatureScale()
float maxSetpoint
float step
if (locationScale == "C")
{
maxSetpoint = 30;
step = 0.5
}
else
{
maxSetpoint = 86
step = 1
}
if (currentSetpoint < maxSetpoint)
{
currentSetpoint = currentSetpoint + step
quickSetHeat(currentSetpoint)
}
}
}
def decreaseHeatSetpoint()
{
def currentMode = device.currentState("thermostatMode")?.value
if (currentMode != "off")
{
float currentSetpoint = device.currentValue("heatingSetpoint")
def locationScale = getTemperatureScale()
float minSetpoint
float step
if (locationScale == "C")
{
minSetpoint = 5;
step = 0.5
}
else
{
minSetpoint = 41
step = 1
}
if (currentSetpoint > minSetpoint)
{
currentSetpoint = currentSetpoint - step
quickSetHeat(currentSetpoint)
}
}
}
def switchMode() {
def currentMode = device.currentState("thermostatMode")?.value
def lastTriedMode = state.lastTriedMode ?: currentMode ?: "comfort"
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 != "comfort") {
nextMode = next(nextMode)
}
}
state.lastTriedMode = nextMode
delayBetween([
zwave.thermostatModeV2.thermostatModeSet(mode: modeMap[nextMode]).format(),
poll()
], 1000)
}
def modes() {
["comfort", "eco", "off"]
}
def getModeMap() { [
"off": 0,
"comfort": 1,
"eco": 11,
]}
def getDataByName(String name) {
state[name] ?: device.getDataValue(name)
}
def setCoolingSetpoint(coolingSetpoint) {
log.trace "${device.displayName} does not support cool setpoint"
}
def off() {
log.trace "off mode applied"
delayBetween([
zwave.thermostatModeV2.thermostatModeSet(mode: 0).format(),
poll()
], 1000)
}
def heat() {
log.trace "heat mode applied"
delayBetween([
zwave.thermostatModeV2.thermostatModeSet(mode: 1).format(),
poll()
], 1000)
}
def eco() {
log.trace "eco mode applied"
delayBetween([
zwave.thermostatModeV2.thermostatModeSet(mode: 11).format(),
poll()
], 1000)
}
def auto() {
log.trace "${device.displayName} does not support auto mode"
}
def emergencyHeat() {
log.trace "${device.displayName} does not support emergency heat mode"
}
def cool() {
log.trace "${device.displayName} does not support cool mode"
}
def setCustomThermostatMode(mode) {
setThermostatMode(mode)
}
def setThermostatMode(String value) {
delayBetween([
zwave.thermostatModeV2.thermostatModeSet(mode: modeMap[value]).format(),
poll()
], 1000)
}
def fanOn() {
log.trace "${device.displayName} does not support fan on"
}
def fanAuto() {
log.trace "${device.displayName} does not support fan auto"
}
def fanCirculate() {
log.trace "${device.displayName} does not support fan circulate"
}
def setThermostatFanMode() {
log.trace "${device.displayName} does not support fan mode"
}

View File

@@ -0,0 +1,651 @@
/**
* Copyright 2017 Stelpro
*
* 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.
*
* Stelpro Ki ZigBee Thermostat
*
* Author: Stelpro
*
* Date: 2017-06-08
*/
preferences {
section {
input("unitformat", "enum", title: "What unit format do you want your thermostat to display temperature?", options: ["Celsius", "Fahrenheit"], defaultValue: "Celsius", required: false, displayDuringSetup: false)
input("lock", "enum", title: "Do you want to lock your thermostat's physical keypad?", options: ["No", "Yes"], defaultValue: "No", required: false, displayDuringSetup: false)
input("heatdetails", "enum", title: "Do you want a detailed operating state notification?", options: ["No", "Yes"], defaultValue: "No", required: false, displayDuringSetup: true)
input("zipcode", "text", title: "ZipCode (Outdoor Temperature)", description: "[Do not use space](Blank = No Forecast)")
}
}
metadata {
definition (name: "Stelpro Ki ZigBee Thermostat", namespace: "Stelpro", author: "Stelpro") {
capability "Thermostat"
capability "Temperature Measurement"
capability "Actuator"
capability "Polling"
capability "Refresh"
capability "Sensor"
capability "Configuration"
command "switchMode"
command "quickSetHeat"
command "quickSetOutTemp"
command "increaseHeatSetpoint"
command "decreaseHeatSetpoint"
command "parameterSetting"
fingerprint profileId: "0104", endpointId: "19", inClusters: " 0000,0003,0201,0204", outClusters: "0402"
}
// simulator metadata
simulator { }
tiles(scale: 2) {
multiAttributeTile(name:"thermostatMulti", type:"thermostat", width:6, height:4) {
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
attributeState("temp", label:'${currentValue}')
attributeState("high", label:'HIGH')
attributeState("low", label:'LOW')
attributeState("--", label:'--')
}
tileAttribute("device.heatingSetpoint", key: "VALUE_CONTROL") {
attributeState("VALUE_UP", action: "increaseHeatSetpoint")
attributeState("VALUE_DOWN", action: "decreaseHeatSetpoint")
}
tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE") {
attributeState("idle", backgroundColor:"#44b621")
attributeState("heating", backgroundColor:"#ffa81e")
}
tileAttribute("device.thermostatMode", key: "THERMOSTAT_MODE") {
attributeState("off", label:'Off')
attributeState("comfort", label:'Comfort')
attributeState("eco", label:'Eco')
}
tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT")
{
attributeState("heatingSetpoint", label:'${currentValue}')
}
}
standardTile("mode", "device.thermostatMode", width: 2, height: 2) {
state "off", label:'Off', action:"switchMode", nextState:"comfort", icon:"st.thermostat.off"
state "comfort", label:'Comfort', action:"switchMode", nextState:"eco", icon:"http://cdn.device-icons.smartthings.com/Home/home29-icn@2x.png"
state "eco", label:'Eco', action:"switchMode", nextState:"off", icon:"http://cdn.device-icons.smartthings.com/Outdoor/outdoor3-icn@2x.png"
}
standardTile("refresh", "device.refresh", decoration: "flat", width: 2, height: 2) {
state "default", action:"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 ("thermostatMulti")
details(["thermostatMulti", "mode", "refresh", "configure"])
}
}
// parse events into attributes
def parse(String description) {
log.debug "Parse description $description"
def map = [:]
if (description?.startsWith("read attr -")) {
def descMap = parseDescriptionAsMap(description)
log.debug "Desc Map: $descMap"
if (descMap.cluster == "0201" && descMap.attrId == "0000")
{
log.debug "TEMP"
map.name = "temperature"
map.value = getTemperature(descMap.value)
if (descMap.value == "7ffd") //0x7FFD
{
map.value = "low"
}
else if (descMap.value == "7fff") //0x7FFF
{
map.value = "high"
}
else if (descMap.value == "8000") //0x8000
{
map.value = "--"
}
sendEvent(name:"temperature", value:map.value)
}
else if (descMap.cluster == "0201" && descMap.attrId == "0012")
{
log.debug "HEATING SETPOINT"
map.name = "heatingSetpoint"
map.value = getTemperature(descMap.value)
if (descMap.value == "8000") //0x8000
{
map.value = "--"
}
sendEvent(name:"heatingSetpoint", value:map.value)
}
else if (descMap.cluster == "0201" && descMap.attrId == "001c")
{
if (descMap.value.size() == 8)
{
log.debug "MODE"
map.name = "thermostatMode"
map.value = getModeMap()[descMap.value]
sendEvent(name:"thermostatMode", value:map.value)
}
else if (descMap.value.size() == 10)
{
log.debug "MODE & SETPOINT MODE"
def twoModesAttributes = descMap.value[0..-9]
map.name = "thermostatMode"
map.value = getModeMap()[twoModesAttributes]
sendEvent(name:"thermostatMode", value:map.value)
}
}
else if (descMap.cluster == "0201" && descMap.attrId == "401c")
{
log.debug "SETPOINT MODE"
log.debug "descMap.value $descMap.value"
map.name = "thermostatMode"
map.value = getModeMap()[descMap.value]
sendEvent(name:"thermostatMode", value:map.value)
}
else if (descMap.cluster == "0201" && descMap.attrId == "0008")
{
log.debug "HEAT DEMAND"
map.name = "thermostatOperatingState"
map.value = getModeMap()[descMap.value]
if (map.value == "off")
{
map.value = "idle"
}
else
{
map.value = "heating"
}
sendEvent(name:"thermostatOperatingState", value:map.value)
if (settings.heatdetails == "No")
{
map.displayed = false
}
}
}
def result = null
if (map) {
result = createEvent(map)
}
log.debug "Parse returned $map"
return result
}
def parseDescriptionAsMap(description) {
(description - "read attr - ").split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
}
}
def getModeMap() { [
"00":"off",
"04":"comfort",
"05":"eco"
]}
def getFanModeMap() { [
"04":"fanOn",
"05":"fanAuto"
]}
def poll() {
def weather
// If there is a zipcode defined, weather forecast will be sent. Otherwise, no weather forecast.
if (settings.zipcode) {
log.debug "ZipCode: ${settings.zipcode}"
weather = getWeatherFeature( "conditions", settings.zipcode )
// Check if the variable is populated, otherwise return.
if (!weather) {
log.debug( "Something went wrong, no data found." )
return false
}
// Set the tiles
def locationScale = getTemperatureScale()
def tempToSend
log.debug( "Outdoor Temperature: ${weather.current_observation.temp_c}ºC" )
sendEvent( name: 'outsideTemp', value: weather.current_observation.temp_c )
tempToSend = weather.current_observation.temp_c
delayBetween([
quickSetOutTemp(tempToSend),
zigbee.readAttribute(0x201, 0x0000), //Read Local Temperature
zigbee.readAttribute(0x201, 0x0008), //Read PI Heating State
zigbee.readAttribute(0x201, 0x0012), //Read Heat Setpoint
zigbee.readAttribute(0x201, 0x001C), //Read System Mode
my_readAttribute(0x201, 0x401C, ["mfgCode": "0x1185"]), //Read Manufacturer Specific Setpoint Mode
zigbee.readAttribute(0x204, 0x0000), //Read Temperature Display Mode
zigbee.readAttribute(0x204, 0x0001), //Read Keypad Lockout
sendEvent( name: 'change', value: 0 )
], 200)
} else {
delayBetween([
zigbee.readAttribute(0x201, 0x0000), //Read Local Temperature
zigbee.readAttribute(0x201, 0x0008), //Read PI Heating State
zigbee.readAttribute(0x201, 0x0012), //Read Heat Setpoint
zigbee.readAttribute(0x201, 0x001C), //Read System Mode
my_readAttribute(0x201, 0x401C, ["mfgCode": "0x1185"]), //Read Manufacturer Specific Setpoint Mode
zigbee.readAttribute(0x204, 0x0000), //Read Temperature Display Mode
zigbee.readAttribute(0x204, 0x0001), //Read Keypad Lockout
sendEvent( name: 'change', value: 0 )
], 200)
}
}
def getTemperature(value) {
if (value != null) {
log.debug("value $value")
def celsius = Integer.parseInt(value, 16) / 100
if (getTemperatureScale() == "C") {
return celsius
} else {
return Math.round(celsiusToFahrenheit(celsius))
}
}
}
def quickSetHeat(degrees) {
sendEvent( name: 'change', value: 1 )
setHeatingSetpoint(degrees)
}
def setHeatingSetpoint(degrees) {
if (degrees != null) {
def temperatureScale = getTemperatureScale()
def degreesInteger = Math.round(degrees)
float tempfloat;
tempfloat = (Math.round(degrees.toFloat() * 2)) / 2
log.debug "setHeatingSetpoint({$tempfloat} ${temperatureScale})"
sendEvent("name": "heatingSetpoint", "value": tempfloat)
def celsius = (getTemperatureScale() == "C") ? tempfloat : (fahrenheitToCelsius(tempfloat) as Float).round(2)
delayBetween([
"st wattr 0x${device.deviceNetworkId} 0x19 0x201 0x12 0x29 {" + hex(celsius * 100) + "}",
zigbee.readAttribute(0x201, 0x12), //Read Heat Setpoint
], 100)
}
}
def setCoolingSetpoint(degrees) {
if (degrees != null) {
def degreesInteger = Math.round(degrees)
log.debug "setCoolingSetpoint({$degreesInteger} ${temperatureScale})"
sendEvent("name": "coolingSetpoint", "value": degreesInteger)
def celsius = (getTemperatureScale() == "C") ? degreesInteger : (fahrenheitToCelsius(degreesInteger) as Double).round(2)
"st wattr 0x${device.deviceNetworkId} 0x19 0x201 0x11 0x29 {" + hex(celsius * 100) + "}"
}
}
def quickSetOutTemp(degrees) {
setOutdoorTemperature(degrees, 0)
}
def setOutdoorTemperature(degrees, delay = 0) {
setOutdoorTemperature(degrees.toDouble(), delay)
}
def setOutdoorTemperature(Double degrees, Integer delay = 0) {
def deviceScale
def locationScale = getTemperatureScale()
def p = (state.precision == null) ? 1 : state.precision
Integer tempToSend
def tempToSendInString
if (locationScale == "C")
{
deviceScale = 0
}
else
{
deviceScale = 1
}
if (degrees < 0)
{
tempToSend = -degrees*100 - 65536
tempToSend = -tempToSend
}
else
{
tempToSend = (degrees*100)
}
tempToSendInString = zigbee.convertToHexString(tempToSend, 4)
my_writeAttribute(0x201, 0x4001, 0x29, tempToSendInString, ["mfgCode": "0x1185"])
}
def increaseHeatSetpoint()
{
def currentMode = device.currentState("thermostatMode")?.value
if (currentMode != "off")
{
float currentSetpoint = device.currentValue("heatingSetpoint")
def locationScale = getTemperatureScale()
float maxSetpoint
float step
if (locationScale == "C")
{
maxSetpoint = 30;
step = 0.5
}
else
{
maxSetpoint = 86
step = 1
}
if (currentSetpoint < maxSetpoint)
{
currentSetpoint = currentSetpoint + step
quickSetHeat(currentSetpoint)
}
}
}
def decreaseHeatSetpoint()
{
def currentMode = device.currentState("thermostatMode")?.value
if (currentMode != "off")
{
float currentSetpoint = device.currentValue("heatingSetpoint")
def locationScale = getTemperatureScale()
float minSetpoint
float step
if (locationScale == "C")
{
minSetpoint = 5;
step = 0.5
}
else
{
minSetpoint = 41
step = 1
}
if (currentSetpoint > minSetpoint)
{
currentSetpoint = currentSetpoint - step
quickSetHeat(currentSetpoint)
}
}
}
def modes() {
["comfort", "eco", "off"]
}
def switchMode() {
def currentMode = device.currentState("thermostatMode")?.value
def lastTriedMode = state.lastTriedMode ?: currentMode ?: "comfort"
def modeOrder = modes()
def next = { modeOrder[modeOrder.indexOf(it) + 1] ?: modeOrder[0] }
def nextMode = next(currentMode)
def modeNumber;
Integer setpointModeNumber;
def modeToSendInString;
if (nextMode == "comfort")
{
modeNumber = 04
setpointModeNumber = 04
}
else if (nextMode == "eco")
{
modeNumber = 04
setpointModeNumber = 05
}
else
{
modeNumber = 00
setpointModeNumber = 00
}
if (supportedModes?.contains(currentMode)) {
while (!supportedModes.contains(nextMode) && nextMode != "comfort") {
nextMode = next(nextMode)
}
}
state.lastTriedMode = nextMode
modeToSendInString = zigbee.convertToHexString(setpointModeNumber, 2)
delayBetween([
"st wattr 0x${device.deviceNetworkId} 0x19 0x201 0x001C 0x30 {$modeNumber}",
my_writeAttribute(0x201, 0x401C, 0x30, modeToSendInString, ["mfgCode": "0x1185"]),
poll()
], 1000)
}
def setThermostatMode() {
log.debug "switching thermostatMode"
def currentMode = device.currentState("thermostatMode")?.value
def modeOrder = modes()
def index = modeOrder.indexOf(currentMode)
def next = index >= 0 && index < modeOrder.size() - 1 ? modeOrder[index + 1] : modeOrder[0]
log.debug "switching mode from $currentMode to $next"
"$next"()
}
def setThermostatFanMode() {
log.debug "Switching fan mode"
def currentFanMode = device.currentState("thermostatFanMode")?.value
log.debug "switching fan from current mode: $currentFanMode"
def returnCommand
switch (currentFanMode) {
case "fanAuto":
returnCommand = fanOn()
break
case "fanOn":
returnCommand = fanAuto()
break
}
if(!currentFanMode) { returnCommand = fanAuto() }
returnCommand
}
def setThermostatMode(String value) {
log.debug "setThermostatMode({$value})"
"$value"()
}
def setThermostatFanMode(String value) {
log.debug "setThermostatFanMode({$value})"
"$value"()
}
def off() {
log.debug "off"
sendEvent("name":"thermostatMode", "value":"off")
"st wattr 0x${device.deviceNetworkId} 0x19 0x201 0x1C 0x30 {00}"
}
def cool() {
log.debug "cool"
sendEvent("name":"thermostatMode", "value":"cool")
"st wattr 0x${device.deviceNetworkId} 0x19 0x201 0x1C 0x30 {03}"
}
def heat() {
log.debug "heat"
sendEvent("name":"thermostatMode", "value":"heat")
"st wattr 0x${device.deviceNetworkId} 0x19 0x201 0x1C 0x30 {04}"
}
def emergencyHeat() {
log.debug "emergencyHeat"
sendEvent("name":"thermostatMode", "value":"emergency heat")
"st wattr 0x${device.deviceNetworkId} 0x19 0x201 0x1C 0x30 {05}"
}
def on() {
fanOn()
}
def fanOn() {
log.debug "fanOn"
sendEvent("name":"thermostatFanMode", "value":"fanOn")
"st wattr 0x${device.deviceNetworkId} 0x19 0x202 0 0x30 {04}"
}
def auto() {
fanAuto()
}
def fanAuto() {
log.debug "fanAuto"
sendEvent("name":"thermostatFanMode", "value":"fanAuto")
"st wattr 0x${device.deviceNetworkId} 1 0x202 0 0x30 {05}"
}
def configure() {
log.debug "binding to Thermostat cluster"
delayBetween([
"zdo bind 0x${device.deviceNetworkId} 1 0x19 0x201 {${device.zigbeeId}} {}",
//Cluster ID (0x0201 = Thermostat Cluster), Attribute ID, Data Type, Payload (Min report, Max report, On change trigger)
zigbee.configureReporting(0x0201, 0x0000, 0x29, 10, 60, 50), //Attribute ID 0x0000 = local temperature, Data Type: S16BIT
zigbee.configureReporting(0x0201, 0x0012, 0x29, 1, 0, 50), //Attribute ID 0x0012 = occupied heat setpoint, Data Type: S16BIT
zigbee.configureReporting(0x0201, 0x001C, 0x30, 1, 0, 1), //Attribute ID 0x001C = system mode, Data Type: 8 bits enum
zigbee.configureReporting(0x0201, 0x401C, 0x30, 1, 0, 1), //Attribute ID 0x401C = manufacturer specific setpoint mode, Data Type: 8 bits enum
zigbee.configureReporting(0x0201, 0x0008, 0x20, 300, 900, 5), //Attribute ID 0x0008 = pi heating demand, Data Type: U8BIT
//Cluster ID (0x0204 = Thermostat Ui Conf Cluster), Attribute ID, Data Type, Payload (Min report, Max report, On change trigger)
zigbee.configureReporting(0x0204, 0x0000, 0x30, 1, 0, 1), //Attribute ID 0x0000 = temperature display mode, Data Type: 8 bits enum
zigbee.configureReporting(0x0204, 0x0001, 0x30, 1, 0, 1), //Attribute ID 0x0001 = keypad lockout, Data Type: 8 bits enum
//Read the configured variables
zigbee.readAttribute(0x201, 0x0000), //Read Local Temperature
zigbee.readAttribute(0x201, 0x0012), //Read Heat Setpoint
zigbee.readAttribute(0x201, 0x001C), //Read System Mode
my_readAttribute(0x201, 0x401C, ["mfgCode": "0x1185"]), //Read Manufacturer Specific Setpoint Mode
zigbee.readAttribute(0x201, 0x0008), //Read PI Heating State
zigbee.readAttribute(0x204, 0x0000), //Read Temperature Display Mode
zigbee.readAttribute(0x204, 0x0001), //Read Keypad Lockout
], 200)
}
def updated() {
log.debug "updated called"
response(parameterSetting())
}
def parameterSetting() {
def format = null
def lockmode = null
def valid_format = 0
def valid_lock = 0
log.info "unitformat : $settings.unitformat"
if (settings.unitformat == "Celsius") {
format = 0x00
valid_format = 1
}
else if (settings.unitformat == "Fahrenheit") {
format = 0x01
valid_format = 1
}
log.info "lock : $settings.lock"
if (settings.lock == "Yes") {
lockmode = 0x01
valid_lock = 1
}
else if (settings.lock == "No") {
lockmode = 0x00
valid_lock = 1
}
if ((valid_format == 1) && (valid_lock == 1))
{
log.info "both valid"
delayBetween([
zigbee.writeAttribute(0x204, 0x00, 0x30, format), //Write Unit Format Mode
zigbee.writeAttribute(0x204, 0x01, 0x30, lockmode), //Write Lock Mode
poll(),
], 200)
}
else if (valid_format == 1)
{
log.info "format valid"
delayBetween([
zigbee.writeAttribute(0x204, 0x00, 0x30, format), //Write Unit Format Mode
poll(),
], 200)
}
else if (valid_lock == 1)
{
log.info "lock valid"
delayBetween([
zigbee.writeAttribute(0x204, 0x01, 0x30, lockmode), //Write Lock Mode
poll(),
], 200)
}
else
{
log.info "nothing valid"
}
}
private hex(value) {
new BigInteger(Math.round(value).toString()).toString(16)
}
private getEndpointId() {
new BigInteger(device.endpointId, 16).toString()
}
private String swapEndianHex(String hex) {
reverseArray(hex.decodeHex()).encodeHex()
}
private byte[] reverseArray(byte[] array) {
int i = 0;
int j = array.length - 1;
byte tmp;
while (j > i) {
tmp = array[j];
array[j] = array[i];
array[i] = tmp;
j--;
i++;
}
return array
}
def my_readAttribute(cluster, attributeId, Map additional=null)
{
if (additional?.get("mfgCode")) {
[ "zcl mfg-code ${additional['mfgCode']}", "delay 200",
"zcl global read ${cluster} ${attributeId}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}" ]
} else {
zigbee.readAttribute(cluster, attributeId)
}
}
def my_writeAttribute(cluster, attributeId, dataType, value, Map additional=null)
{
value = swapEndianHex(value)
if (additional ?.get("mfgCode")) {
[ "zcl mfg-code ${additional['mfgCode']}", "delay 200",
"zcl global write ${cluster} ${attributeId} ${dataType} {${value}}", "delay 200",
"send 0x${device.deviceNetworkId} 1 ${endpointId}" ]
} else {
zigbee.writeAttribute(cluster, attributeId, dataType, value)
}
}

View File

@@ -1083,7 +1083,6 @@ def on(childDevice) {
log.debug "Executing 'on'"
def id = getId(childDevice)
updateInProgress()
createSwitchEvent(childDevice, "on")
put("lights/$id/state", [on: true])
return "Bulb is turning On"
}
@@ -1092,7 +1091,6 @@ def off(childDevice) {
log.debug "Executing 'off'"
def id = getId(childDevice)
updateInProgress()
createSwitchEvent(childDevice, "off")
put("lights/$id/state", [on: false])
return "Bulb is turning Off"
}
@@ -1108,8 +1106,6 @@ def setLevel(childDevice, percent) {
else
level = Math.min(Math.round(percent * 254 / 100), 254)
createSwitchEvent(childDevice, level > 0, percent)
// For Zigbee lights, if level is set to 0 ST just turns them off without changing level
// that means that the light will still be on when on is called next time
// Lets emulate that here
@@ -1128,7 +1124,6 @@ def setSaturation(childDevice, percent) {
// 0 - 254
def level = Math.min(Math.round(percent * 254 / 100), 254)
// TODO should this be done by app only or should we default to on?
createSwitchEvent(childDevice, "on")
put("lights/$id/state", [sat: level, on: true])
return "Setting saturation to $percent"
}
@@ -1140,7 +1135,6 @@ def setHue(childDevice, percent) {
// 0 - 65535
def level = Math.min(Math.round(percent * 65535 / 100), 65535)
// TODO should this be done by app only or should we default to on?
createSwitchEvent(childDevice, "on")
put("lights/$id/state", [hue: level, on: true])
return "Setting hue to $percent"
}
@@ -1151,7 +1145,6 @@ def setColorTemperature(childDevice, huesettings) {
updateInProgress()
// 153 (6500K) to 500 (2000K)
def ct = hueSettings == 6500 ? 153 : Math.round(1000000 / huesettings)
createSwitchEvent(childDevice, "on")
put("lights/$id/state", [ct: ct, on: true])
return "Setting color temperature to $ct"
}
@@ -1210,7 +1203,6 @@ def setColor(childDevice, huesettings) {
if (huesettings.switch == "off")
value.on = false
createSwitchEvent(childDevice, value.on ? "on" : "off")
put("lights/$id/state", value)
return "Setting color to $value"
}
@@ -1322,32 +1314,6 @@ private List getRealHubFirmwareVersions() {
return location.hubs*.firmwareVersionString.findAll { it }
}
/**
* Sends appropriate turningOn/turningOff state events depending on switch or level changes.
*
* @param childDevice device to send event for
* @param setSwitch The new switch state, "on" or "off"
* @param setLevel Optional, switchLevel between 0-100, used if you set level to 0 for example since
* that should generate "off" instead of level change
*/
private void createSwitchEvent(childDevice, setSwitch, setLevel = null) {
if (setLevel == null) {
setLevel = childDevice.device?.currentValue("level")
}
// Create on, off, turningOn or turningOff event as necessary
def currentState = childDevice.device?.currentValue("switch")
if ((currentState == "off" || currentState == "turningOff")) {
if (setSwitch == "on" || setLevel > 0) {
childDevice.sendEvent(name: "switch", value: "turningOn", displayed: false)
}
} else if ((currentState == "on" || currentState == "turningOn")) {
if (setSwitch == "off" || setLevel == 0) {
childDevice.sendEvent(name: "switch", value: "turningOff", displayed: false)
}
}
}
/**
* Return the supported color range for different Hue lights. If model is not specified
* it defaults to the smallest Gamut (B) to ensure that colors set on a mix of devices