Compare commits

..

1 Commits

8 changed files with 61 additions and 377 deletions

View File

@@ -7,10 +7,9 @@ apply plugin: 'smartthings-hipchat'
buildscript { buildscript {
dependencies { dependencies {
classpath "com.smartthings.deployment:executable-deployment-scripts:1.0.6" classpath "com.smartthings.deployment:executable-deployment-scripts:1.0.3"
} }
repositories { repositories {
mavenLocal()
jcenter() jcenter()
maven { maven {
credentials { credentials {

View File

@@ -15,13 +15,13 @@ deployment:
develop: develop:
branch: master branch: master
commands: commands:
- ./gradlew deployArchives -PsmartThingsArtifactoryUserName=$ARTIFACTORY_USERNAME -PsmartThingsArtifactoryPassword=$ARTIFACTORY_PASSWORD -Ps3Buckets="$S3_BUCKETS_DEV" - ./gradlew deployArchives -PsmartThingsArtifactoryUserName=$ARTIFACTORY_USERNAME -PsmartThingsArtifactoryPassword=$ARTIFACTORY_PASSWORD -Ps3BucketName=$S3_BUCKET_NAME_PREPROD_DEV
- ./gradlew hipchatSendNotification -PsmartThingsArtifactoryUserName=$ARTIFACTORY_USERNAME -PsmartThingsArtifactoryPassword=$ARTIFACTORY_PASSWORD -Pbranch=$CIRCLE_BRANCH - ./gradlew hipchatSendNotification -PsmartThingsArtifactoryUserName=$ARTIFACTORY_USERNAME -PsmartThingsArtifactoryPassword=$ARTIFACTORY_PASSWORD -Pbranch=$CIRCLE_BRANCH
- ./gradlew hipchatShareFile -PsmartThingsArtifactoryUserName=$ARTIFACTORY_USERNAME -PsmartThingsArtifactoryPassword=$ARTIFACTORY_PASSWORD - ./gradlew hipchatShareFile -PsmartThingsArtifactoryUserName=$ARTIFACTORY_USERNAME -PsmartThingsArtifactoryPassword=$ARTIFACTORY_PASSWORD
stage: stage:
branch: staging branch: staging
commands: commands:
- ./gradlew deployArchives -PsmartThingsArtifactoryUserName=$ARTIFACTORY_USERNAME -PsmartThingsArtifactoryPassword=$ARTIFACTORY_PASSWORD -Ps3Buckets="$S3_BUCKETS_STAGE" - ./gradlew deployArchives -PsmartThingsArtifactoryUserName=$ARTIFACTORY_USERNAME -PsmartThingsArtifactoryPassword=$ARTIFACTORY_PASSWORD -Ps3BucketName=$S3_BUCKET_NAME_PREPROD_STAGING
- ./gradlew hipchatSendNotification -PsmartThingsArtifactoryUserName=$ARTIFACTORY_USERNAME -PsmartThingsArtifactoryPassword=$ARTIFACTORY_PASSWORD -Pbranch=$CIRCLE_BRANCH - ./gradlew hipchatSendNotification -PsmartThingsArtifactoryUserName=$ARTIFACTORY_USERNAME -PsmartThingsArtifactoryPassword=$ARTIFACTORY_PASSWORD -Pbranch=$CIRCLE_BRANCH
- ./gradlew hipchatShareFile -PsmartThingsArtifactoryUserName=$ARTIFACTORY_USERNAME -PsmartThingsArtifactoryPassword=$ARTIFACTORY_PASSWORD - ./gradlew hipchatShareFile -PsmartThingsArtifactoryUserName=$ARTIFACTORY_USERNAME -PsmartThingsArtifactoryPassword=$ARTIFACTORY_PASSWORD

View File

@@ -1,227 +0,0 @@
/**
* Hue Bloom
*
* Philips Hue Type "Color Light"
*
* Author: SmartThings
*/
// for the UI
metadata {
// Automatically generated. Make future change here.
definition (name: "Hue Bloom", namespace: "smartthings", author: "SmartThings") {
capability "Switch Level"
capability "Actuator"
capability "Color Control"
capability "Switch"
capability "Refresh"
capability "Sensor"
command "setAdjustedColor"
command "reset"
command "refresh"
}
simulator {
// TODO: define status and reply messages here
}
tiles (scale: 2){
multiAttributeTile(name:"rich-control", type: "lighting", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#00A0DC", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#C6C7CC", nextState:"turningOn"
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#00A0DC", nextState:"turningOff"
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#C6C7CC", nextState:"turningOn"
}
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel", range:"(0..100)"
}
tileAttribute ("device.level", key: "SECONDARY_CONTROL") {
attributeState "level", label: 'Level ${currentValue}%'
}
tileAttribute ("device.color", key: "COLOR_CONTROL") {
attributeState "color", action:"setAdjustedColor"
}
}
standardTile("reset", "device.reset", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:"Reset Color", action:"reset", icon:"st.lights.philips.hue-single"
}
standardTile("refresh", "device.refresh", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main(["rich-control"])
details(["rich-control", "colorTempSliderControl", "colorTemp", "reset", "refresh"])
}
}
// parse events into attributes
def parse(description) {
log.debug "parse() - $description"
def results = []
def map = description
if (description instanceof String) {
log.debug "Hue Bulb stringToMap - ${map}"
map = stringToMap(description)
}
if (map?.name && map?.value) {
results << createEvent(name: "${map?.name}", value: "${map?.value}")
}
results
}
// handle commands
void on() {
log.trace parent.on(this)
sendEvent(name: "switch", value: "on")
}
void off() {
log.trace parent.off(this)
sendEvent(name: "switch", value: "off")
}
void nextLevel() {
def level = device.latestValue("level") as Integer ?: 0
if (level <= 100) {
level = Math.min(25 * (Math.round(level / 25) + 1), 100) as Integer
}
else {
level = 25
}
setLevel(level)
}
void setLevel(percent) {
log.debug "Executing 'setLevel'"
if (verifyPercent(percent)) {
parent.setLevel(this, percent)
sendEvent(name: "level", value: percent, descriptionText: "Level has changed to ${percent}%")
sendEvent(name: "switch", value: "on")
}
}
void setSaturation(percent) {
log.debug "Executing 'setSaturation'"
if (verifyPercent(percent)) {
parent.setSaturation(this, percent)
sendEvent(name: "saturation", value: percent, displayed: false)
}
}
void setHue(percent) {
log.debug "Executing 'setHue'"
if (verifyPercent(percent)) {
parent.setHue(this, percent)
sendEvent(name: "hue", value: percent, displayed: false)
}
}
void setColor(value) {
log.debug "setColor: ${value}, $this"
def events = []
def validValues = [:]
if (verifyPercent(value.hue)) {
events << createEvent(name: "hue", value: value.hue, displayed: false)
validValues.hue = value.hue
}
if (verifyPercent(value.saturation)) {
events << createEvent(name: "saturation", value: value.saturation, displayed: false)
validValues.saturation = value.saturation
}
if (value.hex != null) {
if (value.hex ==~ /^\#([A-Fa-f0-9]){6}$/) {
events << createEvent(name: "color", value: value.hex)
validValues.hex = value.hex
} else {
log.warn "$value.hex is not a valid color"
}
}
if (verifyPercent(value.level)) {
events << createEvent(name: "level", value: value.level, descriptionText: "Level has changed to ${value.level}%")
validValues.level = value.level
}
if (value.switch == "off" || (value.level != null && value.level <= 0)) {
events << createEvent(name: "switch", value: "off")
validValues.switch = "off"
} else {
events << createEvent(name: "switch", value: "on")
validValues.switch = "on"
}
if (!events.isEmpty()) {
parent.setColor(this, validValues)
}
events.each {
sendEvent(it)
}
}
void reset() {
log.debug "Executing 'reset'"
def value = [level:100, saturation:56, hue:23]
setAdjustedColor(value)
parent.poll()
}
void setAdjustedColor(value) {
if (value) {
log.trace "setAdjustedColor: ${value}"
def adjusted = value + [:]
adjusted.hue = adjustOutgoingHue(value.hue)
// Needed because color picker always sends 100
adjusted.level = null
setColor(adjusted)
} else {
log.warn "Invalid color input"
}
}
void setColorTemperature(value) {
if (value) {
log.trace "setColorTemperature: ${value}k"
parent.setColorTemperature(this, value)
sendEvent(name: "colorTemperature", value: value)
sendEvent(name: "switch", value: "on")
} else {
log.warn "Invalid color temperature"
}
}
void refresh() {
log.debug "Executing 'refresh'"
parent.manualRefresh()
}
def adjustOutgoingHue(percent) {
def adjusted = percent
if (percent > 31) {
if (percent < 63.0) {
adjusted = percent + (7 * (percent -30 ) / 32)
}
else if (percent < 73.0) {
adjusted = 69 + (5 * (percent - 62) / 10)
}
else {
adjusted = percent + (2 * (100 - percent) / 28)
}
}
log.info "percent: $percent, adjusted: $adjusted"
adjusted
}
def verifyPercent(percent) {
if (percent == null)
return false
else if (percent >= 0 && percent <= 100) {
return true
} else {
log.warn "$percent is not 0-100"
return false
}
}

View File

@@ -1,8 +1,6 @@
/** /**
* Hue Bulb * Hue Bulb
* *
* Philips Hue Type "Extended Color Light"
*
* Author: SmartThings * Author: SmartThings
*/ */
@@ -71,13 +69,11 @@ metadata {
def parse(description) { def parse(description) {
log.debug "parse() - $description" log.debug "parse() - $description"
def results = [] def results = []
def map = description def map = description
if (description instanceof String) { if (description instanceof String) {
log.debug "Hue Bulb stringToMap - ${map}" log.debug "Hue Bulb stringToMap - ${map}"
map = stringToMap(description) map = stringToMap(description)
} }
if (map?.name && map?.value) { if (map?.name && map?.value) {
results << createEvent(name: "${map?.name}", value: "${map?.value}") results << createEvent(name: "${map?.name}", value: "${map?.value}")
} }
@@ -233,4 +229,4 @@ def verifyPercent(percent) {
log.warn "$percent is not 0-100" log.warn "$percent is not 0-100"
return false return false
} }
} }

View File

@@ -1,8 +1,6 @@
/** /**
* Hue Lux Bulb * Hue Lux Bulb
* *
* Philips Hue Type "Dimmable Light"
*
* Author: SmartThings * Author: SmartThings
*/ */
// for the UI // for the UI
@@ -25,10 +23,10 @@ metadata {
tiles(scale: 2) { tiles(scale: 2) {
multiAttributeTile(name:"rich-control", type: "lighting", canChangeIcon: true){ multiAttributeTile(name:"rich-control", type: "lighting", canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") { tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#00A0DC", nextState:"turningOff" attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#C6C7CC", nextState:"turningOn" attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#00A0DC", nextState:"turningOff" attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#C6C7CC", nextState:"turningOn" attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
} }
tileAttribute ("device.level", key: "SLIDER_CONTROL") { tileAttribute ("device.level", key: "SLIDER_CONTROL") {
attributeState "level", action:"switch level.setLevel", range:"(0..100)" attributeState "level", action:"switch level.setLevel", range:"(0..100)"
@@ -70,12 +68,12 @@ def parse(description) {
// handle commands // handle commands
void on() { void on() {
log.trace parent.on(this) parent.on(this)
sendEvent(name: "switch", value: "on") sendEvent(name: "switch", value: "on")
} }
void off() { void off() {
log.trace parent.off(this) parent.off(this)
sendEvent(name: "switch", value: "off") sendEvent(name: "switch", value: "off")
} }
@@ -84,7 +82,6 @@ void setLevel(percent) {
if (percent != null && percent >= 0 && percent <= 100) { if (percent != null && percent >= 0 && percent <= 100) {
parent.setLevel(this, percent) parent.setLevel(this, percent)
sendEvent(name: "level", value: percent) sendEvent(name: "level", value: percent)
sendEvent(name: "switch", value: "on")
} else { } else {
log.warn "$percent is not 0-100" log.warn "$percent is not 0-100"
} }

View File

@@ -245,7 +245,6 @@ def retypeBasedOnMSR() {
break break
case "011F-0001-0001": // Schlage motion case "011F-0001-0001": // Schlage motion
case "014A-0001-0001": // Ecolink motion case "014A-0001-0001": // Ecolink motion
case "014A-0004-0001": // Ecolink motion +
case "0060-0001-0002": // Everspring SP814 case "0060-0001-0002": // Everspring SP814
case "0060-0001-0003": // Everspring HSP02 case "0060-0001-0003": // Everspring HSP02
case "011A-0601-0901": // Enerwave ZWN-BPC case "011A-0601-0901": // Enerwave ZWN-BPC

View File

@@ -87,13 +87,10 @@ def authPage(){
} }
def installed() { def installed() {
log.debug "Installed with settings: ${settings}"
initialize() initialize()
} }
def updated() { def updated() {
log.debug "Updated with settings: ${settings}"
unsubscribe()
initialize() initialize()
} }
@@ -106,72 +103,24 @@ def uninstalled() {
} }
def initialize() { def initialize() {
atomicState.attached_sensors = [:] unsubscribe()
if (plantlinksensors){ if (plantlinksensors){
subscribe(plantlinksensors, "moisture_status", moistureHandler)
subscribe(plantlinksensors, "battery_status", batteryHandler)
plantlinksensors.each{ sensor_device -> plantlinksensors.each{ sensor_device ->
sensor_device.setStatusIcon("Waiting on First Measurement") subscribe(sensor_device, "moisture_status", moistureHandler)
subscribe(sensor_device, "battery_status", batteryHandler)
sensor_device.setInstallSmartApp("connectedToSmartApp") sensor_device.setInstallSmartApp("connectedToSmartApp")
} }
} }
} }
def dock_sensor(device_serial, expected_plant_name) { def updatePlantNameIfNeeded(plant, expected_plant_name){
def docking_body_json_builder = new JsonBuilder([version: '1c', smartthings_device_id: device_serial])
def docking_params = [
uri : appSettings.https_plantLinkServer,
path : "/api/v1/smartthings/links",
headers : ["Content-Type": "application/json", "Authorization": "Bearer ${atomicState.authToken}"],
contentType: "application/json",
body: docking_body_json_builder.toString()
]
def plant_post_body_map = [
plant_type_key: 999999,
soil_type_key : 1000004
]
def plant_post_params = [
uri : appSettings.https_plantLinkServer,
path : "/api/v1/plants",
headers : ["Content-Type": "application/json", "Authorization": "Bearer ${atomicState.authToken}"],
contentType: "application/json",
]
log.debug "Creating new plant on myplantlink.com - ${expected_plant_name}"
httpPost(docking_params) { docking_response ->
if (parse_api_response(docking_response, "Docking a link")) {
if (docking_response.data.plants.size() == 0) {
log.debug "creating plant for - ${expected_plant_name}"
plant_post_body_map["name"] = expected_plant_name
plant_post_body_map['links_key'] = [docking_response.data.key]
def plant_post_body_json_builder = new JsonBuilder(plant_post_body_map)
plant_post_params["body"] = plant_post_body_json_builder.toString()
httpPost(plant_post_params) { plant_post_response ->
if(parse_api_response(plant_post_response, 'creating plant')){
def attached_map = atomicState.attached_sensors
attached_map[device_serial] = plant_post_response.data
atomicState.attached_sensors = attached_map
}
}
} else {
def plant = docking_response.data.plants[0]
def attached_map = atomicState.attached_sensors
attached_map[device_serial] = plant
atomicState.attached_sensors = attached_map
checkAndUpdatePlantIfNeeded(plant, expected_plant_name)
}
}
}
return true
}
def checkAndUpdatePlantIfNeeded(plant, expected_plant_name){
def plant_put_params = [ def plant_put_params = [
uri : appSettings.https_plantLinkServer, uri : appSettings.https_plantLinkServer,
headers : ["Content-Type": "application/json", "Authorization": "Bearer ${atomicState.authToken}"], headers : ["Content-Type": "application/json", "Authorization": "Bearer ${atomicState.authToken}"],
contentType : "application/json" contentType : "application/json"
] ]
if (plant.name != expected_plant_name) { if (plant.name != expected_plant_name) {
log.debug "updating plant for - ${expected_plant_name}" log.debug "renaming plant ${plant.key} - ${expected_plant_name}"
plant_put_params["path"] = "/api/v1/plants/${plant.key}" plant_put_params["path"] = "/api/v1/plants/${plant.key}"
def plant_put_body_map = [ def plant_put_body_map = [
name: expected_plant_name name: expected_plant_name
@@ -185,32 +134,39 @@ def checkAndUpdatePlantIfNeeded(plant, expected_plant_name){
} }
def moistureHandler(event){ def moistureHandler(event){
def expected_plant_name = "SmartThings - ${event.displayName}" log.debug "moistureHandler - ${event.value}"
def device_serial = getDeviceSerialFromEvent(event)
if (!atomicState.attached_sensors.containsKey(device_serial)){ def expected_plant_name = "${event.displayName} (ST)"
dock_sensor(device_serial, expected_plant_name) def device_serial = getDeviceSerialFromEvent(event)
def device_battery = atomicState["battery${device_serial}"]
if ( device_battery == null){
log.error "Missing Battery Voltage - next cycle should have it"
}else{ }else{
// {"type":"link","signal":"0x00","zigbeedeviceid":"0022A3000003D75A","created":1458843686,"moisture":"0x1987"}
def appendedEventWithBatteryInfo = event.value.replace('}',",\"battery\":\"${device_battery}\"}")
log.debug "payload - ${appendedEventWithBatteryInfo}"
def measurement_post_params = [ def measurement_post_params = [
uri: appSettings.https_plantLinkServer, uri: appSettings.https_plantLinkServer,
path: "/api/v1/smartthings/links/${device_serial}/measurements", path: "/api/v1/smartthings/links/${device_serial}/measurements",
headers: ["Content-Type": "application/json", "Authorization": "Bearer ${atomicState.authToken}"], headers: ["Content-Type": "application/json", "Authorization": "Bearer ${atomicState.authToken}"],
contentType: "application/json", contentType: "application/json",
body: event.value body: appendedEventWithBatteryInfo
] ]
httpPost(measurement_post_params) { measurement_post_response -> httpPost(measurement_post_params) { measurement_post_response ->
if (parse_api_response(measurement_post_response, 'creating moisture measurement') && if (parse_api_response(measurement_post_response, 'creating moisture measurement') && measurement_post_response.data.size() >0){
measurement_post_response.data.size() >0){
def measurement = measurement_post_response.data[0] def measurement = measurement_post_response.data[0]
def plant = measurement.plant def plant = measurement.plant
log.debug plant
checkAndUpdatePlantIfNeeded(plant, expected_plant_name) updatePlantNameIfNeeded(plant, expected_plant_name)
plantlinksensors.each{ sensor_device -> plantlinksensors.each{ sensor_device ->
if (sensor_device.id == event.deviceId){ if (sensor_device.id == event.deviceId){
sensor_device.setStatusIcon(plant.status) sensor_device.setStatusIcon(plant.status)
if (plant.last_measurements && plant.last_measurements[0].moisture){ if (plant.last_measurements && plant.last_measurements[0].moisture){
sensor_device.setPlantFuelLevel(plant.last_measurements[0].moisture * 100 as int) sensor_device.setPlantFuelLevel(plant.last_measurements[0].moisture * 100 as int)
} }
if (plant.last_measurements && plant.last_measurements[0].battery){ if (plant.last_measurements && plant.last_measurements[0].battery){
sensor_device.setBatteryLevel(plant.last_measurements[0].battery * 100 as int) sensor_device.setBatteryLevel(plant.last_measurements[0].battery * 100 as int)
} }
@@ -224,21 +180,7 @@ def moistureHandler(event){
def batteryHandler(event){ def batteryHandler(event){
def expected_plant_name = "SmartThings - ${event.displayName}" def expected_plant_name = "SmartThings - ${event.displayName}"
def device_serial = getDeviceSerialFromEvent(event) def device_serial = getDeviceSerialFromEvent(event)
atomicState["battery${device_serial}"] = getDeviceBatteryFromEvent(event)
if (!atomicState.attached_sensors.containsKey(device_serial)){
dock_sensor(device_serial, expected_plant_name)
}else{
def measurement_post_params = [
uri: appSettings.https_plantLinkServer,
path: "/api/v1/smartthings/links/${device_serial}/measurements",
headers: ["Content-Type": "application/json", "Authorization": "Bearer ${atomicState.authToken}"],
contentType: "application/json",
body: event.value
]
httpPost(measurement_post_params) { measurement_post_response ->
parse_api_response(measurement_post_response, 'creating battery measurement')
}
}
} }
def getDeviceSerialFromEvent(event){ def getDeviceSerialFromEvent(event){
@@ -247,6 +189,12 @@ def getDeviceSerialFromEvent(event){
return match_result[0][1] return match_result[0][1]
} }
def getDeviceBatteryFromEvent(event){
def pattern = /.*"battery"\s*:\s*"(\w+)".*/
def match_result = (event.value =~ pattern)
return match_result[0][1]
}
def oauthInitUrl(){ def oauthInitUrl(){
atomicState.oauthInitState = UUID.randomUUID().toString() atomicState.oauthInitState = UUID.randomUUID().toString()
def oauthParams = [ def oauthParams = [
@@ -263,12 +211,13 @@ def buildRedirectUrl(){
} }
def swapToken(){ def swapToken(){
log.debug "PlantLink Connector - OAuth Token"
def code = params.code def code = params.code
def oauthState = params.state def oauthState = params.state
def stcid = appSettings.client_id def stcid = appSettings.client_id
def postParams = [ def postParams = [
method: 'POST', method: 'POST',
uri: "https://oso-tech.appspot.com", uri: appSettings.https_plantLinkServer,
path: "/api/v1/oauth-token", path: "/api/v1/oauth-token",
query: [grant_type:'authorization_code', code:params.code, client_id:stcid, query: [grant_type:'authorization_code', code:params.code, client_id:stcid,
client_secret:appSettings.client_secret, redirect_uri: buildRedirectUrl()], client_secret:appSettings.client_secret, redirect_uri: buildRedirectUrl()],
@@ -321,10 +270,11 @@ def swapToken(){
} }
private refreshAuthToken() { private refreshAuthToken() {
log.debug "PlantLink Connector - Refresh OAuth"
def stcid = appSettings.client_id def stcid = appSettings.client_id
def refreshParams = [ def refreshParams = [
method: 'POST', method: 'POST',
uri: "https://hardware-dot-oso-tech.appspot.com", uri: appSettings.https_plantLinkServer,
path: "/api/v1/oauth-token", path: "/api/v1/oauth-token",
query: [grant_type:'refresh_token', code:"${atomicState.refreshToken}", client_id:stcid, query: [grant_type:'refresh_token', code:"${atomicState.refreshToken}", client_id:stcid,
client_secret:appSettings.client_secret], client_secret:appSettings.client_secret],
@@ -333,7 +283,6 @@ private refreshAuthToken() {
def jsonMap def jsonMap
httpPost(refreshParams) { resp -> httpPost(refreshParams) { resp ->
if(resp.status == 200){ if(resp.status == 200){
log.debug "OAuth Token refreshed"
jsonMap = resp.data jsonMap = resp.data
if (resp.data) { if (resp.data) {
atomicState.refreshToken = resp?.data?.refresh_token atomicState.refreshToken = resp?.data?.refresh_token
@@ -346,12 +295,12 @@ private refreshAuthToken() {
} }
data.action = "" data.action = ""
}else{ }else{
log.debug "refresh failed ${resp.status} : ${resp.status.code}" log.debug "PlantLink Server - ${resp.status} : ${resp.status.code}"
} }
} }
} }
catch(Exception e){ catch(Exception e){
log.debug "caught exception refreshing auth token: " + e log.debug "PlantLink Connector - OAuth Refresh Failed: " + e
} }
} }
@@ -364,7 +313,7 @@ def parse_api_response(resp, message) {
refreshAuthToken() refreshAuthToken()
return false return false
} else { } else {
debugEvent("Authentication error, invalid authentication method, lack of credentials, etc.", true) debugEvent("Plantlink Error: ${resp.status} - ${resp.status.code}", true)
return false return false
} }
} }

View File

@@ -289,7 +289,7 @@ def bulbListHandler(hub, data = "") {
def object = new groovy.json.JsonSlurper().parseText(data) def object = new groovy.json.JsonSlurper().parseText(data)
object.each { k,v -> object.each { k,v ->
if (v instanceof Map) if (v instanceof Map)
bulbs[k] = [id: k, name: v.name, type: v.type, modelid: v.modelid, hub:hub] bulbs[k] = [id: k, name: v.name, type: v.type, hub:hub]
} }
} }
def bridge = null def bridge = null
@@ -300,40 +300,6 @@ def bulbListHandler(hub, data = "") {
return msg return msg
} }
private upgradeDeviceType(device, newHueType) {
def deviceType = getDeviceType(newHueType)
// Automatically change users Hue bulbs to correct device types
if (deviceType && !(device?.typeName?.equalsIgnoreCase(deviceType))) {
log.debug "Update device type: \"$device.label\" ${device?.typeName}->$deviceType"
device.setDeviceType(deviceType)
}
}
private getDeviceType(hueType) {
// Determine ST device type based on Hue classification of light
if (hueType?.equalsIgnoreCase("Dimmable light"))
return "Hue Lux Bulb"
else if (hueType?.equalsIgnoreCase("Extended Color Light"))
return "Hue Bulb"
else if (hueType?.equalsIgnoreCase("Color Light"))
return "Hue Bloom"
else
return null
}
private addChildBulb(dni, hueType, name, hub, update=false, device = null) {
def deviceType = getDeviceType(hueType)
if (deviceType) {
return addChildDevice("smartthings", deviceType, dni, hub, ["label": name])
}
else {
log.warn "Device type $hueType not supported"
return null
}
}
def addBulbs() { def addBulbs() {
def bulbs = getHueBulbs() def bulbs = getHueBulbs()
selectedBulbs?.each { dni -> selectedBulbs?.each { dni ->
@@ -343,7 +309,11 @@ def addBulbs() {
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) if (newHueBulb?.value?.type?.equalsIgnoreCase("Dimmable light") ) {
d = addChildDevice("smartthings", "Hue Lux Bulb", dni, newHueBulb?.value.hub, ["label":newHueBulb?.value.name])
} else {
d = addChildDevice("smartthings", "Hue Bulb", dni, newHueBulb?.value.hub, ["label":newHueBulb?.value.name])
}
log.debug "created ${d.displayName} with id $dni" log.debug "created ${d.displayName} with id $dni"
d.refresh() d.refresh()
} else { } else {
@@ -352,15 +322,16 @@ def addBulbs() {
} 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 = addChildDevice("smartthings", "Hue Bulb", dni, newHueBulb?.hub, ["label":newHueBulb?.name])
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
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) if (newHueBulb?.value?.type?.equalsIgnoreCase("Dimmable light") && d.typeName == "Hue Bulb") {
d.setDeviceType("Hue Lux Bulb")
}
} }
} }
} }
@@ -502,7 +473,7 @@ def locationHandler(evt) {
def bulbs = getHueBulbs() def bulbs = getHueBulbs()
log.debug "Adding bulbs to state!" log.debug "Adding bulbs to state!"
body.each { k,v -> body.each { k,v ->
bulbs[k] = [id: k, name: v.name, type: v.type, modelid: v.modelid, hub:parsedEvent.hub] bulbs[k] = [id: k, name: v.name, type: v.type, hub:parsedEvent.hub]
} }
} }
} }
@@ -865,7 +836,7 @@ def convertBulbListToMap() {
if (state.bulbs instanceof java.util.List) { if (state.bulbs instanceof java.util.List) {
def map = [:] def map = [:]
state.bulbs.unique {it.id}.each { bulb -> state.bulbs.unique {it.id}.each { bulb ->
map << ["${bulb.id}":["id":bulb.id, "name":bulb.name, "type": bulb.type, "modelid": bulb.modelid, "hub":bulb.hub]] map << ["${bulb.id}":["id":bulb.id, "name":bulb.name, "hub":bulb.hub]]
} }
state.bulbs = map state.bulbs = map
} }