mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-17 13:10:52 +00:00
Compare commits
1 Commits
test-10
...
MSA-1748-1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
387e59a9bb |
@@ -1,46 +0,0 @@
|
|||||||
/**
|
|
||||||
* Stateless On/Off Button Tile
|
|
||||||
*
|
|
||||||
* Author: Ronald Gouldner
|
|
||||||
*
|
|
||||||
* Date: 2015-05-14
|
|
||||||
*/
|
|
||||||
metadata {
|
|
||||||
// Automatically generated. Make future change here.
|
|
||||||
definition (name: "Stateless On-Off Button Tile", namespace: "gouldner", author: "Ronald Gouldner") {
|
|
||||||
capability "Actuator"
|
|
||||||
capability "Switch"
|
|
||||||
capability "Sensor"
|
|
||||||
}
|
|
||||||
|
|
||||||
// simulator metadata
|
|
||||||
simulator {
|
|
||||||
}
|
|
||||||
|
|
||||||
// UI tile definitions
|
|
||||||
tiles {
|
|
||||||
standardTile("button", "device.switch", width: 2, height: 2, canChangeIcon: true) {
|
|
||||||
state "offReady", label: 'Off', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff", nextState: "onReady"
|
|
||||||
state "onReady", label: 'On', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821", nextState: "offReady"
|
|
||||||
state "off", label: 'Off', action: "switch.on", icon: "st.switches.switch.off", backgroundColor: "#ffffff"
|
|
||||||
state "on", label: 'On', action: "switch.off", icon: "st.switches.switch.on", backgroundColor: "#79b821"
|
|
||||||
}
|
|
||||||
main "button"
|
|
||||||
details "button"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def parse(String description) {
|
|
||||||
}
|
|
||||||
|
|
||||||
def on() {
|
|
||||||
log.debug "Stateless On/Off Button Tile Virtual Switch ${device.name} turned on"
|
|
||||||
sendEvent(name: "switch", value: "on")
|
|
||||||
sendEvent(name: "switch", value: "onReady")
|
|
||||||
}
|
|
||||||
|
|
||||||
def off() {
|
|
||||||
log.debug "Stateless On/Off Button Tile Virtual Switch ${device.name} turned off"
|
|
||||||
sendEvent(name: "switch", value: "off")
|
|
||||||
sendEvent(name: "switch", value: "offReady")
|
|
||||||
}
|
|
||||||
@@ -82,7 +82,7 @@ def on() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def setLevel(value) {
|
def setLevel(value) {
|
||||||
zigbee.setLevel(value) + zigbee.onOffRefresh() + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
|
zigbee.setLevel(value) + ["delay 500"] + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ def on() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def setLevel(value) {
|
def setLevel(value) {
|
||||||
zigbee.setLevel(value) + zigbee.onOffRefresh() + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
|
zigbee.setLevel(value) + ["delay 1500"] + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
|
||||||
}
|
}
|
||||||
|
|
||||||
def refresh() {
|
def refresh() {
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ def refreshAttributes() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def setLevel(value) {
|
def setLevel(value) {
|
||||||
zigbee.setLevel(value) + zigbee.onOffRefresh() + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
|
zigbee.setLevel(value) + ["delay 1500"] + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
|
||||||
}
|
}
|
||||||
|
|
||||||
def setColor(value){
|
def setColor(value){
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ def setColorTemperature(value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def setLevel(value) {
|
def setLevel(value) {
|
||||||
zigbee.setLevel(value) + zigbee.onOffRefresh() + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
|
zigbee.setLevel(value) + ["delay 1500"] + zigbee.levelRefresh() //adding refresh because of ZLL bulb not conforming to send-me-a-report
|
||||||
}
|
}
|
||||||
|
|
||||||
def setColor(value){
|
def setColor(value){
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ def on() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def setLevel(value) {
|
def setLevel(value) {
|
||||||
zigbee.setLevel(value) + zigbee.onOffRefresh() + zigbee.levelRefresh()
|
zigbee.setLevel(value) + ["delay 1500"] + zigbee.levelRefresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
def refresh() {
|
def refresh() {
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ def authPage() {
|
|||||||
return dynamicPage(name: "Credentials", title: "Authorize Connection", nextPage:"listDevices", uninstall: uninstallAllowed, install:false) {
|
return dynamicPage(name: "Credentials", title: "Authorize Connection", nextPage:"listDevices", uninstall: uninstallAllowed, install:false) {
|
||||||
section() {
|
section() {
|
||||||
paragraph "Tap below to log in to the netatmo and authorize SmartThings access."
|
paragraph "Tap below to log in to the netatmo and authorize SmartThings access."
|
||||||
href url:redirectUrl, style:"embedded", required:false, title:"Connect to ${getVendorName()}", description:description
|
href url:redirectUrl, style:"embedded", required:false, title:"Connect to ${getVendorName()}:", description:description
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -146,24 +146,19 @@ def callback() {
|
|||||||
|
|
||||||
// log.debug "PARAMS: ${params}"
|
// log.debug "PARAMS: ${params}"
|
||||||
|
|
||||||
try {
|
httpPost(params) { resp ->
|
||||||
httpPost(params) { resp ->
|
|
||||||
|
|
||||||
def slurper = new JsonSlurper()
|
def slurper = new JsonSlurper()
|
||||||
|
|
||||||
resp.data.each { key, value ->
|
resp.data.each { key, value ->
|
||||||
def data = slurper.parseText(key)
|
def data = slurper.parseText(key)
|
||||||
log.debug "Data: $data"
|
|
||||||
state.refreshToken = data.refresh_token
|
state.refreshToken = data.refresh_token
|
||||||
state.authToken = data.access_token
|
state.authToken = data.access_token
|
||||||
//state.accessToken = data.access_token
|
state.tokenExpires = now() + (data.expires_in * 1000)
|
||||||
state.tokenExpires = now() + (data.expires_in * 1000)
|
// log.debug "swapped token: $resp.data"
|
||||||
// log.debug "swapped token: $resp.data"
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.debug "callback: Call failed $e"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle success and failure here, and render stuff accordingly
|
// Handle success and failure here, and render stuff accordingly
|
||||||
if (state.authToken) {
|
if (state.authToken) {
|
||||||
@@ -392,18 +387,18 @@ def getDeviceList() {
|
|||||||
state.deviceDetail = [:]
|
state.deviceDetail = [:]
|
||||||
state.deviceState = [:]
|
state.deviceState = [:]
|
||||||
|
|
||||||
apiGet("/api/getstationsdata") { response ->
|
apiGet("/api/devicelist") { response ->
|
||||||
response.data.body.devices.each { value ->
|
response.data.body.devices.each { value ->
|
||||||
def key = value._id
|
def key = value._id
|
||||||
deviceList[key] = "${value.station_name}: ${value.module_name}"
|
deviceList[key] = "${value.station_name}: ${value.module_name}"
|
||||||
state.deviceDetail[key] = value
|
state.deviceDetail[key] = value
|
||||||
state.deviceState[key] = value.dashboard_data
|
state.deviceState[key] = value.dashboard_data
|
||||||
value.modules.each { value2 ->
|
}
|
||||||
def key2 = value2._id
|
response.data.body.modules.each { value ->
|
||||||
deviceList[key2] = "${value.station_name}: ${value2.module_name}"
|
def key = value._id
|
||||||
state.deviceDetail[key2] = value2
|
deviceList[key] = "${state.deviceDetail[value.main_device].station_name}: ${value.module_name}"
|
||||||
state.deviceState[key2] = value2.dashboard_data
|
state.deviceDetail[key] = value
|
||||||
}
|
state.deviceState[key] = value.dashboard_data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -453,7 +448,6 @@ def listDevices() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def apiGet(String path, Map query, Closure callback) {
|
def apiGet(String path, Map query, Closure callback) {
|
||||||
|
|
||||||
if(now() >= state.tokenExpires) {
|
if(now() >= state.tokenExpires) {
|
||||||
refreshToken();
|
refreshToken();
|
||||||
}
|
}
|
||||||
@@ -473,16 +467,12 @@ def apiGet(String path, Map query, Closure callback) {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// This is most likely due to an invalid token. Try to refresh it and try again.
|
// This is most likely due to an invalid token. Try to refresh it and try again.
|
||||||
log.debug "apiGet: Call failed $e"
|
log.debug "apiGet: Call failed $e"
|
||||||
if(refreshToken()) {
|
if(refreshToken()) {
|
||||||
log.debug "apiGet: Trying again after refreshing token"
|
log.debug "apiGet: Trying again after refreshing token"
|
||||||
try {
|
httpGet(params) { response ->
|
||||||
httpGet(params) { response ->
|
callback.call(response)
|
||||||
callback.call(response)
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception f) {
|
|
||||||
log.debug "apiGet: Call failed $f"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -571,4 +561,4 @@ private Boolean hasAllHubsOver(String desiredFirmware) {
|
|||||||
|
|
||||||
private List getRealHubFirmwareVersions() {
|
private List getRealHubFirmwareVersions() {
|
||||||
return location.hubs*.firmwareVersionString.findAll { it }
|
return location.hubs*.firmwareVersionString.findAll { it }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ def authPage(){
|
|||||||
atomicState.accessToken = state.accessToken
|
atomicState.accessToken = state.accessToken
|
||||||
}
|
}
|
||||||
|
|
||||||
def redirectUrl = oauthInitUrl()
|
def redirectUrl = oauthInitUrl()
|
||||||
def uninstallAllowed = false
|
def uninstallAllowed = false
|
||||||
def oauthTokenProvided = false
|
def oauthTokenProvided = false
|
||||||
if(atomicState.authToken){
|
if(atomicState.authToken){
|
||||||
@@ -78,9 +78,9 @@ def authPage(){
|
|||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
return dynamicPage(name: "auth", title: "Step 1 of 2 - Completed", nextPage:"deviceList", uninstall:uninstallAllowed) {
|
return dynamicPage(name: "auth", title: "Step 1 of 2 - Completed", nextPage:"deviceList", uninstall:uninstallAllowed) {
|
||||||
section(){
|
section(){
|
||||||
paragraph "You are logged in to myplantlink.com, tap next to continue", image: iconUrl
|
paragraph "You are logged in to myplantlink.com, tap next to continue", image: iconUrl
|
||||||
href(url:redirectUrl, title:"Or", description:"tap to switch accounts")
|
href(url:redirectUrl, title:"Or", description:"tap to switch accounts")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -137,44 +137,36 @@ def dock_sensor(device_serial, expected_plant_name) {
|
|||||||
contentType: "application/json",
|
contentType: "application/json",
|
||||||
]
|
]
|
||||||
log.debug "Creating new plant on myplantlink.com - ${expected_plant_name}"
|
log.debug "Creating new plant on myplantlink.com - ${expected_plant_name}"
|
||||||
try {
|
httpPost(docking_params) { docking_response ->
|
||||||
httpPost(docking_params) { docking_response ->
|
if (parse_api_response(docking_response, "Docking a link")) {
|
||||||
if (parse_api_response(docking_response, "Docking a link")) {
|
if (docking_response.data.plants.size() == 0) {
|
||||||
if (docking_response.data.plants.size() == 0) {
|
log.debug "creating plant for - ${expected_plant_name}"
|
||||||
log.debug "creating plant for - ${expected_plant_name}"
|
plant_post_body_map["name"] = expected_plant_name
|
||||||
plant_post_body_map["name"] = expected_plant_name
|
plant_post_body_map['links_key'] = [docking_response.data.key]
|
||||||
plant_post_body_map['links_key'] = [docking_response.data.key]
|
def plant_post_body_json_builder = new JsonBuilder(plant_post_body_map)
|
||||||
def plant_post_body_json_builder = new JsonBuilder(plant_post_body_map)
|
plant_post_params["body"] = plant_post_body_json_builder.toString()
|
||||||
plant_post_params["body"] = plant_post_body_json_builder.toString()
|
httpPost(plant_post_params) { plant_post_response ->
|
||||||
try {
|
if(parse_api_response(plant_post_response, 'creating plant')){
|
||||||
httpPost(plant_post_params) { plant_post_response ->
|
def attached_map = atomicState.attached_sensors
|
||||||
if(parse_api_response(plant_post_response, 'creating plant')){
|
attached_map[device_serial] = plant_post_response.data
|
||||||
def attached_map = atomicState.attached_sensors
|
atomicState.attached_sensors = attached_map
|
||||||
attached_map[device_serial] = plant_post_response.data
|
|
||||||
atomicState.attached_sensors = attached_map
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception f) {
|
|
||||||
log.debug "call failed $f"
|
|
||||||
}
|
}
|
||||||
} 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)
|
|
||||||
}
|
}
|
||||||
|
} 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
|
||||||
log.debug "call failed $e"
|
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
def checkAndUpdatePlantIfNeeded(plant, expected_plant_name){
|
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"
|
||||||
]
|
]
|
||||||
@@ -182,16 +174,12 @@ def checkAndUpdatePlantIfNeeded(plant, expected_plant_name){
|
|||||||
log.debug "updating plant for - ${expected_plant_name}"
|
log.debug "updating plant for - ${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
|
||||||
]
|
]
|
||||||
def plant_put_body_json_builder = new JsonBuilder(plant_put_body_map)
|
def plant_put_body_json_builder = new JsonBuilder(plant_put_body_map)
|
||||||
plant_put_params["body"] = plant_put_body_json_builder.toString()
|
plant_put_params["body"] = plant_put_body_json_builder.toString()
|
||||||
try {
|
httpPut(plant_put_params) { plant_put_response ->
|
||||||
httpPut(plant_put_params) { plant_put_response ->
|
parse_api_response(plant_put_response, 'updating plant name')
|
||||||
parse_api_response(plant_put_response, 'updating plant name')
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.debug "call failed $e"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -210,29 +198,25 @@ def moistureHandler(event){
|
|||||||
contentType: "application/json",
|
contentType: "application/json",
|
||||||
body: event.value
|
body: event.value
|
||||||
]
|
]
|
||||||
try {
|
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
|
||||||
log.debug plant
|
checkAndUpdatePlantIfNeeded(plant, expected_plant_name)
|
||||||
checkAndUpdatePlantIfNeeded(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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
|
||||||
log.debug "call failed $e"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -251,12 +235,8 @@ def batteryHandler(event){
|
|||||||
contentType: "application/json",
|
contentType: "application/json",
|
||||||
body: event.value
|
body: event.value
|
||||||
]
|
]
|
||||||
try {
|
httpPost(measurement_post_params) { measurement_post_response ->
|
||||||
httpPost(measurement_post_params) { measurement_post_response ->
|
parse_api_response(measurement_post_response, 'creating battery measurement')
|
||||||
parse_api_response(measurement_post_response, 'creating battery measurement')
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.debug "call failed $e"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -268,7 +248,7 @@ def getDeviceSerialFromEvent(event){
|
|||||||
}
|
}
|
||||||
|
|
||||||
def oauthInitUrl(){
|
def oauthInitUrl(){
|
||||||
atomicState.oauthInitState = UUID.randomUUID().toString()
|
atomicState.oauthInitState = UUID.randomUUID().toString()
|
||||||
def oauthParams = [
|
def oauthParams = [
|
||||||
response_type: "code",
|
response_type: "code",
|
||||||
client_id: appSettings.client_id,
|
client_id: appSettings.client_id,
|
||||||
@@ -295,12 +275,8 @@ def swapToken(){
|
|||||||
]
|
]
|
||||||
|
|
||||||
def jsonMap
|
def jsonMap
|
||||||
try {
|
httpPost(postParams) { resp ->
|
||||||
httpPost(postParams) { resp ->
|
jsonMap = resp.data
|
||||||
jsonMap = resp.data
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.debug "call failed $e"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
atomicState.refreshToken = jsonMap.refresh_token
|
atomicState.refreshToken = jsonMap.refresh_token
|
||||||
@@ -311,33 +287,33 @@ def swapToken(){
|
|||||||
<head>
|
<head>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<style>
|
<style>
|
||||||
.container {
|
.container {
|
||||||
padding:25px;
|
padding:25px;
|
||||||
}
|
}
|
||||||
.flex1 {
|
.flex1 {
|
||||||
width:33%;
|
width:33%;
|
||||||
float:left;
|
float:left;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
p {
|
p {
|
||||||
font-size: 2em;
|
font-size: 2em;
|
||||||
font-family: Verdana, Geneva, sans-serif;
|
font-family: Verdana, Geneva, sans-serif;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #777;
|
color: #777;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<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://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/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>
|
<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">
|
<br clear="all">
|
||||||
</div>
|
</div>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<p>Your PlantLink Account is now connected to SmartThings!</p>
|
<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>
|
<p style="color:green;">Click <strong>Done</strong> at the top right to finish setup.</p>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
/**
|
||||||
|
* Xandem Home Integration
|
||||||
|
*
|
||||||
|
* Copyright 2017 Derek Twaddle
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* README
|
||||||
|
*
|
||||||
|
* Hello, after purchsing Xandem Home for a rental property I noticed there were no SmartApps yet developed. This is my
|
||||||
|
* first attempt at integrating Xandem with SmartThings out of personal need and decided to share with the community.
|
||||||
|
*
|
||||||
|
* Depending on your ISP and setup, there are some preliminary steps that need to be performed before installing this app. Xandem is initially
|
||||||
|
* accessible only via local network so you will need to make some minor rule changes to your firewall / router.
|
||||||
|
*
|
||||||
|
* If you have a static IP assignment from your ISP, you may forgo the following step. If your ISP assigns an IP Address to you Dynamically
|
||||||
|
* which is what most do at Resedential installations, you will need a Dynamic DNS service. Dynamic DNS will map your randomly assigned IP
|
||||||
|
* address to a static name like (Ex: myhome.dyndns.com). No matter what your IP changes to, it will always be resolved using the same name you chose.
|
||||||
|
*
|
||||||
|
* Once the above is completed, you will need to configure your router to pass or port forward to your internal Xandem Hub. You will
|
||||||
|
* want to statically assign a local IP to the mac address of the Xandem Hub. This way you will always assign the same local IP to the Xandem Hub which can be mapped
|
||||||
|
* and used in port forwarding rules. After the IP has been assigned (Ex: 192.168.1.25) add a rule on your rounter to pass incoming Port 80 request
|
||||||
|
* to your Xandem Hub IP address on the same port. This effectively lets your router listen for and pass data from SmarthThings to your Xandem Hub.
|
||||||
|
*
|
||||||
|
* Once the above networking steps are completed, create a Xandem API key on your local Xandem Hub. Instructions can
|
||||||
|
* found here: http://documentation.xandem.com/api/#api-keys-and-authorization-header
|
||||||
|
*
|
||||||
|
* This SmartApp is a personal project you are free to use.
|
||||||
|
*/
|
||||||
|
|
||||||
|
definition(
|
||||||
|
name: "Xandem Home Integration",
|
||||||
|
namespace: "Xandem",
|
||||||
|
author: "Derek Twaddle",
|
||||||
|
description: "Integrates Xandem Home to SmartThings. Detects motion, if above a user set threshold, turn on light.",
|
||||||
|
category: "Safety & Security",
|
||||||
|
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
|
||||||
|
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
|
||||||
|
iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png"
|
||||||
|
)
|
||||||
|
|
||||||
|
preferences {
|
||||||
|
section("Network Configuration") {
|
||||||
|
input "ddns", "text", title: " External Hostname or Static IP", required: true
|
||||||
|
paragraph "Example: mynetwork.dyndns.org or 64.102.0.1"
|
||||||
|
input "apikey", "text", title: "API Key", required: true
|
||||||
|
paragraph "Enter the Xandem API Key generated on your local Hub"
|
||||||
|
}
|
||||||
|
section("Turn on when motion detected and at or above level:") {
|
||||||
|
input "thelevel", "text", title: "1 - 10", required: true
|
||||||
|
paragraph "Motion Level: 1 Minimal Motion - 10 High Motion"
|
||||||
|
}
|
||||||
|
section("Turn on this light") {
|
||||||
|
input "theswitch", "capability.switch", required: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def installed() {
|
||||||
|
log.debug "Installed with settings: ${settings}"
|
||||||
|
initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
def updated() {
|
||||||
|
log.debug "Updated with settings: ${settings}"
|
||||||
|
unsubscribe()
|
||||||
|
initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
def initialize() {
|
||||||
|
runEvery5Minutes(updateStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
def updateStatus() {
|
||||||
|
def params = [
|
||||||
|
uri: "http://${ddns}/v1/data",
|
||||||
|
headers: [Authorization: "${apikey}"],
|
||||||
|
body: [data_fields: ["motion_score", "is_motion"]]
|
||||||
|
]
|
||||||
|
|
||||||
|
try {
|
||||||
|
httpPostJson(params) { resp ->
|
||||||
|
resp.headers.each {
|
||||||
|
}
|
||||||
|
|
||||||
|
// If motion detected and motion level is at or above chosen level, Light On
|
||||||
|
if (resp.data.is_motion != 0 && resp.data.motion_score >= thelevel){
|
||||||
|
theswitch.on()
|
||||||
|
log.debug "Motion detected, light on"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log.debug "something went wrong: $e"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user