mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-09 05:11:52 +00:00
Compare commits
54 Commits
MSA-1890-1
...
MSA-1925-2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90d8534b46 | ||
|
|
7e8baeeb0b | ||
|
|
62991f8d23 | ||
|
|
ad824a9dd8 | ||
|
|
11f2775568 | ||
|
|
83b65c0d87 | ||
|
|
e641759a47 | ||
|
|
7820b39b2b | ||
|
|
6a76a8ee39 | ||
|
|
980bef6879 | ||
|
|
120935f14e | ||
|
|
c864fc521e | ||
|
|
032f4a92d4 | ||
|
|
6d528683e6 | ||
|
|
8bcbe7b924 | ||
|
|
69dd13f333 | ||
|
|
016425b7c8 | ||
|
|
c164b201ca | ||
|
|
030dd47b69 | ||
|
|
cc68534b47 | ||
|
|
5e07494dff | ||
|
|
573630232f | ||
|
|
29c7049d60 | ||
|
|
03a79b8bb5 | ||
|
|
d91fc1c9d1 | ||
|
|
db7288f245 | ||
|
|
b59d979fbf | ||
|
|
fc32031555 | ||
|
|
f445ffc67c | ||
|
|
5b39a9f840 | ||
|
|
95c383a2ea | ||
|
|
7f61feaebc | ||
|
|
7a467df659 | ||
|
|
538eb057ce | ||
|
|
2ff9486790 | ||
|
|
5429986e81 | ||
|
|
6514087a1a | ||
|
|
0f2b8c18d2 | ||
|
|
ce0363fa43 | ||
|
|
fd620977bb | ||
|
|
a308bff574 | ||
|
|
068cfaeaad | ||
|
|
fb55349db0 | ||
|
|
476d3caa38 | ||
|
|
9b621c9a7b | ||
|
|
619b3499c8 | ||
|
|
3ec8be708a | ||
|
|
34b2210134 | ||
|
|
8abb82ecae | ||
|
|
55da70cd7c | ||
|
|
80a5a41f7e | ||
|
|
b4276c05e0 | ||
|
|
264e822c9f | ||
|
|
c65c9cc185 |
2
devicetypes/drzwave/ezmultipli.src/.st-ignore
Normal file
2
devicetypes/drzwave/ezmultipli.src/.st-ignore
Normal file
@@ -0,0 +1,2 @@
|
||||
.st-ignore
|
||||
README.md
|
||||
44
devicetypes/drzwave/ezmultipli.src/README.md
Normal file
44
devicetypes/drzwave/ezmultipli.src/README.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# Express Controls EZMultiPli
|
||||
|
||||
Works with:
|
||||
|
||||
* [Express Controls EZMultiPli](https://www.smartthings.com/works-with-smartthings/)
|
||||
|
||||
## Table of contents
|
||||
|
||||
* [Release Notes](#release-notes)
|
||||
* [Capabilities](#capabilities)
|
||||
* [Troubleshooting](#troubleshooting)
|
||||
|
||||
## Release Notes
|
||||
|
||||
* **2017-04-19** - _dkirker_ - Update default config values in config value range check functions, use lux if lum option is null, fix NullPointerException on initial pairing when color data has not been set (and set the default color data!)
|
||||
* **2017-04-10** - _DrZwave_ (with help from Donald Kirker) - changed fingerprint to the new format, lowered the OnTime and other parameters to be "more in line with ST user expectations", get the luminance in LUX so it reports in lux all the time.
|
||||
* **2016-10-06** - _erocm1231_ - Added "updated" method to run when configuration options are changed. Depending on model of unit, luminance is being reported as a relative percentace or as a lux value. Added the option to configure this in the handler.
|
||||
* **2016-01-28** - _erocm1231_ - Changed the configuration method to use scaledConfiguration so that it properly formatted negative numbers. Also, added configurationGet and a configurationReport method so that config values can be verified.
|
||||
* **2015-12-04** - _erocm1231_ - added range value to preferences as suggested by @Dela-Rick.
|
||||
* **2015-11-26** - _erocm1231_ - Fixed null condition error when adding as a new device.
|
||||
* **2015-11-24** - _erocm1231_ - Added refresh command. Made a few changes to how the handler maps colors to the LEDs. Fixed the device not having its on/off status updated when colors are changed.
|
||||
* **2015-11-23** - _erocm1231_ - Changed the look to match SmartThings v2 devices.
|
||||
* **2015-11-21** - _erocm1231_ - Made code much more efficient. Also made it compatible when setColor is passed a hex value. Mapping of special colors: Soft White - Default - Yellow, White - Concentrate - White, Daylight - Energize - Teal, Warm White - Relax - Yellow
|
||||
* **2015-11-19** - _erocm1231_ - Fixed a couple incorrect colors, changed setColor to be more compatible with other apps
|
||||
* **2015-11-18** - _erocm1231_ - Added to setColor for compatibility with Smart Lighting
|
||||
* **v0.1.0** - _DrZWave_ - chose better icons, Got color LED to work - first fully functional version
|
||||
* **v0.0.9** - _jrs_ - got the temp and luminance to work. Motion works. Debugging the color wheel.
|
||||
* **v0.0.8** - _DrZWave_ 2/25/2015 - change the color control to be tiles since there are only 8 colors.
|
||||
* **v0.0.7** - _jrs_ - 02/23/2015 - Jim Sulin
|
||||
|
||||
## Capabilities
|
||||
|
||||
* **Actuator** - represents that a Device has commands
|
||||
* **Sensor** - detects sensor events
|
||||
* **Motion Sensor** - can detect motion
|
||||
* **Temperature Measurement** - defines device measures current temperature
|
||||
* **Illuminance Measurement** - gives the illuminance reading from devices that support it
|
||||
* **Switch** - can detect state (possible values: on/off)
|
||||
* **Color Control** - represents that the color attributes of a device can be controlled (hue, saturation, color value
|
||||
* **Configuration** - configure() command called when device is installed or device preferences updated
|
||||
* **Refresh** - refresh() command for status updates
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
405
devicetypes/drzwave/ezmultipli.src/ezmultipli.groovy
Normal file
405
devicetypes/drzwave/ezmultipli.src/ezmultipli.groovy
Normal file
@@ -0,0 +1,405 @@
|
||||
// Express Controls EZMultiPli Multi-sensor
|
||||
// Motion Sensor - Temperature - Light level - 8 Color Indicator LED - Z-Wave Range Extender - Wall Powered
|
||||
// driver for SmartThings
|
||||
// The EZMultiPli is also known as the HSM200 from HomeSeer.com
|
||||
|
||||
metadata {
|
||||
definition (name: "EZmultiPli", namespace: "DrZWave", author: "Eric Ryherd", oauth: true) {
|
||||
capability "Actuator"
|
||||
capability "Sensor"
|
||||
capability "Motion Sensor"
|
||||
capability "Temperature Measurement"
|
||||
capability "Illuminance Measurement"
|
||||
capability "Switch"
|
||||
capability "Color Control"
|
||||
capability "Configuration"
|
||||
capability "Refresh"
|
||||
|
||||
fingerprint mfr: "001E", prod: "0004", model: "0001" // new format for Fingerprint which is unique for every certified Z-Wave product
|
||||
} // end definition
|
||||
|
||||
simulator {
|
||||
// messages the device returns in response to commands it receives
|
||||
status "motion" : "command: 7105000000FF07, payload: 07"
|
||||
status "no motion" : "command: 7105000000FF07, payload: 00"
|
||||
|
||||
for (int i = 0; i <= 100; i += 20) {
|
||||
status "temperature ${i}F": new physicalgraph.zwave.Zwave().sensorMultilevelV5.sensorMultilevelReport(
|
||||
scaledSensorValue: i, precision: 1, sensorType: 1, scale: 1).incomingMessage()
|
||||
}
|
||||
for (int i = 0; i <= 100; i += 20) {
|
||||
status "luminance ${i} %": new physicalgraph.zwave.Zwave().sensorMultilevelV5.sensorMultilevelReport(
|
||||
scaledSensorValue: i, precision: 0, sensorType: 3).incomingMessage()
|
||||
}
|
||||
|
||||
} //end simulator
|
||||
|
||||
|
||||
tiles (scale: 2){
|
||||
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL", icon: "st.Lighting.light18") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
attributeState "turningOn", label:'${name}', icon:"st.switches.light.on", backgroundColor:"#79b821"
|
||||
attributeState "turningOff", label:'${name}', icon:"st.switches.light.off", backgroundColor:"#ffffff"
|
||||
}
|
||||
tileAttribute ("device.color", key: "COLOR_CONTROL") {
|
||||
attributeState "color", action:"setColor"
|
||||
}
|
||||
tileAttribute ("statusText", key: "SECONDARY_CONTROL") {
|
||||
attributeState "statusText", label:'${currentValue}'
|
||||
}
|
||||
}
|
||||
|
||||
standardTile("motion", "device.motion", width: 2, height: 2, canChangeIcon: true, canChangeBackground: true) {
|
||||
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", width: 2, height: 2) {
|
||||
state "temperature", label:'${currentValue}°', unit:"F", icon:"", // would be better if the units would switch to the desired units of the system (imperial or metric)
|
||||
backgroundColors:[
|
||||
[value: 0, color: "#1010ff"], // blue=cold
|
||||
[value: 65, color: "#a0a0f0"],
|
||||
[value: 70, color: "#e0e050"],
|
||||
[value: 75, color: "#f0d030"], // yellow
|
||||
[value: 80, color: "#fbf020"],
|
||||
[value: 85, color: "#fbdc01"],
|
||||
[value: 90, color: "#fb3a01"],
|
||||
[value: 95, color: "#fb0801"] // red=hot
|
||||
]
|
||||
}
|
||||
|
||||
// icons to use would be st.Weather.weather2 or st.alarm.temperature.normal - see http://scripts.3dgo.net/smartthings/icons/ for a list of icons
|
||||
valueTile("illuminance", "device.illuminance", width: 2, height: 2, inactiveLabel: false) {
|
||||
// jrs 4/7/2015 - Null on display
|
||||
//state "luminosity", label:'${currentValue} ${unit}'
|
||||
state "luminosity", label:'${currentValue}', unit:'${currentValue}', icon:"",
|
||||
backgroundColors:[
|
||||
[value: 25, color: "#404040"],
|
||||
[value: 50, color: "#808080"],
|
||||
[value: 75, color: "#a0a0a0"],
|
||||
[value: 90, color: "#e0e0e0"],
|
||||
//lux measurement values
|
||||
[value: 150, color: "#404040"],
|
||||
[value: 300, color: "#808080"],
|
||||
[value: 600, color: "#a0a0a0"],
|
||||
[value: 900, color: "#e0e0e0"]
|
||||
]
|
||||
}
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
standardTile("configure", "device.configure", inactiveLabel: false, width: 2, height: 2, decoration: "flat") {
|
||||
state "configure", label:'', action:"configuration.configure", icon:"st.secondary.configure"
|
||||
}
|
||||
|
||||
main (["temperature","motion", "switch"])
|
||||
details(["switch", "motion", "temperature", "illuminance", "refresh", "configure"])
|
||||
} // end tiles
|
||||
|
||||
preferences {
|
||||
input "OnTime", "number", title: "No Motion Interval", description: "N minutes lights stay on after no motion detected [0, 1-127]", range: "0..127", defaultValue: 2, displayDuringSetup: true, required: false
|
||||
input "OnLevel", "number", title: "Dimmer Onlevel", description: "Dimmer OnLevel for associated node 2 lights [-1, 0, 1-99]", range: "-1..99", defaultValue: -1, displayDuringSetup: true, required: false
|
||||
input "LiteMin", "number", title: "Luminance Report Frequency", description: "Luminance report sent every N minutes [0-127]", range: "0..127", defaultValue: 6, displayDuringSetup: true, required: false
|
||||
input "TempMin", "number", title: "Temperature Report Frequency", description: "Temperature report sent every N minutes [0-127]", range: "0..127", defaultValue: 6, displayDuringSetup: true, required: false
|
||||
input "TempAdj", "number", title: "Temperature Calibration", description: "Adjust temperature up/down N tenths of a degree F [(-127)-(+128)]", range: "-127..128", defaultValue: 0, displayDuringSetup: true, required: false
|
||||
input("lum", "enum", title:"Illuminance Measurement", description: "Percent or Lux", defaultValue: 2 ,required: false, displayDuringSetup: true, options:
|
||||
[1:"Percent",
|
||||
2:"Lux"])
|
||||
}
|
||||
|
||||
} // end metadata
|
||||
|
||||
// Parse incoming device messages from device to generate events
|
||||
def parse(String description){
|
||||
//log.debug "==> New Zwave Event: ${description}"
|
||||
def result = []
|
||||
def cmd = zwave.parse(description, [0x31: 5]) // 0x31=SensorMultilevel which we force to be version 5
|
||||
if (cmd) {
|
||||
def cmdData = zwaveEvent(cmd)
|
||||
if (cmdData != [:])
|
||||
result << createEvent(cmdData)
|
||||
}
|
||||
|
||||
def statusTextmsg = ""
|
||||
if (device.currentState('temperature') != null && device.currentState('illuminance') != null) {
|
||||
statusTextmsg = "${device.currentState('temperature').value} ° - ${device.currentState('illuminance').value} ${(lum == 1) ? "%" : "LUX"}"
|
||||
sendEvent("name":"statusText", "value":statusTextmsg, displayed:false)
|
||||
}
|
||||
if (result != [null] && result != []) log.debug "Parse returned ${result}"
|
||||
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
// Event Generation
|
||||
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd){
|
||||
def map = [:]
|
||||
switch (cmd.sensorType) {
|
||||
case 0x01: // SENSOR_TYPE_TEMPERATURE_VERSION_1
|
||||
def cmdScale = cmd.scale == 1 ? "F" : "C"
|
||||
map.value = convertTemperatureIfNeeded(cmd.scaledSensorValue, cmdScale, cmd.precision)
|
||||
map.unit = getTemperatureScale()
|
||||
map.name = "temperature"
|
||||
log.debug "Temperature report"
|
||||
break;
|
||||
case 0x03 : // SENSOR_TYPE_LUMINANCE_VERSION_1
|
||||
map.value = cmd.scaledSensorValue.toInteger().toString()
|
||||
if(lum == 1) map.unit = "%"
|
||||
else map.unit = "lux"
|
||||
map.name = "illuminance"
|
||||
log.debug "Luminance report"
|
||||
break;
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.configurationv2.ConfigurationReport cmd) {
|
||||
log.debug "${device.displayName} parameter '${cmd.parameterNumber}' with a byte size of '${cmd.size}' is set to '${cmd.configurationValue}'"
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.notificationv3.NotificationReport cmd) {
|
||||
def map = [:]
|
||||
if (cmd.notificationType==0x07) { // NOTIFICATION_TYPE_BURGLAR
|
||||
if (cmd.event==0x07 || cmd.event==0x08) {
|
||||
map.name = "motion"
|
||||
map.value = "active"
|
||||
map.descriptionText = "$device.displayName motion detected"
|
||||
log.debug "motion recognized"
|
||||
} else if (cmd.event==0) {
|
||||
map.name = "motion"
|
||||
map.value = "inactive"
|
||||
map.descriptionText = "$device.displayName no motion detected"
|
||||
log.debug "No motion recognized"
|
||||
}
|
||||
}
|
||||
if (map.name != "motion") {
|
||||
log.debug "unmatched parameters for cmd: ${cmd.toString()}}"
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
|
||||
// The EZMultiPli sets the color back to #ffffff on "off" or at init, so update the ST device to reflect this.
|
||||
if (device.latestState("color") == null || (cmd.value == 0 && device.latestState("color").value != "#ffffff")) {
|
||||
sendEvent(name: "color", value: "#ffffff", displayed: true)
|
||||
}
|
||||
[name: "switch", value: cmd.value ? "on" : "off", type: "digital"]
|
||||
}
|
||||
|
||||
def updated()
|
||||
{
|
||||
log.debug "updated() is being called"
|
||||
|
||||
def cmds = configure()
|
||||
|
||||
if (cmds != []) response(cmds)
|
||||
}
|
||||
|
||||
def on() {
|
||||
log.debug "Turning Light 'on'"
|
||||
delayBetween([
|
||||
zwave.basicV1.basicSet(value: 0xFF).format(),
|
||||
zwave.basicV1.basicGet().format()
|
||||
], 500)
|
||||
}
|
||||
|
||||
def off() {
|
||||
log.debug "Turning Light 'off'"
|
||||
delayBetween([
|
||||
zwave.basicV1.basicSet(value: 0x00).format(),
|
||||
zwave.basicV1.basicGet().format()
|
||||
], 500)
|
||||
}
|
||||
|
||||
|
||||
def setColor(value) {
|
||||
log.debug "setColor() : ${value}"
|
||||
def myred
|
||||
def mygreen
|
||||
def myblue
|
||||
def hexValue
|
||||
def cmds = []
|
||||
|
||||
if ( value.level == 1 && value.saturation > 20) {
|
||||
def rgb = huesatToRGB(value.hue as Integer, 100)
|
||||
myred = rgb[0] >=128 ? 255 : 0
|
||||
mygreen = rgb[1] >=128 ? 255 : 0
|
||||
myblue = rgb[2] >=128 ? 255 : 0
|
||||
}
|
||||
else if ( value.level > 1 ) {
|
||||
def rgb = huesatToRGB(value.hue as Integer, value.saturation as Integer)
|
||||
myred = rgb[0] >=128 ? 255 : 0
|
||||
mygreen = rgb[1] >=128 ? 255 : 0
|
||||
myblue = rgb[2] >=128 ? 255 : 0
|
||||
}
|
||||
else if (value.hex) {
|
||||
def rgb = value.hex.findAll(/[0-9a-fA-F]{2}/).collect { Integer.parseInt(it, 16) }
|
||||
myred = rgb[0] >=128 ? 255 : 0
|
||||
mygreen = rgb[1] >=128 ? 255 : 0
|
||||
myblue = rgb[2] >=128 ? 255 : 0
|
||||
}
|
||||
else {
|
||||
myred=value.red >=128 ? 255 : 0 // the EZMultiPli has just on/off for each of the 3 channels RGB so convert the 0-255 value into 0 or 255.
|
||||
mygreen=value.green >=128 ? 255 : 0
|
||||
myblue=value.blue>=128 ? 255 : 0
|
||||
}
|
||||
//log.debug "Red: ${myred} Green: ${mygreen} Blue: ${myblue}"
|
||||
//cmds << zwave.colorControlV1.stateSet(stateDataLength: 3, VariantGroup1: [0x02, myred], VariantGroup2:[ 0x03, mygreen], VariantGroup3:[0x04,myblue]).format() // ST support for this command as of 2015/02/23 does not support the color IDs so this command cannot be used.
|
||||
// So instead we'll use these commands to hack around the lack of support of the above command
|
||||
cmds << zwave.basicV1.basicSet(value: 0x00).format() // As of 2015/02/23 ST is not supporting stateSet properly but found this hack that works.
|
||||
if (myred!=0) {
|
||||
cmds << zwave.colorControlV1.startCapabilityLevelChange(capabilityId: 0x02, startState: myred, ignoreStartState: True, updown: True).format()
|
||||
cmds << zwave.colorControlV1.stopStateChange(capabilityId: 0x02).format()
|
||||
}
|
||||
if (mygreen!=0) {
|
||||
cmds << zwave.colorControlV1.startCapabilityLevelChange(capabilityId: 0x03, startState: mygreen, ignoreStartState: True, updown: True).format()
|
||||
cmds << zwave.colorControlV1.stopStateChange(capabilityId: 0x03).format()
|
||||
}
|
||||
if (myblue!=0) {
|
||||
cmds << zwave.colorControlV1.startCapabilityLevelChange(capabilityId: 0x04, startState: myblue, ignoreStartState: True, updown: True).format()
|
||||
cmds << zwave.colorControlV1.stopStateChange(capabilityId: 0x04).format()
|
||||
}
|
||||
cmds << zwave.basicV1.basicGet().format()
|
||||
hexValue = rgbToHex([r:myred, g:mygreen, b:myblue])
|
||||
if(hexValue) sendEvent(name: "color", value: hexValue, displayed: true)
|
||||
delayBetween(cmds, 100)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
||||
// Handles all Z-Wave commands we aren't interested in
|
||||
[:]
|
||||
}
|
||||
|
||||
// ensure we are passing acceptable param values for LiteMin & TempMin configs
|
||||
def checkLiteTempInput(value) {
|
||||
if (value == null) {
|
||||
value=6
|
||||
}
|
||||
def liteTempVal = value.toInteger()
|
||||
switch (liteTempVal) {
|
||||
case { it < 0 }:
|
||||
return 6 // bad value, set to default
|
||||
break
|
||||
case { it > 127 }:
|
||||
return 127 // bad value, greater then MAX, set to MAX
|
||||
break
|
||||
default:
|
||||
return liteTempVal // acceptable value
|
||||
}
|
||||
}
|
||||
|
||||
// ensure we are passing acceptable param value for OnTime config
|
||||
def checkOnTimeInput(value) {
|
||||
if (value == null) {
|
||||
value=2
|
||||
}
|
||||
def onTimeVal = value.toInteger()
|
||||
switch (onTimeVal) {
|
||||
case { it < 0 }:
|
||||
return 2 // bad value set to default
|
||||
break
|
||||
case { it > 127 }:
|
||||
return 127 // bad value, greater then MAX, set to MAX
|
||||
break
|
||||
default:
|
||||
return onTimeVal // acceptable value
|
||||
}
|
||||
}
|
||||
|
||||
// ensure we are passing acceptable param value for OnLevel config
|
||||
def checkOnLevelInput(value) {
|
||||
if (value == null) {
|
||||
value=99
|
||||
}
|
||||
def onLevelVal = value.toInteger()
|
||||
switch (onLevelVal) {
|
||||
case { it < -1 }:
|
||||
return -1 // bad value set to default
|
||||
break
|
||||
case { it > 99 }:
|
||||
return 99 // bad value, greater then MAX, set to MAX
|
||||
break
|
||||
default:
|
||||
return onLevelVal // acceptable value
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ensure we are passing an acceptable param value for TempAdj configs
|
||||
def checkTempAdjInput(value) {
|
||||
if (value == null) {
|
||||
value=0
|
||||
}
|
||||
def tempAdjVal = value.toInteger()
|
||||
switch (tempAdjVal) {
|
||||
case { it < -127 }:
|
||||
return 0 // bad value, set to default
|
||||
break
|
||||
case { it > 128 }:
|
||||
return 128 // bad value, greater then MAX, set to MAX
|
||||
break
|
||||
default:
|
||||
return tempAdjVal // acceptable value
|
||||
}
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
def cmd = []
|
||||
cmd << zwave.switchColorV3.switchColorGet().format()
|
||||
cmd << zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType:1, scale:1).format()
|
||||
cmd << zwave.sensorMultilevelV5.sensorMultilevelGet(sensorType:3, scale:1).format()
|
||||
cmd << zwave.basicV1.basicGet().format()
|
||||
delayBetween(cmd, 1000)
|
||||
}
|
||||
|
||||
def configure() {
|
||||
log.debug "OnTime=${settings.OnTime} OnLevel=${settings.OnLevel} TempAdj=${settings.TempAdj}"
|
||||
def cmd = delayBetween([
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 1, size: 1, scaledConfigurationValue: checkOnTimeInput(settings.OnTime)).format(),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 2, size: 1, scaledConfigurationValue: checkOnLevelInput(settings.OnLevel)).format(),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 3, size: 1, scaledConfigurationValue: checkLiteTempInput(settings.LiteMin)).format(),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 4, size: 1, scaledConfigurationValue: checkLiteTempInput(settings.TempMin)).format(),
|
||||
zwave.configurationV1.configurationSet(parameterNumber: 5, size: 1, scaledConfigurationValue: checkTempAdjInput(settings.TempAdj)).format(),
|
||||
zwave.configurationV1.configurationGet(parameterNumber: 1).format(),
|
||||
zwave.configurationV1.configurationGet(parameterNumber: 2).format(),
|
||||
zwave.configurationV1.configurationGet(parameterNumber: 3).format(),
|
||||
zwave.configurationV1.configurationGet(parameterNumber: 4).format(),
|
||||
zwave.configurationV1.configurationGet(parameterNumber: 5).format()
|
||||
], 100)
|
||||
//log.debug cmd
|
||||
cmd + refresh()
|
||||
}
|
||||
|
||||
def huesatToRGB(float hue, float sat) {
|
||||
while(hue >= 100) hue -= 100
|
||||
int h = (int)(hue / 100 * 6)
|
||||
float f = hue / 100 * 6 - h
|
||||
int p = Math.round(255 * (1 - (sat / 100)))
|
||||
int q = Math.round(255 * (1 - (sat / 100) * f))
|
||||
int t = Math.round(255 * (1 - (sat / 100) * (1 - f)))
|
||||
switch (h) {
|
||||
case 0: return [255, t, p]
|
||||
case 1: return [q, 255, p]
|
||||
case 2: return [p, 255, t]
|
||||
case 3: return [p, q, 255]
|
||||
case 4: return [t, p, 255]
|
||||
case 5: return [255, p, q]
|
||||
}
|
||||
}
|
||||
def rgbToHex(rgb) {
|
||||
def r = hex(rgb.r)
|
||||
def g = hex(rgb.g)
|
||||
def b = hex(rgb.b)
|
||||
def hexColor = "#${r}${g}${b}"
|
||||
|
||||
hexColor
|
||||
}
|
||||
private hex(value, width=2) {
|
||||
def s = new BigInteger(Math.round(value).toString()).toString(16)
|
||||
while (s.size() < width) {
|
||||
s = "0" + s
|
||||
}
|
||||
s
|
||||
}
|
||||
2
devicetypes/osotech/plantlink.src/.st-ignore
Normal file
2
devicetypes/osotech/plantlink.src/.st-ignore
Normal file
@@ -0,0 +1,2 @@
|
||||
.st-ignore
|
||||
README.md
|
||||
33
devicetypes/osotech/plantlink.src/README.md
Normal file
33
devicetypes/osotech/plantlink.src/README.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Osotech Plant Link
|
||||
|
||||
Cloud Execution
|
||||
|
||||
Works with:
|
||||
|
||||
* [OSO Technologies PlantLink Soil Moisture Sensor](https://www.smartthings.com/works-with-smartthings/oso-technologies/oso-technologies-plantlink-soil-moisture-sensor)
|
||||
|
||||
## Table of contents
|
||||
|
||||
* [Capabilities](#capabilities)
|
||||
* [Health](#device-health)
|
||||
* [Troubleshooting](#troubleshooting)
|
||||
|
||||
## Capabilities
|
||||
|
||||
* **Sensor** - detects sensor events
|
||||
* **Health Check** - indicates ability to get device health notifications
|
||||
|
||||
## Device Health
|
||||
|
||||
Plant Link sensor is a ZigBee sleepy device and checks in every 15 minutes.
|
||||
Device-Watch allows 2 check-in misses from device plus some lag time. So Check-in interval = (2*15 + 2)mins = 32 mins.
|
||||
|
||||
* __32min__ checkInterval
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the sensor is out of range.
|
||||
Pairing needs to be tried again by placing the sensor closer to the hub.
|
||||
Instructions related to pairing, resetting and removing the different motion sensors from SmartThings can be found in the following links
|
||||
for the different models:
|
||||
* [OSO Technologies PlantLink Soil Moisture Sensor Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/206868986-PlantLink-Soil-Moisture-Sensor)
|
||||
@@ -24,6 +24,7 @@ import groovy.json.JsonBuilder
|
||||
metadata {
|
||||
definition (name: "PlantLink", namespace: "OsoTech", author: "Oso Technologies") {
|
||||
capability "Sensor"
|
||||
capability "Health Check"
|
||||
|
||||
command "setStatusIcon"
|
||||
command "setPlantFuelLevel"
|
||||
@@ -70,6 +71,16 @@ metadata {
|
||||
}
|
||||
}
|
||||
|
||||
def updated() {
|
||||
// Device-Watch allows 2 check-in misses from device
|
||||
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
}
|
||||
|
||||
def installed() {
|
||||
// Device-Watch allows 2 check-in misses from device
|
||||
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
}
|
||||
|
||||
def setStatusIcon(value){
|
||||
def status = ''
|
||||
switch (value) {
|
||||
@@ -161,4 +172,4 @@ def parseDescriptionAsMap(description) {
|
||||
map += []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
/**
|
||||
* SmartSense Open/Closed Sensor
|
||||
*
|
||||
* Copyright 2014 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.
|
||||
*
|
||||
*/
|
||||
import physicalgraph.zigbee.clusters.iaszone.ZoneStatus
|
||||
|
||||
metadata {
|
||||
definition(name: "Sensative ZB Strips", namespace: "sensative", 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: "Sensative", model: "Strips-001"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05", outClusters: "0019", manufacturer: "Sensative", model: "Strips-001"
|
||||
fingerprint inClusters: "0000,0001,0003,0020,0402,0500,0B05", outClusters: "0019", manufacturer: "Sensativee", model: "Strips-001", deviceJoinName: "Sensative ZB Strips"
|
||||
|
||||
}
|
||||
|
||||
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") {
|
||||
attributeState "open", label: '${name}', icon: "st.contact.contact.open", backgroundColor: "#e86d13"
|
||||
attributeState "closed", label: '${name}', icon: "st.contact.contact.closed", backgroundColor: "#00A0DC"
|
||||
}
|
||||
}
|
||||
|
||||
valueTile("temperature", "device.temperature", inactiveLabel: false, width: 2, height: 2) {
|
||||
state "temperature", label: '${currentValue}°F',
|
||||
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", decoration: "flat", inactiveLabel: false, width: 2, height: 2) {
|
||||
state "battery", label: 'Battery\n${currentValue}%', unit: ""
|
||||
}
|
||||
|
||||
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "default", action: "refresh.refresh", icon: "st.secondary.refresh"
|
||||
}
|
||||
|
||||
main(["contact"])
|
||||
details(["contact", "battery", "temperature", "refresh"])
|
||||
}
|
||||
}
|
||||
|
||||
def parse(String description) {
|
||||
log.debug "description: $description"
|
||||
|
||||
Map map = zigbee.getEvent(description)
|
||||
if (!map) {
|
||||
if (description?.startsWith('zone status')) {
|
||||
map = parseIasMessage(description)
|
||||
} else {
|
||||
Map descMap = zigbee.parseDescriptionAsMap(description)
|
||||
if (descMap?.clusterInt == 0x0001 && descMap.commandInt != 0x07 && descMap?.value) {
|
||||
map = getBatteryResult(Integer.parseInt(descMap.value, 16))
|
||||
} else if (descMap?.clusterInt == zigbee.TEMPERATURE_MEASUREMENT_CLUSTER && descMap.commandInt == 0x07) {
|
||||
if (descMap.data[0] == "00") {
|
||||
log.debug "TEMP REPORTING CONFIG RESPONSE: $descMap"
|
||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
} else {
|
||||
log.warn "TEMP REPORTING CONFIG FAILED- error code: ${descMap.data[0]}"
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (map.name == "temperature") {
|
||||
if (tempOffset) {
|
||||
map.value = (int) map.value + (int) tempOffset
|
||||
}
|
||||
map.descriptionText = temperatureScale == 'C' ? '{{ device.displayName }} was {{ value }}°C' : '{{ device.displayName }} was {{ value }}°F'
|
||||
map.translatable = true
|
||||
}
|
||||
|
||||
log.debug "Parse returned $map"
|
||||
def result = map ? createEvent(map) : [:]
|
||||
|
||||
if (description?.startsWith('enroll request')) {
|
||||
List cmds = zigbee.enrollResponse()
|
||||
log.debug "enroll response: ${cmds}"
|
||||
result = cmds?.collect { new physicalgraph.device.HubAction(it) }
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
private Map parseIasMessage(String description) {
|
||||
ZoneStatus zs = zigbee.parseZoneStatus(description)
|
||||
return zs.isAlarm1Set() ? getContactResult('open') : getContactResult('closed')
|
||||
}
|
||||
|
||||
private Map getBatteryResult(rawValue) {
|
||||
log.debug 'Battery'
|
||||
def linkText = getLinkText(device)
|
||||
|
||||
def result = [:]
|
||||
|
||||
def volts = rawValue / 10
|
||||
if (!(rawValue == 0 || rawValue == 255)) {
|
||||
def minVolts = 2.1
|
||||
def maxVolts = 3.0
|
||||
def pct = (volts - minVolts) / (maxVolts - minVolts)
|
||||
def roundedPct = Math.round(pct * 100)
|
||||
if (roundedPct <= 0)
|
||||
roundedPct = 1
|
||||
result.value = Math.min(100, roundedPct)
|
||||
result.descriptionText = "${linkText} battery was ${result.value}%"
|
||||
result.name = 'battery'
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private Map getContactResult(value) {
|
||||
log.debug 'Contact Status'
|
||||
def linkText = getLinkText(device)
|
||||
def descriptionText = "${linkText} was ${value == 'open' ? 'opened' : 'closed'}"
|
||||
return [
|
||||
name : 'contact',
|
||||
value : value,
|
||||
descriptionText: descriptionText
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* PING is used by Device-Watch in attempt to reach the Device
|
||||
* */
|
||||
def ping() {
|
||||
return zigbee.readAttribute(0x001, 0x0020) // Read the Battery Level
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
log.debug "Refreshing Temperature and Battery"
|
||||
def refreshCmds = zigbee.readAttribute(zigbee.TEMPERATURE_MEASUREMENT_CLUSTER, 0x0000) +
|
||||
zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0020)
|
||||
|
||||
return refreshCmds + zigbee.enrollResponse()
|
||||
}
|
||||
|
||||
def configure() {
|
||||
// Device-Watch allows 2 check-in misses from device + ping (plus 1 min lag time)
|
||||
// enrolls with default periodic reporting until newer 5 min interval is confirmed
|
||||
sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
|
||||
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
||||
|
||||
// temperature minReportTime 30 seconds, maxReportTime 5 min. Reporting interval if no activity
|
||||
// battery minReport 30 seconds, maxReportTime 6 hrs by default
|
||||
return refresh() + zigbee.batteryConfig() + zigbee.temperatureConfig(30, 300) // send refresh cmds as part of config
|
||||
}
|
||||
2
devicetypes/smartthings/arrival-sensor-ha.src/.st-ignore
Normal file
2
devicetypes/smartthings/arrival-sensor-ha.src/.st-ignore
Normal file
@@ -0,0 +1,2 @@
|
||||
.st-ignore
|
||||
README.md
|
||||
50
devicetypes/smartthings/arrival-sensor-ha.src/README.md
Normal file
50
devicetypes/smartthings/arrival-sensor-ha.src/README.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# Arrival Sensor HA (2016+ Model)
|
||||
|
||||
Cloud Execution
|
||||
|
||||
Works with:
|
||||
|
||||
* [Samsung SmartThings Arrival Sensor](https://support.smartthings.com/hc/en-us/articles/212417083-Samsung-SmartThings-Arrival-Sensor)
|
||||
|
||||
## Table of contents
|
||||
|
||||
* [Capabilities](#capabilities)
|
||||
* [Health](#device-health)
|
||||
* [Battery](#battery)
|
||||
* [Troubleshooting](#troubleshooting)
|
||||
|
||||
|
||||
## Capabilities
|
||||
|
||||
* **Tone** - beep command to allow an audible tone
|
||||
* **Actuator** - device has commands
|
||||
* **Presence Sensor** - device tells presence with enum - {present, not present}
|
||||
* **Sensor** - device has attributes
|
||||
* **Battery** - defines device uses a battery
|
||||
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
|
||||
* **Health Check** - indicates ability to get device health notifications
|
||||
|
||||
|
||||
## Device Health
|
||||
|
||||
Arrival Sensor ZigBee is an untracked device. Sends broadcast of battery level every 20 seconds.
|
||||
Disconnects when Hub goes OFFLINE.
|
||||
|
||||
|
||||
## Battery
|
||||
|
||||
Uses 1 CR2032 Battery
|
||||
|
||||
* [Changing the Battery](https://support.smartthings.com/hc/en-us/articles/200907400-How-to-change-the-battery-in-the-SmartSense-Presence-Sensor-and-Samsung-SmartThings-Arrival-Sensor)
|
||||
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the arrival sensor is out of range.
|
||||
Pairing needs to be tried again by placing the sensor closer to the hub.
|
||||
|
||||
* [Samsung SmartThings Arrival Sensor Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/205382134-Samsung-SmartThings-Arrival-Sensor-2015-model-)
|
||||
|
||||
If the arrival sensor doesn't update its status, here are a few things you can try to debug.
|
||||
|
||||
* [Troubleshooting: Samsung SmartThings Arrival Sensor won't update its status](https://support.smartthings.com/hc/en-us/articles/200846514-Troubleshooting-Samsung-SmartThings-Arrival-Sensor-won-t-update-its-status)
|
||||
@@ -1,5 +1,7 @@
|
||||
import groovy.json.JsonOutput
|
||||
|
||||
/**
|
||||
* Copyright 2016 SmartThings
|
||||
* Copyright 2017 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:
|
||||
@@ -19,6 +21,7 @@ metadata {
|
||||
capability "Sensor"
|
||||
capability "Battery"
|
||||
capability "Configuration"
|
||||
capability "Health Check"
|
||||
|
||||
fingerprint inClusters: "0000,0001,0003,000F,0020", outClusters: "0003,0019",
|
||||
manufacturer: "SmartThings", model: "tagv4", deviceJoinName: "Arrival Sensor"
|
||||
@@ -58,8 +61,13 @@ def updated() {
|
||||
startTimer()
|
||||
}
|
||||
|
||||
def installed() {
|
||||
// Arrival sensors only goes OFFLINE when Hub is off
|
||||
sendEvent(name: "DeviceWatch-Enroll", value: JsonOutput.toJson([protocol: "zigbee", scheme:"untracked"]), displayed: false)
|
||||
}
|
||||
|
||||
def configure() {
|
||||
def cmds = zigbee.batteryConfig(20, 20, 0x01)
|
||||
def cmds = zigbee.readAttribute(zigbee.POWER_CONFIGURATION_CLUSTER, 0x0020) + zigbee.batteryConfig(20, 20, 0x01)
|
||||
log.debug "configure -- cmds: ${cmds}"
|
||||
return cmds
|
||||
}
|
||||
@@ -166,4 +174,4 @@ def checkPresenceCallback() {
|
||||
if (timeSinceLastCheckin >= theCheckInterval) {
|
||||
handlePresenceEvent(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
2
devicetypes/smartthings/arrival-sensor.src/.st-ignore
Normal file
2
devicetypes/smartthings/arrival-sensor.src/.st-ignore
Normal file
@@ -0,0 +1,2 @@
|
||||
.st-ignore
|
||||
README.md
|
||||
49
devicetypes/smartthings/arrival-sensor.src/README.md
Normal file
49
devicetypes/smartthings/arrival-sensor.src/README.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Arrival Sensor (2015 Model)
|
||||
|
||||
Cloud Execution
|
||||
|
||||
Works with:
|
||||
|
||||
* [Arrival Sensor](https://www.smartthings.com/products/samsung-smartthings-arrival-sensor)
|
||||
|
||||
## Table of contents
|
||||
|
||||
* [Capabilities](#capabilities)
|
||||
* [Health](#device-health)
|
||||
* [Battery](#battery)
|
||||
* [Troubleshooting](#troubleshooting)
|
||||
|
||||
|
||||
## Capabilities
|
||||
|
||||
* **Tone** - beep command to allow an audible tone
|
||||
* **Actuator** - device has commands
|
||||
* **Signal Strength** - device can read the strength of signal- lqi: Link Quality Indication, rssi: Received Signal Strength Indication
|
||||
* **Presence Sensor** - device tells presence with enum - {present, not present}
|
||||
* **Sensor** - device has attributes
|
||||
* **Battery** - defines device uses a battery
|
||||
* **Health Check** - indicates ability to get device health notifications
|
||||
|
||||
|
||||
## Device Health
|
||||
|
||||
Arrival Sensor ZigBee is an untracked device. Disconnects when Hub goes OFFLINE.
|
||||
|
||||
|
||||
## Battery
|
||||
|
||||
Uses 1 CR2032 Battery
|
||||
|
||||
* [Changing the Battery](https://support.smartthings.com/hc/en-us/articles/200907400-How-to-change-the-battery-in-the-SmartSense-Presence-Sensor-and-Samsung-SmartThings-Arrival-Sensor)
|
||||
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the arrival sensor is out of range.
|
||||
Pairing needs to be tried again by placing the sensor closer to the hub.
|
||||
|
||||
* [Samsung SmartThings Arrival Sensor Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/205382134-Samsung-SmartThings-Arrival-Sensor-2015-model-)
|
||||
|
||||
If the arrival sensor doesn't update its status, here are a few things you can try to debug.
|
||||
|
||||
* [Troubleshooting: Samsung SmartThings Arrival Sensor won't update its status](https://support.smartthings.com/hc/en-us/articles/200846514-Troubleshooting-Samsung-SmartThings-Arrival-Sensor-won-t-update-its-status)
|
||||
@@ -1,3 +1,5 @@
|
||||
import groovy.json.JsonOutput
|
||||
|
||||
/**
|
||||
* Copyright 2015 SmartThings
|
||||
*
|
||||
@@ -19,6 +21,7 @@ metadata {
|
||||
capability "Presence Sensor"
|
||||
capability "Sensor"
|
||||
capability "Battery"
|
||||
capability "Health Check"
|
||||
|
||||
fingerprint profileId: "FC01", deviceId: "019A"
|
||||
fingerprint profileId: "FC01", deviceId: "0131", inClusters: "0000,0003", outClusters: "0003"
|
||||
@@ -111,6 +114,11 @@ def beep() {
|
||||
]
|
||||
}
|
||||
|
||||
def installed() {
|
||||
// Arrival sensors only goes OFFLINE when Hub is off
|
||||
sendEvent(name: "DeviceWatch-Enroll", value: JsonOutput.toJson([protocol: "zigbee", scheme:"untracked"]), displayed: false)
|
||||
}
|
||||
|
||||
def parse(String description) {
|
||||
def results
|
||||
if (isBatteryMessage(description)) {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
*
|
||||
*/
|
||||
metadata {
|
||||
definition (name: "Dimmer Switch", namespace: "smartthings", author: "SmartThings", ocfDeviceType: "oic.d.switch") {
|
||||
definition (name: "Dimmer Switch", namespace: "smartthings", author: "SmartThings", ocfDeviceType: "oic.d.light") {
|
||||
capability "Switch Level"
|
||||
capability "Actuator"
|
||||
capability "Indicator"
|
||||
@@ -23,9 +23,9 @@ metadata {
|
||||
capability "Health Check"
|
||||
capability "Light"
|
||||
|
||||
fingerprint mfr:"0063", prod:"4457", deviceJoinName: "GE In-Wall Smart Dimmer "
|
||||
fingerprint mfr:"0063", prod:"4944", deviceJoinName: "GE In-Wall Smart Dimmer "
|
||||
fingerprint mfr:"0063", prod:"5044", deviceJoinName: "GE Plug-In Smart Dimmer "
|
||||
fingerprint mfr:"0063", prod:"4457", deviceJoinName: "GE In-Wall Smart Dimmer"
|
||||
fingerprint mfr:"0063", prod:"4944", deviceJoinName: "GE In-Wall Smart Dimmer"
|
||||
fingerprint mfr:"0063", prod:"5044", deviceJoinName: "GE Plug-In Smart Dimmer"
|
||||
fingerprint mfr:"0063", prod:"4944", model:"3034", deviceJoinName: "GE In-Wall Smart Fan Control"
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ Works with:
|
||||
|
||||
## Device Health
|
||||
|
||||
Plant Link sensor is a Z-wave sleepy device and checks in every 15 minutes.
|
||||
Plant Link sensor is a ZigBee sleepy device and checks in every 15 minutes.
|
||||
Device-Watch allows 2 check-in misses from device plus some lag time. So Check-in interval = (2*15 + 2)mins = 32 mins.
|
||||
|
||||
* __32min__ checkInterval
|
||||
|
||||
2
devicetypes/smartthings/smartalert-siren.src/.st-ignore
Normal file
2
devicetypes/smartthings/smartalert-siren.src/.st-ignore
Normal file
@@ -0,0 +1,2 @@
|
||||
.st-ignore
|
||||
README.md
|
||||
39
devicetypes/smartthings/smartalert-siren.src/README.md
Normal file
39
devicetypes/smartthings/smartalert-siren.src/README.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Smartalert Siren
|
||||
|
||||
Cloud Execution
|
||||
|
||||
Works with:
|
||||
|
||||
* [FortrezZ Siren Strobe Alarm](https://www.smartthings.com/works-with-smartthings/other/fortrezz-water-valve)
|
||||
|
||||
## Table of contents
|
||||
|
||||
* [Capabilities](#capabilities)
|
||||
* [Health](#device-health)
|
||||
* [Troubleshooting](#troubleshooting)
|
||||
|
||||
## Capabilities
|
||||
|
||||
* **Actuator** - represents that a Device has commands
|
||||
* **Switch** - can detect state (possible values: on/off)
|
||||
* **Sensor** - detects sensor events
|
||||
* **Alarm** - allows for interacting with devices that serve as alarms
|
||||
* **Health Check** - indicates ability to get device health notifications
|
||||
|
||||
## Device Health
|
||||
|
||||
FortrezZ Siren Strobe Alarm is polled by the hub.
|
||||
As of hubCore version 0.14.38 the hub sends up reports every 15 minutes regardless of whether the state changed.
|
||||
Device-Watch allows 2 check-in misses from device plus some lag time. So Check-in interval = (2*15 + 2)mins = 32 mins.
|
||||
Not to mention after going OFFLINE when the device is plugged back in, it might take a considerable amount of time for
|
||||
the device to appear as ONLINE again. This is because if this listening device does not respond to two poll requests in a row,
|
||||
it is not polled for 5 minutes by the hub. This can delay up the process of being marked ONLINE by quite some time.
|
||||
|
||||
* __32min__ checkInterval
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
|
||||
Pairing needs to be tried again by placing the device closer to the hub.
|
||||
Instructions related to pairing, resetting and removing the device from SmartThings can be found in the following link:
|
||||
* [FortrezZ Siren Strobe Alarm Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/202294760-FortrezZ-Siren-Strobe-Alarm)
|
||||
@@ -21,10 +21,12 @@ metadata {
|
||||
capability "Switch"
|
||||
capability "Sensor"
|
||||
capability "Alarm"
|
||||
capability "Health Check"
|
||||
|
||||
command "test"
|
||||
|
||||
fingerprint deviceId: "0x1100", inClusters: "0x26,0x71"
|
||||
fingerprint mfr:"0084", prod:"0313", model:"010B", deviceJoinName: "FortrezZ Siren Strobe Alarm"
|
||||
}
|
||||
|
||||
simulator {
|
||||
@@ -68,6 +70,16 @@ metadata {
|
||||
}
|
||||
}
|
||||
|
||||
def installed() {
|
||||
// Device-Watch simply pings if no device events received for 32min(checkInterval)
|
||||
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
|
||||
}
|
||||
|
||||
def updated() {
|
||||
// Device-Watch simply pings if no device events received for 32min(checkInterval)
|
||||
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
|
||||
}
|
||||
|
||||
def on() {
|
||||
[
|
||||
zwave.basicV1.basicSet(value: 0xFF).format(),
|
||||
@@ -149,3 +161,10 @@ def createEvents(physicalgraph.zwave.commands.basicv1.BasicReport cmd)
|
||||
def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
||||
log.warn "UNEXPECTED COMMAND: $cmd"
|
||||
}
|
||||
|
||||
/**
|
||||
* PING is used by Device-Watch in attempt to reach the Device
|
||||
* */
|
||||
def ping() {
|
||||
secure(zwave.basicV1.basicGet())
|
||||
}
|
||||
@@ -33,7 +33,6 @@ metadata {
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3320"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3321"
|
||||
fingerprint inClusters: "0000,0001,0003,0402,0500,0020,0B05,FC02", outClusters: "0019", manufacturer: "CentraLite", model: "3321-S", deviceJoinName: "Multipurpose Sensor"
|
||||
fingerprint inClusters: "0000,0001,0003,0020,0402,0500,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3323-G", deviceJoinName: "Centralite Micro Door Sensor"
|
||||
fingerprint inClusters: "0000,0001,0003,000F,0020,0402,0500,FC02", outClusters: "0019", manufacturer: "SmartThings", model: "multiv4", deviceJoinName: "Multipurpose Sensor"
|
||||
|
||||
attribute "status", "string"
|
||||
@@ -192,6 +191,10 @@ private List<Map> parseAxis(List<Map> attrData) {
|
||||
def y = hexToSignedInt(attrData.find { it.attrInt == 0x0013 }?.value)
|
||||
def z = hexToSignedInt(attrData.find { it.attrInt == 0x0014 }?.value)
|
||||
|
||||
if ([x, y ,z].any { it == null }) {
|
||||
return []
|
||||
}
|
||||
|
||||
def xyzResults = [:]
|
||||
if (device.getDataValue("manufacturer") == "SmartThings") {
|
||||
// This mapping matches the current behavior of the Device Handler for the Centralite sensors
|
||||
@@ -372,6 +375,10 @@ def updated() {
|
||||
}
|
||||
|
||||
private hexToSignedInt(hexVal) {
|
||||
if (!hexVal) {
|
||||
return null
|
||||
}
|
||||
|
||||
def unsignedVal = hexToInt(hexVal)
|
||||
unsignedVal > 32767 ? unsignedVal - 65536 : unsignedVal
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ metadata {
|
||||
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"
|
||||
fingerprint inClusters: "0000,0001,0003,0020,0402,0500,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3323-G", deviceJoinName: "Centralite Micro Door Sensor"
|
||||
}
|
||||
|
||||
simulator {
|
||||
|
||||
@@ -70,19 +70,27 @@ def parse(String description) {
|
||||
else {
|
||||
sendEvent(event)
|
||||
}
|
||||
}
|
||||
else {
|
||||
def cluster = zigbee.parse(description)
|
||||
if (cluster && cluster.clusterId == 0x0006 && cluster.command == 0x07) {
|
||||
if (cluster.data[0] == 0x00){
|
||||
} else {
|
||||
def descMap = zigbee.parseDescriptionAsMap(description)
|
||||
if (descMap && descMap.clusterInt == 0x0006 && descMap.commandInt == 0x07) {
|
||||
if (descMap.data[0] == "00") {
|
||||
log.debug "ON/OFF REPORTING CONFIG RESPONSE: " + cluster
|
||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
log.warn "ON/OFF REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else if (device.getDataValue("manufacturer") == "sengled" && descMap && descMap.clusterInt == 0x0008 && descMap.attrInt == 0x0000) {
|
||||
// This is being done because the sengled element touch incorrectly uses the value 0xFF for the max level.
|
||||
// Per the ZCL spec for the UINT8 data type 0xFF is an invalid value, and 0xFE should be the max. Here we
|
||||
// manually handle the invalid attribute value since it will be ignored by getEvent as an invalid value.
|
||||
// We also set the level of the bulb to 0xFE so future level reports will be 0xFE until it is changed by
|
||||
// something else.
|
||||
if (descMap.value.toUpperCase() == "FF") {
|
||||
descMap.value = "FE"
|
||||
}
|
||||
sendHubCommand(zigbee.command(zigbee.LEVEL_CONTROL_CLUSTER, 0x00, "FE0000").collect { new physicalgraph.device.HubAction(it) }, 0)
|
||||
sendEvent(zigbee.getEventFromAttrData(descMap.clusterInt, descMap.attrInt, descMap.encoding, descMap.value))
|
||||
} else {
|
||||
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
||||
log.debug zigbee.parseDescriptionAsMap(description)
|
||||
}
|
||||
@@ -114,9 +122,17 @@ def refresh() {
|
||||
|
||||
def configure() {
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
|
||||
def cmds = []
|
||||
if (device.getDataValue("manufacturer") == "sengled") {
|
||||
def startLevel = 0xFE
|
||||
if ((device.currentState("level")?.value != null)) {
|
||||
startLevel = Math.round(Integer.parseInt(device.currentState("level").value) * 0xFE / 100)
|
||||
}
|
||||
// Level Control Cluster, command Move to Level, level start level, transition time 0
|
||||
cmds << zigbee.command(zigbee.LEVEL_CONTROL_CLUSTER, 0x00, sprintf("%02X0000", startLevel))
|
||||
}
|
||||
// Device-Watch allows 2 check-in misses from device + ping (plus 1 min lag time)
|
||||
// enrolls with default periodic reporting until newer 5 min interval is confirmed
|
||||
sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||
refresh()
|
||||
cmds + refresh()
|
||||
}
|
||||
|
||||
@@ -30,6 +30,9 @@ metadata {
|
||||
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 mfr:"0086", prod:"0002", model:"001D", deviceJoinName: "Aeon Labs Door/Window Sensor (Gen 5)"
|
||||
fingerprint mfr:"014A", prod:"0001", model:"0002", deviceJoinName: "Ecolink Door/Window Sensor"
|
||||
fingerprint mfr:"0086", prod:"0102", model:"0070", deviceJoinName: "Aeon Labs Door/Window Sensor 6"
|
||||
fingerprint mfr:"011A", prod:"0601", model:"0903", deviceJoinName: "Enerwave Magnetic Door/Window Sensor"
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Z-Wave Switch
|
||||
# Z-Wave Lock
|
||||
|
||||
Cloud Execution
|
||||
|
||||
@@ -6,7 +6,6 @@ Works with:
|
||||
|
||||
* [Yale Key Free Touchscreen Deadbolt (YRD240)](https://www.smartthings.com/works-with-smartthings/yale/yale-key-free-touchscreen-deadbolt-yrd240)
|
||||
|
||||
|
||||
## Table of contents
|
||||
|
||||
* [Capabilities](#capabilities)
|
||||
@@ -41,5 +40,3 @@ If the device doesn't pair when trying from the SmartThings mobile app, it is po
|
||||
Pairing needs to be tried again by placing the device closer to the hub.
|
||||
Instructions related to pairing, resetting and removing the device from SmartThings can be found in the following link:
|
||||
* [General Z-Wave/ZigBee Yale Lock Troubleshooting](https://support.smartthings.com/hc/en-us/articles/205138400-How-to-connect-Yale-locks)
|
||||
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
*
|
||||
*/
|
||||
metadata {
|
||||
definition (name: "Z-Wave Metering Dimmer", namespace: "smartthings", author: "SmartThings", ocfDeviceType: "oic.d.switch") {
|
||||
definition (name: "Z-Wave Metering Dimmer", namespace: "smartthings", author: "SmartThings", ocfDeviceType: "oic.d.light") {
|
||||
capability "Switch"
|
||||
capability "Polling"
|
||||
capability "Power Meter"
|
||||
|
||||
@@ -22,9 +22,9 @@ metadata {
|
||||
capability "Health Check"
|
||||
capability "Light"
|
||||
|
||||
fingerprint mfr:"0063", prod:"4952", deviceJoinName: "Z-Wave Wall Switch"
|
||||
fingerprint mfr:"0063", prod:"5257", deviceJoinName: "Z-Wave Wall Switch"
|
||||
fingerprint mfr:"0063", prod:"5052", deviceJoinName: "Z-Wave Plug-In Switch"
|
||||
fingerprint mfr:"0063", prod:"4952", deviceJoinName: "GE Wall Switch"
|
||||
fingerprint mfr:"0063", prod:"5257", deviceJoinName: "GE Wall Switch"
|
||||
fingerprint mfr:"0063", prod:"5052", deviceJoinName: "GE Plug-In Switch"
|
||||
fingerprint mfr:"0113", prod:"5257", deviceJoinName: "Z-Wave Wall Switch"
|
||||
}
|
||||
|
||||
|
||||
@@ -52,15 +52,15 @@ definition(
|
||||
//Device Inputs
|
||||
preferences {
|
||||
section("Allow OpenT2T to control these things...") {
|
||||
input "contactSensors", "capability.contactSensor", title: "Which Contact Sensors", multiple: true, required: false
|
||||
input "garageDoors", "capability.garageDoorControl", title: "Which Garage Doors?", multiple: true, required: false
|
||||
input "locks", "capability.lock", title: "Which Locks?", multiple: true, required: false
|
||||
input "cameras", "capability.videoCapture", title: "Which Cameras?", multiple: true, required: false
|
||||
input "motionSensors", "capability.motionSensor", title: "Which Motion Sensors?", multiple: true, required: false
|
||||
input "presenceSensors", "capability.presenceSensor", title: "Which Presence Sensors", multiple: true, required: false
|
||||
input "switches", "capability.switch", title: "Which Switches and Lights?", multiple: true, required: false
|
||||
input "thermostats", "capability.thermostat", title: "Which Thermostat?", multiple: true, required: false
|
||||
input "waterSensors", "capability.waterSensor", title: "Which Water Leak Sensors?", multiple: true, required: false
|
||||
input "contactSensors", "capability.contactSensor", title: "Which Contact Sensors", multiple: true, required: false, hideWhenEmpty: true
|
||||
input "garageDoors", "capability.garageDoorControl", title: "Which Garage Doors?", multiple: true, required: false, hideWhenEmpty: true
|
||||
input "locks", "capability.lock", title: "Which Locks?", multiple: true, required: false, hideWhenEmpty: true
|
||||
input "cameras", "capability.videoCapture", title: "Which Cameras?", multiple: true, required: false, hideWhenEmpty: true
|
||||
input "motionSensors", "capability.motionSensor", title: "Which Motion Sensors?", multiple: true, required: false, hideWhenEmpty: true
|
||||
input "presenceSensors", "capability.presenceSensor", title: "Which Presence Sensors", multiple: true, required: false, hideWhenEmpty: true
|
||||
input "switches", "capability.switch", title: "Which Switches and Lights?", multiple: true, required: false, hideWhenEmpty: true
|
||||
input "thermostats", "capability.thermostat", title: "Which Thermostat?", multiple: true, required: false, hideWhenEmpty: true
|
||||
input "waterSensors", "capability.waterSensor", title: "Which Water Leak Sensors?", multiple: true, required: false, hideWhenEmpty: true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,16 +80,6 @@ def getInputs() {
|
||||
|
||||
//API external Endpoints
|
||||
mappings {
|
||||
path("/subscriptionURL/:url") {
|
||||
action: [
|
||||
PUT: "updateEndpointURL"
|
||||
]
|
||||
}
|
||||
path("/connectionId/:connId") {
|
||||
action: [
|
||||
PUT: "updateConnectionId"
|
||||
]
|
||||
}
|
||||
path("/devices") {
|
||||
action: [
|
||||
GET: "getDevices"
|
||||
@@ -105,33 +95,52 @@ mappings {
|
||||
PUT: "updateDevice"
|
||||
]
|
||||
}
|
||||
path("/subscription/:id") {
|
||||
path("/deviceSubscription") {
|
||||
action: [
|
||||
POST: "registerDeviceChange",
|
||||
DELETE: "unregisterDeviceChange"
|
||||
]
|
||||
}
|
||||
path("/locationSubscription") {
|
||||
action: [
|
||||
POST: "registerDeviceGraph",
|
||||
DELETE: "unregisterDeviceGraph"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
def installed() {
|
||||
log.debug "Installed with settings: ${settings}"
|
||||
log.debug "Installing with settings: ${settings}"
|
||||
initialize()
|
||||
}
|
||||
|
||||
def updated() {
|
||||
log.debug "Updated with settings: ${settings}"
|
||||
log.debug "Updating with settings: ${settings}"
|
||||
if(state.deviceSubscriptionMap == null){
|
||||
state.deviceSubscriptionMap = [:]
|
||||
log.debug "deviceSubscriptionMap created."
|
||||
}
|
||||
if( state.locationSubscriptionMap == null){
|
||||
state.locationSubscriptionMap = [:]
|
||||
log.debug "locationSubscriptionMap created."
|
||||
}
|
||||
unsubscribe()
|
||||
registerSubscriptions()
|
||||
registerAllDeviceSubscriptions()
|
||||
}
|
||||
|
||||
def initialize() {
|
||||
state.connectionId = ""
|
||||
state.endpointURL = "https://ifs.windows-int.com/v1/cb/81C7E77B-EABC-488A-B2BF-FEC42F0DABD2/notify"
|
||||
registerSubscriptions()
|
||||
log.debug "Initializing with settings: ${settings}"
|
||||
state.deviceSubscriptionMap = [:]
|
||||
log.debug "deviceSubscriptionMap created."
|
||||
registerAllDeviceSubscriptions()
|
||||
state.locationSubscriptionMap = [:]
|
||||
log.debug "locationSubscriptionMap created."
|
||||
}
|
||||
|
||||
/*** Subscription Functions ***/
|
||||
|
||||
//Subscribe events for all devices
|
||||
def registerSubscriptions() {
|
||||
def registerAllDeviceSubscriptions() {
|
||||
registerChangeHandler(inputs)
|
||||
}
|
||||
|
||||
@@ -140,101 +149,195 @@ def registerChangeHandler(myList) {
|
||||
myList.each { myDevice ->
|
||||
def theAtts = myDevice.supportedAttributes
|
||||
theAtts.each {att ->
|
||||
subscribe(myDevice, att.name, eventHandler)
|
||||
log.info "Registering ${myDevice.displayName}.${att.name}"
|
||||
subscribe(myDevice, att.name, deviceEventHandler)
|
||||
log.info "Registering for ${myDevice.displayName}.${att.name}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Endpoints function: Subscribe to events from a specific device
|
||||
def registerDeviceChange() {
|
||||
def myDevice = findDevice(params.id)
|
||||
def subscriptionEndpt = params.subscriptionURL
|
||||
def deviceId = params.deviceId
|
||||
def myDevice = findDevice(deviceId)
|
||||
if( myDevice == null ){
|
||||
httpError(404, "Cannot find device with device ID ${deviceId}.")
|
||||
}
|
||||
|
||||
def theAtts = myDevice.supportedAttributes
|
||||
try {
|
||||
theAtts.each {att ->
|
||||
subscribe(myDevice, att.name, eventHandler)
|
||||
log.info "Registering ${myDevice.displayName}.${att.name}"
|
||||
subscribe(myDevice, att.name, deviceEventHandler)
|
||||
}
|
||||
log.info "Subscribing for ${myDevice.displayName}"
|
||||
|
||||
if(subscriptionEndpt != null){
|
||||
if(state.deviceSubscriptionMap[deviceId] == null){
|
||||
state.deviceSubscriptionMap.put(deviceId, [subscriptionEndpt])
|
||||
log.info "Added subscription URL: ${subscriptionEndpt} for ${myDevice.displayName}"
|
||||
} else if (!state.deviceSubscriptionMap[deviceId].contains(subscriptionEndpt)){
|
||||
state.deviceSubscriptionMap[deviceId] << subscriptionEndpt
|
||||
log.info "Added subscription URL: ${subscriptionEndpt} for ${myDevice.displayName}"
|
||||
}
|
||||
}
|
||||
return ["succeed"]
|
||||
} catch (e) {
|
||||
httpError(500, "something went wrong: $e")
|
||||
}
|
||||
|
||||
log.info "Current subscription map is ${state.deviceSubscriptionMap}"
|
||||
return ["succeed"]
|
||||
}
|
||||
|
||||
//Endpoints function: Unsubscribe to events from a specific device
|
||||
def unregisterDeviceChange() {
|
||||
def myDevice = findDevice(params.id)
|
||||
def subscriptionEndpt = params.subscriptionURL
|
||||
def deviceId = params.deviceId
|
||||
def myDevice = findDevice(deviceId)
|
||||
|
||||
if( myDevice == null ){
|
||||
httpError(404, "Cannot find device with device ID ${deviceId}.")
|
||||
}
|
||||
|
||||
try {
|
||||
unsubscribe(myDevice)
|
||||
log.info "Unregistering ${myDevice.displayName}"
|
||||
return ["succeed"]
|
||||
if(subscriptionEndpt != null && subscriptionEndpt != "undefined"){
|
||||
if (state.deviceSubscriptionMap[deviceId]?.contains(subscriptionEndpt)){
|
||||
if(state.deviceSubscriptionMap[deviceId].size() == 1){
|
||||
state.deviceSubscriptionMap.remove(deviceId)
|
||||
} else {
|
||||
state.deviceSubscriptionMap[deviceId].remove(subscriptionEndpt)
|
||||
}
|
||||
log.info "Removed subscription URL: ${subscriptionEndpt} for ${myDevice.displayName}"
|
||||
}
|
||||
} else {
|
||||
state.deviceSubscriptionMap.remove(deviceId)
|
||||
log.info "Unsubscriping for ${myDevice.displayName}"
|
||||
}
|
||||
} catch (e) {
|
||||
httpError(500, "something went wrong: $e")
|
||||
}
|
||||
|
||||
log.info "Current subscription map is ${state.deviceSubscriptionMap}"
|
||||
}
|
||||
|
||||
//Endpoints function: Subscribe to device additiona/removal updated in a location
|
||||
def registerDeviceGraph() {
|
||||
def subscriptionEndpt = params.subscriptionURL
|
||||
|
||||
if (subscriptionEndpt != null && subscriptionEndpt != "undefined"){
|
||||
subscribe(location, "DeviceCreated", locationEventHandler, [filterEvents: false])
|
||||
subscribe(location, "DeviceUpdated", locationEventHandler, [filterEvents: false])
|
||||
subscribe(location, "DeviceDeleted", locationEventHandler, [filterEvents: false])
|
||||
|
||||
if(state.locationSubscriptionMap[location.id] == null){
|
||||
state.locationSubscriptionMap.put(location.id, [subscriptionEndpt])
|
||||
log.info "Added subscription URL: ${subscriptionEndpt} for Location ${location.name}"
|
||||
} else if (!state.locationSubscriptionMap[location.id].contains(subscriptionEndpt)){
|
||||
state.locationSubscriptionMap[location.id] << subscriptionEndpt
|
||||
log.info "Added subscription URL: ${subscriptionEndpt} for Location ${location.name}"
|
||||
}
|
||||
log.info "Current location subscription map is ${state.locationSubscriptionMap}"
|
||||
return ["succeed"]
|
||||
} else {
|
||||
httpError(400, "missing input parameter: subscriptionURL")
|
||||
}
|
||||
}
|
||||
|
||||
//Endpoints function: Unsubscribe to events from a specific device
|
||||
def unregisterDeviceGraph() {
|
||||
def subscriptionEndpt = params.subscriptionURL
|
||||
|
||||
try {
|
||||
if(subscriptionEndpt != null && subscriptionEndpt != "undefined"){
|
||||
if (state.locationSubscriptionMap[location.id]?.contains(subscriptionEndpt)){
|
||||
if(state.locationSubscriptionMap[location.id].size() == 1){
|
||||
state.locationSubscriptionMap.remove(location.id)
|
||||
} else {
|
||||
state.locationSubscriptionMap[location.id].remove(subscriptionEndpt)
|
||||
}
|
||||
log.info "Removed subscription URL: ${subscriptionEndpt} for Location ${location.name}"
|
||||
}
|
||||
}else{
|
||||
httpError(400, "missing input parameter: subscriptionURL")
|
||||
}
|
||||
} catch (e) {
|
||||
httpError(500, "something went wrong: $e")
|
||||
}
|
||||
|
||||
log.info "Current location subscription map is ${state.locationSubscriptionMap}"
|
||||
}
|
||||
|
||||
//When events are triggered, send HTTP post to web socket servers
|
||||
def eventHandler(evt) {
|
||||
def evt_device_id = evt.deviceId
|
||||
def evt_device_value = evt.value
|
||||
def evt_name = evt.name
|
||||
def deviceEventHandler(evt) {
|
||||
def evt_device = evt.device
|
||||
def evt_deviceType = getDeviceType(evt_device);
|
||||
def evt_deviceType = getDeviceType(evt_device)
|
||||
def deviceInfo
|
||||
|
||||
if(evt_deviceType == "thermostat")
|
||||
{
|
||||
deviceInfo = [name: evt_device.displayName, id: evt_device.id, status:evt_device.getStatus(), deviceType:evt_deviceType, manufacturer:evt_device.getManufacturerName(), model:evt_device.getModelName(), attributes: deviceAttributeList(evt_device), locationMode: getLocationModeInfo()]
|
||||
}
|
||||
else
|
||||
{
|
||||
deviceInfo = [name: evt_device.displayName, id: evt_device.id, status:evt_device.getStatus(), deviceType:evt_deviceType, manufacturer:evt_device.getManufacturerName(), model:evt_device.getModelName(), attributes: deviceAttributeList(evt_device)]
|
||||
def params = [ body: [deviceName: evt_device.displayName, deviceId: evt_device.id, locationId: location.id] ]
|
||||
|
||||
if(evt.data != null){
|
||||
def evtData = parseJson(evt.data)
|
||||
log.info "Received event for ${evt_device.displayName}, data: ${evtData}, description: ${evt.descriptionText}"
|
||||
}
|
||||
|
||||
def params = [
|
||||
uri: "${state.endpointURL}/${state.connectionId}",
|
||||
body: [ deviceInfo ]
|
||||
]
|
||||
try {
|
||||
//send event to all subscriptions urls
|
||||
log.debug "Current subscription urls for ${evt_device.displayName} is ${state.deviceSubscriptionMap[evt_device.id]}"
|
||||
state.deviceSubscriptionMap[evt_device.id].each {
|
||||
params.uri = "${it}"
|
||||
log.trace "POST URI: ${params.uri}"
|
||||
log.trace "Payload: ${params.body}"
|
||||
httpPostJson(params) { resp ->
|
||||
resp.headers.each {
|
||||
log.debug "${it.name} : ${it.value}"
|
||||
try{
|
||||
httpPostJson(params) { resp ->
|
||||
log.trace "response status code: ${resp.status}"
|
||||
log.trace "response data: ${resp.data}"
|
||||
}
|
||||
log.trace "response status code: ${resp.status}"
|
||||
log.trace "response data: ${resp.data}"
|
||||
} catch (e) {
|
||||
log.error "something went wrong: $e"
|
||||
}
|
||||
} catch (e) {
|
||||
log.debug "something went wrong: $e"
|
||||
}
|
||||
}
|
||||
|
||||
//Endpoints function: update subcription endpoint url [state.endpoint]
|
||||
void updateEndpointURL() {
|
||||
state.endpointURL = params.url
|
||||
log.info "Updated EndpointURL to ${state.endpointURL}"
|
||||
def locationEventHandler(evt) {
|
||||
log.info "Received event for location ${location.name}/${location.id}, Event: ${evt.name}, description: ${evt.descriptionText}, apiServerUrl: ${apiServerUrl("")}"
|
||||
switch(evt.name){
|
||||
case "DeviceCreated":
|
||||
case "DeviceDeleted":
|
||||
def evt_device = evt.device
|
||||
def evt_deviceType = getDeviceType(evt_device)
|
||||
log.info "DeviceName: ${evt_device.displayName}, DeviceID: ${evt_device.id}, deviceType: ${evt_deviceType}"
|
||||
|
||||
def params = [ body: [ eventType:evt.name, deviceId: evt_device.id, locationId: location.id ] ]
|
||||
|
||||
state.locationSubscriptionMap[location.id].each {
|
||||
params.uri = "${it}"
|
||||
log.trace "POST URI: ${params.uri}"
|
||||
log.trace "Payload: ${params.body}"
|
||||
try{
|
||||
httpPostJson(params) { resp ->
|
||||
log.trace "response status code: ${resp.status}"
|
||||
log.trace "response data: ${resp.data}"
|
||||
}
|
||||
} catch (e) {
|
||||
log.error "something went wrong: $e"
|
||||
}
|
||||
}
|
||||
case "DeviceUpdated":
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
//Endpoints function: update global variable [state.connectionId]
|
||||
void updateConnectionId() {
|
||||
def connId = params.connId
|
||||
state.connectionId = connId
|
||||
log.info "Updated ConnectionID to ${state.connectionId}"
|
||||
}
|
||||
|
||||
/*** Device Query/Update Functions ***/
|
||||
|
||||
//Endpoints function: return all device data in json format
|
||||
def getDevices() {
|
||||
def deviceData = []
|
||||
inputs?.each {
|
||||
def deviceType = getDeviceType(it)
|
||||
if(deviceType == "thermostat")
|
||||
{
|
||||
deviceData << [name: it.displayName, id: it.id, status:it.getStatus(), deviceType:deviceType, manufacturer:it.getManufacturerName(), model:it.getModelName(), attributes: deviceAttributeList(it), locationMode: getLocationModeInfo()]
|
||||
}
|
||||
else
|
||||
{
|
||||
deviceData << [name: it.displayName, id: it.id, status:it.getStatus(), deviceType:deviceType, manufacturer:it.getManufacturerName(), model:it.getModelName(), attributes: deviceAttributeList(it)]
|
||||
if(deviceType == "thermostat") {
|
||||
deviceData << [name: it.displayName, id: it.id, status:it.status, deviceType:deviceType, manufacturer:it.manufacturerName, model:it.modelName, attributes: deviceAttributeList(it, deviceType), locationMode: getLocationModeInfo()]
|
||||
} else {
|
||||
deviceData << [name: it.displayName, id: it.id, status:it.status, deviceType:deviceType, manufacturer:it.manufacturerName, model:it.modelName, attributes: deviceAttributeList(it, deviceType)]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,14 +350,12 @@ def getDevice() {
|
||||
def it = findDevice(params.id)
|
||||
def deviceType = getDeviceType(it)
|
||||
def device
|
||||
if(deviceType == "thermostat")
|
||||
{
|
||||
device = [name: it.displayName, id: it.id, status:it.getStatus(), deviceType:deviceType, manufacturer:it.getManufacturerName(), model:it.getModelName(), attributes: deviceAttributeList(it), locationMode: getLocationModeInfo()]
|
||||
}
|
||||
else
|
||||
{
|
||||
device = [name: it.displayName, id: it.id, status:it.getStatus(), deviceType:deviceType, manufacturer:it.getManufacturerName(), model:it.getModelName(), attributes: deviceAttributeList(it)]
|
||||
if(deviceType == "thermostat") {
|
||||
device = [name: it.displayName, id: it.id, status:it.status, deviceType:deviceType, manufacturer:it.manufacturerName, model:it.modelName, attributes: deviceAttributeList(it,deviceType), locationMode: getLocationModeInfo()]
|
||||
} else {
|
||||
device = [name: it.displayName, id: it.id, status:it.status, deviceType:deviceType, manufacturer:it.manufacturerName, model:it.modelName, attributes: deviceAttributeList(it, deviceType)]
|
||||
}
|
||||
|
||||
log.debug "getDevice, return: ${device}"
|
||||
return device
|
||||
}
|
||||
@@ -350,9 +451,6 @@ private getDeviceType(device) {
|
||||
}
|
||||
}
|
||||
break
|
||||
case "contact sensor":
|
||||
deviceType = "contactSensor"
|
||||
return deviceType
|
||||
case "garageDoorControl":
|
||||
deviceType = "garageDoor"
|
||||
return deviceType
|
||||
@@ -362,17 +460,15 @@ private getDeviceType(device) {
|
||||
case "video camera":
|
||||
deviceType = "camera"
|
||||
return deviceType
|
||||
case "motion sensor":
|
||||
deviceType = "motionSensor"
|
||||
return deviceType
|
||||
case "presence sensor":
|
||||
deviceType = "presenceSensor"
|
||||
return deviceType
|
||||
case "thermostat":
|
||||
deviceType = "thermostat"
|
||||
return deviceType
|
||||
case "acceleration sensor":
|
||||
case "contact sensor":
|
||||
case "motion sensor":
|
||||
case "presence sensor":
|
||||
case "water sensor":
|
||||
deviceType = "waterSensor"
|
||||
deviceType = "genericSensor"
|
||||
return deviceType
|
||||
default:
|
||||
break
|
||||
@@ -387,14 +483,33 @@ private findDevice(deviceId) {
|
||||
}
|
||||
|
||||
//Return a list of device attributes
|
||||
private deviceAttributeList(device) {
|
||||
device.supportedAttributes.collectEntries { attribute->
|
||||
private deviceAttributeList(device, deviceType) {
|
||||
def attributeList = [:]
|
||||
def allAttributes = device.supportedAttributes
|
||||
allAttributes.each { attribute ->
|
||||
try {
|
||||
[ (attribute.name): device.currentValue(attribute.name) ]
|
||||
def currentState = device.currentState(attribute.name)
|
||||
if(currentState != null ){
|
||||
switch(attribute.name){
|
||||
case 'temperature':
|
||||
attributeList.putAll([ (attribute.name): currentState.value, 'temperatureScale':location.temperatureScale ])
|
||||
break;
|
||||
default:
|
||||
attributeList.putAll([(attribute.name): currentState.value ])
|
||||
break;
|
||||
}
|
||||
if( deviceType == "genericSensor" ){
|
||||
def key = attribute.name + "_lastUpdated"
|
||||
attributeList.putAll([ (key): currentState.isoDate ])
|
||||
}
|
||||
} else {
|
||||
attributeList.putAll([ (attribute.name): null ]);
|
||||
}
|
||||
} catch(e) {
|
||||
[ (attribute.name): null ]
|
||||
attributeList.putAll([ (attribute.name): null ]);
|
||||
}
|
||||
}
|
||||
return attributeList
|
||||
}
|
||||
|
||||
//Map device command and value.
|
||||
|
||||
@@ -73,7 +73,7 @@ def bridgeDiscovery(params = [:]) {
|
||||
}
|
||||
|
||||
ssdpSubscribe()
|
||||
|
||||
log.trace "bridgeRefreshCount: $bridgeRefreshCount"
|
||||
//bridge discovery request every 15 //25 seconds
|
||||
if ((bridgeRefreshCount % 5) == 0) {
|
||||
discoverBridges()
|
||||
@@ -207,6 +207,7 @@ def bulbDiscovery() {
|
||||
}
|
||||
|
||||
private discoverBridges() {
|
||||
log.trace "Sending Hue Discovery message to the hub"
|
||||
sendHubCommand(new physicalgraph.device.HubAction("lan discovery urn:schemas-upnp-org:device:basic:1", physicalgraph.device.Protocol.LAN))
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ def getCallbackUrl() { return "${getServerUrl()}/oauth/callback" }
|
||||
def apiURL(path = '/') { return "https://api.lifx.com/v1${path}" }
|
||||
def getSecretKey() { return appSettings.secretKey }
|
||||
def getClientId() { return appSettings.clientId }
|
||||
private getVendorName() { "LIFX" }
|
||||
|
||||
def authPage() {
|
||||
log.debug "authPage test1"
|
||||
@@ -76,6 +77,7 @@ def authPage() {
|
||||
return dynamicPage(name:"Credentials", title:"", nextPage:"", install:true, uninstall: true) {
|
||||
section("Select your location") {
|
||||
input "selectedLocationId", "enum", required:true, title:"Select location ({{count}} found)", messageArgs: [count: count], multiple:false, options:options, submitOnChange: true
|
||||
paragraph "Devices will be added automatically from your ${vendorName} account. To add or delete devices please use the Official ${vendorName} App."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,10 +136,20 @@ def getDataForChild(child, startDate, endDate) {
|
||||
|
||||
def wattvisionURL = wattvisionURL(child.deviceNetworkId, startDate, endDate)
|
||||
if (wattvisionURL) {
|
||||
httpGet(uri: wattvisionURL) { response ->
|
||||
def json = new org.json.JSONObject(response.data.toString())
|
||||
child.addWattvisionData(json)
|
||||
return "success"
|
||||
try {
|
||||
httpGet(uri: wattvisionURL) { response ->
|
||||
def json = new org.json.JSONObject(response.data.toString())
|
||||
child.addWattvisionData(json)
|
||||
return "success"
|
||||
}
|
||||
} catch (groovyx.net.http.HttpResponseException httpE) {
|
||||
log.error "Wattvision getDataForChild HttpResponseException: ${httpE} -> ${httpE.response.data}"
|
||||
//log.debug "wattvisionURL = ${wattvisionURL}"
|
||||
return "fail"
|
||||
} catch (e) {
|
||||
log.error "Wattvision getDataForChild General Exception: ${e}"
|
||||
//log.debug "wattvisionURL = ${wattvisionURL}"
|
||||
return "fail"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -164,9 +174,14 @@ def wattvisionURL(senorId, startDate, endDate) {
|
||||
if (diff > 259200000) { // 3 days in milliseconds
|
||||
// Wattvision only allows pulling 3 hours of data at a time
|
||||
startDate = new Date(hours: endDate.hours - 3)
|
||||
} else if (diff < 10000) { // 10 seconds in milliseconds
|
||||
// Wattvision throws errors when the difference between start_time and end_time is 5 seconds or less
|
||||
// So we are going to make sure that we have a few more seconds of breathing room
|
||||
use (groovy.time.TimeCategory) {
|
||||
startDate = endDate - 10.seconds
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def params = [
|
||||
"sensor_id" : senorId,
|
||||
"api_id" : wattvisionApiAccess.id,
|
||||
@@ -480,4 +495,3 @@ def connectionSuccessful(deviceName, iconSrc) {
|
||||
|
||||
render contentType: 'text/html', data: html
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user