Compare commits

...

1 Commits

Author SHA1 Message Date
meerasahib
19ad0b315a MSA-1488: mu home automation 2016-09-22 09:21:52 -05:00
2 changed files with 668 additions and 0 deletions

View File

@@ -0,0 +1,320 @@
/**
* Copyright 2016 Eric Maycock
*
* 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.
*
* Sonoff Wifi Switch 2.0
*
* Author: Eric Maycock (erocm123)
* Date: 2016-01-27
*/
import groovy.json.JsonSlurper
import groovy.util.XmlSlurper
metadata {
definition (name: "Sonoff Wifi Switch 2.0", namespace: "erocm123", author: "Eric Maycock") {
capability "Actuator"
capability "Switch"
capability "Refresh"
capability "Sensor"
capability "Configuration"
command "reboot"
}
simulator {
}
preferences {
input("ip", "string", title:"IP Address", description: "192.168.1.150" ,required: true, displayDuringSetup: true)
// Port should always be 80
//input("port", "string", title:"Port", description: "80" , required: true, displayDuringSetup: true)
}
tiles (scale: 2){
multiAttributeTile(name:"switch", type: "lighting", width: 6, height: 4, canChangeIcon: true){
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
attributeState "on", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "off", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
attributeState "turningOn", label:'${name}', action:"switch.off", icon:"st.lights.philips.hue-single", backgroundColor:"#79b821", nextState:"turningOff"
attributeState "turningOff", label:'${name}', action:"switch.on", icon:"st.lights.philips.hue-single", backgroundColor:"#ffffff", nextState:"turningOn"
}
}
standardTile("refresh", "device.switch", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
state "default", label:"", action:"refresh.refresh", icon:"st.secondary.refresh"
}
standardTile("configure", "device.configure", inactiveLabel: false, width: 2, height: 2, decoration: "flat") {
state "configure", label:'', action:"configuration.configure", icon:"st.secondary.configure"
}
valueTile("reboot", "device.reboot", decoration: "flat", height: 2, width: 2, inactiveLabel: false, canChangeIcon: false) {
state "default", label:"Reboot", action:"reboot", icon:"", backgroundColor:"#FFFFFF"
}
valueTile("hubInfo", "device.hubInfo", decoration: "flat", height: 2, width: 6, inactiveLabel: false, canChangeIcon: false) {
state "hubInfo", label:'${currentValue}' //backgroundColor:"#FFFFFF"
}
}
main(["switch"])
details(["switch",
"refresh","configure","reboot",
"hubInfo"])
}
def installed() {
log.debug "installed()"
configure()
}
def updated() {
log.debug "updated()"
configure()
}
def configure() {
log.debug "configure()"
log.debug "Configuring Device For SmartThings Use"
state.ruleConfigured = false
state.switchConfigured = false
state.buttonConfigured = false
sendEvent(name:"hubInfo", value:"Sonoff switch still being configured")
if (state.MAC != null) state.dni = setDeviceNetworkId(state.MAC)
else
if (ip != null) state.dni = setDeviceNetworkId(ip, "80")
state.hubIP = device.hub.getDataValue("localIP")
response(setupDevices() + setupRules() + refresh())
}
def setupDevices() {
def cmds = []
cmds << postAction("/devices?index=1&page=1", "taskdevicenumber=1&edit=1&page=1")
cmds << postAction("/devices?index=1&page=1", "taskdevicenumber=1&taskdevicename=BUTTON&taskdevicetimer=0&taskdeviceid=1&taskdevicepin1=0&taskdevicepin1pullup=on&plugin_001_type=1&plugin_001_button=2&taskdevicevaluename1=Switch&edit=1&page=1")
cmds << postAction("/devices?index=2&page=1", "taskdevicenumber=1&edit=1&page=1")
cmds << postAction("/devices?index=2&page=1", "taskdevicenumber=1&taskdevicename=SWITCH&taskdevicetimer=0&taskdeviceid=2&taskdevicepin1=12&taskdevicepin1pullup=on&plugin_001_type=1&plugin_001_button=0&taskdevicesenddata=on&taskdevicevaluename1=Switch&edit=1&page=1")
return delayBetween(cmds, 1000)
}
def setupRules() {
def cmds = []
cmds << postAction("/advanced", "mqttsubscribe=&mqttpublish=&messagedelay=1000&ip=0&ntphost=&timezone=0&syslogip=0.0.0.0&sysloglevel=0&udpport=0&useserial=on&serialloglevel=0&webloglevel=0&baudrate=115200&wdi2caddress=0&wireclockstretchlimit=0&userules=on&edit=1")
cmds << postAction("/rules", "rules=On+BUTTON%23Switch+do%0D%0A++if+%5BSWITCH%23Switch%5D%3D0%0D%0A++++gpio%2C12%2C1%0D%0A++else%0D%0A++++gpio%2C12%2C0%0D%0A++endif%0D%0Aendon%0D%0A%0D%0AOn+SWITCH%23Switch+do%0D%0A+if+%5BSWITCH%23Switch%5D%3D1%0D%0A++++gpio%2C13%2C0%0D%0A++else%0D%0A++++gpio%2C13%2C1%0D%0A++endif%0D%0Aendon")
return delayBetween(cmds, 1000)
}
def setupConfig() {
// Automatic config page submit not working at this time. Has to be done manually
/*def hubIP = device.hub.getDataValue("localIP")
log.debug "Hub IP: ${hubIP}"
def cmds = []
//cmds << postAction("/config", "protocol=1&usedns=0&controllerip=$hubIP&controllerport=39500&controlleruser=&controllerpassword=&delay=60")
return delayBetween(cmds, 1000)*/
}
def parse(description) {
//log.debug "Parsing: ${description}"
def events = []
def cmds
def descMap = parseDescriptionAsMap(description)
def body
log.debug "descMap: ${descMap}"
if (!state.MAC || state.MAC != descMap["mac"]) {
log.debug "Mac address of device found ${descMap["mac"]}"
updateDataValue("MAC", descMap["mac"])
}
if (state.MAC != null && state.dni != state.MAC) state.dni = setDeviceNetworkId(state.MAC)
if (descMap["body"]) body = new String(descMap["body"].decodeBase64())
if (body && body != "") {
if(body.startsWith("{") || body.startsWith("[")) {
def slurper = new JsonSlurper()
def result = slurper.parseText(body)
//log.debug "result: ${result}"
if (result.containsKey("Sensors")) {
def mySwitch = result.Sensors.find { it.TaskName == "SWITCH" }
def myButton = result.Sensors.find { it.TaskName == "BUTTON" }
def myLED = result.Sensors.find { it.TaskName == "LED" }
if (mySwitch) {
events << createEvent(name:"switch", value: (mySwitch.Switch.toInteger() == 0 ? 'off' : 'on'))
state.switchConfigured = true
}
if (myButton) state.buttonConfigured = true
//if (myLED) log.debug "LED is ${(myLED.Switch.toInteger() == 0 ? 'off' : 'on')}"
}
if (result.containsKey("pin")) {
if (result.pin == 12) events << createEvent(name:"switch", value: (result.state.toInteger() == 0 ? 'off' : 'on'))
}
if (result.containsKey("System")) {
if (result.System.containsKey("Uptime")) log.debug "System has been up ${result.System.Uptime.toInteger() / 60} hours"
}
} else {
//log.debug "Response is not JSON: $body"
def ruleSearch = "OnBUTTONSwitchdoifSWITCHSwitch0gpio121elsegpio120endifendonOnSWITCHSwitchdoifSWITCHSwitch1gpio130elsegpio131endifendon"
if (body.replaceAll("\\W", "").indexOf(ruleSearch) > 0) state.ruleConfigured = true
}
} else {
cmds = refresh()
}
if (settings.ip) {
//log.debug "switch: $state.switchConfigured, button: $state.buttonConfigured, rule: $state.ruleConfigured"
if (state.switchConfigured == true && state.buttonConfigured == true && state.ruleConfigured == true) {
events << createEvent(name:"hubInfo", value:"For instant status updates, configure switch at http://$settings.ip/config | Hub Info - IP: ${device.hub.getDataValue("localIP")}, Port: 39500")
} else {
events << createEvent(name:"hubInfo", value:"Sonoff switch still being configured")
}
}
else {
events << createEvent(name:"hubInfo", value:"IP address of the switch not entered. Please do so in device preferences.")
}
if (cmds) return cmds else return events
}
def parseDescriptionAsMap(description) {
description.split(",").inject([:]) { map, param ->
def nameAndValue = param.split(":")
if (nameAndValue.length == 2) map += [(nameAndValue[0].trim()):nameAndValue[1].trim()]
else map += [(nameAndValue[0].trim()):""]
}
}
private parseHTML(html) {
//log.debug html
def myHtml = html.split("<")
def result = []
switch (myHtml[0]) {
case "GPIO 12 Set to 0":
result = [name: "switch", value: "off"]
break
case "GPIO 12 Set to 1":
result = [name: "switch", value: "on"]
break
case "GPIO 13 Set to 0":
log.debug "LED is on"
break
case "GPIO 13 Set to 1":
log.debug "LED is off"
break
default:
break
}
return result
}
private ledOn() {
return getAction("/control?cmd=GPIO,13,0")
}
private ledOff() {
return getAction("/control?cmd=GPIO,13,1")
}
def on() {
log.debug "on()"
def cmds = []
cmds << getAction("/control?cmd=GPIO,12,1")
return cmds
}
def off() {
log.debug "off()"
def cmds = []
cmds << getAction("/control?cmd=GPIO,12,0")
return cmds
}
def refresh() {
log.debug "refresh()"
def cmds = []
cmds << getAction("/json")
return cmds
}
private getAction(uri){
updateDNI()
def headers = getHeader()
def hubAction = new physicalgraph.device.HubAction(
method: "GET",
path: uri,
headers: headers
)
return hubAction
}
private postAction(uri, data){
updateDNI()
def headers = getHeader()
def hubAction = new physicalgraph.device.HubAction(
method: "POST",
path: uri,
headers: headers,
body: data
)
return hubAction
}
private setDeviceNetworkId(ip, port = null){
def myDNI
if (port == null) {
myDNI = ip
} else {
def iphex = convertIPtoHex(ip)
def porthex = convertPortToHex(port)
myDNI = "$iphex:$porthex"
}
log.debug "Device Network Id set to ${myDNI}"
return myDNI
}
private updateDNI() {
if (device.deviceNetworkId != state.dni) {
device.deviceNetworkId = state.dni
}
}
private getHostAddress() {
return "${ip}:80"
}
private String convertIPtoHex(ipAddress) {
String hex = ipAddress.tokenize( '.' ).collect { String.format( '%02x', it.toInteger() ) }.join()
return hex
}
private String convertPortToHex(port) {
String hexport = port.toString().format( '%04x', port.toInteger() )
return hexport
}
private getHeader(){
def headers = [:]
headers.put("Host", getHostAddress())
headers.put("Content-Type", "application/x-www-form-urlencoded")
return headers
}
def reboot() {
log.debug "reboot()"
def uri = "/?cmd=reboot"
getAction(uri)
}

View File

@@ -0,0 +1,348 @@
/**
* Copyright 2016 Eric Maycock
*
* 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.
*
* Sonoff (Connect)
*
* Author: Eric Maycock (erocm123)
* Date: 2016-06-02
*/
definition(
name: "Sonoff (Connect)",
namespace: "erocm123",
author: "Eric Maycock (erocm123)",
description: "Service Manager for Sonoff switches",
category: "Convenience",
iconUrl: "https://raw.githubusercontent.com/erocm123/SmartThingsPublic/master/smartapps/erocm123/sonoff-connect.src/sonoff-connect-icon.png",
iconX2Url: "https://raw.githubusercontent.com/erocm123/SmartThingsPublic/master/smartapps/erocm123/sonoff-connect.src/sonoff-connect-icon-2x.png",
iconX3Url: "https://raw.githubusercontent.com/erocm123/SmartThingsPublic/master/smartapps/erocm123/sonoff-connect.src/sonoff-connect-icon-3x.png"
)
preferences {
page(name: "mainPage")
page(name: "configurePDevice")
page(name: "deletePDevice")
page(name: "changeName")
page(name: "discoveryPage", title: "Device Discovery", content: "discoveryPage", refreshTimeout:5)
page(name: "addDevices", title: "Add Sonoff Switches", content: "addDevices")
page(name: "deviceDiscovery")
}
def mainPage() {
dynamicPage(name: "mainPage", title: "Manage your Sonoff switches", nextPage: null, uninstall: true, install: true) {
section("Configure"){
href "deviceDiscovery", title:"Discover Sonoff Devices", description:""
}
section("Installed Devices"){
getChildDevices().sort({ a, b -> a["deviceNetworkId"] <=> b["deviceNetworkId"] }).each {
href "configurePDevice", title:"$it.label", description:"", params: [did: it.deviceNetworkId]
}
}
}
}
def configurePDevice(params){
def currentDevice
getChildDevices().each {
if(it.deviceNetworkId == params.did){
state.currentDeviceId = it.deviceNetworkId
state.currentDisplayName = it.displayName
}
}
dynamicPage(name: "configurePDevice", title: "Configure Sonoff Switches created with this app", nextPage: null) {
section {
app.updateSetting("${state.currentDeviceId}_label", getChildDevice(state.currentDeviceId).label)
input "${state.currentDeviceId}_label", "text", title:"Device Name", description: "", required: false
href "changeName", title:"Change Device Name", description: "Edit the name above and click here to change it", params: [did: state.currentDeviceId]
}
section {
href "deletePDevice", title:"Delete $state.currentDisplayName", description: "", params: [did: state.currentDeviceId]
}
}
}
def deletePDevice(params){
try {
unsubscribe()
getChildDevices().each {
if(it.deviceNetworkId.startsWith("${params.did}/")) deleteChildDevice(it.deviceNetworkId)
}
deleteChildDevice(params.did)
dynamicPage(name: "deletePDevice", title: "Deletion Summary", nextPage: "mainPage") {
section {
paragraph "The device has been deleted. Press next to continue"
}
}
} catch (e) {
dynamicPage(name: "deletePDevice", title: "Deletion Summary", nextPage: "mainPage") {
section {
paragraph "Error: ${(e as String).split(":")[1]}."
}
}
}
}
def changeName(params){
def thisDevice = getChildDevice(params.did)
thisDevice.label = settings["${params.did}_label"]
dynamicPage(name: "changeName", title: "Change Name Summary", nextPage: "mainPage") {
section {
paragraph "The device has been renamed. Press \"Next\" to continue"
}
}
}
def discoveryPage(){
return deviceDiscovery()
}
def deviceDiscovery(params=[:])
{
def devices = devicesDiscovered()
int deviceRefreshCount = !state.deviceRefreshCount ? 0 : state.deviceRefreshCount as int
state.deviceRefreshCount = deviceRefreshCount + 1
def refreshInterval = 3
def options = devices ?: []
def numFound = options.size() ?: 0
if ((numFound == 0 && state.deviceRefreshCount > 25) || params.reset == "true") {
log.trace "Cleaning old device memory"
state.devices = [:]
state.deviceRefreshCount = 0
app.updateSetting("selectedDevice", "")
}
ssdpSubscribe()
//sonoff discovery request every 15 //25 seconds
if((deviceRefreshCount % 5) == 0) {
discoverDevices()
}
//setup.xml request every 3 seconds except on discoveries
if(((deviceRefreshCount % 3) == 0) && ((deviceRefreshCount % 5) != 0)) {
verifyDevices()
}
return dynamicPage(name:"deviceDiscovery", title:"Discovery Started!", nextPage:"addDevices", refreshInterval:refreshInterval, uninstall: true) {
section("Please wait while we discover your Sonoff devices. Discovery can take five minutes or more, so sit back and relax! Select your device below once discovered.") {
input "selectedDevices", "enum", required:false, title:"Select Sonoff Switch (${numFound} found)", multiple:true, options:options
}
section("Options") {
href "deviceDiscovery", title:"Reset list of discovered devices", description:"", params: ["reset": "true"]
}
}
}
Map devicesDiscovered() {
def vdevices = getVerifiedDevices()
def map = [:]
vdevices.each {
def value = "${it.value.name}"
def key = "${it.value.mac}"
map["${key}"] = value
}
map
}
def getVerifiedDevices() {
getDevices().findAll{ it?.value?.verified == true }
}
private discoverDevices() {
sendHubCommand(new physicalgraph.device.HubAction("lan discovery urn:schemas-upnp-org:device:Basic:1", physicalgraph.device.Protocol.LAN))
}
def configured() {
}
def buttonConfigured(idx) {
return settings["lights_$idx"]
}
def isConfigured(){
if(getChildDevices().size() > 0) return true else return false
}
def isVirtualConfigured(did){
def foundDevice = false
getChildDevices().each {
if(it.deviceNetworkId != null){
if(it.deviceNetworkId.startsWith("${did}/")) foundDevice = true
}
}
return foundDevice
}
private virtualCreated(number) {
if (getChildDevice(getDeviceID(number))) {
return true
} else {
return false
}
}
private getDeviceID(number) {
return "${state.currentDeviceId}/${app.id}/${number}"
}
def installed() {
initialize()
}
def updated() {
unsubscribe()
unschedule()
initialize()
}
def initialize() {
ssdpSubscribe()
runEvery5Minutes("ssdpDiscover")
}
void ssdpSubscribe() {
subscribe(location, "ssdpTerm.urn:schemas-upnp-org:device:Basic:1", ssdpHandler)
}
void ssdpDiscover() {
sendHubCommand(new physicalgraph.device.HubAction("lan discovery urn:schemas-upnp-org:device:Basic:1", physicalgraph.device.Protocol.LAN))
}
def ssdpHandler(evt) {
def description = evt.description
def hub = evt?.hubId
def parsedEvent = parseLanMessage(description)
parsedEvent << ["hub":hub]
def devices = getDevices()
String ssdpUSN = parsedEvent.ssdpUSN.toString()
if (devices."${ssdpUSN}") {
def d = devices."${ssdpUSN}"
def child = getChildDevice(parsedEvent.mac)
def childIP
def childPort
if (child) {
childIP = child.getDeviceDataByName("ip")
childPort = child.getDeviceDataByName("port").toString()
log.debug "Device data: ($childIP:$childPort) - reporting data: (${convertHexToIP(parsedEvent.networkAddress)}:${convertHexToInt(parsedEvent.deviceAddress)})."
if(childIP != convertHexToIP(parsedEvent.networkAddress) || childPort != convertHexToInt(parsedEvent.deviceAddress).toString()){
log.debug "Device data (${child.getDeviceDataByName("ip")}) does not match what it is reporting(${convertHexToIP(parsedEvent.networkAddress)}). Attempting to update."
child.sync(convertHexToIP(parsedEvent.networkAddress), convertHexToInt(parsedEvent.deviceAddress).toString())
}
}
if (d.networkAddress != parsedEvent.networkAddress || d.deviceAddress != parsedEvent.deviceAddress) {
d.networkAddress = parsedEvent.networkAddress
d.deviceAddress = parsedEvent.deviceAddress
}
} else {
devices << ["${ssdpUSN}": parsedEvent]
}
}
void verifyDevices() {
def devices = getDevices().findAll { it?.value?.verified != true }
devices.each {
def ip = convertHexToIP(it.value.networkAddress)
def port = convertHexToInt(it.value.deviceAddress)
String host = "${ip}:${port}"
sendHubCommand(new physicalgraph.device.HubAction("""GET ${it.value.ssdpPath} HTTP/1.1\r\nHOST: $host\r\n\r\n""", physicalgraph.device.Protocol.LAN, host, [callback: deviceDescriptionHandler]))
}
}
def getDevices() {
state.devices = state.devices ?: [:]
}
void deviceDescriptionHandler(physicalgraph.device.HubResponse hubResponse) {
log.trace "description.xml response (application/xml)"
def body = hubResponse.xml
if (body?.device?.modelName?.text().startsWith("Sonoff Wifi Switch")) {
def devices = getDevices()
def device = devices.find {it?.key?.contains(body?.device?.UDN?.text())}
if (device) {
device.value << [name:body?.device?.friendlyName?.text() + " (" + convertHexToIP(hubResponse.ip) + ")", serialNumber:body?.device?.serialNumber?.text(), verified: true]
} else {
log.error "/description.xml returned a device that didn't exist"
}
}
}
def addDevices() {
def devices = getDevices()
def sectionText = ""
selectedDevices.each { dni ->bridgeLinking
def selectedDevice = devices.find { it.value.mac == dni }
def d
if (selectedDevice) {
d = getChildDevices()?.find {
it.deviceNetworkId == selectedDevice.value.mac
}
}
if (!d) {
log.debug "Creating Sonoff Switch with dni: ${selectedDevice.value.mac}"
log.debug Integer.parseInt(selectedDevice.value.deviceAddress,16)
addChildDevice("erocm123", "Sonoff Wifi Switch", selectedDevice.value.mac, selectedDevice?.value.hub, [
"label": selectedDevice?.value?.name ?: "Sonoff Wifi Switch",
"data": [
"mac": selectedDevice.value.mac,
"ip": convertHexToIP(selectedDevice.value.networkAddress),
"port": "" + Integer.parseInt(selectedDevice.value.deviceAddress,16)
]
])
sectionText = sectionText + "Succesfully added Sonoff Wifi Switch with ip address ${convertHexToIP(selectedDevice.value.networkAddress)} \r\n"
}
}
log.debug sectionText
return dynamicPage(name:"addDevices", title:"Devices Added", nextPage:"mainPage", uninstall: true) {
if(sectionText != ""){
section("Add Sonoff Results:") {
paragraph sectionText
}
}else{
section("No devices added") {
paragraph "All selected devices have previously been added"
}
}
}
}
def uninstalled() {
unsubscribe()
getChildDevices().each {
deleteChildDevice(it.deviceNetworkId)
}
}
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)
}
def log(message){
}