diff --git a/devicetypes/wackford/tcp-bulb.src/tcp-bulb.groovy b/devicetypes/wackford/tcp-bulb.src/tcp-bulb.groovy
index 186c8b2..729a1ed 100644
--- a/devicetypes/wackford/tcp-bulb.src/tcp-bulb.groovy
+++ b/devicetypes/wackford/tcp-bulb.src/tcp-bulb.groovy
@@ -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)
}
diff --git a/smartapps/mmacaula/tcp-bulbs-more-reliable.src/tcp-bulbs-more-reliable.groovy b/smartapps/mmacaula/tcp-bulbs-more-reliable.src/tcp-bulbs-more-reliable.groovy
new file mode 100644
index 0000000..6d51d3a
--- /dev/null
+++ b/smartapps/mmacaula/tcp-bulbs-more-reliable.src/tcp-bulbs-more-reliable.groovy
@@ -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 = "1${atomicState.token}"
+
+ 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 = "1${atomicState.token}"
+
+ 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 = "1${username}${hashedPassword}"
+
+ 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("[^<]*", '${atomicState.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 = "1${atomicState.token}${dni}"
+
+ 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 = "1$atomicState.token${dni}power1"
+ cmd = "RoomSendCommand"
+ } else {
+ data = "1$atomicState.token${dni}power1"
+ 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 = "1$atomicState.token${dni}power0"
+ cmd = "RoomSendCommand"
+ } else {
+ data = "1$atomicState.token${dni}power0"
+ 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 = "1${atomicState.token}${dni}level${value}"
+ cmd = "RoomSendCommand"
+ } else {
+ data = "1${atomicState.token}${dni}level${value}"
+ 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 = "1${atomicState.token}${dni}name,power,control,status,state"
+ 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 = "1${atomicState.token}${dni}"
+ 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] )
+
+ }
+
+
+}