mirror of
https://github.com/mtan93/SmartThingsPublic.git
synced 2026-03-09 05:11:52 +00:00
MSA-2069: home auto
This commit is contained in:
@@ -0,0 +1,450 @@
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* Author: Eric Maycock (erocm123)
|
||||
* Date: 2016-06-02
|
||||
*/
|
||||
|
||||
import groovy.json.JsonSlurper
|
||||
import groovy.util.XmlSlurper
|
||||
|
||||
metadata {
|
||||
definition (name: "Sonoff Wifi Switch", namespace: "erocm123", author: "Eric Maycock") {
|
||||
capability "Actuator"
|
||||
capability "Switch"
|
||||
capability "Refresh"
|
||||
capability "Sensor"
|
||||
capability "Configuration"
|
||||
capability "Health Check"
|
||||
|
||||
command "reboot"
|
||||
|
||||
attribute "needUpdate", "string"
|
||||
}
|
||||
|
||||
simulator {
|
||||
}
|
||||
|
||||
preferences {
|
||||
input description: "Once you change values on this page, the corner of the \"configuration\" icon will change orange until all configuration parameters are updated.", title: "Settings", displayDuringSetup: false, type: "paragraph", element: "paragraph"
|
||||
generate_preferences(configuration_model())
|
||||
}
|
||||
|
||||
tiles (scale: 2){
|
||||
multiAttributeTile(name:"switch", type: "generic", width: 6, height: 4, canChangeIcon: true){
|
||||
tileAttribute ("device.switch", key: "PRIMARY_CONTROL") {
|
||||
attributeState "on", label:'${name}', action:"switch.off", backgroundColor:"#00a0dc", icon: "st.switches.switch.on", nextState:"turningOff"
|
||||
attributeState "off", label:'${name}', action:"switch.on", backgroundColor:"#ffffff", icon: "st.switches.switch.off", nextState:"turningOn"
|
||||
attributeState "turningOn", label:'${name}', action:"switch.off", backgroundColor:"#00a0dc", icon: "st.switches.switch.off", nextState:"turningOff"
|
||||
attributeState "turningOff", label:'${name}', action:"switch.on", backgroundColor:"#ffffff", icon: "st.switches.switch.on", 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.needUpdate", inactiveLabel: false, decoration: "flat", width: 2, height: 2) {
|
||||
state "NO" , label:'', action:"configuration.configure", icon:"st.secondary.configure"
|
||||
state "YES", label:'', action:"configuration.configure", icon:"https://github.com/erocm123/SmartThingsPublic/raw/master/devicetypes/erocm123/qubino-flush-1d-relay.src/configure@2x.png"
|
||||
}
|
||||
standardTile("reboot", "device.reboot", decoration: "flat", height: 2, width: 2, inactiveLabel: false) {
|
||||
state "default", label:"Reboot", action:"reboot", icon:"", backgroundColor:"#ffffff"
|
||||
}
|
||||
valueTile("ip", "ip", width: 2, height: 1) {
|
||||
state "ip", label:'IP Address\r\n${currentValue}'
|
||||
}
|
||||
valueTile("uptime", "uptime", width: 2, height: 1) {
|
||||
state "uptime", label:'Uptime ${currentValue}'
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
main(["switch"])
|
||||
details(["switch",
|
||||
"refresh","configure","reboot",
|
||||
"ip", "uptime"])
|
||||
}
|
||||
|
||||
def installed() {
|
||||
log.debug "installed()"
|
||||
configure()
|
||||
}
|
||||
|
||||
def configure() {
|
||||
logging("configure()", 1)
|
||||
def cmds = []
|
||||
cmds = update_needed_settings()
|
||||
if (cmds != []) cmds
|
||||
}
|
||||
|
||||
def updated()
|
||||
{
|
||||
logging("updated()", 1)
|
||||
def cmds = []
|
||||
cmds = update_needed_settings()
|
||||
sendEvent(name: "checkInterval", value: 2 * 15 * 60 + 2 * 60, displayed: false, data: [protocol: "lan", hubHardwareId: device.hub.hardwareID])
|
||||
sendEvent(name:"needUpdate", value: device.currentValue("needUpdate"), displayed:false, isStateChange: true)
|
||||
if (cmds != []) response(cmds)
|
||||
}
|
||||
|
||||
private def logging(message, level) {
|
||||
if (logLevel != "0"){
|
||||
switch (logLevel) {
|
||||
case "1":
|
||||
if (level > 1)
|
||||
log.debug "$message"
|
||||
break
|
||||
case "99":
|
||||
log.debug "$message"
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def parse(description) {
|
||||
//log.debug "Parsing: ${description}"
|
||||
def events = []
|
||||
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("type")) {
|
||||
if (result.type == "configuration")
|
||||
events << update_current_properties(result)
|
||||
}
|
||||
if (result.containsKey("power")) {
|
||||
events << createEvent(name: "switch", value: result.power)
|
||||
}
|
||||
if (result.containsKey("uptime")) {
|
||||
events << createEvent(name: "uptime", value: result.uptime, displayed: false)
|
||||
}
|
||||
} else {
|
||||
//log.debug "Response is not JSON: $body"
|
||||
}
|
||||
}
|
||||
|
||||
if (!device.currentValue("ip") || (device.currentValue("ip") != getDataValue("ip"))) events << createEvent(name: 'ip', value: getDataValue("ip"))
|
||||
|
||||
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()):""]
|
||||
}
|
||||
}
|
||||
|
||||
def on() {
|
||||
log.debug "on()"
|
||||
def cmds = []
|
||||
cmds << getAction("/on")
|
||||
return cmds
|
||||
}
|
||||
|
||||
def off() {
|
||||
log.debug "off()"
|
||||
def cmds = []
|
||||
cmds << getAction("/off")
|
||||
return cmds
|
||||
}
|
||||
|
||||
def refresh() {
|
||||
log.debug "refresh()"
|
||||
def cmds = []
|
||||
cmds << getAction("/status")
|
||||
return cmds
|
||||
}
|
||||
|
||||
def ping() {
|
||||
log.debug "ping()"
|
||||
refresh()
|
||||
}
|
||||
|
||||
private getAction(uri){
|
||||
updateDNI()
|
||||
def userpass
|
||||
//log.debug uri
|
||||
if(password != null && password != "")
|
||||
userpass = encodeCredentials("admin", password)
|
||||
|
||||
def headers = getHeader(userpass)
|
||||
|
||||
def hubAction = new physicalgraph.device.HubAction(
|
||||
method: "GET",
|
||||
path: uri,
|
||||
headers: headers
|
||||
)
|
||||
return hubAction
|
||||
}
|
||||
|
||||
private postAction(uri, data){
|
||||
updateDNI()
|
||||
|
||||
def userpass
|
||||
|
||||
if(password != null && password != "")
|
||||
userpass = encodeCredentials("admin", password)
|
||||
|
||||
def headers = getHeader(userpass)
|
||||
|
||||
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 (state.dni != null && state.dni != "" && device.deviceNetworkId != state.dni) {
|
||||
device.deviceNetworkId = state.dni
|
||||
}
|
||||
}
|
||||
|
||||
private getHostAddress() {
|
||||
if (override == "true" && ip != null && ip != ""){
|
||||
return "${ip}:80"
|
||||
}
|
||||
else if(getDeviceDataByName("ip") && getDeviceDataByName("port")){
|
||||
return "${getDeviceDataByName("ip")}:${getDeviceDataByName("port")}"
|
||||
}else{
|
||||
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 encodeCredentials(username, password){
|
||||
def userpassascii = "${username}:${password}"
|
||||
def userpass = "Basic " + userpassascii.encodeAsBase64().toString()
|
||||
return userpass
|
||||
}
|
||||
|
||||
private getHeader(userpass = null){
|
||||
def headers = [:]
|
||||
headers.put("Host", getHostAddress())
|
||||
headers.put("Content-Type", "application/x-www-form-urlencoded")
|
||||
if (userpass != null)
|
||||
headers.put("Authorization", userpass)
|
||||
return headers
|
||||
}
|
||||
|
||||
def reboot() {
|
||||
log.debug "reboot()"
|
||||
def uri = "/reboot"
|
||||
getAction(uri)
|
||||
}
|
||||
|
||||
def sync(ip, port) {
|
||||
def existingIp = getDataValue("ip")
|
||||
def existingPort = getDataValue("port")
|
||||
if (ip && ip != existingIp) {
|
||||
updateDataValue("ip", ip)
|
||||
sendEvent(name: 'ip', value: ip)
|
||||
}
|
||||
if (port && port != existingPort) {
|
||||
updateDataValue("port", port)
|
||||
}
|
||||
}
|
||||
|
||||
def generate_preferences(configuration_model)
|
||||
{
|
||||
def configuration = parseXml(configuration_model)
|
||||
|
||||
configuration.Value.each
|
||||
{
|
||||
if(it.@hidden != "true" && it.@disabled != "true"){
|
||||
switch(it.@type)
|
||||
{
|
||||
case ["number"]:
|
||||
input "${it.@index}", "number",
|
||||
title:"${it.@label}\n" + "${it.Help}",
|
||||
range: "${it.@min}..${it.@max}",
|
||||
defaultValue: "${it.@value}",
|
||||
displayDuringSetup: "${it.@displayDuringSetup}"
|
||||
break
|
||||
case "list":
|
||||
def items = []
|
||||
it.Item.each { items << ["${it.@value}":"${it.@label}"] }
|
||||
input "${it.@index}", "enum",
|
||||
title:"${it.@label}\n" + "${it.Help}",
|
||||
defaultValue: "${it.@value}",
|
||||
displayDuringSetup: "${it.@displayDuringSetup}",
|
||||
options: items
|
||||
break
|
||||
case ["password"]:
|
||||
input "${it.@index}", "password",
|
||||
title:"${it.@label}\n" + "${it.Help}",
|
||||
displayDuringSetup: "${it.@displayDuringSetup}"
|
||||
break
|
||||
case "decimal":
|
||||
input "${it.@index}", "decimal",
|
||||
title:"${it.@label}\n" + "${it.Help}",
|
||||
range: "${it.@min}..${it.@max}",
|
||||
defaultValue: "${it.@value}",
|
||||
displayDuringSetup: "${it.@displayDuringSetup}"
|
||||
break
|
||||
case "boolean":
|
||||
input "${it.@index}", "boolean",
|
||||
title:"${it.@label}\n" + "${it.Help}",
|
||||
defaultValue: "${it.@value}",
|
||||
displayDuringSetup: "${it.@displayDuringSetup}"
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Code has elements from other community source @CyrilPeponnet (Z-Wave Parameter Sync). */
|
||||
|
||||
def update_current_properties(cmd)
|
||||
{
|
||||
def currentProperties = state.currentProperties ?: [:]
|
||||
currentProperties."${cmd.name}" = cmd.value
|
||||
|
||||
if (settings."${cmd.name}" != null)
|
||||
{
|
||||
if (settings."${cmd.name}".toString() == cmd.value)
|
||||
{
|
||||
sendEvent(name:"needUpdate", value:"NO", displayed:false, isStateChange: true)
|
||||
}
|
||||
else
|
||||
{
|
||||
sendEvent(name:"needUpdate", value:"YES", displayed:false, isStateChange: true)
|
||||
}
|
||||
}
|
||||
state.currentProperties = currentProperties
|
||||
}
|
||||
|
||||
|
||||
def update_needed_settings()
|
||||
{
|
||||
def cmds = []
|
||||
def currentProperties = state.currentProperties ?: [:]
|
||||
|
||||
def configuration = parseXml(configuration_model())
|
||||
def isUpdateNeeded = "NO"
|
||||
|
||||
cmds << getAction("/configSet?name=haip&value=${device.hub.getDataValue("localIP")}")
|
||||
cmds << getAction("/configSet?name=haport&value=${device.hub.getDataValue("localSrvPortTCP")}")
|
||||
|
||||
configuration.Value.each
|
||||
{
|
||||
if ("${it.@setting_type}" == "lan" && it.@disabled != "true"){
|
||||
if (currentProperties."${it.@index}" == null)
|
||||
{
|
||||
if (it.@setonly == "true"){
|
||||
logging("Setting ${it.@index} will be updated to ${it.@value}", 2)
|
||||
cmds << getAction("/configSet?name=${it.@index}&value=${it.@value}")
|
||||
} else {
|
||||
isUpdateNeeded = "YES"
|
||||
logging("Current value of setting ${it.@index} is unknown", 2)
|
||||
cmds << getAction("/configGet?name=${it.@index}")
|
||||
}
|
||||
}
|
||||
else if ((settings."${it.@index}" != null || it.@hidden == "true") && currentProperties."${it.@index}" != (settings."${it.@index}"? settings."${it.@index}".toString() : "${it.@value}"))
|
||||
{
|
||||
isUpdateNeeded = "YES"
|
||||
logging("Setting ${it.@index} will be updated to ${settings."${it.@index}"}", 2)
|
||||
cmds << getAction("/configSet?name=${it.@index}&value=${settings."${it.@index}"}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sendEvent(name:"needUpdate", value: isUpdateNeeded, displayed:false, isStateChange: true)
|
||||
return cmds
|
||||
}
|
||||
|
||||
def configuration_model()
|
||||
{
|
||||
'''
|
||||
<configuration>
|
||||
<Value type="password" byteSize="1" index="password" label="Password" min="" max="" value="" setting_type="preference" fw="">
|
||||
<Help>
|
||||
</Help>
|
||||
</Value>
|
||||
<Value type="list" byteSize="1" index="pos" label="Boot Up State" min="0" max="2" value="0" setting_type="lan" fw="">
|
||||
<Help>
|
||||
Default: Off
|
||||
</Help>
|
||||
<Item label="Off" value="0" />
|
||||
<Item label="On" value="1" />
|
||||
<Item label="Previous" value="2" />
|
||||
</Value>
|
||||
<Value type="number" byteSize="1" index="autooff" label="Auto Off" min="0" max="65536" value="0" setting_type="lan" fw="">
|
||||
<Help>
|
||||
Automatically turn the switch off after this many seconds.
|
||||
Range: 0 to 65536
|
||||
Default: 0 (Disabled)
|
||||
</Help>
|
||||
</Value>
|
||||
<Value type="list" byteSize="1" index="switchtype" label="External Switch Type" min="0" max="1" value="0" setting_type="lan" fw="">
|
||||
<Help>
|
||||
If a switch is attached to GPIO 14.
|
||||
Default: Momentary
|
||||
</Help>
|
||||
<Item label="Momentary" value="0" />
|
||||
<Item label="Toggle" value="1" />
|
||||
</Value>
|
||||
<Value type="list" index="logLevel" label="Debug Logging Level?" value="0" setting_type="preference" fw="">
|
||||
<Help>
|
||||
</Help>
|
||||
<Item label="None" value="0" />
|
||||
<Item label="Reports" value="1" />
|
||||
<Item label="All" value="99" />
|
||||
</Value>
|
||||
</configuration>
|
||||
'''
|
||||
}
|
||||
409
smartapps/erocm123/sonoff-connect.src/sonoff-connect.groovy
Normal file
409
smartapps/erocm123/sonoff-connect.src/sonoff-connect.groovy
Normal file
@@ -0,0 +1,409 @@
|
||||
/**
|
||||
* 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: "manuallyAdd")
|
||||
page(name: "manuallyAddConfirm")
|
||||
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 Devices", description:""
|
||||
href "manuallyAdd", title:"Manually Add Device", 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){
|
||||
if (params?.did || params?.params?.did) {
|
||||
if (params.did) {
|
||||
state.currentDeviceId = params.did
|
||||
state.currentDisplayName = getChildDevice(params.did)?.displayName
|
||||
} else {
|
||||
state.currentDeviceId = params.params.did
|
||||
state.currentDisplayName = getChildDevice(params.params.did)?.displayName
|
||||
}
|
||||
}
|
||||
if (getChildDevice(state.currentDeviceId) != null) getChildDevice(state.currentDeviceId).configure()
|
||||
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"
|
||||
}
|
||||
section {
|
||||
href "deletePDevice", title:"Delete $state.currentDisplayName", description: ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def manuallyAdd(){
|
||||
dynamicPage(name: "manuallyAdd", title: "Manually add a Sonoff device", nextPage: "manuallyAddConfirm") {
|
||||
section {
|
||||
paragraph "This process will manually create a Sonoff device based on the entered IP address. The SmartApp needs to then communicate with the device to obtain additional information from it. Make sure the device is on and connected to your wifi network."
|
||||
input "deviceType", "enum", title:"Device Type", description: "", required: false, options: ["Sonoff Wifi Switch","Sonoff TH Wifi Switch","Sonoff POW Wifi Switch","Sonoff Dual Wifi Switch","Sonoff 4CH Wifi Switch"]
|
||||
input "ipAddress", "text", title:"IP Address", description: "", required: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def manuallyAddConfirm(){
|
||||
if ( ipAddress =~ /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/) {
|
||||
log.debug "Creating Sonoff Wifi Switch with dni: ${convertIPtoHex(ipAddress)}:${convertPortToHex("80")}"
|
||||
addChildDevice("erocm123", deviceType ? deviceType : "Sonoff Wifi Switch", "${convertIPtoHex(ipAddress)}:${convertPortToHex("80")}", location.hubs[0].id, [
|
||||
"label": (deviceType ? deviceType : "Sonoff Wifi Switch") + " (${ipAddress})",
|
||||
"data": [
|
||||
"ip": ipAddress,
|
||||
"port": "80"
|
||||
]
|
||||
])
|
||||
|
||||
app.updateSetting("ipAddress", "")
|
||||
|
||||
dynamicPage(name: "manuallyAddConfirm", title: "Manually add a Sonoff device", nextPage: "mainPage") {
|
||||
section {
|
||||
paragraph "The device has been added. Press next to return to the main page."
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dynamicPage(name: "manuallyAddConfirm", title: "Manually add a Sonoff device", nextPage: "mainPage") {
|
||||
section {
|
||||
paragraph "The entered ip address is not valid. Please try again."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def deletePDevice(){
|
||||
try {
|
||||
unsubscribe()
|
||||
deleteChildDevice(state.currentDeviceId)
|
||||
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(){
|
||||
def thisDevice = getChildDevice(state.currentDeviceId)
|
||||
thisDevice.label = settings["${state.currentDeviceId}_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
|
||||
log.debug body?.device?.friendlyName?.text()
|
||||
if (body?.device?.modelName?.text().startsWith("Sonoff")) {
|
||||
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 selectedDevice
|
||||
log.debug "Creating Sonoff Switch with dni: ${selectedDevice.value.mac}"
|
||||
|
||||
def deviceHandlerName
|
||||
if (selectedDevice?.value?.name?.startsWith("Sonoff TH"))
|
||||
deviceHandlerName = "Sonoff TH Wifi Switch"
|
||||
else if (selectedDevice?.value?.name?.startsWith("Sonoff POW"))
|
||||
deviceHandlerName = "Sonoff POW Wifi Switch"
|
||||
else if (selectedDevice?.value?.name?.startsWith("Sonoff Dual"))
|
||||
deviceHandlerName = "Sonoff Dual Wifi Switch"
|
||||
else if (selectedDevice?.value?.name?.startsWith("Sonoff 4CH"))
|
||||
deviceHandlerName = "Sonoff 4CH Wifi Switch"
|
||||
else
|
||||
deviceHandlerName = "Sonoff Wifi Switch"
|
||||
def newDevice = addChildDevice("erocm123", deviceHandlerName, 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 device 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)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user