From 5e93bca030d4a5fa640211021a0f0cb8e41037da Mon Sep 17 00:00:00 2001 From: Warodom Khamphanchai Date: Mon, 16 Nov 2015 16:33:07 -0800 Subject: [PATCH] LiFX - change shardUrl param to apiServerUrl --- .../lifx-color-bulb.groovy | 94 ++++--- .../lifx-white-bulb.groovy | 74 +++-- .../lifx-connect.src/lifx-connect.groovy | 261 +++++++++--------- 3 files changed, 239 insertions(+), 190 deletions(-) diff --git a/devicetypes/smartthings/lifx-color-bulb.src/lifx-color-bulb.groovy b/devicetypes/smartthings/lifx-color-bulb.src/lifx-color-bulb.groovy index 3c6737b..5513896 100644 --- a/devicetypes/smartthings/lifx-color-bulb.src/lifx-color-bulb.groovy +++ b/devicetypes/smartthings/lifx-color-bulb.src/lifx-color-bulb.groovy @@ -17,44 +17,50 @@ metadata { } simulator { - // TODO: define status and reply messages here + } - tiles { - standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) { - state "unreachable", label: "?", action:"refresh.refresh", icon:"st.switches.light.off", backgroundColor:"#666666" - state "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff" - state "off", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn" - state "turningOn", label:'Turning on', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff" - state "turningOff", label:'Turning off', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn" + tiles(scale: 2) { + multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){ + tileAttribute ("device.switch", key: "PRIMARY_CONTROL") { + attributeState "unreachable", label: "?", action:"refresh.refresh", icon:"http://hosted.lifx.co/smartthings/v1/196xUnreachable.png", backgroundColor:"#666666" + attributeState "on", label:'${name}', action:"switch.off", icon:"http://hosted.lifx.co/smartthings/v1/196xOn.png", backgroundColor:"#79b821", nextState:"turningOff" + attributeState "off", label:'${name}', action:"switch.on", icon:"http://hosted.lifx.co/smartthings/v1/196xOff.png", backgroundColor:"#ffffff", nextState:"turningOn" + attributeState "turningOn", label:'Turning on', action:"switch.off", icon:"http://hosted.lifx.co/smartthings/v1/196xOn.png", backgroundColor:"#79b821", nextState:"turningOff" + attributeState "turningOff", label:'Turning off', action:"switch.on", icon:"http://hosted.lifx.co/smartthings/v1/196xOff.png", backgroundColor:"#ffffff", nextState:"turningOn" + } + + tileAttribute ("device.level", key: "SLIDER_CONTROL") { + attributeState "level", action:"switch level.setLevel" + } + + tileAttribute ("device.color", key: "COLOR_CONTROL") { + attributeState "color", action:"setColor" + } + + tileAttribute ("device.model", key: "SECONDARY_CONTROL") { + attributeState "model", label: '${currentValue}' + } } - standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") { + + standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh" } + valueTile("null", "device.switch", inactiveLabel: false, decoration: "flat") { state "default", label:'' } - controlTile("rgbSelector", "device.color", "color", height: 3, width: 3, inactiveLabel: false) { - state "color", action:"setColor" - } - - controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 3, inactiveLabel: false, range:"(0..100)") { - state "level", action:"switch level.setLevel" - } - valueTile("level", "device.level", inactiveLabel: false, icon: "st.illuminance.illuminance.light", decoration: "flat") { - state "level", label: '${currentValue}%' - } - - controlTile("colorTempSliderControl", "device.colorTemperature", "slider", height: 1, width: 2, inactiveLabel: false, range:"(2700..9000)") { + controlTile("colorTempSliderControl", "device.colorTemperature", "slider", height: 2, width: 4, inactiveLabel: false, range:"(2700..9000)") { state "colorTemp", action:"color temperature.setColorTemperature" } - valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat") { + + valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", height: 2, width: 2) { state "colorTemp", label: '${currentValue}K' } - main(["switch"]) - details(["switch", "refresh", "level", "levelSliderControl", "rgbSelector", "colorTempSliderControl", "colorTemp"]) + main "switch" + details(["switch", "colorTempSliderControl", "colorTemp", "refresh"]) } } @@ -70,7 +76,7 @@ def parse(String description) { def setHue(percentage) { log.debug "setHue ${percentage}" parent.logErrors(logObject: log) { - def resp = parent.apiPUT("/lights/${device.deviceNetworkId}/color", [color: "hue:${percentage * 3.6}"]) + def resp = parent.apiPUT("/lights/${selector()}/state", [color: "hue:${percentage * 3.6}", power: "on"]) if (resp.status < 300) { sendEvent(name: "hue", value: percentage) sendEvent(name: "switch", value: "on") @@ -83,7 +89,7 @@ def setHue(percentage) { def setSaturation(percentage) { log.debug "setSaturation ${percentage}" parent.logErrors(logObject: log) { - def resp = parent.apiPUT("/lights/${device.deviceNetworkId}/color", [color: "saturation:${percentage / 100}"]) + def resp = parent.apiPUT("/lights/${selector()}/state", [color: "saturation:${percentage / 100}", power: "on"]) if (resp.status < 300) { sendEvent(name: "saturation", value: percentage) sendEvent(name: "switch", value: "on") @@ -114,7 +120,7 @@ def setColor(Map color) { } } parent.logErrors(logObject:log) { - def resp = parent.apiPUT("/lights/${device.deviceNetworkId}/color", [color: attrs.join(" ")]) + def resp = parent.apiPUT("/lights/${selector()}/state", [color: attrs.join(" "), power: "on"]) if (resp.status < 300) { sendEvent(name: "color", value: color.hex) sendEvent(name: "switch", value: "on") @@ -135,9 +141,10 @@ def setLevel(percentage) { return off() // if the brightness is set to 0, just turn it off } parent.logErrors(logObject:log) { - def resp = parent.apiPUT("/lights/${device.deviceNetworkId}/color", ["color": "brightness:${percentage / 100}"]) + def resp = parent.apiPUT("/lights/${selector()}/state", ["brightness": percentage / 100, "power": "on"]) if (resp.status < 300) { sendEvent(name: "level", value: percentage) + sendEvent(name: "switch.setLevel", value: percentage) sendEvent(name: "switch", value: "on") } else { log.error("Bad setLevel result: [${resp.status}] ${resp.data}") @@ -148,7 +155,7 @@ def setLevel(percentage) { def setColorTemperature(kelvin) { log.debug "Executing 'setColorTemperature' to ${kelvin}" parent.logErrors() { - def resp = parent.apiPUT("/lights/${device.deviceNetworkId}/color", [color: "kelvin:${kelvin}"]) + def resp = parent.apiPUT("/lights/${selector()}/state", [color: "kelvin:${kelvin}", power: "on"]) if (resp.status < 300) { sendEvent(name: "colorTemperature", value: kelvin) sendEvent(name: "color", value: "#ffffff") @@ -163,7 +170,7 @@ def setColorTemperature(kelvin) { def on() { log.debug "Device setOn" parent.logErrors() { - if (parent.apiPUT("/lights/${device.deviceNetworkId}/power", [state: "on"]) != null) { + if (parent.apiPUT("/lights/${selector()}/state", [power: "on"]) != null) { sendEvent(name: "switch", value: "on") } } @@ -172,7 +179,7 @@ def on() { def off() { log.debug "Device setOff" parent.logErrors() { - if (parent.apiPUT("/lights/${device.deviceNetworkId}/power", [state: "off"]) != null) { + if (parent.apiPUT("/lights/${selector()}/state", [power: "off"]) != null) { sendEvent(name: "switch", value: "off") } } @@ -180,19 +187,26 @@ def off() { def poll() { log.debug "Executing 'poll' for ${device} ${this} ${device.deviceNetworkId}" - def resp = parent.apiGET("/lights/${device.deviceNetworkId}") - if (resp.status != 200) { + def resp = parent.apiGET("/lights/${selector()}") + if (resp.status == 404) { + sendEvent(name: "switch", value: "unreachable") + return [] + } else if (resp.status != 200) { log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}") return [] } - def data = resp.data + def data = resp.data[0] + log.debug("Data: ${data}") - sendEvent(name: "level", value: sprintf("%.1f", (data.brightness ?: 1) * 100)) + sendEvent(name: "label", value: data.label) + sendEvent(name: "level", value: Math.round((data.brightness ?: 1) * 100)) + sendEvent(name: "switch.setLevel", value: Math.round((data.brightness ?: 1) * 100)) sendEvent(name: "switch", value: data.connected ? data.power : "unreachable") sendEvent(name: "color", value: colorUtil.hslToHex((data.color.hue / 3.6) as int, (data.color.saturation * 100) as int)) - sendEvent(name: "hue", value: data.color.hue / 3.6) + sendEvent(name: "hue", value: data.color.hue / 3.6) sendEvent(name: "saturation", value: data.color.saturation * 100) sendEvent(name: "colorTemperature", value: data.color.kelvin) + sendEvent(name: "model", value: "${data.product.company} ${data.product.name}") return [] } @@ -201,3 +215,11 @@ def refresh() { log.debug "Executing 'refresh'" poll() } + +def selector() { + if (device.deviceNetworkId.contains(":")) { + return device.deviceNetworkId + } else { + return "id:${device.deviceNetworkId}" + } +} diff --git a/devicetypes/smartthings/lifx-white-bulb.src/lifx-white-bulb.groovy b/devicetypes/smartthings/lifx-white-bulb.src/lifx-white-bulb.groovy index 164c673..09107c0 100644 --- a/devicetypes/smartthings/lifx-white-bulb.src/lifx-white-bulb.groovy +++ b/devicetypes/smartthings/lifx-white-bulb.src/lifx-white-bulb.groovy @@ -16,41 +16,44 @@ metadata { } simulator { - // TODO: define status and reply messages here + } - tiles { - standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) { - state "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff" - state "off", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn" - state "turningOn", label:'Turning on', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff" - state "turningOff", label:'Turning off', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn" - state "unreachable", label: "?", action:"refresh.refresh", icon:"st.switches.light.off", backgroundColor:"#666666" + tiles(scale: 2) { + multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){ + tileAttribute ("device.switch", key: "PRIMARY_CONTROL") { + attributeState "unreachable", label: "?", action:"refresh.refresh", icon:"http://hosted.lifx.co/smartthings/v1/196xUnreachable.png", backgroundColor:"#666666" + attributeState "on", label:'${name}', action:"switch.off", icon:"http://hosted.lifx.co/smartthings/v1/196xOn.png", backgroundColor:"#79b821", nextState:"turningOff" + attributeState "off", label:'${name}', action:"switch.on", icon:"http://hosted.lifx.co/smartthings/v1/196xOff.png", backgroundColor:"#ffffff", nextState:"turningOn" + attributeState "turningOn", label:'Turning on', action:"switch.off", icon:"http://hosted.lifx.co/smartthings/v1/196xOn.png", backgroundColor:"#79b821", nextState:"turningOff" + attributeState "turningOff", label:'Turning off', action:"switch.on", icon:"http://hosted.lifx.co/smartthings/v1/196xOff.png", backgroundColor:"#ffffff", nextState:"turningOn" + + } + tileAttribute ("device.level", key: "SLIDER_CONTROL") { + attributeState "level", action:"switch level.setLevel" + } } - standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") { + + standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) { state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh" } + valueTile("null", "device.switch", inactiveLabel: false, decoration: "flat") { state "default", label:'' } - controlTile("levelSliderControl", "device.level", "slider", height: 1, width: 3, inactiveLabel: false, range:"(0..100)") { - state "level", action:"switch level.setLevel" - } - valueTile("level", "device.level", inactiveLabel: false, icon: "st.illuminance.illuminance.light", decoration: "flat") { - state "level", label: '${currentValue}%' - } - - controlTile("colorTempSliderControl", "device.colorTemperature", "slider", height: 1, width: 2, inactiveLabel: false, range:"(2700..6500)") { + controlTile("colorTempSliderControl", "device.colorTemperature", "slider", height: 2, width: 4, inactiveLabel: false, range:"(2700..9000)") { state "colorTemp", action:"color temperature.setColorTemperature" } - valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat") { + + valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", height: 2, width: 2) { state "colorTemp", label: '${currentValue}K' } - main(["switch"]) - details(["switch", "refresh", "level", "levelSliderControl", "colorTempSliderControl", "colorTemp"]) + main "switch" + details(["switch", "colorTempSliderControl", "colorTemp", "refresh"]) } + } // parse events into attributes @@ -72,9 +75,10 @@ def setLevel(percentage) { return off() // if the brightness is set to 0, just turn it off } parent.logErrors(logObject:log) { - def resp = parent.apiPUT("/lights/${device.deviceNetworkId}/color", ["color": "brightness:${percentage / 100}"]) + def resp = parent.apiPUT("/lights/${selector()}/state", [brightness: percentage / 100, power: "on"]) if (resp.status < 300) { sendEvent(name: "level", value: percentage) + sendEvent(name: "switch.setLevel", value: percentage) sendEvent(name: "switch", value: "on") } else { log.error("Bad setLevel result: [${resp.status}] ${resp.data}") @@ -85,7 +89,7 @@ def setLevel(percentage) { def setColorTemperature(kelvin) { log.debug "Executing 'setColorTemperature' to ${kelvin}" parent.logErrors() { - def resp = parent.apiPUT("/lights/${device.deviceNetworkId}/color", [color: "kelvin:${kelvin}"]) + def resp = parent.apiPUT("/lights/${selector()}/state", [color: "kelvin:${kelvin}", power: "on"]) if (resp.status < 300) { sendEvent(name: "colorTemperature", value: kelvin) sendEvent(name: "color", value: "#ffffff") @@ -100,7 +104,7 @@ def setColorTemperature(kelvin) { def on() { log.debug "Device setOn" parent.logErrors() { - if (parent.apiPUT("/lights/${device.deviceNetworkId}/power", [state: "on"]) != null) { + if (parent.apiPUT("/lights/${selector()}/state", [power: "on"]) != null) { sendEvent(name: "switch", value: "on") } } @@ -109,7 +113,7 @@ def on() { def off() { log.debug "Device setOff" parent.logErrors() { - if (parent.apiPUT("/lights/${device.deviceNetworkId}/power", [state: "off"]) != null) { + if (parent.apiPUT("/lights/${selector()}/state", [power: "off"]) != null) { sendEvent(name: "switch", value: "off") } } @@ -117,16 +121,22 @@ def off() { def poll() { log.debug "Executing 'poll' for ${device} ${this} ${device.deviceNetworkId}" - def resp = parent.apiGET("/lights/${device.deviceNetworkId}") - if (resp.status != 200) { + def resp = parent.apiGET("/lights/${selector()}") + if (resp.status == 404) { + sendEvent(name: "switch", value: "unreachable") + return [] + } else if (resp.status != 200) { log.error("Unexpected result in poll(): [${resp.status}] ${resp.data}") return [] } - def data = resp.data + def data = resp.data[0] - sendEvent(name: "level", value: sprintf("%f", (data.brightness ?: 1) * 100)) + sendEvent(name: "label", value: data.label) + sendEvent(name: "level", value: Math.round((data.brightness ?: 1) * 100)) + sendEvent(name: "switch.setLevel", value: Math.round((data.brightness ?: 1) * 100)) sendEvent(name: "switch", value: data.connected ? data.power : "unreachable") sendEvent(name: "colorTemperature", value: data.color.kelvin) + sendEvent(name: "model", value: data.product.name) return [] } @@ -135,3 +145,11 @@ def refresh() { log.debug "Executing 'refresh'" poll() } + +def selector() { + if (device.deviceNetworkId.contains(":")) { + return device.deviceNetworkId + } else { + return "id:${device.deviceNetworkId}" + } +} diff --git a/smartapps/smartthings/lifx-connect.src/lifx-connect.groovy b/smartapps/smartthings/lifx-connect.src/lifx-connect.groovy index dea5939..587c311 100644 --- a/smartapps/smartthings/lifx-connect.src/lifx-connect.groovy +++ b/smartapps/smartthings/lifx-connect.src/lifx-connect.groovy @@ -5,23 +5,23 @@ * */ definition( - name: "LIFX (Connect)", - namespace: "smartthings", - author: "LIFX", - description: "Allows you to use LIFX smart light bulbs with SmartThings.", - category: "Convenience", - iconUrl: "https://cloud.lifx.com/images/lifx.png", - iconX2Url: "https://cloud.lifx.com/images/lifx.png", - iconX3Url: "https://cloud.lifx.com/images/lifx.png", - oauth: true, - singleInstance: true) { - appSetting "clientId" - appSetting "clientSecret" - } + name: "LIFX (Connect)", + namespace: "smartthings", + author: "LIFX", + description: "Allows you to use LIFX smart light bulbs with SmartThings.", + category: "Convenience", + iconUrl: "https://cloud.lifx.com/images/lifx.png", + iconX2Url: "https://cloud.lifx.com/images/lifx.png", + iconX3Url: "https://cloud.lifx.com/images/lifx.png", + oauth: true, + singleInstance: true) { + appSetting "clientId" + appSetting "clientSecret" +} preferences { - page(name: "Credentials", title: "LIFX", content: "authPage", install: false) + page(name: "Credentials", title: "LIFX", content: "authPage", install: true) } mappings { @@ -33,29 +33,29 @@ mappings { path("/test") { action: [ GET: "oauthSuccess" ] } } -def getServerUrl() { return "https://graph.api.smartthings.com" } -def apiURL(path = '/') { return "https://api.lifx.com/v1beta1${path}" } -def buildRedirectUrl(page) { - return "${serverUrl}/api/token/${state.accessToken}/smartapps/installations/${app.id}/${page}" -} +def getServerUrl() { return "https://graph.api.smartthings.com" } +def getCallbackUrl() { return "https://graph.api.smartthings.com/oauth/callback"} +def apiURL(path = '/') { return "https://api.lifx.com/v1${path}" } +def getSecretKey() { return appSettings.secretKey } +def getClientId() { return appSettings.clientId } def authPage() { - log.debug "authPage" + log.debug "authPage test1" if (!state.lifxAccessToken) { log.debug "no LIFX access token" // This is the SmartThings access token if (!state.accessToken) { log.debug "no access token, create access token" - createAccessToken() // predefined method + state.accessToken = createAccessToken() // predefined method } def description = "Tap to enter LIFX credentials" - def redirectUrl = "${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${state.accessToken}" // this triggers oauthInit() below - log.debug "app id: ${app.id}" + def redirectUrl = "${serverUrl}/oauth/initialize?appId=${app.id}&access_token=${state.accessToken}&apiServerUrl=${apiServerUrl}" // this triggers oauthInit() below +// def redirectUrl = "${apiServerUrl}" + log.debug "app id: ${app.id}" log.debug "redirect url: ${redirectUrl}" - return dynamicPage(name: "Credentials", title: "Connect to LIFX", nextPage: null, uninstall: true, install:false) { + return dynamicPage(name: "Credentials", title: "Connect to LIFX", nextPage: null, uninstall: true, install:true) { section { href(url:redirectUrl, required:true, title:"Connect to LIFX", description:"Tap here to connect your LIFX account") - // href(url:buildRedirectUrl("test"), title: "Message test") } } } else { @@ -63,17 +63,15 @@ def authPage() { def options = locationOptions() ?: [] def count = options.size() - def refreshInterval = 3 - return dynamicPage(name:"Credentials", title:"Select devices...", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) { + return dynamicPage(name:"Credentials", title:"", nextPage:"", install:true, uninstall: true) { section("Select your location") { - input "selectedLocationId", "enum", required:true, title:"Select location (${count} found)", multiple:false, options:options + input "selectedLocationId", "enum", required:true, title:"Select location (${count} found)", multiple:false, options:options, submitOnChange: true } } } } - // OAuth def oauthInit() { @@ -112,7 +110,7 @@ def oauthCallback() { } def oauthReceiveToken(redirectUrl = null) { - + // Not sure what redirectUrl is for log.debug "receiveToken - params: ${params}" def oauthParams = [ client_id: "${appSettings.clientId}", client_secret: "${appSettings.clientSecret}", grant_type: "authorization_code", code: params.code, scope: params.scope ] // how is params.code valid here? def params = [ @@ -135,25 +133,25 @@ def oauthReceiveToken(redirectUrl = null) { def oauthSuccess() { def message = """ -

Your LIFX Account is now connected to SmartThings!

-

Click 'Done' to finish setup.

- """ +

Your LIFX Account is now connected to SmartThings!

+

Click 'Done' to finish setup.

+ """ oauthConnectionStatus(message) } def oauthFailure() { def message = """ -

The connection could not be established!

-

Click 'Done' to return to the menu.

- """ +

The connection could not be established!

+

Click 'Done' to return to the menu.

+ """ oauthConnectionStatus(message) } def oauthReceivedToken() { def message = """ -

Your LIFX Account is already connected to SmartThings!

-

Click 'Done' to finish setup.

- """ +

Your LIFX Account is already connected to SmartThings!

+

Click 'Done' to finish setup.

+ """ oauthConnectionStatus(message) } @@ -161,74 +159,74 @@ def oauthConnectionStatus(message, redirectUrl = null) { def redirectHtml = "" if (redirectUrl) { redirectHtml = """ - - """ + + """ } def html = """ - - - - - SmartThings Connection - - ${redirectHtml} - - -
- LIFX icon - connected device icon - SmartThings logo -

- ${message} -

-
- - - """ + + + + + SmartThings Connection + + ${redirectHtml} + + +
+ LIFX icon + connected device icon + SmartThings logo +

+ ${message} +

+
+ + + """ render contentType: 'text/html', data: html } @@ -239,7 +237,6 @@ String toQueryString(Map m) { // App lifecycle hooks def installed() { - enableCallback() // wtf does this do? if (!state.accessToken) { createAccessToken() } else { @@ -251,7 +248,6 @@ def installed() { // called after settings are changed def updated() { - enableCallback() // not sure what this does if (!state.accessToken) { createAccessToken() } else { @@ -305,27 +301,36 @@ def logErrors(options = [errorReturn: null, logObject: log], Closure c) { state.remove("lifxAccessToken") options.logObject.warn "Access token is not valid" } - return options.errerReturn + return options.errorReturn } catch (java.net.SocketTimeoutException e) { options.logObject.warn "Connection timed out, not much we can do here" - return options.errerReturn + return options.errorReturn } } def apiGET(path) { - httpGet(uri: apiURL(path), headers: apiRequestHeaders()) {response -> - logResponse(response) - return response + try { + httpGet(uri: apiURL(path), headers: apiRequestHeaders()) {response -> + logResponse(response) + return response + } + } catch (groovyx.net.http.HttpResponseException e) { + logResponse(e.response) + return e.response } } def apiPUT(path, body = [:]) { - log.debug("Beginning API PUT: ${path}, ${body}") - httpPutJson(uri: apiURL(path), body: new groovy.json.JsonBuilder(body).toString(), headers: apiRequestHeaders(), ) {response -> - logResponse(response) - return response - } -} + try { + log.debug("Beginning API PUT: ${path}, ${body}") + httpPutJson(uri: apiURL(path), body: new groovy.json.JsonBuilder(body).toString(), headers: apiRequestHeaders(), ) {response -> + logResponse(response) + return response + } + } catch (groovyx.net.http.HttpResponseException e) { + logResponse(e.response) + return e.response + }} def devicesList(selector = '') { logErrors([]) { @@ -340,12 +345,12 @@ def devicesList(selector = '') { } Map locationOptions() { - def options = [:] def devices = devicesList() devices.each { device -> options[device.location.id] = device.location.name } + log.debug("Locations: ${options}") return options } @@ -359,28 +364,32 @@ def updateDevices() { state.devices = [:] } def devices = devicesInLocation() - def deviceIds = devices*.id + def selectors = [] + + log.debug("All selectors: ${selectors}") + devices.each { device -> def childDevice = getChildDevice(device.id) + selectors.add("${device.id}") if (!childDevice) { - log.info("Adding device ${device.id}: ${device.capabilities}") + log.info("Adding device ${device.id}: ${device.product}") def data = [ label: device.label, - level: sprintf("%f", (device.brightness ?: 1) * 100), + level: Math.round((device.brightness ?: 1) * 100), switch: device.connected ? device.power : "unreachable", colorTemperature: device.color.kelvin ] - if (device.capabilities.has_color) { + if (device.product.capabilities.has_color) { data["color"] = colorUtil.hslToHex((device.color.hue / 3.6) as int, (device.color.saturation * 100) as int) data["hue"] = device.color.hue / 3.6 data["saturation"] = device.color.saturation * 100 - childDevice = addChildDevice("smartthings", "LIFX Color Bulb", device.id, null, data) + childDevice = addChildDevice(app.namespace, "LIFX Color Bulb", device.id, null, data) } else { - childDevice = addChildDevice("smartthings", "LIFX White Bulb", device.id, null, data) + childDevice = addChildDevice(app.namespace, "LIFX White Bulb", device.id, null, data) } } } - getChildDevices().findAll { !deviceIds.contains(it.deviceNetworkId) }.each { + getChildDevices().findAll { !selectors.contains("${it.deviceNetworkId}") }.each { log.info("Deleting ${it.deviceNetworkId}") deleteChildDevice(it.deviceNetworkId) } @@ -392,4 +401,4 @@ def refreshDevices() { getChildDevices().each { device -> device.refresh() } -} \ No newline at end of file +}