I’ve adapted the Smartthings sample code for Wemo Switches to work for Wemo Insight Switches. There are still a few problems with it. The code overrides the device handler for Wemo Switch but cannot deal with old (non-insight) Wemo Switches. Therefore, it is only suitable if you only have Wemo Insight Switches and no Wemo Switches.
The device handler adds a Power Meter capability and can respond to changes in energy consumption. I use it to monitor our washing machine and dryer in an outbuilding and send me a notification when they are done.
The device handler reports:
- Energy consumption.
- The correct on/off status of the device (previously, Wemo Insight Switches that were on but consumed little energy were reported as off.
- Time on now
- Time on today
To install the code,
-
Create or overwrite the Wemo Switch device handler with the code below.
-
Run the Wemo (Connect) SmartApp on your mobile device.
/**
-
Copyright 2015 SmartThings
-
Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except
-
in compliance with the License. You may obtain a copy of the License at:
-
http://www.apache.org/licenses/LICENSE-2.0
-
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
-
on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
-
for the specific language governing permissions and limitations under the License.
-
Wemo Switch
-
Author: superuser
-
Date: 2013-10-11
-
Updated for Wemo Insight Switch
-
Author: Jeroen Keppens, 14/3/2016
*/
metadata {
definition (name: “Wemo Switch”, namespace: “smartthings”, author: “SmartThings”) {
capability “Actuator”
capability “Switch”
capability “Polling”
capability “Power Meter”
capability “Refresh”
capability “Sensor”attribute "status", "string" attribute "onNow", "string" attribute "onToday", "string" command "subscribe" command "resubscribe" command "unsubscribe"
}
// simulator metadata
simulator {}// UI tile definitions
tiles {
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"
}
valueTile(“power”, “device.power”, decoration: “flat”, width: 1, height: 1) {
state “power”, label:’{currentValue} W' } standardTile("status", "device.status", width: 1, height: 1 ) { state( "on", label: 'ON', backgroundColor: "#79b821" ) state( "standby", label: 'STANDBY', backgroundColor: "#FFA500" ) state( "off", label: 'OFF', backgroundColor: "#ffffff" ) } valueTile("onNow", "device.onNow", decoration: "flat", width: 1, height: 1) { state "onNow", label:'{currentValue}’
}
valueTile(“onToday”, “device.onToday”, decoration: “flat”, width: 1, height: 1) {
state “onToday”, label:’${currentValue}’
}
standardTile(“refresh”, “device.switch”, inactiveLabel: false, decoration: “flat”, width: 1, height: 1) {
state “default”, label:’’, action:“refresh.refresh”, icon:“st.secondary.refresh”
}main "switch" details (["switch", "power", "status", "onNow", "onToday", "refresh"])
}
}
private def parseBinaryStateString(stateString) {
//log.debug “stateString: stateString" //log.debug "stateString.size(): {stateString.size()}”
def states =
def token = “”
for(int i=0; i<stateString.size(); i++) {
if (stateString[i] ==~ /[0-9]/) {
//log.debug “symbol: ${stateString[i]}”
token = token + stateString[i]
} else {
//log.debug “adding token: $token”
int state = token.toLong()
states << state
token = “”
}
}
//log.debug “states: $states”
return states
}private def convertSecondsToTimeString(seconds) {
log.debug “convertSecondsToTimeString of $seconds seconds”
int h = seconds.intdiv(3600)
int m = (seconds-h3600).intdiv(60)
int s = seconds-(h3600)-(m*60)
log.debug “h: $h, m: $m, s: $s”
def timeString = “”
if (h==0) {
timeString = timeString + “00”
} else if (h<=9) {
timeString = timeString + “0$h”
} else {
timeString = timeString + “$h”
}
if (m==0) {
timeString = timeString + “:00”
} else if (m<=9) {
timeString = timeString + “:0$m”
} else {
timeString = timeString + “:$m”
}
if (s==0) {
timeString = timeString + “:00”
} else if (s<=9) {
timeString = timeString + “:0$s”
} else {
timeString = timeString + “:$s”
}
return timeString
}// parse events into attributes
def parse(String description) {
log.debug “Parsing ‘${description}’”def msg = parseLanMessage(description) def headerString = msg.header if (headerString?.contains("SID: uuid:")) { def sid = (headerString =~ /SID: uuid:.*/) ? ( headerString =~ /SID: uuid:.*/)[0] : "0" sid -= "SID: uuid:".trim() updateDataValue("subscriptionId", sid) } def result = [] def bodyString = msg.body if (bodyString) { def body = new XmlSlurper().parseText(bodyString) //log.info "Line 67 bodyString: $bodyString" //log.info "Line 68 body: $body" if (body?.property?.TimeSyncRequest?.text()) { log.trace "Got TimeSyncRequest" result << timeSyncResponse() } else if (body?.Body?.SetBinaryStateResponse?.BinaryState?.text()) { log.trace "Got SetBinaryStateResponse = ${body?.Body?.SetBinaryStateResponse?.BinaryState?.text()}" } else if (body?.property?.BinaryState?.text()) { // To Do: Refactor def wemoStateString = body?.property?.BinaryState?.text() log.info "State string: $wemoStateString" def states = parseBinaryStateString(wemoStateString) def value = states[0] == 0 ? "off" : "on" log.trace "Notify: BinaryState = ${value}" result << createEvent(name: "switch", value: value) if (states[0]==0) { result << createEvent(name: "status", value: "off") } else if (states[0]==1) { result << createEvent(name: "status", value: "on") } else { result << createEvent(name: "status", value: "standby") } if (states[2] != null) { log.debug "states[2]: ${states[2]}" def onNow = convertSecondsToTimeString(states[2]) log.trace "Notify: Time on now = ${onNow}" result << createEvent(name: "onNow", value: "on now $onNow") } if (states[3] != null) { log.debug "states[3]: ${states[3]}" def onToday = convertSecondsToTimeString(states[3]) log.trace "Notify: Time on today = ${onToday}" result << createEvent(name: "onToday", value: "on today $onToday") } if (states[7] != null) { log.debug "states[7]: ${states[7]}" def power = (int)Math.round(states[7]/1000) log.trace "Notify: Current power consumption = ${power}" result << createEvent(name: "power", value: power) } //def value = body?.property?.BinaryState?.text().toInteger() == 1 ? "on" : "off" //log.trace "Notify: BinaryState = ${value}" } else if (body?.property?.TimeZoneNotification?.text()) { log.debug "Notify: TimeZoneNotification = ${body?.property?.TimeZoneNotification?.text()}" } else if (body?.Body?.GetBinaryStateResponse?.BinaryState?.text()) { // To Do: Revise def wemoStateString = body?.Body?.GetBinaryStateResponse?.BinaryState?.text() log.info "Binary State Response (not processed at this point): $wemoStateString" } } result
}
private getTime() {
// This is essentially System.currentTimeMillis()/1000, but System is disallowed by the sandbox.
((new GregorianCalendar().time.time / 1000l).toInteger()).toString()
}private getCallBackAddress() {
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() {
def ip = getDataValue(“ip”)
def port = getDataValue(“port”)if (!ip || !port) { def parts = device.deviceNetworkId.split(":") if (parts.length == 2) { ip = parts[0] port = parts[1] } else { 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}" return convertHexToIP(ip) + ":" + convertHexToInt(port)
}
def on() {
<?xml version="1.0"?>
log.debug “Executing ‘on’”
sendEvent(name: “switch”, value: “on”)
def turnOn = new physicalgraph.device.HubAction("""POST /upnp/control/basicevent1 HTTP/1.1
SOAPAction: “urn:Belkin:service:basicevent:1#SetBinaryState”
Host: ${getHostAddress()}
Content-Type: text/xml
Content-Length: 333<SOAP-ENV:Envelope xmlns:SOAP-ENV=“http://schemas.xmlsoap.org/soap/envelope/” SOAP-ENV:encodingStyle=“http://schemas.xmlsoap.org/soap/encoding/”>
SOAP-ENV:Body
<m:SetBinaryState xmlns:m=“urn:Belkin:service:basicevent:1”>
1
</m:SetBinaryState>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>""", physicalgraph.device.Protocol.LAN)
}def off() {
<?xml version="1.0"?>
log.debug “Executing ‘off’”
sendEvent(name: “switch”, value: “off”)
def turnOff = new physicalgraph.device.HubAction("""POST /upnp/control/basicevent1 HTTP/1.1
SOAPAction: “urn:Belkin:service:basicevent:1#SetBinaryState”
Host: ${getHostAddress()}
Content-Type: text/xml
Content-Length: 333<SOAP-ENV:Envelope xmlns:SOAP-ENV=“http://schemas.xmlsoap.org/soap/envelope/” SOAP-ENV:encodingStyle=“http://schemas.xmlsoap.org/soap/encoding/”>
SOAP-ENV:Body
<m:SetBinaryState xmlns:m=“urn:Belkin:service:basicevent:1”>
0
</m:SetBinaryState>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>""", physicalgraph.device.Protocol.LAN)
}/*def refresh() {
<?xml version="1.0" encoding="utf-8"?>
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<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) {
log.debug “Executing ‘subscribe()’”
def address = getCallBackAddress()
new physicalgraph.device.HubAction("""SUBSCRIBE /upnp/event/basicevent1 HTTP/1.1
HOST: {hostAddress} CALLBACK: <http://{address}/>
NT: upnp:event
TIMEOUT: Second-5400
User-Agent: CyberGarage-HTTP/1.0“”", physicalgraph.device.Protocol.LAN)
}def subscribe() {
subscribe(getHostAddress())
}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)
}
if (port && port != existingPort) {
log.debug “Updating port from $existingPort to $port”
updateDataValue(“port”, port)
}subscribe("${ip}:${port}")
}
////////////////////////////
def resubscribe() {
log.debug “Executing ‘resubscribe()’”def sid = getDeviceDataByName(“subscriptionId”)
new physicalgraph.device.HubAction("""SUBSCRIBE /upnp/event/basicevent1 HTTP/1.1
HOST: {getHostAddress()} SID: uuid:{sid}
TIMEOUT: Second-5400“”", physicalgraph.device.Protocol.LAN)
}
////////////////////////////
def unsubscribe() {
def sid = getDeviceDataByName(“subscriptionId”)
new physicalgraph.device.HubAction("""UNSUBSCRIBE publisher path HTTP/1.1
HOST: {getHostAddress()} SID: uuid:{sid}“”", physicalgraph.device.Protocol.LAN)
}////////////////////////////
<?xml version="1.0" encoding="utf-8"?>
//TODO: Use UTC Timezone
def timeSyncResponse() {
log.debug “Executing ‘timeSyncResponse()’”
new physicalgraph.device.HubAction("""POST /upnp/control/timesync1 HTTP/1.1
Content-Type: text/xml; charset=“utf-8”
SOAPACTION: “urn:Belkin:service:timesync:1#TimeSync”
Content-Length: 376
HOST: ${getHostAddress()}
User-Agent: CyberGarage-HTTP/1.0<s:Envelope xmlns:s=“http://schemas.xmlsoap.org/soap/envelope/” s:encodingStyle=“http://schemas.xmlsoap.org/soap/encoding/”>
<s:Body>
<u:TimeSync xmlns:u=“urn:Belkin:service:timesync:1”>
${getTime()}
-05.00
1
1
</u:TimeSync>
</s:Body>
</s:Envelope>
“”", physicalgraph.device.Protocol.LAN)
}def poll() {
<?xml version="1.0" encoding="utf-8"?>
log.debug “Executing ‘poll’”
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<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)
} -