/** * Tasmota IR Button Controller * For use with Sofabaton X1 and other Universal IR remotes * * Copyright 2024 Gassgs * * * Based on the Hubitat community driver httpGetSwitch * https://raw.githubusercontent.com/hubitat/HubitatPublic/master/examples/drivers/httpGetSwitch.groovy * * 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. * * Change History: * * V1.0.0 01-17-2024 first run * V1.1.0 01-27-2024 cleanup added push cmd method * V1.2.0 02-05-2024 expanded to support up to 30 codes * V1.3.0 02-07-2024 expanded to support up to 40 codes (needed more) */ def driverVer() { return "1.3" } import groovy.json.JsonSlurper import groovy.json.JsonOutput metadata { definition (name: "Tasmota IR Button Controller", namespace: "Gassgs", author: "Gary G") { capability "PushableButton" capability "Actuator" capability "Refresh" capability "Sensor" attribute "wifi","string" } } preferences { input name: "deviceIp",type: "string", title: "Tasmota IR Device IP Address", required: true input name: "hubIp",type: "string", title: "Hubitat Hub IP Address", required: true input (name: "numberOfButtons", type: "enum", title: "Number Of Buttons", defaultValue: "10", options: ["10", "20", "30","40"]) input name: "button1",type: "string", title: "Button 1 IR Data Value", required: false, defaultValue: "0" input name: "button2",type: "string", title: "Button 2 IR Data Value", required: false, defaultValue: "0" input name: "button3",type: "string", title: "Button 3 IR Data Value", required: false, defaultValue: "0" input name: "button4",type: "string", title: "Button 4 IR Data Value", required: false, defaultValue: "0" input name: "button5",type: "string", title: "Button 5 IR Data Value", required: false, defaultValue: "0" input name: "button6",type: "string", title: "Button 6 IR Data Value", required: false, defaultValue: "0" input name: "button7",type: "string", title: "Button 7 IR Data Value", required: false, defaultValue: "0" input name: "button8",type: "string", title: "Button 8 IR Data Value", required: false, defaultValue: "0" input name: "button9",type: "string", title: "Button 9 IR Data Value", required: false, defaultValue: "0" input name: "button10",type: "string", title: "Button 10 IR Data Value", required: false, defaultValue: "0" if (numberOfButtons == "20" || numberOfButtons == "30" || numberOfButtons == "40"){ input name: "button11",type: "string", title: "Button 11 IR Data Value", required: false, defaultValue: "0" input name: "button12",type: "string", title: "Button 12 IR Data Value", required: false, defaultValue: "0" input name: "button13",type: "string", title: "Button 13 IR Data Value", required: false, defaultValue: "0" input name: "button14",type: "string", title: "Button 14 IR Data Value", required: false, defaultValue: "0" input name: "button15",type: "string", title: "Button 15 IR Data Value", required: false, defaultValue: "0" input name: "button16",type: "string", title: "Button 16 IR Data Value", required: false, defaultValue: "0" input name: "button17",type: "string", title: "Button 17 IR Data Value", required: false, defaultValue: "0" input name: "button18",type: "string", title: "Button 18 IR Data Value", required: false, defaultValue: "0" input name: "button19",type: "string", title: "Button 19 IR Data Value", required: false, defaultValue: "0" input name: "button20",type: "string", title: "Button 20 IR Data Value", required: false, defaultValue: "0" } if (numberOfButtons == "30" || numberOfButtons == "40"){ input name: "button21",type: "string", title: "Button 21 IR Data Value", required: false, defaultValue: "0" input name: "button22",type: "string", title: "Button 22 IR Data Value", required: false, defaultValue: "0" input name: "button23",type: "string", title: "Button 23 IR Data Value", required: false, defaultValue: "0" input name: "button24",type: "string", title: "Button 24 IR Data Value", required: false, defaultValue: "0" input name: "button25",type: "string", title: "Button 25 IR Data Value", required: false, defaultValue: "0" input name: "button26",type: "string", title: "Button 26 IR Data Value", required: false, defaultValue: "0" input name: "button27",type: "string", title: "Button 27 IR Data Value", required: false, defaultValue: "0" input name: "button28",type: "string", title: "Button 28 IR Data Value", required: false, defaultValue: "0" input name: "button29",type: "string", title: "Button 29 IR Data Value", required: false, defaultValue: "0" input name: "button30",type: "string", title: "Button 30 IR Data Value", required: false, defaultValue: "0" } if (numberOfButtons == "40"){ input name: "button31",type: "string", title: "Button 31 IR Data Value", required: false, defaultValue: "0" input name: "button32",type: "string", title: "Button 32 IR Data Value", required: false, defaultValue: "0" input name: "button33",type: "string", title: "Button 33 IR Data Value", required: false, defaultValue: "0" input name: "button34",type: "string", title: "Button 34 IR Data Value", required: false, defaultValue: "0" input name: "button35",type: "string", title: "Button 35 IR Data Value", required: false, defaultValue: "0" input name: "button36",type: "string", title: "Button 36 IR Data Value", required: false, defaultValue: "0" input name: "button37",type: "string", title: "Button 37 IR Data Value", required: false, defaultValue: "0" input name: "button38",type: "string", title: "Button 38 IR Data Value", required: false, defaultValue: "0" input name: "button39",type: "string", title: "Button 39 IR Data Value", required: false, defaultValue: "0" input name: "button40",type: "string", title: "Button 40 IR Data Value", required: false, defaultValue: "0" } input name: "refreshEnable",type: "bool", title: "Enable to Refresh every 30mins", defaultValue: true input name: "logInfo", type: "bool", title: "Enable info logging", defaultValue: true input name: "logEnable", type: "bool", title: "Enable debug logging", defaultValue: true } def logsOff() { log.warn "debug logging disabled..." device.updateSetting("logEnable", [value: "false", type: "bool"]) } def updated() { log.info "updated..." log.warn "debug logging is: ${logEnable == true}" state.DriverVersion=driverVer() if (refreshEnable){ runEvery30Minutes(refresh) if (logEnable) log.debug "refresh every 30 minutes scheduled" }else{ unschedule(refresh) if (logEnable) log.debug "refresh schedule canceled" } deviceSetup() syncSetup() if (logEnable) runIn(1800, logsOff) sendEvent(name:"numberOfButtons",value:"$numberOfButtons") } def deviceSetup(){ if (deviceIp){ try { httpGet("http://" + deviceIp + "/cm?cmnd=STATUS%200") { resp -> def json = (resp.data) if (json){ if (logEnable) log.debug "${json}" def macAddress = (json.StatusNET.Mac) def mac = macAddress.replace(":","") state.dni = mac as String if (logEnable) log.debug "Command Success response from Device" if (logEnable) log.debug "Mac Address $macAddress to DNI $mac" setDeviceNetworkId() def name = (json.Status.DeviceName) if (logEnable) log.debug "Device Name set to $name" device.name = "$name" }else{ if (logEnable) log.debug "Command -ERROR- response from Device- $json" } } } catch (Exception e) { log.warn "Call to on failed: ${e.message}" } } } void setDeviceNetworkId(){ if (state.dni != null && state.dni != device.deviceNetworkId) { device.deviceNetworkId = state.dni if (logEnable) log.debug "${state.dni as String} set as Device Network ID" } } def syncSetup(){ if (hubIp){ rule = "ON IrReceived#Data=" + button1 +" DO webquery http://"+ hubIp + ":39501/ POST 1 ENDON " + "ON IrReceived#Data=" + button2 +" DO webquery http://"+ hubIp + ":39501/ POST 2 ENDON " + "ON IrReceived#Data=" + button3 +" DO webquery http://"+ hubIp + ":39501/ POST 3 ENDON " + "ON IrReceived#Data=" + button4 +" DO webquery http://"+ hubIp + ":39501/ POST 4 ENDON " + "ON IrReceived#Data=" + button5 +" DO webquery http://"+ hubIp + ":39501/ POST 5 ENDON " + "ON IrReceived#Data=" + button6 +" DO webquery http://"+ hubIp + ":39501/ POST 6 ENDON " + "ON IrReceived#Data=" + button7 +" DO webquery http://"+ hubIp + ":39501/ POST 7 ENDON " + "ON IrReceived#Data=" + button8 +" DO webquery http://"+ hubIp + ":39501/ POST 8 ENDON " + "ON IrReceived#Data=" + button9 +" DO webquery http://"+ hubIp + ":39501/ POST 9 ENDON " + "ON IrReceived#Data=" + button10 +" DO webquery http://"+ hubIp + ":39501/ POST 10 ENDON " + "ON IrReceived#Data=" + button11 +" DO webquery http://"+ hubIp + ":39501/ POST 11 ENDON " + "ON IrReceived#Data=" + button12 +" DO webquery http://"+ hubIp + ":39501/ POST 12 ENDON " + "ON IrReceived#Data=" + button13 +" DO webquery http://"+ hubIp + ":39501/ POST 13 ENDON " ruleNow = rule.replaceAll("%","%25").replaceAll("#","%23").replaceAll("/","%2F").replaceAll(":","%3A").replaceAll(" ","%20") if (logEnable) log.debug "$ruleNow" try { httpGet("http://" + deviceIp + "/cm?cmnd=RULE1%20${ruleNow}") { resp -> def json = (resp.data) if (json){ if (logEnable) log.debug "Command Success response from Device - Setup Rule 1" }else{ if (logEnable) log.debug "Command -ERROR- response from Device- $json" } } } catch (Exception e) { log.warn "Call to on failed: ${e.message}" } } if (numberOfButtons == "20" || numberOfButtons == "30"|| numberOfButtons == "40"){ runIn(1,syncSetup2) } else{ runIn(2,turnOnRule) } } def syncSetup2(){ if (hubIp){ rule = "ON IrReceived#Data=" + button14 +" DO webquery http://"+ hubIp + ":39501/ POST 14 ENDON " + "ON IrReceived#Data=" + button15 +" DO webquery http://"+ hubIp + ":39501/ POST 15 ENDON " + "ON IrReceived#Data=" + button16 +" DO webquery http://"+ hubIp + ":39501/ POST 16 ENDON " + "ON IrReceived#Data=" + button17 +" DO webquery http://"+ hubIp + ":39501/ POST 17 ENDON " + "ON IrReceived#Data=" + button18 +" DO webquery http://"+ hubIp + ":39501/ POST 18 ENDON " + "ON IrReceived#Data=" + button19 +" DO webquery http://"+ hubIp + ":39501/ POST 19 ENDON " + "ON IrReceived#Data=" + button20 +" DO webquery http://"+ hubIp + ":39501/ POST 20 ENDON " + "ON IrReceived#Data=" + button21 +" DO webquery http://"+ hubIp + ":39501/ POST 21 ENDON " + "ON IrReceived#Data=" + button22 +" DO webquery http://"+ hubIp + ":39501/ POST 22 ENDON " + "ON IrReceived#Data=" + button23 +" DO webquery http://"+ hubIp + ":39501/ POST 23 ENDON " + "ON IrReceived#Data=" + button24 +" DO webquery http://"+ hubIp + ":39501/ POST 24 ENDON " + "ON IrReceived#Data=" + button25 +" DO webquery http://"+ hubIp + ":39501/ POST 25 ENDON " + "ON IrReceived#Data=" + button26 +" DO webquery http://"+ hubIp + ":39501/ POST 26 ENDON " ruleNow = rule.replaceAll("%","%25").replaceAll("#","%23").replaceAll("/","%2F").replaceAll(":","%3A").replaceAll(" ","%20") if (logEnable) log.debug "$ruleNow" try { httpGet("http://" + deviceIp + "/cm?cmnd=RULE2%20${ruleNow}") { resp -> def json = (resp.data) if (json){ if (logEnable) log.debug "Command Success response from Device - Setup Rule 2" }else{ if (logEnable) log.debug "Command -ERROR- response from Device- $json" } } } catch (Exception e) { log.warn "Call to on failed: ${e.message}" } } if (numberOfButtons == "30" || numberOfButtons == "40"){ runIn(1,syncSetup3) } else{ runIn(2,turnOnRule) } } def syncSetup3(){ if (hubIp){ rule = "ON IrReceived#Data=" + button27 +" DO webquery http://"+ hubIp + ":39501/ POST 27 ENDON " + "ON IrReceived#Data=" + button28 +" DO webquery http://"+ hubIp + ":39501/ POST 28 ENDON " + "ON IrReceived#Data=" + button29 +" DO webquery http://"+ hubIp + ":39501/ POST 29 ENDON " + "ON IrReceived#Data=" + button30 +" DO webquery http://"+ hubIp + ":39501/ POST 30 ENDON " + "ON IrReceived#Data=" + button31 +" DO webquery http://"+ hubIp + ":39501/ POST 31 ENDON " + "ON IrReceived#Data=" + button32 +" DO webquery http://"+ hubIp + ":39501/ POST 32 ENDON " + "ON IrReceived#Data=" + button33 +" DO webquery http://"+ hubIp + ":39501/ POST 33 ENDON " + "ON IrReceived#Data=" + button34 +" DO webquery http://"+ hubIp + ":39501/ POST 34 ENDON " + "ON IrReceived#Data=" + button35 +" DO webquery http://"+ hubIp + ":39501/ POST 35 ENDON " + "ON IrReceived#Data=" + button36 +" DO webquery http://"+ hubIp + ":39501/ POST 36 ENDON " + "ON IrReceived#Data=" + button37 +" DO webquery http://"+ hubIp + ":39501/ POST 37 ENDON " + "ON IrReceived#Data=" + button38 +" DO webquery http://"+ hubIp + ":39501/ POST 38 ENDON " + "ON IrReceived#Data=" + button39 +" DO webquery http://"+ hubIp + ":39501/ POST 39 ENDON " + "ON IrReceived#Data=" + button40 +" DO webquery http://"+ hubIp + ":39501/ POST 40 ENDON " ruleNow = rule.replaceAll("%","%25").replaceAll("#","%23").replaceAll("/","%2F").replaceAll(":","%3A").replaceAll(" ","%20") if (logEnable) log.debug "$ruleNow" try { httpGet("http://" + deviceIp + "/cm?cmnd=RULE3%20${ruleNow}") { resp -> def json = (resp.data) if (json){ if (logEnable) log.debug "Command Success response from Device - Setup Rule 3" }else{ if (logEnable) log.debug "Command -ERROR- response from Device- $json" } } } catch (Exception e) { log.warn "Call to on failed: ${e.message}" } } runIn(2,turnOnRule) } def turnOnRule(){ try { httpGet("http://" + deviceIp + "/cm?cmnd=RULE1%20ON") { resp -> def json = (resp.data) if (json){ if (logEnable) log.debug "Command Success response from Device - Rule 1 activated" }else{ if (logEnable) log.debug "Command -ERROR- response from Device- $json" } } } catch (Exception e) { log.warn "Call to on failed: ${e.message}" } if (numberOfButtons == "20" || numberOfButtons == "30" || numberOfButtons == "40"){ runIn(1,turnOnRule2) } else{ refresh() } } def turnOnRule2(){ try { httpGet("http://" + deviceIp + "/cm?cmnd=RULE2%20ON") { resp -> def json = (resp.data) if (json){ if (logEnable) log.debug "Command Success response from Device - Rule 2 activated" }else{ if (logEnable) log.debug "Command -ERROR- response from Device- $json" } } } catch (Exception e) { log.warn "Call to on failed: ${e.message}" } if (numberOfButtons == "30" || numberOfButtons == "40"){ runIn(1,turnOnRule3) } else{ refresh() } } def turnOnRule3(){ try { httpGet("http://" + deviceIp + "/cm?cmnd=RULE3%20ON") { resp -> def json = (resp.data) if (json){ if (logEnable) log.debug "Command Success response from Device - Rule 3 activated" }else{ if (logEnable) log.debug "Command -ERROR- response from Device- $json" } } } catch (Exception e) { log.warn "Call to on failed: ${e.message}" } refresh() } def parse(LanMessage){ if (logEnable) log.debug "data is ${LanMessage}" def msg = parseLanMessage(LanMessage) def json = msg.body if (logEnable) log.debug "Value == ${json}" if (logInfo) log.info "$device.label - Button $json Pushed" sendEvent(name:"pushed",value:"${json}",isStateChange: true) } def push(value){ if (logEnable) log.debug "data is ${value}" if (logInfo) log.info "$device.label - Button $value Pushed" sendEvent(name:"pushed",value:"$value",isStateChange: true) } def refresh() { if(settings.deviceIp){ if (logEnable) log.debug "Refreshing Device Status - [${settings.deviceIp}]" try { httpGet("http://" + deviceIp + "/cm?cmnd=status%2011") { resp -> def json = (resp.data) if (logEnable) log.debug "${json}" if (json.containsKey("StatusSTS")){ signal = json.StatusSTS.Wifi.Signal as String if (logEnable) log.debug "Wifi signal strength $signal db" sendEvent(name:"wifi",value:"${signal}db") } } }catch (Exception e) { sendEvent(name:"wifi",value:"offline") if (logInfo) log.error "$device.label Unable to connect, device is OFFLINE" log.warn "Call to on failed: ${e.message}" } } } def installed() { log.info "installed..." log.warn "debug logging is: ${logEnable == true}" state.DriverVersion=driverVer() if (logEnable) runIn(1800, logsOff) }