Compare commits

..

17 Commits

Author SHA1 Message Date
Dr. Bunsen Honeydew
22bbe08d0c DEVC-489: Adding a fingerprint line to the DTH 2016-09-27 16:30:34 -05:00
Juan Pablo Risso
218cc43520 DVCSMP-2089 - Harmony - Make HTTP Requests Async (#1299) 2016-09-27 15:43:37 -04:00
Zach Varberg
9ddc020f04 Merge pull request #1248 from varzac/add-send-to-raw-messages
Add explicit send commands for zigbee raw
2016-09-27 09:07:55 -05:00
Steve Vlaminck
9fbbaec8f6 Merge pull request #1296 from vlaminck/GWU-completion-percentage-fix
Fix: Cleanup event feed when dimming cycle ends
2016-09-26 14:21:31 -05:00
vlaminck
e4c1824afd Fix: reorder events so the feed makes more sense 2016-09-26 14:18:31 -05:00
vlaminck
797a58cb68 Fix: hide reset events 2016-09-26 14:14:55 -05:00
vlaminck
c428267d63 Fix: stop sending 100 percent from completion 2016-09-26 14:05:03 -05:00
Jason Botello
81cf1179ef Merge pull request #985 from SmartThingsCommunity/MSA-1351-2
MSA-1351: Gideon AI implementation
2016-09-23 13:38:52 -07:00
Juan Pablo Risso
79d20b0edb SSVD-2740 - Remove zipcode input (#1267)
Limit to samsungtv channel
2016-09-22 18:59:07 -04:00
twack
b6d862fdd4 Merge pull request #1283 from twack/update_generic_zigbee_for_yale
(PROD-736) Update generic zigbee lock for Yale Key-Free Deadbolt. Requested changes incorporated.
2016-09-22 14:40:30 -07:00
twack
d58084c438 Update zigbee-lock.groovy 2016-09-22 12:06:29 -07:00
Juan Pablo Risso
dbfaef3e69 DVCSMP-2076 - Async Update (#1279) 2016-09-22 14:55:25 -04:00
twack
40ed88e7fd Changed name to be consistent
Changed name to be consistent with "Yale Touch Screen Deadbolt Lock"
2016-09-22 11:34:33 -07:00
twack
1d6e22dc16 removed tabs 2016-09-22 11:23:29 -07:00
twack
1d180ac487 update generic zigbee lock for Yale Key-Free Deadbolt (PROD-736) 2016-09-22 10:19:23 -07:00
Zach Varberg
230541a145 Add explicit send commands for zigbee raw
Previously a few places depended on the dev-conn behavior of
automatically appending a send command after a raw zigbee command.  We
intend to deprecate that behavior and so we are adding explicit sends
where they were missing.
2016-09-22 11:08:30 -05:00
Nicola Russo
4ad0a6fd9d MSA-1351: The Gideon AI smart app allows you to connect and control all of your smartThings devices with the Gideon AI app. Gideon AI smart app makes your smartThings device even smarter adding features like energy monitoring, vdeo surveillance history etc.. 2016-06-12 08:35:14 -05:00
14 changed files with 581 additions and 2433 deletions

View File

@@ -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",
]
}

View File

@@ -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) {

View File

@@ -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'
]
}

View File

@@ -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){

View File

@@ -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()
}
}

View File

@@ -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)
}

View File

@@ -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
}
}

View 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')]
}
}

View File

@@ -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) {

View File

@@ -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}"

View File

@@ -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() {

View File

@@ -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) {

View File

@@ -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