Compare commits

..

3 Commits

Author SHA1 Message Date
Vinay Rao
8d8b039dda Merge pull request #975 from SmartThingsCommunity/staging
Rolling up staging to production
2016-06-07 12:23:55 -07:00
rohandesai
98d7829d1a Merge pull request #957 from rohandesai/peng-158-staging
PENG-158 UBI should not allow undefined commands
2016-06-02 14:33:49 -07:00
Rohan Desai
34107f935e PENG-158 UBI should not allow undefined commands
- now validating commands per capability of the device in the smartapp

removed commented out code
2016-06-02 11:18:54 -07:00
3 changed files with 105 additions and 812 deletions

View File

@@ -13,14 +13,14 @@
* Documented Header * Documented Header
* *
* Change 2: 2014-03-15 * 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. * levels down.
* *
* Change 3: 2014-04-02 (lieberman) * Change 3: 2014-04-02 (lieberman)
* Changed sendEvent() to createEvent() in parse() * Changed sendEvent() to createEvent() in parse()
* *
* Change 4: 2014-04-12 (wackford) * Change 4: 2014-04-12 (wackford)
* Added current power usage tile * Added current power usage tile
* *
* Change 5: 2014-09-14 (wackford) * Change 5: 2014-09-14 (wackford)
* a. Changed createEvent() to sendEvent() in parse() to * a. Changed createEvent() to sendEvent() in parse() to
@@ -33,7 +33,7 @@
* b. added refresh on udate * b. added refresh on udate
* c. added uninstallFromChildDevice to handle removing from settings * c. added uninstallFromChildDevice to handle removing from settings
* d. Changed to allow bulb to 100%, was possible to get past logic at 99 * d. Changed to allow bulb to 100%, was possible to get past logic at 99
* *
* Change 7: 2014-11-09 (wackford) * Change 7: 2014-11-09 (wackford)
* a. Added bulbpower calcs to device. TCP is broken * 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. * b. Changed to set dim level first then on. Much easier on the eys coming from bright.
@@ -42,7 +42,7 @@
* Code * Code
***************************************************************** *****************************************************************
*/ */
// for the UI // for the UI
metadata { metadata {
definition (name: "TCP Bulb", namespace: "wackford", author: "Todd Wackford") { definition (name: "TCP Bulb", namespace: "wackford", author: "Todd Wackford") {
capability "Switch" capability "Switch"
@@ -52,26 +52,28 @@ metadata {
capability "Switch Level" capability "Switch Level"
attribute "stepsize", "string" attribute "stepsize", "string"
command "levelUp" command "levelUp"
command "levelDown" command "levelDown"
command "on" command "on"
command "off" command "off"
command "setBulbPower" command "setBulbPower"
} }
simulator { simulator {
// TODO: define status and reply messages here // TODO: define status and reply messages here
} }
preferences { preferences {
input "stepsize", "number", title: "Step Size", description: "Dimmer Step Size", defaultValue: 5 input "stepsize", "number", title: "Step Size", description: "Dimmer Step Size", defaultValue: 5
} }
tiles { tiles {
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) { standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
state "on", label:'${name}', action:"off", icon:"st.switches.light.on", backgroundColor:"#79b821" state "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff"
state "off", label:'${name}', action:"on", icon:"st.switches.light.off", backgroundColor:"#ffffff" 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"
} }
controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false) { controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 2, inactiveLabel: false) {
state "level", action:"switch level.setLevel" state "level", action:"switch level.setLevel"
@@ -82,15 +84,15 @@ metadata {
valueTile("level", "device.level", inactiveLabel: false, decoration: "flat") { valueTile("level", "device.level", inactiveLabel: false, decoration: "flat") {
state "level", label: 'Level ${currentValue}%' state "level", label: 'Level ${currentValue}%'
} }
standardTile("lUp", "device.switchLevel", inactiveLabel: false,decoration: "flat", canChangeIcon: false) { standardTile("lUp", "device.switchLevel", inactiveLabel: false,decoration: "flat", canChangeIcon: false) {
state "default", action:"levelUp", icon:"st.illuminance.illuminance.bright" state "default", action:"levelUp", icon:"st.illuminance.illuminance.bright"
} }
standardTile("lDown", "device.switchLevel", inactiveLabel: false,decoration: "flat", canChangeIcon: false) { standardTile("lDown", "device.switchLevel", inactiveLabel: false,decoration: "flat", canChangeIcon: false) {
state "default", action:"levelDown", icon:"st.illuminance.illuminance.light" state "default", action:"levelDown", icon:"st.illuminance.illuminance.light"
} }
valueTile( "power", "device.power", inactiveLabel: false, decoration: "flat") { valueTile( "power", "device.power", inactiveLabel: false, decoration: "flat") {
state "power", label: '${currentValue} Watts' state "power", label: '${currentValue} Watts'
} }
main(["switch"]) main(["switch"])
details(["switch", "lUp", "lDown", "levelSliderControl", "level" , "power", "refresh" ]) details(["switch", "lUp", "lDown", "levelSliderControl", "level" , "power", "refresh" ])
@@ -101,10 +103,10 @@ metadata {
def parse(description) { def parse(description) {
//log.debug "parse() - $description" //log.debug "parse() - $description"
def results = [] def results = []
if ( description == "updated" ) if ( description == "updated" )
return return
if (description?.name && description?.value) if (description?.name && description?.value)
{ {
results << createEvent(name: "${description?.name}", value: "${description?.value}") results << createEvent(name: "${description?.name}", value: "${description?.value}")
@@ -114,73 +116,73 @@ def parse(description) {
// handle commands // handle commands
def setBulbPower(value) { def setBulbPower(value) {
state.bulbPower = value state.bulbPower = value
log.debug "In child with bulbPower of ${state.bulbPower}" log.debug "In child with bulbPower of ${state.bulbPower}"
} }
def on() { def on() {
log.debug "Executing 'on'" log.debug "Executing 'on'"
sendEvent(name:"switch",value:on) sendEvent(name:"switch",value:on)
parent.on(this) parent.on(this)
def levelSetting = device.latestValue("level") as Float ?: 1.0 def levelSetting = device.latestValue("level") as Float ?: 1.0
//def bulbPowerMax = device.latestValue("setBulbPower") as Float def bulbPowerMax = device.latestValue("setBulbPower") as Float
//def calculatedPower = bulbPowerMax * (levelSetting / 100) def calculatedPower = bulbPowerMax * (levelSetting / 100)
//sendEvent(name: "power", value: calculatedPower.round(1)) sendEvent(name: "power", value: calculatedPower.round(1))
if (device.latestValue("level") == null) { if (device.latestValue("level") == null) {
sendEvent( name: "level", value: 1.0 ) sendEvent( name: "level", value: 1.0 )
} }
} }
def off() { def off() {
log.debug "Executing 'off'" log.debug "Executing 'off'"
sendEvent(name:"switch",value:off) sendEvent(name:"switch",value:off)
parent.off(this) parent.off(this)
sendEvent(name: "power", value: 0.0) sendEvent(name: "power", value: 0.0)
} }
def levelUp() { def levelUp() {
def level = device.latestValue("level") as Integer ?: 0 def level = device.latestValue("level") as Integer ?: 0
def step = state.stepsize as float def step = state.stepsize as float
level+= step level+= step
if ( level > 100 ) if ( level > 100 )
level = 100 level = 100
setLevel(level) setLevel(level)
} }
def levelDown() { def levelDown() {
def level = device.latestValue("level") as Integer ?: 0 def level = device.latestValue("level") as Integer ?: 0
def step = state.stepsize as float def step = state.stepsize as float
level-= step level-= step
if ( level < 1 ) if ( level < 1 )
level = 1 level = 1
setLevel(level) setLevel(level)
} }
def setLevel(value) { def setLevel(value) {
log.debug "in setLevel with value: ${value}" log.debug "in setLevel with value: ${value}"
def level = value as Integer def level = value as Integer
sendEvent( name: "level", value: level )
sendEvent( name: "switch.setLevel", value:level )
parent.setLevel( this, level )
if (( level > 0 ) && ( level <= 100 )) sendEvent( name: "level", value: level )
on() sendEvent( name: "switch.setLevel", value:level )
else parent.setLevel( this, level )
off()
//def levelSetting = level as float if (( level > 0 ) && ( level <= 100 ))
//def bulbPowerMax = device.latestValue("setBulbPower") as float on()
//def calculatedPower = bulbPowerMax * (levelSetting / 100) else
//sendEvent(name: "power", value: calculatedPower.round(1)) 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() { def poll() {
@@ -198,29 +200,29 @@ def installed() {
} }
def updated() { def updated() {
initialize() initialize()
refresh() refresh()
} }
def initialize() { def initialize() {
if ( !settings.stepsize ) if ( !settings.stepsize )
state.stepsize = 10 //set the default stepsize state.stepsize = 10 //set the default stepsize
else else
state.stepsize = settings.stepsize state.stepsize = settings.stepsize
} }
/******************************************************************************* /*******************************************************************************
Method :uninstalled(args) Method :uninstalled(args)
(args) :none (args) :none
returns:Nothing returns:Nothing
ERRORS :No error handling is done ERRORS :No error handling is done
Purpose:This is standard ST method. Purpose:This is standard ST method.
Gets called when "remove" is selected in child device "preferences" Gets called when "remove" is selected in child device "preferences"
tile. It also get's called when "deleteChildDevice(child)" is tile. It also get's called when "deleteChildDevice(child)" is
called from parent service manager app. called from parent service manager app.
*******************************************************************************/ *******************************************************************************/
def uninstalled() { def uninstalled() {
log.debug "Executing 'uninstall' in device type" log.debug "Executing 'uninstall' in device type"
parent.uninstallFromChildDevice(this) parent.uninstallFromChildDevice(this)
} }

View File

@@ -1,679 +0,0 @@
/**
* 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] )
}
}

View File

@@ -30,7 +30,6 @@ definition(
preferences { preferences {
page(name:"mainPage", title:"Hue Device Setup", content:"mainPage", refreshTimeout:5) page(name:"mainPage", title:"Hue Device Setup", content:"mainPage", refreshTimeout:5)
page(name:"bridgeDiscovery", title:"Hue Bridge Discovery", content:"bridgeDiscovery", refreshTimeout:5) page(name:"bridgeDiscovery", title:"Hue Bridge Discovery", content:"bridgeDiscovery", refreshTimeout:5)
page(name:"bridgeDiscoveryFailed", title:"Bridge Discovery Failed", content:"bridgeDiscoveryFailed", refreshTimeout:0)
page(name:"bridgeBtnPush", title:"Linking with your Hue", content:"bridgeLinking", refreshTimeout:5) page(name:"bridgeBtnPush", title:"Linking with your Hue", content:"bridgeLinking", refreshTimeout:5)
page(name:"bulbDiscovery", title:"Hue Device Setup", content:"bulbDiscovery", refreshTimeout:5) page(name:"bulbDiscovery", title:"Hue Device Setup", content:"bulbDiscovery", refreshTimeout:5)
} }
@@ -54,21 +53,12 @@ def bridgeDiscovery(params=[:])
def options = bridges ?: [] def options = bridges ?: []
def numFound = options.size() ?: 0 def numFound = options.size() ?: 0
if (numFound == 0) { if (numFound == 0 && state.bridgeRefreshCount > 25) {
if (state.bridgeRefreshCount == 25) { log.trace "Cleaning old bridges memory"
log.trace "Cleaning old bridges memory" state.bridges = [:]
state.bridges = [:] state.bridgeRefreshCount = 0
app.updateSetting("selectedHue", "") app.updateSetting("selectedHue", "")
} else if (state.bridgeRefreshCount > 100) { }
// five minutes have passed, give up
// there seems to be a problem going back from discovey failed page in some instances (compared to pressing next)
// however it is probably a SmartThings settings issue
state.bridges = [:]
app.updateSetting("selectedHue", "")
state.bridgeRefreshCount = 0
return bridgeDiscoveryFailed()
}
}
ssdpSubscribe() ssdpSubscribe()
@@ -89,13 +79,6 @@ def bridgeDiscovery(params=[:])
} }
} }
def bridgeDiscoveryFailed() {
return dynamicPage(name:"bridgeDiscoveryFailed", title: "Bridge Discovery Failed", nextPage: "bridgeDiscovery") {
section("Failed to discover any Hue Bridges. Please confirm that the Hue Bridge is connected to the same network as your SmartThings Hub, and that it has power.") {
}
}
}
def bridgeLinking() def bridgeLinking()
{ {
int linkRefreshcount = !state.linkRefreshcount ? 0 : state.linkRefreshcount as int int linkRefreshcount = !state.linkRefreshcount ? 0 : state.linkRefreshcount as int
@@ -105,15 +88,19 @@ def bridgeLinking()
def nextPage = "" def nextPage = ""
def title = "Linking with your Hue" def title = "Linking with your Hue"
def paragraphText def paragraphText
def hueimage = null
if (selectedHue) { if (selectedHue) {
paragraphText = "Press the button on your Hue Bridge to setup a link. " paragraphText = "Press the button on your Hue Bridge to setup a link. "
hueimage = "http://huedisco.mediavibe.nl/wp-content/uploads/2013/09/pair-bridge.png"
} else { } else {
paragraphText = "You haven't selected a Hue Bridge, please Press \"Done\" and select one before clicking next." paragraphText = "You haven't selected a Hue Bridge, please Press \"Done\" and select one before clicking next."
hueimage = null
} }
if (state.username) { //if discovery worked if (state.username) { //if discovery worked
nextPage = "bulbDiscovery" nextPage = "bulbDiscovery"
title = "Success!" title = "Success!"
paragraphText = "Linking to your hub was a success! Please click 'Next'!" paragraphText = "Linking to your hub was a success! Please click 'Next'!"
hueimage = null
} }
if((linkRefreshcount % 2) == 0 && !state.username) { if((linkRefreshcount % 2) == 0 && !state.username) {
@@ -123,6 +110,8 @@ def bridgeLinking()
return dynamicPage(name:"bridgeBtnPush", title:title, nextPage:nextPage, refreshInterval:refreshInterval) { return dynamicPage(name:"bridgeBtnPush", title:title, nextPage:nextPage, refreshInterval:refreshInterval) {
section("") { section("") {
paragraph """${paragraphText}""" paragraph """${paragraphText}"""
if (hueimage != null)
image "${hueimage}"
} }
} }
} }
@@ -146,14 +135,13 @@ def bulbDiscovery() {
if((bulbRefreshCount % 5) == 0) { if((bulbRefreshCount % 5) == 0) {
discoverHueBulbs() discoverHueBulbs()
} }
def selectedBridge = state.bridges.find { key, value -> value?.serialNumber?.equalsIgnoreCase(selectedHue) }
def title = selectedBridge?.value?.name ?: "Find bridges"
return dynamicPage(name:"bulbDiscovery", title:"Bulb Discovery Started!", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) { return dynamicPage(name:"bulbDiscovery", title:"Bulb Discovery Started!", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) {
section("Please wait while we discover your Hue Bulbs. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") { section("Please wait while we discover your Hue Bulbs. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
input "selectedBulbs", "enum", required:false, title:"Select Hue Bulbs (${numFound} found)", multiple:true, options:bulboptions input "selectedBulbs", "enum", required:false, title:"Select Hue Bulbs (${numFound} found)", multiple:true, options:bulboptions
} }
section { section {
def title = getBridgeIP() ? "Hue bridge (${getBridgeIP()})" : "Find bridges"
href "bridgeDiscovery", title: title, description: "", state: selectedHue ? "complete" : "incomplete", params: [override: true] href "bridgeDiscovery", title: title, description: "", state: selectedHue ? "complete" : "incomplete", params: [override: true]
} }
@@ -360,29 +348,26 @@ def addBulbs() {
def newHueBulb def newHueBulb
if (bulbs instanceof java.util.Map) { if (bulbs instanceof java.util.Map) {
newHueBulb = bulbs.find { (app.id + "/" + it.value.id) == dni } newHueBulb = bulbs.find { (app.id + "/" + it.value.id) == dni }
if (newHueBulb != null) {
if (newHueBulb != null) { d = addChildBulb(dni, newHueBulb?.value?.type, newHueBulb?.value?.name, newHueBulb?.value?.hub)
d = addChildBulb(dni, newHueBulb?.value?.type, newHueBulb?.value?.name, newHueBulb?.value?.hub)
if (d) { if (d) {
log.debug "created ${d.displayName} with id $dni" log.debug "created ${d.displayName} with id $dni"
d.completedSetup = true
d.refresh() d.refresh()
} }
} else { } else {
log.debug "$dni in not longer paired to the Hue Bridge or ID changed" log.debug "$dni in not longer paired to the Hue Bridge or ID changed"
} }
} else { } else {
//backwards compatable //backwards compatable
newHueBulb = bulbs.find { (app.id + "/" + it.id) == dni } newHueBulb = bulbs.find { (app.id + "/" + it.id) == dni }
d = addChildBulb(dni, "Extended Color Light", newHueBulb?.value?.name, newHueBulb?.value?.hub) d = addChildBulb(dni, "Extended Color Light", newHueBulb?.value?.name, newHueBulb?.value?.hub)
d?.completedSetup = true
d?.refresh() d?.refresh()
} }
} else { } else {
log.debug "found ${d.displayName} with id $dni already exists, type: '$d.typeName'" log.debug "found ${d.displayName} with id $dni already exists, type: '$d.typeName'"
if (bulbs instanceof java.util.Map) { if (bulbs instanceof java.util.Map) {
// Update device type if incorrect // Update device type if incorrect
def newHueBulb = bulbs.find { (app.id + "/" + it.value.id) == dni } def newHueBulb = bulbs.find { (app.id + "/" + it.value.id) == dni }
upgradeDeviceType(d, newHueBulb?.value?.type) upgradeDeviceType(d, newHueBulb?.value?.type)
} }
} }
@@ -414,7 +399,6 @@ def addBridge() {
} }
if (newbridge) { if (newbridge) {
d = addChildDevice("smartthings", "Hue Bridge", selectedHue, vbridge.value.hub) d = addChildDevice("smartthings", "Hue Bridge", selectedHue, vbridge.value.hub)
d?.completedSetup = true
log.debug "created ${d.displayName} with id ${d.deviceNetworkId}" log.debug "created ${d.displayName} with id ${d.deviceNetworkId}"
def childDevice = getChildDevice(d.deviceNetworkId) def childDevice = getChildDevice(d.deviceNetworkId)
childDevice.sendEvent(name: "serialNumber", value: vbridge.value.serialNumber) childDevice.sendEvent(name: "serialNumber", value: vbridge.value.serialNumber)
@@ -502,21 +486,7 @@ void bridgeDescriptionHandler(physicalgraph.device.HubResponse hubResponse) {
def bridges = getHueBridges() def bridges = getHueBridges()
def bridge = bridges.find {it?.key?.contains(body?.device?.UDN?.text())} def bridge = bridges.find {it?.key?.contains(body?.device?.UDN?.text())}
if (bridge) { if (bridge) {
// serialNumber from API is in format of 0017882413ad (mac address), however on the actual bridge only last six bridge.value << [name:body?.device?.friendlyName?.text(), serialNumber:body?.device?.serialNumber?.text(), verified: true]
// characters are printed on the back so using that to identify bridge
def idNumber = body?.device?.serialNumber?.text()
if (idNumber?.size() >= 6)
idNumber = idNumber[-6..-1].toUpperCase()
// usually in form of bridge name followed by (ip), i.e. defaults to Philips Hue (192.168.1.2)
// replace IP with serial number to make it easier for user to identify
def name = body?.device?.friendlyName?.text()
def index = name?.indexOf('(')
if (index != -1) {
name = name.substring(0,index)
name += " ($idNumber)"
}
bridge.value << [name:name, serialNumber:body?.device?.serialNumber?.text(), verified: true]
} else { } else {
log.error "/description.xml returned a bridge that didn't exist" log.error "/description.xml returned a bridge that didn't exist"
} }