diff --git a/devicetypes/BrettSheleski/sonoff-tasmota-discovery-device.src/sonoff-tasmota-discovery-device.groovy b/devicetypes/BrettSheleski/sonoff-tasmota-discovery-device.src/sonoff-tasmota-discovery-device.groovy index 3fc1c9af34a..a2d30a68c87 100644 --- a/devicetypes/BrettSheleski/sonoff-tasmota-discovery-device.src/sonoff-tasmota-discovery-device.groovy +++ b/devicetypes/BrettSheleski/sonoff-tasmota-discovery-device.src/sonoff-tasmota-discovery-device.groovy @@ -4,6 +4,17 @@ metadata { command "discover" } + + tiles(scale: 2) { + + + standardTile("discover", "discover", width: 2, height: 2) { + state "discover", label: 'Discover', action: "discover", icon: "st.secondary.refresh", backgroundColor: "#ffffff" + } + + + main "discover" + } } def parse(String description) { @@ -31,7 +42,7 @@ def parse(String description) { def discover(){ log.debug "DISCOVER" - return sendCommand("Status" , "0") + sendCommand("Status" , "0") } @@ -44,6 +55,8 @@ private def sendCommand(String command, String payload){ def hosthex = convertIPtoHex(ipAddress); def porthex = convertPortToHex(port); + def deviceNetworkId = "$hosthex:$porthex" + def path = "/cm" if (payload){ @@ -61,15 +74,10 @@ private def sendCommand(String command, String payload){ } } - def result = new physicalgraph.device.HubAction( - method: "GET", - path: path, - headers: [ - HOST: "${ipAddress}:${port}" - ] - ) + sendHubCommand(new physicalgraph.device.HubAction("""GET $path HTTP/1.1 +HOST: ${ipAddress}:${port} - return result +""", physicalgraph.device.Protocol.LAN, "${deviceNetworkId}")) } private String convertIPtoHex(ipAddress) { diff --git a/smartapps/BrettSheleski/sonoff-tasmota.src/sonoff-tasmota.groovy b/smartapps/BrettSheleski/sonoff-tasmota.src/sonoff-tasmota.groovy index 40f3caabab2..1229787940a 100644 --- a/smartapps/BrettSheleski/sonoff-tasmota.src/sonoff-tasmota.groovy +++ b/smartapps/BrettSheleski/sonoff-tasmota.src/sonoff-tasmota.groovy @@ -10,16 +10,23 @@ definition( ) preferences { - section("Sonoff Host") { - input "ipAddress", "string", title: "IP Address", required: true - } - - section("Authentication") { - input(name: "username", type: "string", title: "Username", description: "Username", displayDuringSetup: false, required: false) - input(name: "password", type: "password", title: "Password", description: "Password", displayDuringSetup: false, required: false) + page("mainPage", "Main Configuration"){ + section("Sonoff Host") { + input "ipAddress", "string", title: "IP Address", required: true + } + + section("Authentication") { + input(name: "username", type: "string", title: "Username", description: "Username", displayDuringSetup: true, required: false) + input(name: "password", type: "password", title: "Password", description: "Password", displayDuringSetup: true, required: false) + } + + section("Module"){ + input(name: "moduleType", type: "enum", title: "Module Type", description: "Module Type", displayDuringSetup: true, required: false, refreshInterval: 5, options : getModuleTypesEnum()) + } } } + def installed() { log.debug "Installed with settings: ${settings}" initialize() @@ -41,56 +48,184 @@ private removeChildDevices(delete) { } def initialize(){ + sendCommand("Status", "0", "discoverCompleted") +} + +def discoverCompleted(physicalgraph.device.HubResponse hubResponse){ + + log.debug "Discovery Completed" + + def body = hubResponse.body; + def allSettings = [:]; + + + if (body){ + //log.debug "RESPONSE: ${body}"; + + def slurper = new groovy.json.JsonSlurper(); + + def settingsLine; + + + def lines = body.split('\n'); + + lines.each { + settingsLine = slurper.parseText(it.substring(it.indexOf('=') + 1)); + allSettings << settingsLine; + } + + def json = new groovy.json.JsonBuilder(allSettings).toPrettyString(); + log.debug "SETTINGS : ${json}" + + state.settings = allSettings; + + setModuleTypeById(allSettings.Status.Module); + + initializeCompleted(allSettings); + } + else{ + log.debug "NO BODY!" + } +} + +def setModuleTypeById(int id){ + + log.debug "Trying to set module for type ID : $id"; + + getModuleTypesMap().each{key, value -> + if (key == "$id"){ + log.debug "Setting Module Type to ${value.name}" + settings.moduleType = value.name + } + } +} + +def getModuleTypesEnum(){ + + def types = []; + + getModuleTypesMap().each{key, value -> types << value.name} + + return types; +} + +def getModuleTypesMap(){ + def deviceTypes = [:]; + + deviceTypes["1"] = ["name" : "Sonoff Basic"] - // SmartApps cannot create LAN requests - // Create a device that is specifically used to discover Sonoff device settings + deviceTypes["2"] = ["name" : "Sonoff RF"] - // get (or create) the discovery device - def discoveryDevice = getDiscoveryDevice() + deviceTypes["18"] = ["name" : "WeMos D1 mini"] - // tell the discovery device to make a - discoveryDevice.discover(); + deviceTypes["25"] = ["name" : "Sonoff Bridge"] + + return deviceTypes; } -def discoverCompleted(Map deviceSettings){ - log.debug "$deviceSettings"; - deleteDiscoveryDevice(); +def initializeCompleted(Map options = null){ + + def deviceTypesMap = getModuleTypesMap(); + + if (options == null) + { + options = state.settings; + } + if (options?.Status?.Module == 1){ // Sonoff Basic + initializeSonoffBasic(options); + } + else if (options?.Status?.Module == 20){ // Sonoff RF Bridge + initializeSonoffRfBridge(options); + } } +def initializeSonoffBasic(Map options){ +} -def getDiscoveryDevice(){ +def initializeSonoffRfBridge(Map options){ + def children = getChildDevices() def namespace = app.namespace - def deviceName = "Sonoff-Tasmota Discovery Device" + def deviceName = "Sonoff-Tasmota RF Bridge Button" def theHubId = location.hubs[0].id - def deviceId = "${app.id}-discoveryDevice" - def childDevice = getChildDevices().find { - it.deviceNetworkId == deviceId - } + + for ( i in 1..16 ) + { + def deviceId = "${app.id}-key${i}" + def childDevice = children.find { + it.deviceNetworkId == deviceId + } + - if (!childDevice){ - def deviceMap = [completedSetup: false] + if (childDevice){ + log.debug "FOUND child device found for ${childDevice.deviceNetworkId}" + children.remove(childDevice) + } + else{ + def deviceMap = [completedSetup: false] - deviceMap['name'] = app.label + " - Device Discovery"; - deviceMap['label'] = deviceMap['name']; + deviceMap['name'] = app.label + " - Key $i"; + deviceMap['label'] = deviceMap['name']; - childDevice = addChildDevice(namespace, deviceName, deviceId, theHubId, deviceMap) - } + childDevice = addChildDevice(namespace, deviceName, deviceId, theHubId, deviceMap) + } + + childDevice.initChild([keyNumber : i]); - return childDevice + // remove any unused child Devices + removeChildDevices(children); + } } -def deleteDiscoveryDevice(){ - def deviceId = "${app.id}-discoveryDevice" +private def sendCommand(String command, String payload, String callback = null){ - def childDevice = getChildDevices().find { - it.deviceNetworkId == deviceId + log.debug "sendCommand(${command}:${payload})" + + def ipAddress = ipAddress; + def port = 80; + def hosthex = convertIPtoHex(ipAddress); + def porthex = convertPortToHex(port); + + def dni = "$hosthex:$porthex"; + + def deviceNetworkId = "$hosthex:$porthex" + + def path = "/cm" + + def options = [callback : callback]; + def headers = ["HOST": "$ipAddress:$port"] + def query = [:] + + if (payload){ + path += "?cmnd=${command}%20${payload}" + } + else{ + path += "?cmnd=${command}" } - if (childDevice) - { - deleteChildDevice(childDevice.deviceNetworkId) + if (username){ + path += "&user=${username}" + + if (password){ + path += "&password=${password}" + } } + + def params = [path : path, method : "GET", protocol : physicalgraph.device.Protocol.LAN, headers : headers, query : query] + + def hubAction = new physicalgraph.device.HubAction(params, dni, options) + + sendHubCommand(hubAction) } + +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 +} \ No newline at end of file