mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-23 05:10:50 +00:00
MSA-1488: mu home automation
This commit is contained in:
348
smartapps/erocm123/sonoff-connect.src/sonoff-connect.groovy
Normal file
348
smartapps/erocm123/sonoff-connect.src/sonoff-connect.groovy
Normal 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){
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user