mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-20 05:10:51 +00:00
Compare commits
2 Commits
MSA-1774-1
...
PROD_2017.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc312286a2 | ||
|
|
0846b6f34c |
@@ -40,11 +40,14 @@ metadata {
|
|||||||
|
|
||||||
// Parse incoming device messages to generate events
|
// Parse incoming device messages to generate events
|
||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
def resMap
|
def name = null
|
||||||
if (description.startsWith("zone")) {
|
def value = description
|
||||||
resMap = createEvent(name: "contact", value: zigbee.parseZoneStatus(description).isAlarm1Set() ? "open" : "closed")
|
if (zigbee.isZoneType19(description)) {
|
||||||
|
name = "contact"
|
||||||
|
value = zigbee.translateStatusZoneType19(description) ? "open" : "closed"
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug "Parse returned $resMap"
|
def result = createEvent(name: name, value: value)
|
||||||
return resMap
|
log.debug "Parse returned ${result?.descriptionText}"
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ def parse(String description) {
|
|||||||
log.debug "parse($description)"
|
log.debug "parse($description)"
|
||||||
def results = [:]
|
def results = [:]
|
||||||
|
|
||||||
if (!isSupportedDescription(description) || description.startsWith("zone")) {
|
if (!isSupportedDescription(description) || zigbee.isZoneType19(description)) {
|
||||||
// Ignore this in favor of orientation-based state
|
// Ignore this in favor of orientation-based state
|
||||||
// results = parseSingleMessage(description)
|
// results = parseSingleMessage(description)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ def parse(String description) {
|
|||||||
log.debug "parse($description)"
|
log.debug "parse($description)"
|
||||||
def results = null
|
def results = null
|
||||||
|
|
||||||
if (!isSupportedDescription(description) || description.startsWith("zone")) {
|
if (!isSupportedDescription(description) || zigbee.isZoneType19(description)) {
|
||||||
// Ignore this in favor of orientation-based state
|
// Ignore this in favor of orientation-based state
|
||||||
// results = parseSingleMessage(description)
|
// results = parseSingleMessage(description)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ metadata {
|
|||||||
|
|
||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
def results = [:]
|
def results = [:]
|
||||||
if (description.startsWith("zone") || !isSupportedDescription(description)) {
|
if (isZoneType19(description) || !isSupportedDescription(description)) {
|
||||||
results = parseBasicMessage(description)
|
results = parseBasicMessage(description)
|
||||||
}
|
}
|
||||||
else if (isMotionStatusMessage(description)){
|
else if (isMotionStatusMessage(description)){
|
||||||
@@ -87,12 +87,16 @@ private String parseName(String description) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String parseValue(String description) {
|
private String parseValue(String description) {
|
||||||
def zs = zigbee.parseZoneStatus(description)
|
if (isZoneType19(description)) {
|
||||||
if (zs) {
|
if (translateStatusZoneType19(description)) {
|
||||||
zs.isAlarm1Set() ? "active" : "inactive"
|
return "active"
|
||||||
} else {
|
}
|
||||||
description
|
else {
|
||||||
|
return "inactive"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
description
|
||||||
}
|
}
|
||||||
|
|
||||||
private parseDescriptionText(String linkText, String value, String description) {
|
private parseDescriptionText(String linkText, String value, String description) {
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ metadata {
|
|||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
def results
|
def results
|
||||||
|
|
||||||
if (!isSupportedDescription(description) || description.startsWith("zone")) {
|
if (!isSupportedDescription(description) || zigbee.isZoneType19(description)) {
|
||||||
results = parseSingleMessage(description)
|
results = parseSingleMessage(description)
|
||||||
}
|
}
|
||||||
else if (description == 'updated') {
|
else if (description == 'updated') {
|
||||||
@@ -488,7 +488,12 @@ private String parseValue(String description) {
|
|||||||
if (!isSupportedDescription(description)) {
|
if (!isSupportedDescription(description)) {
|
||||||
return description
|
return description
|
||||||
}
|
}
|
||||||
return zigbee.parseZoneStatus(description)?.isAlarm1Set() ? "open" : "closed"
|
else if (zigbee.translateStatusZoneType19(description)) {
|
||||||
|
return "open"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return "closed"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private parseDescriptionText(String linkText, String value, String description) {
|
private parseDescriptionText(String linkText, String value, String description) {
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ metadata {
|
|||||||
def parse(String description) {
|
def parse(String description) {
|
||||||
def results
|
def results
|
||||||
|
|
||||||
if (!isSupportedDescription(description) || description.startsWith("zone")) {
|
if (!isSupportedDescription(description) || zigbee.isZoneType19(description)) {
|
||||||
// Ignore this in favor of orientation-based state
|
// Ignore this in favor of orientation-based state
|
||||||
// results = parseSingleMessage(description)
|
// results = parseSingleMessage(description)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,38 +118,15 @@ private Map getBatteryResult(rawValue) {
|
|||||||
private Map parseNonIasButtonMessage(Map descMap){
|
private Map parseNonIasButtonMessage(Map descMap){
|
||||||
def buttonState = ""
|
def buttonState = ""
|
||||||
def buttonNumber = 0
|
def buttonNumber = 0
|
||||||
if ((device.getDataValue("model") == "3460-L") &&(descMap.clusterInt == 0x0006)) {
|
if (((device.getDataValue("model") == "3460-L") || (device.getDataValue("model") == "3450-L"))
|
||||||
if (descMap.commandInt == 1) {
|
&&(descMap.clusterInt == 0x0006)) {
|
||||||
|
if (descMap.command == "01") {
|
||||||
getButtonResult("press")
|
getButtonResult("press")
|
||||||
}
|
}
|
||||||
else if (descMap.commandInt == 0) {
|
else if (descMap.command == "00") {
|
||||||
getButtonResult("release")
|
getButtonResult("release")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if ((device.getDataValue("model") == "3450-L") && (descMap.clusterInt == 0x0006)) {
|
|
||||||
if (descMap.commandInt == 1) {
|
|
||||||
getButtonResult("press")
|
|
||||||
}
|
|
||||||
else if (descMap.commandInt == 0) {
|
|
||||||
def button = 1
|
|
||||||
switch(descMap.sourceEndpoint) {
|
|
||||||
case "01":
|
|
||||||
button = 4
|
|
||||||
break
|
|
||||||
case "02":
|
|
||||||
button = 3
|
|
||||||
break
|
|
||||||
case "03":
|
|
||||||
button = 1
|
|
||||||
break
|
|
||||||
case "04":
|
|
||||||
button = 2
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
getButtonResult("release", button)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (descMap.clusterInt == 0x0006) {
|
else if (descMap.clusterInt == 0x0006) {
|
||||||
buttonState = "pushed"
|
buttonState = "pushed"
|
||||||
if (descMap.command == "01") {
|
if (descMap.command == "01") {
|
||||||
|
|||||||
@@ -93,13 +93,7 @@ def on() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def setLevel(value) {
|
def setLevel(value) {
|
||||||
def additionalCmds = []
|
zigbee.setLevel(value)
|
||||||
if (device.getDataValue("model") == "iQBR30" && value.toInteger() > 0) { // Handle iQ bulb not following spec
|
|
||||||
additionalCmds = zigbee.on()
|
|
||||||
} else if (device.getDataValue("manufacturer") == "MRVL") { // Handle marvel stack not reporting
|
|
||||||
additionalCmds = refresh()
|
|
||||||
}
|
|
||||||
zigbee.setLevel(value) + additionalCmds
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* PING is used by Device-Watch in attempt to reach the Device
|
* PING is used by Device-Watch in attempt to reach the Device
|
||||||
@@ -109,7 +103,7 @@ def ping() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def refresh() {
|
def refresh() {
|
||||||
zigbee.onOffRefresh() + zigbee.levelRefresh()
|
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig(0, 300) + zigbee.levelConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
def configure() {
|
def configure() {
|
||||||
@@ -119,5 +113,5 @@ def configure() {
|
|||||||
sendEvent(name: "checkInterval", value: 2 * 10 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
sendEvent(name: "checkInterval", value: 2 * 10 * 60 + 1 * 60, displayed: false, data: [protocol: "zigbee", hubHardwareId: device.hub.hardwareID])
|
||||||
|
|
||||||
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
|
// OnOff minReportTime 0 seconds, maxReportTime 5 min. Reporting interval if no activity
|
||||||
refresh() + zigbee.onOffConfig(0, 300) + zigbee.levelConfig()
|
zigbee.onOffRefresh() + zigbee.levelRefresh() + zigbee.onOffConfig(0, 300) + zigbee.levelConfig()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,702 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright 2015 SmartThings
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
|
||||||
* in compliance with the License. You may obtain a copy of the License at:
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
|
|
||||||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
|
|
||||||
* for the specific language governing permissions and limitations under the License.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
metadata {
|
|
||||||
definition (name: "Z-Wave Lock Reporting", namespace: "smartthings", author: "SmartThings") {
|
|
||||||
capability "Actuator"
|
|
||||||
capability "Lock"
|
|
||||||
capability "Polling"
|
|
||||||
capability "Refresh"
|
|
||||||
capability "Sensor"
|
|
||||||
capability "Lock Codes"
|
|
||||||
capability "Battery"
|
|
||||||
|
|
||||||
command "unlockwtimeout"
|
|
||||||
|
|
||||||
fingerprint deviceId: "0x4003", inClusters: "0x98"
|
|
||||||
fingerprint deviceId: "0x4004", inClusters: "0x98"
|
|
||||||
}
|
|
||||||
|
|
||||||
simulator {
|
|
||||||
status "locked": "command: 9881, payload: 00 62 03 FF 00 00 FE FE"
|
|
||||||
status "unlocked": "command: 9881, payload: 00 62 03 00 00 00 FE FE"
|
|
||||||
|
|
||||||
reply "9881006201FF,delay 4200,9881006202": "command: 9881, payload: 00 62 03 FF 00 00 FE FE"
|
|
||||||
reply "988100620100,delay 4200,9881006202": "command: 9881, payload: 00 62 03 00 00 00 FE FE"
|
|
||||||
}
|
|
||||||
|
|
||||||
tiles(scale: 2) {
|
|
||||||
multiAttributeTile(name:"toggle", type: "generic", width: 6, height: 4){
|
|
||||||
tileAttribute ("device.lock", key: "PRIMARY_CONTROL") {
|
|
||||||
attributeState "locked", label:'locked', action:"lock.unlock", icon:"st.locks.lock.locked", backgroundColor:"#79b821", nextState:"unlocking"
|
|
||||||
attributeState "unlocked", label:'unlocked', action:"lock.lock", icon:"st.locks.lock.unlocked", backgroundColor:"#ffa81e", nextState:"locking"
|
|
||||||
attributeState "unknown", label:"unknown", action:"lock.lock", icon:"st.locks.lock.unknown", backgroundColor:"#ffa81e", nextState:"locking"
|
|
||||||
attributeState "locking", label:'locking', icon:"st.locks.lock.locked", backgroundColor:"#79b821"
|
|
||||||
attributeState "unlocking", label:'unlocking', icon:"st.locks.lock.unlocked", backgroundColor:"#ffa81e"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
standardTile("lock", "device.lock", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
|
||||||
state "default", label:'lock', action:"lock.lock", icon:"st.locks.lock.locked", nextState:"locking"
|
|
||||||
}
|
|
||||||
standardTile("unlock", "device.lock", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
|
||||||
state "default", label:'unlock', action:"lock.unlock", icon:"st.locks.lock.unlocked", nextState:"unlocking"
|
|
||||||
}
|
|
||||||
valueTile("battery", "device.battery", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
|
||||||
state "battery", label:'${currentValue}% battery', unit:""
|
|
||||||
}
|
|
||||||
standardTile("refresh", "device.lock", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
|
||||||
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
|
|
||||||
}
|
|
||||||
|
|
||||||
main "toggle"
|
|
||||||
details(["toggle", "lock", "unlock", "battery", "refresh"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
import physicalgraph.zwave.commands.doorlockv1.*
|
|
||||||
import physicalgraph.zwave.commands.usercodev1.*
|
|
||||||
|
|
||||||
def updated() {
|
|
||||||
try {
|
|
||||||
if (!state.init) {
|
|
||||||
state.init = true
|
|
||||||
response(secureSequence([zwave.doorLockV1.doorLockOperationGet(), zwave.batteryV1.batteryGet()]))
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
log.warn "updated() threw $e"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def parse(String description) {
|
|
||||||
def result = null
|
|
||||||
if (description.startsWith("Err 106")) {
|
|
||||||
if (state.sec) {
|
|
||||||
result = createEvent(descriptionText:description, displayed:false)
|
|
||||||
} else {
|
|
||||||
result = createEvent(
|
|
||||||
descriptionText: "This lock failed to complete the network security key exchange. If you are unable to control it via SmartThings, you must remove it from your network and add it again.",
|
|
||||||
eventType: "ALERT",
|
|
||||||
name: "secureInclusion",
|
|
||||||
value: "failed",
|
|
||||||
displayed: true,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else if (description == "updated") {
|
|
||||||
return null
|
|
||||||
} else {
|
|
||||||
def cmd = zwave.parse(description, [ 0x98: 1, 0x72: 2, 0x85: 2, 0x86: 1 ])
|
|
||||||
if (cmd) {
|
|
||||||
result = zwaveEvent(cmd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.debug "\"$description\" parsed to ${result.inspect()}"
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityMessageEncapsulation cmd) {
|
|
||||||
def encapsulatedCommand = cmd.encapsulatedCommand([0x62: 1, 0x71: 2, 0x80: 1, 0x85: 2, 0x63: 1, 0x98: 1, 0x86: 1])
|
|
||||||
// log.debug "encapsulated: $encapsulatedCommand"
|
|
||||||
if (encapsulatedCommand) {
|
|
||||||
zwaveEvent(encapsulatedCommand)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.securityv1.NetworkKeyVerify cmd) {
|
|
||||||
createEvent(name:"secureInclusion", value:"success", descriptionText:"Secure inclusion was successful")
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.securityv1.SecurityCommandsSupportedReport cmd) {
|
|
||||||
state.sec = cmd.commandClassSupport.collect { String.format("%02X ", it) }.join()
|
|
||||||
if (cmd.commandClassControl) {
|
|
||||||
state.secCon = cmd.commandClassControl.collect { String.format("%02X ", it) }.join()
|
|
||||||
}
|
|
||||||
log.debug "Security command classes: $state.sec"
|
|
||||||
createEvent(name:"secureInclusion", value:"success", descriptionText:"Lock is securely included")
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(DoorLockOperationReport cmd) {
|
|
||||||
def result = []
|
|
||||||
def map = [ name: "lock" ]
|
|
||||||
if (cmd.doorLockMode == 0xFF) {
|
|
||||||
map.value = "locked"
|
|
||||||
} else if (cmd.doorLockMode >= 0x40) {
|
|
||||||
map.value = "unknown"
|
|
||||||
} else if (cmd.doorLockMode & 1) {
|
|
||||||
map.value = "unlocked with timeout"
|
|
||||||
} else {
|
|
||||||
map.value = "unlocked"
|
|
||||||
if (state.assoc != zwaveHubNodeId) {
|
|
||||||
log.debug "setting association"
|
|
||||||
result << response(secure(zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:zwaveHubNodeId)))
|
|
||||||
result << response(zwave.associationV1.associationSet(groupingIdentifier:2, nodeId:zwaveHubNodeId))
|
|
||||||
result << response(secure(zwave.associationV1.associationGet(groupingIdentifier:1)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result ? [createEvent(map), *result] : createEvent(map)
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.alarmv2.AlarmReport cmd) {
|
|
||||||
def result = []
|
|
||||||
def map = null
|
|
||||||
if (cmd.zwaveAlarmType == 6) {
|
|
||||||
if (1 <= cmd.zwaveAlarmEvent && cmd.zwaveAlarmEvent < 10) {
|
|
||||||
map = [ name: "lock", value: (cmd.zwaveAlarmEvent & 1) ? "locked" : "unlocked" ]
|
|
||||||
}
|
|
||||||
switch(cmd.zwaveAlarmEvent) {
|
|
||||||
case 1:
|
|
||||||
map.descriptionText = "$device.displayName was manually locked"
|
|
||||||
break
|
|
||||||
case 2:
|
|
||||||
map.descriptionText = "$device.displayName was manually unlocked"
|
|
||||||
break
|
|
||||||
case 5:
|
|
||||||
if (cmd.eventParameter) {
|
|
||||||
map.descriptionText = "$device.displayName was locked with code ${cmd.eventParameter.first()}"
|
|
||||||
map.data = [ usedCode: cmd.eventParameter[0] ]
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
map.descriptionText = "$device.displayName was locked with keypad"
|
|
||||||
map.data = [ lockedByKeypad: 1 ]
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 6:
|
|
||||||
if (cmd.eventParameter) {
|
|
||||||
map.descriptionText = "$device.displayName was unlocked with code ${cmd.eventParameter.first()}"
|
|
||||||
map.data = [ usedCode: cmd.eventParameter[0] ]
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 9:
|
|
||||||
map.descriptionText = "$device.displayName was autolocked"
|
|
||||||
break
|
|
||||||
case 7:
|
|
||||||
case 8:
|
|
||||||
case 0xA:
|
|
||||||
map = [ name: "lock", value: "unknown", descriptionText: "$device.displayName was not locked fully" ]
|
|
||||||
break
|
|
||||||
case 0xB:
|
|
||||||
map = [ name: "lock", value: "unknown", descriptionText: "$device.displayName is jammed" ]
|
|
||||||
break
|
|
||||||
case 0xC:
|
|
||||||
map = [ name: "codeChanged", value: "all", descriptionText: "$device.displayName: all user codes deleted", isStateChange: true ]
|
|
||||||
allCodesDeleted()
|
|
||||||
break
|
|
||||||
case 0xD:
|
|
||||||
if (cmd.eventParameter) {
|
|
||||||
map = [ name: "codeReport", value: cmd.eventParameter[0], data: [ code: "" ], isStateChange: true ]
|
|
||||||
map.descriptionText = "$device.displayName code ${map.value} was deleted"
|
|
||||||
map.isStateChange = (state["code$map.value"] != "")
|
|
||||||
state["code$map.value"] = ""
|
|
||||||
} else {
|
|
||||||
map = [ name: "codeChanged", descriptionText: "$device.displayName: user code deleted", isStateChange: true ]
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 0xE:
|
|
||||||
map = [ name: "codeChanged", value: cmd.alarmLevel, descriptionText: "$device.displayName: user code added", isStateChange: true ]
|
|
||||||
if (cmd.eventParameter) {
|
|
||||||
map.value = cmd.eventParameter[0]
|
|
||||||
result << response(requestCode(cmd.eventParameter[0]))
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 0xF:
|
|
||||||
map = [ name: "codeChanged", descriptionText: "$device.displayName: user code not added, duplicate", isStateChange: true ]
|
|
||||||
break
|
|
||||||
case 0x10:
|
|
||||||
map = [ name: "tamper", value: "detected", descriptionText: "$device.displayName: keypad temporarily disabled", displayed: true ]
|
|
||||||
break
|
|
||||||
case 0x11:
|
|
||||||
map = [ descriptionText: "$device.displayName: keypad is busy" ]
|
|
||||||
break
|
|
||||||
case 0x12:
|
|
||||||
map = [ name: "codeChanged", descriptionText: "$device.displayName: program code changed", isStateChange: true ]
|
|
||||||
break
|
|
||||||
case 0x13:
|
|
||||||
map = [ name: "tamper", value: "detected", descriptionText: "$device.displayName: code entry attempt limit exceeded", displayed: true ]
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
map = map ?: [ descriptionText: "$device.displayName: alarm event $cmd.zwaveAlarmEvent", displayed: false ]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} else if (cmd.zwaveAlarmType == 7) {
|
|
||||||
map = [ name: "tamper", value: "detected", displayed: true ]
|
|
||||||
switch (cmd.zwaveAlarmEvent) {
|
|
||||||
case 0:
|
|
||||||
map.value = "clear"
|
|
||||||
map.descriptionText = "$device.displayName: tamper alert cleared"
|
|
||||||
break
|
|
||||||
case 1:
|
|
||||||
case 2:
|
|
||||||
map.descriptionText = "$device.displayName: intrusion attempt detected"
|
|
||||||
break
|
|
||||||
case 3:
|
|
||||||
map.descriptionText = "$device.displayName: covering removed"
|
|
||||||
break
|
|
||||||
case 4:
|
|
||||||
map.descriptionText = "$device.displayName: invalid code"
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
map.descriptionText = "$device.displayName: tamper alarm $cmd.zwaveAlarmEvent"
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} else switch(cmd.alarmType) {
|
|
||||||
//kwikset
|
|
||||||
case 21: // Manually locked
|
|
||||||
map = [ name: "lock", value: "locked" ]
|
|
||||||
if (cmd.alarmLevel == 2) {
|
|
||||||
map.descriptionText = "$device.displayName was locked with Touch"
|
|
||||||
map.data = [ lockedByKeypad: 1 ]
|
|
||||||
} else {
|
|
||||||
map.descriptionText = "$device.displayName: was locked manually"
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 18: // Locked with keypad
|
|
||||||
map = [ name: "lock", value: "locked" ]
|
|
||||||
map.descriptionText = "$device.displayName: was locked with keypad"
|
|
||||||
map.data = [ lockedByKeypad: 1 ]
|
|
||||||
break
|
|
||||||
case 24: // Locked by command (Kwikset 914)
|
|
||||||
break;
|
|
||||||
case 27: // Autolocked
|
|
||||||
map = [ name: "lock", value: "locked" ]
|
|
||||||
break
|
|
||||||
case 16: // Note: for levers this means it's unlocked, for non-motorized deadbolt, it's just unsecured and might not get unlocked
|
|
||||||
break
|
|
||||||
case 19:
|
|
||||||
map = [ name: "lock", value: "unlocked" ]
|
|
||||||
if (cmd.alarmLevel) {
|
|
||||||
map.descriptionText = "$device.displayName was unlocked with code $cmd.alarmLevel"
|
|
||||||
map.data = [ usedCode: cmd.alarmLevel ]
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 22:
|
|
||||||
case 25: // Kwikset 914 unlocked by command
|
|
||||||
map = [ name: "lock", value: "unlocked" ]
|
|
||||||
break
|
|
||||||
case 9:
|
|
||||||
case 17:
|
|
||||||
case 23:
|
|
||||||
case 26:
|
|
||||||
map = [ name: "lock", value: "unknown", descriptionText: "$device.displayName bolt is jammed" ]
|
|
||||||
break
|
|
||||||
case 13:
|
|
||||||
map = [ name: "codeChanged", value: cmd.alarmLevel, descriptionText: "$device.displayName code $cmd.alarmLevel was added", isStateChange: true ]
|
|
||||||
result << response(requestCode(cmd.alarmLevel))
|
|
||||||
break
|
|
||||||
case 32:
|
|
||||||
map = [ name: "codeChanged", value: "all", descriptionText: "$device.displayName: all user codes deleted", isStateChange: true ]
|
|
||||||
allCodesDeleted()
|
|
||||||
break
|
|
||||||
case 33:
|
|
||||||
map = [ name: "codeReport", value: cmd.alarmLevel, data: [ code: "" ], isStateChange: true ]
|
|
||||||
map.descriptionText = "$device.displayName code $cmd.alarmLevel was deleted"
|
|
||||||
map.isStateChange = (state["code$cmd.alarmLevel"] != "")
|
|
||||||
state["code$cmd.alarmLevel"] = ""
|
|
||||||
break
|
|
||||||
case 112:
|
|
||||||
map = [ name: "codeChanged", value: cmd.alarmLevel, descriptionText: "$device.displayName code $cmd.alarmLevel changed", isStateChange: true ]
|
|
||||||
result << response(requestCode(cmd.alarmLevel))
|
|
||||||
break
|
|
||||||
case 130: // Yale YRD batteries replaced
|
|
||||||
map = [ descriptionText: "$device.displayName batteries replaced", isStateChange: true ]
|
|
||||||
break
|
|
||||||
case 131:
|
|
||||||
map = [ /*name: "codeChanged", value: cmd.alarmLevel,*/ descriptionText: "$device.displayName code $cmd.alarmLevel is duplicate", isStateChange: false ]
|
|
||||||
break
|
|
||||||
case 161:
|
|
||||||
if (cmd.alarmLevel == 2) {
|
|
||||||
map = [ descriptionText: "$device.displayName front escutcheon removed", isStateChange: true ]
|
|
||||||
} else {
|
|
||||||
map = [ descriptionText: "$device.displayName detected failed user code attempt", isStateChange: true ]
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 167:
|
|
||||||
if (!state.lastbatt || now() - state.lastbatt > 12*60*60*1000) {
|
|
||||||
map = [ descriptionText: "$device.displayName: battery low", isStateChange: true ]
|
|
||||||
result << response(secure(zwave.batteryV1.batteryGet()))
|
|
||||||
} else {
|
|
||||||
map = [ name: "battery", value: device.currentValue("battery"), descriptionText: "$device.displayName: battery low", displayed: true ]
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 168:
|
|
||||||
map = [ name: "battery", value: 1, descriptionText: "$device.displayName: battery level critical", displayed: true ]
|
|
||||||
break
|
|
||||||
case 169:
|
|
||||||
map = [ name: "battery", value: 0, descriptionText: "$device.displayName: battery too low to operate lock", isStateChange: true ]
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
map = [ displayed: false, descriptionText: "$device.displayName: alarm event $cmd.alarmType level $cmd.alarmLevel" ]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
result ? [createEvent(map), *result] : createEvent(map)
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(UserCodeReport cmd) {
|
|
||||||
def result = []
|
|
||||||
def name = "code$cmd.userIdentifier"
|
|
||||||
def code = cmd.code
|
|
||||||
def map = [:]
|
|
||||||
if (cmd.userIdStatus == UserCodeReport.USER_ID_STATUS_OCCUPIED ||
|
|
||||||
(cmd.userIdStatus == UserCodeReport.USER_ID_STATUS_STATUS_NOT_AVAILABLE && cmd.user && code != "**********"))
|
|
||||||
{
|
|
||||||
if (code == "**********") { // Schlage locks send us this instead of the real code
|
|
||||||
state.blankcodes = true
|
|
||||||
code = state["set$name"] ?: decrypt(state[name]) ?: code
|
|
||||||
state.remove("set$name".toString())
|
|
||||||
}
|
|
||||||
if (!code && cmd.userIdStatus == 1) { // Schlage touchscreen sends blank code to notify of a changed code
|
|
||||||
map = [ name: "codeChanged", value: cmd.userIdentifier, displayed: true, isStateChange: true ]
|
|
||||||
map.descriptionText = "$device.displayName code $cmd.userIdentifier " + (state[name] ? "changed" : "was added")
|
|
||||||
code = state["set$name"] ?: decrypt(state[name]) ?: "****"
|
|
||||||
state.remove("set$name".toString())
|
|
||||||
} else {
|
|
||||||
map = [ name: "codeReport", value: cmd.userIdentifier, data: [ code: code ] ]
|
|
||||||
map.descriptionText = "$device.displayName code $cmd.userIdentifier is set"
|
|
||||||
map.displayed = (cmd.userIdentifier != state.requestCode && cmd.userIdentifier != state.pollCode)
|
|
||||||
map.isStateChange = true
|
|
||||||
}
|
|
||||||
result << createEvent(map)
|
|
||||||
} else {
|
|
||||||
map = [ name: "codeReport", value: cmd.userIdentifier, data: [ code: "" ] ]
|
|
||||||
if (state.blankcodes && state["reset$name"]) { // we deleted this code so we can tell that our new code gets set
|
|
||||||
map.descriptionText = "$device.displayName code $cmd.userIdentifier was reset"
|
|
||||||
map.displayed = map.isStateChange = true
|
|
||||||
result << createEvent(map)
|
|
||||||
state["set$name"] = state["reset$name"]
|
|
||||||
result << response(setCode(cmd.userIdentifier, state["reset$name"]))
|
|
||||||
state.remove("reset$name".toString())
|
|
||||||
} else {
|
|
||||||
if (state[name]) {
|
|
||||||
map.descriptionText = "$device.displayName code $cmd.userIdentifier was deleted"
|
|
||||||
} else {
|
|
||||||
map.descriptionText = "$device.displayName code $cmd.userIdentifier is not set"
|
|
||||||
}
|
|
||||||
map.displayed = (cmd.userIdentifier != state.requestCode && cmd.userIdentifier != state.pollCode)
|
|
||||||
map.isStateChange = true
|
|
||||||
result << createEvent(map)
|
|
||||||
}
|
|
||||||
code = ""
|
|
||||||
}
|
|
||||||
state[name] = code ? encrypt(code) : code
|
|
||||||
|
|
||||||
if (cmd.userIdentifier == state.requestCode) { // reloadCodes() was called, keep requesting the codes in order
|
|
||||||
if (state.requestCode + 1 > state.codes || state.requestCode >= 30) {
|
|
||||||
state.remove("requestCode") // done
|
|
||||||
} else {
|
|
||||||
state.requestCode = state.requestCode + 1 // get next
|
|
||||||
result << response(requestCode(state.requestCode))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (cmd.userIdentifier == state.pollCode) {
|
|
||||||
if (state.pollCode + 1 > state.codes || state.pollCode >= 30) {
|
|
||||||
state.remove("pollCode") // done
|
|
||||||
} else {
|
|
||||||
state.pollCode = state.pollCode + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.debug "code report parsed to ${result.inspect()}"
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(UsersNumberReport cmd) {
|
|
||||||
def result = []
|
|
||||||
state.codes = cmd.supportedUsers
|
|
||||||
if (state.requestCode && state.requestCode <= cmd.supportedUsers) {
|
|
||||||
result << response(requestCode(state.requestCode))
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.associationv2.AssociationReport cmd) {
|
|
||||||
def result = []
|
|
||||||
if (cmd.nodeId.any { it == zwaveHubNodeId }) {
|
|
||||||
state.remove("associationQuery")
|
|
||||||
log.debug "$device.displayName is associated to $zwaveHubNodeId"
|
|
||||||
result << createEvent(descriptionText: "$device.displayName is associated")
|
|
||||||
state.assoc = zwaveHubNodeId
|
|
||||||
if (cmd.groupingIdentifier == 2) {
|
|
||||||
result << response(zwave.associationV1.associationRemove(groupingIdentifier:1, nodeId:zwaveHubNodeId))
|
|
||||||
}
|
|
||||||
} else if (cmd.groupingIdentifier == 1) {
|
|
||||||
result << response(secure(zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:zwaveHubNodeId)))
|
|
||||||
} else if (cmd.groupingIdentifier == 2) {
|
|
||||||
result << response(zwave.associationV1.associationSet(groupingIdentifier:2, nodeId:zwaveHubNodeId))
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.timev1.TimeGet cmd) {
|
|
||||||
def result = []
|
|
||||||
def now = new Date().toCalendar()
|
|
||||||
if(location.timeZone) now.timeZone = location.timeZone
|
|
||||||
result << createEvent(descriptionText: "$device.displayName requested time update", displayed: false)
|
|
||||||
result << response(secure(zwave.timeV1.timeReport(
|
|
||||||
hourLocalTime: now.get(Calendar.HOUR_OF_DAY),
|
|
||||||
minuteLocalTime: now.get(Calendar.MINUTE),
|
|
||||||
secondLocalTime: now.get(Calendar.SECOND)))
|
|
||||||
)
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.basicv1.BasicSet cmd) {
|
|
||||||
// The old Schlage locks use group 1 for basic control - we don't want that, so unsubscribe from group 1
|
|
||||||
def result = [ createEvent(name: "lock", value: cmd.value ? "unlocked" : "locked") ]
|
|
||||||
result << response(zwave.associationV1.associationRemove(groupingIdentifier:1, nodeId:zwaveHubNodeId))
|
|
||||||
if (state.assoc != zwaveHubNodeId) {
|
|
||||||
result << response(zwave.associationV1.associationGet(groupingIdentifier:2))
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.batteryv1.BatteryReport cmd) {
|
|
||||||
def map = [ name: "battery", unit: "%" ]
|
|
||||||
if (cmd.batteryLevel == 0xFF) {
|
|
||||||
map.value = 1
|
|
||||||
map.descriptionText = "$device.displayName has a low battery"
|
|
||||||
} else {
|
|
||||||
map.value = cmd.batteryLevel
|
|
||||||
}
|
|
||||||
state.lastbatt = now()
|
|
||||||
createEvent(map)
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.manufacturerspecificv2.ManufacturerSpecificReport cmd) {
|
|
||||||
def result = []
|
|
||||||
|
|
||||||
def msr = String.format("%04X-%04X-%04X", cmd.manufacturerId, cmd.productTypeId, cmd.productId)
|
|
||||||
log.debug "msr: $msr"
|
|
||||||
updateDataValue("MSR", msr)
|
|
||||||
|
|
||||||
result << createEvent(descriptionText: "$device.displayName MSR: $msr", isStateChange: false)
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.versionv1.VersionReport cmd) {
|
|
||||||
def fw = "${cmd.applicationVersion}.${cmd.applicationSubVersion}"
|
|
||||||
updateDataValue("fw", fw)
|
|
||||||
if (state.MSR == "003B-6341-5044") {
|
|
||||||
updateDataValue("ver", "${cmd.applicationVersion >> 4}.${cmd.applicationVersion & 0xF}")
|
|
||||||
}
|
|
||||||
def text = "$device.displayName: firmware version: $fw, Z-Wave version: ${cmd.zWaveProtocolVersion}.${cmd.zWaveProtocolSubVersion}"
|
|
||||||
createEvent(descriptionText: text, isStateChange: false)
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.applicationstatusv1.ApplicationBusy cmd) {
|
|
||||||
def msg = cmd.status == 0 ? "try again later" :
|
|
||||||
cmd.status == 1 ? "try again in $cmd.waitTime seconds" :
|
|
||||||
cmd.status == 2 ? "request queued" : "sorry"
|
|
||||||
createEvent(displayed: true, descriptionText: "$device.displayName is busy, $msg")
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.commands.applicationstatusv1.ApplicationRejectedRequest cmd) {
|
|
||||||
createEvent(displayed: true, descriptionText: "$device.displayName rejected the last request")
|
|
||||||
}
|
|
||||||
|
|
||||||
def zwaveEvent(physicalgraph.zwave.Command cmd) {
|
|
||||||
createEvent(displayed: false, descriptionText: "$device.displayName: $cmd")
|
|
||||||
}
|
|
||||||
|
|
||||||
def lockAndCheck(doorLockMode) {
|
|
||||||
secureSequence([
|
|
||||||
zwave.doorLockV1.doorLockOperationSet(doorLockMode: doorLockMode),
|
|
||||||
zwave.doorLockV1.doorLockOperationGet()
|
|
||||||
], 4200)
|
|
||||||
}
|
|
||||||
|
|
||||||
def lock() {
|
|
||||||
lockAndCheck(DoorLockOperationSet.DOOR_LOCK_MODE_DOOR_SECURED)
|
|
||||||
}
|
|
||||||
|
|
||||||
def unlock() {
|
|
||||||
lockAndCheck(DoorLockOperationSet.DOOR_LOCK_MODE_DOOR_UNSECURED)
|
|
||||||
}
|
|
||||||
|
|
||||||
def unlockwtimeout() {
|
|
||||||
lockAndCheck(DoorLockOperationSet.DOOR_LOCK_MODE_DOOR_UNSECURED_WITH_TIMEOUT)
|
|
||||||
}
|
|
||||||
|
|
||||||
def refresh() {
|
|
||||||
def cmds = [secure(zwave.doorLockV1.doorLockOperationGet())]
|
|
||||||
if (state.assoc == zwaveHubNodeId) {
|
|
||||||
log.debug "$device.displayName is associated to ${state.assoc}"
|
|
||||||
} else if (!state.associationQuery) {
|
|
||||||
log.debug "checking association"
|
|
||||||
cmds << "delay 4200"
|
|
||||||
cmds << zwave.associationV1.associationGet(groupingIdentifier:2).format() // old Schlage locks use group 2 and don't secure the Association CC
|
|
||||||
cmds << secure(zwave.associationV1.associationGet(groupingIdentifier:1))
|
|
||||||
state.associationQuery = now()
|
|
||||||
} else if (secondsPast(state.associationQuery, 9)) {
|
|
||||||
cmds << "delay 6000"
|
|
||||||
cmds << zwave.associationV1.associationSet(groupingIdentifier:2, nodeId:zwaveHubNodeId).format()
|
|
||||||
cmds << secure(zwave.associationV1.associationSet(groupingIdentifier:1, nodeId:zwaveHubNodeId))
|
|
||||||
cmds << zwave.associationV1.associationGet(groupingIdentifier:2).format()
|
|
||||||
cmds << secure(zwave.associationV1.associationGet(groupingIdentifier:1))
|
|
||||||
state.associationQuery = now()
|
|
||||||
}
|
|
||||||
log.debug "refresh sending ${cmds.inspect()}"
|
|
||||||
cmds
|
|
||||||
}
|
|
||||||
|
|
||||||
def poll() {
|
|
||||||
def cmds = []
|
|
||||||
// Only check lock state if it changed recently or we haven't had an update in an hour
|
|
||||||
def latest = device.currentState("lock")?.date?.time
|
|
||||||
if (!latest || !secondsPast(latest, 6 * 60) || secondsPast(state.lastPoll, 55 * 60)) {
|
|
||||||
cmds << secure(zwave.doorLockV1.doorLockOperationGet())
|
|
||||||
state.lastPoll = now()
|
|
||||||
} else if (!state.lastbatt || now() - state.lastbatt > 53*60*60*1000) {
|
|
||||||
cmds << secure(zwave.batteryV1.batteryGet())
|
|
||||||
state.lastbatt = now() //inside-214
|
|
||||||
}
|
|
||||||
if (cmds) {
|
|
||||||
log.debug "poll is sending ${cmds.inspect()}"
|
|
||||||
cmds
|
|
||||||
} else {
|
|
||||||
// workaround to keep polling from stopping due to lack of activity
|
|
||||||
sendEvent(descriptionText: "skipping poll", isStateChange: true, displayed: false)
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
reportAllCodes(state)
|
|
||||||
}
|
|
||||||
|
|
||||||
def requestCode(codeNumber) {
|
|
||||||
secure(zwave.userCodeV1.userCodeGet(userIdentifier: codeNumber))
|
|
||||||
}
|
|
||||||
|
|
||||||
def reloadAllCodes() {
|
|
||||||
def cmds = []
|
|
||||||
if (!state.codes) {
|
|
||||||
state.requestCode = 1
|
|
||||||
cmds << secure(zwave.userCodeV1.usersNumberGet())
|
|
||||||
} else {
|
|
||||||
if(!state.requestCode) state.requestCode = 1
|
|
||||||
cmds << requestCode(codeNumber)
|
|
||||||
}
|
|
||||||
cmds
|
|
||||||
}
|
|
||||||
|
|
||||||
def setCode(codeNumber, code) {
|
|
||||||
def strcode = code
|
|
||||||
log.debug "setting code $codeNumber to $code"
|
|
||||||
if (code instanceof String) {
|
|
||||||
code = code.toList().findResults { if(it > ' ' && it != ',' && it != '-') it.toCharacter() as Short }
|
|
||||||
} else {
|
|
||||||
strcode = code.collect{ it as Character }.join()
|
|
||||||
}
|
|
||||||
if (state.blankcodes) {
|
|
||||||
// Can't just set, we won't be able to tell if it was successful
|
|
||||||
if (state["code$codeNumber"] != "") {
|
|
||||||
if (state["setcode$codeNumber"] != strcode) {
|
|
||||||
state["resetcode$codeNumber"] = strcode
|
|
||||||
return deleteCode(codeNumber)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
state["setcode$codeNumber"] = strcode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
secureSequence([
|
|
||||||
zwave.userCodeV1.userCodeSet(userIdentifier:codeNumber, userIdStatus:1, user:code),
|
|
||||||
zwave.userCodeV1.userCodeGet(userIdentifier:codeNumber)
|
|
||||||
], 7000)
|
|
||||||
}
|
|
||||||
|
|
||||||
def deleteCode(codeNumber) {
|
|
||||||
log.debug "deleting code $codeNumber"
|
|
||||||
secureSequence([
|
|
||||||
zwave.userCodeV1.userCodeSet(userIdentifier:codeNumber, userIdStatus:0),
|
|
||||||
zwave.userCodeV1.userCodeGet(userIdentifier:codeNumber)
|
|
||||||
], 7000)
|
|
||||||
}
|
|
||||||
|
|
||||||
def updateCodes(codeSettings) {
|
|
||||||
if(codeSettings instanceof String) codeSettings = util.parseJson(codeSettings)
|
|
||||||
def set_cmds = []
|
|
||||||
def get_cmds = []
|
|
||||||
codeSettings.each { name, updated ->
|
|
||||||
def current = decrypt(state[name])
|
|
||||||
if (name.startsWith("code")) {
|
|
||||||
def n = name[4..-1].toInteger()
|
|
||||||
log.debug "$name was $current, set to $updated"
|
|
||||||
if (updated?.size() >= 4 && updated != current) {
|
|
||||||
def cmds = setCode(n, updated)
|
|
||||||
set_cmds << cmds.first()
|
|
||||||
get_cmds << cmds.last()
|
|
||||||
} else if ((current && updated == "") || updated == "0") {
|
|
||||||
def cmds = deleteCode(n)
|
|
||||||
set_cmds << cmds.first()
|
|
||||||
get_cmds << cmds.last()
|
|
||||||
} else if (updated && updated.size() < 4) {
|
|
||||||
// Entered code was too short
|
|
||||||
codeSettings["code$n"] = current
|
|
||||||
}
|
|
||||||
} else log.warn("unexpected entry $name: $updated")
|
|
||||||
}
|
|
||||||
if (set_cmds) {
|
|
||||||
return response(delayBetween(set_cmds, 2200) + ["delay 2200"] + delayBetween(get_cmds, 4200))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def getCode(codeNumber) {
|
|
||||||
decrypt(state["code$codeNumber"])
|
|
||||||
}
|
|
||||||
|
|
||||||
def getAllCodes() {
|
|
||||||
state.findAll { it.key.startsWith 'code' }.collectEntries {
|
|
||||||
[it.key, (it.value instanceof String && it.value.startsWith("~")) ? decrypt(it.value) : it.value]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private secure(physicalgraph.zwave.Command cmd) {
|
|
||||||
zwave.securityV1.securityMessageEncapsulation().encapsulate(cmd).format()
|
|
||||||
}
|
|
||||||
|
|
||||||
private secureSequence(commands, delay=4200) {
|
|
||||||
delayBetween(commands.collect{ secure(it) }, delay)
|
|
||||||
}
|
|
||||||
|
|
||||||
private Boolean secondsPast(timestamp, seconds) {
|
|
||||||
if (!(timestamp instanceof Number)) {
|
|
||||||
if (timestamp instanceof Date) {
|
|
||||||
timestamp = timestamp.time
|
|
||||||
} else if ((timestamp instanceof String) && timestamp.isNumber()) {
|
|
||||||
timestamp = timestamp.toLong()
|
|
||||||
} else {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (now() - timestamp) > (seconds * 1000)
|
|
||||||
}
|
|
||||||
|
|
||||||
private allCodesDeleted() {
|
|
||||||
if (state.codes instanceof Integer) {
|
|
||||||
(1..state.codes).each { n ->
|
|
||||||
if (state["code$n"]) {
|
|
||||||
result << createEvent(name: "codeReport", value: n, data: [ code: "" ], descriptionText: "code $n was deleted",
|
|
||||||
displayed: false, isStateChange: true)
|
|
||||||
}
|
|
||||||
state["code$n"] = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def reportAllCodes(state) {
|
|
||||||
def map = [ name: "reportAllCodes", data: [:], displayed: false, isStateChange: false, type: "physical" ]
|
|
||||||
state.each { entry ->
|
|
||||||
//iterate through all the state entries and add them to the event data to be handled by application event handlers
|
|
||||||
if ( entry.key ==~ /^code\d{1,}/ && entry.value.startsWith("~") ) {
|
|
||||||
map.data.put(entry.key, decrypt(entry.value))
|
|
||||||
} else {
|
|
||||||
map.data.put(entry.key, entry.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sendEvent(map)
|
|
||||||
}
|
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
/**
|
|
||||||
* Slickspaces Lock Manager
|
|
||||||
*
|
|
||||||
* Copyright 2016 Mathew Hunter
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
|
||||||
* in compliance with the License. You may obtain a copy of the License at:
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
|
|
||||||
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
|
|
||||||
* for the specific language governing permissions and limitations under the License.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
definition(
|
|
||||||
name: "Slickspaces Lock Manager",
|
|
||||||
parent: "Slickspaces:Slickspaces v0.25",
|
|
||||||
namespace: "Slickspaces",
|
|
||||||
author: "Mathew Hunter",
|
|
||||||
description: "Allows integration with Slickspaces rental management suite www.slickspaces.com",
|
|
||||||
category: "Safety & Security",
|
|
||||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
|
|
||||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
|
|
||||||
iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
|
|
||||||
oauth: true)
|
|
||||||
|
|
||||||
import groovy.json.JsonSlurper
|
|
||||||
|
|
||||||
preferences {
|
|
||||||
page(name: "SetupLockManager", Title: "Setup Lock Manager", uninstall: true, install: true){
|
|
||||||
section("Access Code Locks") {
|
|
||||||
input "myLocks","capability.lockCodes", title: "Select Locks for Access Codes", required: true, multiple: true, submitOnChange: true
|
|
||||||
}
|
|
||||||
section("Slickspaces Secrets shh"){
|
|
||||||
input (name:"slicksecret", type:"password")
|
|
||||||
}
|
|
||||||
section{
|
|
||||||
href(name: "toLockRoutines", page: "lockRoutinesPage", title: "Lock Action")
|
|
||||||
}
|
|
||||||
section("Unlock Action"){
|
|
||||||
href(name: "toUnlockRoutines", page: "unlockRoutinesPage", title: "Unlock Action")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
page(name:"lockRoutinesPage", title: "Which Routine Would You Like to Run on Lock?", uninstall:false, install: false)
|
|
||||||
page(name:"unlockRoutinesPage", title: "Which Routine Would You Like to Run on Lock?", uninstall:false, install: false)
|
|
||||||
}
|
|
||||||
|
|
||||||
def lockRoutinesPage(){
|
|
||||||
dynamicPage(name: "lockRoutinesPage"){
|
|
||||||
section("lockAction"){
|
|
||||||
def routines = location.getHelloHome()?.getPhrases()*.label
|
|
||||||
log.debug "routines are ${routines}"
|
|
||||||
if (routines){
|
|
||||||
routines.sort()
|
|
||||||
input "lockRoutine", "enum", title: "Lock Action", options: routines, multiple: false, required: false, submitOnChange: true, refreshAfterSelection: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
def unlockRoutinesPage(){
|
|
||||||
dynamicPage(name: "unlockRoutinesPage", title: "Which Routine Would You Like to Run on Unlock?"){
|
|
||||||
section("unlockAction"){
|
|
||||||
def routines = location.getHelloHome()?.getPhrases()*.label
|
|
||||||
if(routines){
|
|
||||||
routines.sort()
|
|
||||||
input "unlockRoutine", "enum", title: "Unlock Action", options: routines, multiple: false, required: false, submitOnChange: true, refreshAfterSelection: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def getLockUsers(id){
|
|
||||||
def lockID = id
|
|
||||||
def targetLock = myLocks.find{ it.id == lockID }
|
|
||||||
def lockCodes = []
|
|
||||||
return [state.codeData]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def setLockCode(targetLock, codeID, code){
|
|
||||||
//dates are provided in java millisecond time, we need to convert to date for groovy
|
|
||||||
log.debug "setting lock code for ${targetLock} ${code} ${date}"
|
|
||||||
def myLock = myLocks.find { it.id == targetLock }
|
|
||||||
myLock.setCode(codeID,code)
|
|
||||||
return [codeID:codeID, code:code]
|
|
||||||
}
|
|
||||||
|
|
||||||
def deleteLockCode(lockID, codeID){
|
|
||||||
def targetLock = myLocks.find { it.id == lockID }
|
|
||||||
targetLock.deleteCode(codeID.toInteger())
|
|
||||||
return [codeID:codeID]
|
|
||||||
}
|
|
||||||
|
|
||||||
def deleteAllLockCodes(lockID){
|
|
||||||
def targetLock = myLocks.find { it.id == lockID }
|
|
||||||
1.upto(30){
|
|
||||||
targetLock.deleteCode(it)
|
|
||||||
}
|
|
||||||
return [status:'success']
|
|
||||||
}
|
|
||||||
|
|
||||||
def storeUserCodes(evt) {
|
|
||||||
//this is where I need to modify the codes to be stored in slickspaces db send token with request etc.
|
|
||||||
def codeData = new JsonSlurper().parseText(evt.data)
|
|
||||||
log.info "poll triggered with ${codeData}"
|
|
||||||
state.codeData = [codeData]
|
|
||||||
}
|
|
||||||
|
|
||||||
def pollLocks(id) {
|
|
||||||
log.info "polling lock for ${id}"
|
|
||||||
def targetLock = myLocks.find{ it.id == id }
|
|
||||||
targetLock.poll()
|
|
||||||
return [completed: "lock: ${targetLock.id}"]
|
|
||||||
}
|
|
||||||
|
|
||||||
def installed() {
|
|
||||||
log.debug "Installed with settings: ${settings}"
|
|
||||||
|
|
||||||
initialize()
|
|
||||||
}
|
|
||||||
|
|
||||||
def updated() {
|
|
||||||
log.debug "Updated with settings: ${settings}"
|
|
||||||
|
|
||||||
unsubscribe()
|
|
||||||
initialize()
|
|
||||||
}
|
|
||||||
|
|
||||||
def initialize() {
|
|
||||||
subscribe(myLocks, "reportAllCodes", storeUserCodes, [filterEvents:false])
|
|
||||||
subscribe(myLocks, "lock", lockHandler, [filterEvents:false])
|
|
||||||
// TODO: subscribe to attributes, devices, locations, etc.
|
|
||||||
}
|
|
||||||
|
|
||||||
def lockHandler(evt) {
|
|
||||||
log.debug "lock event fired"
|
|
||||||
def codeData = new JsonSlurper().parseText(evt.data)
|
|
||||||
log.debug "${codeData}"
|
|
||||||
if (codeData.usedCode){
|
|
||||||
log.info "${codeData.usedCode}"
|
|
||||||
//schlagg
|
|
||||||
if (codeData.microDeviceTile){
|
|
||||||
if (codeData.usedCode.isNumber() && codeData.microDeviceTile.icon == "st.locks.lock.locked") {
|
|
||||||
location.helloHome?.execute(settings.lockRoutine)
|
|
||||||
}
|
|
||||||
if (codeData.usedCode.isNumber() && codeData.microDeviceTile.icon == "st.locks.lock.unlocked") {
|
|
||||||
location.helloHome?.execute(settings.unlockRoutine)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
log.debug "did I break in my previous if statement?"
|
|
||||||
//weiser + yale
|
|
||||||
if (!codeData.microDeviceTile){
|
|
||||||
log.debug "microDeviceTile was empty"
|
|
||||||
if(codeData.usedCode.isNumber()){
|
|
||||||
//unlocked
|
|
||||||
log.debug "running home"
|
|
||||||
location.helloHome?.execute(settings.unlockRoutine)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
//locking with keypad
|
|
||||||
if (codeData.lockedByKeypad == 1) {
|
|
||||||
log.info "lockedByKeypad"
|
|
||||||
location.helloHome?.execute(settings.lockRoutine)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// TODO: implement event handlers
|
|
||||||
@@ -1,773 +0,0 @@
|
|||||||
/**
|
|
||||||
* Slickspaces
|
|
||||||
*
|
|
||||||
* Copyright 2016 Mathew Hunter
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
* v0.19 -- split myPlug and mySwitch to two separate classes
|
|
||||||
* v0.20 -- added status to the getDevices to simplify the UI for www.slickspaces.com
|
|
||||||
* v0.21 -- added motion sensor and CO to sensor preference section
|
|
||||||
* v0.22 -- added routines/current mode
|
|
||||||
* v0.23 -- added child smartapp - Slickspaces Lock Manager
|
|
||||||
*/
|
|
||||||
|
|
||||||
definition(
|
|
||||||
name: "Slickspaces v0.25",
|
|
||||||
namespace: "Slickspaces",
|
|
||||||
author: "Mathew Hunter",
|
|
||||||
description: "Slickspaces Rental Management",
|
|
||||||
category: "Convenience",
|
|
||||||
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
|
|
||||||
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
|
|
||||||
iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
|
|
||||||
oauth: true)
|
|
||||||
|
|
||||||
preferences {
|
|
||||||
section("Door Locks") {
|
|
||||||
input "myLock", "capability.lock", title: "Smart Lock", required: false, multiple: true
|
|
||||||
|
|
||||||
// TODO: put inputs here
|
|
||||||
}
|
|
||||||
section("Ecobee Thermostats") {
|
|
||||||
input "myThermostat", "device.ecobeeThermostat", title: "Smart Thermostat", required: false, multiple: true
|
|
||||||
}
|
|
||||||
section("Smart Sensors") {
|
|
||||||
input "mySensor", "capability.contactSensor", title: "Door/Window Sensors", required: false, multiple: true
|
|
||||||
input "myMotion", "capability.motionSensor", title: "Motion Sensors", required: false, multiple: true
|
|
||||||
input "myBattery", "capability.battery", title: "Battery Operated Devices", required: false, multiple: true
|
|
||||||
input "myCO2", "capability.carbonDioxideMeasurement", title: "CO2 Sensors", required: false, multiple: true
|
|
||||||
input "mySmoke", "capability.smoke", title: "Smoke Detectors", required: false, multiple: true
|
|
||||||
input "myTemperature", "capability.temperatureMeasurement", title: "Temperature Sensors", required: false, multiple: true
|
|
||||||
}
|
|
||||||
section("Remotes") {
|
|
||||||
input "myButton", "capability.button", title: "Remote Control", required: false, multiple: true
|
|
||||||
}
|
|
||||||
section("Light Dimmers"){
|
|
||||||
input "myDimmer", "capability.switchLevel", title: "Smart Dimmers", required: false, multiple: true
|
|
||||||
}
|
|
||||||
section("Switches (Lights)"){
|
|
||||||
input "mySwitch", "capability.switch", title: "Smart Lights (no dimmer)", required: false, multiple: true
|
|
||||||
}
|
|
||||||
section("Switches (Electrical Plugs)"){
|
|
||||||
input "myPlug", "capability.switch", title: "Smart Plugs", required: false, multiple: true
|
|
||||||
}
|
|
||||||
section("Music Player") {
|
|
||||||
input "myMusic", "capability.musicPlayer", title: "Music Player", required: false, multiple: true
|
|
||||||
}
|
|
||||||
section("Addons") {
|
|
||||||
app(name: "LockManager", appName: "Slickspaces Lock Manager", namespace: "Slickspaces", title: "Slickspaces Lock Manager", multiple: true)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
mappings {
|
|
||||||
//working on it
|
|
||||||
path("/refresh-all"){
|
|
||||||
action:[
|
|
||||||
GET:"getAllDeviceStatus"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
//TODO
|
|
||||||
path("/refresh/:deviceType/:id"){
|
|
||||||
action:[
|
|
||||||
GET:"getDeviceStatus"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
//done
|
|
||||||
path("/getdevices"){
|
|
||||||
action:[
|
|
||||||
GET:"getDevices"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
path("/getlocks"){
|
|
||||||
action:[
|
|
||||||
GET:"getLocks"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
//done
|
|
||||||
path("/getroutines"){
|
|
||||||
action:[
|
|
||||||
GET:"getRoutines"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
path("/executeroutine/:routine"){
|
|
||||||
action:[
|
|
||||||
GET:"executeRoutine"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
//lock manager mappings
|
|
||||||
path("/getlockusers/:id"){
|
|
||||||
action:[
|
|
||||||
GET:"getLockUsers"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
path("/setlockcode/:id/:code/:date"){
|
|
||||||
action:[
|
|
||||||
POST:"setLockCode"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
path("/deletelockcode/:id/:codeID"){
|
|
||||||
action:[
|
|
||||||
POST:"deleteLockCode"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
path("/deletealllockcodes/:id"){
|
|
||||||
action:[
|
|
||||||
POST:"deleteAllLockCodes"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
path("/poll/:id"){
|
|
||||||
action:[
|
|
||||||
GET:"pollLocks"
|
|
||||||
]
|
|
||||||
//end of lock manager maps
|
|
||||||
path("/verifytoken"){
|
|
||||||
action:[
|
|
||||||
GET:"verifyToken"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
path("/gettimezone"){
|
|
||||||
action:[
|
|
||||||
GET:"getTimezone"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
path("/:deviceType/:id/:action"){
|
|
||||||
action:[
|
|
||||||
POST:"startAction"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
path("/unschedule"){
|
|
||||||
action:[
|
|
||||||
POST:"startUnschedule"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
path("/timestamp/:timestamp"){
|
|
||||||
action:[
|
|
||||||
GET: "timestamp"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
def timestamp(){
|
|
||||||
def timestamp = params.timestamp
|
|
||||||
def resp = []
|
|
||||||
def myResponse = [st_timestampwithoffset:dateTimezoneOffset(timestamp),timestampraw:timestamp, date:new Date()]
|
|
||||||
resp << myResponse
|
|
||||||
return resp
|
|
||||||
}
|
|
||||||
|
|
||||||
def startUnschedule(){
|
|
||||||
unschedule()
|
|
||||||
}
|
|
||||||
|
|
||||||
def getLocks(){
|
|
||||||
def deviceAttributes
|
|
||||||
def resp = []
|
|
||||||
if (myLock){
|
|
||||||
myLock.each {
|
|
||||||
deviceAttributes = deviceStatus("lock",it.id)
|
|
||||||
def myResponse = [device_id:it.id,status:deviceAttributes]
|
|
||||||
resp << myResponse
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return resp
|
|
||||||
}
|
|
||||||
|
|
||||||
def verifyToken(){
|
|
||||||
return [verifyToken:"Success"]
|
|
||||||
}
|
|
||||||
|
|
||||||
def deleteLockCodeAt(deleteDate, lockID, codeID){
|
|
||||||
log.debug "start of deleteLockCodeAt with vals, applied offset date: ${deleteDate}, ${lockID}, ${codeID}"
|
|
||||||
def lockMgr = findChildAppByName("Slickspaces Lock Manager")
|
|
||||||
def endDate = new Date(deleteDate)
|
|
||||||
log.debug "Setting schedule for code delete on created groovy date ${endDate}, for ${lockID}, ${codeID}"
|
|
||||||
runOnce(endDate, deleteLockCodeNow, [overwrite: false, data:[lockID:lockID, codeID:codeID]])
|
|
||||||
}
|
|
||||||
|
|
||||||
def setLockCodeAt(setDate, lockID, codeID, code){
|
|
||||||
log.debug "start of setLockCodeAt with vals, applied offset date: ${deleteDate}, ${lockID}, ${codeID}"
|
|
||||||
def lockMgr = findChildAppByName("Slickspaces Lock Manager")
|
|
||||||
def startDate = new Date(setDate)
|
|
||||||
log.debug "Setting schedule for code set on created groovy date ${startDate}, for ${lockID}, ${codeID}, ${code}"
|
|
||||||
runOnce(startDate, setLockCodeNow, [overwrite: false,data:[lockID:lockID,codeID:codeID,code:code]])
|
|
||||||
}
|
|
||||||
|
|
||||||
def dateTimezoneOffset(longDate){
|
|
||||||
longDate = longDate.toLong() - location.timeZone.rawOffset
|
|
||||||
return longDate
|
|
||||||
}
|
|
||||||
|
|
||||||
def getLockUsers(){
|
|
||||||
def lockMgr = findChildAppByName("Slickspaces Lock Manager")
|
|
||||||
def lockID = params.id
|
|
||||||
if (lockMgr){
|
|
||||||
return lockMgr.getLockUsers(lockID)
|
|
||||||
}
|
|
||||||
return [error:"lock manager not installed"]
|
|
||||||
}
|
|
||||||
|
|
||||||
def pollLocks() {
|
|
||||||
log.debug "arrived at pollLocks"
|
|
||||||
def id = params.id
|
|
||||||
def lockMgr = findChildAppByName("Slickspaces Lock Manager")
|
|
||||||
log.debug "id is ${id} and lock manager app is ${lockMgr}"
|
|
||||||
def result = lockMgr.pollLocks(id)
|
|
||||||
log.info "result was ${result}"
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
def setLockCodeNow(data){
|
|
||||||
log.info "made it to setLockCodeNow!"
|
|
||||||
log.info "is there a map? ${data["lockID"]}"
|
|
||||||
def lockMgr = findChildAppByName("Slickspaces Lock Manager")
|
|
||||||
if (lockMgr){
|
|
||||||
lockMgr.setLockCode(data["lockID"],data["codeID"],data["code"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
def deleteLockCodeNow(data){
|
|
||||||
log.info "made it to deleteLockCodeNow!"
|
|
||||||
log.info "is there a delete map? ${data["lockID"]}"
|
|
||||||
def lockMgr = findChildAppByName("Slickspaces Lock Manager")
|
|
||||||
if (lockMgr){
|
|
||||||
lockMgr.deleteLockCode(data["lockID"],data["codeID"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def setLockCode(){
|
|
||||||
log.info "made it to setLockCode"
|
|
||||||
def lockMgr = findChildAppByName("Slickspaces Lock Manager")
|
|
||||||
def arrDateRange = params.date.split('-')
|
|
||||||
def lockID = params.id
|
|
||||||
def paramCode = params.code
|
|
||||||
def arrValues = paramCode.split('-')
|
|
||||||
def codeID = arrValues[0].toInteger()
|
|
||||||
def code = arrValues[1]
|
|
||||||
def date = params.date
|
|
||||||
def response = [:]
|
|
||||||
|
|
||||||
log.debug "${lockID} ${codeID} ${code} ${date} original dates submitted: ${arrDateRange[0]} ${arrDateRange[1]}"
|
|
||||||
|
|
||||||
if (arrDateRange[0].toLong() > 0){
|
|
||||||
log.debug "start date for set lock ${dateTimezoneOffset(arrDateRange[0])}"
|
|
||||||
def startDate = setLockCodeAt(dateTimezoneOffset(arrDateRange[0]), lockID, codeID, code)
|
|
||||||
response << [startdate: startDate]
|
|
||||||
}
|
|
||||||
if (arrDateRange[1].toLong() > 0) {
|
|
||||||
log.debug "end date for set lock ${arrDateRange[1]}"
|
|
||||||
def endDate = deleteLockCodeAt(dateTimezoneOffset(arrDateRange[1]), lockID, codeID)
|
|
||||||
response << [enddate: endDate]
|
|
||||||
}
|
|
||||||
|
|
||||||
if(arrDateRange[0].toLong() == 0) {
|
|
||||||
log.debug "setting lock code immediately"
|
|
||||||
response << lockMgr.setLockCode(lockID, codeID, code)
|
|
||||||
}
|
|
||||||
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
||||||
def deleteLockCode(){
|
|
||||||
log.debug "made it to deleteLockCode"
|
|
||||||
def lockMgr = findChildAppByName("Slickspaces Lock Manager")
|
|
||||||
log.debug lockMgr
|
|
||||||
def lockID = params.id
|
|
||||||
def codeID = params.codeID.toInteger()
|
|
||||||
|
|
||||||
if (lockMgr){
|
|
||||||
return lockMgr.deleteLockCode(lockID, codeID)
|
|
||||||
}
|
|
||||||
|
|
||||||
return [error:"lock manager not installed"]
|
|
||||||
}
|
|
||||||
|
|
||||||
def deleteAllLockCodes(){
|
|
||||||
def lockID = params.id
|
|
||||||
log.debug "deleting all lock codes for ${lockID}"
|
|
||||||
def lockMgr = findChildAppByName("Slickspaces Lock Manager")
|
|
||||||
if (lockMgr){
|
|
||||||
return lockMgr.deleteAllLockCodes(lockID)
|
|
||||||
}
|
|
||||||
return [error:"Lock not found"]
|
|
||||||
}
|
|
||||||
|
|
||||||
//gets the status of all devices, returns to the web requestor the attributes of all devices as JSON (used as part of the web api)
|
|
||||||
def getAllDeviceStatus(){
|
|
||||||
//mySwitch, myDimmer, myThermostat, mySensor, myMusic, myLock
|
|
||||||
def deviceAttributes = []
|
|
||||||
def resp = []
|
|
||||||
|
|
||||||
if (myThermostat){
|
|
||||||
myThermostat.each {
|
|
||||||
deviceAttributes = deviceStatus("thermostat",it.id)
|
|
||||||
def myResponse = [device_id:it.id,status:deviceAttributes]
|
|
||||||
resp << myResponse
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (mySwitch){
|
|
||||||
mySwitch.each {
|
|
||||||
deviceAttributes = deviceStatus("switch",it.id)
|
|
||||||
def myResponse = [device_id:it.id,status:deviceAttributes]
|
|
||||||
resp << myResponse
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (myPlug){
|
|
||||||
myPlug.each {
|
|
||||||
deviceAttributes = deviceStatus("switch",it.id)
|
|
||||||
def myResponse = [device_id:it.id,status:deviceAttributes]
|
|
||||||
resp << myResponse
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (myDimmer){
|
|
||||||
myDimmer.each {
|
|
||||||
deviceAttributes = deviceStatus("dimmer",it.id)
|
|
||||||
def myResponse = [device_id:it.id,status:deviceAttributes]
|
|
||||||
resp << myResponse
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (mySensor){
|
|
||||||
mySensor.each {
|
|
||||||
deviceAttributes = deviceStatus("sensor",it.id)
|
|
||||||
def myResponse = [device_id:it.id,status:deviceAttributes]
|
|
||||||
resp << myResponse
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (myLock){
|
|
||||||
myLock.each {
|
|
||||||
deviceAttributes = deviceStatus("lock",it.id)
|
|
||||||
def myResponse = [device_id:it.id,status:deviceAttributes]
|
|
||||||
resp << myResponse
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return resp
|
|
||||||
}
|
|
||||||
|
|
||||||
//gets the attributes of a particular device by type and device ID (used as part of the web api after sending an action to verify completion)
|
|
||||||
def getDeviceStatus() {
|
|
||||||
def deviceType = params.deviceType
|
|
||||||
def deviceID = params.id
|
|
||||||
def deviceAttributes = []
|
|
||||||
def resp = []
|
|
||||||
log.debug "$deviceID"
|
|
||||||
deviceAttributes = deviceStatus(deviceType,deviceID)
|
|
||||||
resp << [completed: deviceAttributes]
|
|
||||||
return resp
|
|
||||||
}
|
|
||||||
|
|
||||||
// takes a deviceType and array of device ID's and returns their attributes as result
|
|
||||||
def deviceStatus(deviceType, deviceID){
|
|
||||||
log.debug "$deviceID"
|
|
||||||
def resp
|
|
||||||
def myDevice
|
|
||||||
switch (deviceType){
|
|
||||||
case "thermostat":
|
|
||||||
myDevice = myThermostat.find { it.id == deviceID}
|
|
||||||
resp = [currentTemperature: myDevice.currentTemperature,
|
|
||||||
coolingSetpoint: myDevice.currentCoolingSetpoint,
|
|
||||||
heatingSetpoint: myDevice.currentHeatingSetpoint,
|
|
||||||
deviceTemperatureUnit: myDevice.currentDeviceTemperatureUnit,
|
|
||||||
thermostatSetpoint: myDevice.currentThermostatSetPoint,
|
|
||||||
thermostatMode: myDevice.currentThermostatMode,
|
|
||||||
thermostatFanMode: myDevice.currentThermostatFanMode,
|
|
||||||
thermostatStatus: myDevice.currentThermostatStatus,
|
|
||||||
humidity: myDevice.currentHumidity]
|
|
||||||
break
|
|
||||||
case "sensor":
|
|
||||||
myDevice = mySensor.find { it.id == deviceID}
|
|
||||||
resp = [contact: myDevice.currentValue("contact")]
|
|
||||||
break
|
|
||||||
case "switch":
|
|
||||||
myDevice = mySwitch.find { it.id == deviceID}
|
|
||||||
resp = [switch: myDevice.currentValue("switch")]
|
|
||||||
break
|
|
||||||
case "plug":
|
|
||||||
myDevice = myPlug.find { it.id == deviceID}
|
|
||||||
resp = [plug: myDevice.currentValue("switch")]
|
|
||||||
break
|
|
||||||
case "lock":
|
|
||||||
myDevice = myLock.find { it.id == deviceID}
|
|
||||||
resp = [lock: myDevice.currentLock]
|
|
||||||
break
|
|
||||||
case "music":
|
|
||||||
myDevice = myMusic.find { it.id == deviceID}
|
|
||||||
resp = [status: myDevice.currentStatus,
|
|
||||||
level: myDevice.currentLevel,
|
|
||||||
trackDescription: myDevice.currentTrackDescription,
|
|
||||||
trackData: myDevice.currentTrackData,
|
|
||||||
mute: myDevice.currentMute]
|
|
||||||
break
|
|
||||||
case "dimmer":
|
|
||||||
myDevice = myDimmer.find { it.id == deviceID}
|
|
||||||
resp = [switch: myDevice.currentSwitch,
|
|
||||||
level: myDevice.currentLevel]
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
log.debug "no devices found of type $deviceType"
|
|
||||||
resp = [error: "no device type found of type $deviceType"]
|
|
||||||
}
|
|
||||||
return resp
|
|
||||||
}
|
|
||||||
|
|
||||||
def startAction() {
|
|
||||||
def action = params.action
|
|
||||||
def deviceID = params.id
|
|
||||||
def deviceType = params.deviceType
|
|
||||||
def result
|
|
||||||
|
|
||||||
switch (deviceType){
|
|
||||||
case "lock":
|
|
||||||
def targetDevice = myLock.find{ it.id == deviceID }
|
|
||||||
if (targetDevice){
|
|
||||||
result = invokeLockAction(targetDevice, action)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case "switch":
|
|
||||||
def targetDevice = mySwitch.find{ it.id == deviceID}
|
|
||||||
result = invokeSwitchAction(targetDevice, action)
|
|
||||||
break
|
|
||||||
case "plug":
|
|
||||||
def targetDevice = myPlug.find{ it.id == deviceID}
|
|
||||||
result = invokeSwitchAction(targetDevice, action)
|
|
||||||
break
|
|
||||||
case "dimmer":
|
|
||||||
def targetDevice = myDimmer.find{ it.id == deviceID}
|
|
||||||
if (action.isInteger()){
|
|
||||||
result = invokeDimmerAction(targetDevice, action.toInteger())
|
|
||||||
}else{
|
|
||||||
result = invokeDimmerAction(targetDevice, action)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case "button":
|
|
||||||
def targetDevice = myButton.find{ it.id == deviceID}
|
|
||||||
result = invokeButtonAction(targetDevice, action)
|
|
||||||
break
|
|
||||||
case "music":
|
|
||||||
def targetDevice = myMusic.find{ it.id == deviceID}
|
|
||||||
result = invokeMusicAction(targetDevice, action)
|
|
||||||
break
|
|
||||||
//working on stat
|
|
||||||
case "thermostat":
|
|
||||||
def targetDevice = myThermostat.find{ it.id == deviceID}
|
|
||||||
result = invokeThermostatAction(targetDevice, action)
|
|
||||||
break
|
|
||||||
case "sensor":
|
|
||||||
result = [Code:"1", Message: "No Actions available for Sensors"]
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
log.debug "No device found for $deviceType"
|
|
||||||
result = [Code:"1", Message:"No Device Found for $deviceType"]
|
|
||||||
}
|
|
||||||
return [completed: result]
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
def invokeSwitchAction(device, action){
|
|
||||||
def result
|
|
||||||
switch (action){
|
|
||||||
case "toggleSwitch":
|
|
||||||
result = toggleSwitch(device)
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
result = "no action, $action"
|
|
||||||
log.debug "no action"
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
def invokeThermostatAction(device, action){
|
|
||||||
def result
|
|
||||||
switch (action){
|
|
||||||
case "resume":
|
|
||||||
device.resumeProgram()
|
|
||||||
result = action
|
|
||||||
break
|
|
||||||
case "auto":
|
|
||||||
device.auto()
|
|
||||||
result = action
|
|
||||||
break
|
|
||||||
case "heat":
|
|
||||||
device.heat()
|
|
||||||
result = action
|
|
||||||
break
|
|
||||||
case "cool":
|
|
||||||
device.cool()
|
|
||||||
result = action
|
|
||||||
break
|
|
||||||
case "off":
|
|
||||||
device.off()
|
|
||||||
result = action
|
|
||||||
break
|
|
||||||
case "emergency heat":
|
|
||||||
device.emergencyHeat()
|
|
||||||
result = action
|
|
||||||
break
|
|
||||||
case "fanauto":
|
|
||||||
device.fanAuto()
|
|
||||||
result = action
|
|
||||||
break
|
|
||||||
case "fanon":
|
|
||||||
device.fanOn()
|
|
||||||
result = action
|
|
||||||
break
|
|
||||||
case "fancirculate":
|
|
||||||
device.fanCirculate()
|
|
||||||
result = action
|
|
||||||
break
|
|
||||||
case ~/heat-(.*)/:
|
|
||||||
def values = action.tokenize('-')
|
|
||||||
def temp = values[1]
|
|
||||||
result = "set heat point failed"
|
|
||||||
if (temp.isDouble()){
|
|
||||||
temp = temp.toDouble()/10
|
|
||||||
device.setHeatingSetpoint(temp)
|
|
||||||
result = temp
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case ~/cool-(.*)/:
|
|
||||||
def values = action.tokenize('-')
|
|
||||||
def temp = values[1]
|
|
||||||
result = "set cool point failed"
|
|
||||||
if (temp.isDouble()){
|
|
||||||
temp = temp.toDouble()/10
|
|
||||||
device.setCoolingSetpoint(temp)
|
|
||||||
result = temp
|
|
||||||
}
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
log.debug "no action"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def invokeButtonAction(device, action){
|
|
||||||
switch (action){
|
|
||||||
case "s1":
|
|
||||||
break
|
|
||||||
case "s2":
|
|
||||||
break
|
|
||||||
case "s3":
|
|
||||||
break
|
|
||||||
case "s4":
|
|
||||||
break
|
|
||||||
case "l1":
|
|
||||||
break
|
|
||||||
case "l2":
|
|
||||||
break
|
|
||||||
case "l3":
|
|
||||||
break
|
|
||||||
case "l4":
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
log.debug "no action"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def invokeMusicAction(device, action){
|
|
||||||
def result = action
|
|
||||||
switch (action){
|
|
||||||
case "play":
|
|
||||||
device.play()
|
|
||||||
result = action
|
|
||||||
break
|
|
||||||
case "stop":
|
|
||||||
device.stop()
|
|
||||||
break
|
|
||||||
case "pause":
|
|
||||||
device.pause()
|
|
||||||
result = action
|
|
||||||
break
|
|
||||||
case "next":
|
|
||||||
device.nextTrack()
|
|
||||||
result = action
|
|
||||||
break
|
|
||||||
case "previous":
|
|
||||||
device.previousTrack()
|
|
||||||
result = action
|
|
||||||
break
|
|
||||||
case 0..100:
|
|
||||||
device.setLevel(action)
|
|
||||||
result = action
|
|
||||||
break
|
|
||||||
case "mute":
|
|
||||||
device.mute()
|
|
||||||
result = action
|
|
||||||
break
|
|
||||||
case "unmute":
|
|
||||||
device.unmute()
|
|
||||||
result = action
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
log.debug "no action"
|
|
||||||
result = "no action found, $action"
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
def invokeDimmerAction(device, action){
|
|
||||||
def result
|
|
||||||
log.info "performing ${action}"
|
|
||||||
switch (action){
|
|
||||||
case "toggleSwitch":
|
|
||||||
result = toggleSwitch(device)
|
|
||||||
break
|
|
||||||
case 0..100:
|
|
||||||
device.setLevel(action)
|
|
||||||
result = action
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
result = "No Action Found Matching $action"
|
|
||||||
log.debug "no action"
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
def invokeLockAction(device, action){
|
|
||||||
def result
|
|
||||||
switch (action){
|
|
||||||
case "lock":
|
|
||||||
device.lock()
|
|
||||||
result = "locked"
|
|
||||||
break
|
|
||||||
case "unlock":
|
|
||||||
device.unlock()
|
|
||||||
result = "unlocked"
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
result = "no action found, $action"
|
|
||||||
log.debug "no action"
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
def toggleSwitch(device){
|
|
||||||
def result
|
|
||||||
if (device.currentSwitch == "on") {
|
|
||||||
device.off()
|
|
||||||
result = "off"
|
|
||||||
}else{
|
|
||||||
device.on()
|
|
||||||
result = "on"
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def getDevices(){
|
|
||||||
def resp = []
|
|
||||||
if (myButton){
|
|
||||||
myButton.each {
|
|
||||||
resp << [device: it, class:'button']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (myThermostat){
|
|
||||||
myThermostat.each {
|
|
||||||
resp << [device: it,
|
|
||||||
class:'thermostat',
|
|
||||||
temp:it.currentTemperature,
|
|
||||||
mode: it.currentThermostatMode,
|
|
||||||
fan: it.currentThermostatFanMode,
|
|
||||||
tempUnit: it.currentDeviceTemperatureUnit,
|
|
||||||
humidity: it.currentHumidity,
|
|
||||||
heatSetpoint: it.currentHeatingSetpoint,
|
|
||||||
coolSetpoint: it.currentCoolingSetpoint,
|
|
||||||
minCoolSetpoint: it.currentMinCoolingSetpoint,
|
|
||||||
maxCoolSetpoint: it.currentMaxCoolingSetpoint,
|
|
||||||
minHeatSetpoint: it.currentMinHeatingSetpoint,
|
|
||||||
maxHeatSetpoint: it.currentMaxHeatingSetpoint
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (mySwitch){
|
|
||||||
mySwitch.each {
|
|
||||||
resp << [device: it, class:'switch', status:it.currentSwitch]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (myPlug){
|
|
||||||
myPlug.each {
|
|
||||||
resp << [device: it, class:'plug', status:it.currentSwitch]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (myDimmer){
|
|
||||||
myDimmer.each {
|
|
||||||
resp << [device: it, class:'dimmer', status:it.currentSwitch, level:it.currentLevel]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (mySensor){
|
|
||||||
mySensor.each {
|
|
||||||
resp << [device: it, class:'sensor', contact: it.currentContact]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (myMotion){
|
|
||||||
myMotion.each{
|
|
||||||
resp << [device: it, class:'sensor', motion: it.currentMotion]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (myTemperature){
|
|
||||||
myTemperature.each{
|
|
||||||
resp << [device: it, class:'sensor', temp: it.currentTemperature] //, attributes:attrs]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (myCO2) {
|
|
||||||
myCO2.each{
|
|
||||||
resp << [device: it, class:'sensor', co2: it.currentCarbonDioxide]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (myBattery){
|
|
||||||
myBattery.each{
|
|
||||||
resp << [device: it, class:'sensor', battery: it.currentBattery]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (mySmoke){
|
|
||||||
mySmoke.each{ smoke->
|
|
||||||
resp << [device: smoke, class:'sensor', smoke: smoke.currentSmoke]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (myLock){
|
|
||||||
myLock.each {
|
|
||||||
resp << [device: it, class:'lock', status: it.currentLock]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return resp
|
|
||||||
}
|
|
||||||
|
|
||||||
def getRoutines(){
|
|
||||||
def resp = []
|
|
||||||
def routines = location.helloHome?.getPhrases()*.label
|
|
||||||
def currentMode = location.getCurrentMode().name
|
|
||||||
resp << [currentMode:currentMode, routines:routines]
|
|
||||||
log.debug("routines available ${routines}")
|
|
||||||
log.debug("current mode is ${currentMode}")
|
|
||||||
return resp
|
|
||||||
}
|
|
||||||
|
|
||||||
def executeRoutine(){
|
|
||||||
def routine = params.routine
|
|
||||||
location.helloHome?.execute(routine)
|
|
||||||
return [completed: routine]
|
|
||||||
}
|
|
||||||
|
|
||||||
def installed() {
|
|
||||||
log.debug "Installed with settings: ${settings}"
|
|
||||||
initialize()
|
|
||||||
}
|
|
||||||
|
|
||||||
def updated() {
|
|
||||||
log.debug "Updated with settings: ${settings}"
|
|
||||||
unsubscribe()
|
|
||||||
initialize()
|
|
||||||
}
|
|
||||||
|
|
||||||
def initialize() {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -20,9 +20,6 @@
|
|||||||
* JLH - 02-15-2014 - Fuller use of ecobee API
|
* JLH - 02-15-2014 - Fuller use of ecobee API
|
||||||
* 10-28-2015 DVCSMP-604 - accessory sensor, DVCSMP-1174, DVCSMP-1111 - not respond to routines
|
* 10-28-2015 DVCSMP-604 - accessory sensor, DVCSMP-1174, DVCSMP-1111 - not respond to routines
|
||||||
*/
|
*/
|
||||||
|
|
||||||
include 'localization'
|
|
||||||
|
|
||||||
definition(
|
definition(
|
||||||
name: "Ecobee (Connect)",
|
name: "Ecobee (Connect)",
|
||||||
namespace: "smartthings",
|
namespace: "smartthings",
|
||||||
@@ -89,7 +86,7 @@ def authPage() {
|
|||||||
if (numFound > 0) {
|
if (numFound > 0) {
|
||||||
section("") {
|
section("") {
|
||||||
paragraph "Tap below to see the list of ecobee sensors available in your ecobee account and select the ones you want to connect to SmartThings."
|
paragraph "Tap below to see the list of ecobee sensors available in your ecobee account and select the ones you want to connect to SmartThings."
|
||||||
input(name: "ecobeesensors", title: "Select Ecobee Sensors ({{numFound}} found)", messageArgs: [numFound: numFound], type: "enum", required:false, description: "Tap to choose", multiple:true, options:options)
|
input(name: "ecobeesensors", title:"Select Ecobee Sensors (${numFound} found)", type: "enum", required:false, description: "Tap to choose", multiple:true, options:options)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
'''Connect your Ecobee thermostat to SmartThings.'''.ar=قم بتوصيل ثرموستات Ecobee بـ SmartThings.
|
|
||||||
'''You are connected.'''.ar=أنت متصل.
|
|
||||||
'''Click to enter Ecobee Credentials'''.ar=النقر لإدخال بيانات اعتماد Ecobee
|
|
||||||
'''Login'''.ar=تسجيل الدخول
|
|
||||||
'''Tap below to log in to the ecobee service and authorize SmartThings access. Be sure to scroll down on page 2 and press the 'Allow' button.'''.ar=انقر أدناه لتسجيل الدخول إلى خدمة ecobee والمصادقة على الوصول إلى SmartThings. تأكد من التمرير للأسفل على الصفحة ٢ والضغط على زر ”السماح“.
|
|
||||||
'''Select Your Thermostats'''.ar=تحديد الثرموستات
|
|
||||||
'''Tap below to see the list of ecobee thermostats available in your ecobee account and select the ones you want to connect to SmartThings.'''.ar=انقر أدناه لرؤية قائمة أجهزة ثرموستات ecobee المتوفرة في حساب ecobee، وحدد الأجهزة التي ترغب في توصيلها بـ SmartThings.
|
|
||||||
'''Tap to choose'''.ar=النقر لاختيار
|
|
||||||
'''Tap below to see the list of ecobee sensors available in your ecobee account and select the ones you want to connect to SmartThings.'''.ar=انقر أدناه لرؤية قائمة مستشعرات ecobee المتوفرة في حساب ecobee، وحدد الأجهزة التي ترغب في توصيلها بـ SmartThings.
|
|
||||||
'''Select Ecobee Sensors ({{numFound}} found)'''.ar=تحديد مستشعرات Ecobee ({{numFound}} found)
|
|
||||||
'''Your ecobee Account is now connected to SmartThings!'''.ar=حساب ecobee متصل الآن بـ SmartThings!
|
|
||||||
'''Click 'Done' to finish setup.'''.ar=انقر فوق ”تم“ لإنهاء الإعداد.
|
|
||||||
'''The connection could not be established!'''.ar=يتعذر إنشاء الاتصال!
|
|
||||||
'''Click 'Done' to return to the menu.'''.ar=انقر فوق ”تم“ للعودة إلى القائمة.
|
|
||||||
'''{{deviceName}} is connected to SmartThings'''.ar={{deviceName}} متصل بـ SmartThings
|
|
||||||
'''{{deviceName}} is disconnected from SmartThings, because the access credential changed or was lost. Please go to the Ecobee (Connect) SmartApp and re-enter your account login credentials.'''.ar=تم قطع اتصال {{deviceName}} بـ SmartThings، لأن بيانات اعتماد الوصول قد تغيرت أو فُقدت. يُرجى الانتقال إلى التطبيق الذكي Ecobee (Connect) وإعادة إدخال بيانات اعتماد تسجيل الدخول إلى حسابك.
|
|
||||||
'''Your Ecobee thermostat'''.ar=ثرموستات Ecobee
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
'''Connect your Ecobee thermostat to SmartThings.'''.th=เชื่อมต่อตัวควบคุมอุณหภูมิ Ecobee ของคุณเข้ากับ SmartThings
|
|
||||||
'''You are connected.'''.th=คุณได้เชื่อมต่อแล้ว
|
|
||||||
'''Click to enter Ecobee Credentials'''.th=คลิกเพื่อใส่ ข้อมูลยืนยันตัวตน Ecobee
|
|
||||||
'''Login'''.th=เข้าสู่ระบบ
|
|
||||||
'''Tap below to log in to the ecobee service and authorize SmartThings access. Be sure to scroll down on page 2 and press the 'Allow' button.'''.th=แตะด้านล่างเพื่อเข้าสู่บริการ Ecobee และอนุญาตการเข้าถึงของ SmartThings ดูให้แน่ใจว่าได้เลื่อนลงมาที่หน้า 2 แล้วกดปุ่ม 'อนุญาต'
|
|
||||||
'''Select Your Thermostats'''.th=เลือกตัวควบคุมอุณหภูมิของคุณ
|
|
||||||
'''Tap below to see the list of ecobee thermostats available in your ecobee account and select the ones you want to connect to SmartThings.'''.th=แตะที่ด้านล่างเพื่อดูรายการตัวควบคุมอุณหภูมิ Ecobee ที่มีอยู่ในบัญชีผู้ใช้ Ecobee ของคุณ และเลือกตัวควบคุมอุณหภูมิที่คุณต้องการจะเชื่อมต่อกับ SmartThings
|
|
||||||
'''Tap to choose'''.th=แตะเพื่อเลือก
|
|
||||||
'''Tap below to see the list of ecobee sensors available in your ecobee account and select the ones you want to connect to SmartThings.'''.th=แตะที่ด้านล่างเพื่อดูรายการเซ็นเซอร์ Ecobee ที่มีอยู่ในบัญชีผู้ใช้ Ecobee ของคุณ และเลือกเซ็นเซอร์ที่คุณต้องการจะเชื่อมต่อกับ SmartThings
|
|
||||||
'''Select Ecobee Sensors ({{numFound} found)'''.th=เลือกเซ็นเซอร์ Ecobee ({{numFound}} found)
|
|
||||||
'''Your ecobee Account is now connected to SmartThings!'''.th=ตอนนี้บัญชีผู้ใช้ Ecobee ของคุณเชื่อมต่อกับ SmartThings แล้ว
|
|
||||||
'''Click 'Done' to finish setup.'''.th=คลิก 'เสร็จสิ้น' เพื่อทำการตั้งค่าให้เสร็จสิ้น
|
|
||||||
'''The connection could not be established!'''.th=ไม่สามารถสร้างการเชื่อมต่อได้!
|
|
||||||
'''Click 'Done' to return to the menu.'''.th=คลิก 'เสร็จสิ้น' เพื่อกลับไปยังเมนู
|
|
||||||
'''{{deviceName}} is connected to SmartThings'''.th={{deviceName}} เชื่อมต่อกับ SmartThings แล้ว
|
|
||||||
'''{{deviceName}} is disconnected from SmartThings, because the access credential changed or was lost. Please go to the Ecobee (Connect) SmartApp and re-enter your account login credentials.'''.th={{deviceName}} ถูกตัดการเชื่อมต่อจาก SmartThings เนื่องจากข้อมูลการเข้าถึงถูกเปลี่ยนแปลงหรือหายไป กรุณาไปที่ Ecobee (การเชื่อมต่อ) SmartApp และใส่ข้อมูลยืนยันตัวตนการเข้าสู่บัญชีผู้ใช้ของคุณอีกครั้ง
|
|
||||||
'''Your Ecobee thermostat'''.th=ตัวควบคุมอุณหภูมิ Ecobee ของคุณ
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
'''Connect your Ecobee thermostat to SmartThings.'''.tr=Ecobee termostatınızı SmartThings'e bağlayın.
|
|
||||||
'''You are connected.'''.tr=Bağlantı kurdunuz.
|
|
||||||
'''Click to enter Ecobee Credentials'''.tr=Ecobee Kimlik Bilgilerinizi girmek için tıklayın
|
|
||||||
'''Login'''.tr=Oturum aç
|
|
||||||
'''Tap below to log in to the ecobee service and authorize SmartThings access. Be sure to scroll down on page 2 and press the 'Allow' button.'''.tr=Ecobee servisinde oturum açmak ve SmartThings erişimine izin vermek için aşağıya dokunun. Ekranı 2. sayfaya kaydırdığınızdan emin olun ve 'İzin Ver' tuşuna basın.
|
|
||||||
'''Select Your Thermostats'''.tr=Termostatlarınızı Seçin
|
|
||||||
'''Tap below to see the list of ecobee thermostats available in your ecobee account and select the ones you want to connect to SmartThings.'''.tr=Ecobee hesabınızda mevcut olan ecobee termostatlarının listesini görüntülemek için aşağıya dokunun ve SmartThings'e bağlamak istediklerinizi seçin.
|
|
||||||
'''Tap to choose'''.tr=Seçmek için dokunun
|
|
||||||
'''Tap below to see the list of ecobee sensors available in your ecobee account and select the ones you want to connect to SmartThings.'''.tr=Ecobee hesabınızda mevcut olan ecobee sensörlerinin listesini görüntülemek için aşağıya dokunun ve SmartThings'e bağlamak istediklerinizi seçin.
|
|
||||||
'''Select Ecobee Sensors ({{numFound}} found)'''.tr=Ecobee Sensörlerini seçin ({{numFound}} bulundu)
|
|
||||||
'''Your ecobee Account is now connected to SmartThings!'''.tr=Ecobee Hesabınız artık SmartThings'e bağlandı!
|
|
||||||
'''Click 'Done' to finish setup.'''.tr=Kurulumu bitirmek için 'Bitti' öğesine tıklayın.
|
|
||||||
'''The connection could not be established!'''.tr=Bağlantı kurulamadı!
|
|
||||||
'''Click 'Done' to return to the menu.'''.tr=Menüye dönmek için 'Bitti' öğesine tıklayın.
|
|
||||||
'''{{deviceName}} is connected to SmartThings'''.tr={{cihazİsmi}} SmartThings'e bağlandı
|
|
||||||
'''{{deviceName}} is disconnected from SmartThings, because the access credential changed or was lost. Please go to the Ecobee (Connect) SmartApp and re-enter your account login credentials.'''.tr=Ecobee termostatınız
|
|
||||||
'''Your Ecobee thermostat'''.tr=Ecobee termostatınız
|
|
||||||
Reference in New Issue
Block a user