mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-11 21:03:07 +00:00
Compare commits
1 Commits
MSA-1372-2
...
MSA-1338-1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d787c1b41e |
@@ -403,21 +403,39 @@ def refresh() {
|
||||
|
||||
if (device.getDataValue("manufacturer") == "SmartThings") {
|
||||
log.debug "Refreshing Values for manufacturer: SmartThings "
|
||||
/* These values of Motion Threshold Multiplier(0x01) and Motion Threshold (0x0276)
|
||||
seem to be giving pretty accurate results for the XYZ co-ordinates for this manufacturer.
|
||||
Separating these out in a separate if-else because I do not want to touch Centralite part
|
||||
as of now.
|
||||
*/
|
||||
refreshCmds += zigbee.writeAttribute(0xFC02, 0x0000, 0x20, 0x01, [mfgCode: manufacturerCode])
|
||||
refreshCmds += zigbee.writeAttribute(0xFC02, 0x0002, 0x21, 0x0276, [mfgCode: manufacturerCode])
|
||||
refreshCmds = refreshCmds + [
|
||||
/* These values of Motion Threshold Multiplier(01) and Motion Threshold (7602)
|
||||
seem to be giving pretty accurate results for the XYZ co-ordinates for this manufacturer.
|
||||
Separating these out in a separate if-else because I do not want to touch Centralite part
|
||||
as of now.
|
||||
*/
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global write 0xFC02 0 0x20 {01}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global write 0xFC02 2 0x21 {7602}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
|
||||
]
|
||||
} else {
|
||||
refreshCmds += zigbee.writeAttribute(0xFC02, 0x0000, 0x20, 0x02, [mfgCode: manufacturerCode])
|
||||
refreshCmds = refreshCmds + [
|
||||
/* sensitivity - default value (8) */
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global write 0xFC02 0 0x20 {02}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 1", "delay 400",
|
||||
]
|
||||
}
|
||||
|
||||
//Common refresh commands
|
||||
refreshCmds += zigbee.readAttribute(0x0402, 0x0000) +
|
||||
zigbee.readAttribute(0x0001, 0x0020) +
|
||||
zigbee.readAttribute(0xFC02, 0x0010, [mfgCode: manufacturerCode])
|
||||
refreshCmds = refreshCmds + [
|
||||
"st rattr 0x${device.deviceNetworkId} 1 0x402 0", "delay 200",
|
||||
"st rattr 0x${device.deviceNetworkId} 1 1 0x20", "delay 200",
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global read 0xFC02 0x0010",
|
||||
"send 0x${device.deviceNetworkId} 1 1","delay 400"
|
||||
]
|
||||
|
||||
return refreshCmds + enrollResponse()
|
||||
}
|
||||
@@ -425,15 +443,38 @@ def refresh() {
|
||||
def configure() {
|
||||
sendEvent(name: "checkInterval", value: 7200, displayed: false)
|
||||
|
||||
String zigbeeEui = swapEndianHex(device.hub.zigbeeEui)
|
||||
log.debug "Configuring Reporting"
|
||||
|
||||
def configCmds = enrollResponse() +
|
||||
zigbee.batteryConfig() +
|
||||
zigbee.temperatureConfig() +
|
||||
zigbee.configureReporting(0xFC02, 0x0010, 0x18, 10, 3600, 0x01, [mfgCode: manufacturerCode]) +
|
||||
zigbee.configureReporting(0xFC02, 0x0012, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) +
|
||||
zigbee.configureReporting(0xFC02, 0x0013, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode]) +
|
||||
zigbee.configureReporting(0xFC02, 0x0014, 0x29, 1, 3600, 0x0001, [mfgCode: manufacturerCode])
|
||||
def configCmds = [
|
||||
"zcl global write 0x500 0x10 0xf0 {${zigbeeEui}}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 1 {${device.zigbeeId}} {}", "delay 200",
|
||||
"zcl global send-me-a-report 1 0x20 0x20 30 21600 {01}", "delay 200", //checkin time 6 hrs
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0x402 {${device.zigbeeId}} {}", "delay 200",
|
||||
"zcl global send-me-a-report 0x402 0 0x29 30 3600 {6400}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zdo bind 0x${device.deviceNetworkId} ${endpointId} 1 0xFC02 {${device.zigbeeId}} {}", "delay 200",
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global send-me-a-report 0xFC02 0x0010 0x18 10 3600 {01}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global send-me-a-report 0xFC02 0x0012 0x29 1 3600 {0100}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global send-me-a-report 0xFC02 0x0013 0x29 1 3600 {0100}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500",
|
||||
|
||||
"zcl mfg-code ${manufacturerCode}", "delay 200",
|
||||
"zcl global send-me-a-report 0xFC02 0x0014 0x29 1 3600 {0100}", "delay 200",
|
||||
"send 0x${device.deviceNetworkId} 1 ${endpointId}", "delay 500"
|
||||
]
|
||||
|
||||
return configCmds + refresh()
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ 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 deviceId: "0x0701", inClusters: "0x5E,0x72,0x5A,0x80,0x73,0x86,0x84,0x85,0x59,0x71,0x70,0x7A,0x98" // Vision door/window
|
||||
fingerprint deviceId: "0x0701", inClusters: "0x5E,0x98,0x86,0x72,0x5A,0x85,0x59,0x73,0x80,0x71,0x31,0x70,0x84,0x7A" // Vision Motion
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
@@ -264,9 +263,5 @@ def retypeBasedOnMSR() {
|
||||
log.debug "Changing device type to Door / Window Sensor Plus (SG)"
|
||||
setDeviceType("Door / Window Sensor Plus (SG)")
|
||||
break
|
||||
case "0109-2002-0205": // Vision Motion
|
||||
log.debug "Changing device type to Vision Motion Sensor Plus (SG)"
|
||||
setDeviceType("Vision Motion Sensor Plus (SG)")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,13 +21,6 @@ metadata {
|
||||
capability "Motion Sensor"
|
||||
capability "Sensor"
|
||||
capability "Battery"
|
||||
|
||||
fingerprint mfr: "011F", prod: "0001", model: "0001", deviceJoinName: "Schlage Motion Sensor" // Schlage motion
|
||||
fingerprint mfr: "014A", prod: "0001", model: "0001", deviceJoinName: "Ecolink Motion Sensor" // Ecolink motion
|
||||
fingerprint mfr: "014A", prod: "0004", model: "0001", deviceJoinName: "Ecolink Motion Sensor" // Ecolink motion +
|
||||
fingerprint mfr: "0060", prod: "0001", model: "0002", deviceJoinName: "Everspring Motion Sensor" // Everspring SP814
|
||||
fingerprint mfr: "0060", prod: "0001", model: "0003", deviceJoinName: "Everspring Motion Sensor" // Everspring HSP02
|
||||
fingerprint mfr: "011A", prod: "0601", model: "0901", deviceJoinName: "Enerwave Motion Sensor" // Enerwave ZWN-BPC
|
||||
}
|
||||
|
||||
simulator {
|
||||
@@ -132,9 +125,9 @@ def zwaveEvent(physicalgraph.zwave.commands.wakeupv1.WakeUpNotification cmd)
|
||||
}
|
||||
if (!state.lastbat || (new Date().time) - state.lastbat > 53*60*60*1000) {
|
||||
result << response(zwave.batteryV1.batteryGet())
|
||||
} else {
|
||||
result << response(zwave.wakeUpV1.wakeUpNoMoreInformation())
|
||||
result << response("delay 1200")
|
||||
}
|
||||
result << response(zwave.wakeUpV1.wakeUpNoMoreInformation())
|
||||
result
|
||||
}
|
||||
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
* Documented Header
|
||||
*
|
||||
* Change 2: 2014-03-15
|
||||
* Fixed bug where we weren't coming on when changing
|
||||
* Fixed bug where we weren't coming on when changing
|
||||
* levels down.
|
||||
*
|
||||
* Change 3: 2014-04-02 (lieberman)
|
||||
* Change 3: 2014-04-02 (lieberman)
|
||||
* Changed sendEvent() to createEvent() in parse()
|
||||
*
|
||||
* Change 4: 2014-04-12 (wackford)
|
||||
* Added current power usage tile
|
||||
* Added current power usage tile
|
||||
*
|
||||
* Change 5: 2014-09-14 (wackford)
|
||||
* a. Changed createEvent() to sendEvent() in parse() to
|
||||
@@ -33,7 +33,7 @@
|
||||
* b. added refresh on udate
|
||||
* c. added uninstallFromChildDevice to handle removing from settings
|
||||
* d. Changed to allow bulb to 100%, was possible to get past logic at 99
|
||||
*
|
||||
*
|
||||
* Change 7: 2014-11-09 (wackford)
|
||||
* a. Added bulbpower calcs to device. TCP is broken
|
||||
* b. Changed to set dim level first then on. Much easier on the eys coming from bright.
|
||||
@@ -42,7 +42,7 @@
|
||||
* Code
|
||||
*****************************************************************
|
||||
*/
|
||||
// for the UI
|
||||
// for the UI
|
||||
metadata {
|
||||
definition (name: "TCP Bulb", namespace: "wackford", author: "Todd Wackford") {
|
||||
capability "Switch"
|
||||
@@ -52,28 +52,26 @@ metadata {
|
||||
capability "Switch Level"
|
||||
|
||||
attribute "stepsize", "string"
|
||||
|
||||
|
||||
command "levelUp"
|
||||
command "levelDown"
|
||||
command "on"
|
||||
command "off"
|
||||
command "setBulbPower"
|
||||
command "on"
|
||||
command "off"
|
||||
command "setBulbPower"
|
||||
}
|
||||
|
||||
simulator {
|
||||
// TODO: define status and reply messages here
|
||||
}
|
||||
|
||||
preferences {
|
||||
|
||||
preferences {
|
||||
input "stepsize", "number", title: "Step Size", description: "Dimmer Step Size", defaultValue: 5
|
||||
}
|
||||
|
||||
|
||||
tiles {
|
||||
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
|
||||
state "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
state "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
state "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||
state "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||
state "on", label:'${name}', action:"off", icon:"st.switches.light.on", backgroundColor:"#79b821"
|
||||
state "off", label:'${name}', action:"on", icon:"st.switches.light.off", backgroundColor:"#ffffff"
|
||||
}
|
||||
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false) {
|
||||
state "level", action:"switch level.setLevel"
|
||||
@@ -84,15 +82,15 @@ metadata {
|
||||
valueTile("level", "device.level", inactiveLabel: false, decoration: "flat") {
|
||||
state "level", label: 'Level ${currentValue}%'
|
||||
}
|
||||
standardTile("lUp", "device.switchLevel", inactiveLabel: false,decoration: "flat", canChangeIcon: false) {
|
||||
state "default", action:"levelUp", icon:"st.illuminance.illuminance.bright"
|
||||
}
|
||||
standardTile("lDown", "device.switchLevel", inactiveLabel: false,decoration: "flat", canChangeIcon: false) {
|
||||
state "default", action:"levelDown", icon:"st.illuminance.illuminance.light"
|
||||
}
|
||||
valueTile( "power", "device.power", inactiveLabel: false, decoration: "flat") {
|
||||
state "power", label: '${currentValue} Watts'
|
||||
}
|
||||
standardTile("lUp", "device.switchLevel", inactiveLabel: false,decoration: "flat", canChangeIcon: false) {
|
||||
state "default", action:"levelUp", icon:"st.illuminance.illuminance.bright"
|
||||
}
|
||||
standardTile("lDown", "device.switchLevel", inactiveLabel: false,decoration: "flat", canChangeIcon: false) {
|
||||
state "default", action:"levelDown", icon:"st.illuminance.illuminance.light"
|
||||
}
|
||||
valueTile( "power", "device.power", inactiveLabel: false, decoration: "flat") {
|
||||
state "power", label: '${currentValue} Watts'
|
||||
}
|
||||
|
||||
main(["switch"])
|
||||
details(["switch", "lUp", "lDown", "levelSliderControl", "level" , "power", "refresh" ])
|
||||
@@ -103,10 +101,10 @@ metadata {
|
||||
def parse(description) {
|
||||
//log.debug "parse() - $description"
|
||||
def results = []
|
||||
|
||||
if ( description == "updated" )
|
||||
return
|
||||
|
||||
|
||||
if ( description == "updated" )
|
||||
return
|
||||
|
||||
if (description?.name && description?.value)
|
||||
{
|
||||
results << createEvent(name: "${description?.name}", value: "${description?.value}")
|
||||
@@ -116,73 +114,73 @@ def parse(description) {
|
||||
// handle commands
|
||||
def setBulbPower(value) {
|
||||
state.bulbPower = value
|
||||
log.debug "In child with bulbPower of ${state.bulbPower}"
|
||||
log.debug "In child with bulbPower of ${state.bulbPower}"
|
||||
}
|
||||
|
||||
def on() {
|
||||
log.debug "Executing 'on'"
|
||||
sendEvent(name:"switch",value:on)
|
||||
sendEvent(name:"switch",value:on)
|
||||
parent.on(this)
|
||||
|
||||
def levelSetting = device.latestValue("level") as Float ?: 1.0
|
||||
def bulbPowerMax = device.latestValue("setBulbPower") as Float
|
||||
def calculatedPower = bulbPowerMax * (levelSetting / 100)
|
||||
sendEvent(name: "power", value: calculatedPower.round(1))
|
||||
|
||||
if (device.latestValue("level") == null) {
|
||||
//def bulbPowerMax = device.latestValue("setBulbPower") as Float
|
||||
//def calculatedPower = bulbPowerMax * (levelSetting / 100)
|
||||
//sendEvent(name: "power", value: calculatedPower.round(1))
|
||||
|
||||
if (device.latestValue("level") == null) {
|
||||
sendEvent( name: "level", value: 1.0 )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def off() {
|
||||
log.debug "Executing 'off'"
|
||||
sendEvent(name:"switch",value:off)
|
||||
sendEvent(name:"switch",value:off)
|
||||
parent.off(this)
|
||||
sendEvent(name: "power", value: 0.0)
|
||||
sendEvent(name: "power", value: 0.0)
|
||||
}
|
||||
|
||||
def levelUp() {
|
||||
def level = device.latestValue("level") as Integer ?: 0
|
||||
def step = state.stepsize as float
|
||||
|
||||
level+= step
|
||||
|
||||
if ( level > 100 )
|
||||
level = 100
|
||||
|
||||
setLevel(level)
|
||||
def step = state.stepsize as float
|
||||
|
||||
level+= step
|
||||
|
||||
if ( level > 100 )
|
||||
level = 100
|
||||
|
||||
setLevel(level)
|
||||
}
|
||||
|
||||
def levelDown() {
|
||||
def level = device.latestValue("level") as Integer ?: 0
|
||||
def step = state.stepsize as float
|
||||
|
||||
level-= step
|
||||
|
||||
def step = state.stepsize as float
|
||||
|
||||
level-= step
|
||||
|
||||
if ( level < 1 )
|
||||
level = 1
|
||||
|
||||
setLevel(level)
|
||||
level = 1
|
||||
|
||||
setLevel(level)
|
||||
}
|
||||
|
||||
def setLevel(value) {
|
||||
log.debug "in setLevel with value: ${value}"
|
||||
def level = value as Integer
|
||||
|
||||
sendEvent( name: "level", value: level )
|
||||
sendEvent( name: "switch.setLevel", value:level )
|
||||
|
||||
sendEvent( name: "level", value: level )
|
||||
sendEvent( name: "switch.setLevel", value:level )
|
||||
parent.setLevel( this, level )
|
||||
|
||||
|
||||
|
||||
if (( level > 0 ) && ( level <= 100 ))
|
||||
on()
|
||||
else
|
||||
off()
|
||||
|
||||
def levelSetting = level as float
|
||||
def bulbPowerMax = device.latestValue("setBulbPower") as float
|
||||
def calculatedPower = bulbPowerMax * (levelSetting / 100)
|
||||
sendEvent(name: "power", value: calculatedPower.round(1))
|
||||
if (( level > 0 ) && ( level <= 100 ))
|
||||
on()
|
||||
else
|
||||
off()
|
||||
|
||||
//def levelSetting = level as float
|
||||
//def bulbPowerMax = device.latestValue("setBulbPower") as float
|
||||
//def calculatedPower = bulbPowerMax * (levelSetting / 100)
|
||||
//sendEvent(name: "power", value: calculatedPower.round(1))
|
||||
}
|
||||
|
||||
def poll() {
|
||||
@@ -200,29 +198,29 @@ def installed() {
|
||||
}
|
||||
|
||||
def updated() {
|
||||
initialize()
|
||||
refresh()
|
||||
initialize()
|
||||
refresh()
|
||||
}
|
||||
|
||||
def initialize() {
|
||||
if ( !settings.stepsize )
|
||||
state.stepsize = 10 //set the default stepsize
|
||||
else
|
||||
state.stepsize = settings.stepsize
|
||||
state.stepsize = 10 //set the default stepsize
|
||||
else
|
||||
state.stepsize = settings.stepsize
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
Method :uninstalled(args)
|
||||
(args) :none
|
||||
returns:Nothing
|
||||
ERRORS :No error handling is done
|
||||
|
||||
Purpose:This is standard ST method.
|
||||
Gets called when "remove" is selected in child device "preferences"
|
||||
tile. It also get's called when "deleteChildDevice(child)" is
|
||||
called from parent service manager app.
|
||||
*******************************************************************************/
|
||||
Method :uninstalled(args)
|
||||
(args) :none
|
||||
returns:Nothing
|
||||
ERRORS :No error handling is done
|
||||
|
||||
Purpose:This is standard ST method.
|
||||
Gets called when "remove" is selected in child device "preferences"
|
||||
tile. It also get's called when "deleteChildDevice(child)" is
|
||||
called from parent service manager app.
|
||||
*******************************************************************************/
|
||||
def uninstalled() {
|
||||
log.debug "Executing 'uninstall' in device type"
|
||||
parent.uninstallFromChildDevice(this)
|
||||
parent.uninstallFromChildDevice(this)
|
||||
}
|
||||
|
||||
@@ -24,24 +24,17 @@ definition(
|
||||
iconX3Url: "http://www.gidjit.com/appicon@3x.png",
|
||||
oauth: [displayName: "Gidjit", displayLink: "www.gidjit.com"])
|
||||
|
||||
preferences(oauthPage: "deviceAuthorization") {
|
||||
// deviceAuthorization page is simply the devices to authorize
|
||||
page(name: "deviceAuthorization", title: "Device Authorization", nextPage: "instructionPage",
|
||||
install: false, uninstall: true) {
|
||||
section ("Allow Gidjit to have access, thereby allowing you to quickly control and monitor your following devices. Privacy Policy can be found at http://priv.gidjit.com/privacy.html") {
|
||||
input "switches", "capability.switch", title: "Control/Monitor your switches", multiple: true, required: false
|
||||
input "thermostats", "capability.thermostat", title: "Control/Monitor your thermostats", multiple: true, required: false
|
||||
input "windowShades", "capability.windowShade", title: "Control/Monitor your window shades", multiple: true, required: false //windowShade
|
||||
}
|
||||
|
||||
}
|
||||
page(name: "instructionPage", title: "Device Discovery", install: true) {
|
||||
section() {
|
||||
paragraph "Now the process is complete return to the Devices section of the Detected Screen. From there and you can add actions to each of your device panels, including launching SmartThings routines."
|
||||
}
|
||||
}
|
||||
|
||||
preferences {
|
||||
section ("Allow Gidjit to have access, there by allowing you to quickly control and monitor the following devices") {
|
||||
input "switches", "capability.switch", title: "Control/Monitor your switches", multiple: true, required: false
|
||||
input "thermostats", "capability.thermostat", title: "Control/Monitor your thermostats", multiple: true, required: false
|
||||
input "windowShades", "capability.windowShade", title: "Control/Monitor your window shades", multiple: true, required: false //windowShade
|
||||
//input "bulbs", "capability.colorControl", title: "Control your lights", multiple: true, required: false //windowShade
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
mappings {
|
||||
path("/structureinfo") {
|
||||
action: [
|
||||
|
||||
@@ -337,10 +337,10 @@ def initialize() {
|
||||
|
||||
settings.devices.each {
|
||||
def deviceId = it
|
||||
def detail = state?.deviceDetail[deviceId]
|
||||
def detail = state.deviceDetail[deviceId]
|
||||
|
||||
try {
|
||||
switch(detail?.type) {
|
||||
switch(detail.type) {
|
||||
case 'NAMain':
|
||||
log.debug "Base station"
|
||||
createChildDevice("Netatmo Basestation", deviceId, "${detail.type}.${deviceId}", detail.module_name)
|
||||
@@ -487,12 +487,12 @@ def poll() {
|
||||
log.debug "State: ${state.deviceState}"
|
||||
|
||||
settings.devices.each { deviceId ->
|
||||
def detail = state?.deviceDetail[deviceId]
|
||||
def data = state?.deviceState[deviceId]
|
||||
def child = children?.find { it.deviceNetworkId == deviceId }
|
||||
def detail = state.deviceDetail[deviceId]
|
||||
def data = state.deviceState[deviceId]
|
||||
def child = children.find { it.deviceNetworkId == deviceId }
|
||||
|
||||
log.debug "Update: $child";
|
||||
switch(detail?.type) {
|
||||
switch(detail.type) {
|
||||
case 'NAMain':
|
||||
log.debug "Updating NAMain $data"
|
||||
child?.sendEvent(name: 'temperature', value: cToPref(data['Temperature']) as float, unit: getTemperatureScale())
|
||||
|
||||
@@ -0,0 +1,679 @@
|
||||
/**
|
||||
* TCP Bulbs (Connect)
|
||||
*
|
||||
* Copyright 2014 Todd Wackford
|
||||
*
|
||||
* 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 java.security.MessageDigest;
|
||||
|
||||
private apiUrl() { "https://tcp.greenwavereality.com/gwr/gop.php?" }
|
||||
|
||||
definition(
|
||||
name: "TCP Bulbs - more reliable",
|
||||
namespace: "mmacaula",
|
||||
author: "Mike Macaulay",
|
||||
description: "Connect your TCP bulbs to SmartThings using Cloud to Cloud integration. You must create a remote login acct on TCP Mobile App.",
|
||||
category: "SmartThings Labs",
|
||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/tcp.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/tcp@2x.png",
|
||||
singleInstance: true
|
||||
)
|
||||
|
||||
|
||||
preferences {
|
||||
def msg = """Tap 'Next' after you have entered in your TCP Mobile remote credentials.
|
||||
|
||||
Once your credentials are accepted, SmartThings will scan your TCP installation for Bulbs."""
|
||||
|
||||
page(name: "selectDevices", title: "Connect Your TCP Lights to SmartThings", install: false, uninstall: true, nextPage: "chooseBulbs") {
|
||||
section("TCP Connected Remote Credentials") {
|
||||
input "username", "text", title: "Enter TCP Remote Email/UserName", required: true
|
||||
input "password", "password", title: "Enter TCP Remote Password", required: true
|
||||
paragraph msg
|
||||
}
|
||||
}
|
||||
|
||||
page(name: "chooseBulbs", title: "Choose Bulbs to Control With SmartThings", content: "initialize")
|
||||
}
|
||||
|
||||
def installed() {
|
||||
debugOut "Installed with settings: ${settings}"
|
||||
|
||||
unschedule()
|
||||
unsubscribe()
|
||||
|
||||
setupBulbs()
|
||||
|
||||
log.debug "schedule every 5 minutes syncronizeDevices)"
|
||||
runEvery5Minutes(syncronizeDevices)
|
||||
}
|
||||
|
||||
def updated() {
|
||||
debugOut "Updated with settings: ${settings}"
|
||||
|
||||
unschedule()
|
||||
|
||||
setupBulbs()
|
||||
|
||||
log.debug "schedule update every 5 minutes syncronizeDevices)"
|
||||
runEvery5Minutes(syncronizeDevices)
|
||||
}
|
||||
|
||||
def uninstalled()
|
||||
{
|
||||
unschedule() //in case we have hanging runIn()'s
|
||||
}
|
||||
|
||||
private removeChildDevices(delete)
|
||||
{
|
||||
debugOut "deleting ${delete.size()} bulbs"
|
||||
debugOut "deleting ${delete}"
|
||||
delete.each {
|
||||
deleteChildDevice(it.device.deviceNetworkId)
|
||||
}
|
||||
}
|
||||
|
||||
def uninstallFromChildDevice(childDevice)
|
||||
{
|
||||
def errorMsg = "uninstallFromChildDevice was called and "
|
||||
if (!settings.selectedBulbs) {
|
||||
debugOut errorMsg += "had empty list passed in"
|
||||
return
|
||||
}
|
||||
|
||||
def dni = childDevice.device.deviceNetworkId
|
||||
|
||||
if ( !dni ) {
|
||||
debugOut errorMsg += "could not find dni of device"
|
||||
return
|
||||
}
|
||||
|
||||
def newDeviceList = settings.selectedBulbs - dni
|
||||
app.updateSetting("selectedBulbs", newDeviceList)
|
||||
|
||||
debugOut errorMsg += "completed succesfully"
|
||||
}
|
||||
|
||||
|
||||
def setupBulbs() {
|
||||
debugOut "In setupBulbs"
|
||||
|
||||
def bulbs = state.devices
|
||||
def deviceFile = "TCP Bulb"
|
||||
|
||||
selectedBulbs.each { did ->
|
||||
//see if this is a selected bulb and install it if not already
|
||||
def d = getChildDevice(did)
|
||||
|
||||
if(!d) {
|
||||
def newBulb = bulbs.find { (it.did) == did }
|
||||
d = addChildDevice("wackford", deviceFile, did, null, [name: "${newBulb?.name}", label: "${newBulb?.name}", completedSetup: true])
|
||||
|
||||
/*if ( isRoom(did) ) { //change to the multi light group icon for a room device
|
||||
d.setIcon("switch", "on", "st.lights.multi-light-bulb-on")
|
||||
d.setIcon("switch", "off", "st.lights.multi-light-bulb-off")
|
||||
d.save()
|
||||
}*/
|
||||
|
||||
} else {
|
||||
debugOut "We already added this device"
|
||||
}
|
||||
}
|
||||
|
||||
// Delete any that are no longer in settings
|
||||
def delete = getChildDevices().findAll { !selectedBulbs?.contains(it.deviceNetworkId) }
|
||||
removeChildDevices(delete)
|
||||
|
||||
//we want to ensure syncronization between rooms and bulbs
|
||||
//syncronizeDevices()
|
||||
}
|
||||
|
||||
def initialize() {
|
||||
|
||||
atomicState.token = ""
|
||||
|
||||
getToken()
|
||||
|
||||
if ( atomicState.token == "error" ) {
|
||||
return dynamicPage(name:"chooseBulbs", title:"TCP Login Failed!\r\nTap 'Done' to try again", nextPage:"", install:false, uninstall: false) {
|
||||
section("") {}
|
||||
}
|
||||
} else {
|
||||
"we're good to go"
|
||||
debugOut "We have Token."
|
||||
}
|
||||
|
||||
//getGatewayData() //we really don't need anything from the gateway
|
||||
|
||||
deviceDiscovery()
|
||||
|
||||
def options = devicesDiscovered() ?: []
|
||||
|
||||
def msg = """Tap 'Done' after you have selected the desired devices."""
|
||||
|
||||
return dynamicPage(name:"chooseBulbs", title:"TCP and SmartThings Connected!", nextPage:"", install:true, uninstall: true) {
|
||||
section("Tap Below to View Device List") {
|
||||
input "selectedBulbs", "enum", required:false, title:"Select Bulb/Fixture", multiple:true, options:options
|
||||
paragraph msg
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def deviceDiscovery() {
|
||||
def data = "<gip><version>1</version><token>${atomicState.token}</token></gip>"
|
||||
|
||||
def Params = [
|
||||
cmd: "RoomGetCarousel",
|
||||
data: "${data}",
|
||||
fmt: "json"
|
||||
]
|
||||
|
||||
def cmd = toQueryString(Params)
|
||||
|
||||
def rooms = ""
|
||||
log.debug 'trying to discover devices'
|
||||
apiPost(cmd) { response ->
|
||||
rooms = response.data.gip.room
|
||||
}
|
||||
|
||||
debugOut "rooms data = ${rooms}"
|
||||
|
||||
def devices = []
|
||||
def bulbIndex = 1
|
||||
def lastRoomName = null
|
||||
def deviceList = []
|
||||
|
||||
if ( rooms[1] == null ) {
|
||||
def roomId = rooms.rid
|
||||
def roomName = rooms.name
|
||||
devices = rooms.device
|
||||
if ( devices[1] != null ) {
|
||||
debugOut "Room Device Data: did:${roomId} roomName:${roomName}"
|
||||
//deviceList += ["name" : "${roomName}", "did" : "${roomId}", "type" : "room"]
|
||||
devices.each({
|
||||
debugOut "Bulb Device Data: did:${it?.did} room:${roomName} BulbName:${it?.name}"
|
||||
deviceList += ["name" : "${roomName} ${it?.name}", "did" : "${it?.did}", "type" : "bulb"]
|
||||
})
|
||||
} else {
|
||||
debugOut "Bulb Device Data: did:${it?.did} room:${roomName} BulbName:${it?.name}"
|
||||
deviceList += ["name" : "${roomName} ${it?.name}", "did" : "${it?.did}", "type" : "bulb"]
|
||||
}
|
||||
} else {
|
||||
rooms.each({
|
||||
devices = it.device
|
||||
def roomName = it.name
|
||||
if ( devices[1] != null ) {
|
||||
def roomId = it?.rid
|
||||
debugOut "Room Device Data: did:${roomId} roomName:${roomName}"
|
||||
//deviceList += ["name" : "${roomName}", "did" : "${roomId}", "type" : "room"]
|
||||
devices.each({
|
||||
debugOut "Bulb Device Data: did:${it?.did} room:${roomName} BulbName:${it?.name}"
|
||||
deviceList += ["name" : "${roomName} ${it?.name}", "did" : "${it?.did}", "type" : "bulb"]
|
||||
})
|
||||
} else {
|
||||
debugOut "Bulb Device Data: did:${devices?.did} room:${roomName} BulbName:${devices?.name}"
|
||||
deviceList += ["name" : "${roomName} ${devices?.name}", "did" : "${devices?.did}", "type" : "bulb"]
|
||||
}
|
||||
})
|
||||
}
|
||||
devices = ["devices" : deviceList]
|
||||
state.devices = devices.devices
|
||||
}
|
||||
|
||||
Map devicesDiscovered() {
|
||||
def devices = state.devices
|
||||
def map = [:]
|
||||
if (devices instanceof java.util.Map) {
|
||||
devices.each {
|
||||
def value = "${it?.name}"
|
||||
def key = it?.did
|
||||
map["${key}"] = value
|
||||
}
|
||||
} else { //backwards compatable
|
||||
devices.each {
|
||||
def value = "${it?.name}"
|
||||
def key = it?.did
|
||||
map["${key}"] = value
|
||||
}
|
||||
}
|
||||
map
|
||||
}
|
||||
|
||||
def getGatewayData() {
|
||||
debugOut "In getGatewayData"
|
||||
|
||||
def data = "<gip><version>1</version><token>${atomicState.token}</token></gip>"
|
||||
|
||||
def qParams = [
|
||||
cmd: "GatewayGetInfo",
|
||||
data: "${data}",
|
||||
fmt: "json"
|
||||
]
|
||||
|
||||
def cmd = toQueryString(qParams)
|
||||
|
||||
apiPost(cmd) { response ->
|
||||
debugOut "the gateway reponse is ${response.data.gip.gateway}"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
def getToken(Closure callback) {
|
||||
|
||||
atomicState.token = ""
|
||||
|
||||
if (password) {
|
||||
def hashedPassword = generateMD5(password)
|
||||
|
||||
def data = "<gip><version>1</version><email>${username}</email><password>${hashedPassword}</password></gip>"
|
||||
|
||||
def qParams = [
|
||||
cmd : "GWRLogin",
|
||||
data: "${data}",
|
||||
fmt : "json"
|
||||
]
|
||||
|
||||
def cmd = toQueryString(qParams)
|
||||
|
||||
apiPost(cmd) { response ->
|
||||
def status = response.data.gip.rc
|
||||
|
||||
//sendNotificationEvent("Get token status ${status}")
|
||||
|
||||
if (status != "200") {//success code = 200
|
||||
def errorText = response.data.gip.error
|
||||
debugOut "Error logging into TCP Gateway. Error = ${errorText}"
|
||||
atomicState.token = "error"
|
||||
} else {
|
||||
atomicState.token = response.data.gip.token
|
||||
if(callback){
|
||||
callback.call()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.warn "Unable to log into TCP Gateway. Error = Password is null"
|
||||
atomicState.token = "error"
|
||||
}
|
||||
}
|
||||
|
||||
def apiPost(String data, Integer retryCount = 0, Closure callback) {
|
||||
debugOut "In apiPost with data: ${data}"
|
||||
def params = [
|
||||
uri: apiUrl(),
|
||||
body: data
|
||||
]
|
||||
|
||||
httpPost(params) {
|
||||
response ->
|
||||
def rc = response.data.gip.rc
|
||||
|
||||
if ( rc == "200" ) {
|
||||
debugOut ("Return Code = ${rc} = Command Succeeded.")
|
||||
callback.call(response)
|
||||
|
||||
} else if ( rc.startsWith("4") || rc.startsWith("5") ) {
|
||||
debugOut "Return Code = ${rc} = Error: Something happened!" //Error code from gateway
|
||||
sendNotificationEvent("Return Code = ${rc} = Error: Something happened! Retry # ${retryCount}" )
|
||||
log.debug "Refreshing Token"
|
||||
if(retryCount > 5){
|
||||
// give up, send a notification
|
||||
sendNotificationEvent("TCP Lighting is having Communication Errors. Error code = ${rc}. Gave up after ${retryCount} tries")
|
||||
}
|
||||
getToken({ ->
|
||||
def updatedTokenData = data.replaceFirst("<token>[^<]*</token>", '<token>${atomicState.token}</token>')
|
||||
// try again if we got our token
|
||||
sendNotificationEvent('re-fetched token, trying again')
|
||||
|
||||
apiPost(updatedTokenData, retryCount++, callback)
|
||||
})
|
||||
//callback.call(response) //stubbed out so getToken works (we had race issue)
|
||||
|
||||
} else {
|
||||
log.error "Return Code = ${rc} = Error!" //Error code from gateway
|
||||
sendNotificationEvent("TCP Lighting is having Communication Errors. Error code = ${rc}. Check that TCP Gateway is online")
|
||||
callback.call(response)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//this is not working. TCP power reporting is broken. Leave it here for future fix
|
||||
def calculateCurrentPowerUse(deviceCapability, usePercentage) {
|
||||
debugOut "In calculateCurrentPowerUse()"
|
||||
|
||||
debugOut "deviceCapability: ${deviceCapability}"
|
||||
debugOut "usePercentage: ${usePercentage}"
|
||||
|
||||
def calcPower = usePercentage * 1000
|
||||
def reportPower = calcPower.round(1) as String
|
||||
|
||||
debugOut "report power = ${reportPower}"
|
||||
|
||||
return reportPower
|
||||
}
|
||||
|
||||
def generateSha256(String s) {
|
||||
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256")
|
||||
digest.update(s.bytes)
|
||||
new BigInteger(1, digest.digest()).toString(16).padLeft(40, '0')
|
||||
}
|
||||
|
||||
def generateMD5(String s) {
|
||||
MessageDigest digest = MessageDigest.getInstance("MD5")
|
||||
digest.update(s.bytes);
|
||||
new BigInteger(1, digest.digest()).toString(16).padLeft(32, '0')
|
||||
}
|
||||
|
||||
String toQueryString(Map m) {
|
||||
return m.collect { k, v -> "${k}=${URLEncoder.encode(v.toString())}" }.sort().join("&")
|
||||
}
|
||||
|
||||
def checkDevicesOnline(bulbs) {
|
||||
debugOut "In checkDevicesOnline()"
|
||||
|
||||
def onlineBulbs = []
|
||||
def thisBulb = []
|
||||
|
||||
bulbs.each {
|
||||
def dni = it?.did
|
||||
thisBulb = it
|
||||
|
||||
def data = "<gip><version>1</version><token>${atomicState.token}</token><did>${dni}</did></gip>"
|
||||
|
||||
def qParams = [
|
||||
cmd: "DeviceGetInfo",
|
||||
data: "${data}",
|
||||
fmt: "json"
|
||||
]
|
||||
|
||||
def cmd = toQueryString(qParams)
|
||||
|
||||
def bulbData = []
|
||||
|
||||
apiPost(cmd) { response ->
|
||||
bulbData = response.data.gip
|
||||
}
|
||||
|
||||
if ( bulbData?.offline == "1" ) {
|
||||
debugOut "${it?.name} is offline with offline value of ${bulbData?.offline}"
|
||||
|
||||
} else {
|
||||
debugOut "${it?.name} is online with offline value of ${bulbData?.offline}"
|
||||
onlineBulbs += thisBulb
|
||||
}
|
||||
}
|
||||
return onlineBulbs
|
||||
}
|
||||
|
||||
def syncronizeDevices() {
|
||||
debugOut "In syncronizeDevices"
|
||||
|
||||
def update = getChildDevices().findAll { selectedBulbs?.contains(it.deviceNetworkId) }
|
||||
|
||||
update.each {
|
||||
def dni = getChildDevice( it.deviceNetworkId )
|
||||
debugOut "dni = ${dni}"
|
||||
|
||||
if (isRoom(dni)) {
|
||||
pollRoom(dni)
|
||||
} else {
|
||||
poll(dni)
|
||||
}
|
||||
}
|
||||
getToken()
|
||||
|
||||
}
|
||||
|
||||
boolean isRoom(dni) {
|
||||
def device = state.devices.find() {(( it.type == 'room') && (it.did == "${dni}"))}
|
||||
}
|
||||
|
||||
boolean isBulb(dni) {
|
||||
def device = state.devices.find() {(( it.type == 'bulb') && (it.did == "${dni}"))}
|
||||
}
|
||||
|
||||
def debugEvent(message, displayEvent) {
|
||||
|
||||
def results = [
|
||||
name: "appdebug",
|
||||
descriptionText: message,
|
||||
displayed: displayEvent
|
||||
]
|
||||
log.debug "Generating AppDebug Event: ${results}"
|
||||
sendEvent (results)
|
||||
|
||||
}
|
||||
|
||||
def debugOut(msg) {
|
||||
log.debug msg
|
||||
//sendNotificationEvent(msg) //Uncomment this for troubleshooting only
|
||||
}
|
||||
|
||||
|
||||
/**************************************************************************
|
||||
Child Device Call In Methods
|
||||
**************************************************************************/
|
||||
def on(childDevice) {
|
||||
debugOut "On request from child device"
|
||||
|
||||
def dni = childDevice.device.deviceNetworkId
|
||||
def data = ""
|
||||
def cmd = ""
|
||||
|
||||
if ( isRoom(dni) ) { // this is a room, not a bulb
|
||||
data = "<gip><version>1</version><token>$atomicState.token</token><rid>${dni}</rid><type>power</type><value>1</value></gip>"
|
||||
cmd = "RoomSendCommand"
|
||||
} else {
|
||||
data = "<gip><version>1</version><token>$atomicState.token</token><did>${dni}</did><type>power</type><value>1</value></gip>"
|
||||
cmd = "DeviceSendCommand"
|
||||
}
|
||||
|
||||
def qParams = [
|
||||
cmd: cmd,
|
||||
data: "${data}",
|
||||
fmt: "json"
|
||||
]
|
||||
|
||||
cmd = toQueryString(qParams)
|
||||
|
||||
apiPost(cmd) { response ->
|
||||
debugOut "ON result: ${response.data}"
|
||||
}
|
||||
|
||||
//we want to ensure syncronization between rooms and bulbs
|
||||
//runIn(2, "syncronizeDevices")
|
||||
}
|
||||
|
||||
def off(childDevice) {
|
||||
debugOut "Off request from child device"
|
||||
|
||||
def dni = childDevice.device.deviceNetworkId
|
||||
def data = ""
|
||||
def cmd = ""
|
||||
|
||||
if ( isRoom(dni) ) { // this is a room, not a bulb
|
||||
data = "<gip><version>1</version><token>$atomicState.token</token><rid>${dni}</rid><type>power</type><value>0</value></gip>"
|
||||
cmd = "RoomSendCommand"
|
||||
} else {
|
||||
data = "<gip><version>1</version><token>$atomicState.token</token><did>${dni}</did><type>power</type><value>0</value></gip>"
|
||||
cmd = "DeviceSendCommand"
|
||||
}
|
||||
|
||||
def qParams = [
|
||||
cmd: cmd,
|
||||
data: "${data}",
|
||||
fmt: "json"
|
||||
]
|
||||
|
||||
cmd = toQueryString(qParams)
|
||||
|
||||
apiPost(cmd) { response ->
|
||||
debugOut "${response.data}"
|
||||
}
|
||||
|
||||
//we want to ensure syncronization between rooms and bulbs
|
||||
//runIn(2, "syncronizeDevices")
|
||||
}
|
||||
|
||||
def setLevel(childDevice, value) {
|
||||
debugOut "setLevel request from child device"
|
||||
|
||||
def dni = childDevice.device.deviceNetworkId
|
||||
def data = ""
|
||||
def cmd = ""
|
||||
|
||||
if ( isRoom(dni) ) { // this is a room, not a bulb
|
||||
data = "<gip><version>1</version><token>${atomicState.token}</token><rid>${dni}</rid><type>level</type><value>${value}</value></gip>"
|
||||
cmd = "RoomSendCommand"
|
||||
} else {
|
||||
data = "<gip><version>1</version><token>${atomicState.token}</token><did>${dni}</did><type>level</type><value>${value}</value></gip>"
|
||||
cmd = "DeviceSendCommand"
|
||||
}
|
||||
|
||||
def qParams = [
|
||||
cmd: cmd,
|
||||
data: "${data}",
|
||||
fmt: "json"
|
||||
]
|
||||
|
||||
cmd = toQueryString(qParams)
|
||||
|
||||
apiPost(cmd) { response ->
|
||||
debugOut "${response.data}"
|
||||
}
|
||||
|
||||
//we want to ensure syncronization between rooms and bulbs
|
||||
//runIn(2, "syncronizeDevices")
|
||||
}
|
||||
|
||||
// Really not called from child, but called from poll() if it is a room
|
||||
def pollRoom(dni) {
|
||||
debugOut "In pollRoom"
|
||||
def data = ""
|
||||
def cmd = ""
|
||||
def roomDeviceData = []
|
||||
|
||||
data = "<gip><version>1</version><token>${atomicState.token}</token><rid>${dni}</rid><fields>name,power,control,status,state</fields></gip>"
|
||||
cmd = "RoomGetDevices"
|
||||
|
||||
def qParams = [
|
||||
cmd: cmd,
|
||||
data: "${data}",
|
||||
fmt: "json"
|
||||
]
|
||||
|
||||
cmd = toQueryString(qParams)
|
||||
|
||||
apiPost(cmd) { response ->
|
||||
roomDeviceData = response.data.gip
|
||||
}
|
||||
|
||||
debugOut "Room Data: ${roomDeviceData}"
|
||||
|
||||
def totalPower = 0
|
||||
def totalLevel = 0
|
||||
def cnt = 0
|
||||
def onCnt = 0 //used to tally on/off states
|
||||
|
||||
roomDeviceData.device.each({
|
||||
if ( getChildDevice(it.did) ) {
|
||||
totalPower += it.other.bulbpower.toInteger()
|
||||
totalLevel += it.level.toInteger()
|
||||
onCnt += it.state.toInteger()
|
||||
cnt += 1
|
||||
}
|
||||
})
|
||||
|
||||
def avgLevel = totalLevel/cnt
|
||||
def usingPower = totalPower * (avgLevel / 100) as float
|
||||
def room = getChildDevice( dni )
|
||||
|
||||
//the device is a room but we use same type file
|
||||
sendEvent( dni, [name: "setBulbPower",value:"${totalPower}"] ) //used in child device calcs
|
||||
|
||||
//if all devices in room are on, room is on
|
||||
if ( cnt == onCnt ) { // all devices are on
|
||||
sendEvent( dni, [name: "switch",value:"on"] )
|
||||
sendEvent( dni, [name: "power",value:usingPower.round(1)] )
|
||||
|
||||
} else { //if any device in room is off, room is off
|
||||
sendEvent( dni, [name: "switch",value:"off"] )
|
||||
sendEvent( dni, [name: "power",value:0.0] )
|
||||
}
|
||||
|
||||
debugOut "Room Using Power: ${usingPower.round(1)}"
|
||||
}
|
||||
|
||||
def poll(childDevice) {
|
||||
debugOut "In poll() with ${childDevice}"
|
||||
|
||||
|
||||
def dni = childDevice.device.deviceNetworkId
|
||||
|
||||
def bulbData = []
|
||||
def data = ""
|
||||
def cmd = ""
|
||||
|
||||
if ( isRoom(dni) ) { // this is a room, not a bulb
|
||||
pollRoom(dni)
|
||||
return
|
||||
}
|
||||
|
||||
data = "<gip><version>1</version><token>${atomicState.token}</token><did>${dni}</did></gip>"
|
||||
cmd = "DeviceGetInfo"
|
||||
|
||||
def qParams = [
|
||||
cmd: cmd,
|
||||
data: "${data}",
|
||||
fmt: "json"
|
||||
]
|
||||
|
||||
cmd = toQueryString(qParams)
|
||||
|
||||
apiPost(cmd) { response ->
|
||||
bulbData = response.data.gip
|
||||
debugOut "This Bulbs Data Return = ${bulbData}"
|
||||
|
||||
def bulb = getChildDevice( dni )
|
||||
|
||||
//set the devices power max setting to do calcs within the device type
|
||||
if ( bulbData.other.bulbpower )
|
||||
sendEvent( dni, [name: "setBulbPower",value:"${bulbData.other.bulbpower}"] )
|
||||
|
||||
if (( bulbData.state == "1" ) && ( bulb?.currentValue("switch") != "on" ))
|
||||
sendEvent( dni, [name: "switch",value:"on"] )
|
||||
|
||||
if (( bulbData.state == "0" ) && ( bulb?.currentValue("switch") != "off" ))
|
||||
sendEvent( dni, [name: "switch",value:"off"] )
|
||||
|
||||
//if ( bulbData.level != bulb?.currentValue("level")) {
|
||||
// sendEvent( dni, [name: "level",value: "${bulbData.level}"] )
|
||||
// sendEvent( dni, [name: "setLevel",value: "${bulbData.level}"] )
|
||||
//}
|
||||
|
||||
if (( bulbData.state == "1" ) && ( bulbData.other.bulbpower )) {
|
||||
def levelSetting = bulbData.level as float
|
||||
def bulbPowerMax = bulbData.other.bulbpower as float
|
||||
def calculatedPower = bulbPowerMax * (levelSetting / 100)
|
||||
sendEvent( dni, [name: "power", value: calculatedPower.round(1)] )
|
||||
}
|
||||
|
||||
if (( bulbData.state == "0" ) && ( bulbData.other.bulbpower ))
|
||||
sendEvent( dni, [name: "power", value: 0.0] )
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -39,7 +39,6 @@ preferences {
|
||||
page(name: "completionPage")
|
||||
page(name: "numbersPage")
|
||||
page(name: "controllerExplanationPage")
|
||||
page(name: "unsupportedDevicesPage")
|
||||
}
|
||||
|
||||
def rootPage() {
|
||||
@@ -48,9 +47,6 @@ def rootPage() {
|
||||
section("What to dim") {
|
||||
input(name: "dimmers", type: "capability.switchLevel", title: "Dimmers", description: null, multiple: true, required: true, submitOnChange: true)
|
||||
if (dimmers) {
|
||||
if (dimmersContainUnsupportedDevices()) {
|
||||
href(name: "toUnsupportedDevicesPage", page: "unsupportedDevicesPage", title: "Some of your selected dimmers don't seem to be supported", description: "Tap here to fix it", required: true)
|
||||
}
|
||||
href(name: "toNumbersPage", page: "numbersPage", title: "Duration & Direction", description: numbersPageHrefDescription(), state: "complete")
|
||||
}
|
||||
}
|
||||
@@ -75,31 +71,6 @@ def rootPage() {
|
||||
}
|
||||
}
|
||||
|
||||
def unsupportedDevicesPage() {
|
||||
|
||||
def unsupportedDimmers = dimmers.findAll { !hasSetLevelCommand(it) }
|
||||
|
||||
dynamicPage(name: "unsupportedDevicesPage") {
|
||||
if (unsupportedDimmers) {
|
||||
section("These devices do not support the setLevel command") {
|
||||
unsupportedDimmers.each {
|
||||
paragraph deviceLabel(it)
|
||||
}
|
||||
}
|
||||
section {
|
||||
input(name: "dimmers", type: "capability.sensor", title: "Please remove the above devices from this list.", submitOnChange: true, multiple: true)
|
||||
}
|
||||
section {
|
||||
paragraph "If you think there is a mistake here, please contact support."
|
||||
}
|
||||
} else {
|
||||
section {
|
||||
paragraph "You're all set. You can hit the back button, now. Thanks for cleaning up your settings :)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def controllerExplanationPage() {
|
||||
dynamicPage(name: "controllerExplanationPage", title: "How To Control Gentle Wake Up") {
|
||||
|
||||
@@ -557,16 +528,14 @@ def updateDimmers(percentComplete) {
|
||||
} else {
|
||||
|
||||
def shouldChangeColors = (colorize && colorize != "false")
|
||||
def canChangeColors = hasSetColorCommand(dimmer)
|
||||
|
||||
if (shouldChangeColors && hasSetColorCommand(dimmer)) {
|
||||
def hue = getHue(dimmer, nextLevel)
|
||||
log.debug "Setting ${deviceLabel(dimmer)} level to ${nextLevel} and hue to ${hue}"
|
||||
dimmer.setColor([hue: hue, saturation: 100, level: nextLevel])
|
||||
} else if (hasSetLevelCommand(dimmer)) {
|
||||
log.debug "Setting ${deviceLabel(dimmer)} level to ${nextLevel}"
|
||||
dimmer.setLevel(nextLevel)
|
||||
log.debug "Setting ${deviceLabel(dimmer)} to ${nextLevel}"
|
||||
|
||||
if (shouldChangeColors && canChangeColors) {
|
||||
dimmer.setColor([hue: getHue(dimmer, nextLevel), saturation: 100, level: nextLevel])
|
||||
} else {
|
||||
log.warn "${deviceLabel(dimmer)} does not have setColor or setLevel commands."
|
||||
dimmer.setLevel(nextLevel)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -848,21 +817,24 @@ private getRedHue(level) {
|
||||
if (level >= 96) return 17
|
||||
}
|
||||
|
||||
private dimmersContainUnsupportedDevices() {
|
||||
def found = dimmers.find { hasSetLevelCommand(it) == false }
|
||||
return found != null
|
||||
}
|
||||
|
||||
private hasSetLevelCommand(device) {
|
||||
return hasCommand(device, "setLevel")
|
||||
def isDimmer = false
|
||||
device.supportedCommands.each {
|
||||
if (it.name.contains("setLevel")) {
|
||||
isDimmer = true
|
||||
}
|
||||
}
|
||||
return isDimmer
|
||||
}
|
||||
|
||||
private hasSetColorCommand(device) {
|
||||
return hasCommand(device, "setColor")
|
||||
}
|
||||
|
||||
private hasCommand(device, String command) {
|
||||
return (device.supportedCommands.find { it.name == command } != null)
|
||||
def hasColor = false
|
||||
device.supportedCommands.each {
|
||||
if (it.name.contains("setColor")) {
|
||||
hasColor = true
|
||||
}
|
||||
}
|
||||
return hasColor
|
||||
}
|
||||
|
||||
private dimmersWithSetColorCommand() {
|
||||
@@ -1101,4 +1073,4 @@ def hasStartLevel() {
|
||||
|
||||
def hasEndLevel() {
|
||||
return (endLevel != null && endLevel != "")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -658,73 +658,29 @@ def updateDevice() {
|
||||
def data = request.JSON
|
||||
def command = data.command
|
||||
def arguments = data.arguments
|
||||
|
||||
log.debug "updateDevice, params: ${params}, request: ${data}"
|
||||
if (!command) {
|
||||
render status: 400, data: '{"msg": "command is required"}'
|
||||
} else {
|
||||
def device = allDevices.find { it.id == params.id }
|
||||
if (device) {
|
||||
if (validateCommand(device, command)) {
|
||||
if (arguments) {
|
||||
device."$command"(*arguments)
|
||||
} else {
|
||||
device."$command"()
|
||||
}
|
||||
render status: 204, data: "{}"
|
||||
} else {
|
||||
render status: 403, data: '{"msg": "Access denied. This command is not supported by current capability."}'
|
||||
}
|
||||
} else {
|
||||
render status: 404, data: '{"msg": "Device not found"}'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validating the command passed by the user based on capability.
|
||||
* @return boolean
|
||||
*/
|
||||
def validateCommand(device, command) {
|
||||
def capabilityCommands = getDeviceCapabilityCommands(device.capabilities)
|
||||
def currentDeviceCapability = getCapabilityName(device)
|
||||
if (currentDeviceCapability != "" && capabilityCommands[currentDeviceCapability]) {
|
||||
return command in capabilityCommands[currentDeviceCapability] ? true : false
|
||||
} else {
|
||||
// Handling other device types here, which don't accept commands
|
||||
httpError(400, "Bad request.")
|
||||
if (device) {
|
||||
if (device.hasCommand("$command")) {
|
||||
if (arguments) {
|
||||
device."$command"(*arguments)
|
||||
} else {
|
||||
device."$command"()
|
||||
}
|
||||
render status: 204, data: "{}"
|
||||
} else {
|
||||
render status: 404, data: '{"msg": "Command not supported by this Device"}'
|
||||
}
|
||||
} else {
|
||||
render status: 404, data: '{"msg": "Device not found"}'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Need to get the attribute name to do the lookup. Only
|
||||
* doing it for the device types which accept commands
|
||||
* @return attribute name of the device type
|
||||
*/
|
||||
def getCapabilityName(device) {
|
||||
def capName = ""
|
||||
if (switches.find{it.id == device.id})
|
||||
capName = "Switch"
|
||||
else if (alarms.find{it.id == device.id})
|
||||
capName = "Alarm"
|
||||
else if (locks.find{it.id == device.id})
|
||||
capName = "Lock"
|
||||
log.trace "Device: $device - Capability Name: $capName"
|
||||
return capName
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructing the map over here of
|
||||
* supported commands by device capability
|
||||
* @return a map of device capability -> supported commands
|
||||
*/
|
||||
def getDeviceCapabilityCommands(deviceCapabilities) {
|
||||
def map = [:]
|
||||
deviceCapabilities.collect {
|
||||
map[it.name] = it.commands.collect{ it.name.toString() }
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
def listSubscriptions() {
|
||||
log.debug "listSubscriptions()"
|
||||
app.subscriptions?.findAll { it.device?.device && it.device.id }?.collect {
|
||||
@@ -824,51 +780,17 @@ def deviceHandler(evt) {
|
||||
|
||||
def sendToHarmony(evt, String callbackUrl) {
|
||||
def callback = new URI(callbackUrl)
|
||||
if(isIP(callback.host)){
|
||||
def host = callback.port != -1 ? "${callback.host}:${callback.port}" : callback.host
|
||||
def path = callback.query ? "${callback.path}?${callback.query}".toString() : callback.path
|
||||
sendHubCommand(new physicalgraph.device.HubAction(
|
||||
method: "POST",
|
||||
path: path,
|
||||
headers: [
|
||||
"Host": host,
|
||||
"Content-Type": "application/json"
|
||||
],
|
||||
body: [evt: [deviceId: evt.deviceId, name: evt.name, value: evt.value]]
|
||||
))
|
||||
} else {
|
||||
def params = [
|
||||
uri: callbackUrl,
|
||||
body: [evt: [deviceId: evt.deviceId, name: evt.name, value: evt.value]]
|
||||
]
|
||||
try {
|
||||
log.debug "Sending data to Harmony Cloud: $params"
|
||||
httpPostJson(params) { resp ->
|
||||
log.debug "Harmony Cloud - Response: ${resp.status}"
|
||||
}
|
||||
} catch (e) {
|
||||
log.error "Harmony Cloud - Something went wrong: $e"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isIP(String str) {
|
||||
try {
|
||||
String[] parts = str.split("\\.");
|
||||
if (parts.length != 4) return false;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
int p
|
||||
try {
|
||||
p = Integer.parseInt(parts[i]);
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
if (p > 255 || p < 0) return false;
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
def host = callback.port != -1 ? "${callback.host}:${callback.port}" : callback.host
|
||||
def path = callback.query ? "${callback.path}?${callback.query}".toString() : callback.path
|
||||
sendHubCommand(new physicalgraph.device.HubAction(
|
||||
method: "POST",
|
||||
path: path,
|
||||
headers: [
|
||||
"Host": host,
|
||||
"Content-Type": "application/json"
|
||||
],
|
||||
body: [evt: [deviceId: evt.deviceId, name: evt.name, value: evt.value]]
|
||||
))
|
||||
}
|
||||
|
||||
def listHubs() {
|
||||
|
||||
@@ -92,89 +92,24 @@ void updateLock() {
|
||||
|
||||
private void updateAll(devices) {
|
||||
def command = request.JSON?.command
|
||||
def type = params.param1
|
||||
if (!devices) {
|
||||
httpError(404, "Devices not found")
|
||||
}
|
||||
|
||||
if (command){
|
||||
devices.each { device ->
|
||||
executeCommand(device, type, command)
|
||||
}
|
||||
if (command) {
|
||||
devices."$command"()
|
||||
}
|
||||
}
|
||||
|
||||
private void update(devices) {
|
||||
log.debug "update, request: ${request.JSON}, params: ${params}, devices: $devices.id"
|
||||
def command = request.JSON?.command
|
||||
def type = params.param1
|
||||
def device = devices?.find { it.id == params.id }
|
||||
|
||||
if (!device) {
|
||||
httpError(404, "Device not found")
|
||||
}
|
||||
|
||||
if (command) {
|
||||
executeCommand(device, type, command)
|
||||
def device = devices.find { it.id == params.id }
|
||||
if (!device) {
|
||||
httpError(404, "Device not found")
|
||||
} else {
|
||||
device."$command"()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validating the command passed by the user based on capability.
|
||||
* @return boolean
|
||||
*/
|
||||
def validateCommand(device, deviceType, command) {
|
||||
def capabilityCommands = getDeviceCapabilityCommands(device.capabilities)
|
||||
def currentDeviceCapability = getCapabilityName(deviceType)
|
||||
if (capabilityCommands[currentDeviceCapability]) {
|
||||
return command in capabilityCommands[currentDeviceCapability] ? true : false
|
||||
} else {
|
||||
// Handling other device types here, which don't accept commands
|
||||
httpError(400, "Bad request.")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Need to get the attribute name to do the lookup. Only
|
||||
* doing it for the device types which accept commands
|
||||
* @return attribute name of the device type
|
||||
*/
|
||||
def getCapabilityName(type) {
|
||||
switch(type) {
|
||||
case "switches":
|
||||
return "Switch"
|
||||
case "locks":
|
||||
return "Lock"
|
||||
default:
|
||||
return type
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructing the map over here of
|
||||
* supported commands by device capability
|
||||
* @return a map of device capability -> supported commands
|
||||
*/
|
||||
def getDeviceCapabilityCommands(deviceCapabilities) {
|
||||
def map = [:]
|
||||
deviceCapabilities.collect {
|
||||
map[it.name] = it.commands.collect{ it.name.toString() }
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and executes the command
|
||||
* on the device or devices
|
||||
*/
|
||||
def executeCommand(device, type, command) {
|
||||
if (validateCommand(device, type, command)) {
|
||||
device."$command"()
|
||||
} else {
|
||||
httpError(403, "Access denied. This command is not supported by current capability.")
|
||||
}
|
||||
}
|
||||
|
||||
private show(devices, name) {
|
||||
def device = devices.find { it.id == params.id }
|
||||
if (!device) {
|
||||
|
||||
Reference in New Issue
Block a user