Compare commits

..

1 Commits

9 changed files with 200 additions and 179 deletions

View File

@@ -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")
}

View File

@@ -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
} }
/** /**

View File

@@ -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() {

View File

@@ -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){

View File

@@ -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){

View File

@@ -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() {

View File

@@ -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,23 +146,18 @@ 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
@@ -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
deviceList[key2] = "${value.station_name}: ${value2.module_name}"
state.deviceDetail[key2] = value2
state.deviceState[key2] = value2.dashboard_data
} }
response.data.body.modules.each { value ->
def key = value._id
deviceList[key] = "${state.deviceDetail[value.main_device].station_name}: ${value.module_name}"
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();
} }
@@ -475,13 +469,9 @@ def apiGet(String path, Map query, Closure callback) {
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"
}
} }
} }
} }

View File

@@ -137,7 +137,6 @@ 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) {
@@ -146,7 +145,6 @@ def dock_sensor(device_serial, 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()
try {
httpPost(plant_post_params) { plant_post_response -> httpPost(plant_post_params) { plant_post_response ->
if(parse_api_response(plant_post_response, 'creating plant')){ if(parse_api_response(plant_post_response, 'creating plant')){
def attached_map = atomicState.attached_sensors def attached_map = atomicState.attached_sensors
@@ -154,9 +152,6 @@ def dock_sensor(device_serial, expected_plant_name) {
atomicState.attached_sensors = attached_map atomicState.attached_sensors = attached_map
} }
} }
} catch (Exception f) {
log.debug "call failed $f"
}
} else { } else {
def plant = docking_response.data.plants[0] def plant = docking_response.data.plants[0]
def attached_map = atomicState.attached_sensors def attached_map = atomicState.attached_sensors
@@ -166,9 +161,6 @@ def dock_sensor(device_serial, expected_plant_name) {
} }
} }
} }
} catch (Exception e) {
log.debug "call failed $e"
}
return true return true
} }
@@ -186,13 +178,9 @@ def checkAndUpdatePlantIfNeeded(plant, 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,7 +198,6 @@ 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){
@@ -231,9 +218,6 @@ def moistureHandler(event){
} }
} }
} }
} catch (Exception e) {
log.debug "call failed $e"
}
} }
} }
@@ -251,13 +235,9 @@ 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"
}
} }
} }
@@ -295,13 +275,9 @@ 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
atomicState.authToken = jsonMap.access_token atomicState.authToken = jsonMap.access_token

View File

@@ -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"
}
}