mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-09 05:11:52 +00:00
Compare commits
1 Commits
PROD_2016.
...
MSA-1277-1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
970e5404ac |
@@ -94,11 +94,11 @@ def parse(String description) {
|
||||
def cmd = zwave.parse(description, [0x31: 1, 0x32: 1, 0x60: 3])
|
||||
if (cmd) {
|
||||
result = createEvent(zwaveEvent(cmd))
|
||||
log.debug "Parse returned ${result?.descriptionText}"
|
||||
storeGraphData(result.name, result.value)
|
||||
} else {
|
||||
log.debug "zwave.parse returned null command. Cannot create event"
|
||||
}
|
||||
log.debug "Parse returned ${result?.descriptionText}"
|
||||
|
||||
storeGraphData(result.name, result.value)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ metadata {
|
||||
capability "Configuration"
|
||||
capability "Sensor"
|
||||
capability "Battery"
|
||||
capability "Health Check"
|
||||
|
||||
attribute "tamper", "enum", ["detected", "clear"]
|
||||
attribute "batteryStatus", "string"
|
||||
@@ -327,9 +326,6 @@ def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
||||
}
|
||||
|
||||
def configure() {
|
||||
// allow device user configured or default 16 min to check in; double the periodic reporting interval
|
||||
sendEvent(name: "checkInterval", value: 2* timeOptionValueMap[reportInterval] ?: 2*8*60, displayed: false)
|
||||
|
||||
// This sensor joins as a secure device if you double-click the button to include it
|
||||
log.debug "${device.displayName} is configuring its settings"
|
||||
def request = []
|
||||
|
||||
@@ -20,9 +20,6 @@ metadata {
|
||||
capability "Configuration"
|
||||
capability "Sensor"
|
||||
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"
|
||||
}
|
||||
@@ -248,8 +245,6 @@ def configureAfterSecure() {
|
||||
def configure() {
|
||||
// log.debug "configure()"
|
||||
//["delay 30000"] + secure(zwave.securityV1.securityCommandsSupportedGet())
|
||||
// allow device 16 min to check in; double the periodic reporting interval
|
||||
sendEvent(name: "checkInterval", value: 2*8*60, displayed: false)
|
||||
}
|
||||
|
||||
private setConfigured() {
|
||||
|
||||
@@ -20,7 +20,6 @@ metadata {
|
||||
capability "Illuminance Measurement"
|
||||
capability "Sensor"
|
||||
capability "Battery"
|
||||
capability "Health Check"
|
||||
|
||||
fingerprint deviceId: "0x2001", inClusters: "0x30,0x31,0x80,0x84,0x70,0x85,0x72,0x86"
|
||||
}
|
||||
@@ -181,9 +180,6 @@ def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
||||
}
|
||||
|
||||
def configure() {
|
||||
// allow device 10 min to check in; double the periodic reporting interval
|
||||
sendEvent(name: "checkInterval", value: 2*5*60, displayed: false)
|
||||
|
||||
delayBetween([
|
||||
// send binary sensor report instead of basic set for motion
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 5, size: 1, scaledConfigurationValue: 2).format(),
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
//@Deprecated: Moved to ZLL Dimmer Bulb
|
||||
metadata {
|
||||
definition (name: "Cree Bulb", namespace: "smartthings", author: "SmartThings") {
|
||||
|
||||
@@ -24,7 +25,7 @@ metadata {
|
||||
capability "Switch"
|
||||
capability "Switch Level"
|
||||
|
||||
fingerprint profileId: "C05E", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0000,0019"
|
||||
//fingerprint profileId: "C05E", inClusters: "0000,0003,0004,0005,0006,0008,1000", outClusters: "0000,0019"
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
|
||||
@@ -138,7 +138,6 @@ def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerS
|
||||
log.debug "productTypeId: ${cmd.productTypeId}"
|
||||
def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId)
|
||||
updateDataValue("MSR", msr)
|
||||
updateDataValue("manufacturer", cmd.manufacturerName)
|
||||
createEvent([descriptionText: "$device.displayName MSR: $msr", isStateChange: false])
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,456 @@
|
||||
/**
|
||||
* Device Type Definition File
|
||||
*
|
||||
* Device Type: Fibaro Dimmer
|
||||
* File Name: fibaro-dimmer.groovy
|
||||
* Initial Release: 2015-06-00
|
||||
* Author: SmartThings
|
||||
*
|
||||
* 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 {
|
||||
// Automatically generated. Make future change here.
|
||||
definition (name: "Fibaro Dimmer 1", namespace: "smartthings", author: "SmartThings, Elnar Hajiyev") {
|
||||
capability "Switch Level"
|
||||
capability "Actuator"
|
||||
capability "Switch"
|
||||
capability "Polling"
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
capability "Configuration"
|
||||
|
||||
//Extending Fibaro Dimmer 1 devices with scene attribute
|
||||
attribute "scene", "number"
|
||||
|
||||
command "configureParams"
|
||||
|
||||
fingerprint deviceId: "0x1101", inClusters: "0x72,0x86,0x70,0x85,0x8E,0x26,0x7A,0x27,0x73,0xEF,0x26,0x2B"
|
||||
}
|
||||
|
||||
simulator {
|
||||
status "on": "command: 2003, payload: FF"
|
||||
status "off": "command: 2003, payload: 00"
|
||||
status "09%": "command: 2003, payload: 09"
|
||||
status "10%": "command: 2003, payload: 0A"
|
||||
status "33%": "command: 2003, payload: 21"
|
||||
status "66%": "command: 2003, payload: 42"
|
||||
status "99%": "command: 2003, payload: 63"
|
||||
|
||||
// reply messages
|
||||
reply "2001FF,delay 5000,2602": "command: 2603, payload: FF"
|
||||
reply "200100,delay 5000,2602": "command: 2603, payload: 00"
|
||||
reply "200119,delay 5000,2602": "command: 2603, payload: 19"
|
||||
reply "200132,delay 5000,2602": "command: 2603, payload: 32"
|
||||
reply "20014B,delay 5000,2602": "command: 2603, payload: 4B"
|
||||
reply "200163,delay 5000,2602": "command: 2603, payload: 63"
|
||||
}
|
||||
|
||||
tiles {
|
||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||
attributeState "level", action:"switch level.setLevel"
|
||||
}
|
||||
}
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
standardTile("configureParams", "device.configure", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "configure", label:'', action:"configureParams", icon:"st.secondary.configure"
|
||||
}
|
||||
|
||||
main(["switch"])
|
||||
details(["switch", "refresh",
|
||||
"levelSliderControl",
|
||||
"configureParams"
|
||||
])
|
||||
}
|
||||
|
||||
preferences {
|
||||
input name: "param1", type: "enum", defaultValue: "255", required: true,
|
||||
options: ["0" : "0",
|
||||
"1" : "1",
|
||||
"2" : "2",
|
||||
"255" : "255"],
|
||||
title: "1. Activate / deactivate functions ALL ON / ALL OFF.\n" +
|
||||
"Available settings:\n" +
|
||||
"0 = All ON not active, All OFF not active,\n" +
|
||||
"1 = All ON not active, All OFF active,\n" +
|
||||
"2 = All ON active, All OFF not active,\n" +
|
||||
"255 = All ON active, All OFF active.\n" +
|
||||
"Default value: 255."
|
||||
|
||||
input name: "param6", type: "number", range: "0..2", defaultValue: "0", required: true,
|
||||
title: "6. Sending commands to control devices assigned to 1st association group (key no. 1).\n" +
|
||||
"Available settings:\n" +
|
||||
"0 = commands are sent when device is turned on and off.\n" +
|
||||
"1 = commands are sent when device is turned off. Enabling device does not send control commands. Double-clicking key sends 'turn on' command, dimmers memorize the last saved state (e.g. 50% brightness).\n" +
|
||||
"2 = commands are sent when device is turned off. Enabling device does not send control commands. Double-clicking key sends 'turn on' command and dimmers are set to 100% brightness.\n" +
|
||||
"Default value: 1.\n\n" +
|
||||
"NOTE: Parameter 15 value must be set to 1 to work properly. This activates the double-click functionality - dimmer/roller shutter control."
|
||||
|
||||
input name: "param7", type: "number", range: "0..1", defaultValue: "1", required: true,
|
||||
title: "7. Checking the device status before sending a control frame from the key no. 2.\n" +
|
||||
"Available settings:\n" +
|
||||
"0 = Device status is not checked.\n" +
|
||||
"1 = Device status is checked.\n" +
|
||||
"Default value: 1.\n\n" +
|
||||
"Info: Key no. 2 is not represented by any physical device expect of devices on association list. " +
|
||||
"This functionality prevents of lack of reaction on pressing key no. 2 through polling devices from list one by one and checking thier actual states.\n" +
|
||||
"It is not possible to check the device status before sending a control frame from the key no. 2 if roller blind switch is chosen in parameter 14 (value 2)\n" +
|
||||
"If devices state is checked before sending asociation then parameter 19 should be set to value 0."
|
||||
|
||||
input name: "param8", type: "number", range: "1..99", defaultValue: "1", required: true,
|
||||
title: "8. The percentage of a dimming step at automatic control.\n" +
|
||||
"Available settings: 1-99\n" +
|
||||
"Default value: 1."
|
||||
|
||||
input name: "param9", type: "number", range: "1..255", defaultValue: "5", required: true,
|
||||
title: "9. Time of manually moving the Dimmer between the extreme dimming values.\n" +
|
||||
"Available settings: 1-255 (10ms – 2.5s)\n" +
|
||||
"Default value: 5."
|
||||
|
||||
input name: "param10", type: "number", range: "0..255", defaultValue: "1", required: true,
|
||||
title: "10. Time of Automatic moving the Dimmer between the extreme dimming values.\n" +
|
||||
"Available settings: 0-255 (0ms – 2.5s)\n" +
|
||||
"0 - this value disables the smooth change in light intensity\n" +
|
||||
"Default value: 1.\n\n" +
|
||||
"NOTE value 0 is required for inductive and capacitive devices unsuitable for dimming, (e.g. fluorescent lamps , motors etc.)."
|
||||
|
||||
input name: "param11", type: "number", range: "1..99", defaultValue: "1", required: true,
|
||||
title: "11. The percentage of a dimming step at manual control.\n" +
|
||||
"Available settings: 1-99\n" +
|
||||
"Default value: 1."
|
||||
|
||||
input name: "param12", type: "number", range: "2..99", defaultValue: "99", required: true,
|
||||
title: "12. Maximum Dimmer level control.\n" +
|
||||
"Available settings: 2-99\n" +
|
||||
"Default value: 99."
|
||||
|
||||
input name: "param13", type: "number", range: "1..98", defaultValue: "2", required: true,
|
||||
title: "13. Minimum Dimmer level control\n" +
|
||||
"Available settings: 1-98\n" +
|
||||
"Default value: 2.\n\n" +
|
||||
"NOTE: The maximum level may not be lower than the minimum level.\n" +
|
||||
"Recommended values of parameters 12 and 13 (max and min level) for controlling the devices are as follows:\n" +
|
||||
"- AC motors [min 60%, max 99%]\n" +
|
||||
"- fluorescent lamps, fluorescent tubes, LED [min 98%, max 99%] [parameter 10 set to 0]"
|
||||
|
||||
input name: "param14", type: "number", range: "0..2", defaultValue: "0", required: true,
|
||||
title: "14. Switch type. Choose between momentary switch and toggle switch.\n" +
|
||||
"Available settings:\n" +
|
||||
"0 = momentary switch,\n" +
|
||||
"1 = toggle switch,\n" +
|
||||
"2 = Roller blind switch (UP / DOWN) - two switch keys operate the Dimmer.\n" +
|
||||
"Default value: 0."
|
||||
|
||||
input name: "param15", type: "number", range: "0..1", defaultValue: "1", required: true,
|
||||
title: "15. Double click option (set lightning at 100%).\n" +
|
||||
"Available settings:\n" +
|
||||
"0 = Double click disabled,\n" +
|
||||
"1 = Double click enabled.\n" +
|
||||
"Default value: 1."
|
||||
|
||||
input name: "param16", type: "number", range: "0..1", defaultValue: "1", required: true,
|
||||
title: "16. Saving the state of the device after a power failure. The Dimmer will return to the last position before power failure.\n" +
|
||||
"Available settings:\n" +
|
||||
"0 = the Dimmer does not save the state after a power failure, it returns to 'off' position,\n" +
|
||||
"1 = the Dimmer saves its state before power failure.\n" +
|
||||
"Default value: 1."
|
||||
|
||||
input name: "param17", type: "number", range: "0..1", defaultValue: "0", required: true,
|
||||
title: "17. The function of 3 - way switch, provides the option to double key no. 1. " +
|
||||
"The Dimmer may control two toggle push-buttons or an infinite number of momentary push-buttons.\n" +
|
||||
"Available settings:\n" +
|
||||
"0 = the function of 3-way switch is disabled,\n" +
|
||||
"1 = the function of 3-way switch is enabled.\n" +
|
||||
"Default value: 0."
|
||||
|
||||
input name: "param18", type: "number", range: "0..1", defaultValue: "0", required: true,
|
||||
title: "18. The function of synchronizing the light level for associated devices. The Dimmer communicates the position to the associated device.\n" +
|
||||
"Available settings:\n" +
|
||||
"0 = function disabled,\n" +
|
||||
"1 = function enabled.\n" +
|
||||
"Default value: 0."
|
||||
|
||||
input name: "param19", type: "number", range: "0..1", defaultValue: "0", required: true,
|
||||
title: "19. Assigns bistable key status to the device status.\n" +
|
||||
"Available settings:\n" +
|
||||
"0 = [On / Off] device changes status on key status change.\n" +
|
||||
"1 = Device status depends on key status: ON when the key is ON, OFF when the key is OFF.\n"
|
||||
"Default value: 0.\n\n" +
|
||||
"Info: Remote control from Fibaro System Is Still Possible. This function is useful When you want display status of external devices, e.g. Motion Sensor, in Fibaro System."
|
||||
|
||||
input name: "param30", type: "number", range: "0..3", defaultValue: "3", required: true,
|
||||
title: "30. Alarm of any type (general alarm, water flooding alarm, smoke alarm: CO, CO2, temperature alarm).\n" +
|
||||
"Available settings:\n" +
|
||||
"0 = DEACTIVATION - the device does not respond to alarm data frames,\n" +
|
||||
"1 = ALARM DIMMER ON - the device turns on after detecting an alarm,\n" +
|
||||
"2 = ALARM DIMMER OFF - the device turns off after detecting an alarm,\n" +
|
||||
"3 = ALARM FLASHING the device periodically changes its status to the opposite, when it detects an alarm within 10 min."
|
||||
"Default value: 3."
|
||||
|
||||
input name: "param39", type: "number", range: "1..65535", defaultValue: "600", required: true,
|
||||
title: "39. Active flashing alarm time.\n" +
|
||||
"Available settings: [1-65535][ms]\n" +
|
||||
"Default value: 600."
|
||||
|
||||
input name: "param41", type: "number", range: "0..1", defaultValue: "0", required: true,
|
||||
title: "41. Scene activation functionality.\n" +
|
||||
"The device offers the possibility of sending commands compatible with Command class scene activation. " +
|
||||
"Information is sent to devices assigned to association group no. 3. " +
|
||||
"Controllers such as Home Center 2 are able to interpret such commands and based on these commands they activate scenes, to which specific scene IDs have been assigned. " +
|
||||
"The user may expand the functionality of the button connected to inputs S1 and S2 by distinguishing the actions of keys connected to those inputs. " +
|
||||
"For example: double click would activate the scene “goodnight” and triple click would activate the scene “morning”.\n" +
|
||||
"Available settings:\n" +
|
||||
"0 = functionality deactivated,\n" +
|
||||
"1 = functionality activated.\n" +
|
||||
"Default value: 0.\n\n" +
|
||||
"Scene ID is determined as follows:\n" +
|
||||
"Momentary switch:\n" +
|
||||
"Input S1: holding down ID 12 (option inactive in case of roller blind switch), " +
|
||||
"releasing ID 13, double click ID 14 (depends on parameters 15 value - 1 = double click active), " +
|
||||
"triple click ID 15, one click ID 16.\n" +
|
||||
"Input S2: holding down ID 22 (option inactive in case of roller blind switch), " +
|
||||
"releasing ID 23, double click ID 24 (depends on parameters 15 value - 1 = double click active) option inactive in case of roller blind switch, " +
|
||||
"triple click ID 25, one click ID 26.\n\n" +
|
||||
"Toggle switch:\n" +
|
||||
"Input S1: holding down ID 12, releasing ID 13, double click ID 14 (depends on parameters 15 value - 1 = double click active), triple click ID 15. " +
|
||||
"If parameter no. 19 is set to 0: single click ID16 is sent. If parameter no. 19 is set to 1 following IDs are sent: switch from “off” to “on” ID 10, switch from “on” to “off” ID 11.\n" +
|
||||
"Input S2: holding down ID 22, releasing ID 23, double click ID 24, (depends on parameters 15 value - 1 = double click active), triple click ID 25. " +
|
||||
"If parameter no.19 set to 0 (default), then one click ID 26 is sent. If parameter no.19 is set to 1 following IDs are sent: switch from “off” to “on” ID 20, switch from “on” to “off” ID 21.\n\n" +
|
||||
"Roller blind switch:\n" +
|
||||
"Input S1, Turning on the light: switch from “off” to “on” ID 10, double click ID 14 (depends of parameter 15 value 1 - double click functionality), " +
|
||||
"triple click ID 15, brighten ID 17, releasing ID 13.\n" +
|
||||
"Input S2, Turning off the light: switch from “on” to “off” ID 11, triple click ID 25, dim ID 18, releasing ID 13."
|
||||
|
||||
input name: "param20", type: "number", range: "101..170", defaultValue: "110", required: true,
|
||||
title: "ADVANCED FUNCTION\n\n" +
|
||||
"20. The function enabling the change of control impulse length. " +
|
||||
"This function will enable decreasing the minimum level of the Dimmer by extending the control impulse. " +
|
||||
"By changing the minimum level, the user may completely dim LED bulbs. Not all LED bulbs available on the market have the dimming option.\n" +
|
||||
"Available settings:\n" +
|
||||
"Default values: 110 for 50Hz (UK), 101 for 60Hz (USA).\n\n"+
|
||||
"WARNING!\nWrong setting of the function may cause incorrect operation of the Dimmer."
|
||||
|
||||
input name: "paramAssociationGroup1", type: "bool", defaultValue: false, required: true,
|
||||
title: "The Dimmer 1 provides the association of three groups.\n\n" +
|
||||
"1 = 1st group is assigned to key no. 1,\n" +
|
||||
"Default value: true"
|
||||
|
||||
input name: "paramAssociationGroup2", type: "bool", defaultValue: false, required: true,
|
||||
title: "2nd group is assigned to key no. 2\n" +
|
||||
"Default value: true"
|
||||
|
||||
input name: "paramAssociationGroup3", type: "bool", defaultValue: true, required: true,
|
||||
title: "3rd group for controllers such Home Center for state reporting,\n" +
|
||||
"Default value: false"
|
||||
}
|
||||
}
|
||||
|
||||
def parse(String description) {
|
||||
def item1 = [
|
||||
canBeCurrentState: false,
|
||||
linkText: getLinkText(device),
|
||||
isStateChange: false,
|
||||
displayed: false,
|
||||
descriptionText: description,
|
||||
value: description
|
||||
]
|
||||
def result
|
||||
def cmd = zwave.parse(description, [0x26: 1, 0x70: 2, 072: 2])
|
||||
//log.debug "cmd: ${cmd}"
|
||||
|
||||
if (cmd) {
|
||||
result = createEvent(cmd, item1)
|
||||
}
|
||||
else {
|
||||
item1.displayed = displayed(description, item1.isStateChange)
|
||||
result = [item1]
|
||||
}
|
||||
|
||||
if(result?.descriptionText)
|
||||
log.debug "Parse returned ${result?.descriptionText}"
|
||||
|
||||
result
|
||||
|
||||
}
|
||||
|
||||
def createEvent(physicalgraph.zwave.commands.sceneactivationv1.SceneActivationSet cmd, Map map) {
|
||||
log.debug( "Scene ID: $cmd.sceneId")
|
||||
log.debug( "Dimming Duration: $cmd.dimmingDuration")
|
||||
|
||||
sendEvent(name: "scene", value: "$cmd.sceneId", data: [switchType: "$settings.param20"], descriptionText: "Scene id $cmd.sceneId was activated", isStateChange: true)
|
||||
log.debug( "Scene id $cmd.sceneId was activated" )
|
||||
}
|
||||
|
||||
def createEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd, Map item1) {
|
||||
def result = doCreateEvent(cmd, item1)
|
||||
for (int i = 0; i < result.size(); i++) {
|
||||
result[i].type = "physical"
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
def createEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd, Map item1) {
|
||||
def result = doCreateEvent(cmd, item1)
|
||||
for (int i = 0; i < result.size(); i++) {
|
||||
result[i].type = "physical"
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
def createEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelStartLevelChange cmd, Map item1) {
|
||||
[]
|
||||
}
|
||||
|
||||
def createEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelStopLevelChange cmd, Map item1) {
|
||||
[response(zwave.basicV1.basicGet())]
|
||||
}
|
||||
|
||||
def createEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelSet cmd, Map item1) {
|
||||
def result = doCreateEvent(cmd, item1)
|
||||
for (int i = 0; i < result.size(); i++) {
|
||||
result[i].type = "physical"
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
def createEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelReport cmd, Map item1) {
|
||||
def result = doCreateEvent(cmd, item1)
|
||||
result[0].descriptionText = "${item1.linkText} is ${item1.value}"
|
||||
result[0].handlerName = cmd.value ? "statusOn" : "statusOff"
|
||||
for (int i = 0; i < result.size(); i++) {
|
||||
result[i].type = "digital"
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
def doCreateEvent(physicalgraph.zwave.Command cmd, Map item1) {
|
||||
def result = [item1]
|
||||
|
||||
item1.name = "switch"
|
||||
item1.value = cmd.value ? "on" : "off"
|
||||
item1.handlerName = item1.value
|
||||
item1.descriptionText = "${item1.linkText} was turned ${item1.value}"
|
||||
item1.canBeCurrentState = true
|
||||
item1.isStateChange = isStateChange(device, item1.name, item1.value)
|
||||
item1.displayed = item1.isStateChange
|
||||
|
||||
if (cmd.value >= 5) {
|
||||
def item2 = new LinkedHashMap(item1)
|
||||
item2.name = "level"
|
||||
item2.value = cmd.value as String
|
||||
item2.unit = "%"
|
||||
item2.descriptionText = "${item1.linkText} dimmed ${item2.value} %"
|
||||
item2.canBeCurrentState = true
|
||||
item2.isStateChange = isStateChange(device, item2.name, item2.value)
|
||||
item2.displayed = false
|
||||
result << item2
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
def createEvent(physicalgraph.zwave.Command cmd, Map map) {
|
||||
// Handles any Z-Wave commands we aren't interested in
|
||||
log.debug "UNHANDLED COMMAND $cmd"
|
||||
}
|
||||
|
||||
def on() {
|
||||
log.info "on"
|
||||
delayBetween([zwave.basicV1.basicSet(value: 0xFF).format(), zwave.switchMultilevelV1.switchMultilevelGet().format()], 5000)
|
||||
}
|
||||
|
||||
def off() {
|
||||
delayBetween ([zwave.basicV1.basicSet(value: 0x00).format(), zwave.switchMultilevelV1.switchMultilevelGet().format()], 5000)
|
||||
}
|
||||
|
||||
def setLevel(value) {
|
||||
def level = Math.min(value as Integer, 99)
|
||||
delayBetween ([zwave.basicV1.basicSet(value: level).format(), zwave.switchMultilevelV1.switchMultilevelGet().format()], 5000)
|
||||
}
|
||||
|
||||
def setLevel(value, duration) {
|
||||
def level = Math.min(value as Integer, 99)
|
||||
def dimmingDuration = duration < 128 ? duration : 128 + Math.round(duration / 60)
|
||||
zwave.switchMultilevelV2.switchMultilevelSet(value: level, dimmingDuration: dimmingDuration).format()
|
||||
}
|
||||
|
||||
def poll() {
|
||||
zwave.switchMultilevelV1.switchMultilevelGet().format()
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
zwave.switchMultilevelV1.switchMultilevelGet().format()
|
||||
}
|
||||
|
||||
def createEvent(physicalgraph.zwave.commands.configurationv2.ConfigurationReport cmd, Map item1) {
|
||||
|
||||
log.debug "${device.displayName} parameter '${cmd.parameterNumber}' with a byte size of '${cmd.size}' is set to '${cmd.configurationValue}'"
|
||||
|
||||
}
|
||||
|
||||
def configureParams() {
|
||||
log.debug "Configuring ${device.displayName} parameters"
|
||||
def cmds = []
|
||||
cmds << zwave.configurationV1.configurationSet(parameterNumber: 1, size: 1, scaledConfigurationValue: param1.toInteger()).format()
|
||||
cmds << zwave.configurationV1.configurationSet(parameterNumber: 6, size: 1, scaledConfigurationValue: param6.toInteger()).format()
|
||||
cmds << zwave.configurationV1.configurationSet(parameterNumber: 7, size: 1, scaledConfigurationValue: param7.toInteger()).format()
|
||||
cmds << zwave.configurationV1.configurationSet(parameterNumber: 8, size: 1, scaledConfigurationValue: param8.toInteger()).format()
|
||||
cmds << zwave.configurationV1.configurationSet(parameterNumber: 9, size: 1, scaledConfigurationValue: param9.toInteger()).format()
|
||||
cmds << zwave.configurationV1.configurationSet(parameterNumber: 10, size: 1, scaledConfigurationValue: param10.toInteger()).format()
|
||||
cmds << zwave.configurationV1.configurationSet(parameterNumber: 11, size: 1, scaledConfigurationValue: param11.toInteger()).format()
|
||||
cmds << zwave.configurationV1.configurationSet(parameterNumber: 13, size: 1, scaledConfigurationValue: param13.toInteger()).format()
|
||||
cmds << zwave.configurationV1.configurationSet(parameterNumber: 15, size: 1, scaledConfigurationValue: param15.toInteger()).format()
|
||||
cmds << zwave.configurationV1.configurationSet(parameterNumber: 16, size: 1, scaledConfigurationValue: param16.toInteger()).format()
|
||||
cmds << zwave.configurationV1.configurationSet(parameterNumber: 19, size: 1, scaledConfigurationValue: param19.toInteger()).format()
|
||||
cmds << zwave.configurationV1.configurationSet(parameterNumber: 20, size: 1, scaledConfigurationValue: param20.toInteger()).format()
|
||||
cmds << zwave.configurationV1.configurationSet(parameterNumber: 30, size: 1, scaledConfigurationValue: param30.toInteger()).format()
|
||||
cmds << zwave.configurationV1.configurationSet(parameterNumber: 39, size: 1, scaledConfigurationValue: param39.toInteger()).format()
|
||||
cmds << zwave.configurationV1.configurationSet(parameterNumber: 41, size: 1, scaledConfigurationValue: param41.toInteger()).format()
|
||||
|
||||
// Register for Group 1
|
||||
if(paramAssociationGroup1) {
|
||||
cmds << zwave.associationV2.associationSet(groupingIdentifier:1, nodeId: [zwaveHubNodeId]).format()
|
||||
}
|
||||
else {
|
||||
cmds << zwave.associationV2.associationRemove(groupingIdentifier:1, nodeId: [zwaveHubNodeId]).format()
|
||||
}
|
||||
// Register for Group 2
|
||||
if(paramAssociationGroup2) {
|
||||
cmds << zwave.associationV2.associationSet(groupingIdentifier:2, nodeId: [zwaveHubNodeId]).format()
|
||||
}
|
||||
else {
|
||||
cmds << zwave.associationV2.associationRemove(groupingIdentifier:2, nodeId: [zwaveHubNodeId]).format()
|
||||
}
|
||||
// Register for Group 3
|
||||
if(paramAssociationGroup3) {
|
||||
cmds << zwave.associationV2.associationSet(groupingIdentifier:3, nodeId: [zwaveHubNodeId]).format()
|
||||
}
|
||||
else {
|
||||
cmds << zwave.associationV2.associationRemove(groupingIdentifier:3, nodeId: [zwaveHubNodeId]).format()
|
||||
}
|
||||
|
||||
delayBetween(cmds, 500)
|
||||
}
|
||||
|
||||
def updated() {
|
||||
log.debug "updated()"
|
||||
response(["delay 2000"] + configureParams() + refresh())
|
||||
}
|
||||
@@ -0,0 +1,787 @@
|
||||
/**
|
||||
* 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: "Fibaro Dimmer 2", namespace: "smartthings", author: "Rajiv, Elnar Hajiyev") {
|
||||
capability "Energy Meter"
|
||||
capability "Actuator"
|
||||
capability "Switch"
|
||||
capability "Power Meter"
|
||||
capability "Polling"
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
capability "Configuration"
|
||||
capability "Switch Level"
|
||||
|
||||
//Extending Fibaro Dimmer 2 devices with scene attribute
|
||||
attribute "scene", "number"
|
||||
|
||||
command "reset"
|
||||
command "configureAfterSecure"
|
||||
|
||||
fingerprint deviceId: "0x1001", inClusters: "0x5E, 0x20, 0x86, 0x72, 0x26, 0x5A, 0x59, 0x85, 0x73, 0x98, 0x7A, 0x56, 0x70, 0x31, 0x32, 0x8E, 0x60, 0x75, 0x71, 0x27, 0x22, 0xEF, 0x2B"
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
simulator {
|
||||
status "on": "command: 2003, payload: FF"
|
||||
status "off": "command: 2003, payload: 00"
|
||||
status "09%": "command: 2003, payload: 09"
|
||||
status "10%": "command: 2003, payload: 0A"
|
||||
status "33%": "command: 2003, payload: 21"
|
||||
status "66%": "command: 2003, payload: 42"
|
||||
status "99%": "command: 2003, payload: 63"
|
||||
|
||||
for (int i = 0; i <= 10000; i += 1000) {
|
||||
status "power ${i} W": new physicalgraph.zwave.Zwave().meterV3.meterReport(
|
||||
scaledMeterValue: i, precision: 3, meterType: 4, scale: 2, size: 4).incomingMessage()
|
||||
}
|
||||
for (int i = 0; i <= 100; i += 10) {
|
||||
status "energy ${i} kWh": new physicalgraph.zwave.Zwave().meterV3.meterReport(
|
||||
scaledMeterValue: i, precision: 3, meterType: 0, scale: 0, size: 4).incomingMessage()
|
||||
}
|
||||
|
||||
["FF", "00", "09", "0A", "21", "42", "63"].each { val ->
|
||||
reply "2001$val,delay 100,2602": "command: 2603, payload: $val"
|
||||
}
|
||||
}
|
||||
|
||||
// tile definitions
|
||||
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||
attributeState "level", action:"switch level.setLevel"
|
||||
}
|
||||
}
|
||||
valueTile("power", "device.power", decoration: "flat", width: 2, height: 2) {
|
||||
state "default", label:'${currentValue} W'
|
||||
}
|
||||
valueTile("energy", "device.energy", decoration: "flat", width: 2, height: 2) {
|
||||
state "default", label:'${currentValue} kWh'
|
||||
}
|
||||
standardTile("reset", "device.energy", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "default", label:'reset kWh', action:"reset"
|
||||
}
|
||||
standardTile("configureAfterSecure", "device.configure", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "configure", label:'', action:"configureAfterSecure", icon:"st.secondary.configure"
|
||||
}
|
||||
standardTile("refresh", "device.power", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
main(["switch","power","energy"])
|
||||
details(["switch","power","energy","configureAfterSecure","refresh","reset"])
|
||||
}
|
||||
|
||||
preferences {
|
||||
def paragraph = "GROUP 0 - The Dimmer 2 behavior - Basic functionalities"
|
||||
input name: "param1", type: "number", range: "1..98", defaultValue: "1", required: true,
|
||||
title: paragraph + "\n\n" +
|
||||
"1. Minimum brightness level. " +
|
||||
"This parameter is set automatically during the calibration process. " +
|
||||
"The parameter can be changed manually after the calibration.\n" +
|
||||
"Available settings: 1-98 - percentage level of brightness.\n" +
|
||||
"Default value: 1."
|
||||
|
||||
input name: "param2", type: "number", range: "2..99", defaultValue: "99", required: true,
|
||||
title: "2. Maximum brightness level. " +
|
||||
"This parameter is set automatically during the calibration process. " +
|
||||
"The parameter can be changed manually after the calibration.\n" +
|
||||
"Available settings: 2-99 - percentage level of brightness.\n" +
|
||||
"Default value: 99."
|
||||
|
||||
input name: "param3", type: "number", range: "1..99", defaultValue: "1", required: true,
|
||||
title: "3. Incandescence level of dimmable compact fluorescent lamps. " +
|
||||
"Virtual value set as a percentage level between parameters MIN (1%) and MAX (99%). " +
|
||||
"The Dimmer 2 will set to this value after first switch on. " +
|
||||
"It is required for warming up and switching dimmable compact fluorescent lamps and certain types of light sources.\n" +
|
||||
"Available settings: 1-99 - percentage level of brightness.\n" +
|
||||
"Default value: 1."
|
||||
|
||||
input name: "param4", type: "number", range: "0..255", defaultValue: "0", required: true,
|
||||
title: "4. Incandescence time of dimmable compact fluorescent lamps. " +
|
||||
"This parameter determines the time required for switching compact fluorescent lamps and certain types of light sources. " +
|
||||
"Setting this parameter to 0 will disable the incandescence functionality.\n" +
|
||||
"Available settings: 0-255 (0-25.5s).\n" +
|
||||
"Default value: 0."
|
||||
|
||||
input name: "param5", type: "number", range: "1..99", defaultValue: "1", required: true,
|
||||
title: "5. Automatic control - dimming step size. " +
|
||||
"This parameter defines the percentage value of dimming step during the automatic control.\n" +
|
||||
"Available settings: 1-99 - dimming step percentage value.\n" +
|
||||
"Default value: 1."
|
||||
|
||||
input name: "param6", type: "number", range: "0..255", defaultValue: "1", required: true,
|
||||
title: "6. Automatic control - time of a dimming step. " +
|
||||
"This parameter defines the time of single dimming step set in parameter 5 during the automatic control.\n" +
|
||||
"Available settings: 0-255 (0-2.55s, in 10ms steps).\n" +
|
||||
"Default value: 1."
|
||||
|
||||
input name: "param7", type: "number", range: "1..99", defaultValue: "1", required: true,
|
||||
title: "7. Manual control - dimming step size. " +
|
||||
"This parameter defines the percentage value of dimming step during the manual control.\n" +
|
||||
"Available settings: 1-99 - dimming step percentage value.\n" +
|
||||
"Default value: 1."
|
||||
|
||||
input name: "param8", type: "number", range: "0..255", defaultValue: "5", required: true,
|
||||
title: "8. Manual control - time of a dimming step. " +
|
||||
"This parameter defines the time of single dimming step set in parameter 7 during the manual control.\n" +
|
||||
"Available settings: 0-255 (0-2.55s, in 10ms steps).\n" +
|
||||
"Default value: 5."
|
||||
|
||||
input name: "param9", type: "number", range: "0..1", defaultValue: "1", required: true,
|
||||
title: "9. State of the device after a power failure. " +
|
||||
"The Dimmer 2 will return to the last state before power failure.\n" +
|
||||
"Available settings:\n" +
|
||||
"0 = the Dimmer 2 does not save the state before a power failure, it returns to „off” position,\n" +
|
||||
"1 = the Dimmer 2 restores its state before power failure.\n" +
|
||||
"Default value: 1."
|
||||
|
||||
input name: "param10", type: "number", range: "0..32767", defaultValue: "0", required: true,
|
||||
title: "10. Timer functionality (auto - off). " +
|
||||
"This parameter allows to automatically switch off the device after specified time from switching on the light source. " +
|
||||
"It may be useful when the Dimmer 2 is installed in the stairway.\n" +
|
||||
"Available settings: 0 - Function disabled,\n1-32767 - time to turn off measured in seconds (1s-9.1h).\n" +
|
||||
"Default value: 0."
|
||||
|
||||
input name: "param11", type: "enum", defaultValue: "255", required: true,
|
||||
options: ["0" : "0",
|
||||
"1" : "1",
|
||||
"2" : "2",
|
||||
"255" : "255"],
|
||||
title: "11. ALL ON/ALL OFF function. " +
|
||||
"Parameter allows for activation/deactivation of Z-Wave commands enabling/disabling all devices located in direct range of the main controller.\n" +
|
||||
"Available settings:\n" +
|
||||
"0 = All ON not active, All OFF not active,\n" +
|
||||
"1 = All ON not active, All OFF active,\n" +
|
||||
"2 = All ON active, All OFF not active,\n" +
|
||||
"255 = All ON active, All OFF active.\n" +
|
||||
"Default value: 255."
|
||||
|
||||
input name: "param13", type: "number", range: "0..2", defaultValue: "0", required: true,
|
||||
title: "13. Force auto-calibration. " +
|
||||
"Changing value of this parameter will force the calibration process. " +
|
||||
"During the calibration parameter is set to 1 or 2 and switched to 0 upon completion.\n" +
|
||||
"Available settings:\n" +
|
||||
"0 = readout,\n" +
|
||||
"1 = force auto-calibration of the load without FIBARO Bypass 2,\n" +
|
||||
"2 = force auto-calibration of the load with FIBARO Bypass 2.\n" +
|
||||
"Default value: 0."
|
||||
|
||||
input name: "param15", type: "number", range: "0..99", defaultValue: "30", required: true,
|
||||
title: "15. Burnt out bulb detection. " +
|
||||
"Function based on the sudden power variation of a specific value, interpreted as a LOAD ERROR.\n" +
|
||||
"Available settings:\n0 - function disabled,\n" +
|
||||
"1-99 - percentage value of power variation, compared to standard power consumption, measured during the calibration procedure (to be interpreted as load error/burnt out bulb).\n" +
|
||||
"Default value: 30."
|
||||
|
||||
input name: "param16", type: "number", range: "0..255", defaultValue: "5", required: true,
|
||||
title: "16. Time delay of a burnt out bulb (parameter 15) or overload (parameter 39) detection. " +
|
||||
"Time of delay (in seconds) for power variation detection, interpreted as a LOAD ERROR or OVERLOAD detection (too much power connected to the Dimmer 2).\n" +
|
||||
"Available settings:\n0 - detection of a burnt out bulb disabled,\n1-255 - delay time in seconds.\n" +
|
||||
"Default value: 5."
|
||||
|
||||
input name: "param19", type: "number", range: "0..99", defaultValue: "0", required: true,
|
||||
title: "19. Forced switch on brightness level. " +
|
||||
"If the parameter is active, switching on the Dimmer 2 (S1 single click) will always set this brightness level.\n" +
|
||||
"Available settings:\n0 - function disabled,\n1-99 - percentage level of brightness.\n" +
|
||||
"Default value: 0."
|
||||
|
||||
paragraph = "GROUP 20 - Dimmer 2 operation - Switches"
|
||||
input name: "param20", type: "number", range: "0..2", defaultValue: "0", required: true,
|
||||
title: paragraph + "\n\n" +
|
||||
"20. Switch type. " +
|
||||
"Choose between momentary, toggle and roller blind switch.\n" +
|
||||
"Available settings:\n" +
|
||||
"0 = momentary switch,\n1 = toggle switch,\n2 = roller blind switch - two switches operate the Dimmer 2 (S1 to brighten, S2 to dim).\n" +
|
||||
"Default value: 0."
|
||||
|
||||
input name: "param21", type: "number", range: "0..1", defaultValue: "0", required: true,
|
||||
title: "21. The value sent to associated devices on single click. " +
|
||||
"This parameter defines the value sent to devices associated with Dimmer 2 after its enabling.\n" +
|
||||
"Available settings:\n" +
|
||||
"0 = 0xFF value is sent, which will set associated devices to their last saved state,\n" +
|
||||
"1 = current Dimmer 2 state is sent, which will synchronize brightness level of associated devices (other dimmers for example).\n" +
|
||||
"Default value: 0."
|
||||
|
||||
input name: "param22", type: "number", range: "0..1", defaultValue: "0", required: true,
|
||||
title: "22. Assign toggle switch status to the device status. " +
|
||||
"By default each change of toggle switch position results in action of Dimmer 2 (switch on/off) regardless the physical connection of contacts.\n" +
|
||||
"Available settings:\n" +
|
||||
"0 = device changes status on switch status change,\n1 = device status is synchronized with switch status.\n" +
|
||||
"Default value: 0."
|
||||
|
||||
input name: "param23", type: "number", range: "0..1", defaultValue: "1", required: true,
|
||||
title: "23. Double click option - set the brightness level to MAX.\n" +
|
||||
"Available settings:\n" +
|
||||
"0 = double click disabled,\n" +
|
||||
"1 = double click enabled.\n" +
|
||||
"Default value: 1."
|
||||
|
||||
input name: "param24", type: "enum", defaultValue: "0", required: true,
|
||||
options: ["0" : "0",
|
||||
"1" : "1",
|
||||
"2" : "2",
|
||||
"4" : "4",
|
||||
"8" : "8",
|
||||
"16" : "16"],
|
||||
title: "24. Command frames sent in 2nd and 3rd association groups (S1 associations). " +
|
||||
"Parameter determines, which actions will not result in sending frames to association groups.\n" +
|
||||
"Available settings:\n" +
|
||||
"0 = all actions send to association groups,\n" +
|
||||
"1 = do not send when switching ON (single click),\n" +
|
||||
"2 = do not send when switching OFF (single click),\n" +
|
||||
"4 = do not send when changing dimming level (holding and releasing),\n" +
|
||||
"8 = do not send on double click,\n" +
|
||||
"16 = send 0xFF value on double click.\n" +
|
||||
"Default value: 0."
|
||||
|
||||
input name: "param25", type: "enum", defaultValue: "0", required: true,
|
||||
options: ["0" : "0",
|
||||
"1" : "1",
|
||||
"2" : "2",
|
||||
"4" : "4",
|
||||
"8" : "8",
|
||||
"16" : "16"],
|
||||
title: "25. Command frames sent in 4th and 5th association groups (S2 associations). " +
|
||||
"Parameter determines, which actions will not result in sending frames to association groups.\n" +
|
||||
"Available settings:\n" +
|
||||
"0 = all actions send to association groups,\n" +
|
||||
"1 = do not send when switching ON (single click),\n" +
|
||||
"2 = do not send when switching OFF (single click),\n" +
|
||||
"4 = do not send when changing dimming level (holding and releasing),\n" +
|
||||
"8 = do not send on double click,\n" +
|
||||
"16 = send 0xFF value on double click.\n" +
|
||||
"Default value: 0."
|
||||
|
||||
input name: "param26", type: "number", range: "0..1", defaultValue: "0", required: true,
|
||||
title: "26. The function of 3-way switch. " +
|
||||
"Switch no. 2 controls the Dimmer 2 additionally (in 3-way switch mode). Function disabled for parameter 20 set to 2 (roller blind switch).\n" +
|
||||
"Available settings:\n" +
|
||||
"0 = 3-way switch function for S2 disabled,\n" +
|
||||
"1 = 3-way switch function for S2 enabled.\n" +
|
||||
"Default value: 0."
|
||||
|
||||
input name: "param27", type: "enum", defaultValue: "15", required: true,
|
||||
options: ["0" : "0",
|
||||
"1" : "1",
|
||||
"2" : "2",
|
||||
"4" : "4",
|
||||
"8" : "8",
|
||||
"15" : "15"],
|
||||
title: "27. Associations in Z-Wave network security mode. " +
|
||||
"This parameter defines how commands are sent in speci ed association groups: as secure or non-secure. " +
|
||||
"Parameter is active only in Z-Wave network security mode. It does not apply to 1st lifeline group.\n" +
|
||||
"Available settings:\n" +
|
||||
"0 = all groups (II-V) sent as non-secure,\n" +
|
||||
"1 = 2nd group sent as secure,\n" +
|
||||
"2 = 3rd group sent as secure,\n" +
|
||||
"4 = 4th group sent as secure,\n" +
|
||||
"8 = 5th group sent as secure,\n" +
|
||||
"15 = all groups (II-V) sent as secure.\n" +
|
||||
"Default value: 15."
|
||||
|
||||
input name: "param28", type: "number", range: "0..1", defaultValue: "0", required: true,
|
||||
title: "28. Scene activation functionality.\n" +
|
||||
"Available settings:\n" +
|
||||
"0 = functionality deactivated,\n" +
|
||||
"1 = functionality activated.\n" +
|
||||
"Default value: 0.\n\n" +
|
||||
"SCENE ID depends on the switch type configurations.\n" +
|
||||
"Momentary switches:\n" +
|
||||
"SCENE ID: S1 input:\n\t16 : 1 x click\n\t14 : 2 x click\n\t-- : 3 x click\n\t12 : hold\n\t13 : release\n" +
|
||||
"SCENE ID: S2 input:\n\t26 : 1 x click\n\t24 : 2 x click\n\t25 : 3 x click\n\t22 : hold\n\t23 : release\n\n" +
|
||||
"Toggle switches:\n" +
|
||||
"SCENE ID: S1 input:\n\t10 : OFF to ON\n\t11 : ON to OFF\n\t14 : 2 x click\n\t-- : 3 x click\n" +
|
||||
"SCENE ID: S2 input:\n\t20 : OFF to ON\n\t21 : ON to OFF\n\t24 : 2 x click\n\t25 : 3 x click\n\n" +
|
||||
"Roller blinds switches:\n" +
|
||||
"SCENE ID: S1 input:\n\t10 : turn ON (1 x click)\n\t13 : release\n\t14 : 2 x click\n\t-- : 3 x click\n\t17 : brightening\n" +
|
||||
"SCENE ID: S2 input:\n\t11 : turn OFF (1 x click)\n\t13 : release\n\t14 : 2 x click\n\t15 : 3 x click\n\t18 : dimming"
|
||||
|
||||
input name: "param29", type: "number", range: "0..1", defaultValue: "0", required: true,
|
||||
title: "29. Switch functionality of S1 and S2. " +
|
||||
"This parameter allows for switching the role of keys connected to S1 and S2 without changes in connection.\n" +
|
||||
"Available settings:\n" +
|
||||
"0 = standard mode,\n" +
|
||||
"1 = S1 operates as S2, S2 operates as S1.\n" +
|
||||
"Default value: 0."
|
||||
|
||||
paragraph = "GROUP 30 - Dimmer 2 operation - Advanced functionality"
|
||||
input name: "param30", type: "number", range: "0..2", defaultValue: "2", required: true,
|
||||
title: paragraph + "\n\n" +
|
||||
"30. Load control mode. " +
|
||||
"This parameter allows to set the desired load control mode. " +
|
||||
"The de- vice automatically adjusts correct control mode, but the installer may force its change using this parameter. " +
|
||||
"Forced auto-calibration will set this parameter’s value to 2.\n" +
|
||||
"Available settings:\n" +
|
||||
"0 = forced leading edge control,\n" +
|
||||
"1 = forced trailing edge control,\n" +
|
||||
"2 = control mode selected automatically (based on auto-calibration).\n" +
|
||||
"Default value: 2."
|
||||
|
||||
input name: "param32", type: "number", range: "0..2", defaultValue: "2", required: true,
|
||||
title: "32. On/Off mode. " +
|
||||
"This mode is necessary while connecting non-dimmable light sources. " +
|
||||
"Setting this parameter to 1 automatically ignores brightening/dimming time settings. " +
|
||||
"Forced auto-calibration will set this parameter’s value to 2.\n" +
|
||||
"Available settings:\n" +
|
||||
"0 = on/off mode disabled (dimming is possible),\n" +
|
||||
"1 = on/off mode enabled (dimming is not possible),\n" +
|
||||
"2 = mode selected automatically.\n" +
|
||||
"Default value: 2."
|
||||
|
||||
input name: "param34", type: "number", range: "0..2", defaultValue: "1", required: true,
|
||||
title: "34. Soft-Start functionality. " +
|
||||
"Time required to warm up the lament of halogen bulb.\n" +
|
||||
"Available settings:\n" +
|
||||
"0 = no soft-start,\n" +
|
||||
"1 = short soft-start (0.1s),\n" +
|
||||
"2 = long soft-start (0.5s).\n" +
|
||||
"Default value: 1."
|
||||
|
||||
input name: "param35", type: "number", range: "0..4", defaultValue: "1", required: true,
|
||||
title: "35. Auto-calibration after power on. " +
|
||||
"This parameter determines the trigger of auto-calibration procedure, e.g. power on, load error, etc.\n" +
|
||||
"Available settings:\n" +
|
||||
"0 = No auto-calibration of the load after power on,\n" +
|
||||
"1 = Auto-calibration performed after first power on,\n" +
|
||||
"2 = Auto-calibration performed after each power on,\n" +
|
||||
"3 = Auto-calibration performed after first power on or after each LOAD ERROR alarm (no load, load failure, burnt out bulb), if parameter 37 is set to 1 also after alarms: SURGE (Dimmer 2 output overvoltage) and OVERCURRENT (Dimmer 2 output overcurrent),\n" +
|
||||
"4 = Auto-calibration performed after each power on or after each LOAD ERROR alarm (no load, load failure, burnt out bulb), if parameter 37 is set to 1 also after alarms: SURGE (Dimmer 2 output overvoltage) and OVERCURRENT (Dimmer 2 output overcurrent).\n" +
|
||||
"Default value: 1."
|
||||
|
||||
input name: "param37", type: "number", range: "0..1", defaultValue: "1", required: true,
|
||||
title: "37. Behaviour of the Dimmer 2 after OVERCURRENT or SURGE. " +
|
||||
"Occuring of errors related to surge or overcurrent results in turning off the output to prevent possible malfunction. " +
|
||||
"By default the device performs three attempts to turn on the load (useful in case of momentary, short failures of the power supply).\n" +
|
||||
"Available settings:\n" +
|
||||
"0 = device permanently disabled until re-ena- bling by command or external switch,\n" +
|
||||
"1 = three attempts to turn on the load.\n" +
|
||||
"Default value: 1."
|
||||
|
||||
input name: "param39", type: "number", range: "0..350", defaultValue: "250", required: true,
|
||||
title: "39. Power limit - OVERLOAD. " +
|
||||
"Reaching the defined value will result in turning off the load. " +
|
||||
"Additional apparent power limit of 350VA is active by default.\n" +
|
||||
"Available settings:\n0 - functionality disabled,\n1-350 - 1-350W.\n" +
|
||||
"Default value: 250."
|
||||
|
||||
paragraph = "GROUP 40 - Dimmer 2 operation - Alarms"
|
||||
input name: "param40", type: "number", range: "0..3", defaultValue: "3", required: true,
|
||||
title: paragraph + "\n\n" +
|
||||
"40. Response to General Purpose Alarm.\n" +
|
||||
"Available settings:\n" +
|
||||
"0 = No reaction,\n" +
|
||||
"1 = Turn on the load,\n" +
|
||||
"2 = Turn off the load,\n" +
|
||||
"3 = Load blinking.\n" +
|
||||
"Default value: 3."
|
||||
|
||||
input name: "param41", type: "number", range: "0..3", defaultValue: "2", required: true,
|
||||
title: "41. Response to Water Flooding Alarm.\n" +
|
||||
"Available settings:\n" +
|
||||
"0 = No reaction,\n" +
|
||||
"1 = Turn on the load,\n" +
|
||||
"2 = Turn off the load,\n" +
|
||||
"3 = Load blinking.\n" +
|
||||
"Default value: 2."
|
||||
|
||||
input name: "param42", type: "number", range: "0..3", defaultValue: "3", required: true,
|
||||
title: "42. Response to Smoke, CO or CO2 Alarm.\n" +
|
||||
"Available settings:\n" +
|
||||
"0 = No reaction,\n" +
|
||||
"1 = Turn on the load,\n" +
|
||||
"2 = Turn off the load,\n" +
|
||||
"3 = Load blinking.\n" +
|
||||
"Default value: 3."
|
||||
|
||||
input name: "param43", type: "number", range: "0..3", defaultValue: "1", required: true,
|
||||
title: "43. Response to Temperature Alarm.\n" +
|
||||
"Available settings:\n" +
|
||||
"0 = No reaction,\n" +
|
||||
"1 = Turn on the load,\n" +
|
||||
"2 = Turn off the load,\n" +
|
||||
"3 = Load blinking.\n" +
|
||||
"Default value: 1."
|
||||
|
||||
input name: "param44", type: "number", range: "1..32767", defaultValue: "600", required: true,
|
||||
title: "44. Time of alarm state.\n" +
|
||||
"Available settings: 1-32767 (1-32767 seconds).\n" +
|
||||
"Default value: 600."
|
||||
|
||||
paragraph = "Alarm settings - reports"
|
||||
input name: "param45", type: "number", range: "0..1", defaultValue: "1", required: true,
|
||||
title: paragraph + "\n\n" +
|
||||
"45. OVERLOAD alarm report (load power consumption too high).\n" +
|
||||
"Available settings:\n" +
|
||||
"0 = No reaction,\n" +
|
||||
"1 = Send an alarm frame.\n" +
|
||||
"Default value: 1."
|
||||
|
||||
input name: "param46", type: "number", range: "0..1", defaultValue: "1", required: true,
|
||||
title: "46. LOAD ERROR alarm report (no load, load failure, burnt out bulb).\n" +
|
||||
"Available settings:\n" +
|
||||
"0 = No reaction,\n" +
|
||||
"1 = Send an alarm frame.\n" +
|
||||
"Default value: 1."
|
||||
|
||||
input name: "param47", type: "number", range: "0..1", defaultValue: "1", required: true,
|
||||
title: "47. OVERCURRENT alarm report (short circuit, burnt out bulb causing overcurrent).\n" +
|
||||
"Available settings:\n" +
|
||||
"0 = No reaction,\n" +
|
||||
"1 = Send an alarm frame.\n" +
|
||||
"Default value: 1."
|
||||
|
||||
input name: "param48", type: "number", range: "0..1", defaultValue: "1", required: true,
|
||||
title: "48. SURGE alarm report (Dimmer 2 output overvoltage).\n" +
|
||||
"Available settings:\n" +
|
||||
"0 = No reaction,\n" +
|
||||
"1 = Send an alarm frame.\n" +
|
||||
"Default value: 1."
|
||||
|
||||
input name: "param49", type: "number", range: "0..1", defaultValue: "1", required: true,
|
||||
title: "49. OVERHEAT (critical temperature) and VOLTAGE DROP (low voltage) alarm report.\n" +
|
||||
"Available settings:\n" +
|
||||
"0 = No reaction,\n" +
|
||||
"1 = Send an alarm frame.\n" +
|
||||
"Default value: 1."
|
||||
|
||||
paragraph = "GROUP 50 - Active power and energy reports"
|
||||
input name: "param50", type: "number", range: "0..100", defaultValue: "10", required: true,
|
||||
title: paragraph + "\n\n" +
|
||||
"50. Active power reports. " +
|
||||
"The parameter defines the power level change that will result in a new power report being sent. " +
|
||||
"The value is a percentage of the previous report.\n" +
|
||||
"Available settings:\n0 - power reports disabled,\n1-100 (1-100%) - power report threshold.\n" +
|
||||
"Default value: 10."
|
||||
|
||||
input name: "param52", type: "number", range: "0..32767", defaultValue: "3600", required: true,
|
||||
title: "52. Periodic active power and energy reports. " +
|
||||
"Parameter 52 defines a time period between consecutive reports. Timer is reset and counted from zero after each report.\n" +
|
||||
"Available settings:\n0 - periodic reports disabled,\n1-32767 (1-32767 seconds).\n" +
|
||||
"Default value: 3600."
|
||||
|
||||
input name: "param53", type: "number", range: "0..255", defaultValue: "10", required: true,
|
||||
title: "53. Energy reports. " +
|
||||
"Energy level change which will result in sending a new energy report.\n" +
|
||||
"Available settings:\n0 - energy reports disabled,\n1-255 (0.01-2.55 kWh) - report triggering threshold.\n" +
|
||||
"Default value: 10."
|
||||
|
||||
input name: "param54", type: "number", range: "0..1", defaultValue: "0", required: true,
|
||||
title: "54. Self-measurement. " +
|
||||
"The Dimmer 2 may include active power and energy consumed by itself in reports sent to the main controller.\n" +
|
||||
"Available settings:\n" +
|
||||
"0 = Self-measurement inactive,\n" +
|
||||
"1 = Self-measurement active.\n" +
|
||||
"Default value: 0."
|
||||
|
||||
input name: "param58", type: "number", range: "0..2", defaultValue: "0", required: true,
|
||||
title: "58. Method of calculating the active power. This parameter defines how to calculate active power. " +
|
||||
"It is useful in a case of 2-wire connection with light sources other than resistive.\n" +
|
||||
"Available settings:\n" +
|
||||
"0 = measurement based on the standard algorithm,\n" +
|
||||
"1 = approximation based on the calibration data,\n" +
|
||||
"2 = approximation based on the control angle.\n" +
|
||||
"Default value: 0."
|
||||
|
||||
input name: "param59", type: "number", range: "0..500", defaultValue: "0", required: true,
|
||||
title: "59. Approximated power at the maximum brightness level. " +
|
||||
"This parameter determines the approximate value of the power that will be reported by the device at its maximum brightness level.\n" +
|
||||
"Available settings: 0-500 (0-500W) - power consumed by the load at the maximum brightness level.\n" +
|
||||
"Default value: 0."
|
||||
|
||||
input name: "paramAssociationGroup1", type: "bool", defaultValue: true, required: true,
|
||||
title: "The Dimmer 2 provides the association of five groups.\n\n" +
|
||||
"1st Association Group „Lifeline”,\n" +
|
||||
"Default value: true"
|
||||
|
||||
input name: "paramAssociationGroup2", type: "bool", defaultValue: true, required: true,
|
||||
title: "2nd Association Group „On/Off (S1)”,\n" +
|
||||
"Default value: true"
|
||||
|
||||
input name: "paramAssociationGroup3", type: "bool", defaultValue: false, required: true,
|
||||
title: "3rd Association Group „Dimmer (S1)”,\n" +
|
||||
"Default value: false"
|
||||
|
||||
input name: "paramAssociationGroup4", type: "bool", defaultValue: false, required: true,
|
||||
title: "4th Association Group „On/Off (S2)”,\n" +
|
||||
"Default value: false"
|
||||
|
||||
input name: "paramAssociationGroup5", type: "bool", defaultValue: false, required: true,
|
||||
title: "5th Association Group „Dimmer (S2)”.\n" +
|
||||
"Default value: false"
|
||||
}
|
||||
}
|
||||
|
||||
def parse(String description) {
|
||||
log.trace(description)
|
||||
log.debug("RAW command: $description")
|
||||
def result = null
|
||||
|
||||
if (description != "updated") {
|
||||
def cmd = zwave.parse(description.replace("98C1", "9881"), [0x20: 1, 0x26: 3, 0x32: 3, 0x25: 1, 0x98: 1, 0x70: 1, 0x85: 2, 0x9B: 1, 0x90: 1, 0x73: 1, 0x30: 1, 0x28: 1, 0x72: 1])
|
||||
if (cmd) {
|
||||
result = zwaveEvent(cmd)
|
||||
}
|
||||
}
|
||||
log.debug "Parsed '${description}' to ${result.inspect()}"
|
||||
return result
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.sceneactivationv1.SceneActivationSet cmd) {
|
||||
log.debug( "Scene ID: $cmd.sceneId")
|
||||
log.debug( "Dimming Duration: $cmd.dimmingDuration")
|
||||
|
||||
sendEvent(name: "scene", value: "$cmd.sceneId", data: [switchType: "$settings.param20"], descriptionText: "Scene id $cmd.sceneId was activated", isStateChange: true)
|
||||
log.debug( "Scene id $cmd.sceneId was activated" )
|
||||
}
|
||||
|
||||
// Devices that support the Security command class can send messages in an encrypted form;
|
||||
// they arrive wrapped in a SecurityMessageEncapsulation command and must be unencapsulated
|
||||
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
|
||||
log.trace(cmd)
|
||||
def encapsulatedCommand = cmd.encapsulatedCommand([0x20: 1, 0x26: 3, 0x32: 3, 0x25: 1, 0x98: 1, 0x70: 1, 0x85: 2, 0x9B: 1, 0x90: 1, 0x73: 1, 0x30: 1, 0x28: 1, 0x72: 1]) // can specify command class versions here like in zwave.parse
|
||||
if (encapsulatedCommand) {
|
||||
return zwaveEvent(encapsulatedCommand)
|
||||
} else {
|
||||
log.warn "Unable to extract encapsulated cmd from $cmd"
|
||||
createEvent(descriptionText: cmd.toString())
|
||||
}
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
|
||||
log.trace(cmd)
|
||||
dimmerEvents(cmd)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd) {
|
||||
log.trace(cmd)
|
||||
//dimmerEvents(cmd)
|
||||
}
|
||||
def zwaveEvent(physicalgraph.zwave.commands.switchmultilevelv3.SwitchMultilevelReport cmd) {
|
||||
log.trace(cmd)
|
||||
dimmerEvents(cmd)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd)
|
||||
{
|
||||
log.trace(cmd)
|
||||
dimmerEvents(cmd)
|
||||
}
|
||||
|
||||
|
||||
def dimmerEvents(physicalgraph.zwave.Command cmd) {
|
||||
log.trace(cmd)
|
||||
def result = []
|
||||
def value = (cmd.value ? "on" : "off")
|
||||
def switchEvent = createEvent(name: "switch", value: value, descriptionText: "$device.displayName was turned $value")
|
||||
result << switchEvent
|
||||
if (cmd.value) {
|
||||
result << createEvent(name: "level", value: cmd.value, unit: "%")
|
||||
}
|
||||
if (switchEvent.isStateChange) {
|
||||
result << response(["delay 3000", zwave.meterV2.meterGet(scale: 2).format()])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.meterv3.MeterReport cmd) {
|
||||
log.trace(cmd)
|
||||
if (cmd.meterType == 1) {
|
||||
if (cmd.scale == 0) {
|
||||
return createEvent(name: "energy", value: cmd.scaledMeterValue, unit: "kWh")
|
||||
} else if (cmd.scale == 1) {
|
||||
return createEvent(name: "energy", value: cmd.scaledMeterValue, unit: "kVAh")
|
||||
} else if (cmd.scale == 2) {
|
||||
return createEvent(name: "power", value: Math.round(cmd.scaledMeterValue), unit: "W")
|
||||
} else {
|
||||
return createEvent(name: "electric", value: cmd.scaledMeterValue, unit: ["pulses", "V", "A", "R/Z", ""][cmd.scale - 3])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
||||
log.trace(cmd)
|
||||
log.debug "No handler for $cmd"
|
||||
// Handles all Z-Wave commands we aren't interested in
|
||||
createEvent(descriptionText: cmd.toString(), isStateChange: false)
|
||||
}
|
||||
|
||||
|
||||
def on() {
|
||||
log.trace("on")
|
||||
secureSequence([
|
||||
zwave.basicV1.basicSet(value: 0xFF),
|
||||
zwave.switchMultilevelV1.switchMultilevelGet()
|
||||
])
|
||||
}
|
||||
|
||||
def off() {
|
||||
log.trace("off")
|
||||
secureSequence([
|
||||
zwave.basicV1.basicSet(value: 0x00),
|
||||
zwave.switchMultilevelV1.switchMultilevelGet()
|
||||
])
|
||||
}
|
||||
|
||||
def poll() {
|
||||
log.trace("poll")
|
||||
secureSequence([
|
||||
zwave.meterV2.meterGet(scale: 0),
|
||||
zwave.meterV2.meterGet(scale: 2)
|
||||
])
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
log.trace("trace")
|
||||
secureSequence([
|
||||
zwave.meterV2.meterGet(scale: 0),
|
||||
zwave.meterV2.meterGet(scale: 2)
|
||||
])
|
||||
}
|
||||
|
||||
def reset() {
|
||||
log.trace("reset")
|
||||
return secureSequence([
|
||||
zwave.switchMultilevelV1.switchMultilevelGet(),
|
||||
zwave.meterV2.meterReset(),
|
||||
zwave.meterV2.meterGet(scale: 0),
|
||||
zwave.meterV2.meterGet(scale: 2)
|
||||
])
|
||||
}
|
||||
|
||||
def setLevel(level) {
|
||||
log.trace("setlevel")
|
||||
if(level > 99) level = 99
|
||||
secureSequence([
|
||||
zwave.basicV1.basicSet(value: level),
|
||||
zwave.switchMultilevelV1.switchMultilevelGet()
|
||||
], 5000)
|
||||
}
|
||||
|
||||
def configureAfterSecure() {
|
||||
log.debug "configureAfterSecure()"
|
||||
def cmds = secureSequence([
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 1, size: 1, scaledConfigurationValue: param1.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 2, size: 1, scaledConfigurationValue: param2.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 3, size: 1, scaledConfigurationValue: param3.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 4, size: 2, scaledConfigurationValue: param4.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 5, size: 1, scaledConfigurationValue: param5.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 6, size: 2, scaledConfigurationValue: param6.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 7, size: 1, scaledConfigurationValue: param7.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 8, size: 2, scaledConfigurationValue: param8.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 9, size: 1, scaledConfigurationValue: param9.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 10, size: 2, scaledConfigurationValue: param10.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 11, size: 2, scaledConfigurationValue: param11.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 13, size: 1, scaledConfigurationValue: param13.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 15, size: 1, scaledConfigurationValue: param15.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 16, size: 2, scaledConfigurationValue: param16.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 19, size: 1, scaledConfigurationValue: param19.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 20, size: 1, scaledConfigurationValue: param20.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 21, size: 1, scaledConfigurationValue: param21.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 22, size: 1, scaledConfigurationValue: param22.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 23, size: 1, scaledConfigurationValue: param23.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 24, size: 1, scaledConfigurationValue: param24.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 25, size: 1, scaledConfigurationValue: param25.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 26, size: 1, scaledConfigurationValue: param26.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 27, size: 1, scaledConfigurationValue: param27.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 28, size: 1, scaledConfigurationValue: param28.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 29, size: 1, scaledConfigurationValue: param29.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 30, size: 1, scaledConfigurationValue: param30.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 32, size: 1, scaledConfigurationValue: param32.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 34, size: 1, scaledConfigurationValue: param34.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 35, size: 1, scaledConfigurationValue: param35.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 37, size: 1, scaledConfigurationValue: param37.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 39, size: 2, scaledConfigurationValue: param39.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 40, size: 1, scaledConfigurationValue: param40.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 41, size: 1, scaledConfigurationValue: param41.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 42, size: 1, scaledConfigurationValue: param42.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 43, size: 1, scaledConfigurationValue: param43.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 44, size: 2, scaledConfigurationValue: param44.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 45, size: 1, scaledConfigurationValue: param45.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 46, size: 1, scaledConfigurationValue: param46.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 47, size: 1, scaledConfigurationValue: param47.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 48, size: 1, scaledConfigurationValue: param48.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 49, size: 1, scaledConfigurationValue: param49.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 50, size: 1, scaledConfigurationValue: param50.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 52, size: 2, scaledConfigurationValue: param52.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 53, size: 2, scaledConfigurationValue: param53.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 54, size: 1, scaledConfigurationValue: param54.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 58, size: 1, scaledConfigurationValue: param58.toInteger()),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 59, size: 2, scaledConfigurationValue: param59.toInteger())
|
||||
])
|
||||
|
||||
// Register for Group 1
|
||||
if(paramAssociationGroup1) {
|
||||
cmds << secure(zwave.associationV2.associationSet(groupingIdentifier:1, nodeId: [zwaveHubNodeId]))
|
||||
}
|
||||
else {
|
||||
cmds << secure(zwave.associationV2.associationRemove(groupingIdentifier:1, nodeId: [zwaveHubNodeId]))
|
||||
}
|
||||
// Register for Group 2
|
||||
if(paramAssociationGroup2) {
|
||||
cmds << secure(zwave.associationV2.associationSet(groupingIdentifier:2, nodeId: [zwaveHubNodeId]))
|
||||
}
|
||||
else {
|
||||
cmds << secure(zwave.associationV2.associationRemove(groupingIdentifier:2, nodeId: [zwaveHubNodeId]))
|
||||
}
|
||||
// Register for Group 3
|
||||
if(paramAssociationGroup3) {
|
||||
cmds << secure(zwave.associationV2.associationSet(groupingIdentifier:3, nodeId: [zwaveHubNodeId]))
|
||||
}
|
||||
else {
|
||||
cmds << secure(zwave.associationV2.associationRemove(groupingIdentifier:3, nodeId: [zwaveHubNodeId]))
|
||||
}
|
||||
// Register for Group 4
|
||||
if(paramAssociationGroup4) {
|
||||
cmds << secure(zwave.associationV2.associationSet(groupingIdentifier:4, nodeId: [zwaveHubNodeId]))
|
||||
}
|
||||
else {
|
||||
cmds << secure(zwave.associationV2.associationRemove(groupingIdentifier:4, nodeId: [zwaveHubNodeId]))
|
||||
}
|
||||
// Register for Group 5
|
||||
if(paramAssociationGroups5) {
|
||||
cmds << secure(zwave.associationV2.associationSet(groupingIdentifier:5, nodeId: [zwaveHubNodeId]))
|
||||
}
|
||||
else {
|
||||
cmds << secure(zwave.associationV2.associationRemove(groupingIdentifier:5, nodeId: [zwaveHubNodeId]))
|
||||
}
|
||||
|
||||
cmds
|
||||
}
|
||||
|
||||
def configure() {
|
||||
// Wait until after the secure exchange for this
|
||||
log.debug "configure()"
|
||||
}
|
||||
|
||||
def updated() {
|
||||
log.debug "updated()"
|
||||
response(["delay 2000"] + configureAfterSecure() + refresh())
|
||||
}
|
||||
|
||||
private secure(physicalgraph.zwave.Command cmd) {
|
||||
log.trace(cmd)
|
||||
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
|
||||
}
|
||||
|
||||
private secureSequence(commands, delay=200) {
|
||||
log.debug "$commands"
|
||||
delayBetween(commands.collect{ secure(it) }, delay)
|
||||
}
|
||||
@@ -21,7 +21,6 @@ metadata {
|
||||
attribute "tamper", "enum", ["detected", "clear"]
|
||||
attribute "heatAlarm", "enum", ["overheat detected", "clear", "rapid temperature rise", "underheat detected"]
|
||||
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x86, 0x72, 0x5A, 0x59, 0x85, 0x73, 0x84, 0x80, 0x71, 0x56, 0x70, 0x31, 0x8E, 0x22, 0x9C, 0x98, 0x7A", outClusters: "0x20, 0x8B"
|
||||
fingerprint mfr:"010F", prod:"0C02", model:"1002"
|
||||
}
|
||||
simulator {
|
||||
//battery
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
/**
|
||||
* Hue White Ambiance Bulb
|
||||
*
|
||||
* Philips Hue Type "Color Temperature Light"
|
||||
*
|
||||
* Author: SmartThings
|
||||
*/
|
||||
|
||||
// for the UI
|
||||
metadata {
|
||||
// Automatically generated. Make future change here.
|
||||
definition (name: "Hue White Ambiance Bulb", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Switch Level"
|
||||
capability "Actuator"
|
||||
capability "Color Temperature"
|
||||
capability "Switch"
|
||||
capability "Refresh"
|
||||
|
||||
command "refresh"
|
||||
}
|
||||
|
||||
simulator {
|
||||
// TODO: define status and reply messages here
|
||||
}
|
||||
|
||||
tiles (scale: 2){
|
||||
multiAttributeTile(name:"rich-control", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||
attributeState "level", action:"switch level.setLevel", range:"(0..100)"
|
||||
}
|
||||
}
|
||||
|
||||
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2000..6500)") {
|
||||
state "colorTemperature", action:"color temperature.setColorTemperature"
|
||||
}
|
||||
|
||||
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "colorTemperature", label: '${currentValue} K'
|
||||
}
|
||||
|
||||
standardTile("refresh", "device.refresh", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
main(["rich-control"])
|
||||
details(["rich-control", "colorTempSliderControl", "colorTemp", "refresh"])
|
||||
}
|
||||
}
|
||||
|
||||
// parse events into attributes
|
||||
def parse(description) {
|
||||
log.debug "parse() - $description"
|
||||
def results = []
|
||||
|
||||
def map = description
|
||||
if (description instanceof String) {
|
||||
log.debug "Hue Ambience Bulb stringToMap - ${map}"
|
||||
map = stringToMap(description)
|
||||
}
|
||||
|
||||
if (map?.name && map?.value) {
|
||||
results << createEvent(name: "${map?.name}", value: "${map?.value}")
|
||||
}
|
||||
results
|
||||
}
|
||||
|
||||
// handle commands
|
||||
void on() {
|
||||
log.trace parent.on(this)
|
||||
sendEvent(name: "switch", value: "on")
|
||||
}
|
||||
|
||||
void off() {
|
||||
log.trace parent.off(this)
|
||||
sendEvent(name: "switch", value: "off")
|
||||
}
|
||||
|
||||
void setLevel(percent) {
|
||||
log.debug "Executing 'setLevel'"
|
||||
if (percent != null && percent >= 0 && percent <= 100) {
|
||||
parent.setLevel(this, percent)
|
||||
sendEvent(name: "level", value: percent)
|
||||
sendEvent(name: "switch", value: "on")
|
||||
} else {
|
||||
log.warn "$percent is not 0-100"
|
||||
}
|
||||
}
|
||||
|
||||
void setColorTemperature(value) {
|
||||
if (value) {
|
||||
log.trace "setColorTemperature: ${value}k"
|
||||
parent.setColorTemperature(this, value)
|
||||
sendEvent(name: "colorTemperature", value: value)
|
||||
sendEvent(name: "switch", value: "on")
|
||||
} else {
|
||||
log.warn "Invalid color temperature"
|
||||
}
|
||||
}
|
||||
|
||||
void refresh() {
|
||||
log.debug "Executing 'refresh'"
|
||||
parent.manualRefresh()
|
||||
}
|
||||
|
||||
@@ -19,6 +19,11 @@ metadata {
|
||||
capability "Sensor"
|
||||
|
||||
attribute "colorName", "string"
|
||||
|
||||
// indicates that device keeps track of heartbeat (in state.heartbeat)
|
||||
attribute "heartbeat", "string"
|
||||
|
||||
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
@@ -70,6 +75,9 @@ metadata {
|
||||
def parse(String description) {
|
||||
//log.trace description
|
||||
|
||||
// save heartbeat (i.e. last time we got a message from device)
|
||||
state.heartbeat = Calendar.getInstance().getTimeInMillis()
|
||||
|
||||
if (description?.startsWith("catchall:")) {
|
||||
if(description?.endsWith("0100") ||description?.endsWith("1001") || description?.matches("on/off\\s*:\\s*1"))
|
||||
{
|
||||
@@ -124,6 +132,7 @@ def off() {
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
sendEvent(name: "heartbeat", value: "alive", displayed:false)
|
||||
[
|
||||
"st rattr 0x${device.deviceNetworkId} ${endpointId} 6 0", "delay 500",
|
||||
"st rattr 0x${device.deviceNetworkId} ${endpointId} 8 0", "delay 500",
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
metadata {
|
||||
// Automatically generated. Make future change here.
|
||||
definition (name: "SmartPower Outlet", namespace: "smartthings", author: "SmartThings", category: "C1") {
|
||||
definition (name: "SmartPower Outlet", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Actuator"
|
||||
capability "Switch"
|
||||
capability "Power Meter"
|
||||
@@ -25,6 +25,9 @@ metadata {
|
||||
capability "Sensor"
|
||||
capability "Health Check"
|
||||
|
||||
// indicates that device keeps track of heartbeat (in state.heartbeat)
|
||||
attribute "heartbeat", "string"
|
||||
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3200", deviceJoinName: "Outlet"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3200-Sgb", deviceJoinName: "Outlet"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "4257050-RZHAC", deviceJoinName: "Outlet"
|
||||
@@ -78,6 +81,9 @@ metadata {
|
||||
def parse(String description) {
|
||||
log.debug "description is $description"
|
||||
|
||||
// save heartbeat (i.e. last time we got a message from device)
|
||||
state.heartbeat = Calendar.getInstance().getTimeInMillis()
|
||||
|
||||
def finalResult = zigbee.getKnownDescription(description)
|
||||
|
||||
//TODO: Remove this after getKnownDescription can parse it automatically
|
||||
@@ -118,6 +124,7 @@ def on() {
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
sendEvent(name: "heartbeat", value: "alive", displayed:false)
|
||||
zigbee.onOffRefresh() + zigbee.refreshData("0x0B04", "0x050B")
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
metadata {
|
||||
definition (name: "SmartSense Moisture Sensor",namespace: "smartthings", author: "SmartThings", category: "C2") {
|
||||
definition (name: "SmartSense Moisture Sensor",namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Configuration"
|
||||
capability "Battery"
|
||||
capability "Refresh"
|
||||
@@ -255,8 +255,7 @@ private Map getBatteryResult(rawValue) {
|
||||
def minVolts = 2.1
|
||||
def maxVolts = 3.0
|
||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||
def roundedPct = Math.round(pct * 100)
|
||||
result.value = Math.min(100, roundedPct)
|
||||
result.value = Math.min(100, (int) pct * 100)
|
||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
.st-ignore
|
||||
README.md
|
||||
@@ -1,30 +0,0 @@
|
||||
# Smartsense Motion Sensor
|
||||
|
||||
|
||||
|
||||
Works with:
|
||||
|
||||
* [Samsung SmartThings Motion Sensor](https://shop.smartthings.com/#!/products/samsung-smartthings-motion-sensor)
|
||||
|
||||
## Table of contents
|
||||
|
||||
* [Capabilities](#capabilities)
|
||||
* [Health]($health)
|
||||
|
||||
## Capabilities
|
||||
|
||||
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
|
||||
* **Motion Sensor** - can detect motion
|
||||
* **Battery** - defines device uses a battery
|
||||
* **Refresh** - _refresh()_ command for status updates
|
||||
* **Health Check** - indicates ability to get device health notifications
|
||||
|
||||
## Device Health
|
||||
|
||||
A Category C2 motion sensor that has 120min check-in interval
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
metadata {
|
||||
definition (name: "SmartSense Motion Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") {
|
||||
definition (name: "SmartSense Motion Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Motion Sensor"
|
||||
capability "Configuration"
|
||||
capability "Battery"
|
||||
@@ -271,8 +271,7 @@ private Map getBatteryResult(rawValue) {
|
||||
def minVolts = 2.1
|
||||
def maxVolts = 3.0
|
||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||
def roundedPct = Math.round(pct * 100)
|
||||
result.value = Math.min(100, roundedPct)
|
||||
result.value = Math.min(100, (int) pct * 100)
|
||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ metadata {
|
||||
capability "Temperature Measurement"
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
|
||||
|
||||
command "enrollResponse"
|
||||
|
||||
}
|
||||
@@ -73,7 +73,7 @@ metadata {
|
||||
|
||||
def parse(String description) {
|
||||
log.debug "description: $description"
|
||||
|
||||
|
||||
Map map = [:]
|
||||
if (description?.startsWith('catchall:')) {
|
||||
map = parseCatchAllMessage(description)
|
||||
@@ -87,10 +87,10 @@ def parse(String description) {
|
||||
else if (description?.startsWith('zone status')) {
|
||||
map = parseIasMessage(description)
|
||||
}
|
||||
|
||||
|
||||
log.debug "Parse returned $map"
|
||||
def result = map ? createEvent(map) : null
|
||||
|
||||
|
||||
if (description?.startsWith('enroll request')) {
|
||||
List cmds = enrollResponse()
|
||||
log.debug "enroll response: ${cmds}"
|
||||
@@ -128,7 +128,7 @@ private Map parseCatchAllMessage(String description) {
|
||||
private boolean shouldProcessMessage(cluster) {
|
||||
// 0x0B is default response indicating message got through
|
||||
// 0x07 is bind message
|
||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||
cluster.command == 0x0B ||
|
||||
cluster.command == 0x07 ||
|
||||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
|
||||
@@ -141,7 +141,7 @@ private Map parseReportAttributeMessage(String description) {
|
||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
||||
}
|
||||
log.debug "Desc Map: $descMap"
|
||||
|
||||
|
||||
Map resultMap = [:]
|
||||
if (descMap.cluster == "0402" && descMap.attrId == "0000") {
|
||||
def value = getTemperature(descMap.value)
|
||||
@@ -153,11 +153,11 @@ private Map parseReportAttributeMessage(String description) {
|
||||
else if (descMap.cluster == "0406" && descMap.attrId == "0000") {
|
||||
def value = descMap.value.endsWith("01") ? "active" : "inactive"
|
||||
resultMap = getMotionResult(value)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return resultMap
|
||||
}
|
||||
|
||||
|
||||
private Map parseCustomMessage(String description) {
|
||||
Map resultMap = [:]
|
||||
if (description?.startsWith('temperature: ')) {
|
||||
@@ -170,7 +170,7 @@ private Map parseCustomMessage(String description) {
|
||||
private Map parseIasMessage(String description) {
|
||||
List parsedMsg = description.split(' ')
|
||||
String msgCode = parsedMsg[2]
|
||||
|
||||
|
||||
Map resultMap = [:]
|
||||
switch(msgCode) {
|
||||
case '0x0020': // Closed/No Motion/Dry
|
||||
@@ -240,8 +240,7 @@ private Map getBatteryResult(rawValue) {
|
||||
def minVolts = 2.1
|
||||
def maxVolts = 3.0
|
||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||
def roundedPct = Math.round(pct * 100)
|
||||
result.value = Math.min(100, roundedPct)
|
||||
result.value = Math.min(100, (int) pct * 100)
|
||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
metadata {
|
||||
definition (name: "SmartSense Multi Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") {
|
||||
definition (name: "SmartSense Multi Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||
|
||||
capability "Three Axis"
|
||||
capability "Battery"
|
||||
@@ -338,8 +338,7 @@ private Map getBatteryResult(rawValue) {
|
||||
def minVolts = 2.1
|
||||
def maxVolts = 3.0
|
||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||
def roundedPct = Math.round(pct * 100)
|
||||
result.value = Math.min(100, roundedPct)
|
||||
result.value = Math.min(100, (int) pct * 100)
|
||||
result.descriptionText = "{{ device.displayName }} battery was {{ value }}%"
|
||||
}
|
||||
}
|
||||
@@ -404,21 +403,39 @@ def refresh() {
|
||||
|
||||
if (device.getDataValue("manufacturer") == "SmartThings") {
|
||||
log.debug "Refreshing Values for manufacturer: SmartThings "
|
||||
/* These values of Motion Threshold Multiplier(0x01) and Motion Threshold (0x0276)
|
||||
seem to be giving pretty accurate results for the XYZ co-ordinates for this manufacturer.
|
||||
Separating these out in a separate if-else because I do not want to touch Centralite part
|
||||
as of now.
|
||||
*/
|
||||
refreshCmds += zigbee.writeAttribute(0xFC02, 0x0000, 0x20, 0x01, [mfgCode: manufacturerCode])
|
||||
refreshCmds += zigbee.writeAttribute(0xFC02, 0x0002, 0x21, 0x0276, [mfgCode: manufacturerCode])
|
||||
refreshCmds = refreshCmds + [
|
||||
/* These values of Motion Threshold Multiplier(01) and Motion Threshold (7602)
|
||||
seem to be giving pretty accurate results for the XYZ co-ordinates for this manufacturer.
|
||||
Separating these out in a separate if-else because I do not want to touch Centralite part
|
||||
as of now.
|
||||
*/
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global write 0xFC02 0 0x20 {01}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global write 0xFC02 2 0x21 {7602}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
|
||||
]
|
||||
} else {
|
||||
refreshCmds += zigbee.writeAttribute(0xFC02, 0x0000, 0x20, 0x02, [mfgCode: manufacturerCode])
|
||||
refreshCmds = refreshCmds + [
|
||||
/* sensitivity - default value (8) */
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global write 0xFC02 0 0x20 {02}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
|
||||
]
|
||||
}
|
||||
|
||||
//Common refresh commands
|
||||
refreshCmds += zigbee.readAttribute(0x0402, 0x0000) +
|
||||
zigbee.readAttribute(0x0001, 0x0020) +
|
||||
zigbee.readAttribute(0xFC02, 0x0010, [mfgCode: manufacturerCode])
|
||||
refreshCmds = refreshCmds + [
|
||||
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
|
||||
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200",
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global read 0xFC02 0x0010",
|
||||
"send 0x${device.deviceNetworkId} 1 1","delay 400"
|
||||
]
|
||||
|
||||
return refreshCmds + enrollResponse()
|
||||
}
|
||||
@@ -426,15 +443,38 @@ def refresh() {
|
||||
def configure() {
|
||||
sendEvent(name: "checkInterval", value: 7200, displayed: false)
|
||||
|
||||
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||
log.debug "Configuring Reporting"
|
||||
|
||||
def configCmds = enrollResponse() +
|
||||
zigbee.batteryConfig() +
|
||||
zigbee.temperatureConfig() +
|
||||
zigbee.configureReporting(0xFC02, 0x0010, 0x18, 10, 3600, 0x01, [mfgCode: manufacturerCode]) +
|
||||
zigbee.configureReporting(0xFC02, 0x0012, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) +
|
||||
zigbee.configureReporting(0xFC02, 0x0013, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) +
|
||||
zigbee.configureReporting(0xFC02, 0x0014, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode])
|
||||
def configCmds = [
|
||||
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 200",
|
||||
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", "delay 200", //checkin time 6 hrs
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 200",
|
||||
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0xFC02 {${device.zigbeeId}} {}", "delay 200",
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global send-me-a-report 0xFC02 0x0010 0x18 10 3600 {01}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global send-me-a-report 0xFC02 0x0012 0x29 1 3600 {0100}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global send-me-a-report 0xFC02 0x0013 0x29 1 3600 {0100}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global send-me-a-report 0xFC02 0x0014 0x29 1 3600 {0100}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
|
||||
]
|
||||
|
||||
return configCmds + refresh()
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
//DEPRECATED - Using the smartsense-multi-sensor.groovy DTH for this device. Users need to be moved before deleting this DTH
|
||||
|
||||
metadata {
|
||||
definition (name: "SmartSense Open/Closed Accelerometer Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") {
|
||||
definition (name: "SmartSense Open/Closed Accelerometer Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Battery"
|
||||
capability "Configuration"
|
||||
capability "Contact Sensor"
|
||||
@@ -234,8 +234,7 @@ def getTemperature(value) {
|
||||
def minVolts = 2.1
|
||||
def maxVolts = 3.0
|
||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||
def roundedPct = Math.round(pct * 100)
|
||||
result.value = Math.min(100, roundedPct)
|
||||
result.value = Math.min(100, (int) pct * 100)
|
||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||
}
|
||||
|
||||
|
||||
@@ -13,34 +13,32 @@
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
metadata {
|
||||
definition (name: "SmartSense Open/Closed Sensor", namespace: "smartthings", author: "SmartThings", category: "C2") {
|
||||
definition (name: "SmartSense Open/Closed Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Battery"
|
||||
capability "Configuration"
|
||||
capability "Contact Sensor"
|
||||
capability "Refresh"
|
||||
capability "Temperature Measurement"
|
||||
capability "Health Check"
|
||||
capability "Sensor"
|
||||
|
||||
|
||||
command "enrollResponse"
|
||||
|
||||
|
||||
|
||||
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3300-S"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3300"
|
||||
fingerprint inClusters: "0000,0001,0003,0020,0402,0500,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3320-L", deviceJoinName: "Iris Contact Sensor"
|
||||
}
|
||||
|
||||
|
||||
simulator {
|
||||
|
||||
|
||||
}
|
||||
|
||||
preferences {
|
||||
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
|
||||
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
|
||||
}
|
||||
|
||||
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"contact", type: "generic", width: 6, height: 4){
|
||||
tileAttribute ("device.contact", key: "PRIMARY_CONTROL") {
|
||||
@@ -73,10 +71,10 @@ metadata {
|
||||
details(["contact","temperature","battery","refresh"])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def parse(String description) {
|
||||
log.debug "description: $description"
|
||||
|
||||
|
||||
Map map = [:]
|
||||
if (description?.startsWith('catchall:')) {
|
||||
map = parseCatchAllMessage(description)
|
||||
@@ -90,10 +88,10 @@ def parse(String description) {
|
||||
else if (description?.startsWith('zone status')) {
|
||||
map = parseIasMessage(description)
|
||||
}
|
||||
|
||||
|
||||
log.debug "Parse returned $map"
|
||||
def result = map ? createEvent(map) : null
|
||||
|
||||
|
||||
if (description?.startsWith('enroll request')) {
|
||||
List cmds = enrollResponse()
|
||||
log.debug "enroll response: ${cmds}"
|
||||
@@ -101,7 +99,7 @@ def parse(String description) {
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
private Map parseCatchAllMessage(String description) {
|
||||
Map resultMap = [:]
|
||||
def cluster = zigbee.parse(description)
|
||||
@@ -127,7 +125,7 @@ private Map parseCatchAllMessage(String description) {
|
||||
private boolean shouldProcessMessage(cluster) {
|
||||
// 0x0B is default response indicating message got through
|
||||
// 0x07 is bind message
|
||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||
cluster.command == 0x0B ||
|
||||
cluster.command == 0x07 ||
|
||||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
|
||||
@@ -137,14 +135,14 @@ private boolean shouldProcessMessage(cluster) {
|
||||
private int getHumidity(value) {
|
||||
return Math.round(Double.parseDouble(value))
|
||||
}
|
||||
|
||||
|
||||
private Map parseReportAttributeMessage(String description) {
|
||||
Map descMap = (description - "read attr - ").split(",").inject([:]) { map, param ->
|
||||
def nameAndValue = param.split(":")
|
||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
||||
}
|
||||
log.debug "Desc Map: $descMap"
|
||||
|
||||
|
||||
Map resultMap = [:]
|
||||
if (descMap.cluster == "0402" && descMap.attrId == "0000") {
|
||||
def value = getTemperature(descMap.value)
|
||||
@@ -153,10 +151,10 @@ private Map parseReportAttributeMessage(String description) {
|
||||
else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
|
||||
resultMap = getBatteryResult(Integer.parseInt(descMap.value, 16))
|
||||
}
|
||||
|
||||
|
||||
return resultMap
|
||||
}
|
||||
|
||||
|
||||
private Map parseCustomMessage(String description) {
|
||||
Map resultMap = [:]
|
||||
if (description?.startsWith('temperature: ')) {
|
||||
@@ -169,7 +167,7 @@ private Map parseCustomMessage(String description) {
|
||||
private Map parseIasMessage(String description) {
|
||||
List parsedMsg = description.split(' ')
|
||||
String msgCode = parsedMsg[2]
|
||||
|
||||
|
||||
Map resultMap = [:]
|
||||
switch(msgCode) {
|
||||
case '0x0020': // Closed/No Motion/Dry
|
||||
@@ -202,7 +200,7 @@ private Map parseIasMessage(String description) {
|
||||
}
|
||||
return resultMap
|
||||
}
|
||||
|
||||
|
||||
def getTemperature(value) {
|
||||
def celsius = Integer.parseInt(value, 16).shortValue() / 100
|
||||
if(getTemperatureScale() == "C"){
|
||||
@@ -215,11 +213,11 @@ def getTemperature(value) {
|
||||
private Map getBatteryResult(rawValue) {
|
||||
log.debug 'Battery'
|
||||
def linkText = getLinkText(device)
|
||||
|
||||
|
||||
def result = [
|
||||
name: 'battery'
|
||||
]
|
||||
|
||||
|
||||
def volts = rawValue / 10
|
||||
def descriptionText
|
||||
if (rawValue == 0 || rawValue == 255) {}
|
||||
@@ -230,8 +228,7 @@ private Map getBatteryResult(rawValue) {
|
||||
def minVolts = 2.1
|
||||
def maxVolts = 3.0
|
||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||
def roundedPct = Math.round(pct * 100)
|
||||
result.value = Math.min(100, roundedPct)
|
||||
result.value = Math.min(100, (int) pct * 100)
|
||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||
}
|
||||
|
||||
@@ -276,7 +273,6 @@ def refresh() {
|
||||
}
|
||||
|
||||
def configure() {
|
||||
sendEvent(name: "checkInterval", value: 7200, displayed: false)
|
||||
|
||||
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
*
|
||||
*/
|
||||
metadata {
|
||||
definition (name: "SmartSense Temp/Humidity Sensor",namespace: "smartthings", author: "SmartThings", category: "C2") {
|
||||
definition (name: "SmartSense Temp/Humidity Sensor",namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Configuration"
|
||||
capability "Battery"
|
||||
capability "Refresh"
|
||||
@@ -205,8 +205,7 @@ private Map getBatteryResult(rawValue) {
|
||||
def minVolts = 2.1
|
||||
def maxVolts = 3.0
|
||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||
def roundedPct = Math.round(pct * 100)
|
||||
result.value = Math.min(100, roundedPct)
|
||||
result.value = Math.min(100, (int) pct * 100)
|
||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||
}
|
||||
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
# Device Tiles Examples and Reference
|
||||
|
||||
This package contains examples of Device tiles, organized by tile type.
|
||||
|
||||
## Purpose
|
||||
|
||||
Each Device Handler shows example usages of a specific tile, and is meant to represent the variety of permutations that a tile can be configured.
|
||||
|
||||
The various tiles can be used by QA to test tiles on all supported mobile devices, and by developers as a reference implementation.
|
||||
|
||||
## Installation
|
||||
|
||||
1. Self-publish the Device Handlers in this package.
|
||||
2. Self-publish the Device Tile Controller SmartApp. The SmartApp can be found [here](https://github.com/SmartThingsCommunity/SmartThingsPublic/blob/master/smartapps/smartthings/tile-ux/device-tile-controller.src/device-tile-controller.groovy).
|
||||
3. Install the SmartApp from the Marketplace, under "My Apps".
|
||||
4. Select the simulated devices you want to install and press "Done".
|
||||
|
||||
The simulated devices can then be found in the "Things" view of "My Home" in the mobile app.
|
||||
You may wish to create a new room for these simulated devices for easy access.
|
||||
|
||||
## Usage
|
||||
|
||||
Each simulated device can be interacted with like other devices.
|
||||
You can use the mobile app to interact with the tiles to see how they look and behave.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If you get an error when installing the simulated devices using the controller SmartApp, ensure that you have published all the Device Handlers for yourself.
|
||||
Also check live logging to see if there is a specific tile that is causing installation issues.
|
||||
|
||||
## FAQ
|
||||
|
||||
*Question: A tile isn't behaving as expected. What should I do?*
|
||||
|
||||
QA should create a JIRA ticket for any issues or inconsistencies of tiles across devices.
|
||||
|
||||
Developers may file a support ticket, and reference the specific tile and issue observed.
|
||||
|
||||
*Question: I'd like to contribute an example tile usage that would be helpful for testing and reference purposes. Can I do that?*
|
||||
|
||||
We recommend that you open an issue in the SmartThingsPublic repository describing the example tile and usage.
|
||||
That way we can discuss with you the proposed change, and then if appropriate you can create a PR associated to the issue.
|
||||
@@ -22,7 +22,7 @@ metadata {
|
||||
|
||||
tiles(scale: 2) {
|
||||
valueTile("currentColor", "device.color") {
|
||||
state "color", label: '${currentValue}', defaultState: true
|
||||
state "default", label: '${currentValue}'
|
||||
}
|
||||
|
||||
controlTile("rgbSelector", "device.color", "color", height: 6, width: 6, inactiveLabel: false) {
|
||||
@@ -41,13 +41,6 @@ def parse(String description) {
|
||||
log.debug "Parsing '${description}'"
|
||||
}
|
||||
|
||||
def setColor(value) {
|
||||
log.debug "setting color: $value"
|
||||
if (value.hex) { sendEvent(name: "color", value: value.hex) }
|
||||
if (value.hue) { sendEvent(name: "hue", value: value.hue) }
|
||||
if (value.saturation) { sendEvent(name: "saturation", value: value.saturation) }
|
||||
}
|
||||
|
||||
def setSaturation(percent) {
|
||||
log.debug "Executing 'setSaturation'"
|
||||
sendEvent(name: "saturation", value: percent)
|
||||
|
||||
@@ -39,7 +39,7 @@ metadata {
|
||||
}
|
||||
|
||||
valueTile("rangeValue", "device.rangedLevel", height: 2, width: 2) {
|
||||
state "range", label:'${currentValue}', defaultState: true
|
||||
state "default", label:'${currentValue}'
|
||||
}
|
||||
|
||||
controlTile("rangeSliderConstrained", "device.rangedLevel", "slider", height: 2, width: 4, range: "(40..60)") {
|
||||
|
||||
@@ -41,17 +41,17 @@ metadata {
|
||||
|
||||
// standard flat tile with only a label
|
||||
standardTile("flatLabel", "device.switch", width: 2, height: 2, decoration: "flat") {
|
||||
state "label", label: 'On Action', action: "switch.on", backgroundColor: "#ffffff", defaultState: true
|
||||
state "default", label: 'On Action', action: "switch.on", backgroundColor: "#ffffff"
|
||||
}
|
||||
|
||||
// standard flat tile with icon and label
|
||||
standardTile("flatIconLabel", "device.switch", width: 2, height: 2, decoration: "flat") {
|
||||
state "iconLabel", label: 'Off Action', action: "switch.off", icon:"st.switches.switch.off", backgroundColor: "#ffffff", defaultState: true
|
||||
state "default", label: 'Off Action', action: "switch.off", icon:"st.switches.switch.off", backgroundColor: "#ffffff"
|
||||
}
|
||||
|
||||
// standard flat tile with only icon (Refreh text is IN the icon file)
|
||||
standardTile("flatIcon", "device.switch", width: 2, height: 2, decoration: "flat") {
|
||||
state "icon", action:"refresh.refresh", icon:"st.secondary.refresh", defaultState: true
|
||||
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
// standard with defaultState = true
|
||||
@@ -74,19 +74,19 @@ metadata {
|
||||
|
||||
// utility tiles to fill the spaces
|
||||
standardTile("empty2x2", "null", width: 2, height: 2, decoration: "flat") {
|
||||
state "emptySmall", label:'', defaultState: true
|
||||
state "default", label:''
|
||||
}
|
||||
standardTile("empty4x2", "null", width: 4, height: 2, decoration: "flat") {
|
||||
state "emptyBigger", label:'', defaultState: true
|
||||
state "default", label:''
|
||||
}
|
||||
|
||||
// multi-line text (explicit newlines)
|
||||
standardTile("multiLine", "device.multiLine", width: 2, height: 2) {
|
||||
state "multiLine", label: '${currentValue}', defaultState: true
|
||||
state "default", label: '${currentValue}'
|
||||
}
|
||||
|
||||
standardTile("multiLineWithIcon", "device.multiLine", width: 2, height: 2) {
|
||||
state "multiLineIcon", label: '${currentValue}', icon: "st.switches.switch.off", defaultState: true
|
||||
state "default", label: '${currentValue}', icon: "st.switches.switch.off"
|
||||
}
|
||||
|
||||
main("actionRings")
|
||||
|
||||
@@ -22,68 +22,68 @@ metadata {
|
||||
|
||||
tiles(scale: 2) {
|
||||
valueTile("text", "device.text", width: 2, height: 2) {
|
||||
state "val", label:'${currentValue}', defaultState: true
|
||||
state "default", label:'${currentValue}'
|
||||
}
|
||||
|
||||
valueTile("longText", "device.longText", width: 2, height: 2) {
|
||||
state "val", label:'${currentValue}', defaultState: true
|
||||
state "default", label:'${currentValue}'
|
||||
}
|
||||
|
||||
valueTile("integer", "device.integer", width: 2, height: 2) {
|
||||
state "val", label:'${currentValue}', defaultState: true
|
||||
state "default", label:'${currentValue}'
|
||||
}
|
||||
|
||||
valueTile("integerFloat", "device.integerFloat", width: 2, height: 2) {
|
||||
state "val", label:'${currentValue}', defaultState: true
|
||||
state "default", label:'${currentValue}'
|
||||
}
|
||||
|
||||
valueTile("pi", "device.pi", width: 2, height: 2) {
|
||||
state "val", label:'${currentValue}', defaultState: true
|
||||
state "default", label:'${currentValue}'
|
||||
}
|
||||
|
||||
valueTile("floatAsText", "device.floatAsText", width: 2, height: 2) {
|
||||
state "val", label:'${currentValue}', defaultState: true
|
||||
state "default", label:'${currentValue}'
|
||||
}
|
||||
|
||||
valueTile("bgColor", "device.integer", width: 2, height: 2) {
|
||||
state "val", label:'${currentValue}', backgroundColor: "#e86d13", defaultState: true
|
||||
state "default", label:'${currentValue}', backgroundColor: "#e86d13"
|
||||
}
|
||||
|
||||
valueTile("bgColorRange", "device.integer", width: 2, height: 2) {
|
||||
state "val", label:'${currentValue}', defaultState: true, backgroundColors: [
|
||||
state "default", label:'${currentValue}', backgroundColors: [
|
||||
[value: 10, color: "#ff0000"],
|
||||
[value: 90, color: "#0000ff"]
|
||||
]
|
||||
}
|
||||
|
||||
valueTile("bgColorRangeSingleItem", "device.integer", width: 2, height: 2) {
|
||||
state "val", label:'${currentValue}', defaultState: true, backgroundColors: [
|
||||
state "default", label:'${currentValue}', backgroundColors: [
|
||||
[value: 10, color: "#333333"]
|
||||
]
|
||||
}
|
||||
|
||||
valueTile("bgColorRangeConflict", "device.integer", width: 2, height: 2) {
|
||||
state "valWithConflict", label:'${currentValue}', defaultState: true, backgroundColors: [
|
||||
state "default", label:'${currentValue}', backgroundColors: [
|
||||
[value: 10, color: "#990000"],
|
||||
[value: 10, color: "#000099"]
|
||||
]
|
||||
}
|
||||
|
||||
valueTile("noValue", "device.nada", width: 4, height: 2) {
|
||||
state "noval", label:'${currentValue}', defaultState: true
|
||||
state "default", label:'${currentValue}'
|
||||
}
|
||||
|
||||
valueTile("multiLine", "device.multiLine", width: 3, height: 2) {
|
||||
state "val", label: '${currentValue}', defaultState: true
|
||||
state "default", label: '${currentValue}'
|
||||
}
|
||||
|
||||
valueTile("multiLineWithIcon", "device.multiLine", width: 3, height: 2) {
|
||||
state "val", label: '${currentValue}', icon: "st.switches.switch.off", defaultState: true
|
||||
state "default", label: '${currentValue}', icon: "st.switches.switch.off"
|
||||
}
|
||||
|
||||
main("text")
|
||||
details([
|
||||
"text", "longText", "integer",
|
||||
"text", "longText", "integer",
|
||||
"integerFloat", "pi", "floatAsText",
|
||||
"bgColor", "bgColorRange", "bgColorRangeSingleItem",
|
||||
"bgColorRangeConflict", "noValue",
|
||||
|
||||
@@ -39,15 +39,15 @@ metadata {
|
||||
attributeState "turningOff", label:'${name}', backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
}
|
||||
tileAttribute("device.level", key: "SECONDARY_CONTROL") {
|
||||
attributeState "level", icon: 'st.Weather.weather1', action:"randomizeLevel", defaultState: true
|
||||
attributeState "default", icon: 'st.Weather.weather1', action:"randomizeLevel"
|
||||
}
|
||||
tileAttribute("device.level", key: "SLIDER_CONTROL") {
|
||||
attributeState "level", action:"switch level.setLevel", defaultState: true
|
||||
attributeState "default", action:"switch level.setLevel"
|
||||
}
|
||||
}
|
||||
multiAttributeTile(name:"valueTile", type:"generic", width:6, height:4) {
|
||||
tileAttribute("device.level", key: "PRIMARY_CONTROL") {
|
||||
attributeState "level", label:'${currentValue}', defaultState: true, backgroundColors:[
|
||||
attributeState "default", label:'${currentValue}', backgroundColors:[
|
||||
[value: 0, color: "#ff0000"],
|
||||
[value: 20, color: "#ffff00"],
|
||||
[value: 40, color: "#00ff00"],
|
||||
@@ -69,34 +69,34 @@ metadata {
|
||||
}
|
||||
multiAttributeTile(name:"lengthyTile", type:"generic", width:6, height:4) {
|
||||
tileAttribute("device.lengthyText", key: "PRIMARY_CONTROL") {
|
||||
attributeState "lengthyText", label:'The value of this tile is long and should wrap to two lines', backgroundColor:"#79b821", defaultState: true
|
||||
attributeState "default", label:'The value of this tile is long and should wrap to two lines', backgroundColor:"#79b821"
|
||||
}
|
||||
tileAttribute("device.lengthyText", key: "SECONDARY_CONTROL") {
|
||||
attributeState "lengthyText", label:'The value of this tile is long and should wrap to two lines', backgroundColor:"#79b821", defaultState: true
|
||||
attributeState "default", label:'The value of this tile is long and should wrap to two lines', backgroundColor:"#79b821"
|
||||
}
|
||||
}
|
||||
multiAttributeTile(name:"multilineTile", type:"generic", width:6, height:4) {
|
||||
tileAttribute("device.multilineText", key: "PRIMARY_CONTROL") {
|
||||
attributeState "multiLineText", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821", defaultState: true
|
||||
attributeState "default", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821"
|
||||
}
|
||||
tileAttribute("device.multilineText", key: "SECONDARY_CONTROL") {
|
||||
attributeState "multiLineText", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821", defaultState: true
|
||||
attributeState "default", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821"
|
||||
}
|
||||
}
|
||||
multiAttributeTile(name:"lengthyTileWithIcon", type:"generic", width:6, height:4) {
|
||||
tileAttribute("device.lengthyText", key: "PRIMARY_CONTROL") {
|
||||
attributeState "lengthyText", label:'The value of this tile is long and should wrap to two lines', backgroundColor:"#79b821", icon: "st.switches.switch.on", defaultState: true
|
||||
attributeState "default", label:'The value of this tile is long and should wrap to two lines', backgroundColor:"#79b821", icon: "st.switches.switch.on"
|
||||
}
|
||||
tileAttribute("device.lengthyText", key: "SECONDARY_CONTROL") {
|
||||
attributeState "lengthyText", label:'The value of this tile is long and should wrap to two lines', backgroundColor:"#79b821", icon: "st.switches.switch.on", defaultState: true
|
||||
attributeState "default", label:'The value of this tile is long and should wrap to two lines', backgroundColor:"#79b821", icon: "st.switches.switch.on"
|
||||
}
|
||||
}
|
||||
multiAttributeTile(name:"multilineTileWithIcon", type:"generic", width:6, height:4) {
|
||||
tileAttribute("device.multilineText", key: "PRIMARY_CONTROL") {
|
||||
attributeState "multilineText", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821", icon: "st.switches.switch.on", defaultState: true
|
||||
attributeState "default", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821", icon: "st.switches.switch.on"
|
||||
}
|
||||
tileAttribute("device.multilineText", key: "SECONDARY_CONTROL") {
|
||||
attributeState "multilineText", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821", icon: "st.switches.switch.on", defaultState: true
|
||||
attributeState "default", label:'Line 1 YES\nLine 2 YES\nLine 3 NO', backgroundColor:"#79b821", icon: "st.switches.switch.on"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -96,10 +96,10 @@ metadata {
|
||||
}
|
||||
|
||||
standardTile("reset", "device.reset", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "reset", label:"Reset Color", action:"reset", icon:"st.lights.philips.hue-single", defaultState: true
|
||||
state "default", label:"Reset Color", action:"reset", icon:"st.lights.philips.hue-single"
|
||||
}
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "refresh", label:"", action:"refresh.refresh", icon:"st.secondary.refresh", defaultState: true
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
main(["switch"])
|
||||
@@ -173,6 +173,7 @@ def setColor(value) {
|
||||
def reset() {
|
||||
log.debug "Executing 'reset'"
|
||||
setAdjustedColor([level:100, hex:"#90C638", saturation:56, hue:23])
|
||||
//parent.poll()
|
||||
}
|
||||
|
||||
def setAdjustedColor(value) {
|
||||
@@ -188,6 +189,7 @@ def setAdjustedColor(value) {
|
||||
|
||||
def refresh() {
|
||||
log.debug "Executing 'refresh'"
|
||||
//parent.manualRefresh()
|
||||
}
|
||||
|
||||
def adjustOutgoingHue(percent) {
|
||||
@@ -206,3 +208,4 @@ def adjustOutgoingHue(percent) {
|
||||
log.info "percent: $percent, adjusted: $adjusted"
|
||||
adjusted
|
||||
}
|
||||
|
||||
|
||||
@@ -37,10 +37,10 @@ metadata {
|
||||
attributeState("stopped", label:"Stopped", action:"music Player.play", nextState: "playing")
|
||||
}
|
||||
tileAttribute("device.status", key: "PREVIOUS_TRACK") {
|
||||
attributeState("status", action:"music Player.previousTrack", defaultState: true)
|
||||
attributeState("default", action:"music Player.previousTrack")
|
||||
}
|
||||
tileAttribute("device.status", key: "NEXT_TRACK") {
|
||||
attributeState("status", action:"music Player.nextTrack", defaultState: true)
|
||||
attributeState("default", action:"music Player.nextTrack")
|
||||
}
|
||||
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||
attributeState("level", action:"music Player.setLevel")
|
||||
@@ -50,7 +50,7 @@ metadata {
|
||||
attributeState("muted", action:"music Player.unmute", nextState: "unmuted")
|
||||
}
|
||||
tileAttribute("device.trackDescription", key: "MARQUEE") {
|
||||
attributeState("trackDescription", label:"${currentValue}", defaultState: true)
|
||||
attributeState("default", label:"${currentValue}")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,14 +32,14 @@ metadata {
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"thermostatFull", type:"thermostat", width:6, height:4) {
|
||||
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
|
||||
attributeState("temp", label:'${currentValue}', unit:"dF", defaultState: true)
|
||||
attributeState("default", label:'${currentValue}', unit:"dF")
|
||||
}
|
||||
tileAttribute("device.temperature", key: "VALUE_CONTROL") {
|
||||
attributeState("VALUE_UP", action: "tempUp")
|
||||
attributeState("VALUE_DOWN", action: "tempDown")
|
||||
}
|
||||
tileAttribute("device.humidity", key: "SECONDARY_CONTROL") {
|
||||
attributeState("humidity", label:'${currentValue}%', unit:"%", defaultState: true)
|
||||
attributeState("default", label:'${currentValue}%', unit:"%")
|
||||
}
|
||||
tileAttribute("device.thermostatOperatingState", key: "OPERATING_STATE") {
|
||||
attributeState("idle", backgroundColor:"#44b621")
|
||||
@@ -53,16 +53,15 @@ metadata {
|
||||
attributeState("auto", label:'${name}')
|
||||
}
|
||||
tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT") {
|
||||
attributeState("heatingSetpoint", label:'${currentValue}', unit:"dF", defaultState: true)
|
||||
attributeState("default", label:'${currentValue}', unit:"dF")
|
||||
}
|
||||
tileAttribute("device.coolingSetpoint", key: "COOLING_SETPOINT") {
|
||||
attributeState("coolingSetpoint", label:'${currentValue}', unit:"dF", defaultState: true)
|
||||
attributeState("default", label:'${currentValue}', unit:"dF")
|
||||
}
|
||||
}
|
||||
multiAttributeTile(name:"thermostatNoHumidity", type:"thermostat", width:6, height:4) {
|
||||
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
|
||||
attributeState("coolingSetpoint", label:'${currentValue}', unit:"dF", defaultState: true)
|
||||
attributeState("temp", label:'${currentValue}', unit:"dF")
|
||||
attributeState("default", label:'${currentValue}', unit:"dF")
|
||||
}
|
||||
tileAttribute("device.temperature", key: "VALUE_CONTROL") {
|
||||
attributeState("VALUE_UP", action: "tempUp")
|
||||
@@ -80,16 +79,15 @@ metadata {
|
||||
attributeState("auto", label:'${name}')
|
||||
}
|
||||
tileAttribute("device.heatingSetpoint", key: "HEATING_SETPOINT") {
|
||||
attributeState("coolingSetpoint", label:'${currentValue}', unit:"dF", defaultState: true)
|
||||
attributeState("heatingSetpoint", label:'${currentValue}', unit:"dF")
|
||||
attributeState("default", label:'${currentValue}', unit:"dF")
|
||||
}
|
||||
tileAttribute("device.coolingSetpoint", key: "COOLING_SETPOINT") {
|
||||
attributeState("coolingSetpoint", label:'${currentValue}', unit:"dF", defaultState: true)
|
||||
attributeState("default", label:'${currentValue}', unit:"dF")
|
||||
}
|
||||
}
|
||||
multiAttributeTile(name:"thermostatBasic", type:"thermostat", width:6, height:4) {
|
||||
tileAttribute("device.temperature", key: "PRIMARY_CONTROL") {
|
||||
attributeState("temp", label:'${currentValue}', unit:"dF", defaultState: true,
|
||||
attributeState("default", label:'${currentValue}', unit:"dF",
|
||||
backgroundColors:[
|
||||
[value: 31, color: "#153591"],
|
||||
[value: 44, color: "#1e9cbb"],
|
||||
@@ -120,30 +118,30 @@ metadata {
|
||||
)
|
||||
}
|
||||
standardTile("tempDown", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
||||
state "tempDown", label:'down', action:"tempDown", defaultState: true
|
||||
state "default", label:'down', action:"tempDown"
|
||||
}
|
||||
standardTile("tempUp", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
||||
state "tempUp", label:'up', action:"tempUp", defaultState: true
|
||||
state "default", label:'up', action:"tempUp"
|
||||
}
|
||||
|
||||
valueTile("heatingSetpoint", "device.heatingSetpoint", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
||||
state "heat", label:'${currentValue} heat', unit: "F", backgroundColor:"#ffffff"
|
||||
}
|
||||
standardTile("heatDown", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
||||
state "heatDown", label:'down', action:"heatDown", defaultState: true
|
||||
state "default", label:'down', action:"heatDown"
|
||||
}
|
||||
standardTile("heatUp", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
||||
state "heatUp", label:'up', action:"heatUp", defaultState: true
|
||||
state "default", label:'up', action:"heatUp"
|
||||
}
|
||||
|
||||
valueTile("coolingSetpoint", "device.coolingSetpoint", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
||||
state "cool", label:'${currentValue} cool', unit:"F", backgroundColor:"#ffffff"
|
||||
}
|
||||
standardTile("coolDown", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
||||
state "coolDown", label:'down', action:"coolDown", defaultState: true
|
||||
state "default", label:'down', action:"coolDown"
|
||||
}
|
||||
standardTile("coolUp", "device.temperature", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
||||
state "coolUp", label:'up', action:"coolUp", defaultState: true
|
||||
state "default", label:'up', action:"coolUp"
|
||||
}
|
||||
|
||||
standardTile("mode", "device.thermostatMode", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* for the specific language governing permissions and limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
metadata {
|
||||
definition (name: "Tyco Door/Window Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Battery"
|
||||
@@ -21,28 +21,28 @@ metadata {
|
||||
capability "Contact Sensor"
|
||||
capability "Refresh"
|
||||
capability "Temperature Measurement"
|
||||
|
||||
|
||||
command "enrollResponse"
|
||||
|
||||
|
||||
|
||||
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "Visonic", model: "MCT-340 SMA"
|
||||
}
|
||||
|
||||
|
||||
simulator {
|
||||
|
||||
|
||||
}
|
||||
|
||||
preferences {
|
||||
input title: "Temperature Offset", description: "This feature allows you to correct any temperature variations by selecting an offset. Ex: If your sensor consistently reports a temp that's 5 degrees too warm, you'd enter \"-5\". If 3 degrees too cold, enter \"+3\".", displayDuringSetup: false, type: "paragraph", element: "paragraph"
|
||||
input "tempOffset", "number", title: "Degrees", description: "Adjust temperature by this many degrees", range: "*..*", displayDuringSetup: false
|
||||
}
|
||||
|
||||
|
||||
tiles {
|
||||
standardTile("contact", "device.contact", width: 2, height: 2) {
|
||||
state("open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e")
|
||||
state("closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#79b821")
|
||||
}
|
||||
|
||||
|
||||
valueTile("temperature", "device.temperature", inactiveLabel: false) {
|
||||
state "temperature", label:'${currentValue}°',
|
||||
backgroundColors:[
|
||||
@@ -58,23 +58,23 @@ metadata {
|
||||
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false) {
|
||||
state "battery", label:'${currentValue}% battery', unit:""
|
||||
}
|
||||
|
||||
|
||||
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat") {
|
||||
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
|
||||
standardTile("configure", "device.configure", inactiveLabel: false, decoration: "flat") {
|
||||
state "configure", label:'', action:"configuration.configure", icon:"st.secondary.configure"
|
||||
}
|
||||
|
||||
|
||||
main (["contact", "temperature"])
|
||||
details(["contact","temperature","battery","refresh","configure"])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def parse(String description) {
|
||||
log.debug "description: $description"
|
||||
|
||||
|
||||
Map map = [:]
|
||||
if (description?.startsWith('catchall:')) {
|
||||
map = parseCatchAllMessage(description)
|
||||
@@ -88,10 +88,10 @@ def parse(String description) {
|
||||
else if (description?.startsWith('zone status')) {
|
||||
map = parseIasMessage(description)
|
||||
}
|
||||
|
||||
|
||||
log.debug "Parse returned $map"
|
||||
def result = map ? createEvent(map) : null
|
||||
|
||||
|
||||
if (description?.startsWith('enroll request')) {
|
||||
List cmds = enrollResponse()
|
||||
log.debug "enroll response: ${cmds}"
|
||||
@@ -99,7 +99,7 @@ def parse(String description) {
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
private Map parseCatchAllMessage(String description) {
|
||||
Map resultMap = [:]
|
||||
def cluster = zigbee.parse(description)
|
||||
@@ -125,20 +125,20 @@ private Map parseCatchAllMessage(String description) {
|
||||
private boolean shouldProcessMessage(cluster) {
|
||||
// 0x0B is default response indicating message got through
|
||||
// 0x07 is bind message
|
||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||
boolean ignoredMessage = cluster.profileId != 0x0104 ||
|
||||
cluster.command == 0x0B ||
|
||||
cluster.command == 0x07 ||
|
||||
(cluster.data.size() > 0 && cluster.data.first() == 0x3e)
|
||||
return !ignoredMessage
|
||||
}
|
||||
|
||||
|
||||
private Map parseReportAttributeMessage(String description) {
|
||||
Map descMap = (description - "read attr - ").split(",").inject([:]) { map, param ->
|
||||
def nameAndValue = param.split(":")
|
||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
||||
}
|
||||
log.debug "Desc Map: $descMap"
|
||||
|
||||
|
||||
Map resultMap = [:]
|
||||
if (descMap.cluster == "0402" && descMap.attrId == "0000") {
|
||||
def value = getTemperature(descMap.value)
|
||||
@@ -147,10 +147,10 @@ private Map parseReportAttributeMessage(String description) {
|
||||
else if (descMap.cluster == "0001" && descMap.attrId == "0020") {
|
||||
resultMap = getBatteryResult(Integer.parseInt(descMap.value, 16))
|
||||
}
|
||||
|
||||
|
||||
return resultMap
|
||||
}
|
||||
|
||||
|
||||
private Map parseCustomMessage(String description) {
|
||||
Map resultMap = [:]
|
||||
if (description?.startsWith('temperature: ')) {
|
||||
@@ -163,7 +163,7 @@ private Map parseCustomMessage(String description) {
|
||||
private Map parseIasMessage(String description) {
|
||||
List parsedMsg = description.split(' ')
|
||||
String msgCode = parsedMsg[2]
|
||||
|
||||
|
||||
Map resultMap = [:]
|
||||
switch(msgCode) {
|
||||
case '0x0020': // Closed/No Motion/Dry
|
||||
@@ -196,7 +196,7 @@ private Map parseIasMessage(String description) {
|
||||
}
|
||||
return resultMap
|
||||
}
|
||||
|
||||
|
||||
def getTemperature(value) {
|
||||
def celsius = Integer.parseInt(value, 16).shortValue() / 100
|
||||
if(getTemperatureScale() == "C"){
|
||||
@@ -209,11 +209,11 @@ def getTemperature(value) {
|
||||
private Map getBatteryResult(rawValue) {
|
||||
log.debug 'Battery'
|
||||
def linkText = getLinkText(device)
|
||||
|
||||
|
||||
def result = [
|
||||
name: 'battery'
|
||||
]
|
||||
|
||||
|
||||
def volts = rawValue / 10
|
||||
def descriptionText
|
||||
if (volts > 3.5) {
|
||||
@@ -223,8 +223,7 @@ private Map getBatteryResult(rawValue) {
|
||||
def minVolts = 2.1
|
||||
def maxVolts = 3.0
|
||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||
def roundedPct = Math.round(pct * 100)
|
||||
result.value = Math.min(100, roundedPct)
|
||||
result.value = Math.min(100, (int) pct * 100)
|
||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||
}
|
||||
|
||||
@@ -262,7 +261,7 @@ def refresh()
|
||||
{
|
||||
log.debug "Refreshing Temperature and Battery"
|
||||
[
|
||||
|
||||
|
||||
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
|
||||
"st rattr 0x${device.deviceNetworkId} 1 1 0x20"
|
||||
|
||||
@@ -275,24 +274,24 @@ def configure() {
|
||||
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
||||
def configCmds = [
|
||||
"delay 1000",
|
||||
|
||||
|
||||
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
|
||||
|
||||
|
||||
"zcl global send-me-a-report 1 0x20 0x20 600 3600 {01}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
|
||||
|
||||
|
||||
"zcl global send-me-a-report 0x402 0 0x29 300 3600 {6400}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
|
||||
|
||||
|
||||
|
||||
|
||||
//"raw 0x500 {01 23 00 00 00}", "delay 200",
|
||||
//"send 0x${device.deviceNetworkId} 1 1", "delay 1500",
|
||||
|
||||
|
||||
|
||||
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 1 0x402 {${device.zigbeeId}} {}", "delay 500",
|
||||
"zdo bind 0x${device.deviceNetworkId} 1 1 1 {${device.zigbeeId}} {}",
|
||||
|
||||
|
||||
"delay 500"
|
||||
]
|
||||
return configCmds + enrollResponse() + refresh() // send refresh cmds as part of config
|
||||
@@ -300,11 +299,11 @@ def configure() {
|
||||
|
||||
def enrollResponse() {
|
||||
log.debug "Sending enroll response"
|
||||
[
|
||||
|
||||
[
|
||||
|
||||
"raw 0x500 {01 23 00 00 00}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 1"
|
||||
|
||||
|
||||
]
|
||||
}
|
||||
private hex(value) {
|
||||
|
||||
@@ -22,9 +22,8 @@ metadata {
|
||||
capability "Switch"
|
||||
capability "Switch Level"
|
||||
|
||||
//fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 1000", outClusters: "0000,0019"
|
||||
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 1000", outClusters: "0019"
|
||||
//fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 1000", outClusters: "0000,0019", manufacturer: "CREE", model: "Connected A-19 60W Equivalent", deviceJoinName: "Cree Connected Bulb"
|
||||
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 1000", outClusters: "0000,0019"
|
||||
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 1000", outClusters: "0000,0019", manufacturer: "CREE", model: "Connected A-19 60W Equivalent", deviceJoinName: "Cree Connected Bulb"
|
||||
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 1000, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Classic A60 W clear", deviceJoinName: "OSRAM LIGHTIFY LED Smart Connected Light"
|
||||
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 1000, 0B04, FC0F", outClusters: "0019", manufacturer: "OSRAM", model: "Classic A60 W clear - LIGHTIFY", deviceJoinName: "OSRAM LIGHTIFY LED Smart Connected Light"
|
||||
fingerprint profileId: "C05E", inClusters: "0000, 0003, 0004, 0005, 0006, 0008, 1000", outClusters: "0019", manufacturer: "Philips", model: "LWB006", deviceJoinName: "Philips Hue White"
|
||||
|
||||
@@ -28,6 +28,7 @@ metadata {
|
||||
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x98"
|
||||
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x86,0x72,0x98", outClusters: "0x5A,0x82"
|
||||
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x80,0x71,0x85,0x70,0x72,0x86,0x30,0x31,0x84,0x59,0x73,0x5A,0x8F,0x98,0x7A", outClusters:"0x20" // Philio multi+
|
||||
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x72,0x5A,0x80,0x73,0x86,0x84,0x85,0x59,0x71,0x70,0x7A,0x98" // Vision door/window
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
@@ -35,7 +36,6 @@ metadata {
|
||||
// status messages
|
||||
status "open": "command: 2001, payload: FF"
|
||||
status "closed": "command: 2001, payload: 00"
|
||||
status "wake up": "command: 8407, payload: "
|
||||
}
|
||||
|
||||
// UI tile definitions
|
||||
@@ -81,7 +81,7 @@ def updated() {
|
||||
def cmds = []
|
||||
if (!state.MSR) {
|
||||
cmds = [
|
||||
command(zwave.manufacturerSpecificV2.manufacturerSpecificGet()),
|
||||
zwave.manufacturerSpecificV2.manufacturerSpecificGet().format(),
|
||||
"delay 1200",
|
||||
zwave.wakeUpV1.wakeUpNoMoreInformation().format()
|
||||
]
|
||||
@@ -94,9 +94,9 @@ def updated() {
|
||||
}
|
||||
|
||||
def configure() {
|
||||
commands([
|
||||
zwave.manufacturerSpecificV2.manufacturerSpecificGet(),
|
||||
zwave.batteryV1.batteryGet()
|
||||
delayBetween([
|
||||
zwave.manufacturerSpecificV2.manufacturerSpecificGet().format(),
|
||||
batteryGetCommand()
|
||||
], 6000)
|
||||
}
|
||||
|
||||
@@ -147,11 +147,12 @@ def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cm
|
||||
result << sensorValueEvent(1)
|
||||
} else if (cmd.event == 0x03) {
|
||||
result << createEvent(descriptionText: "$device.displayName covering was removed", isStateChange: true)
|
||||
if(!state.MSR) result << response(command(zwave.manufacturerSpecificV2.manufacturerSpecificGet()))
|
||||
result << response(zwave.wakeUpV1.wakeUpIntervalSet(seconds:4*3600, nodeid:zwaveHubNodeId))
|
||||
if(!state.MSR) result << response(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
|
||||
} else if (cmd.event == 0x05 || cmd.event == 0x06) {
|
||||
result << createEvent(descriptionText: "$device.displayName detected glass breakage", isStateChange: true)
|
||||
} else if (cmd.event == 0x07) {
|
||||
if(!state.MSR) result << response(command(zwave.manufacturerSpecificV2.manufacturerSpecificGet()))
|
||||
if(!state.MSR) result << response(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
|
||||
result << createEvent(name: "motion", value: "active", descriptionText:"$device.displayName detected motion")
|
||||
}
|
||||
} else if (cmd.notificationType) {
|
||||
@@ -169,11 +170,12 @@ def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd)
|
||||
def event = createEvent(descriptionText: "${device.displayName} woke up", isStateChange: false)
|
||||
def cmds = []
|
||||
if (!state.MSR) {
|
||||
cmds << command(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
|
||||
cmds << zwave.wakeUpV1.wakeUpIntervalSet(seconds:4*3600, nodeid:zwaveHubNodeId).format()
|
||||
cmds << zwave.manufacturerSpecificV2.manufacturerSpecificGet().format()
|
||||
cmds << "delay 1200"
|
||||
}
|
||||
if (!state.lastbat || now() - state.lastbat > 53*60*60*1000) {
|
||||
cmds << command(zwave.batteryV1.batteryGet())
|
||||
cmds << batteryGetCommand()
|
||||
} else {
|
||||
cmds << zwave.wakeUpV1.wakeUpNoMoreInformation().format()
|
||||
}
|
||||
@@ -210,7 +212,7 @@ def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerS
|
||||
if (msr == "0086-0102-0059") {
|
||||
result << response(zwave.securityV1.securityMessageEncapsulation().encapsulate(zwave.batteryV1.batteryGet()).format())
|
||||
} else {
|
||||
result << response(command(zwave.batteryV1.batteryGet()))
|
||||
result << response(batteryGetCommand())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,7 +220,7 @@ def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerS
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
|
||||
def encapsulatedCommand = cmd.encapsulatedCommand([0x20: 1, 0x25: 1, 0x30: 1, 0x31: 5, 0x80: 1, 0x84: 1, 0x71: 3, 0x9C: 1])
|
||||
def encapsulatedCommand = cmd.encapsulatedCommand([0x20: 1, 0x85: 2, 0x70: 1])
|
||||
// log.debug "encapsulated: $encapsulatedCommand"
|
||||
if (encapsulatedCommand) {
|
||||
state.sec = 1
|
||||
@@ -230,16 +232,12 @@ def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
||||
createEvent(descriptionText: "$device.displayName: $cmd", displayed: false)
|
||||
}
|
||||
|
||||
private command(physicalgraph.zwave.Command cmd) {
|
||||
if (state.sec == 1) {
|
||||
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
|
||||
} else {
|
||||
cmd.format()
|
||||
def batteryGetCommand() {
|
||||
def cmd = zwave.batteryV1.batteryGet()
|
||||
if (state.sec) {
|
||||
cmd = zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd)
|
||||
}
|
||||
}
|
||||
|
||||
private commands(commands, delay=200) {
|
||||
delayBetween(commands.collect{ command(it) }, delay)
|
||||
cmd.format()
|
||||
}
|
||||
|
||||
def retypeBasedOnMSR() {
|
||||
@@ -262,12 +260,8 @@ def retypeBasedOnMSR() {
|
||||
setDeviceType("3-in-1 Multisensor Plus (SG)")
|
||||
break
|
||||
case "0109-2001-0106": // Vision door/window
|
||||
log.debug "Changing device type to Z-Wave Plus Door/Window Sensor"
|
||||
setDeviceType("Z-Wave Plus Door/Window Sensor")
|
||||
break
|
||||
case "0109-2002-0205": // Vision Motion
|
||||
log.debug "Changing device type to Z-Wave Plus Motion/Temp Sensor"
|
||||
setDeviceType("Z-Wave Plus Motion/Temp Sensor")
|
||||
log.debug "Changing device type to Door / Window Sensor Plus (SG)"
|
||||
setDeviceType("Door / Window Sensor Plus (SG)")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,13 +21,6 @@ metadata {
|
||||
capability "Motion Sensor"
|
||||
capability "Sensor"
|
||||
capability "Battery"
|
||||
|
||||
fingerprint mfr: "011F", prod: "0001", model: "0001", deviceJoinName: "Schlage Motion Sensor" // Schlage motion
|
||||
fingerprint mfr: "014A", prod: "0001", model: "0001", deviceJoinName: "Ecolink Motion Sensor" // Ecolink motion
|
||||
fingerprint mfr: "014A", prod: "0004", model: "0001", deviceJoinName: "Ecolink Motion Sensor" // Ecolink motion +
|
||||
fingerprint mfr: "0060", prod: "0001", model: "0002", deviceJoinName: "Everspring Motion Sensor" // Everspring SP814
|
||||
fingerprint mfr: "0060", prod: "0001", model: "0003", deviceJoinName: "Everspring Motion Sensor" // Everspring HSP02
|
||||
fingerprint mfr: "011A", prod: "0601", model: "0901", deviceJoinName: "Enerwave Motion Sensor" // Enerwave ZWN-BPC
|
||||
}
|
||||
|
||||
simulator {
|
||||
@@ -132,9 +125,9 @@ def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd)
|
||||
}
|
||||
if (!state.lastbat || (new Date().time) - state.lastbat > 53*60*60*1000) {
|
||||
result << response(zwave.batteryV1.batteryGet())
|
||||
} else {
|
||||
result << response(zwave.wakeUpV1.wakeUpNoMoreInformation())
|
||||
result << response("delay 1200")
|
||||
}
|
||||
result << response(zwave.wakeUpV1.wakeUpNoMoreInformation())
|
||||
result
|
||||
}
|
||||
|
||||
|
||||
@@ -1,270 +0,0 @@
|
||||
/**
|
||||
* Copyright 2016 SmartThings
|
||||
* Copyright 2015 AstraLink
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Z-Wave Plus Door/Window Sensor, ZD2102*-5
|
||||
*
|
||||
*/
|
||||
|
||||
metadata {
|
||||
definition (name: "Z-Wave Plus Door/Window Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Contact Sensor"
|
||||
capability "Configuration"
|
||||
capability "Battery"
|
||||
capability "Sensor"
|
||||
|
||||
// for Astralink
|
||||
attribute "ManufacturerCode", "string"
|
||||
attribute "ProduceTypeCode", "string"
|
||||
attribute "ProductCode", "string"
|
||||
attribute "WakeUp", "string"
|
||||
attribute "WirelessConfig", "string"
|
||||
|
||||
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x98, 0x86, 0x72, 0x5A, 0x85, 0x59, 0x73, 0x80, 0x71, 0x70, 0x84, 0x7A"
|
||||
fingerprint type:"8C07", inClusters: "5E,98,86,72,5A,71"
|
||||
fingerprint mfr:"0109", prod:"2001", model:"0106" // not using deviceJoinName because it's sold under different brand names
|
||||
}
|
||||
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"contact", type: "generic", width: 6, height: 4){
|
||||
tileAttribute ("device.contact", key: "PRIMARY_CONTROL") {
|
||||
attributeState "open", label:'${name}', icon:"st.contact.contact.open", backgroundColor:"#ffa81e"
|
||||
attributeState "closed", label:'${name}', icon:"st.contact.contact.closed", backgroundColor:"#79b821"
|
||||
}
|
||||
}
|
||||
valueTile("battery", "device.battery", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
|
||||
state "battery", label:'${currentValue}% battery', unit:""
|
||||
}
|
||||
|
||||
main (["contact"])
|
||||
details(["contact","battery"])
|
||||
}
|
||||
|
||||
simulator {
|
||||
// messages the device returns in response to commands it receives
|
||||
status "open (basic)" : "command: 9881, payload: 00 20 01 FF"
|
||||
status "closed (basic)" : "command: 9881 payload: 00 20 01 00"
|
||||
status "open (notification)" : "command: 9881, payload: 00 71 05 06 FF 00 FF 06 16 00 00"
|
||||
status "closed (notification)" : "command: 9881, payload: 00 71 05 06 00 00 FF 06 17 00 00"
|
||||
status "tamper: enclosure opened" : "command: 9881, payload: 00 71 05 07 FF 00 FF 07 03 00 00"
|
||||
status "tamper: enclosure replaced" : "command: 9881, payload: 00 71 05 07 00 00 FF 07 00 00 00"
|
||||
status "wake up" : "command: 9881, payload: 00 84 07"
|
||||
status "battery (100%)" : "command: 9881, payload: 00 80 03 64"
|
||||
status "battery low" : "command: 9881, payload: 00 80 03 FF"
|
||||
}
|
||||
}
|
||||
|
||||
def configure() {
|
||||
log.debug "configure()"
|
||||
def cmds = []
|
||||
|
||||
if (state.sec != 1) {
|
||||
// secure inclusion may not be complete yet
|
||||
cmds << "delay 1000"
|
||||
}
|
||||
|
||||
cmds += secureSequence([
|
||||
zwave.manufacturerSpecificV2.manufacturerSpecificGet(),
|
||||
zwave.batteryV1.batteryGet(),
|
||||
], 500)
|
||||
|
||||
cmds << "delay 8000"
|
||||
cmds << secure(zwave.wakeUpV1.wakeUpNoMoreInformation())
|
||||
return cmds
|
||||
}
|
||||
|
||||
private getCommandClassVersions() {
|
||||
[
|
||||
0x71: 3, // Notification
|
||||
0x5E: 2, // ZwaveplusInfo
|
||||
0x59: 1, // AssociationGrpInfo
|
||||
0x85: 2, // Association
|
||||
0x20: 1, // Basic
|
||||
0x80: 1, // Battery
|
||||
0x70: 1, // Configuration
|
||||
0x5A: 1, // DeviceResetLocally
|
||||
0x7A: 2, // FirmwareUpdateMd
|
||||
0x72: 2, // ManufacturerSpecific
|
||||
0x73: 1, // Powerlevel
|
||||
0x98: 1, // Security
|
||||
0x84: 2, // WakeUp
|
||||
0x86: 1, // Version
|
||||
]
|
||||
}
|
||||
|
||||
// Parse incoming device messages to generate events
|
||||
def parse(String description) {
|
||||
def result = []
|
||||
def cmd
|
||||
if (description.startsWith("Err 106")) {
|
||||
state.sec = 0
|
||||
result = createEvent( name: "secureInclusion", value: "failed", eventType: "ALERT",
|
||||
descriptionText: "This sensor failed to complete the network security key exchange. If you are unable to control it via SmartThings, you must remove it from your network and add it again.")
|
||||
} else if (description.startsWith("Err")) {
|
||||
result = createEvent(descriptionText: "$device.displayName $description", isStateChange: true)
|
||||
} else {
|
||||
cmd = zwave.parse(description, commandClassVersions)
|
||||
if (cmd) {
|
||||
result = zwaveEvent(cmd)
|
||||
}
|
||||
}
|
||||
|
||||
if (result instanceof List) {
|
||||
result = result.flatten()
|
||||
}
|
||||
|
||||
log.debug "Parsed '$description' to $result"
|
||||
return result
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
|
||||
def encapsulatedCommand = cmd.encapsulatedCommand(commandClassVersions)
|
||||
log.debug "encapsulated: $encapsulatedCommand"
|
||||
if (encapsulatedCommand) {
|
||||
state.sec = 1
|
||||
return zwaveEvent(encapsulatedCommand)
|
||||
} else {
|
||||
log.warn "Unable to extract encapsulated cmd from $cmd"
|
||||
return [createEvent(descriptionText: cmd.toString())]
|
||||
}
|
||||
}
|
||||
|
||||
def sensorValueEvent(value) {
|
||||
if (value) {
|
||||
createEvent(name: "contact", value: "open", descriptionText: "$device.displayName is open")
|
||||
} else {
|
||||
createEvent(name: "contact", value: "closed", descriptionText: "$device.displayName is closed")
|
||||
}
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
|
||||
return sensorValueEvent(cmd.value)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd) {
|
||||
return sensorValueEvent(cmd.value)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.sensorbinaryv2.SensorBinaryReport cmd) {
|
||||
return sensorValueEvent(cmd.sensorValue)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.sensoralarmv1.SensorAlarmReport cmd) {
|
||||
return sensorValueEvent(cmd.sensorState)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) {
|
||||
def result = []
|
||||
if (cmd.notificationType == 0x06 && cmd.event == 0x16) {
|
||||
result << sensorValueEvent(1)
|
||||
} else if (cmd.notificationType == 0x06 && cmd.event == 0x17) {
|
||||
result << sensorValueEvent(0)
|
||||
} else if (cmd.notificationType == 0x07) {
|
||||
if (cmd.event == 0x00) {
|
||||
if (cmd.eventParametersLength == 0 || cmd.eventParameter[0] != 3) {
|
||||
result << createEvent(descriptionText: "$device.displayName covering replaced", isStateChange: true, displayed: false)
|
||||
} else {
|
||||
result << sensorValueEvent(0)
|
||||
}
|
||||
} else if (cmd.event == 0x01 || cmd.event == 0x02) {
|
||||
result << sensorValueEvent(1)
|
||||
} else if (cmd.event == 0x03) {
|
||||
result << createEvent(descriptionText: "$device.displayName covering was removed", isStateChange: true)
|
||||
if (!device.currentState("ManufacturerCode")) {
|
||||
result << response(secure(zwave.manufacturerSpecificV2.manufacturerSpecificGet()))
|
||||
}
|
||||
} else if (cmd.event == 0x05 || cmd.event == 0x06) {
|
||||
result << createEvent(descriptionText: "$device.displayName detected glass breakage", isStateChange: true)
|
||||
} else {
|
||||
result << createEvent(descriptionText: "$device.displayName event $cmd.event ${cmd.eventParameter.inspect()}", isStateChange: true, displayed: false)
|
||||
}
|
||||
} else if (cmd.notificationType) {
|
||||
result << createEvent(descriptionText: "$device.displayName notification $cmd.notificationType event $cmd.event ${cmd.eventParameter.inspect()}", isStateChange: true, displayed: false)
|
||||
} else {
|
||||
def value = cmd.v1AlarmLevel == 255 ? "active" : cmd.v1AlarmLevel ?: "inactive"
|
||||
result << createEvent(name: "alarm $cmd.v1AlarmType", value: value, isStateChange: true, displayed: false)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpNotification cmd) {
|
||||
def event = createEvent(name: "WakeUp", value: "wakeup", descriptionText: "${device.displayName} woke up", isStateChange: true, displayed: false) // for Astralink
|
||||
def cmds = []
|
||||
|
||||
if (!device.currentState("ManufacturerCode")) {
|
||||
cmds << secure(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
|
||||
cmds << "delay 2000"
|
||||
}
|
||||
if (!state.lastbat || now() - state.lastbat > 10*60*60*1000) {
|
||||
event.descriptionText += ", requesting battery"
|
||||
cmds << secure(zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType:1, scale:1))
|
||||
cmds << "delay 800"
|
||||
cmds << secure(zwave.batteryV1.batteryGet())
|
||||
cmds << "delay 2000"
|
||||
} else {
|
||||
log.debug "not checking battery, was updated ${(now() - state.lastbat)/60000 as int} min ago"
|
||||
}
|
||||
cmds << secure(zwave.wakeUpV1.wakeUpNoMoreInformation())
|
||||
|
||||
return [event, response(cmds)]
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
|
||||
def map = [ name: "battery", unit: "%" ]
|
||||
if (cmd.batteryLevel == 0xFF) {
|
||||
map.value = 1
|
||||
map.descriptionText = "${device.displayName} has a low battery"
|
||||
map.isStateChange = true
|
||||
} else {
|
||||
map.value = cmd.batteryLevel
|
||||
}
|
||||
def event = createEvent(map)
|
||||
|
||||
// Save at least one battery report in events list every few days
|
||||
if (!event.isStateChange && (now() - 3*24*60*60*1000) > device.latestState("battery")?.date?.time) {
|
||||
map.isStateChange = true
|
||||
}
|
||||
state.lastbat = now()
|
||||
return [event]
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
|
||||
def result = []
|
||||
def manufacturerCode = String.format("%04X", cmd.manufacturerId)
|
||||
def productTypeCode = String.format("%04X", cmd.productTypeId)
|
||||
def productCode = String.format("%04X", cmd.productId)
|
||||
def wirelessConfig = "ZWP"
|
||||
log.debug "MSR ${manufacturerCode} ${productTypeCode} ${productCode}"
|
||||
|
||||
result << createEvent(name: "ManufacturerCode", value: manufacturerCode)
|
||||
result << createEvent(name: "ProduceTypeCode", value: productTypeCode)
|
||||
result << createEvent(name: "ProductCode", value: productCode)
|
||||
result << createEvent(name: "WirelessConfig", value: wirelessConfig)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
||||
return [createEvent(descriptionText: "$device.displayName: $cmd", displayed: false)]
|
||||
}
|
||||
|
||||
private secure(physicalgraph.zwave.Command cmd) {
|
||||
if (state.sec == 0) { // default to secure
|
||||
cmd.format()
|
||||
} else {
|
||||
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
|
||||
}
|
||||
}
|
||||
|
||||
private secureSequence(commands, delay=200) {
|
||||
delayBetween(commands.collect{ secure(it) }, delay)
|
||||
}
|
||||
@@ -1,327 +0,0 @@
|
||||
/**
|
||||
* Copyright 2016 SmartThings
|
||||
* Copyright 2015 AstraLink
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Z-Wave Plus Motion Sensor with Temperature Measurement, ZP3102*-5
|
||||
*
|
||||
*/
|
||||
|
||||
metadata {
|
||||
definition (name: "Z-Wave Plus Motion/Temp Sensor", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Motion Sensor"
|
||||
capability "Temperature Measurement"
|
||||
capability "Configuration"
|
||||
capability "Battery"
|
||||
capability "Sensor"
|
||||
|
||||
// for Astralink
|
||||
attribute "ManufacturerCode", "string"
|
||||
attribute "ProduceTypeCode", "string"
|
||||
attribute "ProductCode", "string"
|
||||
attribute "WakeUp", "string"
|
||||
attribute "WirelessConfig", "string"
|
||||
|
||||
fingerprint deviceId: "0x0701", inClusters: "0x5E, 0x98, 0x86, 0x72, 0x5A, 0x85, 0x59, 0x73, 0x80, 0x71, 0x31, 0x70, 0x84, 0x7A"
|
||||
fingerprint type:"8C07", inClusters: "5E,98,86,72,5A,31,71"
|
||||
fingerprint mfr:"0109", prod:"2002", model:"0205" // not using deviceJoinName because it's sold under different brand names
|
||||
}
|
||||
|
||||
tiles {
|
||||
standardTile("motion", "device.motion", width: 3, height: 2) {
|
||||
state "active", label:'motion', icon:"st.motion.motion.active", backgroundColor:"#53a7c0"
|
||||
state "inactive", label:'no motion', icon:"st.motion.motion.inactive", backgroundColor:"#ffffff"
|
||||
}
|
||||
|
||||
valueTile("temperature", "device.temperature", inactiveLabel: false) {
|
||||
state "temperature", label:'${currentValue}°',
|
||||
backgroundColors:[
|
||||
[value: 31, color: "#153591"],
|
||||
[value: 44, color: "#1e9cbb"],
|
||||
[value: 59, color: "#90d2a7"],
|
||||
[value: 74, color: "#44b621"],
|
||||
[value: 84, color: "#f1d801"],
|
||||
[value: 95, color: "#d04e00"],
|
||||
[value: 96, color: "#bc2323"]
|
||||
]
|
||||
}
|
||||
valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat") {
|
||||
state "battery", label:'${currentValue}% battery', unit:"%"
|
||||
}
|
||||
|
||||
main(["motion", "temperature"])
|
||||
details(["motion", "temperature", "battery"])
|
||||
}
|
||||
}
|
||||
|
||||
def updated() {
|
||||
if (!device.currentState("ManufacturerCode")) {
|
||||
response(secure(zwave.manufacturerSpecificV2.manufacturerSpecificGet()))
|
||||
}
|
||||
}
|
||||
|
||||
def configure() {
|
||||
log.debug "configure()"
|
||||
def cmds = []
|
||||
|
||||
if (state.sec != 1) {
|
||||
// secure inclusion may not be complete yet
|
||||
cmds << "delay 1000"
|
||||
}
|
||||
|
||||
cmds += secureSequence([
|
||||
zwave.manufacturerSpecificV2.manufacturerSpecificGet(),
|
||||
zwave.batteryV1.batteryGet(),
|
||||
zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType:1, scale:1)
|
||||
], 500)
|
||||
|
||||
cmds << "delay 8000"
|
||||
cmds << secure(zwave.wakeUpV1.wakeUpNoMoreInformation())
|
||||
return cmds
|
||||
}
|
||||
|
||||
private getCommandClassVersions() {
|
||||
[
|
||||
0x71: 3, // Notification
|
||||
0x5E: 2, // ZwaveplusInfo
|
||||
0x59: 1, // AssociationGrpInfo
|
||||
0x85: 2, // Association
|
||||
0x20: 1, // Basic
|
||||
0x80: 1, // Battery
|
||||
0x70: 1, // Configuration
|
||||
0x5A: 1, // DeviceResetLocally
|
||||
0x7A: 2, // FirmwareUpdateMd
|
||||
0x72: 2, // ManufacturerSpecific
|
||||
0x73: 1, // Powerlevel
|
||||
0x98: 1, // Security
|
||||
0x31: 5, // SensorMultilevel
|
||||
0x84: 2 // WakeUp
|
||||
]
|
||||
}
|
||||
|
||||
// Parse incoming device messages to generate events
|
||||
def parse(String description) {
|
||||
def result = []
|
||||
def cmd
|
||||
if (description.startsWith("Err 106")) {
|
||||
state.sec = 0
|
||||
result = createEvent( name: "secureInclusion", value: "failed", eventType: "ALERT",
|
||||
descriptionText: "This sensor failed to complete the network security key exchange. If you are unable to control it via SmartThings, you must remove it from your network and add it again.")
|
||||
} else if (description.startsWith("Err")) {
|
||||
result = createEvent(descriptionText: "$device.displayName $description", isStateChange: true)
|
||||
} else {
|
||||
cmd = zwave.parse(description, commandClassVersions)
|
||||
if (cmd) {
|
||||
result = zwaveEvent(cmd)
|
||||
}
|
||||
}
|
||||
|
||||
if (result instanceof List) {
|
||||
result = result.flatten()
|
||||
}
|
||||
|
||||
log.debug "Parsed '$description' to $result"
|
||||
return result
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
|
||||
def encapsulatedCommand = cmd.encapsulatedCommand(commandClassVersions)
|
||||
log.debug "encapsulated: $encapsulatedCommand"
|
||||
if (encapsulatedCommand) {
|
||||
state.sec = 1
|
||||
return zwaveEvent(encapsulatedCommand)
|
||||
} else {
|
||||
log.warn "Unable to extract encapsulated cmd from $cmd"
|
||||
return [createEvent(descriptionText: cmd.toString())]
|
||||
}
|
||||
}
|
||||
|
||||
def sensorValueEvent(value) {
|
||||
def result = []
|
||||
if (value) {
|
||||
log.debug "sensorValueEvent($value) : active"
|
||||
result << createEvent(name: "motion", value: "active", descriptionText: "$device.displayName detected motion")
|
||||
} else {
|
||||
log.debug "sensorValueEvent($value) : inactive"
|
||||
result << createEvent(name: "motion", value: "inactive", descriptionText: "$device.displayName motion has stopped")
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
|
||||
return sensorValueEvent(cmd.value)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd) {
|
||||
return sensorValueEvent(cmd.value)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) {
|
||||
return sensorValueEvent(cmd.value)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.sensorbinaryv2.SensorBinaryReport cmd) {
|
||||
return sensorValueEvent(cmd.sensorValue)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.sensoralarmv1.SensorAlarmReport cmd) {
|
||||
return sensorValueEvent(cmd.sensorState)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) {
|
||||
def result = []
|
||||
if (cmd.notificationType == 0x07) {
|
||||
if (cmd.event == 0x01 || cmd.event == 0x02) {
|
||||
result << sensorValueEvent(1)
|
||||
} else if (cmd.event == 0x03) {
|
||||
result << createEvent(descriptionText: "$device.displayName covering was removed", isStateChange: true)
|
||||
result << response(secure(zwave.manufacturerSpecificV2.manufacturerSpecificGet()))
|
||||
} else if (cmd.event == 0x05 || cmd.event == 0x06) {
|
||||
result << createEvent(descriptionText: "$device.displayName detected glass breakage", isStateChange: true)
|
||||
} else if (cmd.event == 0x07) {
|
||||
result << sensorValueEvent(1)
|
||||
} else if (cmd.event == 0x08) {
|
||||
result << sensorValueEvent(1)
|
||||
} else if (cmd.event == 0x00) {
|
||||
if (cmd.eventParametersLength && cmd.eventParameter[0] == 3) {
|
||||
result << createEvent(descriptionText: "$device.displayName covering replaced", isStateChange: true, displayed: false)
|
||||
} else {
|
||||
result << sensorValueEvent(0)
|
||||
}
|
||||
} else if (cmd.event == 0xFF) {
|
||||
result << sensorValueEvent(1)
|
||||
} else {
|
||||
result << createEvent(descriptionText: "$device.displayName sent event $cmd.event")
|
||||
}
|
||||
} else if (cmd.notificationType) {
|
||||
def text = "Notification $cmd.notificationType: event ${([cmd.event] + cmd.eventParameter).join(", ")}"
|
||||
result << createEvent(name: "notification$cmd.notificationType", value: "$cmd.event", descriptionText: text, displayed: false)
|
||||
} else {
|
||||
def value = cmd.v1AlarmLevel == 255 ? "active" : cmd.v1AlarmLevel ?: "inactive"
|
||||
result << createEvent(name: "alarm $cmd.v1AlarmType", value: value, displayed: false)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.wakeupv2.WakeUpNotification cmd) {
|
||||
def event = createEvent(name: "WakeUp", value: "wakeup", descriptionText: "${device.displayName} woke up", isStateChange: true, displayed: false) // for Astralink
|
||||
def cmds = []
|
||||
|
||||
if (!device.currentState("ManufacturerCode")) {
|
||||
cmds << secure(zwave.manufacturerSpecificV2.manufacturerSpecificGet())
|
||||
cmds << "delay 2000"
|
||||
}
|
||||
if (!state.lastbat || now() - state.lastbat > 10*60*60*1000) {
|
||||
event.descriptionText += ", requesting battery"
|
||||
cmds << secure(zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType:1, scale:1))
|
||||
cmds << "delay 800"
|
||||
cmds << secure(zwave.batteryV1.batteryGet())
|
||||
cmds << "delay 2000"
|
||||
} else {
|
||||
log.debug "not checking battery, was updated ${(now() - state.lastbat)/60000 as int} min ago"
|
||||
}
|
||||
cmds << secure(zwave.wakeUpV1.wakeUpNoMoreInformation())
|
||||
|
||||
return [event, response(cmds)]
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
|
||||
def result = []
|
||||
def map = [ name: "battery", unit: "%" ]
|
||||
if (cmd.batteryLevel == 0xFF) {
|
||||
map.value = 1
|
||||
map.descriptionText = "${device.displayName} has a low battery"
|
||||
map.isStateChange = true
|
||||
} else {
|
||||
map.value = cmd.batteryLevel
|
||||
}
|
||||
def event = createEvent(map)
|
||||
|
||||
// Save at least one battery report in events list every few days
|
||||
if (!event.isStateChange && (now() - 3*24*60*60*1000) > device.latestState("battery")?.date?.time) {
|
||||
map.isStateChange = true
|
||||
}
|
||||
state.lastbat = now()
|
||||
return [event]
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd) {
|
||||
def result = []
|
||||
def map = [:]
|
||||
switch (cmd.sensorType) {
|
||||
case 1:
|
||||
def cmdScale = cmd.scale == 1 ? "F" : "C"
|
||||
map.name = "temperature"
|
||||
map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmdScale, cmd.precision)
|
||||
map.unit = getTemperatureScale()
|
||||
break;
|
||||
case 3:
|
||||
map.name = "illuminance"
|
||||
map.value = cmd.scaledSensorValue.toInteger().toString()
|
||||
map.unit = "lux"
|
||||
break;
|
||||
case 5:
|
||||
map.name = "humidity"
|
||||
map.value = cmd.scaledSensorValue.toInteger().toString()
|
||||
map.unit = cmd.scale == 0 ? "%" : ""
|
||||
break;
|
||||
case 0x1E:
|
||||
map.name = "loudness"
|
||||
map.unit = cmd.scale == 1 ? "dBA" : "dB"
|
||||
map.value = cmd.scaledSensorValue.toString()
|
||||
break;
|
||||
default:
|
||||
map.descriptionText = cmd.toString()
|
||||
}
|
||||
result << createEvent(map)
|
||||
return result
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
|
||||
def result = []
|
||||
def manufacturerCode = String.format("%04X", cmd.manufacturerId)
|
||||
def productTypeCode = String.format("%04X", cmd.productTypeId)
|
||||
def productCode = String.format("%04X", cmd.productId)
|
||||
def wirelessConfig = "ZWP"
|
||||
log.debug "MSR ${manufacturerCode} ${productTypeCode} ${productCode}"
|
||||
|
||||
result << createEvent(name: "ManufacturerCode", value: manufacturerCode)
|
||||
result << createEvent(name: "ProduceTypeCode", value: productTypeCode)
|
||||
result << createEvent(name: "ProductCode", value: productCode)
|
||||
result << createEvent(name: "WirelessConfig", value: wirelessConfig)
|
||||
|
||||
if (manufacturerCode == "0109" && productTypeCode == "2002") {
|
||||
result << response(secureSequence([
|
||||
// Change re-trigger duration to 1 minute
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 1, configurationValue: [1], size: 1),
|
||||
zwave.batteryV1.batteryGet(),
|
||||
zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType:1, scale:1)
|
||||
], 400))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
||||
return [createEvent(descriptionText: "$device.displayName: $cmd", displayed: false)]
|
||||
}
|
||||
|
||||
private secure(physicalgraph.zwave.Command cmd) {
|
||||
if (state.sec == 0) { // default to secure
|
||||
cmd.format()
|
||||
} else {
|
||||
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
|
||||
}
|
||||
}
|
||||
|
||||
private secureSequence(commands, delay=200) {
|
||||
delayBetween(commands.collect{ secure(it) }, delay)
|
||||
}
|
||||
@@ -95,17 +95,11 @@ def zwaveEvent(physicalgraph.zwave.commands.hailv1.Hail cmd) {
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
|
||||
log.debug "manufacturerId: ${cmd.manufacturerId}"
|
||||
log.debug "manufacturerName: ${cmd.manufacturerName}"
|
||||
log.debug "productId: ${cmd.productId}"
|
||||
log.debug "productTypeId: ${cmd.productTypeId}"
|
||||
def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId)
|
||||
updateDataValue("MSR", msr)
|
||||
updateDataValue("manufacturer", cmd.manufacturerName)
|
||||
createEvent([descriptionText: "$device.displayName MSR: $msr", isStateChange: false])
|
||||
if (state.manufacturer != cmd.manufacturerName) {
|
||||
updateDataValue("manufacturer", cmd.manufacturerName)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
||||
// Handles all Z-Wave commands we aren't interested in
|
||||
[:]
|
||||
|
||||
@@ -1,252 +0,0 @@
|
||||
/**
|
||||
* Gidjit Hub
|
||||
*
|
||||
* Copyright 2016 Matthew Page
|
||||
*
|
||||
* 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: "Gidjit Hub",
|
||||
namespace: "com.gidjit.smartthings.hub",
|
||||
author: "Matthew Page",
|
||||
description: "Act as an endpoint so user's of Gidjit can quickly access and control their devices and execute routines. Users can do this quickly as Gidjit filters these actions based on their environment",
|
||||
category: "Convenience",
|
||||
iconUrl: "http://www.gidjit.com/appicon.png",
|
||||
iconX2Url: "http://www.gidjit.com/appicon@2x.png",
|
||||
iconX3Url: "http://www.gidjit.com/appicon@3x.png",
|
||||
oauth: [displayName: "Gidjit", displayLink: "www.gidjit.com"])
|
||||
|
||||
|
||||
|
||||
preferences {
|
||||
section ("Allow Gidjit to have access, there by allowing you to quickly control and monitor the following devices") {
|
||||
input "switches", "capability.switch", title: "Control/Monitor your switches", multiple: true, required: false
|
||||
input "thermostats", "capability.thermostat", title: "Control/Monitor your thermostats", multiple: true, required: false
|
||||
input "windowShades", "capability.windowShade", title: "Control/Monitor your window shades", multiple: true, required: false //windowShade
|
||||
//input "bulbs", "capability.colorControl", title: "Control your lights", multiple: true, required: false //windowShade
|
||||
|
||||
}
|
||||
}
|
||||
mappings {
|
||||
path("/structureinfo") {
|
||||
action: [
|
||||
GET: "structureInfo"
|
||||
]
|
||||
}
|
||||
path("/helloactions") {
|
||||
action: [
|
||||
GET: "helloActions"
|
||||
]
|
||||
}
|
||||
path("/helloactions/:label") {
|
||||
action: [
|
||||
PUT: "executeAction"
|
||||
]
|
||||
}
|
||||
|
||||
path("/switch/:id/:command") {
|
||||
action: [
|
||||
PUT: "updateSwitch"
|
||||
]
|
||||
}
|
||||
|
||||
path("/thermostat/:id/:command") {
|
||||
action: [
|
||||
PUT: "updateThermostat"
|
||||
]
|
||||
}
|
||||
|
||||
path("/windowshade/:id/:command") {
|
||||
action: [
|
||||
PUT: "updateWindowShade"
|
||||
]
|
||||
}
|
||||
path("/acquiredata/:id") {
|
||||
action: [
|
||||
GET: "acquiredata"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def installed() {
|
||||
log.debug "Installed with settings: ${settings}"
|
||||
|
||||
initialize()
|
||||
}
|
||||
|
||||
def updated() {
|
||||
log.debug "Updated with settings: ${settings}"
|
||||
|
||||
unsubscribe()
|
||||
initialize()
|
||||
}
|
||||
|
||||
def initialize() {
|
||||
// subscribe to attributes, devices, locations, etc.
|
||||
}
|
||||
def helloActions() {
|
||||
def actions = location.helloHome?.getPhrases()*.label
|
||||
if(!actions) {
|
||||
return []
|
||||
}
|
||||
return actions
|
||||
}
|
||||
def executeAction() {
|
||||
def actions = location.helloHome?.getPhrases()*.label
|
||||
def a = actions?.find() { it == params.label }
|
||||
if (!a) {
|
||||
httpError(400, "invalid label $params.label")
|
||||
return
|
||||
}
|
||||
location.helloHome?.execute(params.label)
|
||||
}
|
||||
/* this is the primary function called to query at the structure and its devices */
|
||||
def structureInfo() { //list all devices
|
||||
def list = [:]
|
||||
def currId = location.id
|
||||
list[currId] = [:]
|
||||
list[currId].name = location.name
|
||||
list[currId].id = location.id
|
||||
list[currId].temperatureScale = location.temperatureScale
|
||||
list[currId].devices = [:]
|
||||
|
||||
def setValues = {
|
||||
if (params.brief) {
|
||||
return [id: it.id, name: it.displayName]
|
||||
}
|
||||
def newList = [id: it.id, name: it.displayName, suppCapab: it.capabilities.collect {
|
||||
"$it.name"
|
||||
}, suppAttributes: it.supportedAttributes.collect {
|
||||
"$it.name"
|
||||
}, suppCommands: it.supportedCommands.collect {
|
||||
"$it.name"
|
||||
}]
|
||||
|
||||
return newList
|
||||
}
|
||||
switches?.each {
|
||||
list[currId].devices[it.id] = setValues(it)
|
||||
}
|
||||
thermostats?.each {
|
||||
list[currId].devices[it.id] = setValues(it)
|
||||
}
|
||||
windowShades?.each {
|
||||
list[currId].devices[it.id] = setValues(it)
|
||||
}
|
||||
|
||||
return list
|
||||
|
||||
}
|
||||
/* This function returns all of the current values of the specified Devices attributes */
|
||||
def acquiredata() {
|
||||
def resp = [:]
|
||||
if (!params.id) {
|
||||
httpError(400, "invalid id $params.id")
|
||||
return
|
||||
}
|
||||
def dev = switches.find() { it.id == params.id } ?: windowShades.find() { it.id == params.id } ?:
|
||||
thermostats.find() { it.id == params.id }
|
||||
|
||||
if (!dev) {
|
||||
httpError(400, "invalid id $params.id")
|
||||
return
|
||||
}
|
||||
def att = dev.supportedAttributes
|
||||
att.each {
|
||||
resp[it.name] = dev.currentValue("$it.name")
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
void updateSwitch() {
|
||||
// use the built-in request object to get the command parameter
|
||||
def command = params.command
|
||||
def sw = switches.find() { it.id == params.id }
|
||||
if (!sw) {
|
||||
httpError(400, "invalid id $params.id")
|
||||
return
|
||||
}
|
||||
switch(command) {
|
||||
case "on":
|
||||
if ( sw.currentSwitch != "on" ) {
|
||||
sw.on()
|
||||
}
|
||||
break
|
||||
case "off":
|
||||
if ( sw.currentSwitch != "off" ) {
|
||||
sw.off()
|
||||
}
|
||||
break
|
||||
default:
|
||||
httpError(400, "$command is not a valid")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void updateThermostat() {
|
||||
// use the built-in request object to get the command parameter
|
||||
def command = params.command
|
||||
def therm = thermostats.find() { it.id == params.id }
|
||||
if (!therm || !command) {
|
||||
httpError(400, "invalid id $params.id")
|
||||
return
|
||||
}
|
||||
def passComm = [
|
||||
"off",
|
||||
"heat",
|
||||
"emergencyHeat",
|
||||
"cool",
|
||||
"fanOn",
|
||||
"fanAuto",
|
||||
"fanCirculate",
|
||||
"auto"
|
||||
|
||||
]
|
||||
def passNumParamComm = [
|
||||
"setHeatingSetpoint",
|
||||
"setCoolingSetpoint",
|
||||
]
|
||||
def passStringParamComm = [
|
||||
"setThermostatMode",
|
||||
"setThermostatFanMode",
|
||||
]
|
||||
if (command in passComm) {
|
||||
therm."$command"()
|
||||
} else if (command in passNumParamComm && params.p1 && params.p1.isFloat()) {
|
||||
therm."$command"(Float.parseFloat(params.p1))
|
||||
} else if (command in passStringParamComm && params.p1) {
|
||||
therm."$command"(params.p1)
|
||||
} else {
|
||||
httpError(400, "$command is not a valid command")
|
||||
}
|
||||
}
|
||||
|
||||
void updateWindowShade() {
|
||||
// use the built-in request object to get the command parameter
|
||||
def command = params.command
|
||||
def ws = windowShades.find() { it.id == params.id }
|
||||
if (!ws || !command) {
|
||||
httpError(400, "invalid id $params.id")
|
||||
return
|
||||
}
|
||||
def passComm = [
|
||||
"open",
|
||||
"close",
|
||||
"presetPosition",
|
||||
]
|
||||
if (command in passComm) {
|
||||
ws."$command"()
|
||||
} else {
|
||||
httpError(400, "$command is not a valid command")
|
||||
}
|
||||
}
|
||||
// TODO: implement event handlers
|
||||
@@ -4,33 +4,29 @@
|
||||
import java.text.DecimalFormat
|
||||
import groovy.json.JsonSlurper
|
||||
|
||||
private getApiUrl() { "https://api.netatmo.com" }
|
||||
private getVendorName() { "netatmo" }
|
||||
private getVendorAuthPath() { "${apiUrl}/oauth2/authorize?" }
|
||||
private getVendorTokenPath(){ "${apiUrl}/oauth2/token" }
|
||||
private apiUrl() { "https://api.netatmo.com" }
|
||||
private getVendorName() { "netatmo" }
|
||||
private getVendorAuthPath() { "https://api.netatmo.com/oauth2/authorize?" }
|
||||
private getVendorTokenPath(){ "https://api.netatmo.com/oauth2/token" }
|
||||
private getVendorIcon() { "https://s3.amazonaws.com/smartapp-icons/Partner/netamo-icon-1%402x.png" }
|
||||
private getClientId() { appSettings.clientId }
|
||||
private getClientSecret() { appSettings.clientSecret }
|
||||
private getServerUrl() { appSettings.serverUrl }
|
||||
private getShardUrl() { return getApiServerUrl() }
|
||||
private getCallbackUrl() { "${serverUrl}/oauth/callback" }
|
||||
private getBuildRedirectUrl() { "${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${state.accessToken}&apiServerUrl=${shardUrl}" }
|
||||
private getClientId() { appSettings.clientId }
|
||||
private getClientSecret() { appSettings.clientSecret }
|
||||
private getServerUrl() { "https://graph.api.smartthings.com" }
|
||||
|
||||
// Automatically generated. Make future change here.
|
||||
definition(
|
||||
name: "Netatmo (Connect)",
|
||||
namespace: "dianoga",
|
||||
author: "Brian Steere",
|
||||
description: "Netatmo Integration",
|
||||
category: "SmartThings Labs",
|
||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/netamo-icon-1.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/netamo-icon-1%402x.png",
|
||||
oauth: true,
|
||||
singleInstance: true
|
||||
name: "Netatmo (Connect)",
|
||||
namespace: "dianoga",
|
||||
author: "Brian Steere",
|
||||
description: "Netatmo Integration",
|
||||
category: "SmartThings Labs",
|
||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/netamo-icon-1.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/netamo-icon-1%402x.png",
|
||||
oauth: true,
|
||||
singleInstance: true
|
||||
){
|
||||
appSetting "clientId"
|
||||
appSetting "clientSecret"
|
||||
appSetting "serverUrl"
|
||||
}
|
||||
|
||||
preferences {
|
||||
@@ -39,52 +35,35 @@ preferences {
|
||||
}
|
||||
|
||||
mappings {
|
||||
path("/oauth/initialize") {action: [GET: "oauthInitUrl"]}
|
||||
path("/oauth/callback") {action: [GET: "callback"]}
|
||||
path("/receivedToken"){action: [POST: "receivedToken", GET: "receivedToken"]}
|
||||
path("/receiveToken"){action: [POST: "receiveToken", GET: "receiveToken"]}
|
||||
path("/auth"){action: [GET: "auth"]}
|
||||
}
|
||||
|
||||
def authPage() {
|
||||
log.debug "In authPage"
|
||||
if(canInstallLabs()) {
|
||||
def description = null
|
||||
|
||||
def description
|
||||
def uninstallAllowed = false
|
||||
def oauthTokenProvided = false
|
||||
if (state.vendorAccessToken == null) {
|
||||
log.debug "About to create access token."
|
||||
|
||||
if (!state.accessToken) {
|
||||
log.debug "About to create access token."
|
||||
state.accessToken = createAccessToken()
|
||||
}
|
||||
createAccessToken()
|
||||
description = "Tap to enter Credentials."
|
||||
|
||||
if (canInstallLabs()) {
|
||||
|
||||
def redirectUrl = getBuildRedirectUrl()
|
||||
log.debug "Redirect url = ${redirectUrl}"
|
||||
|
||||
if (state.authToken) {
|
||||
return dynamicPage(name: "Credentials", title: "Authorize Connection", nextPage:"listDevices", uninstall: true, install:false) {
|
||||
section { href url:buildRedirectUrl("auth"), style:"embedded", required:false, title:"Connect to ${getVendorName()}:", description:description }
|
||||
}
|
||||
} else {
|
||||
description = "Tap 'Next' to proceed"
|
||||
uninstallAllowed = true
|
||||
oauthTokenProvided = true
|
||||
} else {
|
||||
description = "Click to enter Credentials."
|
||||
}
|
||||
|
||||
if (!oauthTokenProvided) {
|
||||
log.debug "Show the login page"
|
||||
return dynamicPage(name: "Credentials", title: "Authorize Connection", nextPage:"listDevices", uninstall: uninstallAllowed, install:false) {
|
||||
section() {
|
||||
paragraph "Tap below to log in to the netatmo and authorize SmartThings access."
|
||||
href url:redirectUrl, style:"embedded", required:false, title:"Connect to ${getVendorName()}:", description:description
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.debug "Show the devices page"
|
||||
return dynamicPage(name: "Credentials", title: "Credentials Accepted!", nextPage:"listDevices", uninstall: uninstallAllowed, install:false) {
|
||||
section() {
|
||||
input(name:"Devices", style:"embedded", required:false, title:"${getVendorName()} is now connected to SmartThings!", description:description)
|
||||
}
|
||||
return dynamicPage(name: "Credentials", title: "Credentials Accepted!", nextPage:"listDevices", uninstall: true, install:false) {
|
||||
section { href url: buildRedirectUrl("receivedToken"), style:"embedded", required:false, title:"${getVendorName()} is now connected to SmartThings!", description:description }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
def upgradeNeeded = """To use SmartThings Labs, your Hub should be completely up to date.
|
||||
|
||||
To update your Hub, access Location Settings in the Main Menu (tap the gear next to your location name), select your Hub, and choose "Update Hub"."""
|
||||
@@ -99,184 +78,44 @@ To update your Hub, access Location Settings in the Main Menu (tap the gear next
|
||||
}
|
||||
}
|
||||
|
||||
def auth() {
|
||||
redirect location: oauthInitUrl()
|
||||
}
|
||||
|
||||
def oauthInitUrl() {
|
||||
log.debug "In oauthInitUrl"
|
||||
|
||||
/* OAuth Step 1: Request access code with our client ID */
|
||||
|
||||
state.oauthInitState = UUID.randomUUID().toString()
|
||||
|
||||
def oauthParams = [
|
||||
response_type: "code",
|
||||
def oauthParams = [ response_type: "code",
|
||||
client_id: getClientId(),
|
||||
client_secret: getClientSecret(),
|
||||
state: state.oauthInitState,
|
||||
redirect_uri: getCallbackUrl(),
|
||||
redirect_uri: buildRedirectUrl("receiveToken") ,
|
||||
scope: "read_station"
|
||||
]
|
||||
|
||||
log.debug "REDIRECT URL: ${getVendorAuthPath() + toQueryString(oauthParams)}"
|
||||
|
||||
redirect (location: getVendorAuthPath() + toQueryString(oauthParams))
|
||||
}
|
||||
|
||||
def callback() {
|
||||
log.debug "callback()>> params: $params, params.code ${params.code}"
|
||||
|
||||
def code = params.code
|
||||
def oauthState = params.state
|
||||
|
||||
if (oauthState == state.oauthInitState) {
|
||||
|
||||
def tokenParams = [
|
||||
client_secret: getClientSecret(),
|
||||
client_id : getClientId(),
|
||||
grant_type: "authorization_code",
|
||||
redirect_uri: getCallbackUrl(),
|
||||
code: code,
|
||||
scope: "read_station"
|
||||
]
|
||||
|
||||
log.debug "TOKEN URL: ${getVendorTokenPath() + toQueryString(tokenParams)}"
|
||||
|
||||
def tokenUrl = getVendorTokenPath()
|
||||
def params = [
|
||||
uri: tokenUrl,
|
||||
contentType: 'application/x-www-form-urlencoded',
|
||||
body: tokenParams
|
||||
]
|
||||
|
||||
log.debug "PARAMS: ${params}"
|
||||
|
||||
httpPost(params) { resp ->
|
||||
|
||||
def slurper = new JsonSlurper()
|
||||
|
||||
resp.data.each { key, value ->
|
||||
def data = slurper.parseText(key)
|
||||
|
||||
state.refreshToken = data.refresh_token
|
||||
state.authToken = data.access_token
|
||||
state.tokenExpires = now() + (data.expires_in * 1000)
|
||||
log.debug "swapped token: $resp.data"
|
||||
}
|
||||
}
|
||||
|
||||
// Handle success and failure here, and render stuff accordingly
|
||||
if (state.authToken) {
|
||||
success()
|
||||
} else {
|
||||
fail()
|
||||
}
|
||||
|
||||
} else {
|
||||
log.error "callback() failed oauthState != state.oauthInitState"
|
||||
}
|
||||
return getVendorAuthPath() + toQueryString(oauthParams)
|
||||
}
|
||||
|
||||
def success() {
|
||||
log.debug "in success"
|
||||
def message = """
|
||||
<p>We have located your """ + getVendorName() + """ account.</p>
|
||||
<p>Tap 'Done' to continue to Devices.</p>
|
||||
"""
|
||||
connectionStatus(message)
|
||||
def buildRedirectUrl(endPoint) {
|
||||
log.debug "In buildRedirectUrl"
|
||||
|
||||
return getServerUrl() + "/api/token/${state.accessToken}/smartapps/installations/${app.id}/${endPoint}"
|
||||
}
|
||||
|
||||
def fail() {
|
||||
log.debug "in fail"
|
||||
def message = """
|
||||
<p>The connection could not be established!</p>
|
||||
<p>Click 'Done' to return to the menu.</p>
|
||||
"""
|
||||
connectionStatus(message)
|
||||
}
|
||||
|
||||
def connectionStatus(message, redirectUrl = null) {
|
||||
def redirectHtml = ""
|
||||
if (redirectUrl) {
|
||||
redirectHtml = """
|
||||
<meta http-equiv="refresh" content="3; url=${redirectUrl}" />
|
||||
"""
|
||||
}
|
||||
|
||||
def html = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>${getVendorName()} Connection</title>
|
||||
<style type="text/css">
|
||||
* { box-sizing: border-box; }
|
||||
@font-face {
|
||||
font-family: 'Swiss 721 W01 Thin';
|
||||
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot');
|
||||
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot?#iefix') format('embedded-opentype'),
|
||||
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.woff') format('woff'),
|
||||
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.ttf') format('truetype'),
|
||||
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.svg#swis721_th_btthin') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Swiss 721 W01 Light';
|
||||
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot');
|
||||
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot?#iefix') format('embedded-opentype'),
|
||||
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.woff') format('woff'),
|
||||
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.ttf') format('truetype'),
|
||||
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.svg#swis721_lt_btlight') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
.container {
|
||||
width: 100%;
|
||||
padding: 40px;
|
||||
/*background: #eee;*/
|
||||
text-align: center;
|
||||
}
|
||||
img {
|
||||
vertical-align: middle;
|
||||
}
|
||||
img:nth-child(2) {
|
||||
margin: 0 30px;
|
||||
}
|
||||
p {
|
||||
font-size: 2.2em;
|
||||
font-family: 'Swiss 721 W01 Thin';
|
||||
text-align: center;
|
||||
color: #666666;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
/*
|
||||
p:last-child {
|
||||
margin-top: 0px;
|
||||
}
|
||||
*/
|
||||
span {
|
||||
font-family: 'Swiss 721 W01 Light';
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<img src=""" + getVendorIcon() + """ alt="Vendor icon" />
|
||||
<img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/connected-device-icn%402x.png" alt="connected device icon" />
|
||||
<img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/st-logo%402x.png" alt="SmartThings logo" />
|
||||
${message}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
render contentType: 'text/html', data: html
|
||||
}
|
||||
|
||||
def refreshToken() {
|
||||
log.debug "In refreshToken"
|
||||
def receiveToken() {
|
||||
log.debug "In receiveToken"
|
||||
|
||||
def oauthParams = [
|
||||
client_secret: getClientSecret(),
|
||||
client_id: getClientId(),
|
||||
grant_type: "refresh_token",
|
||||
refresh_token: state.refreshToken
|
||||
]
|
||||
grant_type: "authorization_code",
|
||||
redirect_uri: buildRedirectUrl('receiveToken'),
|
||||
code: params.code,
|
||||
scope: "read_station"
|
||||
]
|
||||
|
||||
def tokenUrl = getVendorTokenPath()
|
||||
def params = [
|
||||
@@ -285,7 +124,201 @@ def refreshToken() {
|
||||
body: oauthParams,
|
||||
]
|
||||
|
||||
// OAuth Step 2: Request access token with our client Secret and OAuth "Code"
|
||||
log.debug params
|
||||
|
||||
/* OAuth Step 2: Request access token with our client Secret and OAuth "Code" */
|
||||
try {
|
||||
httpPost(params) { response ->
|
||||
log.debug response.data
|
||||
def slurper = new JsonSlurper();
|
||||
|
||||
response.data.each {key, value ->
|
||||
def data = slurper.parseText(key);
|
||||
log.debug "Data: $data"
|
||||
|
||||
state.vendorRefreshToken = data.refresh_token
|
||||
state.vendorAccessToken = data.access_token
|
||||
state.vendorTokenExpires = now() + (data.expires_in * 1000)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.debug "Error: $e"
|
||||
}
|
||||
|
||||
log.debug "State: $state"
|
||||
|
||||
if ( !state.vendorAccessToken ) { //We didn't get an access token, bail on install
|
||||
return
|
||||
}
|
||||
|
||||
/* OAuth Step 3: Use the access token to call into the vendor API throughout your code using state.vendorAccessToken. */
|
||||
|
||||
def html = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>${getVendorName()} Connection</title>
|
||||
<style type="text/css">
|
||||
* { box-sizing: border-box; }
|
||||
@font-face {
|
||||
font-family: 'Swiss 721 W01 Thin';
|
||||
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot');
|
||||
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot?#iefix') format('embedded-opentype'),
|
||||
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.woff') format('woff'),
|
||||
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.ttf') format('truetype'),
|
||||
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.svg#swis721_th_btthin') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Swiss 721 W01 Light';
|
||||
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot');
|
||||
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot?#iefix') format('embedded-opentype'),
|
||||
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.woff') format('woff'),
|
||||
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.ttf') format('truetype'),
|
||||
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.svg#swis721_lt_btlight') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
.container {
|
||||
width: 100%;
|
||||
padding: 40px;
|
||||
/*background: #eee;*/
|
||||
text-align: center;
|
||||
}
|
||||
img {
|
||||
vertical-align: middle;
|
||||
}
|
||||
img:nth-child(2) {
|
||||
margin: 0 30px;
|
||||
}
|
||||
p {
|
||||
font-size: 2.2em;
|
||||
font-family: 'Swiss 721 W01 Thin';
|
||||
text-align: center;
|
||||
color: #666666;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
/*
|
||||
p:last-child {
|
||||
margin-top: 0px;
|
||||
}
|
||||
*/
|
||||
span {
|
||||
font-family: 'Swiss 721 W01 Light';
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<img src=""" + getVendorIcon() + """ alt="Vendor icon" />
|
||||
<img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/connected-device-icn%402x.png" alt="connected device icon" />
|
||||
<img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/st-logo%402x.png" alt="SmartThings logo" />
|
||||
<p>We have located your """ + getVendorName() + """ account.</p>
|
||||
<p>Tap 'Done' to process your credentials.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
render contentType: 'text/html', data: html
|
||||
}
|
||||
|
||||
def receivedToken() {
|
||||
log.debug "In receivedToken"
|
||||
|
||||
def html = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Withings Connection</title>
|
||||
<style type="text/css">
|
||||
* { box-sizing: border-box; }
|
||||
@font-face {
|
||||
font-family: 'Swiss 721 W01 Thin';
|
||||
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot');
|
||||
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot?#iefix') format('embedded-opentype'),
|
||||
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.woff') format('woff'),
|
||||
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.ttf') format('truetype'),
|
||||
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.svg#swis721_th_btthin') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Swiss 721 W01 Light';
|
||||
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot');
|
||||
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot?#iefix') format('embedded-opentype'),
|
||||
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.woff') format('woff'),
|
||||
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.ttf') format('truetype'),
|
||||
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.svg#swis721_lt_btlight') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
.container {
|
||||
width: 560px;
|
||||
padding: 40px;
|
||||
/*background: #eee;*/
|
||||
text-align: center;
|
||||
}
|
||||
img {
|
||||
vertical-align: middle;
|
||||
}
|
||||
img:nth-child(2) {
|
||||
margin: 0 30px;
|
||||
}
|
||||
p {
|
||||
font-size: 2.2em;
|
||||
font-family: 'Swiss 721 W01 Thin';
|
||||
text-align: center;
|
||||
color: #666666;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
/*
|
||||
p:last-child {
|
||||
margin-top: 0px;
|
||||
}
|
||||
*/
|
||||
span {
|
||||
font-family: 'Swiss 721 W01 Light';
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<img src=""" + getVendorIcon() + """ alt="Vendor icon" />
|
||||
<img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/connected-device-icn%402x.png" alt="connected device icon" />
|
||||
<img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/st-logo%402x.png" alt="SmartThings logo" />
|
||||
<p>Tap 'Done' to continue to Devices.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
render contentType: 'text/html', data: html
|
||||
}
|
||||
|
||||
// "
|
||||
|
||||
def refreshToken() {
|
||||
log.debug "In refreshToken"
|
||||
|
||||
def oauthParams = [
|
||||
client_secret: getClientSecret(),
|
||||
client_id: getClientId(),
|
||||
grant_type: "refresh_token",
|
||||
refresh_token: state.vendorRefreshToken
|
||||
]
|
||||
|
||||
def tokenUrl = getVendorTokenPath()
|
||||
def params = [
|
||||
uri: tokenUrl,
|
||||
contentType: 'application/x-www-form-urlencoded',
|
||||
body: oauthParams,
|
||||
]
|
||||
|
||||
/* OAuth Step 2: Request access token with our client Secret and OAuth "Code" */
|
||||
try {
|
||||
httpPost(params) { response ->
|
||||
def slurper = new JsonSlurper();
|
||||
@@ -294,9 +327,9 @@ def refreshToken() {
|
||||
def data = slurper.parseText(key);
|
||||
log.debug "Data: $data"
|
||||
|
||||
state.refreshToken = data.refresh_token
|
||||
state.accessToken = data.access_token
|
||||
state.tokenExpires = now() + (data.expires_in * 1000)
|
||||
state.vendorRefreshToken = data.refresh_token
|
||||
state.vendorAccessToken = data.access_token
|
||||
state.vendorTokenExpires = now() + (data.expires_in * 1000)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -305,8 +338,9 @@ def refreshToken() {
|
||||
log.debug "Error: $e"
|
||||
}
|
||||
|
||||
// We didn't get an access token
|
||||
if ( !state.accessToken ) {
|
||||
log.debug "State: $state"
|
||||
|
||||
if ( !state.vendorAccessToken ) { //We didn't get an access token
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -337,10 +371,10 @@ def initialize() {
|
||||
|
||||
settings.devices.each {
|
||||
def deviceId = it
|
||||
def detail = state?.deviceDetail[deviceId]
|
||||
def detail = state.deviceDetail[deviceId]
|
||||
|
||||
try {
|
||||
switch(detail?.type) {
|
||||
switch(detail.type) {
|
||||
case 'NAMain':
|
||||
log.debug "Base station"
|
||||
createChildDevice("Netatmo Basestation", deviceId, "${detail.type}.${deviceId}", detail.module_name)
|
||||
@@ -448,13 +482,13 @@ def listDevices() {
|
||||
}
|
||||
|
||||
def apiGet(String path, Map query, Closure callback) {
|
||||
if(now() >= state.tokenExpires) {
|
||||
if(now() >= state.vendorTokenExpires) {
|
||||
refreshToken();
|
||||
}
|
||||
|
||||
query['access_token'] = state.accessToken
|
||||
query['access_token'] = state.vendorAccessToken
|
||||
def params = [
|
||||
uri: getApiUrl(),
|
||||
uri: apiUrl(),
|
||||
path: path,
|
||||
'query': query
|
||||
]
|
||||
@@ -487,12 +521,12 @@ def poll() {
|
||||
log.debug "State: ${state.deviceState}"
|
||||
|
||||
settings.devices.each { deviceId ->
|
||||
def detail = state?.deviceDetail[deviceId]
|
||||
def data = state?.deviceState[deviceId]
|
||||
def child = children?.find { it.deviceNetworkId == deviceId }
|
||||
def detail = state.deviceDetail[deviceId]
|
||||
def data = state.deviceState[deviceId]
|
||||
def child = children.find { it.deviceNetworkId == deviceId }
|
||||
|
||||
log.debug "Update: $child";
|
||||
switch(detail?.type) {
|
||||
switch(detail.type) {
|
||||
case 'NAMain':
|
||||
log.debug "Updating NAMain $data"
|
||||
child?.sendEvent(name: 'temperature', value: cToPref(data['Temperature']) as float, unit: getTemperatureScale())
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
definition(
|
||||
name: "Fibaro Dimmer Scenes",
|
||||
namespace: "My Apps",
|
||||
author: "Elnar Hajiyev",
|
||||
description: "Smart app that allows to control switches with changes of Fibaro Dimmer Scenes.",
|
||||
category: "Convenience",
|
||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Meta/light_outlet.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Meta/light_outlet@2x.png"
|
||||
)
|
||||
|
||||
preferences {
|
||||
section("Listen to scenes from this Fibaro Dimmer devices...") {
|
||||
input "fibaroDevicesSet", "capability.switch", title: "Fibaro Dimmers?", multiple: true, required: true
|
||||
}
|
||||
section("When any of these scenes are activated...") {
|
||||
input "scenesSet1", "enum", title: "Fibaro Dimmer scenes (set 1)?", multiple: true, required: false,
|
||||
options: ["10","11","12","13", "14", "15", "16", "17", "18", "20", "21", "22", "23", "24", "25", "26"]
|
||||
}
|
||||
section("Turn on all of these switches") {
|
||||
input "switchesSet1", "capability.switch", multiple: true, required: false
|
||||
}
|
||||
section("When any of these scenes are activated...") {
|
||||
input "scenesSet2", "enum", title: "Fibaro Dimmer scenes (set 2)?", multiple: true, required: false,
|
||||
options: ["10","11","12","13", "14", "15", "16", "17", "18", "20", "21", "22", "23", "24", "25", "26"]
|
||||
}
|
||||
section("Turn off all of these switches") {
|
||||
input "switchesSet2", "capability.switch", multiple: true, required: false
|
||||
}
|
||||
section("When any of these scenes are activated...") {
|
||||
input "scenesSet3", "enum", title: "Fibaro Dimmer scenes (set 3)?", multiple: true, required: false,
|
||||
options: ["10","11","12","13", "14", "15", "16", "17", "18", "20", "21", "22", "23", "24", "25", "26"]
|
||||
}
|
||||
section("Toggle all of these switches") {
|
||||
input "switchesSet3", "capability.switch", multiple: true, required: false
|
||||
}
|
||||
}
|
||||
|
||||
def installed()
|
||||
{
|
||||
subscribe(fibaroDevicesSet, "scene", sceneHandler, [filterEvents: false])
|
||||
}
|
||||
|
||||
def updated()
|
||||
{
|
||||
unsubscribe()
|
||||
subscribe(fibaroDevicesSet, "scene", sceneHandler, [filterEvents: false])
|
||||
}
|
||||
|
||||
def sceneHandler(evt) {
|
||||
log.debug evt.value
|
||||
log.debug evt.data
|
||||
|
||||
if(scenesSet1 && scenesSet1.contains(evt.value)) {
|
||||
switchesSet1.on()
|
||||
}
|
||||
else if(scenesSet2 && scenesSet2.contains(evt.value)) {
|
||||
switchesSet2.off()
|
||||
}
|
||||
else if(scenesSet3 && scenesSet3.contains(evt.value)) {
|
||||
toggle(switchesSet3)
|
||||
}
|
||||
}
|
||||
|
||||
def toggle(devices) {
|
||||
log.debug "toggle: $devices = ${devices*.currentValue('switch')}"
|
||||
|
||||
if (devices*.currentValue('switch').contains('off')) {
|
||||
devices.on()
|
||||
}
|
||||
else if (devices*.currentValue('switch').contains('on')) {
|
||||
devices.off()
|
||||
}
|
||||
else {
|
||||
devices.on()
|
||||
}
|
||||
}
|
||||
@@ -370,7 +370,9 @@ def parse_api_response(resp, message) {
|
||||
}
|
||||
}
|
||||
|
||||
def getServerUrl() { return getApiServerUrl() }
|
||||
def getServerUrl() {
|
||||
return "https://graph.api.smartthings.com"
|
||||
}
|
||||
|
||||
def debugEvent(message, displayEvent) {
|
||||
def results = [
|
||||
|
||||
@@ -21,12 +21,6 @@ preferences()
|
||||
section("Allow Simple Control to Monitor and Control These Things...")
|
||||
{
|
||||
input "switches", "capability.switch", title: "Which Switches?", multiple: true, required: false
|
||||
input "locks", "capability.lock", title: "Which Locks?", multiple: true, required: false
|
||||
input "thermostats", "capability.thermostat", title: "Which Thermostats?", multiple: true, required: false
|
||||
input "doorControls", "capability.doorControl", title: "Which Door Controls?", multiple: true, required: false
|
||||
input "colorControls", "capability.colorControl", title: "Which Color Controllers?", multiple: true, required: false
|
||||
input "musicPlayers", "capability.musicPlayer", title: "Which Music Players?", multiple: true, required: false
|
||||
input "switchLevels", "capability.switchLevel", title: "Which Adjustable Switches?", multiple: true, required: false
|
||||
}
|
||||
|
||||
page(name: "mainPage", title: "Simple Control Setup", content: "mainPage", refreshTimeout: 5)
|
||||
@@ -37,17 +31,12 @@ preferences()
|
||||
|
||||
mappings {
|
||||
path("/devices") {
|
||||
action: [
|
||||
GET: "getDevices"
|
||||
]
|
||||
}
|
||||
path("/:deviceType/devices") {
|
||||
action: [
|
||||
GET: "getDevices",
|
||||
POST: "handleDevicesWithIDs"
|
||||
]
|
||||
}
|
||||
path("/device/:deviceType/:id") {
|
||||
}
|
||||
path("/device/:id") {
|
||||
action: [
|
||||
GET: "getDevice",
|
||||
POST: "updateDevice"
|
||||
@@ -104,40 +93,33 @@ def handleDevicesWithIDs()
|
||||
//log.debug("ids: ${ids}")
|
||||
def command = data?.command
|
||||
def arguments = data?.arguments
|
||||
def type = params?.deviceType
|
||||
//log.debug("device type: ${type}")
|
||||
if (command)
|
||||
{
|
||||
def statusCode = 404
|
||||
def success = false
|
||||
//log.debug("command ${command}, arguments ${arguments}")
|
||||
for (devId in ids)
|
||||
{
|
||||
def device = allDevices.find { it.id == devId }
|
||||
//log.debug("device: ${device}")
|
||||
// Check if we have a device that responds to the specified command
|
||||
if (validateCommand(device, type, command)) {
|
||||
if (arguments) {
|
||||
if (device) {
|
||||
if (arguments) {
|
||||
device."$command"(*arguments)
|
||||
}
|
||||
else {
|
||||
device."$command"()
|
||||
}
|
||||
statusCode = 200
|
||||
} else {
|
||||
device."$command"()
|
||||
}
|
||||
success = true
|
||||
} else {
|
||||
statusCode = 403
|
||||
//log.debug("device not found ${devId}")
|
||||
}
|
||||
}
|
||||
def responseData = "{}"
|
||||
switch (statusCode)
|
||||
|
||||
if (success)
|
||||
{
|
||||
case 403:
|
||||
responseData = '{"msg": "Access denied. This command is not supported by current capability."}'
|
||||
break
|
||||
case 404:
|
||||
responseData = '{"msg": "Device not found"}'
|
||||
break
|
||||
render status: 200, data: "{}"
|
||||
}
|
||||
else
|
||||
{
|
||||
render status: 404, data: '{"msg": "Device not found"}'
|
||||
}
|
||||
render status: statusCode, data: responseData
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -182,101 +164,25 @@ def updateDevice()
|
||||
def data = request.JSON
|
||||
def command = data?.command
|
||||
def arguments = data?.arguments
|
||||
def type = params?.deviceType
|
||||
//log.debug("device type: ${type}")
|
||||
|
||||
//log.debug("updateDevice, params: ${params}, request: ${data}")
|
||||
if (!command) {
|
||||
render status: 400, data: '{"msg": "command is required"}'
|
||||
} else {
|
||||
def statusCode = 404
|
||||
def device = allDevices.find { it.id == params.id }
|
||||
if (device) {
|
||||
// Check if we have a device that responds to the specified command
|
||||
if (validateCommand(device, type, command)) {
|
||||
if (arguments) {
|
||||
device."$command"(*arguments)
|
||||
}
|
||||
else {
|
||||
device."$command"()
|
||||
}
|
||||
statusCode = 200
|
||||
if (arguments) {
|
||||
device."$command"(*arguments)
|
||||
} else {
|
||||
statusCode = 403
|
||||
device."$command"()
|
||||
}
|
||||
render status: 204, data: "{}"
|
||||
} else {
|
||||
render status: 404, data: '{"msg": "Device not found"}'
|
||||
}
|
||||
|
||||
def responseData = "{}"
|
||||
switch (statusCode)
|
||||
{
|
||||
case 403:
|
||||
responseData = '{"msg": "Access denied. This command is not supported by current capability."}'
|
||||
break
|
||||
case 404:
|
||||
responseData = '{"msg": "Device not found"}'
|
||||
break
|
||||
}
|
||||
render status: statusCode, data: responseData
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validating the command passed by the user based on capability.
|
||||
* @return boolean
|
||||
*/
|
||||
def validateCommand(device, deviceType, command) {
|
||||
//log.debug("validateCommand ${command}")
|
||||
def capabilityCommands = getDeviceCapabilityCommands(device.capabilities)
|
||||
//log.debug("capabilityCommands: ${capabilityCommands}")
|
||||
def currentDeviceCapability = getCapabilityName(deviceType)
|
||||
//log.debug("currentDeviceCapability: ${currentDeviceCapability}")
|
||||
if (capabilityCommands[currentDeviceCapability]) {
|
||||
return command in capabilityCommands[currentDeviceCapability] ? true : false
|
||||
} else {
|
||||
// Handling other device types here, which don't accept commands
|
||||
httpError(400, "Bad request.")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Need to get the attribute name to do the lookup. Only
|
||||
* doing it for the device types which accept commands
|
||||
* @return attribute name of the device type
|
||||
*/
|
||||
def getCapabilityName(type) {
|
||||
switch(type) {
|
||||
case "switches":
|
||||
return "Switch"
|
||||
case "locks":
|
||||
return "Lock"
|
||||
case "thermostats":
|
||||
return "Thermostat"
|
||||
case "doorControls":
|
||||
return "Door Control"
|
||||
case "colorControls":
|
||||
return "Color Control"
|
||||
case "musicPlayers":
|
||||
return "Music Player"
|
||||
case "switchLevels":
|
||||
return "Switch Level"
|
||||
default:
|
||||
return type
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructing the map over here of
|
||||
* supported commands by device capability
|
||||
* @return a map of device capability -> supported commands
|
||||
*/
|
||||
def getDeviceCapabilityCommands(deviceCapabilities) {
|
||||
def map = [:]
|
||||
deviceCapabilities.collect {
|
||||
map[it.name] = it.commands.collect{ it.name.toString() }
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
def listSubscriptions()
|
||||
{
|
||||
//log.debug "listSubscriptions()"
|
||||
@@ -455,13 +361,7 @@ def agentDiscovery(params=[:])
|
||||
}
|
||||
section("Allow Simple Control to Monitor and Control These Things...")
|
||||
{
|
||||
input "switches", "capability.switch", title: "Which Switches?", multiple: true, required: false
|
||||
input "locks", "capability.lock", title: "Which Locks?", multiple: true, required: false
|
||||
input "thermostats", "capability.thermostat", title: "Which Thermostats?", multiple: true, required: false
|
||||
input "doorControls", "capability.doorControl", title: "Which Door Controls?", multiple: true, required: false
|
||||
input "colorControls", "capability.colorControl", title: "Which Color Controllers?", multiple: true, required: false
|
||||
input "musicPlayers", "capability.musicPlayer", title: "Which Music Players?", multiple: true, required: false
|
||||
input "switchLevels", "capability.switchLevel", title: "Which Adjustable Switches?", multiple: true, required: false
|
||||
input "switches", "capability.switch", title: "Which Switches?", multiple: true, required: false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -772,3 +672,5 @@ def List getRealHubFirmwareVersions()
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -297,8 +297,8 @@ private getTimeOk() {
|
||||
def result = true
|
||||
if (starting && ending) {
|
||||
def currTime = now()
|
||||
def start = timeToday(starting, location.timeZone).time
|
||||
def stop = timeToday(ending, location.timeZone).time
|
||||
def start = timeToday(starting).time
|
||||
def stop = timeToday(ending).time
|
||||
result = start < stop ? currTime >= start && currTime <= stop : currTime <= stop || currTime >= start
|
||||
}
|
||||
log.trace "timeOk = $result"
|
||||
|
||||
@@ -39,7 +39,6 @@ preferences {
|
||||
page(name: "completionPage")
|
||||
page(name: "numbersPage")
|
||||
page(name: "controllerExplanationPage")
|
||||
page(name: "unsupportedDevicesPage")
|
||||
}
|
||||
|
||||
def rootPage() {
|
||||
@@ -48,9 +47,6 @@ def rootPage() {
|
||||
section("What to dim") {
|
||||
input(name: "dimmers", type: "capability.switchLevel", title: "Dimmers", description: null, multiple: true, required: true, submitOnChange: true)
|
||||
if (dimmers) {
|
||||
if (dimmersContainUnsupportedDevices()) {
|
||||
href(name: "toUnsupportedDevicesPage", page: "unsupportedDevicesPage", title: "Some of your selected dimmers don't seem to be supported", description: "Tap here to fix it", required: true)
|
||||
}
|
||||
href(name: "toNumbersPage", page: "numbersPage", title: "Duration & Direction", description: numbersPageHrefDescription(), state: "complete")
|
||||
}
|
||||
}
|
||||
@@ -75,31 +71,6 @@ def rootPage() {
|
||||
}
|
||||
}
|
||||
|
||||
def unsupportedDevicesPage() {
|
||||
|
||||
def unsupportedDimmers = dimmers.findAll { !hasSetLevelCommand(it) }
|
||||
|
||||
dynamicPage(name: "unsupportedDevicesPage") {
|
||||
if (unsupportedDimmers) {
|
||||
section("These devices do not support the setLevel command") {
|
||||
unsupportedDimmers.each {
|
||||
paragraph deviceLabel(it)
|
||||
}
|
||||
}
|
||||
section {
|
||||
input(name: "dimmers", type: "capability.sensor", title: "Please remove the above devices from this list.", submitOnChange: true, multiple: true)
|
||||
}
|
||||
section {
|
||||
paragraph "If you think there is a mistake here, please contact support."
|
||||
}
|
||||
} else {
|
||||
section {
|
||||
paragraph "You're all set. You can hit the back button, now. Thanks for cleaning up your settings :)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def controllerExplanationPage() {
|
||||
dynamicPage(name: "controllerExplanationPage", title: "How To Control Gentle Wake Up") {
|
||||
|
||||
@@ -557,16 +528,14 @@ def updateDimmers(percentComplete) {
|
||||
} else {
|
||||
|
||||
def shouldChangeColors = (colorize && colorize != "false")
|
||||
def canChangeColors = hasSetColorCommand(dimmer)
|
||||
|
||||
if (shouldChangeColors && hasSetColorCommand(dimmer)) {
|
||||
def hue = getHue(dimmer, nextLevel)
|
||||
log.debug "Setting ${deviceLabel(dimmer)} level to ${nextLevel} and hue to ${hue}"
|
||||
dimmer.setColor([hue: hue, saturation: 100, level: nextLevel])
|
||||
} else if (hasSetLevelCommand(dimmer)) {
|
||||
log.debug "Setting ${deviceLabel(dimmer)} level to ${nextLevel}"
|
||||
dimmer.setLevel(nextLevel)
|
||||
log.debug "Setting ${deviceLabel(dimmer)} to ${nextLevel}"
|
||||
|
||||
if (shouldChangeColors && canChangeColors) {
|
||||
dimmer.setColor([hue: getHue(dimmer, nextLevel), saturation: 100, level: nextLevel])
|
||||
} else {
|
||||
log.warn "${deviceLabel(dimmer)} does not have setColor or setLevel commands."
|
||||
dimmer.setLevel(nextLevel)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -720,7 +689,7 @@ def completionPercentage() {
|
||||
|
||||
def now = new Date().getTime()
|
||||
def timeElapsed = now - atomicState.start
|
||||
def totalRunTime = totalRunTimeMillis() ?: 1
|
||||
def totalRunTime = totalRunTimeMillis()
|
||||
def percentComplete = timeElapsed / totalRunTime * 100
|
||||
log.debug "percentComplete: ${percentComplete}"
|
||||
|
||||
@@ -848,21 +817,24 @@ private getRedHue(level) {
|
||||
if (level >= 96) return 17
|
||||
}
|
||||
|
||||
private dimmersContainUnsupportedDevices() {
|
||||
def found = dimmers.find { hasSetLevelCommand(it) == false }
|
||||
return found != null
|
||||
}
|
||||
|
||||
private hasSetLevelCommand(device) {
|
||||
return hasCommand(device, "setLevel")
|
||||
def isDimmer = false
|
||||
device.supportedCommands.each {
|
||||
if (it.name.contains("setLevel")) {
|
||||
isDimmer = true
|
||||
}
|
||||
}
|
||||
return isDimmer
|
||||
}
|
||||
|
||||
private hasSetColorCommand(device) {
|
||||
return hasCommand(device, "setColor")
|
||||
}
|
||||
|
||||
private hasCommand(device, String command) {
|
||||
return (device.supportedCommands.find { it.name == command } != null)
|
||||
def hasColor = false
|
||||
device.supportedCommands.each {
|
||||
if (it.name.contains("setColor")) {
|
||||
hasColor = true
|
||||
}
|
||||
}
|
||||
return hasColor
|
||||
}
|
||||
|
||||
private dimmersWithSetColorCommand() {
|
||||
@@ -1101,4 +1073,4 @@ def hasStartLevel() {
|
||||
|
||||
def hasEndLevel() {
|
||||
return (endLevel != null && endLevel != "")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ definition(
|
||||
preferences {
|
||||
page(name:"mainPage", title:"Hue Device Setup", content:"mainPage", refreshTimeout:5)
|
||||
page(name:"bridgeDiscovery", title:"Hue Bridge Discovery", content:"bridgeDiscovery", refreshTimeout:5)
|
||||
page(name:"bridgeDiscoveryFailed", title:"Bridge Discovery Failed", content:"bridgeDiscoveryFailed", refreshTimeout:0)
|
||||
page(name:"bridgeBtnPush", title:"Linking with your Hue", content:"bridgeLinking", refreshTimeout:5)
|
||||
page(name:"bulbDiscovery", title:"Hue Device Setup", content:"bulbDiscovery", refreshTimeout:5)
|
||||
}
|
||||
@@ -54,21 +53,12 @@ def bridgeDiscovery(params=[:])
|
||||
def options = bridges ?: []
|
||||
def numFound = options.size() ?: 0
|
||||
|
||||
if (numFound == 0) {
|
||||
if (state.bridgeRefreshCount == 25) {
|
||||
log.trace "Cleaning old bridges memory"
|
||||
state.bridges = [:]
|
||||
app.updateSetting("selectedHue", "")
|
||||
} else if (state.bridgeRefreshCount > 100) {
|
||||
// five minutes have passed, give up
|
||||
// there seems to be a problem going back from discovey failed page in some instances (compared to pressing next)
|
||||
// however it is probably a SmartThings settings issue
|
||||
state.bridges = [:]
|
||||
app.updateSetting("selectedHue", "")
|
||||
state.bridgeRefreshCount = 0
|
||||
return bridgeDiscoveryFailed()
|
||||
}
|
||||
}
|
||||
if (numFound == 0 && state.bridgeRefreshCount > 25) {
|
||||
log.trace "Cleaning old bridges memory"
|
||||
state.bridges = [:]
|
||||
state.bridgeRefreshCount = 0
|
||||
app.updateSetting("selectedHue", "")
|
||||
}
|
||||
|
||||
ssdpSubscribe()
|
||||
|
||||
@@ -89,13 +79,6 @@ def bridgeDiscovery(params=[:])
|
||||
}
|
||||
}
|
||||
|
||||
def bridgeDiscoveryFailed() {
|
||||
return dynamicPage(name:"bridgeDiscoveryFailed", title: "Bridge Discovery Failed", nextPage: "bridgeDiscovery") {
|
||||
section("Failed to discover any Hue Bridges. Please confirm that the Hue Bridge is connected to the same network as your SmartThings Hub, and that it has power.") {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def bridgeLinking()
|
||||
{
|
||||
int linkRefreshcount = !state.linkRefreshcount ? 0 : state.linkRefreshcount as int
|
||||
@@ -105,15 +88,19 @@ def bridgeLinking()
|
||||
def nextPage = ""
|
||||
def title = "Linking with your Hue"
|
||||
def paragraphText
|
||||
def hueimage = null
|
||||
if (selectedHue) {
|
||||
paragraphText = "Press the button on your Hue Bridge to setup a link. "
|
||||
hueimage = "http://huedisco.mediavibe.nl/wp-content/uploads/2013/09/pair-bridge.png"
|
||||
} else {
|
||||
paragraphText = "You haven't selected a Hue Bridge, please Press \"Done\" and select one before clicking next."
|
||||
hueimage = null
|
||||
}
|
||||
if (state.username) { //if discovery worked
|
||||
nextPage = "bulbDiscovery"
|
||||
title = "Success!"
|
||||
paragraphText = "Linking to your hub was a success! Please click 'Next'!"
|
||||
hueimage = null
|
||||
}
|
||||
|
||||
if((linkRefreshcount % 2) == 0 && !state.username) {
|
||||
@@ -123,6 +110,8 @@ def bridgeLinking()
|
||||
return dynamicPage(name:"bridgeBtnPush", title:title, nextPage:nextPage, refreshInterval:refreshInterval) {
|
||||
section("") {
|
||||
paragraph """${paragraphText}"""
|
||||
if (hueimage != null)
|
||||
image "${hueimage}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -146,14 +135,13 @@ def bulbDiscovery() {
|
||||
if((bulbRefreshCount % 5) == 0) {
|
||||
discoverHueBulbs()
|
||||
}
|
||||
def selectedBridge = state.bridges.find { key, value -> value?.serialNumber?.equalsIgnoreCase(selectedHue) }
|
||||
def title = selectedBridge?.value?.name ?: "Find bridges"
|
||||
|
||||
return dynamicPage(name:"bulbDiscovery", title:"Bulb Discovery Started!", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) {
|
||||
section("Please wait while we discover your Hue Bulbs. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
|
||||
input "selectedBulbs", "enum", required:false, title:"Select Hue Bulbs (${numFound} found)", multiple:true, options:bulboptions
|
||||
}
|
||||
section {
|
||||
def title = getBridgeIP() ? "Hue bridge (${getBridgeIP()})" : "Find bridges"
|
||||
href "bridgeDiscovery", title: title, description: "", state: selectedHue ? "complete" : "incomplete", params: [override: true]
|
||||
|
||||
}
|
||||
@@ -335,8 +323,6 @@ private getDeviceType(hueType) {
|
||||
return "Hue Bulb"
|
||||
else if (hueType?.equalsIgnoreCase("Color Light"))
|
||||
return "Hue Bloom"
|
||||
else if (hueType?.equalsIgnoreCase("Color Temperature Light"))
|
||||
return "Hue White Ambiance Bulb"
|
||||
else
|
||||
return null
|
||||
}
|
||||
@@ -360,29 +346,26 @@ def addBulbs() {
|
||||
def newHueBulb
|
||||
if (bulbs instanceof java.util.Map) {
|
||||
newHueBulb = bulbs.find { (app.id + "/" + it.value.id) == dni }
|
||||
|
||||
if (newHueBulb != null) {
|
||||
d = addChildBulb(dni, newHueBulb?.value?.type, newHueBulb?.value?.name, newHueBulb?.value?.hub)
|
||||
if (newHueBulb != null) {
|
||||
d = addChildBulb(dni, newHueBulb?.value?.type, newHueBulb?.value?.name, newHueBulb?.value?.hub)
|
||||
if (d) {
|
||||
log.debug "created ${d.displayName} with id $dni"
|
||||
d.completedSetup = true
|
||||
d.refresh()
|
||||
}
|
||||
} else {
|
||||
log.debug "$dni in not longer paired to the Hue Bridge or ID changed"
|
||||
}
|
||||
} else {
|
||||
log.debug "$dni in not longer paired to the Hue Bridge or ID changed"
|
||||
}
|
||||
} else {
|
||||
//backwards compatable
|
||||
//backwards compatable
|
||||
newHueBulb = bulbs.find { (app.id + "/" + it.id) == dni }
|
||||
d = addChildBulb(dni, "Extended Color Light", newHueBulb?.value?.name, newHueBulb?.value?.hub)
|
||||
d?.completedSetup = true
|
||||
d?.refresh()
|
||||
}
|
||||
} else {
|
||||
log.debug "found ${d.displayName} with id $dni already exists, type: '$d.typeName'"
|
||||
if (bulbs instanceof java.util.Map) {
|
||||
// Update device type if incorrect
|
||||
def newHueBulb = bulbs.find { (app.id + "/" + it.value.id) == dni }
|
||||
def newHueBulb = bulbs.find { (app.id + "/" + it.value.id) == dni }
|
||||
upgradeDeviceType(d, newHueBulb?.value?.type)
|
||||
}
|
||||
}
|
||||
@@ -414,7 +397,6 @@ def addBridge() {
|
||||
}
|
||||
if (newbridge) {
|
||||
d = addChildDevice("smartthings", "Hue Bridge", selectedHue, vbridge.value.hub)
|
||||
d?.completedSetup = true
|
||||
log.debug "created ${d.displayName} with id ${d.deviceNetworkId}"
|
||||
def childDevice = getChildDevice(d.deviceNetworkId)
|
||||
childDevice.sendEvent(name: "serialNumber", value: vbridge.value.serialNumber)
|
||||
@@ -502,21 +484,7 @@ void bridgeDescriptionHandler(physicalgraph.device.HubResponse hubResponse) {
|
||||
def bridges = getHueBridges()
|
||||
def bridge = bridges.find {it?.key?.contains(body?.device?.UDN?.text())}
|
||||
if (bridge) {
|
||||
// serialNumber from API is in format of 0017882413ad (mac address), however on the actual bridge only last six
|
||||
// characters are printed on the back so using that to identify bridge
|
||||
def idNumber = body?.device?.serialNumber?.text()
|
||||
if (idNumber?.size() >= 6)
|
||||
idNumber = idNumber[-6..-1].toUpperCase()
|
||||
|
||||
// usually in form of bridge name followed by (ip), i.e. defaults to Philips Hue (192.168.1.2)
|
||||
// replace IP with serial number to make it easier for user to identify
|
||||
def name = body?.device?.friendlyName?.text()
|
||||
def index = name?.indexOf('(')
|
||||
if (index != -1) {
|
||||
name = name.substring(0,index)
|
||||
name += " ($idNumber)"
|
||||
}
|
||||
bridge.value << [name:name, serialNumber:body?.device?.serialNumber?.text(), verified: true]
|
||||
bridge.value << [name:body?.device?.friendlyName?.text(), serialNumber:body?.device?.serialNumber?.text(), verified: true]
|
||||
} else {
|
||||
log.error "/description.xml returned a bridge that didn't exist"
|
||||
}
|
||||
@@ -689,7 +657,7 @@ def parse(childDevice, description) {
|
||||
log.warn "Parsing Body failed - trying again..."
|
||||
poll()
|
||||
}
|
||||
if (body instanceof java.util.Map) {
|
||||
if (body instanceof java.util.HashMap) {
|
||||
//poll response
|
||||
def bulbs = getChildDevices()
|
||||
for (bulb in body) {
|
||||
@@ -830,22 +798,22 @@ def setColorTemperature(childDevice, huesettings) {
|
||||
|
||||
def setColor(childDevice, huesettings) {
|
||||
log.debug "Executing 'setColor($huesettings)'"
|
||||
|
||||
|
||||
def value = [:]
|
||||
def hue = null
|
||||
def sat = null
|
||||
def xy = null
|
||||
|
||||
|
||||
if (huesettings.hex != null) {
|
||||
value.xy = getHextoXY(huesettings.hex)
|
||||
} else {
|
||||
if (huesettings.hue != null)
|
||||
value.hue = Math.min(Math.round(huesettings.hue * 65535 / 100), 65535)
|
||||
if (huesettings.saturation != null)
|
||||
if (huesettings.saturation != null)
|
||||
value.sat = Math.min(Math.round(huesettings.saturation * 255 / 100), 255)
|
||||
}
|
||||
|
||||
// Default behavior is to turn light on
|
||||
|
||||
// Default behavior is to turn light on
|
||||
value.on = true
|
||||
|
||||
if (huesettings.level != null) {
|
||||
@@ -853,7 +821,7 @@ def setColor(childDevice, huesettings) {
|
||||
value.on = false
|
||||
else if (huesettings.level == 1)
|
||||
value.bri = 1
|
||||
else
|
||||
else
|
||||
value.bri = Math.min(Math.round(huesettings.level * 255 / 100), 255)
|
||||
}
|
||||
value.alert = huesettings.alert ? huesettings.alert : "none"
|
||||
|
||||
@@ -73,8 +73,7 @@ def temperatureHandler(evt) {
|
||||
// TODO: Send "Temperature back to normal" SMS, turn switch off
|
||||
} else {
|
||||
log.debug "Temperature dropped below $tooCold: sending SMS to $phone1 and activating $mySwitch"
|
||||
def tempScale = location.temperatureScale ?: "F"
|
||||
send("${temperatureSensor1.displayName} is too cold, reporting a temperature of ${evt.value}${evt.unit?:tempScale}")
|
||||
send("${temperatureSensor1.displayName} is too cold, reporting a temperature of ${evt.value}${evt.unit?:"F"}")
|
||||
switch1?.on()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,8 +73,7 @@ def temperatureHandler(evt) {
|
||||
// TODO: Send "Temperature back to normal" SMS, turn switch off
|
||||
} else {
|
||||
log.debug "Temperature rose above $tooHot: sending SMS to $phone1 and activating $mySwitch"
|
||||
def tempScale = location.temperatureScale ?: "F"
|
||||
send("${temperatureSensor1.displayName} is too hot, reporting a temperature of ${evt.value}${evt.unit?:tempScale}")
|
||||
send("${temperatureSensor1.displayName} is too hot, reporting a temperature of ${evt.value}${evt.unit?:"F"}")
|
||||
switch1?.on()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ definition(
|
||||
) {
|
||||
appSetting "clientId"
|
||||
appSetting "clientSecret"
|
||||
appSetting "serverUrl"
|
||||
}
|
||||
|
||||
preferences {
|
||||
@@ -191,7 +192,7 @@ def getSmartThingsClientId() {
|
||||
return "pREqugabRetre4EstetherufrePumamExucrEHuc"
|
||||
}
|
||||
|
||||
def getServerUrl() { getApiServerUrl() }
|
||||
def getServerUrl() { appSettings.serverUrl }
|
||||
|
||||
def buildRedirectUrl()
|
||||
{
|
||||
|
||||
@@ -339,7 +339,7 @@ def initialize() {
|
||||
state.aux = 0
|
||||
if (selectedhubs || selectedactivities) {
|
||||
addDevice()
|
||||
runEvery5Minutes("poll")
|
||||
runEvery5Minutes("discovery")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -394,9 +394,9 @@ def discovery() {
|
||||
}
|
||||
} catch (java.net.SocketTimeoutException e) {
|
||||
log.warn "Connection to the hub timed out. Please restart the hub and try again."
|
||||
state.resethub = true
|
||||
state.resethub = true
|
||||
} catch (e) {
|
||||
log.info "Logitech Harmony - Error: $e"
|
||||
log.warn "Hostname in certificate didn't match. Please try again later."
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -474,7 +474,7 @@ def activity(dni,mode) {
|
||||
def poll() {
|
||||
// GET THE LIST OF ACTIVITIES
|
||||
if (state.HarmonyAccessToken) {
|
||||
getActivityList()
|
||||
getActivityList()
|
||||
def Params = [auth: state.HarmonyAccessToken]
|
||||
def url = "https://home.myharmony.com/cloudapi/state?${toQueryString(Params)}"
|
||||
try {
|
||||
@@ -520,17 +520,14 @@ def poll() {
|
||||
return "Poll completed $map - $state.hubs"
|
||||
}
|
||||
} catch (groovyx.net.http.HttpResponseException e) {
|
||||
if (e.statusCode == 401) { // token is expired
|
||||
state.remove("HarmonyAccessToken")
|
||||
log.warn "Harmony Access token has expired"
|
||||
}
|
||||
} catch (java.net.SocketTimeoutException e) {
|
||||
log.warn "Connection to the hub timed out. Please restart the hub and try again."
|
||||
state.resethub = true
|
||||
} catch (e) {
|
||||
log.info "Logitech Harmony - Error: $e"
|
||||
}
|
||||
}
|
||||
if (e.statusCode == 401) { // token is expired
|
||||
state.remove("HarmonyAccessToken")
|
||||
return "Harmony Access token has expired"
|
||||
}
|
||||
} catch(Exception e) {
|
||||
log.trace e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -658,73 +655,29 @@ def updateDevice() {
|
||||
def data = request.JSON
|
||||
def command = data.command
|
||||
def arguments = data.arguments
|
||||
|
||||
log.debug "updateDevice, params: ${params}, request: ${data}"
|
||||
if (!command) {
|
||||
render status: 400, data: '{"msg": "command is required"}'
|
||||
} else {
|
||||
def device = allDevices.find { it.id == params.id }
|
||||
if (device) {
|
||||
if (validateCommand(device, command)) {
|
||||
if (arguments) {
|
||||
device."$command"(*arguments)
|
||||
} else {
|
||||
device."$command"()
|
||||
}
|
||||
render status: 204, data: "{}"
|
||||
} else {
|
||||
render status: 403, data: '{"msg": "Access denied. This command is not supported by current capability."}'
|
||||
}
|
||||
} else {
|
||||
render status: 404, data: '{"msg": "Device not found"}'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validating the command passed by the user based on capability.
|
||||
* @return boolean
|
||||
*/
|
||||
def validateCommand(device, command) {
|
||||
def capabilityCommands = getDeviceCapabilityCommands(device.capabilities)
|
||||
def currentDeviceCapability = getCapabilityName(device)
|
||||
if (currentDeviceCapability != "" && capabilityCommands[currentDeviceCapability]) {
|
||||
return (command in capabilityCommands[currentDeviceCapability] || (currentDeviceCapability == "Switch" && command == "setLevel" && device.hasCommand("setLevel"))) ? true : false
|
||||
} else {
|
||||
// Handling other device types here, which don't accept commands
|
||||
httpError(400, "Bad request.")
|
||||
if (device) {
|
||||
if (device.hasCommand("$command")) {
|
||||
if (arguments) {
|
||||
device."$command"(*arguments)
|
||||
} else {
|
||||
device."$command"()
|
||||
}
|
||||
render status: 204, data: "{}"
|
||||
} else {
|
||||
render status: 404, data: '{"msg": "Command not supported by this Device"}'
|
||||
}
|
||||
} else {
|
||||
render status: 404, data: '{"msg": "Device not found"}'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Need to get the attribute name to do the lookup. Only
|
||||
* doing it for the device types which accept commands
|
||||
* @return attribute name of the device type
|
||||
*/
|
||||
def getCapabilityName(device) {
|
||||
def capName = ""
|
||||
if (switches.find{it.id == device.id})
|
||||
capName = "Switch"
|
||||
else if (alarms.find{it.id == device.id})
|
||||
capName = "Alarm"
|
||||
else if (locks.find{it.id == device.id})
|
||||
capName = "Lock"
|
||||
log.trace "Device: $device - Capability Name: $capName"
|
||||
return capName
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructing the map over here of
|
||||
* supported commands by device capability
|
||||
* @return a map of device capability -> supported commands
|
||||
*/
|
||||
def getDeviceCapabilityCommands(deviceCapabilities) {
|
||||
def map = [:]
|
||||
deviceCapabilities.collect {
|
||||
map[it.name] = it.commands.collect{ it.name.toString() }
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
def listSubscriptions() {
|
||||
log.debug "listSubscriptions()"
|
||||
app.subscriptions?.findAll { it.device?.device && it.device.id }?.collect {
|
||||
@@ -823,33 +776,18 @@ def deviceHandler(evt) {
|
||||
}
|
||||
|
||||
def sendToHarmony(evt, String callbackUrl) {
|
||||
def callback = new URI(callbackUrl)
|
||||
if (callback.port != -1) {
|
||||
def host = callback.port != -1 ? "${callback.host}:${callback.port}" : callback.host
|
||||
def path = callback.query ? "${callback.path}?${callback.query}".toString() : callback.path
|
||||
sendHubCommand(new physicalgraph.device.HubAction(
|
||||
method: "POST",
|
||||
path: path,
|
||||
headers: [
|
||||
"Host": host,
|
||||
"Content-Type": "application/json"
|
||||
],
|
||||
body: [evt: [deviceId: evt.deviceId, name: evt.name, value: evt.value]]
|
||||
))
|
||||
} else {
|
||||
def params = [
|
||||
uri: callbackUrl,
|
||||
body: [evt: [deviceId: evt.deviceId, name: evt.name, value: evt.value]]
|
||||
]
|
||||
try {
|
||||
log.debug "Sending data to Harmony Cloud: $params"
|
||||
httpPostJson(params) { resp ->
|
||||
log.debug "Harmony Cloud - Response: ${resp.status}"
|
||||
}
|
||||
} catch (e) {
|
||||
log.error "Harmony Cloud - Something went wrong: $e"
|
||||
}
|
||||
}
|
||||
def callback = new URI(callbackUrl)
|
||||
def host = callback.port != -1 ? "${callback.host}:${callback.port}" : callback.host
|
||||
def path = callback.query ? "${callback.path}?${callback.query}".toString() : callback.path
|
||||
sendHubCommand(new physicalgraph.device.HubAction(
|
||||
method: "POST",
|
||||
path: path,
|
||||
headers: [
|
||||
"Host": host,
|
||||
"Content-Type": "application/json"
|
||||
],
|
||||
body: [evt: [deviceId: evt.deviceId, name: evt.name, value: evt.value]]
|
||||
))
|
||||
}
|
||||
|
||||
def listHubs() {
|
||||
|
||||
@@ -107,8 +107,8 @@ mappings {
|
||||
path("/locks") {
|
||||
action: [
|
||||
GET: "listLocks",
|
||||
PUT: "updateLocks",
|
||||
POST: "updateLocks"
|
||||
PUT: "updateLock",
|
||||
POST: "updateLock"
|
||||
]
|
||||
}
|
||||
path("/locks/:id") {
|
||||
@@ -442,87 +442,31 @@ def executePhrase() {
|
||||
}
|
||||
|
||||
private void updateAll(devices) {
|
||||
def type = params.param1
|
||||
def command = request.JSON?.command
|
||||
if (!devices) {
|
||||
httpError(404, "Devices not found")
|
||||
}
|
||||
if (command){
|
||||
devices.each { device ->
|
||||
executeCommand(device, type, command)
|
||||
}
|
||||
if (command)
|
||||
{
|
||||
command = command.toLowerCase()
|
||||
devices."$command"()
|
||||
}
|
||||
}
|
||||
|
||||
private void update(devices) {
|
||||
log.debug "update, request: ${request.JSON}, params: ${params}, devices: $devices.id"
|
||||
def type = params.param1
|
||||
def command = request.JSON?.command
|
||||
def device = devices?.find { it.id == params.id }
|
||||
|
||||
if (!device) {
|
||||
//def command = request.JSON?.command
|
||||
def command = params.command
|
||||
if (command)
|
||||
{
|
||||
command = command.toLowerCase()
|
||||
def device = devices.find { it.id == params.id }
|
||||
if (!device)
|
||||
{
|
||||
httpError(404, "Device not found")
|
||||
}
|
||||
else
|
||||
{
|
||||
device."$command"()
|
||||
}
|
||||
}
|
||||
|
||||
if (command) {
|
||||
executeCommand(device, type, command)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validating the command passed by the user based on capability.
|
||||
* @return boolean
|
||||
*/
|
||||
def validateCommand(device, deviceType, command) {
|
||||
def capabilityCommands = getDeviceCapabilityCommands(device.capabilities)
|
||||
def currentDeviceCapability = getCapabilityName(deviceType)
|
||||
if (capabilityCommands[currentDeviceCapability]) {
|
||||
return command in capabilityCommands[currentDeviceCapability] ? true : false
|
||||
} else {
|
||||
// Handling other device types here, which don't accept commands
|
||||
httpError(400, "Bad request.")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Need to get the attribute name to do the lookup. Only
|
||||
* doing it for the device types which accept commands
|
||||
* @return attribute name of the device type
|
||||
*/
|
||||
def getCapabilityName(type) {
|
||||
switch(type) {
|
||||
case "switches":
|
||||
return "Switch"
|
||||
case "locks":
|
||||
return "Lock"
|
||||
default:
|
||||
return type
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructing the map over here of
|
||||
* supported commands by device capability
|
||||
* @return a map of device capability -> supported commands
|
||||
*/
|
||||
def getDeviceCapabilityCommands(deviceCapabilities) {
|
||||
def map = [:]
|
||||
deviceCapabilities.collect {
|
||||
map[it.name] = it.commands.collect{ it.name.toString() }
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and executes the command
|
||||
* on the device or devices
|
||||
*/
|
||||
def executeCommand(device, type, command) {
|
||||
if (validateCommand(device, type, command)) {
|
||||
device."$command"()
|
||||
} else {
|
||||
httpError(403, "Access denied. This command is not supported by current capability.")
|
||||
}
|
||||
}
|
||||
|
||||
private show(devices, type) {
|
||||
|
||||
@@ -273,7 +273,7 @@ def scheduleCheck(evt) {
|
||||
else {
|
||||
if(people){
|
||||
//don't turn off lights if anyone is home
|
||||
if(someoneIsHome){
|
||||
if(someoneIsHome()){
|
||||
log.debug("Stopping Check for Light")
|
||||
unschedule()
|
||||
}
|
||||
|
||||
@@ -92,89 +92,24 @@ void updateLock() {
|
||||
|
||||
private void updateAll(devices) {
|
||||
def command = request.JSON?.command
|
||||
def type = params.param1
|
||||
if (!devices) {
|
||||
httpError(404, "Devices not found")
|
||||
}
|
||||
|
||||
if (command){
|
||||
devices.each { device ->
|
||||
executeCommand(device, type, command)
|
||||
}
|
||||
if (command) {
|
||||
devices."$command"()
|
||||
}
|
||||
}
|
||||
|
||||
private void update(devices) {
|
||||
log.debug "update, request: ${request.JSON}, params: ${params}, devices: $devices.id"
|
||||
def command = request.JSON?.command
|
||||
def type = params.param1
|
||||
def device = devices?.find { it.id == params.id }
|
||||
|
||||
if (!device) {
|
||||
httpError(404, "Device not found")
|
||||
}
|
||||
|
||||
if (command) {
|
||||
executeCommand(device, type, command)
|
||||
def device = devices.find { it.id == params.id }
|
||||
if (!device) {
|
||||
httpError(404, "Device not found")
|
||||
} else {
|
||||
device."$command"()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validating the command passed by the user based on capability.
|
||||
* @return boolean
|
||||
*/
|
||||
def validateCommand(device, deviceType, command) {
|
||||
def capabilityCommands = getDeviceCapabilityCommands(device.capabilities)
|
||||
def currentDeviceCapability = getCapabilityName(deviceType)
|
||||
if (capabilityCommands[currentDeviceCapability]) {
|
||||
return command in capabilityCommands[currentDeviceCapability] ? true : false
|
||||
} else {
|
||||
// Handling other device types here, which don't accept commands
|
||||
httpError(400, "Bad request.")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Need to get the attribute name to do the lookup. Only
|
||||
* doing it for the device types which accept commands
|
||||
* @return attribute name of the device type
|
||||
*/
|
||||
def getCapabilityName(type) {
|
||||
switch(type) {
|
||||
case "switches":
|
||||
return "Switch"
|
||||
case "locks":
|
||||
return "Lock"
|
||||
default:
|
||||
return type
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructing the map over here of
|
||||
* supported commands by device capability
|
||||
* @return a map of device capability -> supported commands
|
||||
*/
|
||||
def getDeviceCapabilityCommands(deviceCapabilities) {
|
||||
def map = [:]
|
||||
deviceCapabilities.collect {
|
||||
map[it.name] = it.commands.collect{ it.name.toString() }
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and executes the command
|
||||
* on the device or devices
|
||||
*/
|
||||
def executeCommand(device, type, command) {
|
||||
if (validateCommand(device, type, command)) {
|
||||
device."$command"()
|
||||
} else {
|
||||
httpError(403, "Access denied. This command is not supported by current capability.")
|
||||
}
|
||||
}
|
||||
|
||||
private show(devices, name) {
|
||||
def device = devices.find { it.id == params.id }
|
||||
if (!device) {
|
||||
|
||||
Reference in New Issue
Block a user