mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-08 05:31:56 +00:00
498 lines
13 KiB
Groovy
498 lines
13 KiB
Groovy
/**
|
|
* 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.
|
|
*
|
|
* Wattvision Manager
|
|
*
|
|
* Author: steve
|
|
* Date: 2014-02-13
|
|
*/
|
|
|
|
// Automatically generated. Make future change here.
|
|
definition(
|
|
name: "Wattvision Manager",
|
|
namespace: "smartthings",
|
|
author: "SmartThings",
|
|
description: "Monitor your whole-house energy use by connecting to your Wattvision account",
|
|
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/wattvision.png",
|
|
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/wattvision%402x.png",
|
|
oauth: [displayName: "Wattvision", displayLink: "https://www.wattvision.com/"]
|
|
)
|
|
|
|
preferences {
|
|
page(name: "rootPage")
|
|
}
|
|
|
|
def rootPage() {
|
|
def sensors = state.sensors
|
|
def hrefState = sensors ? "complete" : ""
|
|
def hrefDescription = ""
|
|
sensors.each { sensorId, sensorName ->
|
|
hrefDescription += "${sensorName}\n"
|
|
}
|
|
|
|
dynamicPage(name: "rootPage", install: sensors ? true : false, uninstall: true) {
|
|
section {
|
|
href(url: loginURL(), title: "Connect Wattvision Sensors", style: "embedded", description: hrefDescription, state: hrefState)
|
|
}
|
|
section {
|
|
href(url: "https://www.wattvision.com", title: "Learn More About Wattvision", style: "external", description: null)
|
|
}
|
|
}
|
|
}
|
|
|
|
mappings {
|
|
path("/access") {
|
|
actions:
|
|
[
|
|
POST : "setApiAccess",
|
|
DELETE: "revokeApiAccess"
|
|
]
|
|
}
|
|
path("/devices") {
|
|
actions:
|
|
[
|
|
GET: "listDevices"
|
|
]
|
|
}
|
|
path("/device/:sensorId") {
|
|
actions:
|
|
[
|
|
GET : "getDevice",
|
|
PUT : "updateDevice",
|
|
POST : "createDevice",
|
|
DELETE: "deleteDevice"
|
|
]
|
|
}
|
|
path("/${loginCallbackPath()}") {
|
|
actions:
|
|
[
|
|
GET: "loginCallback"
|
|
]
|
|
}
|
|
}
|
|
|
|
def installed() {
|
|
log.debug "Installed with settings: ${settings}"
|
|
|
|
initialize()
|
|
}
|
|
|
|
def updated() {
|
|
log.debug "Updated with settings: ${settings}"
|
|
|
|
unsubscribe()
|
|
unschedule()
|
|
initialize()
|
|
}
|
|
|
|
def initialize() {
|
|
getDataFromWattvision()
|
|
scheduleDataCollection()
|
|
}
|
|
|
|
def getDataFromWattvision() {
|
|
|
|
log.trace "Getting data from Wattvision"
|
|
|
|
def children = getChildDevices()
|
|
if (!children) {
|
|
log.warn "No children. Not collecting data from Wattviwion"
|
|
// currently only support one child
|
|
return
|
|
}
|
|
|
|
def endDate = new Date()
|
|
def startDate
|
|
|
|
if (!state.lastUpdated) {
|
|
// log.debug "no state.lastUpdated"
|
|
startDate = new Date(hours: endDate.hours - 3)
|
|
} else {
|
|
// log.debug "parsing state.lastUpdated"
|
|
startDate = new Date().parse(smartThingsDateFormat(), state.lastUpdated)
|
|
}
|
|
|
|
state.lastUpdated = endDate.format(smartThingsDateFormat())
|
|
|
|
children.each { child ->
|
|
getDataForChild(child, startDate, endDate)
|
|
}
|
|
|
|
}
|
|
|
|
def getDataForChild(child, startDate, endDate) {
|
|
if (!child) {
|
|
return
|
|
}
|
|
|
|
def wattvisionURL = wattvisionURL(child.deviceNetworkId, startDate, endDate)
|
|
if (wattvisionURL) {
|
|
try {
|
|
httpGet(uri: wattvisionURL) { response ->
|
|
def json = new org.json.JSONObject(response.data.toString())
|
|
child.addWattvisionData(json)
|
|
return "success"
|
|
}
|
|
} catch (groovyx.net.http.HttpResponseException httpE) {
|
|
log.error "Wattvision getDataForChild HttpResponseException: ${httpE} -> ${httpE.response.data}"
|
|
//log.debug "wattvisionURL = ${wattvisionURL}"
|
|
return "fail"
|
|
} catch (e) {
|
|
log.error "Wattvision getDataForChild General Exception: ${e}"
|
|
//log.debug "wattvisionURL = ${wattvisionURL}"
|
|
return "fail"
|
|
}
|
|
}
|
|
}
|
|
|
|
def wattvisionURL(senorId, startDate, endDate) {
|
|
|
|
log.trace "getting wattvisionURL"
|
|
|
|
def wattvisionApiAccess = state.wattvisionApiAccess
|
|
if (!wattvisionApiAccess.id || !wattvisionApiAccess.key) {
|
|
return null
|
|
}
|
|
|
|
if (!endDate) {
|
|
endDate = new Date()
|
|
}
|
|
if (!startDate) {
|
|
startDate = new Date(hours: endDate.hours - 3)
|
|
}
|
|
|
|
def diff = endDate.getTime() - startDate.getTime()
|
|
if (diff > 259200000) { // 3 days in milliseconds
|
|
// Wattvision only allows pulling 3 hours of data at a time
|
|
startDate = new Date(hours: endDate.hours - 3)
|
|
} else if (diff < 10000) { // 10 seconds in milliseconds
|
|
// Wattvision throws errors when the difference between start_time and end_time is 5 seconds or less
|
|
// So we are going to make sure that we have a few more seconds of breathing room
|
|
use (groovy.time.TimeCategory) {
|
|
startDate = endDate - 10.seconds
|
|
}
|
|
}
|
|
|
|
def params = [
|
|
"sensor_id" : senorId,
|
|
"api_id" : wattvisionApiAccess.id,
|
|
"api_key" : wattvisionApiAccess.key,
|
|
"type" : wattvisionDataType ?: "rate",
|
|
"start_time": startDate.format(wattvisionDateFormat()),
|
|
"end_time" : endDate.format(wattvisionDateFormat())
|
|
]
|
|
|
|
def parameterString = params.collect { key, value -> "${key.encodeAsURL()}=${value.encodeAsURL()}" }.join("&")
|
|
def accessURL = wattvisionApiAccess.url ?: "https://www.wattvision.com/api/v0.2/elec"
|
|
def url = "${accessURL}?${parameterString}"
|
|
|
|
// log.debug "wattvisionURL: ${url}"
|
|
return url
|
|
}
|
|
|
|
def getData() {
|
|
state.lastUpdated = new Date().format(smartThingsDateFormat())
|
|
}
|
|
|
|
public smartThingsDateFormat() { "yyyy-MM-dd'T'HH:mm:ss.SSSZ" }
|
|
|
|
public wattvisionDateFormat() { "yyyy-MM-dd'T'HH:mm:ss" }
|
|
|
|
def childMarshaller(child) {
|
|
return [
|
|
name : child.name,
|
|
label : child.label,
|
|
sensor_id: child.deviceNetworkId,
|
|
location : child.location.name
|
|
]
|
|
}
|
|
|
|
// ========================================================
|
|
// ENDPOINTS
|
|
// ========================================================
|
|
|
|
def listDevices() {
|
|
getChildDevices().collect { childMarshaller(it) }
|
|
}
|
|
|
|
def getDevice() {
|
|
|
|
log.trace "Getting device"
|
|
|
|
def child = getChildDevice(params.sensorId)
|
|
|
|
if (!child) {
|
|
httpError(404, "Device not found")
|
|
}
|
|
|
|
return childMarshaller(child)
|
|
}
|
|
|
|
def updateDevice() {
|
|
|
|
log.trace "Updating Device with data from Wattvision"
|
|
|
|
def body = request.JSON
|
|
|
|
def child = getChildDevice(params.sensorId)
|
|
|
|
if (!child) {
|
|
httpError(404, "Device not found")
|
|
}
|
|
|
|
child.addWattvisionData(body)
|
|
|
|
render([status: 204, data: " "])
|
|
}
|
|
|
|
def createDevice() {
|
|
|
|
log.trace "Creating Wattvision device"
|
|
|
|
if (getChildDevice(params.sensorId)) {
|
|
httpError(403, "Device already exists")
|
|
}
|
|
|
|
def child = addChildDevice("smartthings", "Wattvision", params.sensorId, null, [name: "Wattvision", label: request.JSON.label])
|
|
|
|
child.setGraphUrl(getGraphUrl(params.sensorId));
|
|
|
|
getDataForChild(child, null, null)
|
|
|
|
return childMarshaller(child)
|
|
}
|
|
|
|
def deleteDevice() {
|
|
|
|
log.trace "Deleting Wattvision device"
|
|
|
|
deleteChildDevice(params.sensorId)
|
|
render([status: 204, data: " "])
|
|
}
|
|
|
|
def setApiAccess() {
|
|
|
|
log.trace "Granting access to Wattvision API"
|
|
|
|
def body = request.JSON
|
|
|
|
state.wattvisionApiAccess = [
|
|
url: body.url,
|
|
id : body.id,
|
|
key: body.key
|
|
]
|
|
|
|
scheduleDataCollection()
|
|
|
|
render([status: 204, data: " "])
|
|
}
|
|
|
|
def scheduleDataCollection() {
|
|
schedule("* /1 * * * ?", "getDataFromWattvision") // every 1 minute
|
|
}
|
|
|
|
def revokeApiAccess() {
|
|
|
|
log.trace "Revoking access to Wattvision API"
|
|
|
|
state.wattvisionApiAccess = [:]
|
|
render([status: 204, data: " "])
|
|
}
|
|
|
|
public getGraphUrl(sensorId) {
|
|
|
|
log.trace "Collecting URL for Wattvision graph"
|
|
|
|
def apiId = state.wattvisionApiAccess.id
|
|
def apiKey = state.wattvisionApiAccess.key
|
|
|
|
// TODO: allow the changing of type?
|
|
"http://www.wattvision.com/partners/smartthings/charts?s=${sensorId}&api_id=${apiId}&api_key=${apiKey}&type=w"
|
|
}
|
|
|
|
// ========================================================
|
|
// SmartThings initiated setup
|
|
// ========================================================
|
|
|
|
/* Debug info for Steve / Andrew
|
|
|
|
this page: /partners/smartthings/whatswv
|
|
- linked from within smartthings, will tell you how to get a wattvision sensor, etc.
|
|
- pass the debug flag (?debug=1) to show this text.
|
|
|
|
login page: /partners/smartthings/login?callback_url=CALLBACKURL
|
|
- open this page, which will require login.
|
|
- once login is complete, we call you back at callback_url with:
|
|
<callback_url>?id=<wattvision_api_id>&key=<wattvision_api_key>
|
|
question: will you know which user this is on your end?
|
|
|
|
sensor json: /partners/smartthings/sensor_list?api_id=...&api_key=...
|
|
- returns a list of sensors and their associated house names, as a json object
|
|
- example return value with one sensor id 2, associated with house 'Test's House'
|
|
- content type is application/json
|
|
- {"2": "Test's House"}
|
|
|
|
*/
|
|
|
|
def loginCallback() {
|
|
log.trace "loginCallback"
|
|
|
|
state.wattvisionApiAccess = [
|
|
id : params.id,
|
|
key: params.key
|
|
]
|
|
|
|
getSensorJSON(params.id, params.key)
|
|
|
|
connectionSuccessful("Wattvision", "https://s3.amazonaws.com/smartapp-icons/Partner/wattvision@2x.png")
|
|
}
|
|
|
|
private getSensorJSON(id, key) {
|
|
log.trace "getSensorJSON"
|
|
|
|
def sensorUrl = "${wattvisionBaseURL()}/partners/smartthings/sensor_list?api_id=${id}&api_key=${key}"
|
|
|
|
httpGet(uri: sensorUrl) { response ->
|
|
|
|
def sensors = [:]
|
|
|
|
response.data.each { sensorId, sensorName ->
|
|
sensors[sensorId] = sensorName
|
|
createChild(sensorId, sensorName)
|
|
}
|
|
|
|
state.sensors = sensors
|
|
|
|
return "success"
|
|
}
|
|
|
|
}
|
|
|
|
def createChild(sensorId, sensorName) {
|
|
log.trace "creating Wattvision Child"
|
|
|
|
def child = getChildDevice(sensorId)
|
|
|
|
if (child) {
|
|
log.warn "Device already exists"
|
|
} else {
|
|
child = addChildDevice("smartthings", "Wattvision", sensorId, null, [name: "Wattvision", label: sensorName])
|
|
}
|
|
|
|
child.setGraphUrl(getGraphUrl(sensorId));
|
|
|
|
getDataForChild(child, null, null)
|
|
|
|
scheduleDataCollection()
|
|
|
|
return childMarshaller(child)
|
|
}
|
|
|
|
// ========================================================
|
|
// URL HELPERS
|
|
// ========================================================
|
|
|
|
private loginURL() { "${wattvisionBaseURL()}${loginPath()}" }
|
|
|
|
private wattvisionBaseURL() { "https://www.wattvision.com" }
|
|
|
|
private loginPath() { "/partners/smartthings/login?callback_url=${loginCallbackURL().encodeAsURL()}" }
|
|
|
|
private loginCallbackURL() {
|
|
if (!atomicState.accessToken) { createAccessToken() }
|
|
buildActionUrl(loginCallbackPath())
|
|
}
|
|
private loginCallbackPath() { "login/callback" }
|
|
|
|
// ========================================================
|
|
// Access Token
|
|
// ========================================================
|
|
|
|
private getMyAccessToken() { return atomicState.accessToken ?: createAccessToken() }
|
|
|
|
// ========================================================
|
|
// CONNECTED HTML
|
|
// ========================================================
|
|
|
|
def connectionSuccessful(deviceName, iconSrc) {
|
|
def html = """
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta name="viewport" content="width=640">
|
|
<title>Withings Connection</title>
|
|
<style type="text/css">
|
|
@font-face {
|
|
font-family: 'Swiss 721 W01 Thin';
|
|
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot');
|
|
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot?#iefix') format('embedded-opentype'),
|
|
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.woff') format('woff'),
|
|
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.ttf') format('truetype'),
|
|
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.svg#swis721_th_btthin') format('svg');
|
|
font-weight: normal;
|
|
font-style: normal;
|
|
}
|
|
@font-face {
|
|
font-family: 'Swiss 721 W01 Light';
|
|
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot');
|
|
src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot?#iefix') format('embedded-opentype'),
|
|
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.woff') format('woff'),
|
|
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.ttf') format('truetype'),
|
|
url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.svg#swis721_lt_btlight') format('svg');
|
|
font-weight: normal;
|
|
font-style: normal;
|
|
}
|
|
.container {
|
|
width: 560px;
|
|
padding: 40px;
|
|
/*background: #eee;*/
|
|
text-align: center;
|
|
}
|
|
img {
|
|
vertical-align: middle;
|
|
}
|
|
img:nth-child(2) {
|
|
margin: 0 30px;
|
|
}
|
|
p {
|
|
font-size: 2.2em;
|
|
font-family: 'Swiss 721 W01 Thin';
|
|
text-align: center;
|
|
color: #666666;
|
|
padding: 0 40px;
|
|
margin-bottom: 0;
|
|
}
|
|
/*
|
|
p:last-child {
|
|
margin-top: 0px;
|
|
}
|
|
*/
|
|
span {
|
|
font-family: 'Swiss 721 W01 Light';
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<img src="${iconSrc}" alt="${deviceName} icon" />
|
|
<img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/connected-device-icn%402x.png" alt="connected device icon" />
|
|
<img src="https://s3.amazonaws.com/smartapp-icons/Partner/support/st-logo%402x.png" alt="SmartThings logo" />
|
|
<p>Your ${deviceName} is now connected to SmartThings!</p>
|
|
<p>Click 'Done' to finish setup.</p>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
"""
|
|
|
|
render contentType: 'text/html', data: html
|
|
}
|