Compare commits

...

25 Commits

Author SHA1 Message Date
Collin Dinsmoor
477fec7735 MSA-1467: I copied and pasted from the community. If the same is already available I will be happy to use that way. I am new to this process so I hope the developer understands. 2016-09-06 20:09:08 -05:00
Vinay Rao
bac37f9ca2 Merge pull request #1202 from SmartThingsCommunity/staging
Rolling down staging hotfix to master
2016-09-06 17:08:39 -07:00
Vinay Rao
f0ecb65c09 Merge pull request #1201 from SmartThingsCommunity/revert-1197-revert_WWST-40_staging
Revert "Revert "WWST-40 Philips Hue: Implement device watch""
2016-09-06 17:07:33 -07:00
Vinay Rao
1c0ddd2571 Revert "Revert "WWST-40 Philips Hue: Implement device watch"" 2016-09-06 17:07:14 -07:00
Vinay Rao
b7e0cbda09 Merge pull request #1200 from SmartThingsCommunity/revert-1194-revert-latest-ecobee-changes
Revert "revert changes for DVCSMP-1980 and SSVD-2534 on staging"
2016-09-06 17:07:06 -07:00
Vinay Rao
f80e094bd9 Revert "revert changes for DVCSMP-1980 and SSVD-2534 on staging" 2016-09-06 17:06:38 -07:00
Juan Pablo Risso
f3f5cc42c9 CREX-5826 - Missing braces (#1198) 2016-09-06 17:38:45 -04:00
Vinay Rao
313fe8b734 Merge pull request #1197 from larsfinander/revert_WWST-40_staging
Revert "WWST-40 Philips Hue: Implement device watch"
2016-09-06 12:33:51 -07:00
Vinay Rao
0d693386d1 Merge pull request #1194 from jimmyjames/revert-latest-ecobee-changes
revert changes for DVCSMP-1980 and SSVD-2534 on staging
2016-09-06 12:01:30 -07:00
Lars Finander
d1aee1e874 Revert "WWST-40 Philips Hue: Implement device watch"
This reverts commit bacd335991.
2016-09-06 12:42:11 -06:00
Lars Finander
3528e7da51 Merge pull request #1195 from larsfinander/DVCSMP-2004_Philips_Hue_Nullpointer_in_bridge_staging
DVCSMP-2004 Philips Hue: Nullpointer in bridge
2016-09-06 12:40:02 -06:00
Lars Finander
5c2e06c98d DVCSMP-2004 Philips Hue: Nullpointer in bridge 2016-09-06 10:43:03 -06:00
Jim Anderson
26df619b4f revert changes for DVCSMP-1980 and SSVD-2534 on staging 2016-09-06 11:22:06 -05:00
Juan Pablo Risso
af2ea04442 CREX-5826 - 2 SMS bug fix (#1193)
options.method = 'none' & variable name update

Revert variable name
2016-09-06 12:04:26 -04:00
Juan Pablo Risso
383f72580a SSVD-2738 - SSVD-2734 - SSVD-2733 (#1187)
* SSVD-2738 - Count Down Bug

* SSVD-2734 - Copy text

* SSVD-2733 - Add Unit
2016-09-06 11:39:18 -04:00
Vinay Rao
090a306939 Merge pull request #1184 from SmartThingsCommunity/staging
Rolling down staging hotfix to master
2016-08-31 16:05:08 -07:00
Vinay Rao
d0a16c10b2 Merge pull request #1183 from SmartThingsCommunity/production
Rolling down production hotfix to staging
2016-08-31 16:04:45 -07:00
Lars Finander
faa65f204d Merge pull request #1181 from larsfinander/WWST-40_Philips_Hue_device_watch_staging
WWST-40 Philips Hue: Implement device watch
2016-08-31 16:39:26 -06:00
Lars Finander
bacd335991 WWST-40 Philips Hue: Implement device watch 2016-08-31 15:00:34 -06:00
Vinay Rao
740e5e096c Merge pull request #1179 from varzac/smartsense-motion-temp-alarm2
DVCSMP-1999 Use proper IAS Zone alarm for triggering motion
2016-08-31 13:00:49 -07:00
Vinay Rao
aac2f9b177 Merge pull request #1174 from workingmonk/bug/osram_hue_name
SSVD-2632 The Device Details Recently tab does not log any
2016-08-31 13:00:24 -07:00
Vinay Rao
048eb77e64 fixes SSVD-2632 The Device Details Recently tab does not log any activity regarding OSRAM Lightify RGWB bulb color change 2016-08-31 12:47:09 -07:00
Zach Varberg
dadec937fa Use proper IAS Zone alarm for triggering motion
Previously we were incorrectly looking at the alarm1 bit when we should
be looking at the alarm2 bit.

This resolves: https://smartthings.atlassian.net/browse/DVCSMP-1999
2016-08-31 13:09:18 -05:00
Vinay Rao
78aa6691c4 Merge pull request #1173 from SmartThingsCommunity/master
Rolling up changes for next week deploy
2016-08-30 13:32:36 -07:00
Vinay Rao
a22f71bc29 Merge pull request #1171 from SmartThingsCommunity/staging
Rolling up staging to prod for deploy
2016-08-30 12:57:12 -07:00
14 changed files with 804 additions and 70 deletions

View File

@@ -0,0 +1,320 @@
/**
* Copyright 2015 Chris Kitch
*
* 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.
*
* Wemo Maker Service Manager
*
* Author: Chris Kitch
* Date: 2016-04-06
*/
definition(
name: "Wemo Maker (Connect)",
namespace: "kriskit.wemo",
author: "Chris Kitch",
description: "Allows you to integrate your WeMo Maker with SmartThings.",
category: "My Apps",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/wemo.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/wemo@2x.png",
singleInstance: true
)
preferences {
page(name:"firstPage", title:"Wemo Device Setup", content:"firstPage")
}
def firstPage()
{
int refreshCount = !state.refreshCount ? 0 : state.refreshCount as int
state.refreshCount = refreshCount + 1
def refreshInterval = 5
log.debug "REFRESH COUNT :: ${refreshCount}"
ssdpSubscribe()
//ssdp request every 25 seconds
if((refreshCount % 5) == 0) {
discoverWemoMakers()
}
//setup.xml request every 5 seconds except on discoveries
if(((refreshCount % 1) == 0) && ((refreshCount % 5) != 0)) {
verifyDevices()
}
def makersDiscovered = makersDiscovered()
return dynamicPage(name:"firstPage", title:"Discovery Started!", nextPage:"", refreshInterval: refreshInterval, install:true, uninstall: true) {
section("Select a device...") {
input "selectedMakers", "enum", required:false, title:"Select Wemo Makers \n(${makersDiscovered.size() ?: 0} found)", multiple:true, options:makersDiscovered
}
}
}
def installed() {
log.debug "Installed with settings: ${settings}"
initialize()
}
def updated() {
log.debug "Updated with settings: ${settings}"
initialize()
}
def initialize() {
unsubscribe()
unschedule()
ssdpSubscribe()
if (selectedMakers)
addMakers()
runIn(5, "subscribeToDevices") //initial subscriptions delayed by 5 seconds
runIn(10, "refreshDevices") //refresh devices, delayed by 10 seconds
runEvery5Minutes("refresh")
}
private discoverWemoMakers()
{
log.debug "Sending discover request..."
sendHubCommand(new physicalgraph.device.HubAction("lan discovery urn:Belkin:device:Maker:1", physicalgraph.device.Protocol.LAN))
}
private getFriendlyName(ip, deviceNetworkId) {
log.debug "Getting friendly name from http://${ip}/setup.xml"
sendHubCommand(new physicalgraph.device.HubAction("""GET /setup.xml HTTP/1.1\r\nHOST: $ip\r\n\r\n""", physicalgraph.device.Protocol.LAN, "${deviceNetworkId}", [callback: "setupHandler"]))
}
private verifyDevices() {
def makers = getWemoMakers().findAll { it?.value?.verified != true }
def devices = makers
devices.each {
def host = convertHexToIP(it.value.ip) + ":" + convertHexToInt(it.value.port)
def networkId = (it.value.ip + ":" + it.value.port)
getFriendlyName(host, networkId)
}
}
void ssdpSubscribe() {
if (state.ssdpSubscribed)
return
log.debug "Subscribing to SSDP..."
subscribe(location, "ssdpTerm.urn:Belkin:device:Maker:1", ssdpMakerHandler)
state.ssdpSubscribed = true
}
def devicesDiscovered() {
def makers = getWemoMakers()
def devices = makers
devices?.collect{ [app.id, it.ssdpUSN].join('.') }
}
def makersDiscovered() {
def makers = getWemoMakers().findAll { it?.value?.verified == true }
def map = [:]
makers.each {
def value = it.value.name ?: "WeMo Maker ${it.value.ssdpUSN.split(':')[1][-3..-1]}"
def key = it.value.mac
map["${key}"] = value
}
map
}
def getWemoMakers()
{
if (!state.makers) { state.makers = [:] }
state.makers
}
def resubscribe() {
log.debug "Resubscribe called, delegating to refresh()"
refresh()
}
def refresh() {
log.debug "refresh() called"
doDeviceSync()
refreshDevices()
}
def refreshDevices() {
log.debug "refreshDevices() called"
def devices = getAllChildDevices()
devices.each { d ->
log.debug "Calling refresh() on device: ${d.id}"
d.refresh()
}
}
def subscribeToDevices() {
log.debug "subscribeToDevices() called"
def devices = getAllChildDevices()
devices.each { d ->
d.subscribe()
}
}
def addMakers() {
def makers = getWemoMakers()
selectedMakers.each { dni ->
def selectedMaker = makers.find { it.value.mac == dni } ?: makers.find { "${it.value.ip}:${it.value.port}" == dni }
def d
if (selectedMaker) {
d = getChildDevices()?.find {
it.deviceNetworkId == selectedMaker.value.mac || it.device.getDataValue("mac") == selectedMaker.value.mac
}
}
if (!d) {
log.debug "Creating WeMo Maker with dni: ${selectedMaker.value.mac}"
d = addChildDevice("kriskit.wemo", "Wemo Maker", selectedMaker.value.mac, selectedMaker?.value.hub, [
"label": selectedMaker?.value?.name ?: "Wemo Maker",
"data": [
"mac": selectedMaker.value.mac,
"ip": selectedMaker.value.ip,
"port": selectedMaker.value.port
]
])
def ipvalue = convertHexToIP(selectedMaker.value.ip)
d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}")
log.debug "Created ${d.displayName} with id: ${d.id}, dni: ${d.deviceNetworkId}"
} else {
log.debug "found ${d.displayName} with id $dni already exists"
}
}
}
def ssdpMakerHandler(evt) {
def description = evt.description
def hub = evt?.hubId
def parsedEvent = parseDiscoveryMessage(description)
parsedEvent << ["hub":hub]
def makers = getWemoMakers()
if (!(makers."${parsedEvent.ssdpUSN.toString()}")) {
//if it doesn't already exist
makers << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
} else {
log.debug "Device was already found in state..."
def d = makers."${parsedEvent.ssdpUSN.toString()}"
boolean deviceChangedValues = false
log.debug "$d.ip <==> $parsedEvent.ip"
if(d.ip != parsedEvent.ip || d.port != parsedEvent.port) {
d.ip = parsedEvent.ip
d.port = parsedEvent.port
deviceChangedValues = true
log.debug "Device's port or ip changed..."
def child = getChildDevice(parsedEvent.mac)
if (child) {
child.subscribe(parsedEvent.ip, parsedEvent.port)
child.poll()
} else {
log.debug "Device with mac $parsedEvent.mac not found"
}
}
}
}
void setupHandler(hubResponse) {
String contentType = hubResponse?.headers['Content-Type']
log.debug "Response received from ${convertHexToIP(hubResponse.ip)}:${convertHexToInt(hubResponse.port)}"
if (contentType != null && contentType == 'text/xml') {
def body = hubResponse.xml
def wemoDevices = []
String deviceType = body?.device?.deviceType?.text() ?: ""
if (deviceType.startsWith("urn:Belkin:device:Maker:1")) {
wemoDevices = getWemoMakers()
}
def wemoDevice = wemoDevices.find {it?.key?.contains(body?.device?.UDN?.text())}
if (wemoDevice) {
def friendlyName = body?.device?.friendlyName?.text()
log.debug "Verified '${friendlyName}'"
wemoDevice.value << [name: friendlyName, verified: true]
} else {
log.error "/setup.xml returned a wemo device that didn't exist"
}
}
}
private def parseDiscoveryMessage(String description) {
def event = [:]
def parts = description.split(',')
parts.each { part ->
part = part.trim()
if (part.startsWith('devicetype:')) {
part -= "devicetype:"
event.devicetype = part.trim()
}
else if (part.startsWith('mac:')) {
part -= "mac:"
event.mac = part.trim()
}
else if (part.startsWith('networkAddress:')) {
part -= "networkAddress:"
event.ip = part.trim()
}
else if (part.startsWith('deviceAddress:')) {
part -= "deviceAddress:"
event.port = part.trim()
}
else if (part.startsWith('ssdpPath:')) {
part -= "ssdpPath:"
event.ssdpPath = part.trim()
}
else if (part.startsWith('ssdpUSN:')) {
part -= "ssdpUSN:"
event.ssdpUSN = part.trim()
}
else if (part.startsWith('ssdpTerm:')) {
part -= "ssdpTerm:"
event.ssdpTerm = part.trim()
}
else if (part.startsWith('headers')) {
part -= "headers:"
event.headers = part.trim()
}
else if (part.startsWith('body')) {
part -= "body:"
event.body = part.trim()
}
}
event
}
def doDeviceSync(){
log.debug "Doing Device Sync!"
discoverWemoMakers()
}
private String convertHexToIP(hex) {
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
}
private Integer convertHexToInt(hex) {
Integer.parseInt(hex,16)
}
private Boolean canInstallLabs() {
return hasAllHubsOver("000.011.00603")
}
private Boolean hasAllHubsOver(String desiredFirmware) {
return realHubFirmwareVersions.every { fw -> fw >= desiredFirmware }
}
private List getRealHubFirmwareVersions() {
return location.hubs*.firmwareVersionString.findAll { it }
}

View File

@@ -16,6 +16,7 @@ metadata {
capability "Switch"
capability "Refresh"
capability "Sensor"
capability "Health Check"
command "setAdjustedColor"
command "reset"
@@ -55,6 +56,10 @@ metadata {
}
}
void installed() {
sendEvent(name: "checkInterval", value: 60 * 30, data: [protocol: "lan"], displayed: false)
}
// parse events into attributes
def parse(description) {
log.debug "parse() - $description"
@@ -166,3 +171,7 @@ def verifyPercent(percent) {
return false
}
}
def ping() {
log.debug "${parent.ping(this)}"
}

View File

@@ -62,7 +62,7 @@ def parse(description) {
log.trace "HUE BRIDGE, GENERATING EVENT: $map.name: $map.value"
results << createEvent(name: "${map.name}", value: "${map.value}")
} else {
log.trace "Parsing description"
log.trace "Parsing description"
def msg = parseLanMessage(description)
if (msg.body) {
def contentType = msg.headers["Content-Type"]
@@ -72,13 +72,13 @@ def parse(description) {
log.info "Bridge response: $msg.body"
} else {
// Sending Bulbs List to parent"
if (parent.state.inBulbDiscovery)
log.info parent.bulbListHandler(device.hub.id, msg.body)
if (parent.isInBulbDiscovery())
log.info parent.bulbListHandler(device.hub.id, msg.body)
}
}
else if (contentType?.contains("xml")) {
log.debug "HUE BRIDGE ALREADY PRESENT"
parent.hubVerification(device.hub.id, msg.body)
parent.hubVerification(device.hub.id, msg.body)
}
}
}

View File

@@ -17,6 +17,7 @@ metadata {
capability "Switch"
capability "Refresh"
capability "Sensor"
capability "Health Check"
command "setAdjustedColor"
command "reset"
@@ -64,6 +65,10 @@ metadata {
}
}
void installed() {
sendEvent(name: "checkInterval", value: 60 * 30, data: [protocol: "lan"], displayed: false)
}
// parse events into attributes
def parse(description) {
log.debug "parse() - $description"
@@ -182,3 +187,7 @@ def verifyPercent(percent) {
return false
}
}
def ping() {
log.trace "${parent.ping(this)}"
}

View File

@@ -14,6 +14,7 @@ metadata {
capability "Switch"
capability "Refresh"
capability "Sensor"
capability "Health Check"
command "refresh"
}
@@ -48,6 +49,10 @@ metadata {
}
}
void installed() {
sendEvent(name: "checkInterval", value: 60 * 30, data: [protocol: "lan"], displayed: false)
}
// parse events into attributes
def parse(description) {
log.debug "parse() - $description"
@@ -87,3 +92,7 @@ void refresh() {
log.debug "Executing 'refresh'"
parent.manualRefresh()
}
def ping() {
log.debug "${parent.ping(this)}"
}

View File

@@ -15,6 +15,7 @@ metadata {
capability "Color Temperature"
capability "Switch"
capability "Refresh"
capability "Health Check"
command "refresh"
}
@@ -53,6 +54,10 @@ metadata {
}
}
void installed() {
sendEvent(name: "checkInterval", value: 60 * 30, data: [protocol: "lan"], displayed: false)
}
// parse events into attributes
def parse(description) {
log.debug "parse() - $description"
@@ -101,3 +106,7 @@ void refresh() {
log.debug "Executing 'refresh'"
parent.manualRefresh()
}
def ping() {
log.debug "${parent.ping(this)}"
}

View File

@@ -170,7 +170,7 @@ private Map parseCustomMessage(String description) {
private Map parseIasMessage(String description) {
ZoneStatus zs = zigbee.parseZoneStatus(description)
return zs.isAlarm1Set() ? getMotionResult('active') : getMotionResult('inactive')
return (zs.isAlarm1Set() || zs.isAlarm2Set()) ? getMotionResult('active') : getMotionResult('inactive')
}
def getTemperature(value) {

View File

@@ -106,11 +106,11 @@ def parse(String description) {
if (zigbeeMap?.clusterInt == COLOR_CONTROL_CLUSTER) {
if(zigbeeMap.attrInt == ATTRIBUTE_HUE){ //Hue Attribute
def hueValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 360)
sendEvent(name: "hue", value: hueValue, displayed:false)
sendEvent(name: "hue", value: hueValue, descriptionText: "Color has changed")
}
else if(zigbeeMap.attrInt == ATTRIBUTE_SATURATION){ //Saturation Attribute
def saturationValue = Math.round(zigbee.convertHexToInt(zigbeeMap.value) / 255 * 100)
sendEvent(name: "saturation", value: saturationValue, displayed:false)
sendEvent(name: "saturation", value: saturationValue, descriptionText: "Color has changed", displayed: false)
}
}
else {

View File

@@ -0,0 +1,320 @@
/**
* Copyright 2015 Chris Kitch
*
* 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.
*
* Wemo Maker Service Manager
*
* Author: Chris Kitch
* Date: 2016-04-06
*/
definition(
name: "Wemo Maker (Connect)",
namespace: "kriskit.wemo",
author: "Chris Kitch",
description: "Allows you to integrate your WeMo Maker with SmartThings.",
category: "My Apps",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Partner/wemo.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Partner/wemo@2x.png",
singleInstance: true
)
preferences {
page(name:"firstPage", title:"Wemo Device Setup", content:"firstPage")
}
def firstPage()
{
int refreshCount = !state.refreshCount ? 0 : state.refreshCount as int
state.refreshCount = refreshCount + 1
def refreshInterval = 5
log.debug "REFRESH COUNT :: ${refreshCount}"
ssdpSubscribe()
//ssdp request every 25 seconds
if((refreshCount % 5) == 0) {
discoverWemoMakers()
}
//setup.xml request every 5 seconds except on discoveries
if(((refreshCount % 1) == 0) && ((refreshCount % 5) != 0)) {
verifyDevices()
}
def makersDiscovered = makersDiscovered()
return dynamicPage(name:"firstPage", title:"Discovery Started!", nextPage:"", refreshInterval: refreshInterval, install:true, uninstall: true) {
section("Select a device...") {
input "selectedMakers", "enum", required:false, title:"Select Wemo Makers \n(${makersDiscovered.size() ?: 0} found)", multiple:true, options:makersDiscovered
}
}
}
def installed() {
log.debug "Installed with settings: ${settings}"
initialize()
}
def updated() {
log.debug "Updated with settings: ${settings}"
initialize()
}
def initialize() {
unsubscribe()
unschedule()
ssdpSubscribe()
if (selectedMakers)
addMakers()
runIn(5, "subscribeToDevices") //initial subscriptions delayed by 5 seconds
runIn(10, "refreshDevices") //refresh devices, delayed by 10 seconds
runEvery5Minutes("refresh")
}
private discoverWemoMakers()
{
log.debug "Sending discover request..."
sendHubCommand(new physicalgraph.device.HubAction("lan discovery urn:Belkin:device:Maker:1", physicalgraph.device.Protocol.LAN))
}
private getFriendlyName(ip, deviceNetworkId) {
log.debug "Getting friendly name from http://${ip}/setup.xml"
sendHubCommand(new physicalgraph.device.HubAction("""GET /setup.xml HTTP/1.1\r\nHOST: $ip\r\n\r\n""", physicalgraph.device.Protocol.LAN, "${deviceNetworkId}", [callback: "setupHandler"]))
}
private verifyDevices() {
def makers = getWemoMakers().findAll { it?.value?.verified != true }
def devices = makers
devices.each {
def host = convertHexToIP(it.value.ip) + ":" + convertHexToInt(it.value.port)
def networkId = (it.value.ip + ":" + it.value.port)
getFriendlyName(host, networkId)
}
}
void ssdpSubscribe() {
if (state.ssdpSubscribed)
return
log.debug "Subscribing to SSDP..."
subscribe(location, "ssdpTerm.urn:Belkin:device:Maker:1", ssdpMakerHandler)
state.ssdpSubscribed = true
}
def devicesDiscovered() {
def makers = getWemoMakers()
def devices = makers
devices?.collect{ [app.id, it.ssdpUSN].join('.') }
}
def makersDiscovered() {
def makers = getWemoMakers().findAll { it?.value?.verified == true }
def map = [:]
makers.each {
def value = it.value.name ?: "WeMo Maker ${it.value.ssdpUSN.split(':')[1][-3..-1]}"
def key = it.value.mac
map["${key}"] = value
}
map
}
def getWemoMakers()
{
if (!state.makers) { state.makers = [:] }
state.makers
}
def resubscribe() {
log.debug "Resubscribe called, delegating to refresh()"
refresh()
}
def refresh() {
log.debug "refresh() called"
doDeviceSync()
refreshDevices()
}
def refreshDevices() {
log.debug "refreshDevices() called"
def devices = getAllChildDevices()
devices.each { d ->
log.debug "Calling refresh() on device: ${d.id}"
d.refresh()
}
}
def subscribeToDevices() {
log.debug "subscribeToDevices() called"
def devices = getAllChildDevices()
devices.each { d ->
d.subscribe()
}
}
def addMakers() {
def makers = getWemoMakers()
selectedMakers.each { dni ->
def selectedMaker = makers.find { it.value.mac == dni } ?: makers.find { "${it.value.ip}:${it.value.port}" == dni }
def d
if (selectedMaker) {
d = getChildDevices()?.find {
it.deviceNetworkId == selectedMaker.value.mac || it.device.getDataValue("mac") == selectedMaker.value.mac
}
}
if (!d) {
log.debug "Creating WeMo Maker with dni: ${selectedMaker.value.mac}"
d = addChildDevice("kriskit.wemo", "Wemo Maker", selectedMaker.value.mac, selectedMaker?.value.hub, [
"label": selectedMaker?.value?.name ?: "Wemo Maker",
"data": [
"mac": selectedMaker.value.mac,
"ip": selectedMaker.value.ip,
"port": selectedMaker.value.port
]
])
def ipvalue = convertHexToIP(selectedMaker.value.ip)
d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}")
log.debug "Created ${d.displayName} with id: ${d.id}, dni: ${d.deviceNetworkId}"
} else {
log.debug "found ${d.displayName} with id $dni already exists"
}
}
}
def ssdpMakerHandler(evt) {
def description = evt.description
def hub = evt?.hubId
def parsedEvent = parseDiscoveryMessage(description)
parsedEvent << ["hub":hub]
def makers = getWemoMakers()
if (!(makers."${parsedEvent.ssdpUSN.toString()}")) {
//if it doesn't already exist
makers << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
} else {
log.debug "Device was already found in state..."
def d = makers."${parsedEvent.ssdpUSN.toString()}"
boolean deviceChangedValues = false
log.debug "$d.ip <==> $parsedEvent.ip"
if(d.ip != parsedEvent.ip || d.port != parsedEvent.port) {
d.ip = parsedEvent.ip
d.port = parsedEvent.port
deviceChangedValues = true
log.debug "Device's port or ip changed..."
def child = getChildDevice(parsedEvent.mac)
if (child) {
child.subscribe(parsedEvent.ip, parsedEvent.port)
child.poll()
} else {
log.debug "Device with mac $parsedEvent.mac not found"
}
}
}
}
void setupHandler(hubResponse) {
String contentType = hubResponse?.headers['Content-Type']
log.debug "Response received from ${convertHexToIP(hubResponse.ip)}:${convertHexToInt(hubResponse.port)}"
if (contentType != null && contentType == 'text/xml') {
def body = hubResponse.xml
def wemoDevices = []
String deviceType = body?.device?.deviceType?.text() ?: ""
if (deviceType.startsWith("urn:Belkin:device:Maker:1")) {
wemoDevices = getWemoMakers()
}
def wemoDevice = wemoDevices.find {it?.key?.contains(body?.device?.UDN?.text())}
if (wemoDevice) {
def friendlyName = body?.device?.friendlyName?.text()
log.debug "Verified '${friendlyName}'"
wemoDevice.value << [name: friendlyName, verified: true]
} else {
log.error "/setup.xml returned a wemo device that didn't exist"
}
}
}
private def parseDiscoveryMessage(String description) {
def event = [:]
def parts = description.split(',')
parts.each { part ->
part = part.trim()
if (part.startsWith('devicetype:')) {
part -= "devicetype:"
event.devicetype = part.trim()
}
else if (part.startsWith('mac:')) {
part -= "mac:"
event.mac = part.trim()
}
else if (part.startsWith('networkAddress:')) {
part -= "networkAddress:"
event.ip = part.trim()
}
else if (part.startsWith('deviceAddress:')) {
part -= "deviceAddress:"
event.port = part.trim()
}
else if (part.startsWith('ssdpPath:')) {
part -= "ssdpPath:"
event.ssdpPath = part.trim()
}
else if (part.startsWith('ssdpUSN:')) {
part -= "ssdpUSN:"
event.ssdpUSN = part.trim()
}
else if (part.startsWith('ssdpTerm:')) {
part -= "ssdpTerm:"
event.ssdpTerm = part.trim()
}
else if (part.startsWith('headers')) {
part -= "headers:"
event.headers = part.trim()
}
else if (part.startsWith('body')) {
part -= "body:"
event.body = part.trim()
}
}
event
}
def doDeviceSync(){
log.debug "Doing Device Sync!"
discoverWemoMakers()
}
private String convertHexToIP(hex) {
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
}
private Integer convertHexToInt(hex) {
Integer.parseInt(hex,16)
}
private Boolean canInstallLabs() {
return hasAllHubsOver("000.011.00603")
}
private Boolean hasAllHubsOver(String desiredFirmware) {
return realHubFirmwareVersions.every { fw -> fw >= desiredFirmware }
}
private List getRealHubFirmwareVersions() {
return location.hubs*.firmwareVersionString.findAll { it }
}

View File

@@ -17,7 +17,7 @@ definition(
name: "Monitor on Sense",
namespace: "resteele",
author: "Rachel Steele",
description: "Turn on Monitor when vibration is sensed",
description: "Turn on switch when vibration is sensed",
category: "My Apps",
iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
@@ -25,10 +25,10 @@ definition(
preferences {
section("When the keyboard is used...") {
section("When vibration is sensed...") {
input "accelerationSensor", "capability.accelerationSensor", title: "Which Sensor?"
}
section("Turn on/off a light...") {
section("Turn on switch...") {
input "switch1", "capability.switch"
}
}
@@ -47,5 +47,3 @@ def updated() {
def accelerationActiveHandler(evt) {
switch1.on()
}

View File

@@ -64,10 +64,12 @@ def meterHandler(evt) {
def lastValue = atomicState.lastValue as double
atomicState.lastValue = meterValue
def dUnit ? evt.unit : "Watts"
def aboveThresholdValue = aboveThreshold as int
if (meterValue > aboveThresholdValue) {
if (lastValue < aboveThresholdValue) { // only send notifications when crossing the threshold
def msg = "${meter} reported ${evt.value} ${evt.unit} which is above your threshold of ${aboveThreshold}."
def msg = "${meter} reported ${evt.value} ${dUnit} which is above your threshold of ${aboveThreshold}."
sendMessage(msg)
} else {
// log.debug "not sending notification for ${evt.description} because the threshold (${aboveThreshold}) has already been crossed"
@@ -78,7 +80,7 @@ def meterHandler(evt) {
def belowThresholdValue = belowThreshold as int
if (meterValue < belowThresholdValue) {
if (lastValue > belowThresholdValue) { // only send notifications when crossing the threshold
def msg = "${meter} reported ${evt.value} ${evt.unit} which is below your threshold of ${belowThreshold}."
def msg = "${meter} reported ${evt.value} ${dUnit} which is below your threshold of ${belowThreshold}."
sendMessage(msg)
} else {
// log.debug "not sending notification for ${evt.description} because the threshold (${belowThreshold}) has already been crossed"

View File

@@ -761,7 +761,7 @@ String displayableTime(timeRemaining) {
return "${minutes}:00"
}
def fraction = "0.${parts[1]}" as double
def seconds = "${60 * fraction as int}".padRight(2, "0")
def seconds = "${60 * fraction as int}".padLeft(2, "0")
return "${minutes}:${seconds}"
}
@@ -1101,4 +1101,4 @@ def hasStartLevel() {
def hasEndLevel() {
return (endLevel != null && endLevel != "")
}
}

View File

@@ -95,8 +95,7 @@ def bridgeDiscoveryFailed() {
}
}
def bridgeLinking()
{
def bridgeLinking() {
int linkRefreshcount = !state.linkRefreshcount ? 0 : state.linkRefreshcount as int
state.linkRefreshcount = linkRefreshcount + 1
def refreshInterval = 3
@@ -328,7 +327,7 @@ def bulbListHandler(hub, data = "") {
def object = new groovy.json.JsonSlurper().parseText(data)
object.each { k,v ->
if (v instanceof Map)
bulbs[k] = [id: k, name: v.name, type: v.type, modelid: v.modelid, hub:hub]
bulbs[k] = [id: k, name: v.name, type: v.type, modelid: v.modelid, hub:hub, online: v.state?.reachable]
}
}
def bridge = null
@@ -448,7 +447,6 @@ def addBridge() {
updateBridgeStatus(childDevice)
childDevice.sendEvent(name: "idNumber", value: idNumber)
if (vbridge.value.ip && vbridge.value.port) {
if (vbridge.value.ip.contains(".")) {
childDevice.sendEvent(name: "networkAddress", value: vbridge.value.ip + ":" + vbridge.value.port)
@@ -649,8 +647,7 @@ def locationHandler(evt) {
}
}
}
}
else if (parsedEvent.headers && parsedEvent.body) {
} else if (parsedEvent.headers && parsedEvent.body) {
log.trace "HUE BRIDGE RESPONSES"
def headerString = parsedEvent.headers.toString()
if (headerString?.contains("xml")) {
@@ -733,7 +730,7 @@ private void updateBridgeStatus(childDevice) {
private void checkBridgeStatus() {
def bridges = getHueBridges()
// Check if each bridge has been heard from within the last 16 minutes (3 poll intervals times 5 minutes plus buffer)
def time = now() - (1000 * 60 * 16)
def time = now() - (1000 * 60 * 30)
bridges.each {
def d = getChildDevice(it.value.mac)
if(d) {
@@ -746,6 +743,8 @@ private void checkBridgeStatus() {
if (it.value.lastActivity < time) { // it.value.lastActivity != null &&
log.warn "Bridge $it.key is Offline"
d.sendEvent(name: "status", value: "Offline")
// set all lights to offline since bridge is not reachable
state.bulbs?.each {it.value.online = false}
} else {
d.sendEvent(name: "status", value: "Online")//setOnline(false)
}
@@ -758,6 +757,10 @@ def isValidSource(macAddress) {
return (vbridges?.find {"${it.value.mac}" == macAddress}) != null
}
def isInBulbDiscovery() {
return state.inBulbDiscovery
}
/////////////////////////////////////
//CHILD DEVICE METHODS
/////////////////////////////////////
@@ -781,8 +784,7 @@ def parse(childDevice, description) {
if (body instanceof java.util.Map) {
// get (poll) reponse
return handlePoll(body)
}
else {
} else {
//put response
return handleCommandResponse(body)
}
@@ -879,36 +881,40 @@ private handleCommandResponse(body) {
// scan entire response before sending events to make sure they are always in the same order
def updates = [:]
body.each { payload ->
log.debug $payload
body.each { payload ->
log.debug $payload
if (payload?.success) {
def childDeviceNetworkId = app.id + "/"
def eventType
def childDeviceNetworkId = app.id + "/"
def eventType
payload.success.each { k, v ->
def data = k.split("/")
if (data.length == 5) {
childDeviceNetworkId = app.id + "/" + k.split("/")[2]
if (!updates[childDeviceNetworkId])
updates[childDeviceNetworkId] = [:]
eventType = k.split("/")[4]
eventType = k.split("/")[4]
updates[childDeviceNetworkId]."$eventType" = v
}
}
} else if (payload.error) {
log.warn "Error returned from Hue bridge error = ${body?.error}"
}
}
}
}
// send events for each update found above (order of events should be same as handlePoll())
updates.each { childDeviceNetworkId, params ->
def device = getChildDevice(childDeviceNetworkId)
sendBasicEvents(device, "on", params.on)
sendBasicEvents(device, "bri", params.bri)
sendColorEvents(device, params.xy, params.hue, params.sat, params.ct)
}
def id = getId(device)
// If device is offline, then don't send events which will update device watch
if (isOnline(id)) {
sendBasicEvents(device, "on", params.on)
sendBasicEvents(device, "bri", params.bri)
sendColorEvents(device, params.xy, params.hue, params.sat, params.ct)
}
}
return []
}
}
/**
* Handles a response to a poll (GET) sent to the Hue Bridge.
@@ -928,26 +934,32 @@ private handleCommandResponse(body) {
* @return empty array
*/
private handlePoll(body) {
if (state.updating) {
// If user just executed commands, then ignore poll to not confuse the turning on/off state
return []
}
def bulbs = getChildDevices()
for (bulb in body) {
def device = bulbs.find{it.deviceNetworkId == "${app.id}/${bulb.key}"}
if (device) {
if (bulb.value.state?.reachable) {
sendBasicEvents(device, "on", bulb.value?.state?.on)
sendBasicEvents(device, "bri", bulb.value?.state?.bri)
sendColorEvents(device, bulb.value?.state?.xy, bulb.value?.state?.hue, bulb.value?.state?.sat, bulb.value?.state?.ct, bulb.value?.state?.colormode)
if (state.bulbs[bulb.key]?.online == false) {
// light just came back online, notify device watch
def lastActivity = now()
device.sendEvent(name: "deviceWatch-status", value: "ONLINE", description: "Last Activity is on ${new Date((long) lastActivity)}", displayed: false, isStateChange: true)
}
state.bulbs[bulb.key]?.online = true
// If user just executed commands, then do not send events to avoid confusing the turning on/off state
if (!state.updating) {
sendBasicEvents(device, "on", bulb.value?.state?.on)
sendBasicEvents(device, "bri", bulb.value?.state?.bri)
sendColorEvents(device, bulb.value?.state?.xy, bulb.value?.state?.hue, bulb.value?.state?.sat, bulb.value?.state?.ct, bulb.value?.state?.colormode)
}
} else {
state.bulbs[bulb.key]?.online = false
log.warn "$device is not reachable by Hue bridge"
}
}
}
return []
}
}
}
return []
}
private updateInProgress() {
state.updating = true
@@ -976,22 +988,34 @@ def hubVerification(bodytext) {
def on(childDevice) {
log.debug "Executing 'on'"
def id = getId(childDevice)
if (!isOnline(id)) {
return "Bulb is unreachable"
}
updateInProgress()
createSwitchEvent(childDevice, "on")
put("lights/${getId(childDevice)}/state", [on: true])
put("lights/$id/state", [on: true])
return "Bulb is turning On"
}
def off(childDevice) {
log.debug "Executing 'off'"
def id = getId(childDevice)
if (!isOnline(id)) {
return "Bulb is unreachable"
}
updateInProgress()
createSwitchEvent(childDevice, "off")
put("lights/${getId(childDevice)}/state", [on: false])
put("lights/$id/state", [on: false])
return "Bulb is turning Off"
}
def setLevel(childDevice, percent) {
log.debug "Executing 'setLevel'"
def id = getId(childDevice)
if (!isOnline(id)) {
return "Bulb is unreachable"
}
updateInProgress()
// 1 - 254
def level
@@ -1006,48 +1030,64 @@ def setLevel(childDevice, percent) {
// that means that the light will still be on when on is called next time
// Lets emulate that here
if (percent > 0) {
put("lights/${getId(childDevice)}/state", [bri: level, on: true])
put("lights/$id/state", [bri: level, on: true])
} else {
put("lights/${getId(childDevice)}/state", [on: false])
put("lights/$id/state", [on: false])
}
return "Setting level to $percent"
}
def setSaturation(childDevice, percent) {
log.debug "Executing 'setSaturation($percent)'"
updateInProgress()
def id = getId(childDevice)
if (!isOnline(id)) {
return "Bulb is unreachable"
}
updateInProgress()
// 0 - 254
def level = Math.min(Math.round(percent * 254 / 100), 254)
// TODO should this be done by app only or should we default to on?
createSwitchEvent(childDevice, "on")
put("lights/${getId(childDevice)}/state", [sat: level, on: true])
put("lights/$id/state", [sat: level, on: true])
return "Setting saturation to $percent"
}
def setHue(childDevice, percent) {
log.debug "Executing 'setHue($percent)'"
def id = getId(childDevice)
if (!isOnline(id)) {
return "Bulb is unreachable"
}
updateInProgress()
// 0 - 65535
def level = Math.min(Math.round(percent * 65535 / 100), 65535)
// TODO should this be done by app only or should we default to on?
createSwitchEvent(childDevice, "on")
put("lights/${getId(childDevice)}/state", [hue: level, on: true])
put("lights/$id/state", [hue: level, on: true])
return "Setting hue to $percent"
}
def setColorTemperature(childDevice, huesettings) {
log.debug "Executing 'setColorTemperature($huesettings)'"
def id = getId(childDevice)
if (!isOnline(id)) {
return "Bulb is unreachable"
}
updateInProgress()
// 153 (6500K) to 500 (2000K)
def ct = hueSettings == 6500 ? 153 : Math.round(1000000/huesettings)
createSwitchEvent(childDevice, "on")
put("lights/${getId(childDevice)}/state", [ct: ct, on: true])
put("lights/$id/state", [ct: ct, on: true])
return "Setting color temperature to $percent"
}
def setColor(childDevice, huesettings) {
log.debug "Executing 'setColor($huesettings)'"
def id = getId(childDevice)
if (!isOnline(id)) {
return "Bulb is unreachable"
}
updateInProgress()
def value = [:]
@@ -1104,15 +1144,23 @@ def setColor(childDevice, huesettings) {
value.on = false
createSwitchEvent(childDevice, value.on ? "on" : "off")
put("lights/${getId(childDevice)}/state", value)
put("lights/$id/state", value)
return "Setting color to $value"
}
def ping(childDevice) {
if (isOnline(getId(childDevice))) {
childDevice.sendEvent(name: "deviceWatch-ping", value: "ONLINE", description: "Hue Light is reachable", displayed: false, isStateChange: true)
return "Device is Online"
} else {
return "Device is Offline"
}
}
private getId(childDevice) {
if (childDevice.device?.deviceNetworkId?.startsWith("HUE")) {
return childDevice.device?.deviceNetworkId[3..-1]
}
else {
} else {
return childDevice.device?.deviceNetworkId.split("/")[-1]
}
}
@@ -1123,10 +1171,13 @@ private poll() {
log.debug "GET: $host$uri"
sendHubCommand(new physicalgraph.device.HubAction("""GET ${uri} HTTP/1.1
HOST: ${host}
""", physicalgraph.device.Protocol.LAN, selectedHue))
}
private isOnline(id) {
return (state.bulbs[id].online != null && state.bulbs[id].online) || state.bulbs[id].online == null
}
private put(path, body) {
def host = getBridgeIP()
def uri = "/api/${state.username}/$path"
@@ -1194,7 +1245,7 @@ def convertBulbListToMap() {
if (state.bulbs instanceof java.util.List) {
def map = [:]
state.bulbs.unique {it.id}.each { bulb ->
map << ["${bulb.id}":["id":bulb.id, "name":bulb.name, "type": bulb.type, "modelid": bulb.modelid, "hub":bulb.hub]]
map << ["${bulb.id}":["id":bulb.id, "name":bulb.name, "type": bulb.type, "modelid": bulb.modelid, "hub":bulb.hub, "online": bulb.online]]
}
state.bulbs = map
}

View File

@@ -111,16 +111,23 @@ private sendMessage(evt) {
if (location.contactBookEnabled) {
sendNotificationToContacts(msg, recipients, options)
} else {
if (pushAndPhone != 'No') {
log.debug 'sending push'
options.method = 'push'
sendNotification(msg, options)
}
if (phone) {
options.phone = phone
log.debug 'sending SMS'
sendNotification(msg, options)
if (pushAndPhone != 'No') {
log.debug 'Sending push and SMS'
options.method = 'both'
} else {
log.debug 'Sending SMS'
options.method = 'phone'
}
} else if (pushAndPhone != 'No') {
log.debug 'Sending push'
options.method = 'push'
} else {
log.debug 'Sending nothing'
options.method = 'none'
}
sendNotification(msg, options)
}
if (frequency) {
state[evt.deviceId] = now()