mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-13 13:21:53 +00:00
Compare commits
15 Commits
MSA-1463-1
...
MSA-1467-1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
477fec7735 | ||
|
|
bac37f9ca2 | ||
|
|
f0ecb65c09 | ||
|
|
1c0ddd2571 | ||
|
|
b7e0cbda09 | ||
|
|
f80e094bd9 | ||
|
|
f3f5cc42c9 | ||
|
|
313fe8b734 | ||
|
|
0d693386d1 | ||
|
|
d1aee1e874 | ||
|
|
3528e7da51 | ||
|
|
5c2e06c98d | ||
|
|
26df619b4f | ||
|
|
af2ea04442 | ||
|
|
383f72580a |
@@ -1,183 +0,0 @@
|
||||
metadata {
|
||||
definition (name: "Rooflight Controller", namespace: "CR76", author: "Cameron.Reid@Glazingvision.co.uk") {
|
||||
capability "Refresh"
|
||||
capability "Polling"
|
||||
capability "Switch"
|
||||
capability "Switch level"
|
||||
|
||||
attribute "Rooflight", "string"
|
||||
attribute "Weather", "string"
|
||||
attribute "Thermostat", "string"
|
||||
|
||||
command "Stop"
|
||||
|
||||
fingerprint profileId: "0104", inClusters: "0000,0006,0008"
|
||||
}
|
||||
|
||||
// simulator metadata
|
||||
simulator {
|
||||
}
|
||||
|
||||
// UI tile definitions
|
||||
tiles {
|
||||
multiAttributeTile(name:"sliderTile", type: "generic", width:6, height:4) {
|
||||
tileAttribute("device.Rooflight", key: "PRIMARY_CONTROL") {
|
||||
attributeState "Open", label:'Open', action: "Switch.off", backgroundColor:"#79b821", nextState:"Closing"
|
||||
attributeState "Closed", label:'Closed', action: "Switch.on", backgroundColor:"#ffffff", nextState:"Opening"
|
||||
attributeState "Opening", label:'Opening', backgroundColor:"#79b821"
|
||||
attributeState "Closing", label:'Closing', backgroundColor:"#ffffff"
|
||||
}
|
||||
tileAttribute ("device.Rooflight", key: "SECONDARY_CONTROL") {
|
||||
attributeState "Open", label: 'Push to close.', nextState:"Closing"
|
||||
attributeState "Closed", label: 'Push to open.', nextState:"Opening"
|
||||
attributeState "Opening", label: 'Skylight opening.'
|
||||
attributeState "Closing", label: 'Skylight closing.'
|
||||
}
|
||||
}
|
||||
standardTile("Close", "device.Rooflight", inactiveLabel: false, decoration: "flat", width:2, height:2 ) {
|
||||
state "default", label: 'Close', action:"Switch.off", icon:"st.thermostat.thermostat-left"
|
||||
}
|
||||
standardTile("Stop", "device.level", inactiveLabel: false, decoration: "flat", width:2, height:2 ) {
|
||||
state "default", action:"Stop", icon:"st.sonos.stop-btn"
|
||||
}
|
||||
standardTile("Open", "device.Rooflight", inactiveLabel: false, decoration: "flat", width:2, height:2 ) {
|
||||
state "default", label: 'open', action:"Switch.on", icon:"st.thermostat.thermostat-right"
|
||||
}
|
||||
standardTile("Rain", "device.Weather", width: 2, height: 2) {
|
||||
state "No", backgroundColor: "#ffffff"
|
||||
state "Dry", label: 'Dry', icon:"st.Weather.weather14", backgroundColor: "#ffffff"
|
||||
state "Raining", label: 'Raining', icon:"st.Weather.weather10", backgroundColor: "#153591"
|
||||
}
|
||||
standardTile("refresh", "device.refresh", inactiveLabel: false, decoration: "flat", width:2, height:2 ) {
|
||||
state "default", action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
|
||||
main ("sliderTile")
|
||||
details (["sliderTile","Close","Stop","Open","Rain","refresh"])
|
||||
}
|
||||
}
|
||||
|
||||
// Parse incoming device messages to generate events
|
||||
def parse(String description) {
|
||||
log.debug "Parse description $description"
|
||||
def name = null
|
||||
def value = null
|
||||
|
||||
if (description?.startsWith("catchall: 0104 0006 38")) {
|
||||
log.debug "On/Off command received from EP 38 open / close command"
|
||||
if (description?.endsWith("01 01 0000001000")) {
|
||||
name = "Rooflight"
|
||||
value = "Closed"
|
||||
}
|
||||
else if (description?.endsWith("01 01 0000001001")) {
|
||||
name = "Rooflight"
|
||||
value = "Open"
|
||||
}
|
||||
else if (description?.endsWith("01 01 0000001003")) {
|
||||
name = "Rooflight"
|
||||
value = "Opening"
|
||||
}
|
||||
else if (description?.endsWith("01 01 0000001004")) {
|
||||
name = "Rooflight"
|
||||
value = "Closing"
|
||||
}
|
||||
}
|
||||
if (description?.startsWith("catchall: 0104 0006 39")) {
|
||||
log.debug "On/Off command received from EP 39 is rain sensor connected"
|
||||
if (description?.endsWith("01 01 0000001000")) {
|
||||
name = "Weather"
|
||||
value = "No"
|
||||
state.tile = 1
|
||||
}
|
||||
else if (description?.endsWith("01 01 0000001001")) {
|
||||
name = "Weather"
|
||||
value = "Dry"
|
||||
state.tile = 0
|
||||
log.debug "${state.tile}"
|
||||
}
|
||||
}
|
||||
if (description?.startsWith("catchall: 0104 0006 40")) {
|
||||
log.debug "On/Off command received from EP 40 Thermostat"
|
||||
if (description?.endsWith("01 01 0000001000")){
|
||||
name = "Switch"
|
||||
value = "on"
|
||||
}
|
||||
else if (description?.endsWith("01 01 0000001001")) {
|
||||
name = "Switch"
|
||||
value = "off"
|
||||
}
|
||||
}
|
||||
if (description?.startsWith("catchall: 0104 0006 41")) {
|
||||
log.debug "On/Off command received from EP 41 rain sensor"
|
||||
if (description?.endsWith("01 01 0000001000")) {
|
||||
name = "Weather"
|
||||
value = "Dry"
|
||||
}
|
||||
else if (description?.endsWith("01 01 0000001001")) {
|
||||
name = "Weather"
|
||||
value = "Raining"
|
||||
}
|
||||
}
|
||||
if(description?.startsWith('read attr -')) {
|
||||
def descMap = parseDescriptionAsMap(description) //Get level value.
|
||||
value = zigbee.convertHexToInt(descMap.value)
|
||||
log.debug " level = $value"
|
||||
name = "level"
|
||||
}
|
||||
def result = createEvent(name: name, value: value)
|
||||
log.debug "Parse returned ${result?.descriptionText}"
|
||||
return result
|
||||
}
|
||||
|
||||
def parseDescriptionAsMap(description) {
|
||||
(description - "read attr - ").split(",").inject([:]) {
|
||||
map, param ->
|
||||
def nameAndValue = param.split(":")
|
||||
map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
|
||||
}
|
||||
}
|
||||
|
||||
// Commands to device
|
||||
def on() {
|
||||
log.debug "Rooflight Opening"
|
||||
def cmd = []
|
||||
cmd <<"st cmd 0x${device.deviceNetworkId} 0x38 0x0006 0x1 {}"
|
||||
cmd << "delay 250"
|
||||
cmd << zigbee.command(0x0008, 0x04, "64")
|
||||
}
|
||||
|
||||
def off() {
|
||||
log.debug "Rooflight Closing"
|
||||
def cmd = []
|
||||
cmd << "st cmd 0x${device.deviceNetworkId} 0x38 0x0006 0x0 {}"
|
||||
cmd << "delay 250"
|
||||
cmd << zigbee.command(0x0008, 0x04, "00")
|
||||
}
|
||||
|
||||
def Stop() { //Send stop command in the level control cluster.
|
||||
log.debug "send stop"
|
||||
"st cmd 0x${device.deviceNetworkId} 0x38 0x0008 0x07 {}"
|
||||
}
|
||||
|
||||
def poll() {
|
||||
log.debug "Poll is calling refresh"
|
||||
refresh()
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
log.debug "sending refresh command"
|
||||
log.debug "Tile State: ${state.tile}"
|
||||
def cmd = []
|
||||
if("${state.tile}" == "0") { //If rain sensor is connected refresh rain sensor and rooflight position.
|
||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0006 0x0000" // Read on / off attribute at End point 0x38 Rooflight open / closed.
|
||||
cmd << "delay 250"
|
||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0008 0x0000" // Read Level position at End point 0x38 Rooflight Position.
|
||||
cmd << "delay 250"
|
||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x41 0x0006 0x0000" // Read on / off attribute at End point 0x41 Rain sensor.
|
||||
}
|
||||
else {
|
||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0006 0x0000" // Read on / off attribute at End point 0x38 Rooflight open / closed.
|
||||
cmd << "delay 250"
|
||||
cmd << "st rattr 0x${device.deviceNetworkId} 0x38 0x0008 0x0000" // Read on / off attribute at End point 0x88 Rain sensor.
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 != "")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -757,6 +757,10 @@ def isValidSource(macAddress) {
|
||||
return (vbridges?.find {"${it.value.mac}" == macAddress}) != null
|
||||
}
|
||||
|
||||
def isInBulbDiscovery() {
|
||||
return state.inBulbDiscovery
|
||||
}
|
||||
|
||||
/////////////////////////////////////
|
||||
//CHILD DEVICE METHODS
|
||||
/////////////////////////////////////
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user