mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-11 13:21:51 +00:00
Compare commits
17 Commits
MSA-1483-1
...
DEVC-489-1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22bbe08d0c | ||
|
|
218cc43520 | ||
|
|
9ddc020f04 | ||
|
|
9fbbaec8f6 | ||
|
|
e4c1824afd | ||
|
|
797a58cb68 | ||
|
|
c428267d63 | ||
|
|
81cf1179ef | ||
|
|
79d20b0edb | ||
|
|
b6d862fdd4 | ||
|
|
d58084c438 | ||
|
|
dbfaef3e69 | ||
|
|
40ed88e7fd | ||
|
|
1d6e22dc16 | ||
|
|
1d180ac487 | ||
|
|
230541a145 | ||
|
|
4ad0a6fd9d |
@@ -87,16 +87,27 @@ def beep() {
|
||||
up to this long from the time you send the message to the time you hear a sound.
|
||||
*/
|
||||
|
||||
// Used source endpoint of 0x02 because we are using smartthings manufacturer specific cluster.
|
||||
[
|
||||
"raw 0xFC05 {15 0A 11 00 00 15 01}",
|
||||
"delay 200",
|
||||
"send 0x$zigbee.deviceNetworkId 0x02 0x$zigbee.endpointId",
|
||||
"delay 7000",
|
||||
"raw 0xFC05 {15 0A 11 00 00 15 01}",
|
||||
"delay 200",
|
||||
"send 0x$zigbee.deviceNetworkId 0x02 0x$zigbee.endpointId",
|
||||
"delay 7000",
|
||||
"raw 0xFC05 {15 0A 11 00 00 15 01}",
|
||||
"delay 200",
|
||||
"send 0x$zigbee.deviceNetworkId 0x02 0x$zigbee.endpointId",
|
||||
"delay 7000",
|
||||
"raw 0xFC05 {15 0A 11 00 00 15 01}",
|
||||
"delay 200",
|
||||
"send 0x$zigbee.deviceNetworkId 0x02 0x$zigbee.endpointId",
|
||||
"delay 7000",
|
||||
"raw 0xFC05 {15 0A 11 00 00 15 01}"
|
||||
"raw 0xFC05 {15 0A 11 00 00 15 01}",
|
||||
"delay 200",
|
||||
"send 0x$zigbee.deviceNetworkId 0x02 0x$zigbee.endpointId",
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -105,11 +105,21 @@ def parseDescriptionAsMap(description) {
|
||||
|
||||
// Commands to device
|
||||
def on() {
|
||||
'zcl on-off on'
|
||||
[
|
||||
'zcl on-off on',
|
||||
'delay 200',
|
||||
"send 0x${zigbee.deviceNetworkId} 0x01 0x${zigbee.endpointId}",
|
||||
'delay 500'
|
||||
]
|
||||
}
|
||||
|
||||
def off() {
|
||||
'zcl on-off off'
|
||||
[
|
||||
'zcl on-off off',
|
||||
'delay 200',
|
||||
"send 0x${zigbee.deviceNetworkId} 0x01 0x${zigbee.endpointId}",
|
||||
'delay 500'
|
||||
]
|
||||
}
|
||||
|
||||
def setLevel(value) {
|
||||
|
||||
@@ -47,9 +47,21 @@ def parse(String description) {
|
||||
|
||||
// Commands to device
|
||||
def on() {
|
||||
'zcl on-off on'
|
||||
[
|
||||
'zcl on-off on',
|
||||
'delay 200',
|
||||
"send 0x${zigbee.deviceNetworkId} 0x01 0x${zigbee.endpointId}",
|
||||
'delay 500'
|
||||
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
def off() {
|
||||
'zcl on-off off'
|
||||
[
|
||||
'zcl on-off off',
|
||||
'delay 200',
|
||||
"send 0x${zigbee.deviceNetworkId} 0x01 0x${zigbee.endpointId}",
|
||||
'delay 500'
|
||||
]
|
||||
}
|
||||
|
||||
@@ -31,7 +31,8 @@
|
||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019", manufacturer: "Yale", model: "YRD210 PB DB", deviceJoinName: "Yale Push Button Deadbolt Lock"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019", manufacturer: "Yale", model: "YRD220/240 TSDB", deviceJoinName: "Yale Touch Screen Deadbolt Lock"
|
||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019", manufacturer: "Yale", model: "YRL210 PB LL", deviceJoinName: "Yale Push Button Lever Lock"
|
||||
}
|
||||
fingerprint profileId: "0104", inClusters: "0000,0001,0003,0009,000A,0101,0020", outClusters: "000A,0019", manufacturer: "Yale", model: "YRD226/246 TSDB", deviceJoinName: "Yale Touch Screen Deadbolt Lock"
|
||||
}
|
||||
|
||||
tiles(scale: 2) {
|
||||
multiAttributeTile(name:"toggle", type:"generic", width:6, height:4){
|
||||
|
||||
@@ -21,6 +21,7 @@ metadata {
|
||||
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0004, 0005, 0006"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0006", outClusters: "0003, 0006, 0019, 0406", manufacturer: "Leviton", model: "ZSS-10", deviceJoinName: "Leviton Switch"
|
||||
fingerprint profileId: "0104", inClusters: "0000, 0003, 0006", outClusters: "000A", manufacturer: "HAI", model: "65A21-1", deviceJoinName: "Leviton Wireless Load Control Module-30amp"
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
@@ -79,4 +80,4 @@ def refresh() {
|
||||
def configure() {
|
||||
log.debug "Configuring Reporting and Bindings."
|
||||
zigbee.onOffConfig() + zigbee.onOffRefresh()
|
||||
}
|
||||
}
|
||||
@@ -1,683 +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:"#ffffff", nextState:"locking"
|
||||
attributeState "unknown", label:"unknown", action:"lock.lock", icon:"st.locks.lock.unknown", backgroundColor:"#ffffff", nextState:"locking"
|
||||
attributeState "locking", label:'locking', icon:"st.locks.lock.locked", backgroundColor:"#79b821"
|
||||
attributeState "unlocking", label:'unlocking', icon:"st.locks.lock.unlocked", backgroundColor:"#ffffff"
|
||||
}
|
||||
}
|
||||
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] ]
|
||||
}
|
||||
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) {
|
||||
case 21: // Manually locked
|
||||
case 18: // Locked with keypad
|
||||
case 24: // Locked by command (Kwikset 914)
|
||||
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
|
||||
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,7 +1,7 @@
|
||||
/**
|
||||
* Smart Windows
|
||||
* Compares two temperatures – indoor vs outdoor, for example – then sends an alert if windows are open (or closed!).
|
||||
*
|
||||
*
|
||||
* Copyright 2014 Eric Gideon
|
||||
*
|
||||
* Based in part on the "When it's going to rain" SmartApp by the SmartThings team,
|
||||
@@ -21,13 +21,18 @@ definition(
|
||||
name: "Smart Windows",
|
||||
namespace: "egid",
|
||||
author: "Eric Gideon",
|
||||
description: "Compares two temperatures – indoor vs outdoor, for example – then sends an alert if windows are open (or closed!). If you don't use an external temperature device, your zipcode will be used instead.",
|
||||
description: "Compares two temperatures – indoor vs outdoor, for example – then sends an alert if windows are open (or closed!). If you don't use an external temperature device, your location will be used instead.",
|
||||
iconUrl: "https://s3.amazonaws.com/smartthings-device-icons/Home/home9-icn.png",
|
||||
iconX2Url: "https://s3.amazonaws.com/smartthings-device-icons/Home/home9-icn@2x.png"
|
||||
)
|
||||
|
||||
|
||||
preferences {
|
||||
|
||||
if (!(location.zipCode || ( location.latitude && location.longitude )) && location.channelName == 'samsungtv') {
|
||||
section { paragraph title: "Note:", "Location is required for this SmartApp. Go to 'Location Name' settings to setup your correct location." }
|
||||
}
|
||||
|
||||
section( "Set the temperature range for your comfort zone..." ) {
|
||||
input "minTemp", "number", title: "Minimum temperature"
|
||||
input "maxTemp", "number", title: "Maximum temperature"
|
||||
@@ -39,9 +44,11 @@ preferences {
|
||||
input "inTemp", "capability.temperatureMeasurement", title: "Indoor"
|
||||
input "outTemp", "capability.temperatureMeasurement", title: "Outdoor (optional)", required: false
|
||||
}
|
||||
section( "Set your location" ) {
|
||||
input "zipCode", "text", title: "Zip code"
|
||||
}
|
||||
|
||||
if (location.channelName != 'samsungtv') {
|
||||
section( "Set your location" ) { input "zipCode", "text", title: "Zip code" }
|
||||
}
|
||||
|
||||
section( "Notifications" ) {
|
||||
input "sendPushMessage", "enum", title: "Send a push notification?", metadata:[values:["Yes","No"]], required:false
|
||||
input "retryPeriod", "number", title: "Minutes between notifications:"
|
||||
@@ -72,7 +79,7 @@ def temperatureHandler(evt) {
|
||||
|
||||
def currentInTemp = evt.doubleValue
|
||||
def openWindows = sensors.findAll { it?.latestValue("contact") == 'open' }
|
||||
|
||||
|
||||
log.trace "Temp event: $evt"
|
||||
log.info "In: $currentInTemp; Out: $currentOutTemp"
|
||||
|
||||
@@ -98,7 +105,7 @@ def temperatureHandler(evt) {
|
||||
if ( currentOutTemp < maxTemp && !openWindows ) {
|
||||
send( "Open some windows to cool down the house! Currently ${currentInTemp}°F inside and ${currentOutTemp}°F outside." )
|
||||
} else if ( currentOutTemp > maxTemp && openWindows ) {
|
||||
send( "It's gotten warmer outside! You should close these windows: ${openWindows.join(', ')}. Currently ${currentInTemp}°F inside and ${currentOutTemp}°F outside." )
|
||||
send( "It's gotten warmer outside! You should close these windows: ${openWindows.join(', ')}. Currently ${currentInTemp}°F inside and ${currentOutTemp}°F outside." )
|
||||
} else {
|
||||
log.debug "No notifications sent. Everything is in the right place."
|
||||
}
|
||||
@@ -125,7 +132,11 @@ def temperatureHandler(evt) {
|
||||
}
|
||||
|
||||
def weatherCheck() {
|
||||
def json = getWeatherFeature("conditions", zipCode)
|
||||
def json
|
||||
if (location.channelName != 'samsungtv')
|
||||
json = getWeatherFeature("conditions", zipCode)
|
||||
else
|
||||
json = getWeatherFeature("conditions")
|
||||
def currentTemp = json?.current_observation?.temp_f
|
||||
|
||||
if ( currentTemp ) {
|
||||
@@ -150,4 +161,4 @@ private send(msg) {
|
||||
}
|
||||
|
||||
log.info msg
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
253
smartapps/gideon-api/gideon.src/gideon.groovy
Normal file
253
smartapps/gideon-api/gideon.src/gideon.groovy
Normal file
@@ -0,0 +1,253 @@
|
||||
/**
|
||||
* Gideon
|
||||
*
|
||||
* Copyright 2016 Nicola Russo
|
||||
*
|
||||
* 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: "Gideon",
|
||||
namespace: "gideon.api",
|
||||
author: "Braindrain Solutions",
|
||||
description: "Gideon AI Smart app allows you to connect and control all of your SmartThings devices through the Gideon AI app, making your SmartThings devices even smarter.",
|
||||
category: "Family",
|
||||
iconUrl: "http://s33.postimg.org/t77u7y7v3/logo.png",
|
||||
iconX2Url: "http://s33.postimg.org/t77u7y7v3/logo.png",
|
||||
iconX3Url: "http://s33.postimg.org/t77u7y7v3/logo.png",
|
||||
oauth: [displayName: "Gideon AI API", displayLink: "gideon.ai"])
|
||||
|
||||
|
||||
preferences {
|
||||
section("Control these switches...") {
|
||||
input "switches", "capability.switch", multiple:true
|
||||
}
|
||||
section("Control these motion sensors...") {
|
||||
input "motions", "capability.motionSensor", multiple:true
|
||||
}
|
||||
section("Control these presence sensors...") {
|
||||
input "presence_sensors", "capability.presenceSensor", multiple:true
|
||||
}
|
||||
section("Control these outlets...") {
|
||||
input "outlets", "capability.switch", multiple:true
|
||||
}
|
||||
section("Control these locks...") {
|
||||
input "locks", "capability.lock", multiple:true
|
||||
}
|
||||
section("Control these locks...") {
|
||||
input "temperature_sensors", "capability.temperatureMeasurement"
|
||||
}
|
||||
}
|
||||
|
||||
def installed() {
|
||||
log.debug "Installed with settings: ${settings}"
|
||||
|
||||
initialize()
|
||||
}
|
||||
|
||||
def updated() {
|
||||
log.debug "Updated with settings: ${settings}"
|
||||
|
||||
unsubscribe()
|
||||
initialize()
|
||||
}
|
||||
|
||||
def initialize() {
|
||||
// TODO: subscribe to attributes, devices, locations, etc.
|
||||
subscribe(outlet, "energy", outletHandler)
|
||||
subscribe(outlet, "switch", outletHandler)
|
||||
}
|
||||
|
||||
// TODO: implement event handlers
|
||||
def outletHandler(evt) {
|
||||
log.debug "$outlet.currentEnergy"
|
||||
//TODO call G API
|
||||
}
|
||||
|
||||
|
||||
private device(it, type) {
|
||||
it ? [id: it.id, label: it.label, type: type] : null
|
||||
}
|
||||
|
||||
//API Mapping
|
||||
mappings {
|
||||
path("/getalldevices") {
|
||||
action: [
|
||||
GET: "getAllDevices"
|
||||
]
|
||||
}
|
||||
path("/doorlocks/:id/:command") {
|
||||
action: [
|
||||
GET: "updateDoorLock"
|
||||
]
|
||||
}
|
||||
path("/doorlocks/:id") {
|
||||
action: [
|
||||
GET: "getDoorLockStatus"
|
||||
]
|
||||
}
|
||||
path("/tempsensors/:id") {
|
||||
action: [
|
||||
GET: "getTempSensorsStatus"
|
||||
]
|
||||
}
|
||||
path("/presences/:id") {
|
||||
action: [
|
||||
GET: "getPresenceStatus"
|
||||
]
|
||||
}
|
||||
path("/motions/:id") {
|
||||
action: [
|
||||
GET: "getMotionStatus"
|
||||
]
|
||||
}
|
||||
path("/outlets/:id") {
|
||||
action: [
|
||||
GET: "getOutletStatus"
|
||||
]
|
||||
}
|
||||
path("/outlets/:id/:command") {
|
||||
action: [
|
||||
GET: "updateOutlet"
|
||||
]
|
||||
}
|
||||
path("/switches/:command") {
|
||||
action: [
|
||||
PUT: "updateSwitch"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
//API Methods
|
||||
def getAllDevices() {
|
||||
def locks_list = locks.collect{device(it,"Lock")}
|
||||
def presences_list = presence_sensors.collect{device(it,"Presence")}
|
||||
def motions_list = motions.collect{device(it,"Motion")}
|
||||
def outlets_list = outlets.collect{device(it,"Outlet")}
|
||||
def switches_list = switches.collect{device(it,"Switch")}
|
||||
def temp_list = temperature_sensors.collect{device(it,"Temperature")}
|
||||
return [Locks: locks_list, Presences: presences_list, Motions: motions_list, Outlets: outlets_list, Switches: switches_list, Temperatures: temp_list]
|
||||
}
|
||||
|
||||
//LOCKS
|
||||
def getDoorLockStatus() {
|
||||
def device = locks.find { it.id == params.id }
|
||||
if (!device) {
|
||||
httpError(404, "Device not found")
|
||||
} else {
|
||||
return [Device_state: device.currentValue('lock')]
|
||||
}
|
||||
}
|
||||
|
||||
def updateDoorLock() {
|
||||
def command = params.command
|
||||
def device = locks.find { it.id == params.id }
|
||||
if (command){
|
||||
if (!device) {
|
||||
httpError(404, "Device not found")
|
||||
} else {
|
||||
if(command == "toggle")
|
||||
{
|
||||
if(device.currentValue('lock') == "locked")
|
||||
device.unlock();
|
||||
else
|
||||
device.lock();
|
||||
|
||||
return [Device_id: params.id, result_action: "200"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//PRESENCE
|
||||
def getPresenceStatus() {
|
||||
|
||||
def device = presence_sensors.find { it.id == params.id }
|
||||
if (!device) {
|
||||
httpError(404, "Device not found")
|
||||
} else {
|
||||
return [Device_state: device.currentValue('presence')]
|
||||
}
|
||||
}
|
||||
|
||||
//MOTION
|
||||
def getMotionStatus() {
|
||||
|
||||
def device = motions.find { it.id == params.id }
|
||||
if (!device) {
|
||||
httpError(404, "Device not found")
|
||||
} else {
|
||||
return [Device_state: device.currentValue('motion')]
|
||||
}
|
||||
}
|
||||
|
||||
//OUTLET
|
||||
def getOutletStatus() {
|
||||
|
||||
def device = outlets.find { it.id == params.id }
|
||||
if (!device) {
|
||||
httpError(404, "Device not found")
|
||||
} else {
|
||||
return [Device_state: device.currentSwitch, Current_watt: device.currentValue("energy")]
|
||||
}
|
||||
}
|
||||
|
||||
def updateOutlet() {
|
||||
|
||||
def command = params.command
|
||||
def device = outlets.find { it.id == params.id }
|
||||
if (command){
|
||||
if (!device) {
|
||||
httpError(404, "Device not found")
|
||||
} else {
|
||||
if(command == "toggle")
|
||||
{
|
||||
if(device.currentSwitch == "on")
|
||||
device.off();
|
||||
else
|
||||
device.on();
|
||||
|
||||
return [Device_id: params.id, result_action: "200"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//SWITCH
|
||||
def updateSwitch() {
|
||||
def command = params.command
|
||||
def device = switches.find { it.id == params.id }
|
||||
if (command){
|
||||
if (!device) {
|
||||
httpError(404, "Device not found")
|
||||
} else {
|
||||
if(command == "toggle")
|
||||
{
|
||||
if(device.currentSwitch == "on")
|
||||
device.off();
|
||||
else
|
||||
device.on();
|
||||
|
||||
return [Device_id: params.id, result_action: "200"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TEMPERATURE
|
||||
def getTempSensorsStatus() {
|
||||
|
||||
def device = temperature_sensors.find { it.id == params.id }
|
||||
if (!device) {
|
||||
httpError(404, "Device not found")
|
||||
} else {
|
||||
return [Device_state: device.currentValue('temperature')]
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,13 @@ definition(
|
||||
)
|
||||
|
||||
preferences {
|
||||
section("Zip code?") {
|
||||
input "zipcode", "text", title: "Zipcode?"
|
||||
|
||||
if (!(location.zipCode || ( location.latitude && location.longitude )) && location.channelName == 'samsungtv') {
|
||||
section { paragraph title: "Note:", "Location is required for this SmartApp. Go to 'Location Name' settings to setup your correct location." }
|
||||
}
|
||||
|
||||
if (location.channelName != 'samsungtv') {
|
||||
section( "Set your location" ) { input "zipCode", "text", title: "Zip code" }
|
||||
}
|
||||
|
||||
section("Things to check?") {
|
||||
@@ -60,7 +65,11 @@ def scheduleCheck(evt) {
|
||||
// Only need to poll if we haven't checked in a while - and if something is left open.
|
||||
if((now() - (30 * 60 * 1000) > state.lastCheck["time"]) && open) {
|
||||
log.info("Something's open - let's check the weather.")
|
||||
def response = getWeatherFeature("forecast", zipcode)
|
||||
def response
|
||||
if (location.channelName != 'samsungtv')
|
||||
response = getWeatherFeature("forecast", zipCode)
|
||||
else
|
||||
response = getWeatherFeature("forecast")
|
||||
def weather = isStormy(response)
|
||||
|
||||
if(weather) {
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
* Author: Juan Risso
|
||||
* Date: 2013-12-19
|
||||
*/
|
||||
|
||||
include 'asynchttp_v1'
|
||||
|
||||
definition(
|
||||
name: "Jawbone UP (Connect)",
|
||||
namespace: "juano2310",
|
||||
@@ -303,7 +306,8 @@ def setup() {
|
||||
def childDevice = addChildDevice('juano2310', "Jawbone User", "${app.id}.${member.xid}",null,[name:"Jawbone UP - " + member.first, completedSetup: true])
|
||||
if (childDevice) {
|
||||
log.debug "Child Device Successfully Created"
|
||||
generateInitialEvent (member, childDevice)
|
||||
childDevice?.generateSleepingEvent(false)
|
||||
pollChild(childDevice)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -349,67 +353,67 @@ def uninstalled() {
|
||||
}
|
||||
|
||||
def pollChild(childDevice) {
|
||||
def member = state.member
|
||||
generatePollingEvents (member, childDevice)
|
||||
def childMap = [ value: "$childDevice.device.deviceNetworkId}"]
|
||||
|
||||
def params = [
|
||||
uri: 'https://jawbone.com',
|
||||
path: '/nudge/api/users/@me/goals',
|
||||
headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ],
|
||||
contentType: 'application/json'
|
||||
]
|
||||
|
||||
asynchttp_v1.get('responseGoals', params, childMap)
|
||||
|
||||
def params2 = [
|
||||
uri: 'https://jawbone.com',
|
||||
path: '/nudge/api/users/@me/moves',
|
||||
headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ],
|
||||
contentType: 'application/json'
|
||||
]
|
||||
|
||||
asynchttp_v1.get('responseMoves', params2, childMap)
|
||||
}
|
||||
|
||||
def generatePollingEvents (member, childDevice) {
|
||||
// lets figure out if the member is currently "home" (At the place)
|
||||
def urlgoals = "https://jawbone.com/nudge/api/users/@me/goals"
|
||||
def urlmoves = "https://jawbone.com/nudge/api/users/@me/moves"
|
||||
def urlsleeps = "https://jawbone.com/nudge/api/users/@me/sleeps"
|
||||
def goals = null
|
||||
def moves = null
|
||||
def sleeps = null
|
||||
httpGet(uri: urlgoals, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ]) {response ->
|
||||
goals = response.data.data
|
||||
}
|
||||
httpGet(uri: urlmoves, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ]) {response ->
|
||||
moves = response.data.data.items[0]
|
||||
}
|
||||
|
||||
try { // we are going to just ignore any errors
|
||||
log.debug "Member = ${member.first}"
|
||||
log.debug "Moves Goal = ${goals.move_steps} Steps"
|
||||
log.debug "Moves = ${moves.details.steps} Steps"
|
||||
|
||||
childDevice?.sendEvent(name:"steps", value: moves.details.steps)
|
||||
childDevice?.sendEvent(name:"goal", value: goals.move_steps)
|
||||
//setColor(moves.details.steps,goals.move_steps,childDevice)
|
||||
}
|
||||
catch (e) {
|
||||
// eat it
|
||||
}
|
||||
def responseGoals(response, dni) {
|
||||
if (response.hasError()) {
|
||||
log.error "response has error: $response.errorMessage"
|
||||
} else {
|
||||
def goals
|
||||
try {
|
||||
// json response already parsed into JSONElement object
|
||||
goals = response.json.data
|
||||
} catch (e) {
|
||||
log.error "error parsing json from response: $e"
|
||||
}
|
||||
if (goals) {
|
||||
def childDevice = getChildDevice(dni.value)
|
||||
log.debug "Goal = ${goals.move_steps} Steps"
|
||||
childDevice?.sendEvent(name:"goal", value: goals.move_steps)
|
||||
} else {
|
||||
log.debug "did not get json results from response body: $response.data"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def generateInitialEvent (member, childDevice) {
|
||||
// lets figure out if the member is currently "home" (At the place)
|
||||
def urlgoals = "https://jawbone.com/nudge/api/users/@me/goals"
|
||||
def urlmoves = "https://jawbone.com/nudge/api/users/@me/moves"
|
||||
def urlsleeps = "https://jawbone.com/nudge/api/users/@me/sleeps"
|
||||
def goals = null
|
||||
def moves = null
|
||||
def sleeps = null
|
||||
httpGet(uri: urlgoals, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ]) {response ->
|
||||
goals = response.data.data
|
||||
}
|
||||
httpGet(uri: urlmoves, headers: ["Authorization": "Bearer ${state.JawboneAccessToken}" ]) {response ->
|
||||
moves = response.data.data.items[0]
|
||||
}
|
||||
|
||||
try { // we are going to just ignore any errors
|
||||
log.debug "Member = ${member.first}"
|
||||
log.debug "Moves Goal = ${goals.move_steps} Steps"
|
||||
log.debug "Moves = ${moves.details.steps} Steps"
|
||||
log.debug "Sleeping state = false"
|
||||
childDevice?.generateSleepingEvent(false)
|
||||
childDevice?.sendEvent(name:"steps", value: moves.details.steps)
|
||||
childDevice?.sendEvent(name:"goal", value: goals.move_steps)
|
||||
//setColor(moves.details.steps,goals.move_steps,childDevice)
|
||||
}
|
||||
catch (e) {
|
||||
// eat it
|
||||
}
|
||||
def responseMoves(response, dni) {
|
||||
if (response.hasError()) {
|
||||
log.error "response has error: $response.errorMessage"
|
||||
} else {
|
||||
def moves
|
||||
try {
|
||||
// json response already parsed into JSONElement object
|
||||
moves = response.json.data.items[0]
|
||||
} catch (e) {
|
||||
log.error "error parsing json from response: $e"
|
||||
}
|
||||
if (moves) {
|
||||
def childDevice = getChildDevice(dni.value)
|
||||
log.debug "Moves = ${moves.details.steps} Steps"
|
||||
childDevice?.sendEvent(name:"steps", value: moves.details.steps)
|
||||
} else {
|
||||
log.debug "did not get json results from response body: $response.data"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def setColor (steps,goal,childDevice) {
|
||||
@@ -433,7 +437,7 @@ def hookEventHandler() {
|
||||
// get some stuff we need
|
||||
def userId = json.events.user_xid[0]
|
||||
def json_type = json.events.type[0]
|
||||
def json_action = json.events.action[0]
|
||||
def json_action = json.events.action[0]
|
||||
|
||||
//log.debug json
|
||||
log.debug "Userid = ${userId}"
|
||||
|
||||
@@ -454,17 +454,23 @@ def sendStopEvent(source) {
|
||||
eventData.value += "cancelled"
|
||||
}
|
||||
|
||||
// send 100% completion event
|
||||
sendTimeRemainingEvent(100)
|
||||
|
||||
// send a non-displayed 0% completion to reset tiles
|
||||
sendTimeRemainingEvent(0, false)
|
||||
|
||||
// send sessionStatus event last so the event feed is ordered properly
|
||||
sendControllerEvent(eventData)
|
||||
sendTimeRemainingEvent(0)
|
||||
}
|
||||
|
||||
def sendTimeRemainingEvent(percentComplete) {
|
||||
def sendTimeRemainingEvent(percentComplete, displayed = true) {
|
||||
log.trace "sendTimeRemainingEvent(${percentComplete})"
|
||||
|
||||
def percentCompleteEventData = [
|
||||
name: "percentComplete",
|
||||
value: percentComplete as int,
|
||||
displayed: true,
|
||||
displayed: displayed,
|
||||
isStateChange: true
|
||||
]
|
||||
sendControllerEvent(percentCompleteEventData)
|
||||
@@ -474,7 +480,7 @@ def sendTimeRemainingEvent(percentComplete) {
|
||||
def timeRemainingEventData = [
|
||||
name: "timeRemaining",
|
||||
value: displayableTime(timeRemaining),
|
||||
displayed: true,
|
||||
displayed: displayed,
|
||||
isStateChange: true
|
||||
]
|
||||
sendControllerEvent(timeRemainingEventData)
|
||||
@@ -608,8 +614,6 @@ private completion() {
|
||||
handleCompletionMessaging()
|
||||
|
||||
handleCompletionModesAndPhrases()
|
||||
|
||||
sendTimeRemainingEvent(100)
|
||||
}
|
||||
|
||||
private handleCompletionSwitches() {
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
* locks | lock | lock, unlock | locked, unlocked
|
||||
* ---------------------+-------------------+-----------------------------+------------------------------------
|
||||
*/
|
||||
include 'asynchttp_v1'
|
||||
|
||||
definition(
|
||||
name: "Logitech Harmony (Connect)",
|
||||
@@ -109,26 +110,28 @@ def authPage() {
|
||||
//device discovery request every 5 //25 seconds
|
||||
int deviceRefreshCount = !state.deviceRefreshCount ? 0 : state.deviceRefreshCount as int
|
||||
state.deviceRefreshCount = deviceRefreshCount + 1
|
||||
def refreshInterval = 3
|
||||
def refreshInterval = 5
|
||||
|
||||
def huboptions = state.HarmonyHubs ?: []
|
||||
def actoptions = state.HarmonyActivities ?: []
|
||||
|
||||
def numFoundHub = huboptions.size() ?: 0
|
||||
def numFoundAct = actoptions.size() ?: 0
|
||||
def numFoundAct = actoptions.size() ?: 0
|
||||
|
||||
if((deviceRefreshCount % 5) == 0) {
|
||||
discoverDevices()
|
||||
}
|
||||
|
||||
return dynamicPage(name:"Credentials", title:"Discovery Started!", nextPage:"", refreshInterval:refreshInterval, install:true, uninstall: true) {
|
||||
section("Please wait while we discover your Harmony Hubs and Activities. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
|
||||
input "selectedhubs", "enum", required:false, title:"Select Harmony Hubs (${numFoundHub} found)", multiple:true, options:huboptions
|
||||
input "selectedhubs", "enum", required:false, title:"Select Harmony Hubs (${numFoundHub} found)", multiple:true, submitOnChange: true, options:huboptions
|
||||
}
|
||||
// Virtual activity flag
|
||||
if (numFoundHub > 0 && numFoundAct > 0 && true)
|
||||
// Virtual activity flag
|
||||
if (numFoundHub > 0 && numFoundAct > 0 && true)
|
||||
section("You can also add activities as virtual switches for other convenient integrations") {
|
||||
input "selectedactivities", "enum", required:false, title:"Select Harmony Activities (${numFoundAct} found)", multiple:true, options:actoptions
|
||||
input "selectedactivities", "enum", required:false, title:"Select Harmony Activities (${numFoundAct} found)", multiple:true, submitOnChange: true, options:actoptions
|
||||
}
|
||||
if (state.resethub)
|
||||
if (state.resethub)
|
||||
section("Connection to the hub timed out. Please restart the hub and try again.") {}
|
||||
}
|
||||
}
|
||||
@@ -380,8 +383,6 @@ def discovery() {
|
||||
log.debug "valid Token"
|
||||
state.Harmonydevices = response.data
|
||||
state.resethub = false
|
||||
getActivityList()
|
||||
poll()
|
||||
} else {
|
||||
log.debug "Error: $response.status"
|
||||
}
|
||||
@@ -430,142 +431,182 @@ def addDevice() {
|
||||
}
|
||||
|
||||
def activity(dni,mode) {
|
||||
def Params = [auth: state.HarmonyAccessToken]
|
||||
def msg = "Command failed"
|
||||
def url = ''
|
||||
def tokenParam = [auth: state.HarmonyAccessToken]
|
||||
def url
|
||||
if (dni == "all") {
|
||||
url = "https://home.myharmony.com/cloudapi/activity/off?${toQueryString(Params)}"
|
||||
url = "https://home.myharmony.com/cloudapi/activity/off?${toQueryString(tokenParam)}"
|
||||
} else {
|
||||
def aux = dni.split('-')
|
||||
def hubId = aux[1]
|
||||
if (mode == "hub" || (aux.size() <= 2) || (aux[2] == "off")){
|
||||
url = "https://home.myharmony.com/cloudapi/hub/${hubId}/activity/off?${toQueryString(Params)}"
|
||||
url = "https://home.myharmony.com/cloudapi/hub/${hubId}/activity/off?${toQueryString(tokenParam)}"
|
||||
} else {
|
||||
def activityId = aux[2]
|
||||
url = "https://home.myharmony.com/cloudapi/hub/${hubId}/activity/${activityId}/${mode}?${toQueryString(Params)}"
|
||||
def activityId = aux[2]
|
||||
url = "https://home.myharmony.com/cloudapi/hub/${hubId}/activity/${activityId}/${mode}?${toQueryString(tokenParam)}"
|
||||
}
|
||||
}
|
||||
try {
|
||||
httpPostJson(uri: url) { response ->
|
||||
if (response.data.code == 200 || dni == "all") {
|
||||
msg = "Command sent succesfully"
|
||||
state.aux = 0
|
||||
} else {
|
||||
msg = "Command failed. Error: $response.data.code"
|
||||
}
|
||||
}
|
||||
} catch (groovyx.net.http.HttpResponseException ex) {
|
||||
log.error ex
|
||||
if (state.aux == 0) {
|
||||
state.aux = 1
|
||||
activity(dni,mode)
|
||||
} else {
|
||||
msg = ex
|
||||
state.aux = 0
|
||||
}
|
||||
} catch(Exception ex) {
|
||||
msg = ex
|
||||
def params = [
|
||||
uri: url,
|
||||
contentType: 'application/json'
|
||||
]
|
||||
asynchttp_v1.post('activityResponse', params)
|
||||
return "Command Sent"
|
||||
}
|
||||
|
||||
def activityResponse(response, data) {
|
||||
if (response.hasError()) {
|
||||
log.error "Logitech Harmony - response has error: $response.errorMessage"
|
||||
if (response.status == 401) { // token is expired
|
||||
state.remove("HarmonyAccessToken")
|
||||
log.warn "Logitech Harmony - Access token has expired"
|
||||
}
|
||||
runIn(10, "poll", [overwrite: true])
|
||||
return msg
|
||||
} else {
|
||||
def ResponseValues
|
||||
try {
|
||||
// json response already parsed into JSONElement object
|
||||
ResponseValues = response.json
|
||||
} catch (e) {
|
||||
log.error "Logitech Harmony - error parsing json from response: $e"
|
||||
}
|
||||
if (ResponseValues) {
|
||||
if (ResponseValues.code == 200) {
|
||||
log.trace "Command sent succesfully"
|
||||
poll()
|
||||
} else {
|
||||
log.trace "Command failed. Error: $response.data.code"
|
||||
}
|
||||
} else {
|
||||
log.debug "Logitech Harmony - did not get json results from response body: $response.data"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def poll() {
|
||||
// GET THE LIST OF ACTIVITIES
|
||||
if (state.HarmonyAccessToken) {
|
||||
getActivityList()
|
||||
def Params = [auth: state.HarmonyAccessToken]
|
||||
def url = "https://home.myharmony.com/cloudapi/state?${toQueryString(Params)}"
|
||||
try {
|
||||
httpGet(uri: url, headers: ["Accept": "application/json"]) {response ->
|
||||
def map = [:]
|
||||
response.data.hubs.each {
|
||||
if (it.value.message == "OK") {
|
||||
map["${it.key}"] = "${it.value.response.data.currentAvActivity},${it.value.response.data.activityStatus}"
|
||||
def hub = getChildDevice("harmony-${it.key}")
|
||||
if (hub) {
|
||||
if (it.value.response.data.currentAvActivity == "-1") {
|
||||
hub.sendEvent(name: "currentActivity", value: "--", descriptionText: "There isn't any activity running", display: false)
|
||||
} else {
|
||||
def currentActivity = getActivityName(it.value.response.data.currentAvActivity,it.key)
|
||||
hub.sendEvent(name: "currentActivity", value: currentActivity, descriptionText: "Current activity is ${currentActivity}", display: false)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.trace it.value.message
|
||||
}
|
||||
}
|
||||
def activities = getChildDevices()
|
||||
def activitynotrunning = true
|
||||
activities.each { activity ->
|
||||
def act = activity.deviceNetworkId.split('-')
|
||||
if (act.size() > 2) {
|
||||
def aux = map.find { it.key == act[1] }
|
||||
if (aux) {
|
||||
def aux2 = aux.value.split(',')
|
||||
def childDevice = getChildDevice(activity.deviceNetworkId)
|
||||
if ((act[2] == aux2[0]) && (aux2[1] == "1" || aux2[1] == "2")) {
|
||||
childDevice?.sendEvent(name: "switch", value: "on")
|
||||
if (aux2[1] == "1")
|
||||
runIn(5, "poll", [overwrite: true])
|
||||
} else {
|
||||
childDevice?.sendEvent(name: "switch", value: "off")
|
||||
if (aux2[1] == "3")
|
||||
runIn(5, "poll", [overwrite: true])
|
||||
}
|
||||
}
|
||||
def tokenParam = [auth: state.HarmonyAccessToken]
|
||||
def params = [
|
||||
uri: "https://home.myharmony.com/cloudapi/state?${toQueryString(tokenParam)}",
|
||||
headers: ["Accept": "application/json"],
|
||||
contentType: 'application/json'
|
||||
]
|
||||
asynchttp_v1.get('pollResponse', params)
|
||||
} else {
|
||||
log.warn "Logitech Harmony - Access token has expired"
|
||||
}
|
||||
}
|
||||
|
||||
def pollResponse(response, data) {
|
||||
if (response.hasError()) {
|
||||
log.error "Logitech Harmony - response has error: $response.errorMessage"
|
||||
if (response.status == 401) { // token is expired
|
||||
state.remove("HarmonyAccessToken")
|
||||
log.warn "Logitech Harmony - Access token has expired"
|
||||
}
|
||||
} else {
|
||||
def ResponseValues
|
||||
try {
|
||||
// json response already parsed into JSONElement object
|
||||
ResponseValues = response.json
|
||||
} catch (e) {
|
||||
log.error "Logitech Harmony - error parsing json from response: $e"
|
||||
}
|
||||
if (ResponseValues) {
|
||||
def map = [:]
|
||||
ResponseValues.hubs.each {
|
||||
if (it.value.message == "OK") {
|
||||
map["${it.key}"] = "${it.value.response.data.currentAvActivity},${it.value.response.data.activityStatus}"
|
||||
def hub = getChildDevice("harmony-${it.key}")
|
||||
if (hub) {
|
||||
if (it.value.response.data.currentAvActivity == "-1") {
|
||||
hub.sendEvent(name: "currentActivity", value: "--", descriptionText: "There isn't any activity running", display: false)
|
||||
} else {
|
||||
def currentActivity = getChildDevice("harmony-${it.key}-${it.value.response.data.currentAvActivity}").device.displayName
|
||||
hub.sendEvent(name: "currentActivity", value: currentActivity, descriptionText: "Current activity is ${currentActivity}", display: false)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.trace "Logitech Harmony - error response: $it.value.message"
|
||||
}
|
||||
}
|
||||
def activities = getChildDevices()
|
||||
def activitynotrunning = true
|
||||
activities.each { activity ->
|
||||
def act = activity.deviceNetworkId.split('-')
|
||||
if (act.size() > 2) {
|
||||
def aux = map.find { it.key == act[1] }
|
||||
if (aux) {
|
||||
def aux2 = aux.value.split(',')
|
||||
def childDevice = getChildDevice(activity.deviceNetworkId)
|
||||
if ((act[2] == aux2[0]) && (aux2[1] == "1" || aux2[1] == "2")) {
|
||||
childDevice?.sendEvent(name: "switch", value: "on")
|
||||
if (aux2[1] == "1")
|
||||
runIn(5, "poll", [overwrite: true])
|
||||
} else {
|
||||
childDevice?.sendEvent(name: "switch", value: "off")
|
||||
if (aux2[1] == "3")
|
||||
runIn(5, "poll", [overwrite: true])
|
||||
}
|
||||
}
|
||||
return "Poll completed $map - $state.hubs"
|
||||
}
|
||||
} catch (groovyx.net.http.HttpResponseException e) {
|
||||
if (e.statusCode == 401) { // token is expired
|
||||
state.remove("HarmonyAccessToken")
|
||||
log.warn "Harmony Access token has expired"
|
||||
}
|
||||
} catch (java.net.SocketTimeoutException e) {
|
||||
log.warn "Connection to the hub timed out. Please restart the hub and try again."
|
||||
state.resethub = true
|
||||
} catch (e) {
|
||||
log.info "Logitech Harmony - Error: $e"
|
||||
}
|
||||
} else {
|
||||
log.debug "Logitech Harmony - did not get json results from response body: $response.data"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def getActivityList() {
|
||||
if (state.HarmonyAccessToken) {
|
||||
def tokenParam = [auth: state.HarmonyAccessToken]
|
||||
def params = [
|
||||
uri: "https://home.myharmony.com/cloudapi/activity/all?${toQueryString(tokenParam)}",
|
||||
headers: ["Accept": "application/json"],
|
||||
contentType: 'application/json'
|
||||
]
|
||||
asynchttp_v1.get('activityListResponse', params)
|
||||
} else {
|
||||
log.warn "Logitech Harmony - Access token has expired"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def getActivityList() {
|
||||
// GET ACTIVITY'S NAME
|
||||
if (state.HarmonyAccessToken) {
|
||||
def Params = [auth: state.HarmonyAccessToken]
|
||||
def url = "https://home.myharmony.com/cloudapi/activity/all?${toQueryString(Params)}"
|
||||
try {
|
||||
httpGet(uri: url, headers: ["Accept": "application/json"]) {response ->
|
||||
response.data.hubs.each {
|
||||
def hub = getChildDevice("harmony-${it.key}")
|
||||
if (hub) {
|
||||
def hubname = getHubName("${it.key}")
|
||||
def activities = []
|
||||
def aux = it.value.response.data.activities.size()
|
||||
if (aux >= 1) {
|
||||
activities = it.value.response.data.activities.collect {
|
||||
[id: it.key, name: it.value['name'], type: it.value['type']]
|
||||
}
|
||||
activities += [id: "off", name: "Activity OFF", type: "0"]
|
||||
log.trace activities
|
||||
}
|
||||
hub.sendEvent(name: "activities", value: new groovy.json.JsonBuilder(activities).toString(), descriptionText: "Activities are ${activities.collect { it.name }?.join(', ')}", display: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (groovyx.net.http.HttpResponseException e) {
|
||||
log.trace e
|
||||
} catch (java.net.SocketTimeoutException e) {
|
||||
log.trace e
|
||||
} catch(Exception e) {
|
||||
log.trace e
|
||||
}
|
||||
}
|
||||
return activity
|
||||
def activityListResponse(response, data) {
|
||||
if (response.hasError()) {
|
||||
log.error "Logitech Harmony - response has error: $response.errorMessage"
|
||||
if (response.status == 401) { // token is expired
|
||||
state.remove("HarmonyAccessToken")
|
||||
log.warn "Logitech Harmony - Access token has expired"
|
||||
}
|
||||
} else {
|
||||
def ResponseValues
|
||||
try {
|
||||
// json response already parsed into JSONElement object
|
||||
ResponseValues = response.json
|
||||
} catch (e) {
|
||||
log.error "Logitech Harmony - error parsing json from response: $e"
|
||||
}
|
||||
if (ResponseValues) {
|
||||
ResponseValues.hubs.each {
|
||||
def hub = getChildDevice("harmony-${it.key}")
|
||||
if (hub) {
|
||||
def hubname = getHubName("${it.key}")
|
||||
def activities = []
|
||||
def aux = it.value.response?.data.activities.size()
|
||||
if (aux >= 1) {
|
||||
activities = it.value.response.data.activities.collect {
|
||||
[id: it.key, name: it.value['name'], type: it.value['type']]
|
||||
}
|
||||
activities += [id: "off", name: "Activity OFF", type: "0"]
|
||||
log.trace activities
|
||||
}
|
||||
hub.sendEvent(name: "activities", value: new groovy.json.JsonBuilder(activities).toString(), descriptionText: "Activities are ${activities.collect { it.name }?.join(', ')}", display: false)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.debug "Logitech Harmony - did not get json results from response body: $response.data"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def getActivityName(activity,hubId) {
|
||||
@@ -746,7 +787,7 @@ def addSubscription() {
|
||||
def attribute = data.attributeName
|
||||
def callbackUrl = data.callbackUrl
|
||||
|
||||
log.debug "addSubscription, params: ${params}, request: ${data}"
|
||||
log.debug "Logitech Harmony - addSubscription, params: ${params}, request: ${data}"
|
||||
if (!attribute) {
|
||||
render status: 400, data: '{"msg": "attributeName is required"}'
|
||||
} else {
|
||||
@@ -808,6 +849,7 @@ def deviceHandler(evt) {
|
||||
def deviceInfo = state[evt.deviceId]
|
||||
if (state.harmonyHubs) {
|
||||
state.harmonyHubs.each { harmonyHub ->
|
||||
log.trace "Logitech Harmony - Sending data to $harmonyHub.name"
|
||||
sendToHarmony(evt, harmonyHub.callbackUrl)
|
||||
}
|
||||
} else if (deviceInfo) {
|
||||
|
||||
@@ -26,17 +26,22 @@ definition(
|
||||
)
|
||||
|
||||
preferences {
|
||||
section ("In addition to push notifications, send text alerts to...") {
|
||||
input("recipients", "contact", title: "Send notifications to") {
|
||||
input "phone1", "phone", title: "Phone Number 1", required: false
|
||||
input "phone2", "phone", title: "Phone Number 2", required: false
|
||||
input "phone3", "phone", title: "Phone Number 3", required: false
|
||||
}
|
||||
|
||||
if (!(location.zipCode || ( location.latitude && location.longitude )) && location.channelName == 'samsungtv') {
|
||||
section { paragraph title: "Note:", "Location is required for this SmartApp. Go to 'Location Name' settings to setup your correct location." }
|
||||
}
|
||||
|
||||
section ("Zip code (optional, defaults to location coordinates)...") {
|
||||
input "zipcode", "text", title: "Zip Code", required: false
|
||||
}
|
||||
if (location.channelName != 'samsungtv') {
|
||||
section( "Set your location" ) { input "zipCode", "text", title: "Zip code" }
|
||||
}
|
||||
|
||||
section ("In addition to push notifications, send text alerts to...") {
|
||||
input("recipients", "contact", title: "Send notifications to") {
|
||||
input "phone1", "phone", title: "Phone Number 1", required: false
|
||||
input "phone2", "phone", title: "Phone Number 2", required: false
|
||||
input "phone3", "phone", title: "Phone Number 3", required: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def installed() {
|
||||
@@ -61,7 +66,7 @@ def checkForSevereWeather() {
|
||||
def alerts
|
||||
if(locationIsDefined()) {
|
||||
if(zipcodeIsValid()) {
|
||||
alerts = getWeatherFeature("alerts", zipcode)?.alerts
|
||||
alerts = getWeatherFeature("alerts", zipCode)?.alerts
|
||||
} else {
|
||||
log.warn "Severe Weather Alert: Invalid zipcode entered, defaulting to location's zipcode"
|
||||
alerts = getWeatherFeature("alerts")?.alerts
|
||||
|
||||
Reference in New Issue
Block a user