mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-09 13:21:53 +00:00
Compare commits
1 Commits
MSA-2122-1
...
MSA-2113-3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7515fab78 |
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* FortrezZ MIMO2+ B-Side
|
||||
*
|
||||
* 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 MIMO2+ B-Side", namespace: "fortrezz", author: "FortrezZ, LLC") {
|
||||
capability "Contact Sensor"
|
||||
capability "Relay Switch"
|
||||
capability "Switch"
|
||||
capability "Voltage Measurement"
|
||||
capability "Refresh"
|
||||
}
|
||||
|
||||
tiles {
|
||||
standardTile("switch", "device.switch", width: 2, height: 2) {
|
||||
state "on", label: "Relay 2 On", action: "off", icon: "http://swiftlet.technology/wp-content/uploads/2016/06/Switch-On-104-edit.png", backgroundColor: "#53a7c0"
|
||||
state "off", label: "Relay 2 Off", action: "on", icon: "http://swiftlet.technology/wp-content/uploads/2016/06/Switch-Off-104-edit.png", backgroundColor: "#ffffff"
|
||||
}
|
||||
standardTile("anaDig1", "device.anaDig1", inactiveLabel: false) {
|
||||
state "open", label: '${name}', icon: "st.contact.contact.open", backgroundColor: "#ffa81e"
|
||||
state "closed", label: '${name}', icon: "st.contact.contact.closed", backgroundColor: "#79b821"
|
||||
state "val", label:'${currentValue}v', unit:"", defaultState: true
|
||||
}
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
standardTile("powered", "device.powered", inactiveLabel: false) {
|
||||
state "powerOn", label: "Power On", icon: "st.switches.switch.on", backgroundColor: "#79b821"
|
||||
state "powerOff", label: "Power Off", icon: "st.switches.switch.off", backgroundColor: "#ffa81e"
|
||||
}
|
||||
standardTile("configure", "device.configure", inactiveLabel: false, decoration: "flat") {
|
||||
state "configure", label:'', action:"configuration.configure", icon:"st.secondary.configure"
|
||||
}
|
||||
standardTile("blank", "device.blank", inactiveLabel: true, decoration: "flat") {
|
||||
state("blank", label: '')
|
||||
}
|
||||
main (["switch"])
|
||||
details(["switch", "anaDig1", "blank", "blank", "refresh", "powered"])
|
||||
}
|
||||
}
|
||||
|
||||
// parse events into attributes
|
||||
def parse(String description) {
|
||||
log.debug "Parsing '${description}'"
|
||||
// TODO: handle 'contact' attribute
|
||||
// TODO: handle 'switch' attribute
|
||||
// TODO: handle 'switch' attribute
|
||||
// TODO: handle 'voltage' attribute
|
||||
|
||||
}
|
||||
|
||||
def eventParse(evt) {
|
||||
log.debug("Event: ${evt.name}=${evt.value}")
|
||||
switch(evt.name) {
|
||||
case "powered":
|
||||
sendEvent(name: evt.name, value: evt.value)
|
||||
break
|
||||
case "switch2":
|
||||
sendEvent(name: "switch", value: evt.value)
|
||||
break
|
||||
case "contact2":
|
||||
sendEvent(name: "contact", value: evt.value)
|
||||
break
|
||||
case "voltage2":
|
||||
sendEvent(name: "voltage", value: evt.value)
|
||||
break
|
||||
case "relay2":
|
||||
sendEvent(name: evt.name, value: evt.value)
|
||||
break
|
||||
case "anaDig2":
|
||||
sendEvent(name: "anaDig1", value: evt.value)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// handle commands
|
||||
def on() {
|
||||
parent.on2(device.id)
|
||||
log.debug("Executing 'on'")
|
||||
// TODO: Send Event to parent device for "on2"
|
||||
}
|
||||
|
||||
def off() {
|
||||
parent.off2(device.id)
|
||||
log.debug("Executing 'off'")
|
||||
// TODO: Send Event to parent device for "off2"
|
||||
}
|
||||
def refresh() {
|
||||
parent.refresh2(device.id)
|
||||
log.debug("Executing 'refresh'")
|
||||
}
|
||||
379
devicetypes/fortrezz/fortrezz-mimo2.src/fortrezz-mimo2.groovy
Normal file
379
devicetypes/fortrezz/fortrezz-mimo2.src/fortrezz-mimo2.groovy
Normal file
@@ -0,0 +1,379 @@
|
||||
/**
|
||||
* MIMO2 Device Handler
|
||||
*
|
||||
* 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 MIMO2+", namespace: "fortrezz", author: "FortrezZ, LLC") {
|
||||
capability "Alarm"
|
||||
capability "Contact Sensor"
|
||||
capability "Switch"
|
||||
capability "Voltage Measurement"
|
||||
capability "Configuration"
|
||||
capability "Refresh"
|
||||
|
||||
attribute "powered", "string"
|
||||
attribute "relay", "string"
|
||||
|
||||
attribute "relay2", "string"
|
||||
attribute "contact2", "string"
|
||||
attribute "voltage2", "string"
|
||||
|
||||
command "on"
|
||||
command "off"
|
||||
command "on2"
|
||||
command "off2"
|
||||
|
||||
fingerprint deviceId: "0x2100", inClusters: "0x5E,0x86,0x72,0x5A,0x59,0x71,0x98,0x7A"
|
||||
}
|
||||
|
||||
preferences {
|
||||
|
||||
input ("RelaySwitchDelay", "decimal", title: "Delay between relay switch on and off in seconds. Only Numbers 0 to 3 allowed. 0 value will remove delay and allow relay to function as a standard switch:\nRelay 1", description: "Numbers 0 to 3.1 allowed.", defaultValue: 0, required: false, displayDuringSetup: true)
|
||||
input ("RelaySwitchDelay2", "decimal", title: "Relay 2", description: "Numbers 0 to 3.1 allowed.", defaultValue: 0, required: false, displayDuringSetup: true)
|
||||
input ("Sig1AD", "bool", title: "Switch off for digital, on for analog:\nSIG1", required: false, displayDuringSetup: true)
|
||||
input ("Sig2AD", "bool", title: "SIG2", required: false, displayDuringSetup: true)
|
||||
} // the range would be 0 to 3.1, but the range value would not accept 3.1, only whole numbers (i tried paranthesis and fractions too. :( )
|
||||
|
||||
|
||||
tiles {
|
||||
standardTile("switch", "device.switch", width: 2, height: 2) {
|
||||
state "on", label: "Relay 1 On", action: "off", icon: "http://swiftlet.technology/wp-content/uploads/2016/06/Switch-On-104-edit.png", backgroundColor: "#53a7c0"
|
||||
state "off", label: "Relay 1 Off", action: "on", icon: "http://swiftlet.technology/wp-content/uploads/2016/06/Switch-Off-104-edit.png", backgroundColor: "#ffffff"
|
||||
|
||||
}
|
||||
standardTile("switch2", "device.switch2", width: 2, height: 2, inactiveLabel: false) {
|
||||
state "on", label: "Relay 2 On", action: "off2", icon: "http://swiftlet.technology/wp-content/uploads/2016/06/Switch-On-104-edit.png", backgroundColor: "#53a7c0"
|
||||
state "off", label: 'Relay 2 Off', action: "on2", icon: "http://swiftlet.technology/wp-content/uploads/2016/06/Switch-Off-104-edit.png", backgroundColor: "#ffffff"
|
||||
}
|
||||
standardTile("anaDig1", "device.anaDig1", inactiveLabel: false) {
|
||||
state "open", label: '${name}', icon: "st.contact.contact.open", backgroundColor: "#ffa81e"
|
||||
state "closed", label: '${name}', icon: "st.contact.contact.closed", backgroundColor: "#79b821"
|
||||
state "val", label:'${currentValue}v', unit:"", defaultState: true
|
||||
}
|
||||
standardTile("anaDig2", "device.anaDig2", inactiveLabel: false) {
|
||||
state "open", label: '${name}', icon: "st.contact.contact.open", backgroundColor: "#ffa81e"
|
||||
state "closed", label: '${name}', icon: "st.contact.contact.closed", backgroundColor: "#79b821"
|
||||
state "val", label:'${currentValue}v', unit:"", defaultState: true
|
||||
}
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
standardTile("powered", "device.powered", inactiveLabel: false) {
|
||||
state "powerOn", label: "Power On", icon: "st.switches.switch.on", backgroundColor: "#79b821"
|
||||
state "powerOff", label: "Power Off", icon: "st.switches.switch.off", backgroundColor: "#ffa81e"
|
||||
}
|
||||
standardTile("configure", "device.configure", inactiveLabel: false, decoration: "flat") {
|
||||
state "configure", label:'', action:"configuration.configure", icon:"st.secondary.configure"
|
||||
}
|
||||
standardTile("blank", "device.blank", inactiveLabel: true, decoration: "flat") {
|
||||
state("blank", label: '')
|
||||
}
|
||||
main (["switch"])
|
||||
details(["switch", "anaDig1", "blank", "switch2", "anaDig2", "blank", "configure", "refresh", "powered"])
|
||||
}
|
||||
}
|
||||
|
||||
// parse events into attributes
|
||||
def parse(String description) {
|
||||
def result = null
|
||||
def cmd = zwave.parse(description)
|
||||
|
||||
if (cmd.CMD == "7105") { //Mimo sent a power loss report
|
||||
log.debug "Device lost power"
|
||||
sendEvent(name: "powered", value: "powerOff", descriptionText: "$device.displayName lost power")
|
||||
} else {
|
||||
sendEvent(name: "powered", value: "powerOn", descriptionText: "$device.displayName regained power")
|
||||
}
|
||||
if (cmd) {
|
||||
def eventReturn = zwaveEvent(cmd)
|
||||
if(eventReturn in physicalgraph.device.HubMultiAction) {
|
||||
result = eventReturn
|
||||
}
|
||||
else {
|
||||
result = createEvent(eventReturn)
|
||||
}
|
||||
}
|
||||
log.debug "Parse returned ${result} $cmd.CMD"
|
||||
return result
|
||||
}
|
||||
|
||||
def updated() { // neat built-in smartThings function which automatically runs whenever any setting inputs are changed in the preferences menu of the device handler
|
||||
|
||||
if (state.count == 1) // this bit with state keeps the function from running twice ( which it always seems to want to do) (( oh, and state.count is a variable which is nonVolatile and doesn't change per every parse request.
|
||||
{
|
||||
state.count = 0
|
||||
log.debug "Settings Updated..."
|
||||
return response(delayBetween([
|
||||
configure(), // the response() function is used for sending commands in reponse to an event, without it, no zWave commands will work for contained function
|
||||
refresh()
|
||||
], 200))
|
||||
}
|
||||
else {state.count = 1}
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd) // basic set is essentially our digital sensor for SIG1 and SIG2 - it doesn't use an endpoint so we are having it send a multilevelGet() for SIG1 and SIG2 to see which one triggered.
|
||||
{
|
||||
log.debug "sent a BasicSet command"
|
||||
return response(refresh())
|
||||
}
|
||||
|
||||
def zwaveEvent(int endPoint, physicalgraph.zwave.commands.sensorbinaryv1.SensorBinaryReport cmd) // event to get the state of the digital sensor SIG1 and SIG2
|
||||
{
|
||||
log.debug "sent a sensorBinaryReport command"
|
||||
return response(refresh())
|
||||
}
|
||||
|
||||
def zwaveEvent(int endPoint, physicalgraph.zwave.commands.switchbinaryv1.SwitchBinaryReport cmd) // event for seeing the states of relay 1 and relay 2
|
||||
{
|
||||
def map = [:] // map for containing the name and state fo the specified relay
|
||||
if (endPoint == 3)
|
||||
{
|
||||
if (cmd.value) // possible values are 255 and 0 (0 is false)
|
||||
{map.value = "on"}
|
||||
else
|
||||
{map.value = "off"}
|
||||
map.name = "switch"
|
||||
log.debug "sent a SwitchBinary command $map.name $map.value" // the map is only used for debug messages. not for the return command to the device
|
||||
return [name: "switch", value: cmd.value ? "on" : "off"]
|
||||
}
|
||||
else if (endPoint == 4)
|
||||
{
|
||||
if (cmd.value)
|
||||
{map.value = "on"}
|
||||
else
|
||||
{map.value = "off"}
|
||||
map.name = "switch2"
|
||||
sendEvent(name: "relay2", value: "$map.value")
|
||||
log.debug "sent a SwitchBinary command $map.name $map.value" // the map is only used for debug messages. not for the return command to the device
|
||||
return [name: "switch2", value: cmd.value ? "on" : "off"]
|
||||
}
|
||||
}
|
||||
|
||||
def zwaveEvent (int endPoint, physicalgraph.zwave.commands.sensormultilevelv5.SensorMultilevelReport cmd) // sensorMultilevelReport is used to report the value of the analog voltage for SIG1
|
||||
{
|
||||
def map = [:]
|
||||
def stdEvent = [:]
|
||||
def voltageVal = CalculateVoltage(cmd.scaledSensorValue) // saving the scaled Sensor Value used to enter into a large formula to determine actual voltage value
|
||||
if (endPoint == 1) //endPoint 1 is for SIG1
|
||||
{
|
||||
if (state.AD1 == false) // state.AD1 is to determine which state the anaDig1 tile should be in (either analogue or digital mode)
|
||||
{
|
||||
map.name = "anaDig1"
|
||||
stdEvent.name = "contact"
|
||||
if (voltageVal < 2) { // DK changed to 2v to follow LED behavior
|
||||
map.value = "closed"
|
||||
stdEvent.value = "closed"
|
||||
}
|
||||
else
|
||||
{
|
||||
map.value = "open"
|
||||
stdEvent.value = "open"
|
||||
}
|
||||
}
|
||||
else //or state.AD1 is true for analogue mode
|
||||
{
|
||||
map.name = "anaDig1"
|
||||
stdEvent.name = "voltage"
|
||||
map.value = voltageVal
|
||||
stdEvent.value = voltageVal
|
||||
map.unit = "v"
|
||||
stdEvent.unit = "v"
|
||||
}
|
||||
}
|
||||
else if (endPoint == 2) // endpoint 2 is for SIG2
|
||||
{
|
||||
if (state.AD2 == false)
|
||||
{
|
||||
map.name = "anaDig2"
|
||||
stdEvent.name = "contact2"
|
||||
if (voltageVal < 2) {
|
||||
map.value = "closed"
|
||||
stdEvent.value = "closed"
|
||||
}
|
||||
else
|
||||
{
|
||||
map.value = "open"
|
||||
stdEvent.value = "open"
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
map.name = "anaDig2"
|
||||
stdEvent.name = "voltage2"
|
||||
map.value = voltageVal
|
||||
stdEvent.value = voltageVal
|
||||
map.unit = "v"
|
||||
stdEvent.unit = "v"
|
||||
}
|
||||
}
|
||||
log.debug "sent a SensorMultilevelReport $map.name $map.value"
|
||||
sendEvent(stdEvent)
|
||||
return map
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) { //standard security encapsulation event code (should be the same on all device handlers)
|
||||
def encapsulatedCommand = cmd.encapsulatedCommand()
|
||||
// can specify command class versions here like in zwave.parse
|
||||
if (encapsulatedCommand) {
|
||||
return zwaveEvent(encapsulatedCommand)
|
||||
}
|
||||
}
|
||||
|
||||
// MultiChannelCmdEncap and MultiInstanceCmdEncap are ways that devices
|
||||
// can indicate that a message is coming from one of multiple subdevices
|
||||
// or "endpoints" that would otherwise be indistinguishable
|
||||
def zwaveEvent(physicalgraph.zwave.commands.multichannelv3.MultiChannelCmdEncap cmd) {
|
||||
def encapsulatedCommand = cmd.encapsulatedCommand()
|
||||
log.debug ("Command from endpoint ${cmd.sourceEndPoint}: ${encapsulatedCommand}")
|
||||
|
||||
if (encapsulatedCommand) {
|
||||
return zwaveEvent(cmd.sourceEndPoint, encapsulatedCommand)
|
||||
}
|
||||
}
|
||||
|
||||
def zwaveEvent(int endPoint, physicalgraph.zwave.commands.multichannelassociationv2.MultiChannelAssociationReport cmd) {
|
||||
|
||||
log.debug "sent an Association Report"
|
||||
log.debug " ${cmd.groupingIdentifier}"
|
||||
//return [:]
|
||||
}
|
||||
|
||||
def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
||||
// Handles all Z-Wave commands we aren't interested in
|
||||
log.debug("Un-parsed Z-Wave message ${cmd}")
|
||||
return [:]
|
||||
}
|
||||
|
||||
def CalculateVoltage(ADCvalue) // used to calculate the voltage based on the collected Scaled sensor value of the multilevel sensor event
|
||||
{
|
||||
def volt = (((2.396*(10**-17))*(ADCvalue**5)) - ((1.817*(10**-13))*(ADCvalue**4)) + ((5.087*(10**-10))*(ADCvalue**3)) - ((5.868*(10**-7))*(ADCvalue**2)) + ((9.967*(10**-4))*(ADCvalue)) - (1.367*(10**-2)))
|
||||
return volt.round(1)
|
||||
}
|
||||
|
||||
|
||||
def configure() {
|
||||
log.debug "Configuring...."
|
||||
def sig1
|
||||
def sig2
|
||||
if (Sig1AD == true)
|
||||
{ sig1 = 0x01
|
||||
state.AD1 = true}
|
||||
else if (Sig1AD == false)
|
||||
{ sig1 = 0x40
|
||||
state.AD1 = false}
|
||||
if (Sig2AD == true)
|
||||
{ sig2 = 0x01
|
||||
state.AD2 = true}
|
||||
else if (Sig2AD == false)
|
||||
{ sig2 = 0x40
|
||||
state.AD2 = false}
|
||||
|
||||
def delay = (RelaySwitchDelay*10).toInteger() // the input which we get from the user is a string and is in seconds while the MIMO2 configuration requires it in 100ms so - change to integer and multiply by 10
|
||||
def delay2 = (RelaySwitchDelay2*10).toInteger() // the input which we get from the user is a string and is in seconds while the MIMO2 configuration requires it in 100ms so - change to integer and multiply by 10
|
||||
if (delay > 31)
|
||||
{
|
||||
log.debug "Relay 1 input ${delay / 10} set too high. Max value is 3.1"
|
||||
delay = 31
|
||||
}
|
||||
if (delay < 0)
|
||||
{
|
||||
log.debug "Relay 1 input ${delay / 10} set too low. Min value is 0"
|
||||
delay = 0
|
||||
}
|
||||
if (delay2 > 31)
|
||||
{
|
||||
log.debug "Relay 2 input ${delay2 / 10} set too high. Max value is 3.1"
|
||||
delay2 = 31
|
||||
}
|
||||
if (delay2 < 0)
|
||||
{
|
||||
log.debug "Relay 2 input ${delay2 / 10} set too low. Min value is 0"
|
||||
delay = 0
|
||||
}
|
||||
|
||||
|
||||
|
||||
return delayBetween([
|
||||
encap(zwave.multiChannelAssociationV2.multiChannelAssociationSet(groupingIdentifier:3, nodeId:[zwaveHubNodeId]), 0),
|
||||
encap(zwave.multiChannelAssociationV2.multiChannelAssociationSet(groupingIdentifier:2, nodeId:[zwaveHubNodeId]), 0),
|
||||
|
||||
encap(zwave.multiChannelAssociationV2.multiChannelAssociationSet(groupingIdentifier:2, nodeId:[zwaveHubNodeId]), 1),
|
||||
encap(zwave.multiChannelAssociationV2.multiChannelAssociationSet(groupingIdentifier:2, nodeId:[zwaveHubNodeId]), 2),
|
||||
encap(zwave.multiChannelAssociationV2.multiChannelAssociationSet(groupingIdentifier:1, nodeId:[zwaveHubNodeId]), 3),
|
||||
encap(zwave.multiChannelAssociationV2.multiChannelAssociationSet(groupingIdentifier:1, nodeId:[zwaveHubNodeId]), 4),
|
||||
|
||||
secure(zwave.configurationV1.configurationSet(configurationValue: [sig1], parameterNumber: 3, size: 1)), // sends a multiLevelSensor report every 30 seconds for SIG1
|
||||
secure(zwave.configurationV1.configurationSet(configurationValue: [sig2], parameterNumber: 9, size: 1)), // sends a multiLevelSensor report every 30 seconds for SIG2
|
||||
secure(zwave.configurationV1.configurationSet(configurationValue: [delay], parameterNumber: 1, size: 1)), // configurationValue for parameterNumber means how many 100ms do you want the relay
|
||||
// to wait before it cycles again / size should just be 1 (for 1 byte.)
|
||||
secure(zwave.configurationV1.configurationSet(configurationValue: [delay2], parameterNumber: 2, size: 1)),
|
||||
|
||||
], 200)
|
||||
}
|
||||
|
||||
def on() {
|
||||
return encap(zwave.basicV1.basicSet(value: 0xff), 3) // physically changes the relay from on to off and requests a report of the relay
|
||||
// oddly, smartThings automatically sends a switchBinaryGet() command whenever the above basicSet command is sent, so we don't need to send one here.
|
||||
}
|
||||
|
||||
def off() {
|
||||
return encap(zwave.basicV1.basicSet(value: 0x00), 3) // physically changes the relay from on to off and requests a report of the relay
|
||||
// oddly, smartThings automatically sends a switchBinaryGet() command whenever the above basicSet command is sent, so we don't need to send one here.
|
||||
}
|
||||
|
||||
def on2() {
|
||||
return encap(zwave.basicV1.basicSet(value: 0xff), 4)
|
||||
// oddly, smartThings automatically sends a switchBinaryGet() command whenever the above basicSet command is sent, so we don't need to send one here.
|
||||
}
|
||||
|
||||
def off2() {
|
||||
return encap(zwave.basicV1.basicSet(value: 0x00), 4)
|
||||
// oddly, smartThings automatically sends a switchBinaryGet() command whenever the above basicSet command is sent, so we don't need to send one here.
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
log.debug "Refresh"
|
||||
return delayBetween([
|
||||
encap(zwave.sensorMultilevelV5.sensorMultilevelGet(), 1),// requests a report of the anologue input voltage for SIG1
|
||||
encap(zwave.sensorMultilevelV5.sensorMultilevelGet(), 2),// requests a report of the anologue input voltage for SIG2
|
||||
encap(zwave.switchBinaryV1.switchBinaryGet(), 3), //requests a report of the relay to make sure that it changed for Relay 1
|
||||
encap(zwave.switchBinaryV1.switchBinaryGet(), 4), //requests a report of the relay to make sure that it changed for Relay 2
|
||||
],200)
|
||||
}
|
||||
|
||||
def refreshZWave() {
|
||||
log.debug "Refresh (Z-Wave Response)"
|
||||
return delayBetween([
|
||||
encap(zwave.sensorMultilevelV5.sensorMultilevelGet(), 1),// requests a report of the anologue input voltage for SIG1
|
||||
encap(zwave.sensorMultilevelV5.sensorMultilevelGet(), 2),// requests a report of the anologue input voltage for SIG2
|
||||
encap(zwave.switchBinaryV1.switchBinaryGet(), 3), //requests a report of the relay to make sure that it changed for Relay 1
|
||||
encap(zwave.switchBinaryV1.switchBinaryGet(), 4) //requests a report of the relay to make sure that it changed for Relay 2
|
||||
],200)
|
||||
}
|
||||
|
||||
private secureSequence(commands, delay=200) { // decided not to use this
|
||||
return delayBetween(commands.collect{ secure(it) }, delay)
|
||||
}
|
||||
|
||||
private secure(physicalgraph.zwave.Command cmd) { //take multiChannel message and securely encrypts the message so the device can read it
|
||||
return zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
|
||||
}
|
||||
|
||||
private encap(cmd, endpoint) { // takes desired command and encapsulates it by multiChannel and then sends it to secure() to be wrapped with another encapsulation for secure encryption
|
||||
if (endpoint) {
|
||||
return secure(zwave.multiChannelV3.multiChannelCmdEncap(bitAddress: false, sourceEndPoint:0, destinationEndPoint: endpoint).encapsulate(cmd))
|
||||
} else {
|
||||
return secure(cmd)
|
||||
}
|
||||
}
|
||||
@@ -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