mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-17 05:10:50 +00:00
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:
committed by
Yaima Valdivia
parent
fc587ef15a
commit
c473745e47
@@ -10,120 +10,142 @@
|
||||
* 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
|
||||
* Wemo Switch
|
||||
*
|
||||
* Author: superuser
|
||||
* Date: 2013-10-11
|
||||
* Author: Juan Risso (SmartThings)
|
||||
* Date: 2015-10-11
|
||||
*/
|
||||
metadata {
|
||||
definition (name: "Wemo Switch", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Actuator"
|
||||
capability "Switch"
|
||||
capability "Polling"
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
definition (name: "Wemo Switch", namespace: "smartthings", author: "SmartThings") {
|
||||
capability "Actuator"
|
||||
capability "Switch"
|
||||
capability "Polling"
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
|
||||
command "subscribe"
|
||||
command "resubscribe"
|
||||
command "unsubscribe"
|
||||
}
|
||||
attribute "currentIP", "string"
|
||||
|
||||
// simulator metadata
|
||||
simulator {}
|
||||
command "subscribe"
|
||||
command "resubscribe"
|
||||
command "unsubscribe"
|
||||
}
|
||||
|
||||
// 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"
|
||||
}
|
||||
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat") {
|
||||
state "default", label:'', action:"refresh.refresh", icon:"st.secondary.refresh"
|
||||
}
|
||||
// simulator metadata
|
||||
simulator {}
|
||||
|
||||
main "switch"
|
||||
details (["switch", "refresh"])
|
||||
}
|
||||
// UI tile definitions
|
||||
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
|
||||
def parse(String description) {
|
||||
log.debug "Parsing '${description}'"
|
||||
log.debug "Parsing '${description}'"
|
||||
|
||||
def msg = parseLanMessage(description)
|
||||
def headerString = msg.header
|
||||
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()
|
||||
if (headerString?.contains("SID: uuid:")) {
|
||||
def sid = (headerString =~ /SID: uuid:.*/) ? ( headerString =~ /SID: uuid:.*/)[0] : "0"
|
||||
sid -= "SID: uuid:".trim()
|
||||
|
||||
updateDataValue("subscriptionId", sid)
|
||||
}
|
||||
updateDataValue("subscriptionId", sid)
|
||||
}
|
||||
|
||||
def result = []
|
||||
def bodyString = msg.body
|
||||
if (bodyString) {
|
||||
def body = new XmlSlurper().parseText(bodyString)
|
||||
|
||||
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()) {
|
||||
def value = body?.property?.BinaryState?.text().toInteger() == 1 ? "on" : "off"
|
||||
log.trace "Notify: BinaryState = ${value}"
|
||||
result << createEvent(name: "switch", value: value)
|
||||
} else if (body?.property?.TimeZoneNotification?.text()) {
|
||||
log.debug "Notify: TimeZoneNotification = ${body?.property?.TimeZoneNotification?.text()}"
|
||||
} else if (body?.Body?.GetBinaryStateResponse?.BinaryState?.text()) {
|
||||
def value = body?.Body?.GetBinaryStateResponse?.BinaryState?.text().toInteger() == 1 ? "on" : "off"
|
||||
log.trace "GetBinaryResponse: BinaryState = ${value}"
|
||||
result << createEvent(name: "switch", value: value)
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
def result = []
|
||||
def bodyString = msg.body
|
||||
if (bodyString) {
|
||||
unschedule("setOffline")
|
||||
def body = new XmlSlurper().parseText(bodyString)
|
||||
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()) {
|
||||
def value = body?.property?.BinaryState?.text().substring(0, 1).toInteger() == 0 ? "off" : "on"
|
||||
log.trace "Notify: BinaryState = ${value}, ${body.property.BinaryState}"
|
||||
def dispaux = device.currentValue("switch") != value
|
||||
result << createEvent(name: "switch", value: value, descriptionText: "Switch is ${value}", displayed: dispaux)
|
||||
} else if (body?.property?.TimeZoneNotification?.text()) {
|
||||
log.debug "Notify: TimeZoneNotification = ${body?.property?.TimeZoneNotification?.text()}"
|
||||
} else if (body?.Body?.GetBinaryStateResponse?.BinaryState?.text()) {
|
||||
def value = body?.Body?.GetBinaryStateResponse?.BinaryState?.text().substring(0, 1).toInteger() == 0 ? "off" : "on"
|
||||
log.trace "GetBinaryResponse: BinaryState = ${value}, ${body.property.BinaryState}"
|
||||
log.info "Connection: ${device.currentValue("connection")}"
|
||||
if (device.currentValue("currentIP") == "Offline") {
|
||||
def ipvalue = convertHexToIP(getDataValue("ip"))
|
||||
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() {
|
||||
// This is essentially System.currentTimeMillis()/1000, but System is disallowed by the sandbox.
|
||||
((new GregorianCalendar().time.time / 1000l).toInteger()).toString()
|
||||
// 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")
|
||||
device.hub.getDataValue("localIP") + ":" + device.hub.getDataValue("localSrvPortTCP")
|
||||
}
|
||||
|
||||
private Integer convertHexToInt(hex) {
|
||||
Integer.parseInt(hex,16)
|
||||
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(".")
|
||||
[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 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() {
|
||||
log.debug "Executing 'on'"
|
||||
sendEvent(name: "switch", value: "on")
|
||||
log.debug "Executing 'on'"
|
||||
def turnOn = new physicalgraph.device.HubAction("""POST /upnp/control/basicevent1 HTTP/1.1
|
||||
SOAPAction: "urn:Belkin:service:basicevent:1#SetBinaryState"
|
||||
Host: ${getHostAddress()}
|
||||
@@ -133,17 +155,16 @@ Content-Length: 333
|
||||
<?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:Body>
|
||||
<m:SetBinaryState xmlns:m="urn:Belkin:service:basicevent:1">
|
||||
<m:SetBinaryState xmlns:m="urn:Belkin:service:basicevent:1">
|
||||
<BinaryState>1</BinaryState>
|
||||
</m:SetBinaryState>
|
||||
</m:SetBinaryState>
|
||||
</SOAP-ENV:Body>
|
||||
</SOAP-ENV:Envelope>""", physicalgraph.device.Protocol.LAN)
|
||||
}
|
||||
|
||||
def off() {
|
||||
log.debug "Executing 'off'"
|
||||
sendEvent(name: "switch", value: "off")
|
||||
def turnOff = new physicalgraph.device.HubAction("""POST /upnp/control/basicevent1 HTTP/1.1
|
||||
log.debug "Executing '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
|
||||
@@ -152,36 +173,13 @@ Content-Length: 333
|
||||
<?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:Body>
|
||||
<m:SetBinaryState xmlns:m="urn:Belkin:service:basicevent:1">
|
||||
<m:SetBinaryState xmlns:m="urn:Belkin:service:basicevent:1">
|
||||
<BinaryState>0</BinaryState>
|
||||
</m:SetBinaryState>
|
||||
</m:SetBinaryState>
|
||||
</SOAP-ENV:Body>
|
||||
</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) {
|
||||
log.debug "Executing 'subscribe()'"
|
||||
def address = getCallBackAddress()
|
||||
@@ -200,27 +198,30 @@ 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)
|
||||
}
|
||||
def refresh() {
|
||||
log.debug "Executing WeMo Switch 'subscribe', then 'timeSyncResponse', then 'poll'"
|
||||
[subscribe(), timeSyncResponse(), poll()]
|
||||
}
|
||||
|
||||
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}")
|
||||
}
|
||||
|
||||
////////////////////////////
|
||||
def resubscribe() {
|
||||
log.debug "Executing 'resubscribe()'"
|
||||
|
||||
def sid = getDeviceDataByName("subscriptionId")
|
||||
|
||||
log.debug "Executing 'resubscribe()'"
|
||||
def sid = getDeviceDataByName("subscriptionId")
|
||||
new physicalgraph.device.HubAction("""SUBSCRIBE /upnp/event/basicevent1 HTTP/1.1
|
||||
HOST: ${getHostAddress()}
|
||||
SID: uuid:${sid}
|
||||
@@ -228,12 +229,11 @@ TIMEOUT: Second-5400
|
||||
|
||||
|
||||
""", physicalgraph.device.Protocol.LAN)
|
||||
|
||||
}
|
||||
|
||||
////////////////////////////
|
||||
|
||||
def unsubscribe() {
|
||||
def sid = getDeviceDataByName("subscriptionId")
|
||||
def sid = getDeviceDataByName("subscriptionId")
|
||||
new physicalgraph.device.HubAction("""UNSUBSCRIBE publisher path HTTP/1.1
|
||||
HOST: ${getHostAddress()}
|
||||
SID: uuid:${sid}
|
||||
@@ -242,7 +242,7 @@ SID: uuid:${sid}
|
||||
""", physicalgraph.device.Protocol.LAN)
|
||||
}
|
||||
|
||||
////////////////////////////
|
||||
|
||||
//TODO: Use UTC Timezone
|
||||
def timeSyncResponse() {
|
||||
log.debug "Executing 'timeSyncResponse()'"
|
||||
@@ -267,9 +267,15 @@ User-Agent: CyberGarage-HTTP/1.0
|
||||
""", 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() {
|
||||
log.debug "Executing 'poll'"
|
||||
if (device.currentValue("currentIP") != "Offline")
|
||||
runIn(10, setOffline)
|
||||
new physicalgraph.device.HubAction("""POST /upnp/control/basicevent1 HTTP/1.1
|
||||
SOAPACTION: "urn:Belkin:service:basicevent:1#GetBinaryState"
|
||||
Content-Length: 277
|
||||
|
||||
Reference in New Issue
Block a user