Merge pull request #1916 from SmartThingsCommunity/staging

Rolling down staging to master
This commit is contained in:
Vinay Rao
2017-04-18 13:42:58 -07:00
committed by GitHub
8 changed files with 664 additions and 111 deletions

View File

@@ -0,0 +1,426 @@
// 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
//
// 2017-04-10 - DrZwave (with help from Don 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
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 == "" || lum == null || 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 == "" || lum == null || 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) {
if (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=60
}
def liteTempVal = value.toInteger()
switch (liteTempVal) {
case { it < 0 }:
return 60 // 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=10
}
def onTimeVal = value.toInteger()
switch (onTimeVal) {
case { it < 0 }:
return 10 // 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
}

View File

@@ -1,5 +1,5 @@
/** /**
* Copyright 2016 SmartThings * Copyright 2017 SmartThings
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * 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: * in compliance with the License. You may obtain a copy of the License at:
@@ -59,7 +59,7 @@ def updated() {
} }
def configure() { 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}" log.debug "configure -- cmds: ${cmds}"
return cmds return cmds
} }
@@ -166,4 +166,4 @@ def checkPresenceCallback() {
if (timeSinceLastCheckin >= theCheckInterval) { if (timeSinceLastCheckin >= theCheckInterval) {
handlePresenceEvent(false) handlePresenceEvent(false)
} }
} }

View File

@@ -23,9 +23,9 @@ metadata {
capability "Health Check" capability "Health Check"
capability "Light" capability "Light"
fingerprint mfr:"0063", prod:"4457", deviceJoinName: "GE In-Wall 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:"4944", deviceJoinName: "GE In-Wall Smart Dimmer"
fingerprint mfr:"0063", prod:"5044", deviceJoinName: "GE Plug-In 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" fingerprint mfr:"0063", prod:"4944", model:"3034", deviceJoinName: "GE In-Wall Smart Fan Control"
} }

View File

@@ -114,9 +114,17 @@ def refresh() {
def configure() { def configure() {
log.debug "Configuring Reporting and Bindings." 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) // 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 // 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]) sendEvent(name: "checkInterval", value: 2 * 60 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
refresh() cmds + refresh()
} }

View File

@@ -30,6 +30,9 @@ metadata {
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x86,0x72,0x98", outClusters: "0x5A,0x82" 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,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:"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 // simulator metadata

View File

@@ -22,9 +22,9 @@ metadata {
capability "Health Check" capability "Health Check"
capability "Light" capability "Light"
fingerprint mfr:"0063", prod:"4952", deviceJoinName: "Z-Wave Wall Switch" fingerprint mfr:"0063", prod:"4952", deviceJoinName: "GE Wall Switch"
fingerprint mfr:"0063", prod:"5257", deviceJoinName: "Z-Wave Wall Switch" fingerprint mfr:"0063", prod:"5257", deviceJoinName: "GE Wall Switch"
fingerprint mfr:"0063", prod:"5052", deviceJoinName: "Z-Wave Plug-In Switch" fingerprint mfr:"0063", prod:"5052", deviceJoinName: "GE Plug-In Switch"
fingerprint mfr:"0113", prod:"5257", deviceJoinName: "Z-Wave Wall Switch" fingerprint mfr:"0113", prod:"5257", deviceJoinName: "Z-Wave Wall Switch"
} }

View File

@@ -52,15 +52,15 @@ definition(
//Device Inputs //Device Inputs
preferences { preferences {
section("Allow OpenT2T to control these things...") { section("Allow OpenT2T to control these things...") {
input "contactSensors", "capability.contactSensor", title: "Which Contact 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 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 input "locks", "capability.lock", title: "Which Locks?", multiple: true, required: false, hideWhenEmpty: true
input "cameras", "capability.videoCapture", title: "Which Cameras?", multiple: true, required: false 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 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 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 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 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 input "waterSensors", "capability.waterSensor", title: "Which Water Leak Sensors?", multiple: true, required: false, hideWhenEmpty: true
} }
} }
@@ -80,16 +80,6 @@ def getInputs() {
//API external Endpoints //API external Endpoints
mappings { mappings {
path("/subscriptionURL/:url") {
action: [
PUT: "updateEndpointURL"
]
}
path("/connectionId/:connId") {
action: [
PUT: "updateConnectionId"
]
}
path("/devices") { path("/devices") {
action: [ action: [
GET: "getDevices" GET: "getDevices"
@@ -105,33 +95,52 @@ mappings {
PUT: "updateDevice" PUT: "updateDevice"
] ]
} }
path("/subscription/:id") { path("/deviceSubscription") {
action: [ action: [
POST: "registerDeviceChange", POST: "registerDeviceChange",
DELETE: "unregisterDeviceChange" DELETE: "unregisterDeviceChange"
] ]
} }
path("/locationSubscription") {
action: [
POST: "registerDeviceGraph",
DELETE: "unregisterDeviceGraph"
]
}
} }
def installed() { def installed() {
log.debug "Installed with settings: ${settings}" log.debug "Installing with settings: ${settings}"
initialize() initialize()
} }
def updated() { 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() unsubscribe()
registerSubscriptions() registerAllDeviceSubscriptions()
} }
def initialize() { def initialize() {
state.connectionId = "" log.debug "Initializing with settings: ${settings}"
state.endpointURL = "https://ifs.windows-int.com/v1/cb/81C7E77B-EABC-488A-B2BF-FEC42F0DABD2/notify" state.deviceSubscriptionMap = [:]
registerSubscriptions() log.debug "deviceSubscriptionMap created."
registerAllDeviceSubscriptions()
state.locationSubscriptionMap = [:]
log.debug "locationSubscriptionMap created."
} }
/*** Subscription Functions ***/
//Subscribe events for all devices //Subscribe events for all devices
def registerSubscriptions() { def registerAllDeviceSubscriptions() {
registerChangeHandler(inputs) registerChangeHandler(inputs)
} }
@@ -140,101 +149,195 @@ def registerChangeHandler(myList) {
myList.each { myDevice -> myList.each { myDevice ->
def theAtts = myDevice.supportedAttributes def theAtts = myDevice.supportedAttributes
theAtts.each {att -> theAtts.each {att ->
subscribe(myDevice, att.name, eventHandler) subscribe(myDevice, att.name, deviceEventHandler)
log.info "Registering ${myDevice.displayName}.${att.name}" log.info "Registering for ${myDevice.displayName}.${att.name}"
} }
} }
} }
//Endpoints function: Subscribe to events from a specific device //Endpoints function: Subscribe to events from a specific device
def registerDeviceChange() { 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 def theAtts = myDevice.supportedAttributes
try { try {
theAtts.each {att -> theAtts.each {att ->
subscribe(myDevice, att.name, eventHandler) subscribe(myDevice, att.name, deviceEventHandler)
log.info "Registering ${myDevice.displayName}.${att.name}" }
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) { } catch (e) {
httpError(500, "something went wrong: $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 //Endpoints function: Unsubscribe to events from a specific device
def unregisterDeviceChange() { 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 { try {
unsubscribe(myDevice) if(subscriptionEndpt != null && subscriptionEndpt != "undefined"){
log.info "Unregistering ${myDevice.displayName}" if (state.deviceSubscriptionMap[deviceId]?.contains(subscriptionEndpt)){
return ["succeed"] 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) { } catch (e) {
httpError(500, "something went wrong: $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 //When events are triggered, send HTTP post to web socket servers
def eventHandler(evt) { def deviceEventHandler(evt) {
def evt_device_id = evt.deviceId
def evt_device_value = evt.value
def evt_name = evt.name
def evt_device = evt.device def evt_device = evt.device
def evt_deviceType = getDeviceType(evt_device); def evt_deviceType = getDeviceType(evt_device)
def deviceInfo def deviceInfo
if(evt_deviceType == "thermostat") def params = [ body: [deviceName: evt_device.displayName, deviceId: evt_device.id, locationId: location.id] ]
{
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()] if(evt.data != null){
} def evtData = parseJson(evt.data)
else log.info "Received event for ${evt_device.displayName}, data: ${evtData}, description: ${evt.descriptionText}"
{
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 = [ //send event to all subscriptions urls
uri: "${state.endpointURL}/${state.connectionId}", log.debug "Current subscription urls for ${evt_device.displayName} is ${state.deviceSubscriptionMap[evt_device.id]}"
body: [ deviceInfo ] state.deviceSubscriptionMap[evt_device.id].each {
] params.uri = "${it}"
try {
log.trace "POST URI: ${params.uri}" log.trace "POST URI: ${params.uri}"
log.trace "Payload: ${params.body}" log.trace "Payload: ${params.body}"
httpPostJson(params) { resp -> try{
resp.headers.each { httpPostJson(params) { resp ->
log.debug "${it.name} : ${it.value}" log.trace "response status code: ${resp.status}"
log.trace "response data: ${resp.data}"
} }
log.trace "response status code: ${resp.status}" } catch (e) {
log.trace "response data: ${resp.data}" log.error "something went wrong: $e"
} }
} catch (e) {
log.debug "something went wrong: $e"
} }
} }
//Endpoints function: update subcription endpoint url [state.endpoint] def locationEventHandler(evt) {
void updateEndpointURL() { log.info "Received event for location ${location.name}/${location.id}, Event: ${evt.name}, description: ${evt.descriptionText}, apiServerUrl: ${apiServerUrl("")}"
state.endpointURL = params.url switch(evt.name){
log.info "Updated EndpointURL to ${state.endpointURL}" 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() { /*** Device Query/Update Functions ***/
def connId = params.connId
state.connectionId = connId
log.info "Updated ConnectionID to ${state.connectionId}"
}
//Endpoints function: return all device data in json format //Endpoints function: return all device data in json format
def getDevices() { def getDevices() {
def deviceData = [] def deviceData = []
inputs?.each { inputs?.each {
def deviceType = getDeviceType(it) def deviceType = getDeviceType(it)
if(deviceType == "thermostat") 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()]
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.status, deviceType:deviceType, manufacturer:it.manufacturerName, model:it.modelName, attributes: deviceAttributeList(it, deviceType)]
else
{
deviceData << [name: it.displayName, id: it.id, status:it.getStatus(), deviceType:deviceType, manufacturer:it.getManufacturerName(), model:it.getModelName(), attributes: deviceAttributeList(it)]
} }
} }
@@ -247,14 +350,12 @@ def getDevice() {
def it = findDevice(params.id) def it = findDevice(params.id)
def deviceType = getDeviceType(it) def deviceType = getDeviceType(it)
def device def device
if(deviceType == "thermostat") 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()]
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.status, deviceType:deviceType, manufacturer:it.manufacturerName, model:it.modelName, attributes: deviceAttributeList(it, deviceType)]
else
{
device = [name: it.displayName, id: it.id, status:it.getStatus(), deviceType:deviceType, manufacturer:it.getManufacturerName(), model:it.getModelName(), attributes: deviceAttributeList(it)]
} }
log.debug "getDevice, return: ${device}" log.debug "getDevice, return: ${device}"
return device return device
} }
@@ -350,9 +451,6 @@ private getDeviceType(device) {
} }
} }
break break
case "contact sensor":
deviceType = "contactSensor"
return deviceType
case "garageDoorControl": case "garageDoorControl":
deviceType = "garageDoor" deviceType = "garageDoor"
return deviceType return deviceType
@@ -362,17 +460,15 @@ private getDeviceType(device) {
case "video camera": case "video camera":
deviceType = "camera" deviceType = "camera"
return deviceType return deviceType
case "motion sensor":
deviceType = "motionSensor"
return deviceType
case "presence sensor":
deviceType = "presenceSensor"
return deviceType
case "thermostat": case "thermostat":
deviceType = "thermostat" deviceType = "thermostat"
return deviceType return deviceType
case "acceleration sensor":
case "contact sensor":
case "motion sensor":
case "presence sensor":
case "water sensor": case "water sensor":
deviceType = "waterSensor" deviceType = "genericSensor"
return deviceType return deviceType
default: default:
break break
@@ -387,14 +483,33 @@ private findDevice(deviceId) {
} }
//Return a list of device attributes //Return a list of device attributes
private deviceAttributeList(device) { private deviceAttributeList(device, deviceType) {
device.supportedAttributes.collectEntries { attribute-> def attributeList = [:]
def allAttributes = device.supportedAttributes
allAttributes.each { attribute ->
try { 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) { } catch(e) {
[ (attribute.name): null ] attributeList.putAll([ (attribute.name): null ]);
} }
} }
return attributeList
} }
//Map device command and value. //Map device command and value.

View File

@@ -73,7 +73,7 @@ def bridgeDiscovery(params = [:]) {
} }
ssdpSubscribe() ssdpSubscribe()
log.trace "bridgeRefreshCount: $bridgeRefreshCount"
//bridge discovery request every 15 //25 seconds //bridge discovery request every 15 //25 seconds
if ((bridgeRefreshCount % 5) == 0) { if ((bridgeRefreshCount % 5) == 0) {
discoverBridges() discoverBridges()
@@ -207,6 +207,7 @@ def bulbDiscovery() {
} }
private discoverBridges() { 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)) sendHubCommand(new physicalgraph.device.HubAction("lan discovery urn:schemas-upnp-org:device:basic:1", physicalgraph.device.Protocol.LAN))
} }