mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-13 13:21:53 +00:00
Compare commits
1 Commits
MSA-2122-1
...
MSA-2112-2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f0e60e9da0 |
@@ -0,0 +1,439 @@
|
||||
/**
|
||||
* FortrezZ Flow Meter Interface
|
||||
*
|
||||
* Copyright 2016 FortrezZ, LLC
|
||||
*
|
||||
* 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: "FortrezZ Flow Meter Interface", namespace: "fortrezz", author: "Daniel Kurin") {
|
||||
capability "Battery"
|
||||
capability "Energy Meter"
|
||||
capability "Image Capture"
|
||||
capability "Temperature Measurement"
|
||||
capability "Sensor"
|
||||
capability "Water Sensor"
|
||||
|
||||
attribute "gpm", "number"
|
||||
attribute "cumulative", "number"
|
||||
attribute "alarmState", "string"
|
||||
attribute "chartMode", "string"
|
||||
attribute "lastThreshhold", "number"
|
||||
|
||||
|
||||
command "chartMode"
|
||||
command "zero"
|
||||
command "setHighFlowLevel", ["number"]
|
||||
|
||||
fingerprint deviceId: "0x2101", inClusters: "0x5E, 0x86, 0x72, 0x5A, 0x73, 0x71, 0x85, 0x59, 0x32, 0x31, 0x70, 0x80, 0x7A"
|
||||
}
|
||||
|
||||
simulator {
|
||||
// TODO: define status and reply messages here
|
||||
}
|
||||
|
||||
preferences {
|
||||
input "gallonThreshhold", "decimal", title: "High Flow Rate Threshhold", description: "Flow rate (in gpm) that will trigger a notification.", defaultValue: 5, required: false, displayDuringSetup: true
|
||||
input("registerEmail", type: "email", required: false, title: "Email Address", description: "Register your device with FortrezZ", displayDuringSetup: true)
|
||||
}
|
||||
|
||||
tiles(scale: 2) {
|
||||
carouselTile("flowHistory", "device.image", width: 6, height: 3) { }
|
||||
valueTile("battery", "device.battery", inactiveLabel: false, width: 2, height: 2) {
|
||||
state "battery", label:'${currentValue}%\nBattery', unit:""
|
||||
}
|
||||
valueTile("temperature", "device.temperature", width: 2, height: 2) {
|
||||
state("temperature", label:'${currentValue}°',
|
||||
backgroundColors:[
|
||||
[value: 31, color: "#153591"],
|
||||
[value: 44, color: "#1e9cbb"],
|
||||
[value: 59, color: "#90d2a7"],
|
||||
[value: 74, color: "#44b621"],
|
||||
[value: 84, color: "#f1d801"],
|
||||
[value: 95, color: "#d04e00"],
|
||||
[value: 96, color: "#bc2323"]
|
||||
]
|
||||
)
|
||||
}
|
||||
valueTile("gpm", "device.gpm", inactiveLabel: false, width: 2, height: 2) {
|
||||
state "gpm", label:'${currentValue}gpm', unit:""
|
||||
}
|
||||
standardTile("powerState", "device.powerState", width: 2, height: 2) {
|
||||
state "reconnected", icon:"http://swiftlet.technology/wp-content/uploads/2016/02/Connected-64.png", backgroundColor:"#cccccc"
|
||||
state "disconnected", icon:"http://swiftlet.technology/wp-content/uploads/2016/02/Disconnected-64.png", backgroundColor:"#cc0000"
|
||||
state "batteryReplaced", icon:"http://swiftlet.technology/wp-content/uploads/2016/04/Full-Battery-96.png", backgroundColor:"#cccccc"
|
||||
state "noBattery", icon:"http://swiftlet.technology/wp-content/uploads/2016/04/No-Battery-96.png", backgroundColor:"#cc0000"
|
||||
}
|
||||
standardTile("waterState", "device.waterState", width: 2, height: 2, canChangeIcon: true) {
|
||||
state "none", icon:"http://cdn.device-icons.smartthings.com/Weather/weather12-icn@2x.png", backgroundColor:"#cccccc", label: "No Flow"
|
||||
state "flow", icon:"http://cdn.device-icons.smartthings.com/Weather/weather12-icn@2x.png", backgroundColor:"#53a7c0", label: "Flow"
|
||||
state "overflow", icon:"http://cdn.device-icons.smartthings.com/Weather/weather12-icn@2x.png", backgroundColor:"#cc0000", label: "High Flow"
|
||||
}
|
||||
standardTile("heatState", "device.heatState", width: 2, height: 2) {
|
||||
state "normal", label:'Normal', icon:"st.alarm.temperature.normal", backgroundColor:"#ffffff"
|
||||
state "freezing", label:'Freezing', icon:"st.alarm.temperature.freeze", backgroundColor:"#2eb82e"
|
||||
state "overheated", label:'Overheated', icon:"st.alarm.temperature.overheat", backgroundColor:"#F80000"
|
||||
}
|
||||
standardTile("take1", "device.image", width: 2, height: 2, canChangeIcon: false, inactiveLabel: true, canChangeBackground: false, decoration: "flat") {
|
||||
state "take", label: "", action: "Image Capture.take", nextState:"taking", icon: "st.secondary.refresh"
|
||||
}
|
||||
standardTile("chartMode", "device.chartMode", width: 2, height: 2, canChangeIcon: false, canChangeBackground: false, decoration: "flat") {
|
||||
state "day", label:'24 Hours\n(press to change)', nextState: "week", action: 'chartMode'
|
||||
state "week", label:'7 Days\n(press to change)', nextState: "month", action: 'chartMode'
|
||||
state "month", label:'4 Weeks\n(press to change)', nextState: "day", action: 'chartMode'
|
||||
}
|
||||
valueTile("zeroTile", "device.zero", width: 2, height: 2, canChangeIcon: false, canChangeBackground: false, decoration: "flat") {
|
||||
state "zero", label:'Zero', action: 'zero'
|
||||
}
|
||||
main (["waterState"])
|
||||
details(["flowHistory", "chartMode", "take1", "temperature", "gpm", "waterState", "battery"])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// parse events into attributes
|
||||
def parse(String description) {
|
||||
def results = []
|
||||
if (description.startsWith("Err")) {
|
||||
results << createEvent(descriptionText:description, displayed:true)
|
||||
} else {
|
||||
def cmd = zwave.parse(description, [ 0x80: 1, 0x84: 1, 0x71: 2, 0x72: 1 ])
|
||||
if (cmd) {
|
||||
results << createEvent( zwaveEvent(cmd) )
|
||||
}
|
||||
}
|
||||
//log.debug "\"$description\" parsed to ${results.inspect()}"
|
||||
if(gallonThreshhold != device.currentValue("lastThreshhold"))
|
||||
{
|
||||
results << setThreshhold(gallonThreshhold)
|
||||
}
|
||||
log.debug "zwave parsed to ${results.inspect()}"
|
||||
return results
|
||||
}
|
||||
|
||||
def updated()
|
||||
{
|
||||
log.debug("Updated")
|
||||
}
|
||||
|
||||
def setHighFlowLevel(level)
|
||||
{
|
||||
setThreshhold(level)
|
||||
}
|
||||
|
||||
def take() {
|
||||
def mode = device.currentValue("chartMode")
|
||||
if(mode == "day")
|
||||
{
|
||||
take1()
|
||||
}
|
||||
else if(mode == "week")
|
||||
{
|
||||
take7()
|
||||
}
|
||||
else if(mode == "month")
|
||||
{
|
||||
take28()
|
||||
}
|
||||
}
|
||||
|
||||
def chartMode(string) {
|
||||
log.debug("ChartMode")
|
||||
def state = device.currentValue("chartMode")
|
||||
def tempValue = ""
|
||||
switch(state)
|
||||
{
|
||||
case "day":
|
||||
tempValue = "week"
|
||||
break
|
||||
|
||||
case "week":
|
||||
tempValue = "month"
|
||||
break
|
||||
|
||||
case "month":
|
||||
tempValue = "day"
|
||||
break
|
||||
|
||||
default:
|
||||
tempValue = "day"
|
||||
break
|
||||
}
|
||||
sendEvent(name: "chartMode", value: tempValue)
|
||||
take()
|
||||
}
|
||||
|
||||
def take1() {
|
||||
api("24hrs", "") {
|
||||
log.debug("Image captured")
|
||||
|
||||
if(it.headers.'Content-Type'.contains("image/png")) {
|
||||
if(it.data) {
|
||||
storeImage(getPictureName("24hrs"), it.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def take7() {
|
||||
api("7days", "") {
|
||||
log.debug("Image captured")
|
||||
|
||||
if(it.headers.'Content-Type'.contains("image/png")) {
|
||||
if(it.data) {
|
||||
storeImage(getPictureName("7days"), it.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def take28() {
|
||||
api("4weeks", "") {
|
||||
log.debug("Image captured")
|
||||
|
||||
if(it.headers.'Content-Type'.contains("image/png")) {
|
||||
if(it.data) {
|
||||
storeImage(getPictureName("4weeks"), it.data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def zero()
|
||||
{
|
||||
delayBetween([
|
||||
zwave.meterV3.meterReset().format(),
|
||||
zwave.meterV3.meterGet().format(),
|
||||
zwave.firmwareUpdateMdV2.firmwareMdGet().format(),
|
||||
], 100)
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd)
|
||||
{
|
||||
log.debug cmd
|
||||
def map = [:]
|
||||
if(cmd.sensorType == 1) {
|
||||
map = [name: "temperature"]
|
||||
if(cmd.scale == 0) {
|
||||
map.value = getTemperature(cmd.scaledSensorValue)
|
||||
} else {
|
||||
map.value = cmd.scaledSensorValue
|
||||
}
|
||||
map.unit = location.temperatureScale
|
||||
} /* else if(cmd.sensorType == 2) {
|
||||
map = [name: "waterState"]
|
||||
if(cmd.sensorValue[0] == 0x80) {
|
||||
map.value = "flow"
|
||||
sendEvent(name: "water", value: "dry")
|
||||
} else if(cmd.sensorValue[0] == 0x00) {
|
||||
map.value = "none"
|
||||
sendEvent(name: "water", value: "dry")
|
||||
} else if(cmd.sensorValue[0] == 0xFF) {
|
||||
map.value = "overflow"
|
||||
sendEvent(name: "water", value: "wet")
|
||||
sendAlarm("waterOverflow")
|
||||
}
|
||||
} */
|
||||
return map
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.meterv3.MeterReport cmd)
|
||||
{
|
||||
def map = [:]
|
||||
map.name = "gpm"
|
||||
def delta = cmd.scaledMeterValue - cmd.scaledPreviousMeterValue
|
||||
if (delta < 0 || delta > 10000) {
|
||||
log.error(cmd)
|
||||
delta = 0
|
||||
}
|
||||
|
||||
map.value = delta
|
||||
map.unit = "gpm"
|
||||
sendDataToCloud(delta)
|
||||
sendEvent(name: "cumulative", value: cmd.scaledMeterValue, displayed: false, unit: "gal")
|
||||
return map
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd)
|
||||
{
|
||||
def map = [:]
|
||||
if (cmd.zwaveAlarmType == 8) // Power Alarm
|
||||
{
|
||||
map.name = "powerState" // For Tile (shows in "Recently")
|
||||
if (cmd.zwaveAlarmEvent == 2) // AC Mains Disconnected
|
||||
{
|
||||
map.value = "disconnected"
|
||||
sendAlarm("acMainsDisconnected")
|
||||
}
|
||||
else if (cmd.zwaveAlarmEvent == 3) // AC Mains Reconnected
|
||||
{
|
||||
map.value = "reconnected"
|
||||
sendAlarm("acMainsReconnected")
|
||||
}
|
||||
else if (cmd.zwaveAlarmEvent == 0x0B) // Replace Battery Now
|
||||
{
|
||||
map.value = "noBattery"
|
||||
sendAlarm("replaceBatteryNow")
|
||||
}
|
||||
else if (cmd.zwaveAlarmEvent == 0x00) // Battery Replaced
|
||||
{
|
||||
map.value = "batteryReplaced"
|
||||
sendAlarm("batteryReplaced")
|
||||
}
|
||||
}
|
||||
else if (cmd.zwaveAlarmType == 4) // Heat Alarm
|
||||
{
|
||||
map.name = "heatState"
|
||||
if (cmd.zwaveAlarmEvent == 0) // Normal
|
||||
{
|
||||
map.value = "normal"
|
||||
}
|
||||
else if (cmd.zwaveAlarmEvent == 1) // Overheat
|
||||
{
|
||||
map.value = "overheated"
|
||||
sendAlarm("tempOverheated")
|
||||
}
|
||||
else if (cmd.zwaveAlarmEvent == 5) // Underheat
|
||||
{
|
||||
map.value = "freezing"
|
||||
sendAlarm("tempFreezing")
|
||||
}
|
||||
}
|
||||
else if (cmd.zwaveAlarmType == 5) // Water Alarm
|
||||
{
|
||||
map.name = "waterState"
|
||||
if (cmd.zwaveAlarmEvent == 0) // Normal
|
||||
{
|
||||
map.value = "none"
|
||||
sendEvent(name: "water", value: "dry")
|
||||
}
|
||||
else if (cmd.zwaveAlarmEvent == 6) // Flow Detected
|
||||
{
|
||||
if(cmd.eventParameter[0] == 2)
|
||||
{
|
||||
map.value = "flow"
|
||||
sendEvent(name: "water", value: "dry")
|
||||
}
|
||||
else if(cmd.eventParameter[0] == 3)
|
||||
{
|
||||
map.value = "overflow"
|
||||
sendAlarm("waterOverflow")
|
||||
sendEvent(name: "water", value: "wet")
|
||||
}
|
||||
}
|
||||
}
|
||||
//log.debug "alarmV2: $cmd"
|
||||
|
||||
return map
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
|
||||
def map = [:]
|
||||
if(cmd.batteryLevel == 0xFF) {
|
||||
map.name = "battery"
|
||||
map.value = 1
|
||||
map.descriptionText = "${device.displayName} has a low battery"
|
||||
map.displayed = true
|
||||
} else {
|
||||
map.name = "battery"
|
||||
map.value = cmd.batteryLevel > 0 ? cmd.batteryLevel.toString() : 1
|
||||
map.unit = "%"
|
||||
map.displayed = false
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.Command cmd)
|
||||
{
|
||||
log.debug "COMMAND CLASS: $cmd"
|
||||
}
|
||||
|
||||
def sendDataToCloud(double data)
|
||||
{
|
||||
def params = [
|
||||
uri: "https://iot.swiftlet.technology",
|
||||
path: "/fortrezz/post.php",
|
||||
body: [
|
||||
id: device.id,
|
||||
value: data,
|
||||
email: registerEmail
|
||||
]
|
||||
]
|
||||
|
||||
//log.debug("POST parameters: ${params}")
|
||||
try {
|
||||
httpPostJson(params) { resp ->
|
||||
resp.headers.each {
|
||||
//log.debug "${it.name} : ${it.value}"
|
||||
}
|
||||
log.debug "sendDataToCloud query response: ${resp.data}"
|
||||
}
|
||||
} catch (e) {
|
||||
log.debug "something went wrong: $e"
|
||||
}
|
||||
}
|
||||
|
||||
def getTemperature(value) {
|
||||
if(location.temperatureScale == "C"){
|
||||
return value
|
||||
} else {
|
||||
return Math.round(celsiusToFahrenheit(value))
|
||||
}
|
||||
}
|
||||
|
||||
private getPictureName(category) {
|
||||
//def pictureUuid = device.id.toString().replaceAll('-', '')
|
||||
def pictureUuid = java.util.UUID.randomUUID().toString().replaceAll('-', '')
|
||||
|
||||
def name = "image" + "_$pictureUuid" + "_" + category + ".png"
|
||||
return name
|
||||
}
|
||||
|
||||
def api(method, args = [], success = {}) {
|
||||
def methods = [
|
||||
//"snapshot": [uri: "http://${ip}:${port}/snapshot.cgi${login()}&${args}", type: "post"],
|
||||
"24hrs": [uri: "https://iot.swiftlet.technology/fortrezz/chart.php?uuid=${device.id}&tz=${location.timeZone.ID}&type=1", type: "get"],
|
||||
"7days": [uri: "https://iot.swiftlet.technology/fortrezz/chart.php?uuid=${device.id}&tz=${location.timeZone.ID}&type=2", type: "get"],
|
||||
"4weeks": [uri: "https://iot.swiftlet.technology/fortrezz/chart.php?uuid=${device.id}&tz=${location.timeZone.ID}&type=3", type: "get"],
|
||||
]
|
||||
|
||||
def request = methods.getAt(method)
|
||||
|
||||
return doRequest(request.uri, request.type, success)
|
||||
}
|
||||
|
||||
private doRequest(uri, type, success) {
|
||||
log.debug(uri)
|
||||
|
||||
if(type == "post") {
|
||||
httpPost(uri , "", success)
|
||||
}
|
||||
|
||||
else if(type == "get") {
|
||||
httpGet(uri, success)
|
||||
}
|
||||
}
|
||||
|
||||
def sendAlarm(text)
|
||||
{
|
||||
sendEvent(name: "alarmState", value: text, descriptionText: text, displayed: false)
|
||||
}
|
||||
|
||||
def setThreshhold(rate)
|
||||
{
|
||||
log.debug "Setting Threshhold to ${rate}"
|
||||
|
||||
def event = createEvent(name: "lastThreshhold", value: rate, displayed: false)
|
||||
def cmds = []
|
||||
cmds << zwave.configurationV2.configurationSet(configurationValue: [(int)Math.round(rate*10)], parameterNumber: 5, size: 1).format()
|
||||
sendEvent(event)
|
||||
return response(cmds) // return a list containing the event and the result of response()
|
||||
}
|
||||
@@ -1,797 +0,0 @@
|
||||
/*****************************************************************************************************************
|
||||
* Copyright David Lomas (codersaur)
|
||||
*
|
||||
* Name: InfluxDB Logger
|
||||
*
|
||||
* Date: 2017-04-03
|
||||
*
|
||||
* Version: 1.11
|
||||
*
|
||||
* Source: https://github.com/codersaur/SmartThings/tree/master/smartapps/influxdb-logger
|
||||
*
|
||||
* Author: David Lomas (codersaur)
|
||||
*
|
||||
* Description: A SmartApp to log SmartThings device states to an InfluxDB database.
|
||||
*
|
||||
* For full information, including installation instructions, exmples, and version history, see:
|
||||
* https://github.com/codersaur/SmartThings/tree/master/smartapps/influxdb-logger
|
||||
*
|
||||
* IMPORTANT - To enable the resolution of groupNames (i.e. room names), you must manually insert the group IDs
|
||||
* into the getGroupName() command code at the end of this file.
|
||||
*
|
||||
* License:
|
||||
* 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.
|
||||
*****************************************************************************************************************/
|
||||
definition(
|
||||
name: "InfluxDB Logger",
|
||||
namespace: "codersaur",
|
||||
author: "David Lomas (codersaur)",
|
||||
description: "Log SmartThings device states to InfluxDB",
|
||||
category: "My Apps",
|
||||
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("General:") {
|
||||
//input "prefDebugMode", "bool", title: "Enable debug logging?", defaultValue: true, displayDuringSetup: true
|
||||
input (
|
||||
name: "configLoggingLevelIDE",
|
||||
title: "IDE Live Logging Level:\nMessages with this level and higher will be logged to the IDE.",
|
||||
type: "enum",
|
||||
options: [
|
||||
"0" : "None",
|
||||
"1" : "Error",
|
||||
"2" : "Warning",
|
||||
"3" : "Info",
|
||||
"4" : "Debug",
|
||||
"5" : "Trace"
|
||||
],
|
||||
defaultValue: "3",
|
||||
displayDuringSetup: true,
|
||||
required: false
|
||||
)
|
||||
}
|
||||
|
||||
section ("InfluxDB Database:") {
|
||||
input "prefDatabaseHost", "text", title: "Host", defaultValue: "10.10.10.10", required: true
|
||||
input "prefDatabasePort", "text", title: "Port", defaultValue: "8086", required: true
|
||||
input "prefDatabaseName", "text", title: "Database Name", defaultValue: "", required: true
|
||||
input "prefDatabaseUser", "text", title: "Username", required: false
|
||||
input "prefDatabasePass", "text", title: "Password", required: false
|
||||
}
|
||||
|
||||
section("Polling:") {
|
||||
input "prefSoftPollingInterval", "number", title:"Soft-Polling interval (minutes)", defaultValue: 10, required: true
|
||||
}
|
||||
|
||||
section("System Monitoring:") {
|
||||
input "prefLogModeEvents", "bool", title:"Log Mode Events?", defaultValue: true, required: true
|
||||
input "prefLogHubProperties", "bool", title:"Log Hub Properties?", defaultValue: true, required: true
|
||||
input "prefLogLocationProperties", "bool", title:"Log Location Properties?", defaultValue: true, required: true
|
||||
}
|
||||
|
||||
section("Devices To Monitor:") {
|
||||
input "accelerometers", "capability.accelerationSensor", title: "Accelerometers", multiple: true, required: false
|
||||
input "alarms", "capability.alarm", title: "Alarms", multiple: true, required: false
|
||||
input "batteries", "capability.battery", title: "Batteries", multiple: true, required: false
|
||||
input "beacons", "capability.beacon", title: "Beacons", multiple: true, required: false
|
||||
input "buttons", "capability.button", title: "Buttons", multiple: true, required: false
|
||||
input "cos", "capability.carbonMonoxideDetector", title: "Carbon Monoxide Detectors", multiple: true, required: false
|
||||
input "co2s", "capability.carbonDioxideMeasurement", title: "Carbon Dioxide Detectors", multiple: true, required: false
|
||||
input "colors", "capability.colorControl", title: "Color Controllers", multiple: true, required: false
|
||||
input "consumables", "capability.consumable", title: "Consumables", multiple: true, required: false
|
||||
input "contacts", "capability.contactSensor", title: "Contact Sensors", multiple: true, required: false
|
||||
input "doorsControllers", "capability.doorControl", title: "Door Controllers", multiple: true, required: false
|
||||
input "energyMeters", "capability.energyMeter", title: "Energy Meters", multiple: true, required: false
|
||||
input "humidities", "capability.relativeHumidityMeasurement", title: "Humidity Meters", multiple: true, required: false
|
||||
input "illuminances", "capability.illuminanceMeasurement", title: "Illuminance Meters", multiple: true, required: false
|
||||
input "locks", "capability.lock", title: "Locks", multiple: true, required: false
|
||||
input "motions", "capability.motionSensor", title: "Motion Sensors", multiple: true, required: false
|
||||
input "musicPlayers", "capability.musicPlayer", title: "Music Players", multiple: true, required: false
|
||||
input "peds", "capability.stepSensor", title: "Pedometers", multiple: true, required: false
|
||||
input "phMeters", "capability.pHMeasurement", title: "pH Meters", multiple: true, required: false
|
||||
input "powerMeters", "capability.powerMeter", title: "Power Meters", multiple: true, required: false
|
||||
input "presences", "capability.presenceSensor", title: "Presence Sensors", multiple: true, required: false
|
||||
input "pressures", "capability.sensor", title: "Pressure Sensors", multiple: true, required: false
|
||||
input "shockSensors", "capability.shockSensor", title: "Shock Sensors", multiple: true, required: false
|
||||
input "signalStrengthMeters", "capability.signalStrength", title: "Signal Strength Meters", multiple: true, required: false
|
||||
input "sleepSensors", "capability.sleepSensor", title: "Sleep Sensors", multiple: true, required: false
|
||||
input "smokeDetectors", "capability.smokeDetector", title: "Smoke Detectors", multiple: true, required: false
|
||||
input "soundSensors", "capability.soundSensor", title: "Sound Sensors", multiple: true, required: false
|
||||
input "spls", "capability.soundPressureLevel", title: "Sound Pressure Level Sensors", multiple: true, required: false
|
||||
input "switches", "capability.switch", title: "Switches", multiple: true, required: false
|
||||
input "switchLevels", "capability.switchLevel", title: "Switch Levels", multiple: true, required: false
|
||||
input "tamperAlerts", "capability.tamperAlert", title: "Tamper Alerts", multiple: true, required: false
|
||||
input "temperatures", "capability.temperatureMeasurement", title: "Temperature Sensors", multiple: true, required: false
|
||||
input "thermostats", "capability.thermostat", title: "Thermostats", multiple: true, required: false
|
||||
input "threeAxis", "capability.threeAxis", title: "Three-axis (Orientation) Sensors", multiple: true, required: false
|
||||
input "touchs", "capability.touchSensor", title: "Touch Sensors", multiple: true, required: false
|
||||
input "uvs", "capability.ultravioletIndex", title: "UV Sensors", multiple: true, required: false
|
||||
input "valves", "capability.valve", title: "Valves", multiple: true, required: false
|
||||
input "volts", "capability.voltageMeasurement", title: "Voltage Meters", multiple: true, required: false
|
||||
input "waterSensors", "capability.waterSensor", title: "Water Sensors", multiple: true, required: false
|
||||
input "windowShades", "capability.windowShade", title: "Window Shades", multiple: true, required: false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*****************************************************************************************************************
|
||||
* SmartThings System Commands:
|
||||
*****************************************************************************************************************/
|
||||
|
||||
/**
|
||||
* installed()
|
||||
*
|
||||
* Runs when the app is first installed.
|
||||
**/
|
||||
def installed() {
|
||||
state.installedAt = now()
|
||||
state.loggingLevelIDE = 5
|
||||
log.debug "${app.label}: Installed with settings: ${settings}"
|
||||
}
|
||||
|
||||
/**
|
||||
* uninstalled()
|
||||
*
|
||||
* Runs when the app is uninstalled.
|
||||
**/
|
||||
def uninstalled() {
|
||||
logger("uninstalled()","trace")
|
||||
}
|
||||
|
||||
/**
|
||||
* updated()
|
||||
*
|
||||
* Runs when app settings are changed.
|
||||
*
|
||||
* Updates device.state with input values and other hard-coded values.
|
||||
* Builds state.deviceAttributes which describes the attributes that will be monitored for each device collection
|
||||
* (used by manageSubscriptions() and softPoll()).
|
||||
* Refreshes scheduling and subscriptions.
|
||||
**/
|
||||
def updated() {
|
||||
logger("updated()","trace")
|
||||
|
||||
// Update internal state:
|
||||
state.loggingLevelIDE = (settings.configLoggingLevelIDE) ? settings.configLoggingLevelIDE.toInteger() : 3
|
||||
|
||||
// Database config:
|
||||
state.databaseHost = settings.prefDatabaseHost
|
||||
state.databasePort = settings.prefDatabasePort
|
||||
state.databaseName = settings.prefDatabaseName
|
||||
state.databaseUser = settings.prefDatabaseUser
|
||||
state.databasePass = settings.prefDatabasePass
|
||||
|
||||
state.path = "/write?db=${state.databaseName}"
|
||||
state.headers = [:]
|
||||
state.headers.put("HOST", "${state.databaseHost}:${state.databasePort}")
|
||||
state.headers.put("Content-Type", "application/x-www-form-urlencoded")
|
||||
if (state.databaseUser && state.databasePass) {
|
||||
state.headers.put("Authorization", encodeCredentialsBasic(state.databaseUser, state.databasePass))
|
||||
}
|
||||
|
||||
// Build array of device collections and the attributes we want to report on for that collection:
|
||||
// Note, the collection names are stored as strings. Adding references to the actual collection
|
||||
// objects causes major issues (possibly memory issues?).
|
||||
state.deviceAttributes = []
|
||||
state.deviceAttributes << [ devices: 'accelerometers', attributes: ['acceleration']]
|
||||
state.deviceAttributes << [ devices: 'alarms', attributes: ['alarm']]
|
||||
state.deviceAttributes << [ devices: 'batteries', attributes: ['battery']]
|
||||
state.deviceAttributes << [ devices: 'beacons', attributes: ['presence']]
|
||||
state.deviceAttributes << [ devices: 'buttons', attributes: ['button']]
|
||||
state.deviceAttributes << [ devices: 'cos', attributes: ['carbonMonoxide']]
|
||||
state.deviceAttributes << [ devices: 'co2s', attributes: ['carbonDioxide']]
|
||||
state.deviceAttributes << [ devices: 'colors', attributes: ['hue','saturation','color']]
|
||||
state.deviceAttributes << [ devices: 'consumables', attributes: ['consumableStatus']]
|
||||
state.deviceAttributes << [ devices: 'contacts', attributes: ['contact']]
|
||||
state.deviceAttributes << [ devices: 'doorsControllers', attributes: ['door']]
|
||||
state.deviceAttributes << [ devices: 'energyMeters', attributes: ['energy']]
|
||||
state.deviceAttributes << [ devices: 'humidities', attributes: ['humidity']]
|
||||
state.deviceAttributes << [ devices: 'illuminances', attributes: ['illuminance']]
|
||||
state.deviceAttributes << [ devices: 'locks', attributes: ['lock']]
|
||||
state.deviceAttributes << [ devices: 'motions', attributes: ['motion']]
|
||||
state.deviceAttributes << [ devices: 'musicPlayers', attributes: ['status','level','trackDescription','trackData','mute']]
|
||||
state.deviceAttributes << [ devices: 'peds', attributes: ['steps','goal']]
|
||||
state.deviceAttributes << [ devices: 'phMeters', attributes: ['pH']]
|
||||
state.deviceAttributes << [ devices: 'powerMeters', attributes: ['power','voltage','current','powerFactor']]
|
||||
state.deviceAttributes << [ devices: 'presences', attributes: ['presence']]
|
||||
state.deviceAttributes << [ devices: 'pressures', attributes: ['pressure']]
|
||||
state.deviceAttributes << [ devices: 'shockSensors', attributes: ['shock']]
|
||||
state.deviceAttributes << [ devices: 'signalStrengthMeters', attributes: ['lqi','rssi']]
|
||||
state.deviceAttributes << [ devices: 'sleepSensors', attributes: ['sleeping']]
|
||||
state.deviceAttributes << [ devices: 'smokeDetectors', attributes: ['smoke']]
|
||||
state.deviceAttributes << [ devices: 'soundSensors', attributes: ['sound']]
|
||||
state.deviceAttributes << [ devices: 'spls', attributes: ['soundPressureLevel']]
|
||||
state.deviceAttributes << [ devices: 'switches', attributes: ['switch']]
|
||||
state.deviceAttributes << [ devices: 'switchLevels', attributes: ['level']]
|
||||
state.deviceAttributes << [ devices: 'tamperAlerts', attributes: ['tamper']]
|
||||
state.deviceAttributes << [ devices: 'temperatures', attributes: ['temperature']]
|
||||
state.deviceAttributes << [ devices: 'thermostats', attributes: ['temperature','heatingSetpoint','coolingSetpoint','thermostatSetpoint','thermostatMode','thermostatFanMode','thermostatOperatingState','thermostatSetpointMode','scheduledSetpoint','optimisation','windowFunction']]
|
||||
state.deviceAttributes << [ devices: 'threeAxis', attributes: ['threeAxis']]
|
||||
state.deviceAttributes << [ devices: 'touchs', attributes: ['touch']]
|
||||
state.deviceAttributes << [ devices: 'uvs', attributes: ['ultravioletIndex']]
|
||||
state.deviceAttributes << [ devices: 'valves', attributes: ['contact']]
|
||||
state.deviceAttributes << [ devices: 'volts', attributes: ['voltage']]
|
||||
state.deviceAttributes << [ devices: 'waterSensors', attributes: ['water']]
|
||||
state.deviceAttributes << [ devices: 'windowShades', attributes: ['windowShade']]
|
||||
|
||||
// Configure Scheduling:
|
||||
state.softPollingInterval = settings.prefSoftPollingInterval.toInteger()
|
||||
manageSchedules()
|
||||
|
||||
// Configure Subscriptions:
|
||||
manageSubscriptions()
|
||||
}
|
||||
|
||||
/*****************************************************************************************************************
|
||||
* Event Handlers:
|
||||
*****************************************************************************************************************/
|
||||
|
||||
/**
|
||||
* handleAppTouch(evt)
|
||||
*
|
||||
* Used for testing.
|
||||
**/
|
||||
def handleAppTouch(evt) {
|
||||
logger("handleAppTouch()","trace")
|
||||
|
||||
softPoll()
|
||||
}
|
||||
|
||||
/**
|
||||
* handleModeEvent(evt)
|
||||
*
|
||||
* Log Mode changes.
|
||||
**/
|
||||
def handleModeEvent(evt) {
|
||||
logger("handleModeEvent(): Mode changed to: ${evt.value}","info")
|
||||
|
||||
def locationId = escapeStringForInfluxDB(location.id)
|
||||
def locationName = escapeStringForInfluxDB(location.name)
|
||||
def mode = '"' + escapeStringForInfluxDB(evt.value) + '"'
|
||||
def data = "_stMode,locationId=${locationId},locationName=${locationName} mode=${mode}"
|
||||
postToInfluxDB(data)
|
||||
}
|
||||
|
||||
/**
|
||||
* handleEvent(evt)
|
||||
*
|
||||
* Builds data to send to InfluxDB.
|
||||
* - Escapes and quotes string values.
|
||||
* - Calculates logical binary values where string values can be
|
||||
* represented as binary values (e.g. contact: closed = 1, open = 0)
|
||||
*
|
||||
* Useful references:
|
||||
* - http://docs.smartthings.com/en/latest/capabilities-reference.html
|
||||
* - https://docs.influxdata.com/influxdb/v0.10/guides/writing_data/
|
||||
**/
|
||||
def handleEvent(evt) {
|
||||
logger("handleEvent(): $evt.displayName($evt.name:$evt.unit) $evt.value","info")
|
||||
|
||||
// Build data string to send to InfluxDB:
|
||||
// Format: <measurement>[,<tag_name>=<tag_value>] field=<field_value>
|
||||
// If value is an integer, it must have a trailing "i"
|
||||
// If value is a string, it must be enclosed in double quotes.
|
||||
def measurement = evt.name
|
||||
// tags:
|
||||
def deviceId = escapeStringForInfluxDB(evt.deviceId)
|
||||
def deviceName = escapeStringForInfluxDB(evt.displayName)
|
||||
def groupId = escapeStringForInfluxDB(evt?.device.device.groupId)
|
||||
def groupName = escapeStringForInfluxDB(getGroupName(evt?.device.device.groupId))
|
||||
def hubId = escapeStringForInfluxDB(evt?.device.device.hubId)
|
||||
def hubName = escapeStringForInfluxDB(evt?.device.device.hub.toString())
|
||||
// Don't pull these from the evt.device as the app itself will be associated with one location.
|
||||
def locationId = escapeStringForInfluxDB(location.id)
|
||||
def locationName = escapeStringForInfluxDB(location.name)
|
||||
|
||||
def unit = escapeStringForInfluxDB(evt.unit)
|
||||
def value = escapeStringForInfluxDB(evt.value)
|
||||
def valueBinary = ''
|
||||
|
||||
def data = "${measurement},deviceId=${deviceId},deviceName=${deviceName},groupId=${groupId},groupName=${groupName},hubId=${hubId},hubName=${hubName},locationId=${locationId},locationName=${locationName}"
|
||||
|
||||
// Unit tag and fields depend on the event type:
|
||||
// Most string-valued attributes can be translated to a binary value too.
|
||||
if ('acceleration' == evt.name) { // acceleration: Calculate a binary value (active = 1, inactive = 0)
|
||||
unit = 'acceleration'
|
||||
value = '"' + value + '"'
|
||||
valueBinary = ('active' == evt.value) ? '1i' : '0i'
|
||||
data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
|
||||
}
|
||||
else if ('alarm' == evt.name) { // alarm: Calculate a binary value (strobe/siren/both = 1, off = 0)
|
||||
unit = 'alarm'
|
||||
value = '"' + value + '"'
|
||||
valueBinary = ('off' == evt.value) ? '0i' : '1i'
|
||||
data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
|
||||
}
|
||||
else if ('button' == evt.name) { // button: Calculate a binary value (held = 1, pushed = 0)
|
||||
unit = 'button'
|
||||
value = '"' + value + '"'
|
||||
valueBinary = ('pushed' == evt.value) ? '0i' : '1i'
|
||||
data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
|
||||
}
|
||||
else if ('carbonMonoxide' == evt.name) { // carbonMonoxide: Calculate a binary value (detected = 1, clear/tested = 0)
|
||||
unit = 'carbonMonoxide'
|
||||
value = '"' + value + '"'
|
||||
valueBinary = ('detected' == evt.value) ? '1i' : '0i'
|
||||
data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
|
||||
}
|
||||
else if ('consumableStatus' == evt.name) { // consumableStatus: Calculate a binary value ("good" = 1, "missing"/"replace"/"maintenance_required"/"order" = 0)
|
||||
unit = 'consumableStatus'
|
||||
value = '"' + value + '"'
|
||||
valueBinary = ('good' == evt.value) ? '1i' : '0i'
|
||||
data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
|
||||
}
|
||||
else if ('contact' == evt.name) { // contact: Calculate a binary value (closed = 1, open = 0)
|
||||
unit = 'contact'
|
||||
value = '"' + value + '"'
|
||||
valueBinary = ('closed' == evt.value) ? '1i' : '0i'
|
||||
data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
|
||||
}
|
||||
else if ('door' == evt.name) { // door: Calculate a binary value (closed = 1, open/opening/closing/unknown = 0)
|
||||
unit = 'door'
|
||||
value = '"' + value + '"'
|
||||
valueBinary = ('closed' == evt.value) ? '1i' : '0i'
|
||||
data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
|
||||
}
|
||||
else if ('lock' == evt.name) { // door: Calculate a binary value (locked = 1, unlocked = 0)
|
||||
unit = 'lock'
|
||||
value = '"' + value + '"'
|
||||
valueBinary = ('locked' == evt.value) ? '1i' : '0i'
|
||||
data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
|
||||
}
|
||||
else if ('motion' == evt.name) { // Motion: Calculate a binary value (active = 1, inactive = 0)
|
||||
unit = 'motion'
|
||||
value = '"' + value + '"'
|
||||
valueBinary = ('active' == evt.value) ? '1i' : '0i'
|
||||
data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
|
||||
}
|
||||
else if ('mute' == evt.name) { // mute: Calculate a binary value (muted = 1, unmuted = 0)
|
||||
unit = 'mute'
|
||||
value = '"' + value + '"'
|
||||
valueBinary = ('muted' == evt.value) ? '1i' : '0i'
|
||||
data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
|
||||
}
|
||||
else if ('presence' == evt.name) { // presence: Calculate a binary value (present = 1, not present = 0)
|
||||
unit = 'presence'
|
||||
value = '"' + value + '"'
|
||||
valueBinary = ('present' == evt.value) ? '1i' : '0i'
|
||||
data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
|
||||
}
|
||||
else if ('shock' == evt.name) { // shock: Calculate a binary value (detected = 1, clear = 0)
|
||||
unit = 'shock'
|
||||
value = '"' + value + '"'
|
||||
valueBinary = ('detected' == evt.value) ? '1i' : '0i'
|
||||
data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
|
||||
}
|
||||
else if ('sleeping' == evt.name) { // sleeping: Calculate a binary value (sleeping = 1, not sleeping = 0)
|
||||
unit = 'sleeping'
|
||||
value = '"' + value + '"'
|
||||
valueBinary = ('sleeping' == evt.value) ? '1i' : '0i'
|
||||
data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
|
||||
}
|
||||
else if ('smoke' == evt.name) { // smoke: Calculate a binary value (detected = 1, clear/tested = 0)
|
||||
unit = 'smoke'
|
||||
value = '"' + value + '"'
|
||||
valueBinary = ('detected' == evt.value) ? '1i' : '0i'
|
||||
data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
|
||||
}
|
||||
else if ('sound' == evt.name) { // sound: Calculate a binary value (detected = 1, not detected = 0)
|
||||
unit = 'sound'
|
||||
value = '"' + value + '"'
|
||||
valueBinary = ('detected' == evt.value) ? '1i' : '0i'
|
||||
data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
|
||||
}
|
||||
else if ('switch' == evt.name) { // switch: Calculate a binary value (on = 1, off = 0)
|
||||
unit = 'switch'
|
||||
value = '"' + value + '"'
|
||||
valueBinary = ('on' == evt.value) ? '1i' : '0i'
|
||||
data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
|
||||
}
|
||||
else if ('tamper' == evt.name) { // tamper: Calculate a binary value (detected = 1, clear = 0)
|
||||
unit = 'tamper'
|
||||
value = '"' + value + '"'
|
||||
valueBinary = ('detected' == evt.value) ? '1i' : '0i'
|
||||
data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
|
||||
}
|
||||
else if ('thermostatMode' == evt.name) { // thermostatMode: Calculate a binary value (<any other value> = 1, off = 0)
|
||||
unit = 'thermostatMode'
|
||||
value = '"' + value + '"'
|
||||
valueBinary = ('off' == evt.value) ? '0i' : '1i'
|
||||
data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
|
||||
}
|
||||
else if ('thermostatFanMode' == evt.name) { // thermostatFanMode: Calculate a binary value (<any other value> = 1, off = 0)
|
||||
unit = 'thermostatFanMode'
|
||||
value = '"' + value + '"'
|
||||
valueBinary = ('off' == evt.value) ? '0i' : '1i'
|
||||
data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
|
||||
}
|
||||
else if ('thermostatOperatingState' == evt.name) { // thermostatOperatingState: Calculate a binary value (heating = 1, <any other value> = 0)
|
||||
unit = 'thermostatOperatingState'
|
||||
value = '"' + value + '"'
|
||||
valueBinary = ('heating' == evt.value) ? '1i' : '0i'
|
||||
data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
|
||||
}
|
||||
else if ('thermostatSetpointMode' == evt.name) { // thermostatSetpointMode: Calculate a binary value (followSchedule = 0, <any other value> = 1)
|
||||
unit = 'thermostatSetpointMode'
|
||||
value = '"' + value + '"'
|
||||
valueBinary = ('followSchedule' == evt.value) ? '0i' : '1i'
|
||||
data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
|
||||
}
|
||||
else if ('threeAxis' == evt.name) { // threeAxis: Format to x,y,z values.
|
||||
unit = 'threeAxis'
|
||||
def valueXYZ = evt.value.split(",")
|
||||
def valueX = valueXYZ[0]
|
||||
def valueY = valueXYZ[1]
|
||||
def valueZ = valueXYZ[2]
|
||||
data += ",unit=${unit} valueX=${valueX}i,valueY=${valueY}i,valueZ=${valueZ}i" // values are integers.
|
||||
}
|
||||
else if ('touch' == evt.name) { // touch: Calculate a binary value (touched = 1, "" = 0)
|
||||
unit = 'touch'
|
||||
value = '"' + value + '"'
|
||||
valueBinary = ('touched' == evt.value) ? '1i' : '0i'
|
||||
data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
|
||||
}
|
||||
else if ('optimisation' == evt.name) { // optimisation: Calculate a binary value (active = 1, inactive = 0)
|
||||
unit = 'optimisation'
|
||||
value = '"' + value + '"'
|
||||
valueBinary = ('active' == evt.value) ? '1i' : '0i'
|
||||
data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
|
||||
}
|
||||
else if ('windowFunction' == evt.name) { // windowFunction: Calculate a binary value (active = 1, inactive = 0)
|
||||
unit = 'windowFunction'
|
||||
value = '"' + value + '"'
|
||||
valueBinary = ('active' == evt.value) ? '1i' : '0i'
|
||||
data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
|
||||
}
|
||||
else if ('touch' == evt.name) { // touch: Calculate a binary value (touched = 1, <any other value> = 0)
|
||||
unit = 'touch'
|
||||
value = '"' + value + '"'
|
||||
valueBinary = ('touched' == evt.value) ? '1i' : '0i'
|
||||
data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
|
||||
}
|
||||
else if ('water' == evt.name) { // water: Calculate a binary value (wet = 1, dry = 0)
|
||||
unit = 'water'
|
||||
value = '"' + value + '"'
|
||||
valueBinary = ('wet' == evt.value) ? '1i' : '0i'
|
||||
data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
|
||||
}
|
||||
else if ('windowShade' == evt.name) { // windowShade: Calculate a binary value (closed = 1, <any other value> = 0)
|
||||
unit = 'windowShade'
|
||||
value = '"' + value + '"'
|
||||
valueBinary = ('closed' == evt.value) ? '1i' : '0i'
|
||||
data += ",unit=${unit} value=${value},valueBinary=${valueBinary}"
|
||||
}
|
||||
// Catch any other event with a string value that hasn't been handled:
|
||||
else if (evt.value ==~ /.*[^0-9\.,-].*/) { // match if any characters are not digits, period, comma, or hyphen.
|
||||
logger("handleEvent(): Found a string value that's not explicitly handled: Device Name: ${deviceName}, Event Name: ${evt.name}, Value: ${evt.value}","warn")
|
||||
value = '"' + value + '"'
|
||||
data += ",unit=${unit} value=${value}"
|
||||
}
|
||||
// Catch any other general numerical event (carbonDioxide, power, energy, humidity, level, temperature, ultravioletIndex, voltage, etc).
|
||||
else {
|
||||
data += ",unit=${unit} value=${value}"
|
||||
}
|
||||
|
||||
// Post data to InfluxDB:
|
||||
postToInfluxDB(data)
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*****************************************************************************************************************
|
||||
* Main Commands:
|
||||
*****************************************************************************************************************/
|
||||
|
||||
/**
|
||||
* softPoll()
|
||||
*
|
||||
* Executed by schedule.
|
||||
*
|
||||
* Forces data to be posted to InfluxDB (even if an event has not been triggered).
|
||||
* Doesn't poll devices, just builds a fake event to pass to handleEvent().
|
||||
*
|
||||
* Also calls LogSystemProperties().
|
||||
**/
|
||||
def softPoll() {
|
||||
logger("softPoll()","trace")
|
||||
|
||||
logSystemProperties()
|
||||
|
||||
// Iterate over each attribute for each device, in each device collection in deviceAttributes:
|
||||
def devs // temp variable to hold device collection.
|
||||
state.deviceAttributes.each { da ->
|
||||
devs = settings."${da.devices}"
|
||||
if (devs && (da.attributes)) {
|
||||
devs.each { d ->
|
||||
da.attributes.each { attr ->
|
||||
if (d.hasAttribute(attr) && d.latestState(attr)?.value != null) {
|
||||
logger("softPoll(): Softpolling device ${d} for attribute: ${attr}","info")
|
||||
// Send fake event to handleEvent():
|
||||
handleEvent([
|
||||
name: attr,
|
||||
value: d.latestState(attr)?.value,
|
||||
unit: d.latestState(attr)?.unit,
|
||||
device: d,
|
||||
deviceId: d.id,
|
||||
displayName: d.displayName
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* logSystemProperties()
|
||||
*
|
||||
* Generates measurements for SmartThings system (hubs and locations) properties.
|
||||
**/
|
||||
def logSystemProperties() {
|
||||
logger("logSystemProperties()","trace")
|
||||
|
||||
def locationId = '"' + escapeStringForInfluxDB(location.id) + '"'
|
||||
def locationName = '"' + escapeStringForInfluxDB(location.name) + '"'
|
||||
|
||||
// Location Properties:
|
||||
if (prefLogLocationProperties) {
|
||||
try {
|
||||
def tz = '"' + escapeStringForInfluxDB(location.timeZone.ID) + '"'
|
||||
def mode = '"' + escapeStringForInfluxDB(location.mode) + '"'
|
||||
def hubCount = location.hubs.size()
|
||||
def times = getSunriseAndSunset()
|
||||
def srt = '"' + times.sunrise.format("HH:mm", location.timeZone) + '"'
|
||||
def sst = '"' + times.sunset.format("HH:mm", location.timeZone) + '"'
|
||||
|
||||
def data = "_stLocation,locationId=${locationId},locationName=${locationName},latitude=${location.latitude},longitude=${location.longitude},timeZone=${tz} mode=${mode},hubCount=${hubCount}i,sunriseTime=${srt},sunsetTime=${sst}"
|
||||
postToInfluxDB(data)
|
||||
} catch (e) {
|
||||
logger("logSystemProperties(): Unable to log Location properties: ${e}","error")
|
||||
}
|
||||
}
|
||||
|
||||
// Hub Properties:
|
||||
if (prefLogHubProperties) {
|
||||
location.hubs.each { h ->
|
||||
try {
|
||||
def hubId = '"' + escapeStringForInfluxDB(h.id) + '"'
|
||||
def hubName = '"' + escapeStringForInfluxDB(h.name) + '"'
|
||||
def hubIP = '"' + escapeStringForInfluxDB(h.localIP) + '"'
|
||||
def hubStatus = '"' + escapeStringForInfluxDB(h.status) + '"'
|
||||
def batteryInUse = ("false" == h.hub.getDataValue("batteryInUse")) ? "0i" : "1i"
|
||||
def hubUptime = h.hub.getDataValue("uptime") + 'i'
|
||||
def zigbeePowerLevel = h.hub.getDataValue("zigbeePowerLevel") + 'i'
|
||||
def zwavePowerLevel = '"' + escapeStringForInfluxDB(h.hub.getDataValue("zwavePowerLevel")) + '"'
|
||||
def firmwareVersion = '"' + escapeStringForInfluxDB(h.firmwareVersionString) + '"'
|
||||
|
||||
def data = "_stHub,locationId=${locationId},locationName=${locationName},hubId=${hubId},hubName=${hubName},hubIP=${hubIP} "
|
||||
data += "status=${hubStatus},batteryInUse=${batteryInUse},uptime=${hubUptime},zigbeePowerLevel=${zigbeePowerLevel},zwavePowerLevel=${zwavePowerLevel},firmwareVersion=${firmwareVersion}"
|
||||
postToInfluxDB(data)
|
||||
} catch (e) {
|
||||
logger("logSystemProperties(): Unable to log Hub properties: ${e}","error")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* postToInfluxDB()
|
||||
*
|
||||
* Posts data to InfluxDB.
|
||||
*
|
||||
* Uses hubAction instead of httpPost() in case InfluxDB server is on the same LAN as the Smartthings Hub.
|
||||
**/
|
||||
def postToInfluxDB(data) {
|
||||
logger("postToInfluxDB(): Posting data to InfluxDB: Host: ${state.databaseHost}, Port: ${state.databasePort}, Database: ${state.databaseName}, Data: [${data}]","debug")
|
||||
|
||||
try {
|
||||
def hubAction = new physicalgraph.device.HubAction(
|
||||
[
|
||||
method: "POST",
|
||||
path: state.path,
|
||||
body: data,
|
||||
headers: state.headers
|
||||
],
|
||||
null,
|
||||
[ callback: handleInfluxResponse ]
|
||||
)
|
||||
|
||||
sendHubCommand(hubAction)
|
||||
}
|
||||
catch (Exception e) {
|
||||
logger("postToInfluxDB(): Exception ${e} on ${hubAction}","error")
|
||||
}
|
||||
|
||||
// For reference, code that could be used for WAN hosts:
|
||||
// def url = "http://${state.databaseHost}:${state.databasePort}/write?db=${state.databaseName}"
|
||||
// try {
|
||||
// httpPost(url, data) { response ->
|
||||
// if (response.status != 999 ) {
|
||||
// log.debug "Response Status: ${response.status}"
|
||||
// log.debug "Response data: ${response.data}"
|
||||
// log.debug "Response contentType: ${response.contentType}"
|
||||
// }
|
||||
// }
|
||||
// } catch (e) {
|
||||
// logger("postToInfluxDB(): Something went wrong when posting: ${e}","error")
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* handleInfluxResponse()
|
||||
*
|
||||
* Handles response from post made in postToInfluxDB().
|
||||
**/
|
||||
def handleInfluxResponse(physicalgraph.device.HubResponse hubResponse) {
|
||||
if(hubResponse.status >= 400) {
|
||||
logger("postToInfluxDB(): Something went wrong! Response from InfluxDB: Headers: ${hubResponse.headers}, Body: ${hubResponse.body}","error")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*****************************************************************************************************************
|
||||
* Private Helper Functions:
|
||||
*****************************************************************************************************************/
|
||||
|
||||
/**
|
||||
* manageSchedules()
|
||||
*
|
||||
* Configures/restarts scheduled tasks:
|
||||
* softPoll() - Run every {state.softPollingInterval} minutes.
|
||||
**/
|
||||
private manageSchedules() {
|
||||
logger("manageSchedules()","trace")
|
||||
|
||||
// Generate a random offset (1-60):
|
||||
Random rand = new Random(now())
|
||||
def randomOffset = 0
|
||||
|
||||
// softPoll:
|
||||
try {
|
||||
unschedule(softPoll)
|
||||
}
|
||||
catch(e) {
|
||||
// logger("manageSchedules(): Unschedule failed!","error")
|
||||
}
|
||||
|
||||
if (state.softPollingInterval > 0) {
|
||||
randomOffset = rand.nextInt(60)
|
||||
logger("manageSchedules(): Scheduling softpoll to run every ${state.softPollingInterval} minutes (offset of ${randomOffset} seconds).","trace")
|
||||
schedule("${randomOffset} 0/${state.softPollingInterval} * * * ?", "softPoll")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* manageSubscriptions()
|
||||
*
|
||||
* Configures subscriptions.
|
||||
**/
|
||||
private manageSubscriptions() {
|
||||
logger("manageSubscriptions()","trace")
|
||||
|
||||
// Unsubscribe:
|
||||
unsubscribe()
|
||||
|
||||
// Subscribe to App Touch events:
|
||||
subscribe(app,handleAppTouch)
|
||||
|
||||
// Subscribe to mode events:
|
||||
if (prefLogModeEvents) subscribe(location, "mode", handleModeEvent)
|
||||
|
||||
// Subscribe to device attributes (iterate over each attribute for each device collection in state.deviceAttributes):
|
||||
def devs // dynamic variable holding device collection.
|
||||
state.deviceAttributes.each { da ->
|
||||
devs = settings."${da.devices}"
|
||||
if (devs && (da.attributes)) {
|
||||
da.attributes.each { attr ->
|
||||
logger("manageSubscriptions(): Subscribing to attribute: ${attr}, for devices: ${da.devices}","info")
|
||||
// There is no need to check if all devices in the collection have the attribute.
|
||||
subscribe(devs, attr, handleEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* logger()
|
||||
*
|
||||
* Wrapper function for all logging.
|
||||
**/
|
||||
private logger(msg, level = "debug") {
|
||||
|
||||
switch(level) {
|
||||
case "error":
|
||||
if (state.loggingLevelIDE >= 1) log.error msg
|
||||
break
|
||||
|
||||
case "warn":
|
||||
if (state.loggingLevelIDE >= 2) log.warn msg
|
||||
break
|
||||
|
||||
case "info":
|
||||
if (state.loggingLevelIDE >= 3) log.info msg
|
||||
break
|
||||
|
||||
case "debug":
|
||||
if (state.loggingLevelIDE >= 4) log.debug msg
|
||||
break
|
||||
|
||||
case "trace":
|
||||
if (state.loggingLevelIDE >= 5) log.trace msg
|
||||
break
|
||||
|
||||
default:
|
||||
log.debug msg
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* encodeCredentialsBasic()
|
||||
*
|
||||
* Encode credentials for HTTP Basic authentication.
|
||||
**/
|
||||
private encodeCredentialsBasic(username, password) {
|
||||
return "Basic " + "${username}:${password}".encodeAsBase64().toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* escapeStringForInfluxDB()
|
||||
*
|
||||
* Escape values to InfluxDB.
|
||||
*
|
||||
* If a tag key, tag value, or field key contains a space, comma, or an equals sign = it must
|
||||
* be escaped using the backslash character \. Backslash characters do not need to be escaped.
|
||||
* Commas and spaces will also need to be escaped for measurements, though equals signs = do not.
|
||||
*
|
||||
* Further info: https://docs.influxdata.com/influxdb/v0.10/write_protocols/write_syntax/
|
||||
**/
|
||||
private escapeStringForInfluxDB(str) {
|
||||
if (str) {
|
||||
str = str.replaceAll(" ", "\\\\ ") // Escape spaces.
|
||||
str = str.replaceAll(",", "\\\\,") // Escape commas.
|
||||
str = str.replaceAll("=", "\\\\=") // Escape equal signs.
|
||||
str = str.replaceAll("\"", "\\\\\"") // Escape double quotes.
|
||||
//str = str.replaceAll("'", "_") // Replace apostrophes with underscores.
|
||||
}
|
||||
else {
|
||||
str = 'null'
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
/**
|
||||
* getGroupName()
|
||||
*
|
||||
* Get the name of a 'Group' (i.e. Room) from its ID.
|
||||
*
|
||||
* This is done manually as there does not appear to be a way to enumerate
|
||||
* groups from a SmartApp currently.
|
||||
*
|
||||
* GroupIds can be obtained from the SmartThings IDE under 'My Locations'.
|
||||
*
|
||||
* See: https://community.smartthings.com/t/accessing-group-within-a-smartapp/6830
|
||||
**/
|
||||
private getGroupName(id) {
|
||||
|
||||
if (id == null) {return 'Home'}
|
||||
else if (id == 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX') {return 'Kitchen'}
|
||||
else if (id == 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX') {return 'Lounge'}
|
||||
else if (id == 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX') {return 'Office'}
|
||||
else {return 'Unknown'}
|
||||
}
|
||||
Reference in New Issue
Block a user