mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-18 13:20:53 +00:00
Compare commits
51 Commits
PROD_2017.
...
MSA-2069-1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
936d8ded3b | ||
|
|
4547d0543b | ||
|
|
59e6f1251b | ||
|
|
e8939a77d7 | ||
|
|
5aee3a1861 | ||
|
|
17ae692aa0 | ||
|
|
f02428b99f | ||
|
|
34174b730c | ||
|
|
12f874408a | ||
|
|
513e44912c | ||
|
|
505ec4ff0f | ||
|
|
fe0f555f10 | ||
|
|
6e1701f955 | ||
|
|
9f5378c2b6 | ||
|
|
39e828b16d | ||
|
|
42353044e6 | ||
|
|
78f06a0b9d | ||
|
|
7f13dd356d | ||
|
|
4042bbbf98 | ||
|
|
20407441d9 | ||
|
|
52925d6d99 | ||
|
|
e6c17131af | ||
|
|
cc70865fbf | ||
|
|
91a77afb2d | ||
|
|
006ffa8a0b | ||
|
|
17465c87c0 | ||
|
|
370b435874 | ||
|
|
be9bdae4cc | ||
|
|
c278e035f6 | ||
|
|
e53d9c910c | ||
|
|
c13014936b | ||
|
|
d6b0f6a8ed | ||
|
|
62c810ba90 | ||
|
|
ea3abb26c0 | ||
|
|
0b81793b0f | ||
|
|
16f41bddae | ||
|
|
117adea586 | ||
|
|
783538e36d | ||
|
|
fb8e4a2416 | ||
|
|
320c8918f8 | ||
|
|
0f3656cd12 | ||
|
|
6110aaa0fa | ||
|
|
c391ce4b43 | ||
|
|
073bac8dac | ||
|
|
6325101f52 | ||
|
|
945a972082 | ||
|
|
535fc112de | ||
|
|
a844e85c47 | ||
|
|
60f6e9c02c | ||
|
|
8aceef9be4 | ||
|
|
a1f39849ed |
18
build.gradle
18
build.gradle
@@ -9,7 +9,7 @@ apply plugin: 'smartthings-slack'
|
|||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath "com.smartthings.deployment:executable-deployment-scripts:1.0.11"
|
classpath "com.smartthings.deployment:executable-deployment-scripts:1.0.12"
|
||||||
}
|
}
|
||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
@@ -19,7 +19,7 @@ buildscript {
|
|||||||
username smartThingsArtifactoryUserName
|
username smartThingsArtifactoryUserName
|
||||||
password smartThingsArtifactoryPassword
|
password smartThingsArtifactoryPassword
|
||||||
}
|
}
|
||||||
url "https://artifactory.smartthings.com/libs-release-local"
|
url "https://smartthings.jfrog.io/smartthings/libs-release-local"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -32,7 +32,7 @@ repositories {
|
|||||||
username smartThingsArtifactoryUserName
|
username smartThingsArtifactoryUserName
|
||||||
password smartThingsArtifactoryPassword
|
password smartThingsArtifactoryPassword
|
||||||
}
|
}
|
||||||
url "https://artifactory.smartthings.com/libs-release-local"
|
url "https://smartthings.jfrog.io/smartthings/libs-release-local"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,10 +51,10 @@ sourceSets {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
devicetypesCompile 'org.codehaus.groovy:groovy-all:2.4.7'
|
devicetypesCompile 'org.codehaus.groovy:groovy-all:2.4.7'
|
||||||
devicetypesCompile 'smartthings:appengine-z-wave:0.1.2'
|
devicetypesCompile 'smartthings:appengine-z-wave:0.1.3'
|
||||||
devicetypesCompile 'smartthings:appengine-zigbee:0.1.11'
|
devicetypesCompile 'smartthings:appengine-zigbee:0.1.12'
|
||||||
smartappsCompile 'org.codehaus.groovy:groovy-all:2.4.7'
|
smartappsCompile 'org.codehaus.groovy:groovy-all:2.4.7'
|
||||||
smartappsCompile 'smartthings:appengine-common:0.1.8'
|
smartappsCompile 'smartthings:appengine-common:0.1.9'
|
||||||
smartappsCompile 'org.codehaus.groovy.modules.http-builder:http-builder:0.7.1'
|
smartappsCompile 'org.codehaus.groovy.modules.http-builder:http-builder:0.7.1'
|
||||||
smartappsCompile 'org.grails:grails-web:2.3.11'
|
smartappsCompile 'org.grails:grails-web:2.3.11'
|
||||||
smartappsCompile 'org.json:json:20140107'
|
smartappsCompile 'org.json:json:20140107'
|
||||||
@@ -74,19 +74,19 @@ slackSendMessage {
|
|||||||
String username
|
String username
|
||||||
switch (branch) {
|
switch (branch) {
|
||||||
case 'master':
|
case 'master':
|
||||||
username = 'Hickory'
|
username = 'DEV'
|
||||||
iconUrl = wolverine
|
iconUrl = wolverine
|
||||||
color = '#35D0F2'
|
color = '#35D0F2'
|
||||||
messageText = 'Began deployment of _SmartThingsPublic[master]_ branch to the _Dev_ environments.'
|
messageText = 'Began deployment of _SmartThingsPublic[master]_ branch to the _Dev_ environments.'
|
||||||
break
|
break
|
||||||
case 'staging':
|
case 'staging':
|
||||||
username = 'Dickory'
|
username = 'STG'
|
||||||
iconUrl = beach
|
iconUrl = beach
|
||||||
color = '#FFDE20'
|
color = '#FFDE20'
|
||||||
messageText = 'Began deployment of _SmartThingsPublic[staging]_ branch to the _Staging_ environments.'
|
messageText = 'Began deployment of _SmartThingsPublic[staging]_ branch to the _Staging_ environments.'
|
||||||
break
|
break
|
||||||
case 'production':
|
case 'production':
|
||||||
username = 'Dock'
|
username = 'PRD'
|
||||||
iconUrl = drinks
|
iconUrl = drinks
|
||||||
color = '#FF1D23'
|
color = '#FF1D23'
|
||||||
messageText = 'Began deployment of _SmartThingsPublic[production]_ branch to the _Prod_ environments.'
|
messageText = 'Began deployment of _SmartThingsPublic[production]_ branch to the _Prod_ environments.'
|
||||||
|
|||||||
@@ -0,0 +1,450 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2016 Eric Maycock
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* Sonoff Wifi Switch
|
||||||
|
*
|
||||||
|
* Author: Eric Maycock (erocm123)
|
||||||
|
* Date: 2016-06-02
|
||||||
|
*/
|
||||||
|
|
||||||
|
import groovy.json.JsonSlurper
|
||||||
|
import groovy.util.XmlSlurper
|
||||||
|
|
||||||
|
metadata {
|
||||||
|
definition (name: "Sonoff Wifi Switch", namespace: "erocm123", author: "Eric Maycock") {
|
||||||
|
capability "Actuator"
|
||||||
|
capability "Switch"
|
||||||
|
capability "Refresh"
|
||||||
|
capability "Sensor"
|
||||||
|
capability "Configuration"
|
||||||
|
capability "Health Check"
|
||||||
|
|
||||||
|
command "reboot"
|
||||||
|
|
||||||
|
attribute "needUpdate", "string"
|
||||||
|
}
|
||||||
|
|
||||||
|
simulator {
|
||||||
|
}
|
||||||
|
|
||||||
|
preferences {
|
||||||
|
input description: "Once you change values on this page, the corner of the \"configuration\" icon will change orange until all configuration parameters are updated.", title: "Settings", displayDuringSetup: false, type: "paragraph", element: "paragraph"
|
||||||
|
generate_preferences(configuration_model())
|
||||||
|
}
|
||||||
|
|
||||||
|
tiles (scale: 2){
|
||||||
|
multiAttributeTile(name:"switch", type: "generic", width: 6, height: 4, canChangeIcon: true){
|
||||||
|
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||||
|
attributeState "on", label:'${name}', action:"switch.off", backgroundColor:"#00a0dc", icon: "st.switches.switch.on", nextState:"turningOff"
|
||||||
|
attributeState "off", label:'${name}', action:"switch.on", backgroundColor:"#ffffff", icon: "st.switches.switch.off", nextState:"turningOn"
|
||||||
|
attributeState "turningOn", label:'${name}', action:"switch.off", backgroundColor:"#00a0dc", icon: "st.switches.switch.off", nextState:"turningOff"
|
||||||
|
attributeState "turningOff", label:'${name}', action:"switch.on", backgroundColor:"#ffffff", icon: "st.switches.switch.on", nextState:"turningOn"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||||
|
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||||
|
}
|
||||||
|
standardTile("configure", "device.needUpdate", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||||
|
state "NO" , label:'', action:"configuration.configure", icon:"st.secondary.configure"
|
||||||
|
state "YES", label:'', action:"configuration.configure", icon:"https://github.com/erocm123/SmartThingsPublic/raw/master/devicetypes/erocm123/qubino-flush-1d-relay.src/configure@2x.png"
|
||||||
|
}
|
||||||
|
standardTile("reboot", "device.reboot", decoration: "flat", height: 2, width: 2, inactiveLabel: false) {
|
||||||
|
state "default", label:"Reboot", action:"reboot", icon:"", backgroundColor:"#ffffff"
|
||||||
|
}
|
||||||
|
valueTile("ip", "ip", width: 2, height: 1) {
|
||||||
|
state "ip", label:'IP Address\r\n${currentValue}'
|
||||||
|
}
|
||||||
|
valueTile("uptime", "uptime", width: 2, height: 1) {
|
||||||
|
state "uptime", label:'Uptime ${currentValue}'
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
main(["switch"])
|
||||||
|
details(["switch",
|
||||||
|
"refresh","configure","reboot",
|
||||||
|
"ip", "uptime"])
|
||||||
|
}
|
||||||
|
|
||||||
|
def installed() {
|
||||||
|
log.debug "installed()"
|
||||||
|
configure()
|
||||||
|
}
|
||||||
|
|
||||||
|
def configure() {
|
||||||
|
logging("configure()", 1)
|
||||||
|
def cmds = []
|
||||||
|
cmds = update_needed_settings()
|
||||||
|
if (cmds != []) cmds
|
||||||
|
}
|
||||||
|
|
||||||
|
def updated()
|
||||||
|
{
|
||||||
|
logging("updated()", 1)
|
||||||
|
def cmds = []
|
||||||
|
cmds = update_needed_settings()
|
||||||
|
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID])
|
||||||
|
sendEvent(name:"needUpdate", value: device.currentValue("needUpdate"), displayed:false, isStateChange: true)
|
||||||
|
if (cmds != []) response(cmds)
|
||||||
|
}
|
||||||
|
|
||||||
|
private def logging(message, level) {
|
||||||
|
if (logLevel != "0"){
|
||||||
|
switch (logLevel) {
|
||||||
|
case "1":
|
||||||
|
if (level > 1)
|
||||||
|
log.debug "$message"
|
||||||
|
break
|
||||||
|
case "99":
|
||||||
|
log.debug "$message"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def parse(description) {
|
||||||
|
//log.debug "Parsing: ${description}"
|
||||||
|
def events = []
|
||||||
|
def descMap = parseDescriptionAsMap(description)
|
||||||
|
def body
|
||||||
|
//log.debug "descMap: ${descMap}"
|
||||||
|
|
||||||
|
if (!state.mac || state.mac != descMap["mac"]) {
|
||||||
|
log.debug "Mac address of device found ${descMap["mac"]}"
|
||||||
|
updateDataValue("mac", descMap["mac"])
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.mac != null && state.dni != state.mac) state.dni = setDeviceNetworkId(state.mac)
|
||||||
|
if (descMap["body"]) body = new String(descMap["body"].decodeBase64())
|
||||||
|
|
||||||
|
if (body && body != "") {
|
||||||
|
|
||||||
|
if(body.startsWith("{") || body.startsWith("[")) {
|
||||||
|
def slurper = new JsonSlurper()
|
||||||
|
def result = slurper.parseText(body)
|
||||||
|
|
||||||
|
log.debug "result: ${result}"
|
||||||
|
|
||||||
|
if (result.containsKey("type")) {
|
||||||
|
if (result.type == "configuration")
|
||||||
|
events << update_current_properties(result)
|
||||||
|
}
|
||||||
|
if (result.containsKey("power")) {
|
||||||
|
events << createEvent(name: "switch", value: result.power)
|
||||||
|
}
|
||||||
|
if (result.containsKey("uptime")) {
|
||||||
|
events << createEvent(name: "uptime", value: result.uptime, displayed: false)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//log.debug "Response is not JSON: $body"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!device.currentValue("ip") || (device.currentValue("ip") != getDataValue("ip"))) events << createEvent(name: 'ip', value: getDataValue("ip"))
|
||||||
|
|
||||||
|
return events
|
||||||
|
}
|
||||||
|
|
||||||
|
def parseDescriptionAsMap(description) {
|
||||||
|
description.split(",").inject([:]) { map, param ->
|
||||||
|
def nameAndValue = param.split(":")
|
||||||
|
|
||||||
|
if (nameAndValue.length == 2) map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
||||||
|
else map += [(nameAndValue[0].trim()):""]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def on() {
|
||||||
|
log.debug "on()"
|
||||||
|
def cmds = []
|
||||||
|
cmds << getAction("/on")
|
||||||
|
return cmds
|
||||||
|
}
|
||||||
|
|
||||||
|
def off() {
|
||||||
|
log.debug "off()"
|
||||||
|
def cmds = []
|
||||||
|
cmds << getAction("/off")
|
||||||
|
return cmds
|
||||||
|
}
|
||||||
|
|
||||||
|
def refresh() {
|
||||||
|
log.debug "refresh()"
|
||||||
|
def cmds = []
|
||||||
|
cmds << getAction("/status")
|
||||||
|
return cmds
|
||||||
|
}
|
||||||
|
|
||||||
|
def ping() {
|
||||||
|
log.debug "ping()"
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
private getAction(uri){
|
||||||
|
updateDNI()
|
||||||
|
def userpass
|
||||||
|
//log.debug uri
|
||||||
|
if(password != null && password != "")
|
||||||
|
userpass = encodeCredentials("admin", password)
|
||||||
|
|
||||||
|
def headers = getHeader(userpass)
|
||||||
|
|
||||||
|
def hubAction = new physicalgraph.device.HubAction(
|
||||||
|
method: "GET",
|
||||||
|
path: uri,
|
||||||
|
headers: headers
|
||||||
|
)
|
||||||
|
return hubAction
|
||||||
|
}
|
||||||
|
|
||||||
|
private postAction(uri, data){
|
||||||
|
updateDNI()
|
||||||
|
|
||||||
|
def userpass
|
||||||
|
|
||||||
|
if(password != null && password != "")
|
||||||
|
userpass = encodeCredentials("admin", password)
|
||||||
|
|
||||||
|
def headers = getHeader(userpass)
|
||||||
|
|
||||||
|
def hubAction = new physicalgraph.device.HubAction(
|
||||||
|
method: "POST",
|
||||||
|
path: uri,
|
||||||
|
headers: headers,
|
||||||
|
body: data
|
||||||
|
)
|
||||||
|
return hubAction
|
||||||
|
}
|
||||||
|
|
||||||
|
private setDeviceNetworkId(ip, port = null){
|
||||||
|
def myDNI
|
||||||
|
if (port == null) {
|
||||||
|
myDNI = ip
|
||||||
|
} else {
|
||||||
|
def iphex = convertIPtoHex(ip)
|
||||||
|
def porthex = convertPortToHex(port)
|
||||||
|
myDNI = "$iphex:$porthex"
|
||||||
|
}
|
||||||
|
log.debug "Device Network Id set to ${myDNI}"
|
||||||
|
return myDNI
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateDNI() {
|
||||||
|
if (state.dni != null && state.dni != "" && device.deviceNetworkId != state.dni) {
|
||||||
|
device.deviceNetworkId = state.dni
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getHostAddress() {
|
||||||
|
if (override == "true" && ip != null && ip != ""){
|
||||||
|
return "${ip}:80"
|
||||||
|
}
|
||||||
|
else if(getDeviceDataByName("ip") && getDeviceDataByName("port")){
|
||||||
|
return "${getDeviceDataByName("ip")}:${getDeviceDataByName("port")}"
|
||||||
|
}else{
|
||||||
|
return "${ip}:80"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String convertIPtoHex(ipAddress) {
|
||||||
|
String hex = ipAddress.tokenize( '.' ).collect { String.format( '%02x', it.toInteger() ) }.join()
|
||||||
|
return hex
|
||||||
|
}
|
||||||
|
|
||||||
|
private String convertPortToHex(port) {
|
||||||
|
String hexport = port.toString().format( '%04x', port.toInteger() )
|
||||||
|
return hexport
|
||||||
|
}
|
||||||
|
|
||||||
|
private encodeCredentials(username, password){
|
||||||
|
def userpassascii = "${username}:${password}"
|
||||||
|
def userpass = "Basic " + userpassascii.encodeAsBase64().toString()
|
||||||
|
return userpass
|
||||||
|
}
|
||||||
|
|
||||||
|
private getHeader(userpass = null){
|
||||||
|
def headers = [:]
|
||||||
|
headers.put("Host", getHostAddress())
|
||||||
|
headers.put("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
if (userpass != null)
|
||||||
|
headers.put("Authorization", userpass)
|
||||||
|
return headers
|
||||||
|
}
|
||||||
|
|
||||||
|
def reboot() {
|
||||||
|
log.debug "reboot()"
|
||||||
|
def uri = "/reboot"
|
||||||
|
getAction(uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
def sync(ip, port) {
|
||||||
|
def existingIp = getDataValue("ip")
|
||||||
|
def existingPort = getDataValue("port")
|
||||||
|
if (ip && ip != existingIp) {
|
||||||
|
updateDataValue("ip", ip)
|
||||||
|
sendEvent(name: 'ip', value: ip)
|
||||||
|
}
|
||||||
|
if (port && port != existingPort) {
|
||||||
|
updateDataValue("port", port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def generate_preferences(configuration_model)
|
||||||
|
{
|
||||||
|
def configuration = parseXml(configuration_model)
|
||||||
|
|
||||||
|
configuration.Value.each
|
||||||
|
{
|
||||||
|
if(it.@hidden != "true" && it.@disabled != "true"){
|
||||||
|
switch(it.@type)
|
||||||
|
{
|
||||||
|
case ["number"]:
|
||||||
|
input "${it.@index}", "number",
|
||||||
|
title:"${it.@label}\n" + "${it.Help}",
|
||||||
|
range: "${it.@min}..${it.@max}",
|
||||||
|
defaultValue: "${it.@value}",
|
||||||
|
displayDuringSetup: "${it.@displayDuringSetup}"
|
||||||
|
break
|
||||||
|
case "list":
|
||||||
|
def items = []
|
||||||
|
it.Item.each { items << ["${it.@value}":"${it.@label}"] }
|
||||||
|
input "${it.@index}", "enum",
|
||||||
|
title:"${it.@label}\n" + "${it.Help}",
|
||||||
|
defaultValue: "${it.@value}",
|
||||||
|
displayDuringSetup: "${it.@displayDuringSetup}",
|
||||||
|
options: items
|
||||||
|
break
|
||||||
|
case ["password"]:
|
||||||
|
input "${it.@index}", "password",
|
||||||
|
title:"${it.@label}\n" + "${it.Help}",
|
||||||
|
displayDuringSetup: "${it.@displayDuringSetup}"
|
||||||
|
break
|
||||||
|
case "decimal":
|
||||||
|
input "${it.@index}", "decimal",
|
||||||
|
title:"${it.@label}\n" + "${it.Help}",
|
||||||
|
range: "${it.@min}..${it.@max}",
|
||||||
|
defaultValue: "${it.@value}",
|
||||||
|
displayDuringSetup: "${it.@displayDuringSetup}"
|
||||||
|
break
|
||||||
|
case "boolean":
|
||||||
|
input "${it.@index}", "boolean",
|
||||||
|
title:"${it.@label}\n" + "${it.Help}",
|
||||||
|
defaultValue: "${it.@value}",
|
||||||
|
displayDuringSetup: "${it.@displayDuringSetup}"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Code has elements from other community source @CyrilPeponnet (Z-Wave Parameter Sync). */
|
||||||
|
|
||||||
|
def update_current_properties(cmd)
|
||||||
|
{
|
||||||
|
def currentProperties = state.currentProperties ?: [:]
|
||||||
|
currentProperties."${cmd.name}" = cmd.value
|
||||||
|
|
||||||
|
if (settings."${cmd.name}" != null)
|
||||||
|
{
|
||||||
|
if (settings."${cmd.name}".toString() == cmd.value)
|
||||||
|
{
|
||||||
|
sendEvent(name:"needUpdate", value:"NO", displayed:false, isStateChange: true)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sendEvent(name:"needUpdate", value:"YES", displayed:false, isStateChange: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.currentProperties = currentProperties
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def update_needed_settings()
|
||||||
|
{
|
||||||
|
def cmds = []
|
||||||
|
def currentProperties = state.currentProperties ?: [:]
|
||||||
|
|
||||||
|
def configuration = parseXml(configuration_model())
|
||||||
|
def isUpdateNeeded = "NO"
|
||||||
|
|
||||||
|
cmds << getAction("/configSet?name=haip&value=${device.hub.getDataValue("localIP")}")
|
||||||
|
cmds << getAction("/configSet?name=haport&value=${device.hub.getDataValue("localSrvPortTCP")}")
|
||||||
|
|
||||||
|
configuration.Value.each
|
||||||
|
{
|
||||||
|
if ("${it.@setting_type}" == "lan" && it.@disabled != "true"){
|
||||||
|
if (currentProperties."${it.@index}" == null)
|
||||||
|
{
|
||||||
|
if (it.@setonly == "true"){
|
||||||
|
logging("Setting ${it.@index} will be updated to ${it.@value}", 2)
|
||||||
|
cmds << getAction("/configSet?name=${it.@index}&value=${it.@value}")
|
||||||
|
} else {
|
||||||
|
isUpdateNeeded = "YES"
|
||||||
|
logging("Current value of setting ${it.@index} is unknown", 2)
|
||||||
|
cmds << getAction("/configGet?name=${it.@index}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ((settings."${it.@index}" != null || it.@hidden == "true") && currentProperties."${it.@index}" != (settings."${it.@index}"? settings."${it.@index}".toString() : "${it.@value}"))
|
||||||
|
{
|
||||||
|
isUpdateNeeded = "YES"
|
||||||
|
logging("Setting ${it.@index} will be updated to ${settings."${it.@index}"}", 2)
|
||||||
|
cmds << getAction("/configSet?name=${it.@index}&value=${settings."${it.@index}"}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sendEvent(name:"needUpdate", value: isUpdateNeeded, displayed:false, isStateChange: true)
|
||||||
|
return cmds
|
||||||
|
}
|
||||||
|
|
||||||
|
def configuration_model()
|
||||||
|
{
|
||||||
|
'''
|
||||||
|
<configuration>
|
||||||
|
<Value type="password" byteSize="1" index="password" label="Password" min="" max="" value="" setting_type="preference" fw="">
|
||||||
|
<Help>
|
||||||
|
</Help>
|
||||||
|
</Value>
|
||||||
|
<Value type="list" byteSize="1" index="pos" label="Boot Up State" min="0" max="2" value="0" setting_type="lan" fw="">
|
||||||
|
<Help>
|
||||||
|
Default: Off
|
||||||
|
</Help>
|
||||||
|
<Item label="Off" value="0" />
|
||||||
|
<Item label="On" value="1" />
|
||||||
|
<Item label="Previous" value="2" />
|
||||||
|
</Value>
|
||||||
|
<Value type="number" byteSize="1" index="autooff" label="Auto Off" min="0" max="65536" value="0" setting_type="lan" fw="">
|
||||||
|
<Help>
|
||||||
|
Automatically turn the switch off after this many seconds.
|
||||||
|
Range: 0 to 65536
|
||||||
|
Default: 0 (Disabled)
|
||||||
|
</Help>
|
||||||
|
</Value>
|
||||||
|
<Value type="list" byteSize="1" index="switchtype" label="External Switch Type" min="0" max="1" value="0" setting_type="lan" fw="">
|
||||||
|
<Help>
|
||||||
|
If a switch is attached to GPIO 14.
|
||||||
|
Default: Momentary
|
||||||
|
</Help>
|
||||||
|
<Item label="Momentary" value="0" />
|
||||||
|
<Item label="Toggle" value="1" />
|
||||||
|
</Value>
|
||||||
|
<Value type="list" index="logLevel" label="Debug Logging Level?" value="0" setting_type="preference" fw="">
|
||||||
|
<Help>
|
||||||
|
</Help>
|
||||||
|
<Item label="None" value="0" />
|
||||||
|
<Item label="Reports" value="1" />
|
||||||
|
<Item label="All" value="99" />
|
||||||
|
</Value>
|
||||||
|
</configuration>
|
||||||
|
'''
|
||||||
|
}
|
||||||
2
devicetypes/smartthings/aeon-key-fob.src/.st-ignore
Normal file
2
devicetypes/smartthings/aeon-key-fob.src/.st-ignore
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
.st-ignore
|
||||||
|
README.md
|
||||||
34
devicetypes/smartthings/aeon-key-fob.src/README.md
Normal file
34
devicetypes/smartthings/aeon-key-fob.src/README.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Aeon Labs Key Fob
|
||||||
|
|
||||||
|
Cloud Execution
|
||||||
|
|
||||||
|
Works with:
|
||||||
|
|
||||||
|
* [Aeon Labs Key Fob](http://aeotec.com/z-wave-key-fob-remote-control)
|
||||||
|
|
||||||
|
## Table of contents
|
||||||
|
|
||||||
|
* [Capabilities](#capabilities)
|
||||||
|
* [Health](#device-health)
|
||||||
|
* [Troubleshooting](#troubleshooting)
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
* **Actuator** - represents device has commands
|
||||||
|
* **Button** - represents a device with one or more buttons
|
||||||
|
* **Holdable Button** - represents a device with one or more holdable buttons
|
||||||
|
* **Configuration** - allows for configuration of devices
|
||||||
|
* **Sensor** - detects sensor events
|
||||||
|
* **Battery** - defines device uses a battery
|
||||||
|
* **Health Check** - indicates ability to get device health notifications
|
||||||
|
|
||||||
|
## Device Health
|
||||||
|
|
||||||
|
Aeon Key Fob is a ZWave totally sleepy device and is marked offline only in the case when Hub is offline.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the sensor is out of range.
|
||||||
|
Pairing needs to be tried again by placing the sensor closer to the hub.
|
||||||
|
Instructions related to pairing, resetting and removing the Aeon Labs Key Fob from SmartThings can be found in the following link:
|
||||||
|
* [Aeotec Key Fob Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/202294120-Aeon-Labs-Key-Fob)
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import groovy.json.JsonOutput
|
||||||
/**
|
/**
|
||||||
* Copyright 2015 SmartThings
|
* Copyright 2015 SmartThings
|
||||||
*
|
*
|
||||||
@@ -19,6 +20,7 @@ metadata {
|
|||||||
capability "Configuration"
|
capability "Configuration"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
capability "Battery"
|
capability "Battery"
|
||||||
|
capability "Health Check"
|
||||||
|
|
||||||
fingerprint deviceId: "0x0101", inClusters: "0x86,0x72,0x70,0x80,0x84,0x85"
|
fingerprint deviceId: "0x0101", inClusters: "0x86,0x72,0x70,0x80,0x84,0x85"
|
||||||
fingerprint mfr: "0086", prod: "0001", model: "0026", deviceJoinName: "Aeon Panic Button"
|
fingerprint mfr: "0086", prod: "0001", model: "0026", deviceJoinName: "Aeon Panic Button"
|
||||||
@@ -131,6 +133,9 @@ def updated() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def initialize() {
|
def initialize() {
|
||||||
|
// Arrival sensors only goes OFFLINE when Hub is off
|
||||||
|
sendEvent(name: "DeviceWatch-Enroll", value: JsonOutput.toJson([protocol: "zigbee", scheme:"untracked"]), displayed: false)
|
||||||
|
|
||||||
def zwMap = getZwaveInfo()
|
def zwMap = getZwaveInfo()
|
||||||
def buttons = 4 // Default for Key Fob
|
def buttons = 4 // Default for Key Fob
|
||||||
|
|
||||||
|
|||||||
2
devicetypes/smartthings/aeon-minimote.src/.st-ignore
Normal file
2
devicetypes/smartthings/aeon-minimote.src/.st-ignore
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
.st-ignore
|
||||||
|
README.md
|
||||||
33
devicetypes/smartthings/aeon-minimote.src/README.md
Normal file
33
devicetypes/smartthings/aeon-minimote.src/README.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# Aeon Minimote
|
||||||
|
|
||||||
|
Cloud Execution
|
||||||
|
|
||||||
|
Works with:
|
||||||
|
|
||||||
|
* [Aeotec Minimote](http://aeotec.com/small-z-wave-remote-control)
|
||||||
|
|
||||||
|
## Table of contents
|
||||||
|
|
||||||
|
* [Capabilities](#capabilities)
|
||||||
|
* [Health](#device-health)
|
||||||
|
* [Troubleshooting](#troubleshooting)
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
* **Actuator** - represents device has commands
|
||||||
|
* **Button** - represents a device with one or more buttons
|
||||||
|
* **Holdable Button** - represents a device with one or more holdable buttons
|
||||||
|
* **Configuration** - allows for configuration of devices
|
||||||
|
* **Sensor** - detects sensor events
|
||||||
|
* **Health Check** - indicates ability to get device health notifications
|
||||||
|
|
||||||
|
## Device Health
|
||||||
|
|
||||||
|
Aeon Minimote is a ZWave totally sleepy device and is marked offline only in the case when Hub is offline.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the sensor is out of range.
|
||||||
|
Pairing needs to be tried again by placing the sensor closer to the hub.
|
||||||
|
Instructions related to pairing, resetting and removing the Aeotec Minimote from SmartThings can be found in the following link:
|
||||||
|
* [Aeotec Minimote Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/202087904-Aeotec-Minimote)
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import groovy.json.JsonOutput
|
||||||
/**
|
/**
|
||||||
* Copyright 2015 SmartThings
|
* Copyright 2015 SmartThings
|
||||||
*
|
*
|
||||||
@@ -18,6 +19,7 @@ metadata {
|
|||||||
capability "Holdable Button"
|
capability "Holdable Button"
|
||||||
capability "Configuration"
|
capability "Configuration"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
|
capability "Health Check"
|
||||||
|
|
||||||
fingerprint deviceId: "0x0101", inClusters: "0x86,0x72,0x70,0x9B", outClusters: "0x26,0x2B"
|
fingerprint deviceId: "0x0101", inClusters: "0x86,0x72,0x70,0x9B", outClusters: "0x26,0x2B"
|
||||||
fingerprint deviceId: "0x0101", inClusters: "0x86,0x72,0x70,0x9B,0x85,0x84", outClusters: "0x26" // old style with numbered buttons
|
fingerprint deviceId: "0x0101", inClusters: "0x86,0x72,0x70,0x9B,0x85,0x84", outClusters: "0x26" // old style with numbered buttons
|
||||||
@@ -119,5 +121,7 @@ def updated() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def initialize() {
|
def initialize() {
|
||||||
|
// Arrival sensors only goes OFFLINE when Hub is off
|
||||||
|
sendEvent(name: "DeviceWatch-Enroll", value: JsonOutput.toJson([protocol: "zigbee", scheme:"untracked"]), displayed: false)
|
||||||
sendEvent(name: "numberOfButtons", value: 4)
|
sendEvent(name: "numberOfButtons", value: 4)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//DEPRECATED. INTEGRATION MOVED TO SUPER LAN CONNECT
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bose SoundTouch
|
* Bose SoundTouch
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -108,11 +108,20 @@ def updated(){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def getCommandClassVersions() {
|
||||||
|
[
|
||||||
|
0x20: 1, // Basic
|
||||||
|
0x26: 1, // SwitchMultilevel
|
||||||
|
0x56: 1, // Crc16Encap
|
||||||
|
0x70: 1, // Configuration
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
def result = null
|
def result = null
|
||||||
if (description != "updated") {
|
if (description != "updated") {
|
||||||
log.debug "parse() >> zwave.parse($description)"
|
log.debug "parse() >> zwave.parse($description)"
|
||||||
def cmd = zwave.parse(description, [0x20: 1, 0x26: 1, 0x70: 1])
|
def cmd = zwave.parse(description, commandClassVersions)
|
||||||
if (cmd) {
|
if (cmd) {
|
||||||
result = zwaveEvent(cmd)
|
result = zwaveEvent(cmd)
|
||||||
}
|
}
|
||||||
@@ -179,6 +188,16 @@ def zwaveEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelS
|
|||||||
[createEvent(name:"switch", value:"on"), response(zwave.switchMultilevelV1.switchMultilevelGet().format())]
|
[createEvent(name:"switch", value:"on"), response(zwave.switchMultilevelV1.switchMultilevelGet().format())]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def zwaveEvent(physicalgraph.zwave.commands.crc16encapv1.Crc16Encap cmd) {
|
||||||
|
def versions = commandClassVersions
|
||||||
|
def version = versions[cmd.commandClass as Integer]
|
||||||
|
def ccObj = version ? zwave.commandClass(cmd.commandClass, version) : zwave.commandClass(cmd.commandClass)
|
||||||
|
def encapsulatedCommand = ccObj?.command(cmd.command)?.parse(cmd.data)
|
||||||
|
if (encapsulatedCommand) {
|
||||||
|
zwaveEvent(encapsulatedCommand)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
||||||
// Handles all Z-Wave commands we aren't interested in
|
// Handles all Z-Wave commands we aren't interested in
|
||||||
[:]
|
[:]
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ metadata {
|
|||||||
state "heat", action:"switchMode", nextState: "updating", icon: "st.thermostat.heat"
|
state "heat", action:"switchMode", nextState: "updating", icon: "st.thermostat.heat"
|
||||||
state "cool", action:"switchMode", nextState: "updating", icon: "st.thermostat.cool"
|
state "cool", action:"switchMode", nextState: "updating", icon: "st.thermostat.cool"
|
||||||
state "auto", action:"switchMode", nextState: "updating", icon: "st.thermostat.auto"
|
state "auto", action:"switchMode", nextState: "updating", icon: "st.thermostat.auto"
|
||||||
state "emergency heat", action:"switchMode", icon: "st.thermostat.emergency-heat" // emergency heat = auxHeatOnly
|
state "auxheatonly", action:"switchMode", icon: "st.thermostat.emergency-heat"
|
||||||
state "updating", label:"Working", icon: "st.secondary.secondary"
|
state "updating", label:"Working", icon: "st.secondary.secondary"
|
||||||
}
|
}
|
||||||
standardTile("fanMode", "device.thermostatFanMode", inactiveLabel: false, decoration: "flat") {
|
standardTile("fanMode", "device.thermostatFanMode", inactiveLabel: false, decoration: "flat") {
|
||||||
@@ -156,47 +156,49 @@ void poll() {
|
|||||||
def generateEvent(Map results) {
|
def generateEvent(Map results) {
|
||||||
log.debug "parsing data $results"
|
log.debug "parsing data $results"
|
||||||
if(results) {
|
if(results) {
|
||||||
results.each { name, value ->
|
|
||||||
|
|
||||||
def linkText = getLinkText(device)
|
def linkText = getLinkText(device)
|
||||||
def isChange = false
|
def supportedThermostatModes = []
|
||||||
def isDisplayed = true
|
def thermostatMode = null
|
||||||
|
|
||||||
|
results.each { name, value ->
|
||||||
def event = [name: name, linkText: linkText, descriptionText: getThermostatDescriptionText(name, value, linkText),
|
def event = [name: name, linkText: linkText, descriptionText: getThermostatDescriptionText(name, value, linkText),
|
||||||
handlerName: name]
|
handlerName: name]
|
||||||
|
|
||||||
if (name=="temperature" || name=="heatingSetpoint" || name=="coolingSetpoint" ) {
|
if (name=="temperature" || name=="heatingSetpoint" || name=="coolingSetpoint" ) {
|
||||||
def sendValue = location.temperatureScale == "C"? roundC(convertFtoC(value.toDouble())) : value.toInteger()
|
def sendValue = location.temperatureScale == "C"? roundC(convertFtoC(value.toDouble())) : value.toInteger()
|
||||||
isChange = isTemperatureStateChange(device, name, value.toString())
|
event << [value: sendValue, unit: temperatureScale]
|
||||||
isDisplayed = isChange
|
|
||||||
event << [value: sendValue, unit: temperatureScale, isStateChange: isChange, displayed: isDisplayed]
|
|
||||||
} else if (name=="maxCoolingSetpoint" || name=="minCoolingSetpoint" || name=="maxHeatingSetpoint" || name=="minHeatingSetpoint") {
|
} else if (name=="maxCoolingSetpoint" || name=="minCoolingSetpoint" || name=="maxHeatingSetpoint" || name=="minHeatingSetpoint") {
|
||||||
def sendValue = location.temperatureScale == "C"? roundC(convertFtoC(value.toDouble())) : value.toInteger()
|
def sendValue = location.temperatureScale == "C"? roundC(convertFtoC(value.toDouble())) : value.toInteger()
|
||||||
event << [value: sendValue, unit: temperatureScale, displayed: false]
|
event << [value: sendValue, unit: temperatureScale, displayed: false]
|
||||||
} else if (name=="heatMode" || name=="coolMode" || name=="autoMode" || name=="auxHeatMode"){
|
} else if (name=="heatMode" || name=="coolMode" || name=="autoMode" || name=="auxHeatMode"){
|
||||||
isChange = isStateChange(device, name, value.toString())
|
if (value == true) {
|
||||||
event << [value: value.toString(), isStateChange: isChange, displayed: false]
|
supportedThermostatModes << ((name == "auxHeatMode") ? "auxheatonly" : name - "Mode")
|
||||||
|
}
|
||||||
|
return // as we don't want to send this event here, proceed to next name/value pair
|
||||||
} else if (name=="thermostatFanMode"){
|
} else if (name=="thermostatFanMode"){
|
||||||
isChange = isStateChange(device, name, value.toString())
|
sendEvent(name: "supportedThermostatFanModes", value: fanModes(), displayed: false)
|
||||||
event << [value: value.toString(), isStateChange: isChange, displayed: false]
|
event << [value: value.toString(), data:[supportedThermostatFanModes: fanModes()]]
|
||||||
} else if (name=="humidity") {
|
} else if (name=="humidity") {
|
||||||
isChange = isStateChange(device, name, value.toString())
|
event << [value: value.toString(), displayed: false, unit: "%"]
|
||||||
event << [value: value.toString(), isStateChange: isChange, displayed: false, unit: "%"]
|
|
||||||
} else if (name == "deviceAlive") {
|
} else if (name == "deviceAlive") {
|
||||||
isChange = isStateChange(device, name, value.toString())
|
|
||||||
event['isStateChange'] = isChange
|
|
||||||
event['displayed'] = false
|
event['displayed'] = false
|
||||||
} else if (name == "thermostatMode") {
|
} else if (name == "thermostatMode") {
|
||||||
def mode = value.toString()
|
thermostatMode = value.toLowerCase()
|
||||||
mode = (mode == "auxHeatOnly") ? "emergency heat" : mode
|
return // as we don't want to send this event here, proceed to next name/value pair
|
||||||
isChange = isStateChange(device, name, mode)
|
|
||||||
event << [value: mode, isStateChange: isChange, displayed: isDisplayed]
|
|
||||||
} else {
|
} else {
|
||||||
isChange = isStateChange(device, name, value.toString())
|
event << [value: value.toString()]
|
||||||
isDisplayed = isChange
|
|
||||||
event << [value: value.toString(), isStateChange: isChange, displayed: isDisplayed]
|
|
||||||
}
|
}
|
||||||
sendEvent(event)
|
sendEvent(event)
|
||||||
}
|
}
|
||||||
|
if (state.supportedThermostatModes != supportedThermostatModes) {
|
||||||
|
state.supportedThermostatModes = supportedThermostatModes
|
||||||
|
sendEvent(name: "supportedThermostatModes", value: supportedThermostatModes, displayed: false)
|
||||||
|
}
|
||||||
|
if (thermostatMode) {
|
||||||
|
sendEvent(name: "thermostatMode", value: thermostatMode, data:[supportedThermostatModes:state.supportedThermostatModes], linkText: linkText,
|
||||||
|
descriptionText: getThermostatDescriptionText("thermostatMode", thermostatMode, linkText), handlerName: "thermostatMode")
|
||||||
|
}
|
||||||
generateSetpointEvent ()
|
generateSetpointEvent ()
|
||||||
generateStatusEvent ()
|
generateStatusEvent ()
|
||||||
}
|
}
|
||||||
@@ -322,15 +324,7 @@ void resumeProgram() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def modes() {
|
def modes() {
|
||||||
if (state.modes) {
|
return state.supportedThermostatModes
|
||||||
log.debug "Modes = ${state.modes}"
|
|
||||||
return state.modes
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
state.modes = parent.availableModes(this)
|
|
||||||
log.debug "Modes = ${state.modes}"
|
|
||||||
return state.modes
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def fanModes() {
|
def fanModes() {
|
||||||
@@ -413,11 +407,13 @@ def setThermostatFanMode(String mode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def generateModeEvent(mode) {
|
def generateModeEvent(mode) {
|
||||||
sendEvent(name: "thermostatMode", value: mode, descriptionText: "$device.displayName is in ${mode} mode", displayed: true)
|
sendEvent(name: "thermostatMode", value: mode, data:[supportedThermostatModes: state.supportedThermostatModes],
|
||||||
|
descriptionText: "$device.displayName is in ${mode} mode")
|
||||||
}
|
}
|
||||||
|
|
||||||
def generateFanModeEvent(fanMode) {
|
def generateFanModeEvent(fanMode) {
|
||||||
sendEvent(name: "thermostatFanMode", value: fanMode, descriptionText: "$device.displayName fan is in ${fanMode} mode", displayed: true)
|
sendEvent(name: "thermostatFanMode", value: fanMode, data:[supportedThermostatFanModes: fanModes()],
|
||||||
|
descriptionText: "$device.displayName fan is in ${fanMode} mode")
|
||||||
}
|
}
|
||||||
|
|
||||||
def generateOperatingStateEvent(operatingState) {
|
def generateOperatingStateEvent(operatingState) {
|
||||||
@@ -453,14 +449,14 @@ def heat() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def emergencyHeat() {
|
def emergencyHeat() {
|
||||||
auxHeatOnly()
|
auxheatonly()
|
||||||
}
|
}
|
||||||
|
|
||||||
def auxHeatOnly() {
|
def auxheatonly() {
|
||||||
log.debug "auxHeatOnly = emergency heat"
|
log.debug "auxheatonly()"
|
||||||
def deviceId = device.deviceNetworkId.split(/\./).last()
|
def deviceId = device.deviceNetworkId.split(/\./).last()
|
||||||
if (parent.setMode ("auxHeatOnly", deviceId))
|
if (parent.setMode ("auxHeatOnly", deviceId))
|
||||||
generateModeEvent("emergency heat") // emergency heat = auxHeatOnly
|
generateModeEvent("auxheatonly")
|
||||||
else {
|
else {
|
||||||
log.debug "Error setting new mode."
|
log.debug "Error setting new mode."
|
||||||
def currentMode = device.currentState("thermostatMode")?.value
|
def currentMode = device.currentState("thermostatMode")?.value
|
||||||
@@ -593,7 +589,7 @@ def generateSetpointEvent() {
|
|||||||
} else if (mode == "off") {
|
} else if (mode == "off") {
|
||||||
sendEvent("name":"thermostatSetpoint", "value":averageSetpoint, "unit":location.temperatureScale)
|
sendEvent("name":"thermostatSetpoint", "value":averageSetpoint, "unit":location.temperatureScale)
|
||||||
sendEvent("name":"displayThermostatSetpoint", "value":"Off", displayed: false)
|
sendEvent("name":"displayThermostatSetpoint", "value":"Off", displayed: false)
|
||||||
} else if (mode == "emergency heat") { // emergency heat = auxHeatOnly
|
} else if (mode == "auxheatonly") {
|
||||||
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale)
|
sendEvent("name":"thermostatSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale)
|
||||||
sendEvent("name":"displayThermostatSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale, displayed: false)
|
sendEvent("name":"displayThermostatSetpoint", "value":heatingSetpoint, "unit":location.temperatureScale, displayed: false)
|
||||||
}
|
}
|
||||||
@@ -632,7 +628,7 @@ void raiseSetpoint() {
|
|||||||
targetvalue = thermostatSetpoint ? thermostatSetpoint : 0
|
targetvalue = thermostatSetpoint ? thermostatSetpoint : 0
|
||||||
targetvalue = location.temperatureScale == "F"? targetvalue + 1 : targetvalue + 0.5
|
targetvalue = location.temperatureScale == "F"? targetvalue + 1 : targetvalue + 0.5
|
||||||
|
|
||||||
if ((mode == "heat" || mode == "emergency heat") && targetvalue > maxHeatingSetpoint) { // emergency heat = auxHeatOnly
|
if ((mode == "heat" || mode == "auxheatonly") && targetvalue > maxHeatingSetpoint) {
|
||||||
targetvalue = maxHeatingSetpoint
|
targetvalue = maxHeatingSetpoint
|
||||||
} else if (mode == "cool" && targetvalue > maxCoolingSetpoint) {
|
} else if (mode == "cool" && targetvalue > maxCoolingSetpoint) {
|
||||||
targetvalue = maxCoolingSetpoint
|
targetvalue = maxCoolingSetpoint
|
||||||
@@ -678,7 +674,7 @@ void lowerSetpoint() {
|
|||||||
targetvalue = thermostatSetpoint ? thermostatSetpoint : 0
|
targetvalue = thermostatSetpoint ? thermostatSetpoint : 0
|
||||||
targetvalue = location.temperatureScale == "F"? targetvalue - 1 : targetvalue - 0.5
|
targetvalue = location.temperatureScale == "F"? targetvalue - 1 : targetvalue - 0.5
|
||||||
|
|
||||||
if ((mode == "heat" || mode == "emergency heat") && targetvalue < minHeatingSetpoint) { // emergency heat = auxHeatOnly
|
if ((mode == "heat" || mode == "auxheatonly") && targetvalue < minHeatingSetpoint) {
|
||||||
targetvalue = minHeatingSetpoint
|
targetvalue = minHeatingSetpoint
|
||||||
} else if (mode == "cool" && targetvalue < minCoolingSetpoint) {
|
} else if (mode == "cool" && targetvalue < minCoolingSetpoint) {
|
||||||
targetvalue = minCoolingSetpoint
|
targetvalue = minCoolingSetpoint
|
||||||
@@ -719,7 +715,7 @@ void alterSetpoint(temp) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//step1: check thermostatMode, enforce limits before sending request to cloud
|
//step1: check thermostatMode, enforce limits before sending request to cloud
|
||||||
if (mode == "heat" || mode == "emergency heat"){ // emergency heat = auxHeatOnly
|
if (mode == "heat" || mode == "auxheatonly"){
|
||||||
if (temp.value > coolingSetpoint){
|
if (temp.value > coolingSetpoint){
|
||||||
targetHeatingSetpoint = temp.value
|
targetHeatingSetpoint = temp.value
|
||||||
targetCoolingSetpoint = temp.value
|
targetCoolingSetpoint = temp.value
|
||||||
@@ -754,7 +750,7 @@ void alterSetpoint(temp) {
|
|||||||
log.debug "alterSetpoint in mode $mode succeed change setpoint to= ${temp.value}"
|
log.debug "alterSetpoint in mode $mode succeed change setpoint to= ${temp.value}"
|
||||||
} else {
|
} else {
|
||||||
log.error "Error alterSetpoint()"
|
log.error "Error alterSetpoint()"
|
||||||
if (mode == "heat" || mode == "emergency heat"){ // emergency heat = auxHeatOnly
|
if (mode == "heat" || mode == "auxheatonly"){
|
||||||
sendEvent("name": "thermostatSetpoint", "value": heatingSetpoint.toString(), displayed: false)
|
sendEvent("name": "thermostatSetpoint", "value": heatingSetpoint.toString(), displayed: false)
|
||||||
sendEvent("name": "displayThermostatSetpoint", "value": heatingSetpoint.toString(), displayed: false)
|
sendEvent("name": "displayThermostatSetpoint", "value": heatingSetpoint.toString(), displayed: false)
|
||||||
} else if (mode == "cool") {
|
} else if (mode == "cool") {
|
||||||
@@ -783,7 +779,7 @@ def generateStatusEvent() {
|
|||||||
log.debug "Cooling set point = ${coolingSetpoint}"
|
log.debug "Cooling set point = ${coolingSetpoint}"
|
||||||
log.debug "HVAC Mode = ${mode}"
|
log.debug "HVAC Mode = ${mode}"
|
||||||
|
|
||||||
if (mode == "heat") {
|
if (mode == "heat" || mode == "auxheatonly") {
|
||||||
if (temperature >= heatingSetpoint) {
|
if (temperature >= heatingSetpoint) {
|
||||||
statusText = "Right Now: Idle"
|
statusText = "Right Now: Idle"
|
||||||
} else {
|
} else {
|
||||||
@@ -806,8 +802,6 @@ def generateStatusEvent() {
|
|||||||
}
|
}
|
||||||
} else if (mode == "off") {
|
} else if (mode == "off") {
|
||||||
statusText = "Right Now: Off"
|
statusText = "Right Now: Off"
|
||||||
} else if (mode == "emergency heat") { // emergency heat = auxHeatOnly
|
|
||||||
statusText = "Emergency Heat"
|
|
||||||
} else {
|
} else {
|
||||||
statusText = "?"
|
statusText = "?"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -300,15 +300,21 @@ def setColor(value) {
|
|||||||
value.hex = "#${hex(value.red)}${hex(value.green)}${hex(value.blue)}"
|
value.hex = "#${hex(value.red)}${hex(value.green)}${hex(value.blue)}"
|
||||||
}
|
}
|
||||||
|
|
||||||
sendEvent(name: "hue", value: value.hue, displayed: false)
|
if(value.hue) {
|
||||||
sendEvent(name: "saturation", value: value.saturation, displayed: false)
|
sendEvent(name: "hue", value: value.hue, displayed: false)
|
||||||
sendEvent(name: "color", value: value.hex, displayed: false)
|
}
|
||||||
if (value.level) {
|
if(value.saturation) {
|
||||||
sendEvent(name: "level", value: value.level)
|
sendEvent(name: "saturation", value: value.saturation, displayed: false)
|
||||||
}
|
}
|
||||||
if (value.switch) {
|
if(value.hex?.trim()) {
|
||||||
sendEvent(name: "switch", value: value.switch)
|
sendEvent(name: "color", value: value.hex, displayed: false)
|
||||||
}
|
}
|
||||||
|
if (value.level) {
|
||||||
|
sendEvent(name: "level", value: value.level)
|
||||||
|
}
|
||||||
|
if (value.switch?.trim()) {
|
||||||
|
sendEvent(name: "switch", value: value.switch)
|
||||||
|
}
|
||||||
|
|
||||||
sendRGB(value.rh, value.gh, value.bh)
|
sendRGB(value.rh, value.gh, value.bh)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ metadata {
|
|||||||
|
|
||||||
// tile definitions
|
// tile definitions
|
||||||
tiles(scale: 2) {
|
tiles(scale: 2) {
|
||||||
multiAttributeTile(name:"valve", type: "generic", width: 6, height: 4, canChangeIcon: true){
|
multiAttributeTile(name:"contact", type: "generic", width: 6, height: 4, canChangeIcon: true){
|
||||||
tileAttribute ("device.valve", key: "PRIMARY_CONTROL") {
|
tileAttribute ("device.contact", key: "PRIMARY_CONTROL") {
|
||||||
attributeState "open", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#00A0DC", nextState:"closing"
|
attributeState "open", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#00A0DC", nextState:"closing"
|
||||||
attributeState "closed", label: '${name}', action: "valve.open", icon: "st.valves.water.closed", backgroundColor: "#ffffff", nextState:"opening"
|
attributeState "closed", label: '${name}', action: "valve.open", icon: "st.valves.water.closed", backgroundColor: "#ffffff", nextState:"opening"
|
||||||
attributeState "opening", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#00A0DC"
|
attributeState "opening", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#00A0DC"
|
||||||
@@ -48,14 +48,16 @@ metadata {
|
|||||||
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
|
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||||
}
|
}
|
||||||
|
|
||||||
main "valve"
|
main "contact"
|
||||||
details(["valve","refresh"])
|
details(["contact","refresh"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def installed(){
|
def installed(){
|
||||||
// Device-Watch simply pings if no device events received for 32min(checkInterval)
|
// Device-Watch simply pings if no device events received for 32min(checkInterval)
|
||||||
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
|
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
|
||||||
|
|
||||||
|
response(refresh())
|
||||||
}
|
}
|
||||||
|
|
||||||
def updated(){
|
def updated(){
|
||||||
@@ -85,11 +87,17 @@ def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def open() {
|
def open() {
|
||||||
zwave.switchBinaryV1.switchBinarySet(switchValue: 0x00).format()
|
delayBetween([
|
||||||
|
zwave.switchBinaryV1.switchBinarySet(switchValue: 0x00).format(),
|
||||||
|
zwave.switchBinaryV1.switchBinaryGet().format()
|
||||||
|
], 500)
|
||||||
}
|
}
|
||||||
|
|
||||||
def close() {
|
def close() {
|
||||||
zwave.switchBinaryV1.switchBinarySet(switchValue: 0xFF).format()
|
delayBetween([
|
||||||
|
zwave.switchBinaryV1.switchBinarySet(switchValue: 0xFF).format(),
|
||||||
|
zwave.switchBinaryV1.switchBinaryGet().format()
|
||||||
|
], 500)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -105,6 +113,6 @@ def refresh() {
|
|||||||
|
|
||||||
def createEventWithDebug(eventMap) {
|
def createEventWithDebug(eventMap) {
|
||||||
def event = createEvent(eventMap)
|
def event = createEvent(eventMap)
|
||||||
log.debug "Event created with ${event?.descriptionText}"
|
log.debug "Event created with ${event?.name}:${event?.value} - ${event?.descriptionText}"
|
||||||
return event
|
return event
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//DEPRECATED. INTEGRATION MOVED TO SUPER LAN CONNECT
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hue Bloom
|
* Hue Bloom
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//DEPRECATED. INTEGRATION MOVED TO SUPER LAN CONNECT
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hue Bridge
|
* Hue Bridge
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//DEPRECATED. INTEGRATION MOVED TO SUPER LAN CONNECT
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hue Bulb
|
* Hue Bulb
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//DEPRECATED. INTEGRATION MOVED TO SUPER LAN CONNECT
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hue Lux Bulb
|
* Hue Lux Bulb
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//DEPRECATED. INTEGRATION MOVED TO SUPER LAN CONNECT
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hue White Ambiance Bulb
|
* Hue White Ambiance Bulb
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ metadata {
|
|||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
capability "Health Check"
|
capability "Health Check"
|
||||||
capability "Light"
|
|
||||||
capability "Outlet"
|
capability "Outlet"
|
||||||
|
|
||||||
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3200", deviceJoinName: "Outlet"
|
fingerprint profileId: "0104", inClusters: "0000,0003,0004,0005,0006,0B04,0B05", outClusters: "0019", manufacturer: "CentraLite", model: "3200", deviceJoinName: "Outlet"
|
||||||
|
|||||||
@@ -49,6 +49,6 @@ def arrived() {
|
|||||||
|
|
||||||
|
|
||||||
def departed() {
|
def departed() {
|
||||||
log.trace "Executing 'arrived'"
|
log.trace "Executing 'departed'"
|
||||||
sendEvent(name: "presence", value: "not present")
|
sendEvent(name: "presence", value: "not present")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//DEPRECATED. INTEGRATION MOVED TO SUPER LAN CONNECT
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copyright 2015 SmartThings
|
* Copyright 2015 SmartThings
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//DEPRECATED. INTEGRATION MOVED TO SUPER LAN CONNECT
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copyright 2015 SmartThings
|
* Copyright 2015 SmartThings
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//DEPRECATED. INTEGRATION MOVED TO SUPER LAN CONNECT
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copyright 2015 SmartThings
|
* Copyright 2015 SmartThings
|
||||||
*
|
*
|
||||||
|
|||||||
2
devicetypes/smartthings/zigbee-button.src/.st-ignore
Normal file
2
devicetypes/smartthings/zigbee-button.src/.st-ignore
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
.st-ignore
|
||||||
|
README.md
|
||||||
43
devicetypes/smartthings/zigbee-button.src/README.md
Normal file
43
devicetypes/smartthings/zigbee-button.src/README.md
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# ZigBee Button
|
||||||
|
|
||||||
|
Cloud Execution
|
||||||
|
|
||||||
|
Works with:
|
||||||
|
|
||||||
|
* [OSRAM LIGHTIFY Dimming Switch](https://support.smartthings.com/hc/en-us/articles/115000236823-SYLVANIA-Dimming-Switch)
|
||||||
|
* [Iris Smart Button](https://support.smartthings.com/hc/en-us/articles/115000190186-Iris-Smart-Button)
|
||||||
|
* [Iris KeyFob](https://support.smartthings.com/hc/en-us/articles/217409686-Iris-Smart-Fob)
|
||||||
|
|
||||||
|
## Table of contents
|
||||||
|
|
||||||
|
* [Capabilities](#capabilities)
|
||||||
|
* [Health](#device-health)
|
||||||
|
* [Troubleshooting](#troubleshooting)
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
* **Actuator** - It represents that a device has commands.
|
||||||
|
* **Battery** - It defines that the device has a battery
|
||||||
|
* **Button** - It defines that a device has one or more buttons
|
||||||
|
* **Holdable Button** - It defines that a device has one or more holdable buttons
|
||||||
|
* **Configuration** - _configure()_ command called when device is installed or device preferences updated
|
||||||
|
* **Refresh** - _refresh()_ command for status updates
|
||||||
|
* **Sensor** - it represents that a Device has attributes.
|
||||||
|
* **Health Check** - indicates ability to get device health notifications
|
||||||
|
|
||||||
|
|
||||||
|
## Device Health
|
||||||
|
|
||||||
|
SmartThings platform will ping the device after `checkInterval` seconds of inactivity in last attempt to reach the device before marking it `OFFLINE`
|
||||||
|
|
||||||
|
* __722min__ checkInterval
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
If the device doesn't pair when trying from the SmartThings mobile app, it is possible that the device is out of range.
|
||||||
|
Pairing needs to be tried again by placing the device closer to the hub.
|
||||||
|
It may also happen that you need to reset the device.
|
||||||
|
Instructions related to pairing, resetting and removing the device from SmartThings can be found in the following link:
|
||||||
|
* [OSRAM LIGHTIFY Dimming Switch Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/115000236823-SYLVANIA-Dimming-Switch)
|
||||||
|
* [Iris Smart Button Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/115000190186-Iris-Smart-Button)
|
||||||
|
* [Iris KeyFob Troubleshooting Tips](https://support.smartthings.com/hc/en-us/articles/217409686-Iris-Smart-Fob)
|
||||||
@@ -24,6 +24,7 @@ metadata {
|
|||||||
capability "Configuration"
|
capability "Configuration"
|
||||||
capability "Refresh"
|
capability "Refresh"
|
||||||
capability "Sensor"
|
capability "Sensor"
|
||||||
|
capability "Health Check"
|
||||||
|
|
||||||
command "enrollResponse"
|
command "enrollResponse"
|
||||||
|
|
||||||
@@ -182,6 +183,13 @@ private Map parseNonIasButtonMessage(Map descMap){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PING is used by Device-Watch in attempt to reach the Device
|
||||||
|
* */
|
||||||
|
def ping() {
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
|
||||||
def refresh() {
|
def refresh() {
|
||||||
log.debug "Refreshing Battery"
|
log.debug "Refreshing Battery"
|
||||||
|
|
||||||
@@ -190,6 +198,8 @@ def refresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
|
// Device-Watch allows 2 check-in misses from device (plus 2 mins lag time)
|
||||||
|
sendEvent(name: "checkInterval", value: 2 * 6 * 60 * 60 + 2 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||||
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
log.debug "Configuring Reporting, IAS CIE, and Bindings."
|
||||||
def cmds = []
|
def cmds = []
|
||||||
if (device.getDataValue("model") == "3450-L") {
|
if (device.getDataValue("model") == "3450-L") {
|
||||||
|
|||||||
@@ -66,22 +66,29 @@ def parse(String description) {
|
|||||||
else {
|
else {
|
||||||
sendEvent(event)
|
sendEvent(event)
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
def descMap = zigbee.parseDescriptionAsMap(description)
|
||||||
def cluster = zigbee.parse(description)
|
if (descMap && descMap.clusterInt == 0x0006 && descMap.commandInt == 0x07) {
|
||||||
|
if (descMap.data[0] == "00") {
|
||||||
if (cluster && cluster.clusterId == 0x0006 && cluster.command == 0x07) {
|
|
||||||
if (cluster.data[0] == 0x00) {
|
|
||||||
log.debug "ON/OFF REPORTING CONFIG RESPONSE: " + cluster
|
log.debug "ON/OFF REPORTING CONFIG RESPONSE: " + cluster
|
||||||
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
sendEvent(name: "checkInterval", value: 60 * 12, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
log.warn "ON/OFF REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
|
log.warn "ON/OFF REPORTING CONFIG FAILED- error code:${cluster.data[0]}"
|
||||||
}
|
}
|
||||||
}
|
} else if (device.getDataValue("manufacturer") == "sengled" && descMap && descMap.clusterInt == 0x0008 && descMap.attrInt == 0x0000) {
|
||||||
else {
|
// This is being done because the sengled element touch/classic incorrectly uses the value 0xFF for the max level.
|
||||||
|
// Per the ZCL spec for the UINT8 data type 0xFF is an invalid value, and 0xFE should be the max. Here we
|
||||||
|
// manually handle the invalid attribute value since it will be ignored by getEvent as an invalid value.
|
||||||
|
// We also set the level of the bulb to 0xFE so future level reports will be 0xFE until it is changed by
|
||||||
|
// something else.
|
||||||
|
if (descMap.value.toUpperCase() == "FF") {
|
||||||
|
descMap.value = "FE"
|
||||||
|
}
|
||||||
|
sendHubCommand(zigbee.command(zigbee.LEVEL_CONTROL_CLUSTER, 0x00, "FE0000").collect { new physicalgraph.device.HubAction(it) }, 0)
|
||||||
|
sendEvent(zigbee.getEventFromAttrData(descMap.clusterInt, descMap.attrInt, descMap.encoding, descMap.value))
|
||||||
|
} else {
|
||||||
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
log.warn "DID NOT PARSE MESSAGE for description : $description"
|
||||||
log.debug "${cluster}"
|
log.debug "${descMap}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ metadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tiles(scale: 2) {
|
tiles(scale: 2) {
|
||||||
multiAttributeTile(name:"valve", type: "generic", width: 6, height: 4, canChangeIcon: true){
|
multiAttributeTile(name:"contact", type: "generic", width: 6, height: 4, canChangeIcon: true){
|
||||||
tileAttribute ("device.valve", key: "PRIMARY_CONTROL") {
|
tileAttribute ("device.contact", key: "PRIMARY_CONTROL") {
|
||||||
attributeState "open", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#00A0DC", nextState:"closing"
|
attributeState "open", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#00A0DC", nextState:"closing"
|
||||||
attributeState "closed", label: '${name}', action: "valve.open", icon: "st.valves.water.closed", backgroundColor: "#ffffff", nextState:"opening"
|
attributeState "closed", label: '${name}', action: "valve.open", icon: "st.valves.water.closed", backgroundColor: "#ffffff", nextState:"opening"
|
||||||
attributeState "opening", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#00A0DC", nextState:"closing"
|
attributeState "opening", label: '${name}', action: "valve.close", icon: "st.valves.water.open", backgroundColor: "#00A0DC", nextState:"closing"
|
||||||
@@ -58,8 +58,8 @@ metadata {
|
|||||||
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||||
}
|
}
|
||||||
|
|
||||||
main(["valve"])
|
main(["contact"])
|
||||||
details(["valve", "battery", "refresh"])
|
details(["contact", "battery", "refresh"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -88,11 +88,20 @@ def updated(){
|
|||||||
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
|
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def getCommandClassVersions() {
|
||||||
|
[
|
||||||
|
0x20: 1, // Basic
|
||||||
|
0x26: 1, // SwitchMultilevel
|
||||||
|
0x56: 1, // Crc16Encap
|
||||||
|
0x70: 1, // Configuration
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
def result = null
|
def result = null
|
||||||
if (description != "updated") {
|
if (description != "updated") {
|
||||||
log.debug "parse() >> zwave.parse($description)"
|
log.debug "parse() >> zwave.parse($description)"
|
||||||
def cmd = zwave.parse(description, [0x20: 1, 0x26: 1, 0x70: 1])
|
def cmd = zwave.parse(description, commandClassVersions)
|
||||||
if (cmd) {
|
if (cmd) {
|
||||||
result = zwaveEvent(cmd)
|
result = zwaveEvent(cmd)
|
||||||
}
|
}
|
||||||
@@ -159,6 +168,16 @@ def zwaveEvent(physicalgraph.zwave.commands.switchmultilevelv1.SwitchMultilevelS
|
|||||||
[createEvent(name:"switch", value:"on"), response(zwave.switchMultilevelV1.switchMultilevelGet().format())]
|
[createEvent(name:"switch", value:"on"), response(zwave.switchMultilevelV1.switchMultilevelGet().format())]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def zwaveEvent(physicalgraph.zwave.commands.crc16encapv1.Crc16Encap cmd) {
|
||||||
|
def versions = commandClassVersions
|
||||||
|
def version = versions[cmd.commandClass as Integer]
|
||||||
|
def ccObj = version ? zwave.commandClass(cmd.commandClass, version) : zwave.commandClass(cmd.commandClass)
|
||||||
|
def encapsulatedCommand = ccObj?.command(cmd.command)?.parse(cmd.data)
|
||||||
|
if (encapsulatedCommand) {
|
||||||
|
zwaveEvent(encapsulatedCommand)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
||||||
// Handles all Z-Wave commands we aren't interested in
|
// Handles all Z-Wave commands we aren't interested in
|
||||||
[:]
|
[:]
|
||||||
|
|||||||
@@ -85,11 +85,21 @@ metadata {
|
|||||||
details(["switch", "power", "energy", "levelSliderControl", "refresh", "reset"])
|
details(["switch", "power", "energy", "levelSliderControl", "refresh", "reset"])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def getCommandClassVersions() {
|
||||||
|
[
|
||||||
|
0x20: 1, // Basic
|
||||||
|
0x26: 3, // SwitchMultilevel
|
||||||
|
0x56: 1, // Crc16Encap
|
||||||
|
0x70: 1, // Configuration
|
||||||
|
0x32: 3, // Meter
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
// parse events into attributes
|
// parse events into attributes
|
||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
def result = null
|
def result = null
|
||||||
if (description != "updated") {
|
if (description != "updated") {
|
||||||
def cmd = zwave.parse(description, [0x20: 1, 0x26: 3, 0x70: 1, 0x32:3])
|
def cmd = zwave.parse(description, commandClassVersions)
|
||||||
if (cmd) {
|
if (cmd) {
|
||||||
result = zwaveEvent(cmd)
|
result = zwaveEvent(cmd)
|
||||||
log.debug("'$description' parsed to $result")
|
log.debug("'$description' parsed to $result")
|
||||||
@@ -124,6 +134,21 @@ def zwaveEvent(physicalgraph.zwave.commands.switchmultilevelv3.SwitchMultilevelR
|
|||||||
dimmerEvents(cmd)
|
dimmerEvents(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def zwaveEvent(physicalgraph.zwave.commands.crc16encapv1.Crc16Encap cmd) {
|
||||||
|
def versions = commandClassVersions
|
||||||
|
def version = versions[cmd.commandClass as Integer]
|
||||||
|
def ccObj = version ? zwave.commandClass(cmd.commandClass, version) : zwave.commandClass(cmd.commandClass)
|
||||||
|
def encapsulatedCommand = ccObj?.command(cmd.command)?.parse(cmd.data)
|
||||||
|
if (encapsulatedCommand) {
|
||||||
|
zwaveEvent(encapsulatedCommand)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
||||||
|
// Handles all Z-Wave commands we aren't interested in
|
||||||
|
[:]
|
||||||
|
}
|
||||||
|
|
||||||
def dimmerEvents(physicalgraph.zwave.Command cmd) {
|
def dimmerEvents(physicalgraph.zwave.Command cmd) {
|
||||||
def result = []
|
def result = []
|
||||||
def value = (cmd.value ? "on" : "off")
|
def value = (cmd.value ? "on" : "off")
|
||||||
|
|||||||
@@ -89,10 +89,19 @@ def updated() {
|
|||||||
} catch (e) { log.debug e }
|
} catch (e) { log.debug e }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def getCommandClassVersions() {
|
||||||
|
[
|
||||||
|
0x20: 1, // Basic
|
||||||
|
0x32: 1, // SwitchMultilevel
|
||||||
|
0x56: 1, // Crc16Encap
|
||||||
|
0x72: 2, // ManufacturerSpecific
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
def result = null
|
def result = null
|
||||||
if(description == "updated") return
|
if(description == "updated") return
|
||||||
def cmd = zwave.parse(description, [0x20: 1, 0x32: 1, 0x72: 2])
|
def cmd = zwave.parse(description, commandClassVersions)
|
||||||
if (cmd) {
|
if (cmd) {
|
||||||
result = zwaveEvent(cmd)
|
result = zwaveEvent(cmd)
|
||||||
}
|
}
|
||||||
@@ -157,6 +166,16 @@ def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerS
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def zwaveEvent(physicalgraph.zwave.commands.crc16encapv1.Crc16Encap cmd) {
|
||||||
|
def versions = commandClassVersions
|
||||||
|
def version = versions[cmd.commandClass as Integer]
|
||||||
|
def ccObj = version ? zwave.commandClass(cmd.commandClass, version) : zwave.commandClass(cmd.commandClass)
|
||||||
|
def encapsulatedCommand = ccObj?.command(cmd.command)?.parse(cmd.data)
|
||||||
|
if (encapsulatedCommand) {
|
||||||
|
zwaveEvent(encapsulatedCommand)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
||||||
log.debug "$device.displayName: Unhandled: $cmd"
|
log.debug "$device.displayName: Unhandled: $cmd"
|
||||||
[:]
|
[:]
|
||||||
|
|||||||
@@ -71,9 +71,17 @@ def updated(){
|
|||||||
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
|
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "zwave", hubHardwareId: device.hub.hardwareID])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def getCommandClassVersions() {
|
||||||
|
[
|
||||||
|
0x20: 1, // Basic
|
||||||
|
0x56: 1, // Crc16Encap
|
||||||
|
0x70: 1, // Configuration
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
def result = null
|
def result = null
|
||||||
def cmd = zwave.parse(description, [0x20: 1, 0x70: 1])
|
def cmd = zwave.parse(description, commandClassVersions)
|
||||||
if (cmd) {
|
if (cmd) {
|
||||||
result = createEvent(zwaveEvent(cmd))
|
result = createEvent(zwaveEvent(cmd))
|
||||||
}
|
}
|
||||||
@@ -120,6 +128,15 @@ def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerS
|
|||||||
createEvent([descriptionText: "$device.displayName MSR: $msr", isStateChange: false])
|
createEvent([descriptionText: "$device.displayName MSR: $msr", isStateChange: false])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def zwaveEvent(physicalgraph.zwave.commands.crc16encapv1.Crc16Encap cmd) {
|
||||||
|
def versions = commandClassVersions
|
||||||
|
def version = versions[cmd.commandClass as Integer]
|
||||||
|
def ccObj = version ? zwave.commandClass(cmd.commandClass, version) : zwave.commandClass(cmd.commandClass)
|
||||||
|
def encapsulatedCommand = ccObj?.command(cmd.command)?.parse(cmd.data)
|
||||||
|
if (encapsulatedCommand) {
|
||||||
|
zwaveEvent(encapsulatedCommand)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
||||||
// Handles all Z-Wave commands we aren't interested in
|
// Handles all Z-Wave commands we aren't interested in
|
||||||
|
|||||||
@@ -89,9 +89,17 @@ def updated(){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def getCommandClassVersions() {
|
||||||
|
[
|
||||||
|
0x20: 1, // Basic
|
||||||
|
0x56: 1, // Crc16Encap
|
||||||
|
0x70: 1, // Configuration
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
def result = null
|
def result = null
|
||||||
def cmd = zwave.parse(description, [0x20: 1, 0x70: 1])
|
def cmd = zwave.parse(description, commandClassVersions)
|
||||||
if (cmd) {
|
if (cmd) {
|
||||||
result = createEvent(zwaveEvent(cmd))
|
result = createEvent(zwaveEvent(cmd))
|
||||||
}
|
}
|
||||||
@@ -138,6 +146,16 @@ def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerS
|
|||||||
createEvent([descriptionText: "$device.displayName MSR: $msr", isStateChange: false])
|
createEvent([descriptionText: "$device.displayName MSR: $msr", isStateChange: false])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def zwaveEvent(physicalgraph.zwave.commands.crc16encapv1.Crc16Encap cmd) {
|
||||||
|
def versions = commandClassVersions
|
||||||
|
def version = versions[cmd.commandClass as Integer]
|
||||||
|
def ccObj = version ? zwave.commandClass(cmd.commandClass, version) : zwave.commandClass(cmd.commandClass)
|
||||||
|
def encapsulatedCommand = ccObj?.command(cmd.command)?.parse(cmd.data)
|
||||||
|
if (encapsulatedCommand) {
|
||||||
|
zwaveEvent(encapsulatedCommand)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
||||||
// Handles all Z-Wave commands we aren't interested in
|
// Handles all Z-Wave commands we aren't interested in
|
||||||
|
|||||||
409
smartapps/erocm123/sonoff-connect.src/sonoff-connect.groovy
Normal file
409
smartapps/erocm123/sonoff-connect.src/sonoff-connect.groovy
Normal file
@@ -0,0 +1,409 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2016 Eric Maycock
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* Sonoff (Connect)
|
||||||
|
*
|
||||||
|
* Author: Eric Maycock (erocm123)
|
||||||
|
* Date: 2016-06-02
|
||||||
|
*/
|
||||||
|
|
||||||
|
definition(
|
||||||
|
name: "Sonoff (Connect)",
|
||||||
|
namespace: "erocm123",
|
||||||
|
author: "Eric Maycock (erocm123)",
|
||||||
|
description: "Service Manager for Sonoff switches",
|
||||||
|
category: "Convenience",
|
||||||
|
iconUrl: "https://raw.githubusercontent.com/erocm123/SmartThingsPublic/master/smartapps/erocm123/sonoff-connect.src/sonoff-connect-icon.png",
|
||||||
|
iconX2Url: "https://raw.githubusercontent.com/erocm123/SmartThingsPublic/master/smartapps/erocm123/sonoff-connect.src/sonoff-connect-icon-2x.png",
|
||||||
|
iconX3Url: "https://raw.githubusercontent.com/erocm123/SmartThingsPublic/master/smartapps/erocm123/sonoff-connect.src/sonoff-connect-icon-3x.png"
|
||||||
|
)
|
||||||
|
|
||||||
|
preferences {
|
||||||
|
page(name: "mainPage")
|
||||||
|
page(name: "configurePDevice")
|
||||||
|
page(name: "deletePDevice")
|
||||||
|
page(name: "changeName")
|
||||||
|
page(name: "discoveryPage", title: "Device Discovery", content: "discoveryPage", refreshTimeout:5)
|
||||||
|
page(name: "addDevices", title: "Add Sonoff Switches", content: "addDevices")
|
||||||
|
page(name: "manuallyAdd")
|
||||||
|
page(name: "manuallyAddConfirm")
|
||||||
|
page(name: "deviceDiscovery")
|
||||||
|
}
|
||||||
|
|
||||||
|
def mainPage() {
|
||||||
|
dynamicPage(name: "mainPage", title: "Manage your Sonoff switches", nextPage: null, uninstall: true, install: true) {
|
||||||
|
section("Configure"){
|
||||||
|
href "deviceDiscovery", title:"Discover Devices", description:""
|
||||||
|
href "manuallyAdd", title:"Manually Add Device", description:""
|
||||||
|
}
|
||||||
|
section("Installed Devices"){
|
||||||
|
getChildDevices().sort({ a, b -> a["deviceNetworkId"] <=> b["deviceNetworkId"] }).each {
|
||||||
|
href "configurePDevice", title:"$it.label", description:"", params: [did: it.deviceNetworkId]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def configurePDevice(params){
|
||||||
|
if (params?.did || params?.params?.did) {
|
||||||
|
if (params.did) {
|
||||||
|
state.currentDeviceId = params.did
|
||||||
|
state.currentDisplayName = getChildDevice(params.did)?.displayName
|
||||||
|
} else {
|
||||||
|
state.currentDeviceId = params.params.did
|
||||||
|
state.currentDisplayName = getChildDevice(params.params.did)?.displayName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (getChildDevice(state.currentDeviceId) != null) getChildDevice(state.currentDeviceId).configure()
|
||||||
|
dynamicPage(name: "configurePDevice", title: "Configure Sonoff Switches created with this app", nextPage: null) {
|
||||||
|
section {
|
||||||
|
app.updateSetting("${state.currentDeviceId}_label", getChildDevice(state.currentDeviceId).label)
|
||||||
|
input "${state.currentDeviceId}_label", "text", title:"Device Name", description: "", required: false
|
||||||
|
href "changeName", title:"Change Device Name", description: "Edit the name above and click here to change it"
|
||||||
|
}
|
||||||
|
section {
|
||||||
|
href "deletePDevice", title:"Delete $state.currentDisplayName", description: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def manuallyAdd(){
|
||||||
|
dynamicPage(name: "manuallyAdd", title: "Manually add a Sonoff device", nextPage: "manuallyAddConfirm") {
|
||||||
|
section {
|
||||||
|
paragraph "This process will manually create a Sonoff device based on the entered IP address. The SmartApp needs to then communicate with the device to obtain additional information from it. Make sure the device is on and connected to your wifi network."
|
||||||
|
input "deviceType", "enum", title:"Device Type", description: "", required: false, options: ["Sonoff Wifi Switch","Sonoff TH Wifi Switch","Sonoff POW Wifi Switch","Sonoff Dual Wifi Switch","Sonoff 4CH Wifi Switch"]
|
||||||
|
input "ipAddress", "text", title:"IP Address", description: "", required: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def manuallyAddConfirm(){
|
||||||
|
if ( ipAddress =~ /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/) {
|
||||||
|
log.debug "Creating Sonoff Wifi Switch with dni: ${convertIPtoHex(ipAddress)}:${convertPortToHex("80")}"
|
||||||
|
addChildDevice("erocm123", deviceType ? deviceType : "Sonoff Wifi Switch", "${convertIPtoHex(ipAddress)}:${convertPortToHex("80")}", location.hubs[0].id, [
|
||||||
|
"label": (deviceType ? deviceType : "Sonoff Wifi Switch") + " (${ipAddress})",
|
||||||
|
"data": [
|
||||||
|
"ip": ipAddress,
|
||||||
|
"port": "80"
|
||||||
|
]
|
||||||
|
])
|
||||||
|
|
||||||
|
app.updateSetting("ipAddress", "")
|
||||||
|
|
||||||
|
dynamicPage(name: "manuallyAddConfirm", title: "Manually add a Sonoff device", nextPage: "mainPage") {
|
||||||
|
section {
|
||||||
|
paragraph "The device has been added. Press next to return to the main page."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dynamicPage(name: "manuallyAddConfirm", title: "Manually add a Sonoff device", nextPage: "mainPage") {
|
||||||
|
section {
|
||||||
|
paragraph "The entered ip address is not valid. Please try again."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def deletePDevice(){
|
||||||
|
try {
|
||||||
|
unsubscribe()
|
||||||
|
deleteChildDevice(state.currentDeviceId)
|
||||||
|
dynamicPage(name: "deletePDevice", title: "Deletion Summary", nextPage: "mainPage") {
|
||||||
|
section {
|
||||||
|
paragraph "The device has been deleted. Press next to continue"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
dynamicPage(name: "deletePDevice", title: "Deletion Summary", nextPage: "mainPage") {
|
||||||
|
section {
|
||||||
|
paragraph "Error: ${(e as String).split(":")[1]}."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def changeName(){
|
||||||
|
def thisDevice = getChildDevice(state.currentDeviceId)
|
||||||
|
thisDevice.label = settings["${state.currentDeviceId}_label"]
|
||||||
|
|
||||||
|
dynamicPage(name: "changeName", title: "Change Name Summary", nextPage: "mainPage") {
|
||||||
|
section {
|
||||||
|
paragraph "The device has been renamed. Press \"Next\" to continue"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def discoveryPage(){
|
||||||
|
return deviceDiscovery()
|
||||||
|
}
|
||||||
|
|
||||||
|
def deviceDiscovery(params=[:])
|
||||||
|
{
|
||||||
|
def devices = devicesDiscovered()
|
||||||
|
|
||||||
|
int deviceRefreshCount = !state.deviceRefreshCount ? 0 : state.deviceRefreshCount as int
|
||||||
|
state.deviceRefreshCount = deviceRefreshCount + 1
|
||||||
|
def refreshInterval = 3
|
||||||
|
|
||||||
|
def options = devices ?: []
|
||||||
|
def numFound = options.size() ?: 0
|
||||||
|
|
||||||
|
if ((numFound == 0 && state.deviceRefreshCount > 25) || params.reset == "true") {
|
||||||
|
log.trace "Cleaning old device memory"
|
||||||
|
state.devices = [:]
|
||||||
|
state.deviceRefreshCount = 0
|
||||||
|
app.updateSetting("selectedDevice", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
ssdpSubscribe()
|
||||||
|
|
||||||
|
//sonoff discovery request every 15 //25 seconds
|
||||||
|
if((deviceRefreshCount % 5) == 0) {
|
||||||
|
discoverDevices()
|
||||||
|
}
|
||||||
|
|
||||||
|
//setup.xml request every 3 seconds except on discoveries
|
||||||
|
if(((deviceRefreshCount % 3) == 0) && ((deviceRefreshCount % 5) != 0)) {
|
||||||
|
verifyDevices()
|
||||||
|
}
|
||||||
|
|
||||||
|
return dynamicPage(name:"deviceDiscovery", title:"Discovery Started!", nextPage:"addDevices", refreshInterval:refreshInterval, uninstall: true) {
|
||||||
|
section("Please wait while we discover your Sonoff devices. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
|
||||||
|
input "selectedDevices", "enum", required:false, title:"Select Sonoff Switch (${numFound} found)", multiple:true, options:options
|
||||||
|
}
|
||||||
|
section("Options") {
|
||||||
|
href "deviceDiscovery", title:"Reset list of discovered devices", description:"", params: ["reset": "true"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map devicesDiscovered() {
|
||||||
|
def vdevices = getVerifiedDevices()
|
||||||
|
def map = [:]
|
||||||
|
vdevices.each {
|
||||||
|
def value = "${it.value.name}"
|
||||||
|
def key = "${it.value.mac}"
|
||||||
|
map["${key}"] = value
|
||||||
|
}
|
||||||
|
map
|
||||||
|
}
|
||||||
|
|
||||||
|
def getVerifiedDevices() {
|
||||||
|
getDevices().findAll{ it?.value?.verified == true }
|
||||||
|
}
|
||||||
|
|
||||||
|
private discoverDevices() {
|
||||||
|
sendHubCommand(new physicalgraph.device.HubAction("lan discovery urn:schemas-upnp-org:device:Basic:1", physicalgraph.device.Protocol.LAN))
|
||||||
|
}
|
||||||
|
|
||||||
|
def configured() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
def buttonConfigured(idx) {
|
||||||
|
return settings["lights_$idx"]
|
||||||
|
}
|
||||||
|
|
||||||
|
def isConfigured(){
|
||||||
|
if(getChildDevices().size() > 0) return true else return false
|
||||||
|
}
|
||||||
|
|
||||||
|
def isVirtualConfigured(did){
|
||||||
|
def foundDevice = false
|
||||||
|
getChildDevices().each {
|
||||||
|
if(it.deviceNetworkId != null){
|
||||||
|
if(it.deviceNetworkId.startsWith("${did}/")) foundDevice = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return foundDevice
|
||||||
|
}
|
||||||
|
|
||||||
|
private virtualCreated(number) {
|
||||||
|
if (getChildDevice(getDeviceID(number))) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getDeviceID(number) {
|
||||||
|
return "${state.currentDeviceId}/${app.id}/${number}"
|
||||||
|
}
|
||||||
|
|
||||||
|
def installed() {
|
||||||
|
initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
def updated() {
|
||||||
|
unsubscribe()
|
||||||
|
unschedule()
|
||||||
|
initialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
def initialize() {
|
||||||
|
ssdpSubscribe()
|
||||||
|
runEvery5Minutes("ssdpDiscover")
|
||||||
|
}
|
||||||
|
|
||||||
|
void ssdpSubscribe() {
|
||||||
|
subscribe(location, "ssdpTerm.urn:schemas-upnp-org:device:Basic:1", ssdpHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
void ssdpDiscover() {
|
||||||
|
sendHubCommand(new physicalgraph.device.HubAction("lan discovery urn:schemas-upnp-org:device:Basic:1", physicalgraph.device.Protocol.LAN))
|
||||||
|
}
|
||||||
|
|
||||||
|
def ssdpHandler(evt) {
|
||||||
|
def description = evt.description
|
||||||
|
def hub = evt?.hubId
|
||||||
|
def parsedEvent = parseLanMessage(description)
|
||||||
|
parsedEvent << ["hub":hub]
|
||||||
|
|
||||||
|
def devices = getDevices()
|
||||||
|
|
||||||
|
String ssdpUSN = parsedEvent.ssdpUSN.toString()
|
||||||
|
|
||||||
|
if (devices."${ssdpUSN}") {
|
||||||
|
def d = devices."${ssdpUSN}"
|
||||||
|
def child = getChildDevice(parsedEvent.mac)
|
||||||
|
def childIP
|
||||||
|
def childPort
|
||||||
|
if (child) {
|
||||||
|
childIP = child.getDeviceDataByName("ip")
|
||||||
|
childPort = child.getDeviceDataByName("port").toString()
|
||||||
|
log.debug "Device data: ($childIP:$childPort) - reporting data: (${convertHexToIP(parsedEvent.networkAddress)}:${convertHexToInt(parsedEvent.deviceAddress)})."
|
||||||
|
if(childIP != convertHexToIP(parsedEvent.networkAddress) || childPort != convertHexToInt(parsedEvent.deviceAddress).toString()){
|
||||||
|
log.debug "Device data (${child.getDeviceDataByName("ip")}) does not match what it is reporting(${convertHexToIP(parsedEvent.networkAddress)}). Attempting to update."
|
||||||
|
child.sync(convertHexToIP(parsedEvent.networkAddress), convertHexToInt(parsedEvent.deviceAddress).toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (d.networkAddress != parsedEvent.networkAddress || d.deviceAddress != parsedEvent.deviceAddress) {
|
||||||
|
d.networkAddress = parsedEvent.networkAddress
|
||||||
|
d.deviceAddress = parsedEvent.deviceAddress
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
devices << ["${ssdpUSN}": parsedEvent]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void verifyDevices() {
|
||||||
|
def devices = getDevices().findAll { it?.value?.verified != true }
|
||||||
|
devices.each {
|
||||||
|
def ip = convertHexToIP(it.value.networkAddress)
|
||||||
|
def port = convertHexToInt(it.value.deviceAddress)
|
||||||
|
String host = "${ip}:${port}"
|
||||||
|
sendHubCommand(new physicalgraph.device.HubAction("""GET ${it.value.ssdpPath} HTTP/1.1\r\nHOST: $host\r\n\r\n""", physicalgraph.device.Protocol.LAN, host, [callback: deviceDescriptionHandler]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def getDevices() {
|
||||||
|
state.devices = state.devices ?: [:]
|
||||||
|
}
|
||||||
|
|
||||||
|
void deviceDescriptionHandler(physicalgraph.device.HubResponse hubResponse) {
|
||||||
|
log.trace "description.xml response (application/xml)"
|
||||||
|
def body = hubResponse.xml
|
||||||
|
log.debug body?.device?.friendlyName?.text()
|
||||||
|
if (body?.device?.modelName?.text().startsWith("Sonoff")) {
|
||||||
|
def devices = getDevices()
|
||||||
|
def device = devices.find {it?.key?.contains(body?.device?.UDN?.text())}
|
||||||
|
if (device) {
|
||||||
|
device.value << [name:body?.device?.friendlyName?.text() + " (" + convertHexToIP(hubResponse.ip) + ")", serialNumber:body?.device?.serialNumber?.text(), verified: true]
|
||||||
|
} else {
|
||||||
|
log.error "/description.xml returned a device that didn't exist"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def addDevices() {
|
||||||
|
def devices = getDevices()
|
||||||
|
def sectionText = ""
|
||||||
|
|
||||||
|
selectedDevices.each { dni ->bridgeLinking
|
||||||
|
def selectedDevice = devices.find { it.value.mac == dni }
|
||||||
|
def d
|
||||||
|
if (selectedDevice) {
|
||||||
|
d = getChildDevices()?.find {
|
||||||
|
it.deviceNetworkId == selectedDevice.value.mac
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!d) {
|
||||||
|
log.debug selectedDevice
|
||||||
|
log.debug "Creating Sonoff Switch with dni: ${selectedDevice.value.mac}"
|
||||||
|
|
||||||
|
def deviceHandlerName
|
||||||
|
if (selectedDevice?.value?.name?.startsWith("Sonoff TH"))
|
||||||
|
deviceHandlerName = "Sonoff TH Wifi Switch"
|
||||||
|
else if (selectedDevice?.value?.name?.startsWith("Sonoff POW"))
|
||||||
|
deviceHandlerName = "Sonoff POW Wifi Switch"
|
||||||
|
else if (selectedDevice?.value?.name?.startsWith("Sonoff Dual"))
|
||||||
|
deviceHandlerName = "Sonoff Dual Wifi Switch"
|
||||||
|
else if (selectedDevice?.value?.name?.startsWith("Sonoff 4CH"))
|
||||||
|
deviceHandlerName = "Sonoff 4CH Wifi Switch"
|
||||||
|
else
|
||||||
|
deviceHandlerName = "Sonoff Wifi Switch"
|
||||||
|
def newDevice = addChildDevice("erocm123", deviceHandlerName, selectedDevice.value.mac, selectedDevice?.value.hub, [
|
||||||
|
"label": selectedDevice?.value?.name ?: "Sonoff Wifi Switch",
|
||||||
|
"data": [
|
||||||
|
"mac": selectedDevice.value.mac,
|
||||||
|
"ip": convertHexToIP(selectedDevice.value.networkAddress),
|
||||||
|
"port": "" + Integer.parseInt(selectedDevice.value.deviceAddress,16)
|
||||||
|
]
|
||||||
|
])
|
||||||
|
sectionText = sectionText + "Succesfully added Sonoff device with ip address ${convertHexToIP(selectedDevice.value.networkAddress)} \r\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
log.debug sectionText
|
||||||
|
return dynamicPage(name:"addDevices", title:"Devices Added", nextPage:"mainPage", uninstall: true) {
|
||||||
|
if(sectionText != ""){
|
||||||
|
section("Add Sonoff Results:") {
|
||||||
|
paragraph sectionText
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
section("No devices added") {
|
||||||
|
paragraph "All selected devices have previously been added"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def uninstalled() {
|
||||||
|
unsubscribe()
|
||||||
|
getChildDevices().each {
|
||||||
|
deleteChildDevice(it.deviceNetworkId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private String convertHexToIP(hex) {
|
||||||
|
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer convertHexToInt(hex) {
|
||||||
|
Integer.parseInt(hex,16)
|
||||||
|
}
|
||||||
|
|
||||||
|
private String convertIPtoHex(ipAddress) {
|
||||||
|
String hex = ipAddress.tokenize( '.' ).collect { String.format( '%02x', it.toInteger() ) }.join()
|
||||||
|
return hex
|
||||||
|
}
|
||||||
|
|
||||||
|
private String convertPortToHex(port) {
|
||||||
|
String hexport = port.toString().format( '%04x', port.toInteger() )
|
||||||
|
return hexport
|
||||||
|
}
|
||||||
@@ -195,7 +195,10 @@ def registerDeviceChange() {
|
|||||||
state.deviceSubscriptionMap.put(deviceId, [subscriptionEndpt])
|
state.deviceSubscriptionMap.put(deviceId, [subscriptionEndpt])
|
||||||
log.info "Added subscription URL: ${subscriptionEndpt} for ${myDevice.displayName}"
|
log.info "Added subscription URL: ${subscriptionEndpt} for ${myDevice.displayName}"
|
||||||
} else if (!state.deviceSubscriptionMap[deviceId].contains(subscriptionEndpt)) {
|
} else if (!state.deviceSubscriptionMap[deviceId].contains(subscriptionEndpt)) {
|
||||||
state.deviceSubscriptionMap[deviceId] << subscriptionEndpt
|
// state.deviceSubscriptionMap[deviceId] << subscriptionEndpt
|
||||||
|
// For now, we will only have one subscription endpoint per device
|
||||||
|
state.deviceSubscriptionMap.remove(deviceId)
|
||||||
|
state.deviceSubscriptionMap.put(deviceId, [subscriptionEndpt])
|
||||||
log.info "Added subscription URL: ${subscriptionEndpt} for ${myDevice.displayName}"
|
log.info "Added subscription URL: ${subscriptionEndpt} for ${myDevice.displayName}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//DEPRECATED. INTEGRATION MOVED TO SUPER LAN CONNECT
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bose SoundTouch (Connect)
|
* Bose SoundTouch (Connect)
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//DEPRECATED. INTEGRATION MOVED TO SUPER LAN CONNECT
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hue Service Manager
|
* Hue Service Manager
|
||||||
*
|
*
|
||||||
@@ -1083,7 +1085,6 @@ def on(childDevice) {
|
|||||||
log.debug "Executing 'on'"
|
log.debug "Executing 'on'"
|
||||||
def id = getId(childDevice)
|
def id = getId(childDevice)
|
||||||
updateInProgress()
|
updateInProgress()
|
||||||
createSwitchEvent(childDevice, "on")
|
|
||||||
put("lights/$id/state", [on: true])
|
put("lights/$id/state", [on: true])
|
||||||
return "Bulb is turning On"
|
return "Bulb is turning On"
|
||||||
}
|
}
|
||||||
@@ -1092,7 +1093,6 @@ def off(childDevice) {
|
|||||||
log.debug "Executing 'off'"
|
log.debug "Executing 'off'"
|
||||||
def id = getId(childDevice)
|
def id = getId(childDevice)
|
||||||
updateInProgress()
|
updateInProgress()
|
||||||
createSwitchEvent(childDevice, "off")
|
|
||||||
put("lights/$id/state", [on: false])
|
put("lights/$id/state", [on: false])
|
||||||
return "Bulb is turning Off"
|
return "Bulb is turning Off"
|
||||||
}
|
}
|
||||||
@@ -1108,8 +1108,6 @@ def setLevel(childDevice, percent) {
|
|||||||
else
|
else
|
||||||
level = Math.min(Math.round(percent * 254 / 100), 254)
|
level = Math.min(Math.round(percent * 254 / 100), 254)
|
||||||
|
|
||||||
createSwitchEvent(childDevice, level > 0, percent)
|
|
||||||
|
|
||||||
// For Zigbee lights, if level is set to 0 ST just turns them off without changing level
|
// For Zigbee lights, if level is set to 0 ST just turns them off without changing level
|
||||||
// that means that the light will still be on when on is called next time
|
// that means that the light will still be on when on is called next time
|
||||||
// Lets emulate that here
|
// Lets emulate that here
|
||||||
@@ -1128,7 +1126,6 @@ def setSaturation(childDevice, percent) {
|
|||||||
// 0 - 254
|
// 0 - 254
|
||||||
def level = Math.min(Math.round(percent * 254 / 100), 254)
|
def level = Math.min(Math.round(percent * 254 / 100), 254)
|
||||||
// TODO should this be done by app only or should we default to on?
|
// TODO should this be done by app only or should we default to on?
|
||||||
createSwitchEvent(childDevice, "on")
|
|
||||||
put("lights/$id/state", [sat: level, on: true])
|
put("lights/$id/state", [sat: level, on: true])
|
||||||
return "Setting saturation to $percent"
|
return "Setting saturation to $percent"
|
||||||
}
|
}
|
||||||
@@ -1140,7 +1137,6 @@ def setHue(childDevice, percent) {
|
|||||||
// 0 - 65535
|
// 0 - 65535
|
||||||
def level = Math.min(Math.round(percent * 65535 / 100), 65535)
|
def level = Math.min(Math.round(percent * 65535 / 100), 65535)
|
||||||
// TODO should this be done by app only or should we default to on?
|
// TODO should this be done by app only or should we default to on?
|
||||||
createSwitchEvent(childDevice, "on")
|
|
||||||
put("lights/$id/state", [hue: level, on: true])
|
put("lights/$id/state", [hue: level, on: true])
|
||||||
return "Setting hue to $percent"
|
return "Setting hue to $percent"
|
||||||
}
|
}
|
||||||
@@ -1151,7 +1147,6 @@ def setColorTemperature(childDevice, huesettings) {
|
|||||||
updateInProgress()
|
updateInProgress()
|
||||||
// 153 (6500K) to 500 (2000K)
|
// 153 (6500K) to 500 (2000K)
|
||||||
def ct = hueSettings == 6500 ? 153 : Math.round(1000000 / huesettings)
|
def ct = hueSettings == 6500 ? 153 : Math.round(1000000 / huesettings)
|
||||||
createSwitchEvent(childDevice, "on")
|
|
||||||
put("lights/$id/state", [ct: ct, on: true])
|
put("lights/$id/state", [ct: ct, on: true])
|
||||||
return "Setting color temperature to $ct"
|
return "Setting color temperature to $ct"
|
||||||
}
|
}
|
||||||
@@ -1210,7 +1205,6 @@ def setColor(childDevice, huesettings) {
|
|||||||
if (huesettings.switch == "off")
|
if (huesettings.switch == "off")
|
||||||
value.on = false
|
value.on = false
|
||||||
|
|
||||||
createSwitchEvent(childDevice, value.on ? "on" : "off")
|
|
||||||
put("lights/$id/state", value)
|
put("lights/$id/state", value)
|
||||||
return "Setting color to $value"
|
return "Setting color to $value"
|
||||||
}
|
}
|
||||||
@@ -1322,32 +1316,6 @@ private List getRealHubFirmwareVersions() {
|
|||||||
return location.hubs*.firmwareVersionString.findAll { it }
|
return location.hubs*.firmwareVersionString.findAll { it }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends appropriate turningOn/turningOff state events depending on switch or level changes.
|
|
||||||
*
|
|
||||||
* @param childDevice device to send event for
|
|
||||||
* @param setSwitch The new switch state, "on" or "off"
|
|
||||||
* @param setLevel Optional, switchLevel between 0-100, used if you set level to 0 for example since
|
|
||||||
* that should generate "off" instead of level change
|
|
||||||
*/
|
|
||||||
private void createSwitchEvent(childDevice, setSwitch, setLevel = null) {
|
|
||||||
|
|
||||||
if (setLevel == null) {
|
|
||||||
setLevel = childDevice.device?.currentValue("level")
|
|
||||||
}
|
|
||||||
// Create on, off, turningOn or turningOff event as necessary
|
|
||||||
def currentState = childDevice.device?.currentValue("switch")
|
|
||||||
if ((currentState == "off" || currentState == "turningOff")) {
|
|
||||||
if (setSwitch == "on" || setLevel > 0) {
|
|
||||||
childDevice.sendEvent(name: "switch", value: "turningOn", displayed: false)
|
|
||||||
}
|
|
||||||
} else if ((currentState == "on" || currentState == "turningOn")) {
|
|
||||||
if (setSwitch == "off" || setLevel == 0) {
|
|
||||||
childDevice.sendEvent(name: "switch", value: "turningOff", displayed: false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the supported color range for different Hue lights. If model is not specified
|
* Return the supported color range for different Hue lights. If model is not specified
|
||||||
* it defaults to the smallest Gamut (B) to ensure that colors set on a mix of devices
|
* it defaults to the smallest Gamut (B) to ensure that colors set on a mix of devices
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
//DEPRECATED. INTEGRATION MOVED TO SUPER LAN CONNECT
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copyright 2015 SmartThings
|
* Copyright 2015 SmartThings
|
||||||
*
|
*
|
||||||
|
|||||||
Reference in New Issue
Block a user