mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-18 05:10:52 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dcfc16cf12 | ||
|
|
1965f10584 | ||
|
|
9c9fba0939 | ||
|
|
725f9ebec7 | ||
|
|
e217805d98 | ||
|
|
ff39270ba4 | ||
|
|
9d016839c8 | ||
|
|
ecb975540b |
163
devicetypes/osotech/plantlink.src/plantlink.groovy
Normal file
163
devicetypes/osotech/plantlink.src/plantlink.groovy
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
/**
|
||||||
|
* PlantLink
|
||||||
|
*
|
||||||
|
* This device type takes sensor data and converts it into a json packet to send to myplantlink.com
|
||||||
|
* where its values will be computed for soil and plant type to show user readable values of how your
|
||||||
|
* specific plant is doing.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Copyright 2015 Oso Technologies
|
||||||
|
*
|
||||||
|
* 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 groovy.json.JsonBuilder
|
||||||
|
|
||||||
|
metadata {
|
||||||
|
definition (name: "PlantLink", namespace: "OsoTech", author: "Oso Technologies") {
|
||||||
|
capability "Sensor"
|
||||||
|
|
||||||
|
command "setStatusIcon"
|
||||||
|
command "setPlantFuelLevel"
|
||||||
|
command "setBatteryLevel"
|
||||||
|
command "setInstallSmartApp"
|
||||||
|
|
||||||
|
attribute "plantStatus","string"
|
||||||
|
attribute "plantFuelLevel","number"
|
||||||
|
attribute "linkBatteryLevel","string"
|
||||||
|
attribute "installSmartApp","string"
|
||||||
|
|
||||||
|
fingerprint profileId: "0104", inClusters: "0000,0001,0B04"
|
||||||
|
}
|
||||||
|
|
||||||
|
simulator {
|
||||||
|
status "battery": "read attr - raw: C0720100010A000021340A, dni: C072, endpoint: 01, cluster: 0001, size: 0A, attrId: 0000, encoding: 21, value: 0a34"
|
||||||
|
status "moisture": "read attr - raw: C072010B040A0001290000, dni: C072, endpoint: 01, cluster: 0B04, size: 0A, attrId: 0100, encoding: 29, value: 0000"
|
||||||
|
}
|
||||||
|
|
||||||
|
tiles {
|
||||||
|
standardTile("Title", "device.label") {
|
||||||
|
state("label", label:'PlantLink ${device.label}')
|
||||||
|
}
|
||||||
|
|
||||||
|
valueTile("plantMoistureTile", "device.plantFuelLevel", width: 1, height: 1) {
|
||||||
|
state("plantMoisture", label: '${currentValue}% Moisture')
|
||||||
|
}
|
||||||
|
|
||||||
|
valueTile("plantStatusTextTile", "device.plantStatus", decoration: "flat", width: 2, height: 2) {
|
||||||
|
state("plantStatusTextTile", label:'${currentValue}')
|
||||||
|
}
|
||||||
|
|
||||||
|
valueTile("battery", "device.linkBatteryLevel" ) {
|
||||||
|
state("battery", label:'${currentValue}% battery')
|
||||||
|
}
|
||||||
|
|
||||||
|
valueTile("installSmartApp","device.installSmartApp", decoration: "flat", width: 3, height: 1) {
|
||||||
|
state "needSmartApp", label:'Please install SmartApp "Required PlantLink Connector"', defaultState:true
|
||||||
|
state "connectedToSmartApp", label:'Connected to myplantlink.com'
|
||||||
|
}
|
||||||
|
|
||||||
|
main "plantStatusTextTile"
|
||||||
|
details(['plantStatusTextTile', "plantMoistureTile", "battery", "installSmartApp"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def setStatusIcon(value){
|
||||||
|
def status = ''
|
||||||
|
switch (value) {
|
||||||
|
case '0':
|
||||||
|
status = 'Needs Water'
|
||||||
|
break
|
||||||
|
case '1':
|
||||||
|
status = 'Dry'
|
||||||
|
break
|
||||||
|
case '2':
|
||||||
|
case '3':
|
||||||
|
status = 'Good'
|
||||||
|
break
|
||||||
|
case '4':
|
||||||
|
status = 'Too Wet'
|
||||||
|
break
|
||||||
|
case 'No Soil':
|
||||||
|
status = 'Too Dry'
|
||||||
|
setPlantFuelLevel(0)
|
||||||
|
break
|
||||||
|
case 'Recently Watered':
|
||||||
|
status = 'Watered'
|
||||||
|
setPlantFuelLevel(100)
|
||||||
|
break
|
||||||
|
case 'Low Battery':
|
||||||
|
status = 'Low Battery'
|
||||||
|
break
|
||||||
|
case 'Waiting on First Measurement':
|
||||||
|
status = 'Calibrating'
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
status = "?"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
sendEvent("name":"plantStatus", "value":status, "description":statusText, displayed: true, isStateChange: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
def setPlantFuelLevel(value){
|
||||||
|
sendEvent("name":"plantFuelLevel", "value":value, "description":statusText, displayed: true, isStateChange: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
def setBatteryLevel(value){
|
||||||
|
sendEvent("name":"linkBatteryLevel", "value":value, "description":statusText, displayed: true, isStateChange: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
def setInstallSmartApp(value){
|
||||||
|
sendEvent("name":"installSmartApp", "value":value)
|
||||||
|
}
|
||||||
|
|
||||||
|
def parse(String description) {
|
||||||
|
|
||||||
|
def description_map = parseDescriptionAsMap(description)
|
||||||
|
def event_name = ""
|
||||||
|
def measurement_map = [
|
||||||
|
type: "link",
|
||||||
|
signal: "0x00",
|
||||||
|
zigbeedeviceid: device.zigbeeId,
|
||||||
|
created: new Date().time /1000 as int
|
||||||
|
]
|
||||||
|
if (description_map.cluster == "0000"){
|
||||||
|
/* version number, not used */
|
||||||
|
|
||||||
|
} else if (description_map.cluster == "0001"){
|
||||||
|
/* battery voltage in mV (device needs minimium 2.1v to run) */
|
||||||
|
log.debug "PlantLink - id ${device.zigbeeId} battery ${description_map.value}"
|
||||||
|
event_name = "battery_status"
|
||||||
|
measurement_map["battery"] = "0x${description_map.value}"
|
||||||
|
|
||||||
|
} else if (description_map.cluster == "0B04"){
|
||||||
|
/* raw moisture reading (needs to be sent to plantlink for soil/plant type conversion) */
|
||||||
|
log.debug "PlantLink - id ${device.zigbeeId} raw moisture ${description_map.value}"
|
||||||
|
measurement_map["moisture"] = "0x${description_map.value}"
|
||||||
|
event_name = "moisture_status"
|
||||||
|
|
||||||
|
} else{
|
||||||
|
log.debug "PlantLink - id ${device.zigbeeId} Unknown '${description}'"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
def json_builder = new JsonBuilder(measurement_map)
|
||||||
|
def result = createEvent(name: event_name, value: json_builder.toString())
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def parseDescriptionAsMap(description) {
|
||||||
|
(description - "read attr - ").split(",").inject([:]) { map, param ->
|
||||||
|
def nameAndValue = param.split(":")
|
||||||
|
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -55,141 +55,136 @@ metadata {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
standardTile("indicator", "device.indicatorStatus", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
|
standardTile("indicator", "device.indicatorStatus", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
||||||
state "when off", action:"indicator.indicatorWhenOn", icon:"st.indicators.lit-when-off"
|
state "when off", action:"indicator.indicatorWhenOn", icon:"st.indicators.lit-when-off"
|
||||||
state "when on", action:"indicator.indicatorNever", icon:"st.indicators.lit-when-on"
|
state "when on", action:"indicator.indicatorNever", icon:"st.indicators.lit-when-on"
|
||||||
state "never", action:"indicator.indicatorWhenOff", icon:"st.indicators.never-lit"
|
state "never", action:"indicator.indicatorWhenOff", icon:"st.indicators.never-lit"
|
||||||
}
|
}
|
||||||
standardTile("refresh", "device.switch", height: 2, width: 2, inactiveLabel: false, decoration: "flat") {
|
|
||||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
standardTile("refresh", "device.switch", width: 2, height: 2, inactiveLabel: false, decoration: "flat") {
|
||||||
|
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||||
|
}
|
||||||
|
|
||||||
|
valueTile("level", "device.level", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||||
|
state "level", label:'${currentValue} %', unit:"%", backgroundColor:"#ffffff"
|
||||||
}
|
}
|
||||||
|
|
||||||
main(["switch"])
|
main(["switch"])
|
||||||
details(["switch", "refresh", "indicator"])
|
details(["switch", "level", "indicator", "refresh"])
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
def item1 = [
|
def result = null
|
||||||
canBeCurrentState: false,
|
if (description != "updated") {
|
||||||
linkText: getLinkText(device),
|
log.debug "parse() >> zwave.parse($description)"
|
||||||
isStateChange: false,
|
def cmd = zwave.parse(description, [0x20: 1, 0x26: 1, 0x70: 1])
|
||||||
displayed: false,
|
if (cmd) {
|
||||||
descriptionText: description,
|
result = zwaveEvent(cmd)
|
||||||
value: description
|
}
|
||||||
]
|
|
||||||
def result
|
|
||||||
def cmd = zwave.parse(description, [0x20: 1, 0x26: 1, 0x70: 1])
|
|
||||||
if (cmd) {
|
|
||||||
result = createEvent(cmd, item1)
|
|
||||||
}
|
}
|
||||||
else {
|
if (result?.name == 'hail' && hubFirmwareLessThan("000.011.00602")) {
|
||||||
item1.displayed = displayed(description, item1.isStateChange)
|
result = [result, response(zwave.basicV1.basicGet())]
|
||||||
result = [item1]
|
log.debug "Was hailed: requesting state update"
|
||||||
|
} else {
|
||||||
|
log.debug "Parse returned ${result?.descriptionText}"
|
||||||
}
|
}
|
||||||
log.debug "Parse returned ${result?.descriptionText}"
|
return result
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def createEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd, Map item1) {
|
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicReport cmd) {
|
||||||
def result = doCreateEvent(cmd, item1)
|
dimmerEvents(cmd)
|
||||||
for (int i = 0; i < result.size(); i++) {
|
}
|
||||||
result[i].type = "physical"
|
|
||||||
|
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd) {
|
||||||
|
dimmerEvents(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
def zwaveEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelReport cmd) {
|
||||||
|
dimmerEvents(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
def zwaveEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelSet cmd) {
|
||||||
|
dimmerEvents(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
private dimmerEvents(physicalgraph.zwave.Command cmd) {
|
||||||
|
def value = (cmd.value ? "on" : "off")
|
||||||
|
def result = [createEvent(name: "switch", value: value)]
|
||||||
|
if (cmd.value && cmd.value <= 100) {
|
||||||
|
result << createEvent(name: "level", value: cmd.value, unit: "%")
|
||||||
}
|
}
|
||||||
result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
def createEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd, Map item1) {
|
|
||||||
def result = doCreateEvent(cmd, item1)
|
|
||||||
for (int i = 0; i < result.size(); i++) {
|
|
||||||
result[i].type = "physical"
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
def createEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelStartLevelChange cmd, Map item1) {
|
|
||||||
[]
|
|
||||||
}
|
|
||||||
|
|
||||||
def createEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelStopLevelChange cmd, Map item1) {
|
|
||||||
[response(zwave.basicV1.basicGet())]
|
|
||||||
}
|
|
||||||
|
|
||||||
def createEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelSet cmd, Map item1) {
|
|
||||||
def result = doCreateEvent(cmd, item1)
|
|
||||||
for (int i = 0; i < result.size(); i++) {
|
|
||||||
result[i].type = "physical"
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
def createEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelReport cmd, Map item1) {
|
|
||||||
def result = doCreateEvent(cmd, item1)
|
|
||||||
result[0].descriptionText = "${item1.linkText} is ${item1.value}"
|
|
||||||
result[0].handlerName = cmd.value ? "statusOn" : "statusOff"
|
|
||||||
for (int i = 0; i < result.size(); i++) {
|
|
||||||
result[i].type = "digital"
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
def doCreateEvent(physicalgraph.zwave.Command cmd, Map item1) {
|
|
||||||
def result = [item1]
|
|
||||||
|
|
||||||
item1.name = "switch"
|
|
||||||
item1.value = cmd.value ? "on" : "off"
|
|
||||||
item1.handlerName = item1.value
|
|
||||||
item1.descriptionText = "${item1.linkText} was turned ${item1.value}"
|
|
||||||
item1.canBeCurrentState = true
|
|
||||||
item1.isStateChange = isStateChange(device, item1.name, item1.value)
|
|
||||||
item1.displayed = item1.isStateChange
|
|
||||||
|
|
||||||
if (cmd.value >= 5) {
|
|
||||||
def item2 = new LinkedHashMap(item1)
|
|
||||||
item2.name = "level"
|
|
||||||
item2.value = cmd.value as String
|
|
||||||
item2.unit = "%"
|
|
||||||
item2.descriptionText = "${item1.linkText} dimmed ${item2.value} %"
|
|
||||||
item2.canBeCurrentState = true
|
|
||||||
item2.isStateChange = isStateChange(device, item2.name, item2.value)
|
|
||||||
item2.displayed = false
|
|
||||||
result << item2
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.configurationv1.ConfigurationReport cmd) {
|
def zwaveEvent(physicalgraph.zwave.commands.configurationv1.ConfigurationReport cmd) {
|
||||||
|
log.debug "ConfigurationReport $cmd"
|
||||||
def value = "when off"
|
def value = "when off"
|
||||||
if (cmd.configurationValue[0] == 1) {value = "when on"}
|
if (cmd.configurationValue[0] == 1) {value = "when on"}
|
||||||
if (cmd.configurationValue[0] == 2) {value = "never"}
|
if (cmd.configurationValue[0] == 2) {value = "never"}
|
||||||
[name: "indicatorStatus", value: value, display: false]
|
createEvent([name: "indicatorStatus", value: value])
|
||||||
}
|
}
|
||||||
|
|
||||||
def createEvent(physicalgraph.zwave.Command cmd, Map map) {
|
def zwaveEvent(physicalgraph.zwave.commands.hailv1.Hail cmd) {
|
||||||
// Handles any Z-Wave commands we aren't interested in
|
createEvent([name: "hail", value: "hail", descriptionText: "Switch button was pressed", displayed: false])
|
||||||
log.debug "UNHANDLED COMMAND $cmd"
|
}
|
||||||
|
|
||||||
|
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
|
||||||
|
log.debug "manufacturerId: ${cmd.manufacturerId}"
|
||||||
|
log.debug "manufacturerName: ${cmd.manufacturerName}"
|
||||||
|
log.debug "productId: ${cmd.productId}"
|
||||||
|
log.debug "productTypeId: ${cmd.productTypeId}"
|
||||||
|
def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId)
|
||||||
|
updateDataValue("MSR", msr)
|
||||||
|
createEvent([descriptionText: "$device.displayName MSR: $msr", isStateChange: false])
|
||||||
|
}
|
||||||
|
|
||||||
|
def zwaveEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelStopLevelChange cmd) {
|
||||||
|
[createEvent(name:"switch", value:"on"), response(zwave.switchMultilevelV1.switchMultilevelGet().format())]
|
||||||
|
}
|
||||||
|
|
||||||
|
def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
||||||
|
// Handles all Z-Wave commands we aren't interested in
|
||||||
|
[:]
|
||||||
}
|
}
|
||||||
|
|
||||||
def on() {
|
def on() {
|
||||||
log.info "on"
|
delayBetween([
|
||||||
delayBetween([zwave.basicV1.basicSet(value: 0xFF).format(), zwave.switchMultilevelV1.switchMultilevelGet().format()], 5000)
|
zwave.basicV1.basicSet(value: 0xFF).format(),
|
||||||
|
zwave.switchMultilevelV1.switchMultilevelGet().format()
|
||||||
|
],5000)
|
||||||
}
|
}
|
||||||
|
|
||||||
def off() {
|
def off() {
|
||||||
delayBetween ([zwave.basicV1.basicSet(value: 0x00).format(), zwave.switchMultilevelV1.switchMultilevelGet().format()], 5000)
|
delayBetween([
|
||||||
|
zwave.basicV1.basicSet(value: 0x00).format(),
|
||||||
|
zwave.switchMultilevelV1.switchMultilevelGet().format()
|
||||||
|
],5000)
|
||||||
}
|
}
|
||||||
|
|
||||||
def setLevel(value) {
|
def setLevel(value) {
|
||||||
|
log.debug "setLevel >> value: $value"
|
||||||
def valueaux = value as Integer
|
def valueaux = value as Integer
|
||||||
def level = Math.min(valueaux, 99)
|
def level = Math.max(Math.min(valueaux, 99), 0)
|
||||||
|
if (level > 0) {
|
||||||
|
sendEvent(name: "switch", value: "on")
|
||||||
|
} else {
|
||||||
|
sendEvent(name: "switch", value: "off")
|
||||||
|
}
|
||||||
|
sendEvent(name: "level", value: level, unit: "%")
|
||||||
delayBetween ([zwave.basicV1.basicSet(value: level).format(), zwave.switchMultilevelV1.switchMultilevelGet().format()], 5000)
|
delayBetween ([zwave.basicV1.basicSet(value: level).format(), zwave.switchMultilevelV1.switchMultilevelGet().format()], 5000)
|
||||||
}
|
}
|
||||||
|
|
||||||
def setLevel(value, duration) {
|
def setLevel(value, duration) {
|
||||||
|
log.debug "setLevel >> value: $value, duration: $duration"
|
||||||
def valueaux = value as Integer
|
def valueaux = value as Integer
|
||||||
def level = Math.min(valueaux, 99)
|
def level = Math.max(Math.min(valueaux, 99), 0)
|
||||||
def dimmingDuration = duration < 128 ? duration : 128 + Math.round(duration / 60)
|
def dimmingDuration = duration < 128 ? duration : 128 + Math.round(duration / 60)
|
||||||
zwave.switchMultilevelV2.switchMultilevelSet(value: level, dimmingDuration: dimmingDuration).format()
|
def getStatusDelay = duration < 128 ? (duration*1000)+2000 : (Math.round(duration / 60)*60*1000)+2000
|
||||||
|
delayBetween ([zwave.switchMultilevelV2.switchMultilevelSet(value: level, dimmingDuration: dimmingDuration).format(),
|
||||||
|
zwave.switchMultilevelV1.switchMultilevelGet().format()], getStatusDelay)
|
||||||
}
|
}
|
||||||
|
|
||||||
def poll() {
|
def poll() {
|
||||||
@@ -197,21 +192,27 @@ def poll() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def refresh() {
|
def refresh() {
|
||||||
zwave.switchMultilevelV1.switchMultilevelGet().format()
|
log.debug "refresh() is called"
|
||||||
|
def commands = []
|
||||||
|
commands << zwave.switchMultilevelV1.switchMultilevelGet().format()
|
||||||
|
if (getDataValue("MSR") == null) {
|
||||||
|
commands << zwave.manufacturerSpecificV1.manufacturerSpecificGet().format()
|
||||||
|
}
|
||||||
|
delayBetween(commands,100)
|
||||||
}
|
}
|
||||||
|
|
||||||
def indicatorWhenOn() {
|
def indicatorWhenOn() {
|
||||||
sendEvent(name: "indicatorStatus", value: "when on", display: false)
|
sendEvent(name: "indicatorStatus", value: "when on")
|
||||||
zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 3, size: 1).format()
|
zwave.configurationV1.configurationSet(configurationValue: [1], parameterNumber: 3, size: 1).format()
|
||||||
}
|
}
|
||||||
|
|
||||||
def indicatorWhenOff() {
|
def indicatorWhenOff() {
|
||||||
sendEvent(name: "indicatorStatus", value: "when off", display: false)
|
sendEvent(name: "indicatorStatus", value: "when off")
|
||||||
zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1).format()
|
zwave.configurationV1.configurationSet(configurationValue: [0], parameterNumber: 3, size: 1).format()
|
||||||
}
|
}
|
||||||
|
|
||||||
def indicatorNever() {
|
def indicatorNever() {
|
||||||
sendEvent(name: "indicatorStatus", value: "never", display: false)
|
sendEvent(name: "indicatorStatus", value: "never")
|
||||||
zwave.configurationV1.configurationSet(configurationValue: [2], parameterNumber: 3, size: 1).format()
|
zwave.configurationV1.configurationSet(configurationValue: [2], parameterNumber: 3, size: 1).format()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2015 SmartThings
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* ZigBee White Color Temperature Bulb
|
||||||
|
*
|
||||||
|
* Author: SmartThings
|
||||||
|
* Date: 2015-09-22
|
||||||
|
*/
|
||||||
|
|
||||||
|
metadata {
|
||||||
|
definition (name: "ZigBee White Color Temperature Bulb", namespace: "smartthings", author: "SmartThings") {
|
||||||
|
|
||||||
|
capability "Actuator"
|
||||||
|
capability "Color Temperature"
|
||||||
|
capability "Configuration"
|
||||||
|
capability "Refresh"
|
||||||
|
capability "Sensor"
|
||||||
|
capability "Switch"
|
||||||
|
capability "Switch Level"
|
||||||
|
|
||||||
|
attribute "colorName", "string"
|
||||||
|
command "setGenericName"
|
||||||
|
|
||||||
|
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0008,0300,0B04", outClusters: "0019"
|
||||||
|
}
|
||||||
|
|
||||||
|
// UI tile definitions
|
||||||
|
tiles(scale: 2) {
|
||||||
|
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
|
||||||
|
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||||
|
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||||
|
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||||
|
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.light.on", backgroundColor:"#79b821", nextState:"turningOff"
|
||||||
|
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.light.off", backgroundColor:"#ffffff", nextState:"turningOn"
|
||||||
|
}
|
||||||
|
tileAttribute ("device.level", key: "SLIDER_CONTROL") {
|
||||||
|
attributeState "level", action:"switch level.setLevel"
|
||||||
|
}
|
||||||
|
tileAttribute ("colorName", key: "SECONDARY_CONTROL") {
|
||||||
|
attributeState "colorName", label:'${currentValue}'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||||
|
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||||
|
}
|
||||||
|
|
||||||
|
controlTile("colorTempSliderControl", "device.colorTemperature", "slider", width: 4, height: 2, inactiveLabel: false, range:"(2700..6500)") {
|
||||||
|
state "colorTemperature", action:"color temperature.setColorTemperature"
|
||||||
|
}
|
||||||
|
valueTile("colorTemp", "device.colorTemperature", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||||
|
state "colorTemperature", label: '${currentValue} K'
|
||||||
|
}
|
||||||
|
|
||||||
|
main(["switch"])
|
||||||
|
details(["switch", "colorTempSliderControl", "colorTemp", "refresh"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse incoming device messages to generate events
|
||||||
|
def parse(String description) {
|
||||||
|
log.debug "description is $description"
|
||||||
|
|
||||||
|
def finalResult = zigbee.getKnownDescription(description)
|
||||||
|
if (finalResult) {
|
||||||
|
log.info finalResult
|
||||||
|
if (finalResult.type == "update") {
|
||||||
|
log.info "$device updates: ${finalResult.value}"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sendEvent(name: finalResult.type, value: finalResult.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
||||||
|
log.debug zigbee.parseDescriptionAsMap(description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def off() {
|
||||||
|
zigbee.off()
|
||||||
|
}
|
||||||
|
|
||||||
|
def on() {
|
||||||
|
zigbee.on()
|
||||||
|
}
|
||||||
|
|
||||||
|
def setLevel(value) {
|
||||||
|
zigbee.setLevel(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
def refresh() {
|
||||||
|
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh() + zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
def configure() {
|
||||||
|
log.debug "Configuring Reporting and Bindings."
|
||||||
|
zigbee.onOffConfig() + zigbee.levelConfig() + zigbee.colorTemperatureConfig() + zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.colorTemperatureRefresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
def setColorTemperature(value) {
|
||||||
|
setGenericName(value)
|
||||||
|
zigbee.setColorTemperature(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Naming based on the wiki article here: http://en.wikipedia.org/wiki/Color_temperature
|
||||||
|
def setGenericName(value){
|
||||||
|
if (value != null) {
|
||||||
|
def genericName = "White"
|
||||||
|
if (value < 3300) {
|
||||||
|
genericName = "Soft White"
|
||||||
|
} else if (value < 4150) {
|
||||||
|
genericName = "Moonlight"
|
||||||
|
} else if (value <= 5000) {
|
||||||
|
genericName = "Cool White"
|
||||||
|
} else if (value >= 5000) {
|
||||||
|
genericName = "Daylight"
|
||||||
|
}
|
||||||
|
sendEvent(name: "colorName", value: genericName)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
/**
|
|
||||||
* Switch Too
|
|
||||||
*
|
|
||||||
* Copyright 2015 Bob Florian
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
metadata {
|
|
||||||
definition (name: "Switch Too", author: "Bob Florian") {
|
|
||||||
capability "Switch"
|
|
||||||
}
|
|
||||||
|
|
||||||
simulator {
|
|
||||||
// TODO: define status and reply messages here
|
|
||||||
}
|
|
||||||
|
|
||||||
tiles {
|
|
||||||
// TODO: define your main and details tiles here
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse events into attributes
|
|
||||||
def parse(String description) {
|
|
||||||
log.debug "Parsing '${description}'"
|
|
||||||
// TODO: handle 'switch' attribute
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle commands
|
|
||||||
def on() {
|
|
||||||
log.debug "Executing 'on'"
|
|
||||||
// TODO: handle 'on' command
|
|
||||||
}
|
|
||||||
|
|
||||||
def off() {
|
|
||||||
log.debug "Executing 'off'"
|
|
||||||
// TODO: handle 'off' command
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,389 @@
|
|||||||
|
/**
|
||||||
|
* Required PlantLink Connector
|
||||||
|
* This SmartApp forwards the raw data of the deviceType to myplantlink.com
|
||||||
|
* and returns it back to your device after calculating soil and plant type.
|
||||||
|
*
|
||||||
|
* Copyright 2015 Oso Technologies
|
||||||
|
*
|
||||||
|
* 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 groovy.json.JsonBuilder
|
||||||
|
import java.util.regex.Matcher
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
|
definition(
|
||||||
|
name: "PlantLink Connector",
|
||||||
|
namespace: "Osotech",
|
||||||
|
author: "Oso Technologies",
|
||||||
|
description: "This SmartApp connects to myplantlink.com and forwards the device data to it so it can calculate easy to read plant status for your specific plant's needs.",
|
||||||
|
category: "Convenience",
|
||||||
|
iconUrl: "https://dashboard.myplantlink.com/images/apple-touch-icon-76x76-precomposed.png",
|
||||||
|
iconX2Url: "https://dashboard.myplantlink.com/images/apple-touch-icon-120x120-precomposed.png",
|
||||||
|
iconX3Url: "https://dashboard.myplantlink.com/images/apple-touch-icon-152x152-precomposed.png"
|
||||||
|
) {
|
||||||
|
appSetting "client_id"
|
||||||
|
appSetting "client_secret"
|
||||||
|
appSetting "https_plantLinkServer"
|
||||||
|
}
|
||||||
|
|
||||||
|
preferences {
|
||||||
|
page(name: "auth", title: "Step 1 of 2", nextPage:"deviceList", content:"authPage")
|
||||||
|
page(name: "deviceList", title: "Step 2 of 2", install:true, uninstall:false){
|
||||||
|
section {
|
||||||
|
input "plantlinksensors", "capability.sensor", title: "Select PlantLink sensors", multiple: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mappings {
|
||||||
|
path("/swapToken") {
|
||||||
|
action: [
|
||||||
|
GET: "swapToken"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def authPage(){
|
||||||
|
if(!atomicState.accessToken){
|
||||||
|
createAccessToken()
|
||||||
|
atomicState.accessToken = state.accessToken
|
||||||
|
}
|
||||||
|
|
||||||
|
def redirectUrl = oauthInitUrl()
|
||||||
|
def uninstallAllowed = false
|
||||||
|
def oauthTokenProvided = false
|
||||||
|
if(atomicState.authToken){
|
||||||
|
uninstallAllowed = true
|
||||||
|
oauthTokenProvided = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!oauthTokenProvided) {
|
||||||
|
return dynamicPage(name: "auth", title: "Step 1 of 2", nextPage:null, uninstall:uninstallAllowed) {
|
||||||
|
section(){
|
||||||
|
href(name:"login",
|
||||||
|
url:redirectUrl,
|
||||||
|
style:"embedded",
|
||||||
|
title:"PlantLink",
|
||||||
|
image:"https://dashboard.myplantlink.com/images/PLlogo.png",
|
||||||
|
description:"Tap to login to myplantlink.com")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
return dynamicPage(name: "auth", title: "Step 1 of 2 - Completed", nextPage:"deviceList", uninstall:uninstallAllowed) {
|
||||||
|
section(){
|
||||||
|
paragraph "You are logged in to myplantlink.com, tap next to continue", image: iconUrl
|
||||||
|
href(url:redirectUrl, title:"Or", description:"tap to switch accounts")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def installed() {
|
||||||
|
log.debug "Installed with settings: ${settings}"
|
||||||
|
initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
def updated() {
|
||||||
|
log.debug "Updated with settings: ${settings}"
|
||||||
|
unsubscribe()
|
||||||
|
initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
def uninstalled() {
|
||||||
|
if (plantlinksensors){
|
||||||
|
plantlinksensors.each{ sensor_device ->
|
||||||
|
sensor_device.setInstallSmartApp("needSmartApp")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def initialize() {
|
||||||
|
atomicState.attached_sensors = [:]
|
||||||
|
if (plantlinksensors){
|
||||||
|
subscribe(plantlinksensors, "moisture_status", moistureHandler)
|
||||||
|
subscribe(plantlinksensors, "battery_status", batteryHandler)
|
||||||
|
plantlinksensors.each{ sensor_device ->
|
||||||
|
sensor_device.setStatusIcon("Waiting on First Measurement")
|
||||||
|
sensor_device.setInstallSmartApp("connectedToSmartApp")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def dock_sensor(device_serial, 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 = [
|
||||||
|
uri : appSettings.https_plantLinkServer,
|
||||||
|
headers : ["Content-Type": "application/json", "Authorization": "Bearer ${atomicState.authToken}"],
|
||||||
|
contentType : "application/json"
|
||||||
|
]
|
||||||
|
if (plant.name != expected_plant_name) {
|
||||||
|
log.debug "updating plant for - ${expected_plant_name}"
|
||||||
|
plant_put_params["path"] = "/api/v1/plants/${plant.key}"
|
||||||
|
def plant_put_body_map = [
|
||||||
|
name: expected_plant_name
|
||||||
|
]
|
||||||
|
def plant_put_body_json_builder = new JsonBuilder(plant_put_body_map)
|
||||||
|
plant_put_params["body"] = plant_put_body_json_builder.toString()
|
||||||
|
httpPut(plant_put_params) { plant_put_response ->
|
||||||
|
parse_api_response(plant_put_response, 'updating plant name')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def moistureHandler(event){
|
||||||
|
def expected_plant_name = "SmartThings - ${event.displayName}"
|
||||||
|
def device_serial = getDeviceSerialFromEvent(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 ->
|
||||||
|
if (parse_api_response(measurement_post_response, 'creating moisture measurement') &&
|
||||||
|
measurement_post_response.data.size() >0){
|
||||||
|
def measurement = measurement_post_response.data[0]
|
||||||
|
def plant = measurement.plant
|
||||||
|
log.debug plant
|
||||||
|
checkAndUpdatePlantIfNeeded(plant, expected_plant_name)
|
||||||
|
plantlinksensors.each{ sensor_device ->
|
||||||
|
if (sensor_device.id == event.deviceId){
|
||||||
|
sensor_device.setStatusIcon(plant.status)
|
||||||
|
if (plant.last_measurements && plant.last_measurements[0].moisture){
|
||||||
|
sensor_device.setPlantFuelLevel(plant.last_measurements[0].moisture * 100 as int)
|
||||||
|
}
|
||||||
|
if (plant.last_measurements && plant.last_measurements[0].battery){
|
||||||
|
sensor_device.setBatteryLevel(plant.last_measurements[0].battery * 100 as int)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def batteryHandler(event){
|
||||||
|
def expected_plant_name = "SmartThings - ${event.displayName}"
|
||||||
|
def device_serial = getDeviceSerialFromEvent(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 pattern = /.*"zigbeedeviceid"\s*:\s*"(\w+)".*/
|
||||||
|
def match_result = (event.value =~ pattern)
|
||||||
|
return match_result[0][1]
|
||||||
|
}
|
||||||
|
|
||||||
|
def oauthInitUrl(){
|
||||||
|
atomicState.oauthInitState = UUID.randomUUID().toString()
|
||||||
|
def oauthParams = [
|
||||||
|
response_type: "code",
|
||||||
|
client_id: appSettings.client_id,
|
||||||
|
state: atomicState.oauthInitState,
|
||||||
|
redirect_uri: buildRedirectUrl()
|
||||||
|
]
|
||||||
|
return appSettings.https_plantLinkServer + "/oauth/oauth2/authorize?" + toQueryString(oauthParams)
|
||||||
|
}
|
||||||
|
|
||||||
|
def buildRedirectUrl(){
|
||||||
|
return getServerUrl() + "/api/token/${atomicState.accessToken}/smartapps/installations/${app.id}/swapToken"
|
||||||
|
}
|
||||||
|
|
||||||
|
def swapToken(){
|
||||||
|
def code = params.code
|
||||||
|
def oauthState = params.state
|
||||||
|
def stcid = appSettings.client_id
|
||||||
|
def postParams = [
|
||||||
|
method: 'POST',
|
||||||
|
uri: "https://oso-tech.appspot.com",
|
||||||
|
path: "/api/v1/oauth-token",
|
||||||
|
query: [grant_type:'authorization_code', code:params.code, client_id:stcid,
|
||||||
|
client_secret:appSettings.client_secret, redirect_uri: buildRedirectUrl()],
|
||||||
|
]
|
||||||
|
|
||||||
|
def jsonMap
|
||||||
|
httpPost(postParams) { resp ->
|
||||||
|
jsonMap = resp.data
|
||||||
|
}
|
||||||
|
|
||||||
|
atomicState.refreshToken = jsonMap.refresh_token
|
||||||
|
atomicState.authToken = jsonMap.access_token
|
||||||
|
|
||||||
|
def html = """
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
padding:25px;
|
||||||
|
}
|
||||||
|
.flex1 {
|
||||||
|
width:33%;
|
||||||
|
float:left;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
font-size: 2em;
|
||||||
|
font-family: Verdana, Geneva, sans-serif;
|
||||||
|
text-align: center;
|
||||||
|
color: #777;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="flex1"><img src="https://dashboard.myplantlink.com/images/PLlogo.png" alt="PlantLink" height="75"/></div>
|
||||||
|
<div class="flex1"><img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/connected-device-icn%402x.png" alt="connected to" height="25" style="padding-top:25px;" /></div>
|
||||||
|
<div class="flex1"><img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/st-logo%402x.png" alt="SmartThings" height="75"/></div>
|
||||||
|
<br clear="all">
|
||||||
|
</div>
|
||||||
|
<div class="container">
|
||||||
|
<p>Your PlantLink Account is now connected to SmartThings!</p>
|
||||||
|
<p style="color:green;">Click <strong>Done</strong> at the top right to finish setup.</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
render contentType: 'text/html', data: html
|
||||||
|
}
|
||||||
|
|
||||||
|
private refreshAuthToken() {
|
||||||
|
def stcid = appSettings.client_id
|
||||||
|
def refreshParams = [
|
||||||
|
method: 'POST',
|
||||||
|
uri: "https://hardware-dot-oso-tech.appspot.com",
|
||||||
|
path: "/api/v1/oauth-token",
|
||||||
|
query: [grant_type:'refresh_token', code:"${atomicState.refreshToken}", client_id:stcid,
|
||||||
|
client_secret:appSettings.client_secret],
|
||||||
|
]
|
||||||
|
try{
|
||||||
|
def jsonMap
|
||||||
|
httpPost(refreshParams) { resp ->
|
||||||
|
if(resp.status == 200){
|
||||||
|
log.debug "OAuth Token refreshed"
|
||||||
|
jsonMap = resp.data
|
||||||
|
if (resp.data) {
|
||||||
|
atomicState.refreshToken = resp?.data?.refresh_token
|
||||||
|
atomicState.authToken = resp?.data?.access_token
|
||||||
|
if (data?.action && data?.action != "") {
|
||||||
|
log.debug data.action
|
||||||
|
"{data.action}"()
|
||||||
|
data.action = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data.action = ""
|
||||||
|
}else{
|
||||||
|
log.debug "refresh failed ${resp.status} : ${resp.status.code}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception e){
|
||||||
|
log.debug "caught exception refreshing auth token: " + e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def parse_api_response(resp, message) {
|
||||||
|
if (resp.status == 200) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
log.error "sent ${message} Json & got http status ${resp.status} - ${resp.status.code}"
|
||||||
|
if (resp.status == 401) {
|
||||||
|
refreshAuthToken()
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
debugEvent("Authentication error, invalid authentication method, lack of credentials, etc.", true)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def getServerUrl() {
|
||||||
|
return "https://graph.api.smartthings.com"
|
||||||
|
}
|
||||||
|
|
||||||
|
def debugEvent(message, displayEvent) {
|
||||||
|
def results = [
|
||||||
|
name: "appdebug",
|
||||||
|
descriptionText: message,
|
||||||
|
displayed: displayEvent
|
||||||
|
]
|
||||||
|
log.debug "Generating AppDebug Event: ${results}"
|
||||||
|
sendEvent (results)
|
||||||
|
}
|
||||||
|
|
||||||
|
def toQueryString(Map m){
|
||||||
|
return m.collect { k, v -> "${k}=${URLEncoder.encode(v.toString())}" }.sort().join("&")
|
||||||
|
}
|
||||||
@@ -50,7 +50,7 @@ preferences {
|
|||||||
}
|
}
|
||||||
section("Send Notifications?") {
|
section("Send Notifications?") {
|
||||||
input("recipients", "contact", title: "Send notifications to") {
|
input("recipients", "contact", title: "Send notifications to") {
|
||||||
input "phone", "phone", title: "Send an SMS to this number?"
|
input "phone", "phone", title: "Send an SMS to this number?", required:false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,7 +266,9 @@ def sendAway(msg) {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
sendPush(msg)
|
sendPush(msg)
|
||||||
sendSms(phone, msg)
|
if(phone){
|
||||||
|
sendSms(phone, msg)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,7 +282,9 @@ def sendHome(msg) {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
sendPush(msg)
|
sendPush(msg)
|
||||||
sendSms(phone, msg)
|
if(phone){
|
||||||
|
sendSms(phone, msg)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user