Wemo refactor final (DVCSMP-1189)

https://smartthings.atlassian.net/browse/DVCSMP-1189

Detect and mark device offline within 5 minutes.
Show Device offline in device tile.
Show Device offline in Recent Activity.
Log the current IP address to Recent Activity.
Log the changed IP address to Recent Activity.
Support 'Turning on' and 'Turning off' (blindly changing the state of
device to ON or OFF without confirming bulb responded correctly)
Turn on / off through Wemo-App reflected timely in SmartThings
App/Ecosystem.
Manual turn on / off of device is reflected timely in SmartThings
App/Ecosystem.

Lower case createEvent

Bug Fixes

Bug fixes

setOffline

Minor cosmetic fixes
This commit is contained in:
juano2310
2015-10-27 15:37:38 -04:00
committed by Yaima Valdivia
parent fc587ef15a
commit c473745e47
4 changed files with 275 additions and 308 deletions

View File

@@ -25,6 +25,8 @@ metadata {
capability "Refresh" capability "Refresh"
capability "Sensor" capability "Sensor"
attribute "currentIP", "string"
command "subscribe" command "subscribe"
command "resubscribe" command "resubscribe"
command "unsubscribe" command "unsubscribe"
@@ -34,21 +36,36 @@ metadata {
// simulator metadata // simulator metadata
simulator {} simulator {}
// UI tile definitions // UI tile definitions
tiles { tiles(scale: 2) {
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) { multiAttributeTile(name:"rich-control", type: "switch", canChangeIcon: true){
state "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821", nextState:"turningOff" tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
state "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff", nextState:"turningOn" attributeState "on", label:'${name}', action:"switch.off", icon:"st.Home.home30", backgroundColor:"#79b821", nextState:"turningOff"
state "turningOn", label:'${name}', icon:"st.switches.switch.on", backgroundColor:"#79b821" attributeState "off", label:'${name}', action:"switch.on", icon:"st.Home.home30", backgroundColor:"#ffffff", nextState:"turningOn"
state "turningOff", label:'${name}', icon:"st.switches.switch.off", backgroundColor:"#ffffff" attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.Home.home30", backgroundColor:"#79b821", nextState:"turningOff"
} attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.Home.home30", backgroundColor:"#ffffff", nextState:"turningOn"
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") { attributeState "offline", label:'${name}', icon:"st.Home.home30", backgroundColor:"#ff0000"
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh" }
} tileAttribute ("currentIP", key: "SECONDARY_CONTROL") {
attributeState "currentIP", label: ''
}
}
main "switch" standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
details (["switch", "refresh"]) state "on", label:'${name}', action:"switch.off", icon:"st.Home.home30", backgroundColor:"#79b821", nextState:"turningOff"
} state "off", label:'${name}', action:"switch.on", icon:"st.Home.home30", backgroundColor:"#ffffff", nextState:"turningOn"
state "turningOn", label:'${name}', action:"switch.off", icon:"st.Home.home30", backgroundColor:"#79b821", nextState:"turningOff"
state "turningOff", label:'${name}', action:"switch.on", icon:"st.Home.home30", backgroundColor:"#ffffff", nextState:"turningOn"
state "offline", label:'${name}', icon:"st.Home.home30", backgroundColor:"#ff0000"
}
standardTile("refresh", "device.switch", inactiveLabel: false, height: 2, width: 2, decoration: "flat") {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main(["switch"])
details(["rich-control", "refresh"])
}
} }
// parse events into attributes // parse events into attributes
@@ -68,6 +85,7 @@ def parse(String description) {
def result = [] def result = []
def bodyString = msg.body def bodyString = msg.body
if (bodyString) { if (bodyString) {
unschedule("setOffline")
def body = new XmlSlurper().parseText(bodyString) def body = new XmlSlurper().parseText(bodyString)
if (body?.property?.TimeSyncRequest?.text()) { if (body?.property?.TimeSyncRequest?.text()) {
@@ -78,13 +96,14 @@ def parse(String description) {
} else if (body?.property?.BinaryState?.text()) { } else if (body?.property?.BinaryState?.text()) {
def value = body?.property?.BinaryState?.text().toInteger() == 1 ? "on" : "off" def value = body?.property?.BinaryState?.text().toInteger() == 1 ? "on" : "off"
log.trace "Notify: BinaryState = ${value}" log.trace "Notify: BinaryState = ${value}"
result << createEvent(name: "switch", value: value) result << createEvent(name: "switch", value: value, descriptionText: "Switch is ${value}")
} else if (body?.property?.TimeZoneNotification?.text()) { } else if (body?.property?.TimeZoneNotification?.text()) {
log.debug "Notify: TimeZoneNotification = ${body?.property?.TimeZoneNotification?.text()}" log.debug "Notify: TimeZoneNotification = ${body?.property?.TimeZoneNotification?.text()}"
} else if (body?.Body?.GetBinaryStateResponse?.BinaryState?.text()) { } else if (body?.Body?.GetBinaryStateResponse?.BinaryState?.text()) {
def value = body?.Body?.GetBinaryStateResponse?.BinaryState?.text().toInteger() == 1 ? "on" : "off" def value = body?.Body?.GetBinaryStateResponse?.BinaryState?.text().toInteger() == 1 ? "on" : "off"
log.trace "GetBinaryResponse: BinaryState = ${value}" log.trace "GetBinaryResponse: BinaryState = ${value}"
result << createEvent(name: "switch", value: value) def dispaux = device.currentValue("switch") != value
result << createEvent(name: "switch", value: value, descriptionText: "Switch is ${value}", displayed: dispaux)
} }
} }
@@ -101,14 +120,6 @@ private getCallBackAddress() {
device.hub.getDataValue("localIP") + ":" + device.hub.getDataValue("localSrvPortTCP") device.hub.getDataValue("localIP") + ":" + device.hub.getDataValue("localSrvPortTCP")
} }
private Integer convertHexToInt(hex) {
Integer.parseInt(hex,16)
}
private String convertHexToIP(hex) {
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
}
private getHostAddress() { private getHostAddress() {
def ip = getDataValue("ip") def ip = getDataValue("ip")
def port = getDataValue("port") def port = getDataValue("port")
@@ -195,6 +206,8 @@ def subscribe(ip, port) {
if (ip && ip != existingIp) { if (ip && ip != existingIp) {
log.debug "Updating ip from $existingIp to $ip" log.debug "Updating ip from $existingIp to $ip"
updateDataValue("ip", ip) updateDataValue("ip", ip)
def ipvalue = convertHexToIP(getDataValue("ip"))
sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP changed to ${ipvalue}")
} }
if (port && port != existingPort) { if (port && port != existingPort) {
log.debug "Updating port from $existingPort to $port" log.debug "Updating port from $existingPort to $port"
@@ -259,6 +272,8 @@ User-Agent: CyberGarage-HTTP/1.0
def poll() { def poll() {
log.debug "Executing 'poll'" log.debug "Executing 'poll'"
if (device.currentValue("currentIP") != "Offline")
runIn(10, setOffline)
new physicalgraph.device.HubAction("""POST /upnp/control/basicevent1 HTTP/1.1 new physicalgraph.device.HubAction("""POST /upnp/control/basicevent1 HTTP/1.1
SOAPACTION: "urn:Belkin:service:basicevent:1#GetBinaryState" SOAPACTION: "urn:Belkin:service:basicevent:1#GetBinaryState"
Content-Length: 277 Content-Length: 277
@@ -274,3 +289,15 @@ User-Agent: CyberGarage-HTTP/1.0
</s:Body> </s:Body>
</s:Envelope>""", physicalgraph.device.Protocol.LAN) </s:Envelope>""", physicalgraph.device.Protocol.LAN)
} }
def setOffline() {
sendEvent(name: "switch", value: "offline", descriptionText: "The device is offline")
}
private Integer convertHexToInt(hex) {
Integer.parseInt(hex,16)
}
private String convertHexToIP(hex) {
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
}

View File

@@ -21,6 +21,8 @@
capability "Refresh" capability "Refresh"
capability "Sensor" capability "Sensor"
attribute "currentIP", "string"
command "subscribe" command "subscribe"
command "resubscribe" command "resubscribe"
command "unsubscribe" command "unsubscribe"
@@ -31,17 +33,30 @@
} }
// UI tile definitions // UI tile definitions
tiles { tiles(scale: 2) {
multiAttributeTile(name:"rich-control", type: "motion", canChangeIcon: true){
tileAttribute ("device.motion", key: "PRIMARY_CONTROL") {
attributeState "active", label:'motion', icon:"st.motion.motion.active", backgroundColor:"#53a7c0"
attributeState "inactive", label:'no motion', icon:"st.motion.motion.inactive", backgroundColor:"#ffffff"
attributeState "offline", label:'${name}', icon:"st.motion.motion.active", backgroundColor:"#ff0000"
}
tileAttribute ("currentIP", key: "SECONDARY_CONTROL") {
attributeState "currentIP", label: ''
}
}
standardTile("motion", "device.motion", width: 2, height: 2) { standardTile("motion", "device.motion", width: 2, height: 2) {
state("active", label:'motion', icon:"st.motion.motion.active", backgroundColor:"#53a7c0") state("active", label:'motion', icon:"st.motion.motion.active", backgroundColor:"#53a7c0")
state("inactive", label:'no motion', icon:"st.motion.motion.inactive", backgroundColor:"#ffffff") state("inactive", label:'no motion', icon:"st.motion.motion.inactive", backgroundColor:"#ffffff")
} state("offline", label:'${name}', icon:"st.motion.motion.inactive", backgroundColor:"#ff0000")
standardTile("refresh", "device.motion", inactiveLabel: false, decoration: "flat") {
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
} }
standardTile("refresh", "device.switch", inactiveLabel: false, height: 2, width: 2, decoration: "flat") {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main "motion" main "motion"
details (["motion", "refresh"]) details (["rich-control", "refresh"])
} }
} }
@@ -62,6 +77,7 @@ def parse(String description) {
def result = [] def result = []
def bodyString = msg.body def bodyString = msg.body
if (bodyString) { if (bodyString) {
unschedule("setOffline")
def body = new XmlSlurper().parseText(bodyString) def body = new XmlSlurper().parseText(bodyString)
if (body?.property?.TimeSyncRequest?.text()) { if (body?.property?.TimeSyncRequest?.text()) {
@@ -72,7 +88,7 @@ def parse(String description) {
} else if (body?.property?.BinaryState?.text()) { } else if (body?.property?.BinaryState?.text()) {
def value = body?.property?.BinaryState?.text().toInteger() == 1 ? "active" : "inactive" def value = body?.property?.BinaryState?.text().toInteger() == 1 ? "active" : "inactive"
log.debug "Notify - BinaryState = ${value}" log.debug "Notify - BinaryState = ${value}"
result << createEvent(name: "motion", value: value) result << createEvent(name: "motion", value: value, descriptionText: "Motion is ${value}")
} else if (body?.property?.TimeZoneNotification?.text()) { } else if (body?.property?.TimeZoneNotification?.text()) {
log.debug "Notify: TimeZoneNotification = ${body?.property?.TimeZoneNotification?.text()}" log.debug "Notify: TimeZoneNotification = ${body?.property?.TimeZoneNotification?.text()}"
} }
@@ -91,14 +107,6 @@ private getCallBackAddress() {
device.hub.getDataValue("localIP") + ":" + device.hub.getDataValue("localSrvPortTCP") device.hub.getDataValue("localIP") + ":" + device.hub.getDataValue("localSrvPortTCP")
} }
private Integer convertHexToInt(hex) {
Integer.parseInt(hex,16)
}
private String convertHexToIP(hex) {
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
}
private getHostAddress() { private getHostAddress() {
def ip = getDataValue("ip") def ip = getDataValue("ip")
def port = getDataValue("port") def port = getDataValue("port")
@@ -125,6 +133,8 @@ def refresh() {
//////////////////////////// ////////////////////////////
def getStatus() { def getStatus() {
log.debug "Executing WeMo Motion 'getStatus'" log.debug "Executing WeMo Motion 'getStatus'"
if (device.currentValue("currentIP") != "Offline")
runIn(10, setOffline)
new physicalgraph.device.HubAction("""POST /upnp/control/basicevent1 HTTP/1.1 new physicalgraph.device.HubAction("""POST /upnp/control/basicevent1 HTTP/1.1
SOAPACTION: "urn:Belkin:service:basicevent:1#GetBinaryState" SOAPACTION: "urn:Belkin:service:basicevent:1#GetBinaryState"
Content-Length: 277 Content-Length: 277
@@ -165,7 +175,9 @@ def subscribe(ip, port) {
def existingPort = getDataValue("port") def existingPort = getDataValue("port")
if (ip && ip != existingIp) { if (ip && ip != existingIp) {
log.debug "Updating ip from $existingIp to $ip" log.debug "Updating ip from $existingIp to $ip"
updateDataValue("ip", ip) updateDataValue("ip", ip)
def ipvalue = convertHexToIP(getDataValue("ip"))
sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP changed to ${ipvalue}")
} }
if (port && port != existingPort) { if (port && port != existingPort) {
log.debug "Updating port from $existingPort to $port" log.debug "Updating port from $existingPort to $port"
@@ -226,3 +238,15 @@ User-Agent: CyberGarage-HTTP/1.0
</s:Envelope> </s:Envelope>
""", physicalgraph.device.Protocol.LAN) """, physicalgraph.device.Protocol.LAN)
} }
def setOffline() {
sendEvent(name: "motion", value: "offline", descriptionText: "The device is offline")
}
private Integer convertHexToInt(hex) {
Integer.parseInt(hex,16)
}
private String convertHexToIP(hex) {
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
}

View File

@@ -10,120 +10,142 @@
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License * 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. * for the specific language governing permissions and limitations under the License.
* *
* Wemo Switch * Wemo Switch
* *
* Author: superuser * Author: Juan Risso (SmartThings)
* Date: 2013-10-11 * Date: 2015-10-11
*/ */
metadata { metadata {
definition (name: "Wemo Switch", namespace: "smartthings", author: "SmartThings") { definition (name: "Wemo Switch", namespace: "smartthings", author: "SmartThings") {
capability "Actuator" capability "Actuator"
capability "Switch" capability "Switch"
capability "Polling" capability "Polling"
capability "Refresh" capability "Refresh"
capability "Sensor" capability "Sensor"
command "subscribe" attribute "currentIP", "string"
command "resubscribe"
command "unsubscribe"
}
// simulator metadata command "subscribe"
simulator {} command "resubscribe"
command "unsubscribe"
}
// UI tile definitions // simulator metadata
tiles { simulator {}
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
state "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.on", backgroundColor:"#79b821"
state "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.off", backgroundColor:"#ffffff"
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
}
main "switch" // UI tile definitions
details (["switch", "refresh"]) tiles(scale: 2) {
} multiAttributeTile(name:"rich-control", type: "switch", canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.off", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.on", backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.off", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.on", backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "offline", label:'${name}', icon:"st.switches.switch.off", backgroundColor:"#ff0000"
}
tileAttribute ("currentIP", key: "SECONDARY_CONTROL") {
attributeState "currentIP", label: ''
}
}
standardTile("switch", "device.switch", width: 2, height: 2, canChangeIcon: true) {
state "on", label:'${name}', action:"switch.off", icon:"st.switches.switch.off", backgroundColor:"#79b821", nextState:"turningOff"
state "off", label:'${name}', action:"switch.on", icon:"st.switches.switch.on", backgroundColor:"#ffffff", nextState:"turningOn"
state "turningOn", label:'${name}', action:"switch.off", icon:"st.switches.switch.off", backgroundColor:"#79b821", nextState:"turningOff"
state "turningOff", label:'${name}', action:"switch.on", icon:"st.switches.switch.on", backgroundColor:"#ffffff", nextState:"turningOn"
state "offline", label:'${name}', icon:"st.switches.switch.off", backgroundColor:"#ff0000"
}
standardTile("refresh", "device.switch", inactiveLabel: false, height: 2, width: 2, decoration: "flat") {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
main(["switch"])
details(["rich-control", "refresh"])
}
} }
// parse events into attributes // parse events into attributes
def parse(String description) { def parse(String description) {
log.debug "Parsing '${description}'" log.debug "Parsing '${description}'"
def msg = parseLanMessage(description) def msg = parseLanMessage(description)
def headerString = msg.header def headerString = msg.header
if (headerString?.contains("SID: uuid:")) { if (headerString?.contains("SID: uuid:")) {
def sid = (headerString =~ /SID: uuid:.*/) ? ( headerString =~ /SID: uuid:.*/)[0] : "0" def sid = (headerString =~ /SID: uuid:.*/) ? ( headerString =~ /SID: uuid:.*/)[0] : "0"
sid -= "SID: uuid:".trim() sid -= "SID: uuid:".trim()
updateDataValue("subscriptionId", sid) updateDataValue("subscriptionId", sid)
} }
def result = [] def result = []
def bodyString = msg.body def bodyString = msg.body
if (bodyString) { if (bodyString) {
def body = new XmlSlurper().parseText(bodyString) unschedule("setOffline")
def body = new XmlSlurper().parseText(bodyString)
if (body?.property?.TimeSyncRequest?.text()) { if (body?.property?.TimeSyncRequest?.text()) {
log.trace "Got TimeSyncRequest" log.trace "Got TimeSyncRequest"
result << timeSyncResponse() result << timeSyncResponse()
} else if (body?.Body?.SetBinaryStateResponse?.BinaryState?.text()) { } else if (body?.Body?.SetBinaryStateResponse?.BinaryState?.text()) {
log.trace "Got SetBinaryStateResponse = ${body?.Body?.SetBinaryStateResponse?.BinaryState?.text()}" log.trace "Got SetBinaryStateResponse = ${body?.Body?.SetBinaryStateResponse?.BinaryState?.text()}"
} else if (body?.property?.BinaryState?.text()) { } else if (body?.property?.BinaryState?.text()) {
def value = body?.property?.BinaryState?.text().toInteger() == 1 ? "on" : "off" def value = body?.property?.BinaryState?.text().substring(0, 1).toInteger() == 0 ? "off" : "on"
log.trace "Notify: BinaryState = ${value}" log.trace "Notify: BinaryState = ${value}, ${body.property.BinaryState}"
result << createEvent(name: "switch", value: value) def dispaux = device.currentValue("switch") != value
} else if (body?.property?.TimeZoneNotification?.text()) { result << createEvent(name: "switch", value: value, descriptionText: "Switch is ${value}", displayed: dispaux)
log.debug "Notify: TimeZoneNotification = ${body?.property?.TimeZoneNotification?.text()}" } else if (body?.property?.TimeZoneNotification?.text()) {
} else if (body?.Body?.GetBinaryStateResponse?.BinaryState?.text()) { log.debug "Notify: TimeZoneNotification = ${body?.property?.TimeZoneNotification?.text()}"
def value = body?.Body?.GetBinaryStateResponse?.BinaryState?.text().toInteger() == 1 ? "on" : "off" } else if (body?.Body?.GetBinaryStateResponse?.BinaryState?.text()) {
log.trace "GetBinaryResponse: BinaryState = ${value}" def value = body?.Body?.GetBinaryStateResponse?.BinaryState?.text().substring(0, 1).toInteger() == 0 ? "off" : "on"
result << createEvent(name: "switch", value: value) log.trace "GetBinaryResponse: BinaryState = ${value}, ${body.property.BinaryState}"
} log.info "Connection: ${device.currentValue("connection")}"
} if (device.currentValue("currentIP") == "Offline") {
def ipvalue = convertHexToIP(getDataValue("ip"))
result sendEvent(name: "IP", value: ipvalue, descriptionText: "IP is ${ipvalue}")
}
def dispaux2 = device.currentValue("switch") != value
result << createEvent(name: "switch", value: value, descriptionText: "Switch is ${value}", displayed: dispaux2)
}
}
result
} }
private getTime() { private getTime() {
// This is essentially System.currentTimeMillis()/1000, but System is disallowed by the sandbox. // This is essentially System.currentTimeMillis()/1000, but System is disallowed by the sandbox.
((new GregorianCalendar().time.time / 1000l).toInteger()).toString() ((new GregorianCalendar().time.time / 1000l).toInteger()).toString()
} }
private getCallBackAddress() { private getCallBackAddress() {
device.hub.getDataValue("localIP") + ":" + device.hub.getDataValue("localSrvPortTCP") device.hub.getDataValue("localIP") + ":" + device.hub.getDataValue("localSrvPortTCP")
} }
private Integer convertHexToInt(hex) { private Integer convertHexToInt(hex) {
Integer.parseInt(hex,16) Integer.parseInt(hex,16)
} }
private String convertHexToIP(hex) { private String convertHexToIP(hex) {
[convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".") [convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
} }
private getHostAddress() { private getHostAddress() {
def ip = getDataValue("ip") def ip = getDataValue("ip")
def port = getDataValue("port") def port = getDataValue("port")
if (!ip || !port) {
if (!ip || !port) { def parts = device.deviceNetworkId.split(":")
def parts = device.deviceNetworkId.split(":") if (parts.length == 2) {
if (parts.length == 2) { ip = parts[0]
ip = parts[0] port = parts[1]
port = parts[1] } else {
} else { log.warn "Can't figure out ip and port for device: ${device.id}"
log.warn "Can't figure out ip and port for device: ${device.id}" }
} }
} log.debug "Using ip: ${ip} and port: ${port} for device: ${device.id}"
log.debug "Using ip: ${ip} and port: ${port} for device: ${device.id}" return convertHexToIP(ip) + ":" + convertHexToInt(port)
return convertHexToIP(ip) + ":" + convertHexToInt(port)
} }
def on() { def on() {
log.debug "Executing 'on'" log.debug "Executing 'on'"
sendEvent(name: "switch", value: "on")
def turnOn = new physicalgraph.device.HubAction("""POST /upnp/control/basicevent1 HTTP/1.1 def turnOn = new physicalgraph.device.HubAction("""POST /upnp/control/basicevent1 HTTP/1.1
SOAPAction: "urn:Belkin:service:basicevent:1#SetBinaryState" SOAPAction: "urn:Belkin:service:basicevent:1#SetBinaryState"
Host: ${getHostAddress()} Host: ${getHostAddress()}
@@ -133,17 +155,16 @@ Content-Length: 333
<?xml version="1.0"?> <?xml version="1.0"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body> <SOAP-ENV:Body>
<m:SetBinaryState xmlns:m="urn:Belkin:service:basicevent:1"> <m:SetBinaryState xmlns:m="urn:Belkin:service:basicevent:1">
<BinaryState>1</BinaryState> <BinaryState>1</BinaryState>
</m:SetBinaryState> </m:SetBinaryState>
</SOAP-ENV:Body> </SOAP-ENV:Body>
</SOAP-ENV:Envelope>""", physicalgraph.device.Protocol.LAN) </SOAP-ENV:Envelope>""", physicalgraph.device.Protocol.LAN)
} }
def off() { def off() {
log.debug "Executing 'off'" log.debug "Executing 'off'"
sendEvent(name: "switch", value: "off") def turnOff = new physicalgraph.device.HubAction("""POST /upnp/control/basicevent1 HTTP/1.1
def turnOff = new physicalgraph.device.HubAction("""POST /upnp/control/basicevent1 HTTP/1.1
SOAPAction: "urn:Belkin:service:basicevent:1#SetBinaryState" SOAPAction: "urn:Belkin:service:basicevent:1#SetBinaryState"
Host: ${getHostAddress()} Host: ${getHostAddress()}
Content-Type: text/xml Content-Type: text/xml
@@ -152,36 +173,13 @@ Content-Length: 333
<?xml version="1.0"?> <?xml version="1.0"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body> <SOAP-ENV:Body>
<m:SetBinaryState xmlns:m="urn:Belkin:service:basicevent:1"> <m:SetBinaryState xmlns:m="urn:Belkin:service:basicevent:1">
<BinaryState>0</BinaryState> <BinaryState>0</BinaryState>
</m:SetBinaryState> </m:SetBinaryState>
</SOAP-ENV:Body> </SOAP-ENV:Body>
</SOAP-ENV:Envelope>""", physicalgraph.device.Protocol.LAN) </SOAP-ENV:Envelope>""", physicalgraph.device.Protocol.LAN)
} }
/*def refresh() {
log.debug "Executing 'refresh'"
new physicalgraph.device.HubAction("""POST /upnp/control/basicevent1 HTTP/1.1
SOAPACTION: "urn:Belkin:service:basicevent:1#GetBinaryState"
Content-Length: 277
Content-Type: text/xml; charset="utf-8"
HOST: ${getHostAddress()}
User-Agent: CyberGarage-HTTP/1.0
<?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:GetBinaryState xmlns:u="urn:Belkin:service:basicevent:1">
</u:GetBinaryState>
</s:Body>
</s:Envelope>""", physicalgraph.device.Protocol.LAN)
}*/
def refresh() {
log.debug "Executing WeMo Switch 'subscribe', then 'timeSyncResponse', then 'poll'"
[subscribe(), timeSyncResponse(), poll()]
}
def subscribe(hostAddress) { def subscribe(hostAddress) {
log.debug "Executing 'subscribe()'" log.debug "Executing 'subscribe()'"
def address = getCallBackAddress() def address = getCallBackAddress()
@@ -200,27 +198,30 @@ def subscribe() {
subscribe(getHostAddress()) subscribe(getHostAddress())
} }
def subscribe(ip, port) { def refresh() {
def existingIp = getDataValue("ip") log.debug "Executing WeMo Switch 'subscribe', then 'timeSyncResponse', then 'poll'"
def existingPort = getDataValue("port") [subscribe(), timeSyncResponse(), poll()]
if (ip && ip != existingIp) { }
log.debug "Updating ip from $existingIp to $ip"
updateDataValue("ip", ip)
}
if (port && port != existingPort) {
log.debug "Updating port from $existingPort to $port"
updateDataValue("port", port)
}
def subscribe(ip, port) {
def existingIp = getDataValue("ip")
def existingPort = getDataValue("port")
if (ip && ip != existingIp) {
log.debug "Updating ip from $existingIp to $ip"
updateDataValue("ip", ip)
def ipvalue = convertHexToIP(getDataValue("ip"))
sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP changed to ${ipvalue}")
}
if (port && port != existingPort) {
log.debug "Updating port from $existingPort to $port"
updateDataValue("port", port)
}
subscribe("${ip}:${port}") subscribe("${ip}:${port}")
} }
////////////////////////////
def resubscribe() { def resubscribe() {
log.debug "Executing 'resubscribe()'" log.debug "Executing 'resubscribe()'"
def sid = getDeviceDataByName("subscriptionId")
def sid = getDeviceDataByName("subscriptionId")
new physicalgraph.device.HubAction("""SUBSCRIBE /upnp/event/basicevent1 HTTP/1.1 new physicalgraph.device.HubAction("""SUBSCRIBE /upnp/event/basicevent1 HTTP/1.1
HOST: ${getHostAddress()} HOST: ${getHostAddress()}
SID: uuid:${sid} SID: uuid:${sid}
@@ -228,12 +229,11 @@ TIMEOUT: Second-5400
""", physicalgraph.device.Protocol.LAN) """, physicalgraph.device.Protocol.LAN)
} }
////////////////////////////
def unsubscribe() { def unsubscribe() {
def sid = getDeviceDataByName("subscriptionId") def sid = getDeviceDataByName("subscriptionId")
new physicalgraph.device.HubAction("""UNSUBSCRIBE publisher path HTTP/1.1 new physicalgraph.device.HubAction("""UNSUBSCRIBE publisher path HTTP/1.1
HOST: ${getHostAddress()} HOST: ${getHostAddress()}
SID: uuid:${sid} SID: uuid:${sid}
@@ -242,7 +242,7 @@ SID: uuid:${sid}
""", physicalgraph.device.Protocol.LAN) """, physicalgraph.device.Protocol.LAN)
} }
////////////////////////////
//TODO: Use UTC Timezone //TODO: Use UTC Timezone
def timeSyncResponse() { def timeSyncResponse() {
log.debug "Executing 'timeSyncResponse()'" log.debug "Executing 'timeSyncResponse()'"
@@ -267,9 +267,15 @@ User-Agent: CyberGarage-HTTP/1.0
""", physicalgraph.device.Protocol.LAN) """, physicalgraph.device.Protocol.LAN)
} }
def setOffline() {
//sendEvent(name: "currentIP", value: "Offline", displayed: false)
sendEvent(name: "switch", value: "offline", descriptionText: "The device is offline")
}
def poll() { def poll() {
log.debug "Executing 'poll'" log.debug "Executing 'poll'"
if (device.currentValue("currentIP") != "Offline")
runIn(10, setOffline)
new physicalgraph.device.HubAction("""POST /upnp/control/basicevent1 HTTP/1.1 new physicalgraph.device.HubAction("""POST /upnp/control/basicevent1 HTTP/1.1
SOAPACTION: "urn:Belkin:service:basicevent:1#GetBinaryState" SOAPACTION: "urn:Belkin:service:basicevent:1#GetBinaryState"
Content-Length: 277 Content-Length: 277

View File

@@ -61,10 +61,7 @@ def firstPage()
log.debug "REFRESH COUNT :: ${refreshCount}" log.debug "REFRESH COUNT :: ${refreshCount}"
if(!state.subscribe) { subscribe(location, null, locationHandler, [filterEvents:false])
subscribe(location, null, locationHandler, [filterEvents:false])
state.subscribe = true
}
//ssdp request every 25 seconds //ssdp request every 25 seconds
if((refreshCount % 5) == 0) { if((refreshCount % 5) == 0) {
@@ -168,21 +165,30 @@ def getWemoLightSwitches()
def installed() { def installed() {
log.debug "Installed with settings: ${settings}" log.debug "Installed with settings: ${settings}"
initialize() initialize()
runIn(5, "subscribeToDevices") //initial subscriptions delayed by 5 seconds
runIn(10, "refreshDevices") //refresh devices, delayed by 10 seconds
runIn(900, "doDeviceSync" , [overwrite: false]) //setup ip:port syncing every 15 minutes
// SUBSCRIBE responses come back with TIMEOUT-1801 (30 minutes), so we refresh things a bit before they expire (29 minutes)
runIn(1740, "refresh", [overwrite: false])
} }
def updated() { def updated() {
log.debug "Updated with settings: ${settings}" log.debug "Updated with settings: ${settings}"
initialize() initialize()
}
runIn(5, "subscribeToDevices") //subscribe again to new/old devices wait 5 seconds def initialize() {
runIn(10, "refreshDevices") //refresh devices again, delayed by 10 seconds unsubscribe()
unschedule()
subscribe(location, null, locationHandler, [filterEvents:false])
if (selectedSwitches)
addSwitches()
if (selectedMotions)
addMotions()
if (selectedLightSwitches)
addLightSwitches()
runIn(5, "subscribeToDevices") //initial subscriptions delayed by 5 seconds
runIn(10, "refreshDevices") //refresh devices, delayed by 10 seconds
runEvery5Minutes("refresh")
} }
def resubscribe() { def resubscribe() {
@@ -192,8 +198,7 @@ def resubscribe() {
def refresh() { def refresh() {
log.debug "refresh() called" log.debug "refresh() called"
//reschedule the refreshes doDeviceSync()
runIn(1740, "refresh", [overwrite: false])
refreshDevices() refreshDevices()
} }
@@ -236,7 +241,8 @@ def addSwitches() {
"port": selectedSwitch.value.port "port": selectedSwitch.value.port
] ]
]) ])
def ipvalue = convertHexToIP(selectedSwitch.value.ip)
d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}")
log.debug "Created ${d.displayName} with id: ${d.id}, dni: ${d.deviceNetworkId}" log.debug "Created ${d.displayName} with id: ${d.id}, dni: ${d.deviceNetworkId}"
} else { } else {
log.debug "found ${d.displayName} with id $dni already exists" log.debug "found ${d.displayName} with id $dni already exists"
@@ -266,8 +272,9 @@ def addMotions() {
"port": selectedMotion.value.port "port": selectedMotion.value.port
] ]
]) ])
def ipvalue = convertHexToIP(selectedMotion.value.ip)
log.debug "Created ${d.displayName} with id: ${d.id}, dni: ${d.deviceNetworkId}" d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}")
log.debug "Created ${d.displayName} with id: ${d.id}, dni: ${d.deviceNetworkId}"
} else { } else {
log.debug "found ${d.displayName} with id $dni already exists" log.debug "found ${d.displayName} with id $dni already exists"
} }
@@ -296,7 +303,8 @@ def addLightSwitches() {
"port": selectedLightSwitch.value.port "port": selectedLightSwitch.value.port
] ]
]) ])
def ipvalue = convertHexToIP(selectedLightSwitch.value.ip)
d.sendEvent(name: "currentIP", value: ipvalue, descriptionText: "IP is ${ipvalue}")
log.debug "created ${d.displayName} with id $dni" log.debug "created ${d.displayName} with id $dni"
} else { } else {
log.debug "found ${d.displayName} with id $dni already exists" log.debug "found ${d.displayName} with id $dni already exists"
@@ -304,27 +312,6 @@ def addLightSwitches() {
} }
} }
def initialize() {
// remove location subscription afterwards
unsubscribe()
state.subscribe = false
if (selectedSwitches)
{
addSwitches()
}
if (selectedMotions)
{
addMotions()
}
if (selectedLightSwitches)
{
addLightSwitches()
}
}
def locationHandler(evt) { def locationHandler(evt) {
def description = evt.description def description = evt.description
def hub = evt?.hubId def hub = evt?.hubId
@@ -333,53 +320,32 @@ def locationHandler(evt) {
log.debug parsedEvent log.debug parsedEvent
if (parsedEvent?.ssdpTerm?.contains("Belkin:device:controllee") || parsedEvent?.ssdpTerm?.contains("Belkin:device:insight")) { if (parsedEvent?.ssdpTerm?.contains("Belkin:device:controllee") || parsedEvent?.ssdpTerm?.contains("Belkin:device:insight")) {
def switches = getWemoSwitches() def switches = getWemoSwitches()
if (!(switches."${parsedEvent.ssdpUSN.toString()}")) {
if (!(switches."${parsedEvent.ssdpUSN.toString()}")) //if it doesn't already exist
{ //if it doesn't already exist
switches << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent] switches << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
} } else {
else
{ // just update the values
log.debug "Device was already found in state..." log.debug "Device was already found in state..."
def d = switches."${parsedEvent.ssdpUSN.toString()}" def d = switches."${parsedEvent.ssdpUSN.toString()}"
boolean deviceChangedValues = false boolean deviceChangedValues = false
log.debug "$d.ip <==> $parsedEvent.ip"
if(d.ip != parsedEvent.ip || d.port != parsedEvent.port) { if(d.ip != parsedEvent.ip || d.port != parsedEvent.port) {
d.ip = parsedEvent.ip d.ip = parsedEvent.ip
d.port = parsedEvent.port d.port = parsedEvent.port
deviceChangedValues = true deviceChangedValues = true
log.debug "Device's port or ip changed..." log.debug "Device's port or ip changed..."
def child = getChildDevice(parsedEvent.mac)
child.subscribe(parsedEvent.ip, parsedEvent.port)
child.poll()
} }
if (deviceChangedValues) {
def children = getChildDevices()
log.debug "Found children ${children}"
children.each {
if (it.getDeviceDataByName("mac") == parsedEvent.mac) {
log.debug "updating ip and port, and resubscribing, for device ${it} with mac ${parsedEvent.mac}"
it.subscribe(parsedEvent.ip, parsedEvent.port)
}
}
}
} }
} }
else if (parsedEvent?.ssdpTerm?.contains("Belkin:device:sensor")) { else if (parsedEvent?.ssdpTerm?.contains("Belkin:device:sensor")) {
def motions = getWemoMotions() def motions = getWemoMotions()
if (!(motions."${parsedEvent.ssdpUSN.toString()}")) {
if (!(motions."${parsedEvent.ssdpUSN.toString()}")) //if it doesn't already exist
{ //if it doesn't already exist
motions << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent] motions << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
} } else { // just update the values
else
{ // just update the values
log.debug "Device was already found in state..." log.debug "Device was already found in state..."
def d = motions."${parsedEvent.ssdpUSN.toString()}" def d = motions."${parsedEvent.ssdpUSN.toString()}"
@@ -412,10 +378,7 @@ def locationHandler(evt) {
if (!(lightSwitches."${parsedEvent.ssdpUSN.toString()}")) if (!(lightSwitches."${parsedEvent.ssdpUSN.toString()}"))
{ //if it doesn't already exist { //if it doesn't already exist
lightSwitches << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent] lightSwitches << ["${parsedEvent.ssdpUSN.toString()}":parsedEvent]
} } else {
else
{ // just update the values
log.debug "Device was already found in state..." log.debug "Device was already found in state..."
def d = lightSwitches."${parsedEvent.ssdpUSN.toString()}" def d = lightSwitches."${parsedEvent.ssdpUSN.toString()}"
@@ -426,21 +389,11 @@ def locationHandler(evt) {
d.port = parsedEvent.port d.port = parsedEvent.port
deviceChangedValues = true deviceChangedValues = true
log.debug "Device's port or ip changed..." log.debug "Device's port or ip changed..."
def child = getChildDevice(parsedEvent.mac)
log.debug "updating ip and port, and resubscribing, for device with mac ${parsedEvent.mac}"
child.subscribe(parsedEvent.ip, parsedEvent.port)
} }
if (deviceChangedValues) {
def children = getChildDevices()
log.debug "Found children ${children}"
children.each {
if (it.getDeviceDataByName("mac") == parsedEvent.mac) {
log.debug "updating ip and port, and resubscribing, for device ${it} with mac ${parsedEvent.mac}"
it.subscribe(parsedEvent.ip, parsedEvent.port)
}
}
}
} }
} }
else if (parsedEvent.headers && parsedEvent.body) { else if (parsedEvent.headers && parsedEvent.body) {
String headerString = new String(parsedEvent.headers.decodeBase64())?.toLowerCase() String headerString = new String(parsedEvent.headers.decodeBase64())?.toLowerCase()
@@ -580,73 +533,30 @@ private def parseDiscoveryMessage(String description) {
} }
} }
} }
device device
} }
def doDeviceSync(){ def doDeviceSync(){
log.debug "Doing Device Sync!" log.debug "Doing Device Sync!"
runIn(900, "doDeviceSync" , [overwrite: false]) //schedule to run again in 15 minutes
if(!state.subscribe) {
subscribe(location, null, locationHandler, [filterEvents:false])
state.subscribe = true
}
discoverAllWemoTypes() discoverAllWemoTypes()
} }
def pollChildren() { private String convertHexToIP(hex) {
def devices = getAllChildDevices() [convertHexToInt(hex[0..1]),convertHexToInt(hex[2..3]),convertHexToInt(hex[4..5]),convertHexToInt(hex[6..7])].join(".")
devices.each { d ->
//only poll switches?
d.poll()
}
} }
def delayPoll() { private Integer convertHexToInt(hex) {
log.debug "Executing 'delayPoll'" Integer.parseInt(hex,16)
runIn(5, "pollChildren")
} }
/*def poll() { private Boolean canInstallLabs() {
log.debug "Executing 'poll'"
runIn(600, "poll", [overwrite: false]) //schedule to run again in 10 minutes
def lastPoll = getLastPollTime()
def currentTime = now()
def lastPollDiff = currentTime - lastPoll
log.debug "lastPoll: $lastPoll, currentTime: $currentTime, lastPollDiff: $lastPollDiff"
setLastPollTime(currentTime)
doDeviceSync()
}
def setLastPollTime(currentTime) {
state.lastpoll = currentTime
}
def getLastPollTime() {
state.lastpoll ?: now()
}
def now() {
new Date().getTime()
}*/
private Boolean canInstallLabs()
{
return hasAllHubsOver("000.011.00603") return hasAllHubsOver("000.011.00603")
} }
private Boolean hasAllHubsOver(String desiredFirmware) private Boolean hasAllHubsOver(String desiredFirmware) {
{
return realHubFirmwareVersions.every { fw -> fw >= desiredFirmware } return realHubFirmwareVersions.every { fw -> fw >= desiredFirmware }
} }
private List getRealHubFirmwareVersions() private List getRealHubFirmwareVersions() {
{
return location.hubs*.firmwareVersionString.findAll { it } return location.hubs*.firmwareVersionString.findAll { it }
} }