/** * Copyright 2020 Markus Liljergren (https://oh-lalabs.com) * * Version: v1.1.1.1123T * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * NOTE: This is an auto-generated file and most comments have been removed! * */ // BEGIN:getDefaultImports() import groovy.json.JsonSlurper import groovy.json.JsonOutput import java.security.MessageDigest // END: getDefaultImports() metadata { definition (name: "Tasmota - Universal Bulb/Light (Child)", namespace: "tasmota", author: "Markus Liljergren", filename: "tasmota-universal-bulb-light-child", importUrl: "https://raw.githubusercontent.com/markus-li/Hubitat/release/drivers/expanded/tasmota-universal-bulb-light-child-expanded.groovy") { capability "Actuator" capability "Switch" capability "Light" capability "Refresh" command "toggle" // BEGIN:getMinimumChildAttributes() attribute "driver", "string" // END: getMinimumChildAttributes() } preferences { // BEGIN:getDefaultMetadataPreferences() input(name: "debugLogging", type: "bool", title: styling_getLogo() + styling_addTitleDiv("Enable debug logging"), description: "" , defaultValue: false, submitOnChange: true, displayDuringSetup: false, required: false) input(name: "infoLogging", type: "bool", title: styling_addTitleDiv("Enable info logging"), description: "", defaultValue: true, submitOnChange: true, displayDuringSetup: false, required: false) // END: getDefaultMetadataPreferences() } // BEGIN:getMetadataCustomizationMethods() metaDataExporter() if(isCSSDisabled() == false) { preferences { input(name: "hiddenSetting", description: "" + getDriverCSSWrapper(), title: "None", displayDuringSetup: false, type: "paragraph", element: "paragraph") } } // END: getMetadataCustomizationMethods() } // BEGIN:getDeviceInfoFunction() String getDeviceInfoByName(infoName) { Map deviceInfo = ['name': 'Tasmota - Universal Bulb/Light (Child)', 'namespace': 'tasmota', 'author': 'Markus Liljergren', 'filename': 'tasmota-universal-bulb-light-child', 'importUrl': 'https://raw.githubusercontent.com/markus-li/Hubitat/release/drivers/expanded/tasmota-universal-bulb-light-child-expanded.groovy'] return(deviceInfo[infoName]) } // END: getDeviceInfoFunction() /* These functions are unique to each driver */ void parse(List description) { description.each { if (it.name in ["switch"]) { logging(it.descriptionText, 100) sendEvent(it) } else { log.warn "Got '$it.name' attribute data, but doesn't know what to do with it! Did you choose the right device type?" } } } void updated() { log.info "updated()" // BEGIN:getChildComponentDefaultUpdatedContent() getDriverVersion() // END: getChildComponentDefaultUpdatedContent() refresh() } void installed() { log.info "installed()" device.removeSetting("logLevel") device.updateSetting("logLevel", "100") refresh() } void refresh() { // BEGIN:getChildComponentMetaConfigCommands() def metaConfig = clearThingsToHide() metaConfig = setDatasToHide(['metaConfig', 'isComponent', 'preferences', 'label', 'name'], metaConfig=metaConfig) // END: getChildComponentMetaConfigCommands() parent?.componentRefresh(this.device) } void on() { parent?.componentOn(this.device) } void off() { parent?.componentOff(this.device) } /** * ----------------------------------------------------------------------------- * Everything below here are LIBRARY includes and should NOT be edited manually! * ----------------------------------------------------------------------------- * --- Nothing to edit here, move along! --------------------------------------- * ----------------------------------------------------------------------------- */ // BEGIN:getDefaultFunctions() private String getDriverVersion() { comment = "" if(comment != "") state.comment = comment String version = "v1.1.1.1123T" logging("getDriverVersion() = ${version}", 100) sendEvent(name: "driver", value: version) updateDataValue('driver', version) return version } // END: getDefaultFunctions() // BEGIN:getHelperFunctions('styling') String styling_addTitleDiv(title) { return '
' + title + '
' } String styling_addDescriptionDiv(description) { return '
' + description + '
' } String styling_makeTextBold(s) { if(isDriver()) { return "$s" } else { return "$s" } } String styling_makeTextItalic(s) { if(isDriver()) { return "$s" } else { return "$s" } } String styling_getDefaultCSS(boolean includeTags=true) { String defaultCSS = ''' /* This is part of the CSS for replacing a Command Title */ div.mdl-card__title div.mdl-grid div.mdl-grid .mdl-cell p::after { visibility: visible; position: absolute; left: 50%; transform: translate(-50%, 0%); width: calc(100% - 20px); padding-left: 5px; padding-right: 5px; margin-top: 0px; } /* This is general CSS Styling for the Driver page */ h3, h4, .property-label { font-weight: bold; } .preference-title { font-weight: bold; } .preference-description { font-style: italic; } ''' if(includeTags == true) { return "" } else { return defaultCSS } } String styling_getLogo() { String logoCSS = ''' #ohla_logo { display: block; width: 200px; height: 50px; position: absolute; top: 10px; right: 10px; } @media screen and (max-device-width:450px), screen and (max-width:450px) { #ohla_logo { width: 120px; top: 55px; } } ''' return "" } // END: getHelperFunctions('styling') // BEGIN:getHelperFunctions('driver-metadata') private Map getMetaConfig() { def metaConfig = getDataValue('metaConfig') if(metaConfig == null) { metaConfig = [:] } else { metaConfig = parseJson(metaConfig) } return metaConfig } boolean isCSSDisabled(Map metaConfig=null) { if(metaConfig==null) metaConfig = getMetaConfig() boolean disableCSS = false if(metaConfig.containsKey("disableCSS")) disableCSS = metaConfig["disableCSS"] return disableCSS } private void saveMetaConfig(Map metaConfig) { updateDataValue('metaConfig', JsonOutput.toJson(metaConfig)) } private Map setSomethingToHide(String type, List something, Map metaConfig=null) { if(metaConfig==null) metaConfig = getMetaConfig() def oldData = [] something = something.unique() if(!metaConfig.containsKey("hide")) { metaConfig["hide"] = [type:something] } else { if(metaConfig["hide"].containsKey(type)) { metaConfig["hide"][type].addAll(something) } else { metaConfig["hide"][type] = something } } saveMetaConfig(metaConfig) logging("setSomethingToHide() = ${metaConfig}", 1) return metaConfig } private Map clearTypeToHide(String type, Map metaConfig=null) { if(metaConfig==null) metaConfig = getMetaConfig() if(!metaConfig.containsKey("hide")) { metaConfig["hide"] = [(type):[]] } else { metaConfig["hide"][(type)] = [] } saveMetaConfig(metaConfig) logging("clearTypeToHide() = ${metaConfig}", 1) return metaConfig } Map clearThingsToHide(Map metaConfig=null) { metaConfig = setSomethingToHide("other", [], metaConfig=metaConfig) metaConfig["hide"] = [:] saveMetaConfig(metaConfig) logging("clearThingsToHide() = ${metaConfig}", 1) return metaConfig } Map setDisableCSS(boolean value, Map metaConfig=null) { if(metaConfig==null) metaConfig = getMetaConfig() metaConfig["disableCSS"] = value saveMetaConfig(metaConfig) logging("setDisableCSS(value = $value) = ${metaConfig}", 1) return metaConfig } Map setStateCommentInCSS(String stateComment, Map metaConfig=null) { if(metaConfig==null) metaConfig = getMetaConfig() metaConfig["stateComment"] = stateComment saveMetaConfig(metaConfig) logging("setStateCommentInCSS(stateComment = $stateComment) = ${metaConfig}", 1) return metaConfig } Map setCommandsToHide(List commands, Map metaConfig=null) { metaConfig = setSomethingToHide("command", commands, metaConfig=metaConfig) logging("setCommandsToHide(${commands})", 1) return metaConfig } Map clearCommandsToHide(Map metaConfig=null) { metaConfig = clearTypeToHide("command", metaConfig=metaConfig) logging("clearCommandsToHide(metaConfig=${metaConfig})", 1) return metaConfig } Map setStateVariablesToHide(List stateVariables, Map metaConfig=null) { metaConfig = setSomethingToHide("stateVariable", stateVariables, metaConfig=metaConfig) logging("setStateVariablesToHide(${stateVariables})", 1) return metaConfig } Map clearStateVariablesToHide(Map metaConfig=null) { metaConfig = clearTypeToHide("stateVariable", metaConfig=metaConfig) logging("clearStateVariablesToHide(metaConfig=${metaConfig})", 1) return metaConfig } Map setCurrentStatesToHide(List currentStates, Map metaConfig=null) { metaConfig = setSomethingToHide("currentState", currentStates, metaConfig=metaConfig) logging("setCurrentStatesToHide(${currentStates})", 1) return metaConfig } Map clearCurrentStatesToHide(Map metaConfig=null) { metaConfig = clearTypeToHide("currentState", metaConfig=metaConfig) logging("clearCurrentStatesToHide(metaConfig=${metaConfig})", 1) return metaConfig } Map setDatasToHide(List datas, Map metaConfig=null) { metaConfig = setSomethingToHide("data", datas, metaConfig=metaConfig) logging("setDatasToHide(${datas})", 1) return metaConfig } Map clearDatasToHide(Map metaConfig=null) { metaConfig = clearTypeToHide("data", metaConfig=metaConfig) logging("clearDatasToHide(metaConfig=${metaConfig})", 1) return metaConfig } Map setPreferencesToHide(List preferences, Map metaConfig=null) { metaConfig = setSomethingToHide("preference", preferences, metaConfig=metaConfig) logging("setPreferencesToHide(${preferences})", 1) return metaConfig } Map clearPreferencesToHide(Map metaConfig=null) { metaConfig = clearTypeToHide("preference", metaConfig=metaConfig) logging("clearPreferencesToHide(metaConfig=${metaConfig})", 1) return metaConfig } def metaDataExporter() { List filteredPrefs = getPreferences()['sections']['input'].name[0] if(filteredPrefs != []) updateDataValue('preferences', "${filteredPrefs}".replaceAll("\\s","")) } String getDriverCSSWrapper() { Map metaConfig = getMetaConfig() boolean disableCSS = isCSSDisabled(metaConfig=metaConfig) String defaultCSS = ''' /* This is part of the CSS for replacing a Command Title */ div.mdl-card__title div.mdl-grid div.mdl-grid .mdl-cell p::after { visibility: visible; position: absolute; left: 50%; transform: translate(-50%, 0%); width: calc(100% - 20px); padding-left: 5px; padding-right: 5px; margin-top: 0px; } /* This is general CSS Styling for the Driver page */ h3, h4, .property-label { font-weight: bold; } .preference-title { font-weight: bold; } .preference-description { font-style: italic; } ''' String r = "" return r } Integer getCommandIndex(String cmd) { List commands = device.getSupportedCommands().unique() Integer i = commands.findIndexOf{ "$it" == cmd}+1 return i } String getCSSForCommandHiding(String cmdToHide) { Integer i = getCommandIndex(cmdToHide) String r = "" if(i > 0) { r = "div.mdl-card__title div.mdl-grid div.mdl-grid .mdl-cell:nth-of-type($i){display: none;}" } return r } String getCSSForCommandsToHide(List commands) { String r = "" commands.each { r += getCSSForCommandHiding(it) } return r } String getCSSToChangeCommandTitle(String cmd, String newTitle) { Integer i = getCommandIndex(cmd) String r = "" if(i > 0) { r += "div.mdl-card__title div.mdl-grid div.mdl-grid .mdl-cell:nth-of-type($i) p {visibility: hidden;}" r += "div.mdl-card__title div.mdl-grid div.mdl-grid .mdl-cell:nth-of-type($i) p::after {content: '$newTitle';}" } return r } Integer getStateVariableIndex(String stateVariable) { def stateVariables = state.keySet() Integer i = stateVariables.findIndexOf{ "$it" == stateVariable}+1 return i } String getCSSForStateVariableHiding(String stateVariableToHide) { Integer i = getStateVariableIndex(stateVariableToHide) String r = "" if(i > 0) { r = "ul#statev li.property-value:nth-of-type($i){display: none;}" } return r } String getCSSForStateVariablesToHide(List stateVariables) { String r = "" stateVariables.each { r += getCSSForStateVariableHiding(it) } return r } String getCSSForCurrentStatesToHide(List currentStates) { String r = "" currentStates.each { r += "ul#cstate li#cstate-$it {display: none;}" } return r } Integer getDataIndex(String data) { def datas = device.getData().keySet() Integer i = datas.findIndexOf{ "$it" == data}+1 return i } String getCSSForDataHiding(String dataToHide) { Integer i = getDataIndex(dataToHide) String r = "" if(i > 0) { r = "table.property-list tr li.property-value:nth-of-type($i) {display: none;}" } return r } String getCSSForDatasToHide(List datas) { String r = "" datas.each { r += getCSSForDataHiding(it) } return r } Integer getPreferenceIndex(String preference, boolean returnMax=false) { def filteredPrefs = getPreferences()['sections']['input'].name[0] if(filteredPrefs == [] || filteredPrefs == null) { d = getDataValue('preferences') if(d != null && d.length() > 2) { try{ filteredPrefs = d[1..d.length()-2].tokenize(',') } catch(e) { } } } Integer i = 0 if(returnMax == true) { i = filteredPrefs.size() } else { i = filteredPrefs.findIndexOf{ "$it" == preference}+1 } return i } String getCSSForPreferenceHiding(String preferenceToHide, Integer overrideIndex=0) { Integer i = 0 if(overrideIndex == 0) { i = getPreferenceIndex(preferenceToHide) } else { i = overrideIndex } String r = "" if(i > 0) { r = "form[action*=\"preference\"] div.mdl-grid div.mdl-cell:nth-of-type($i) {display: none;} " }else if(i == -1) { r = "form[action*=\"preference\"] div.mdl-grid div.mdl-cell:nth-last-child(2) {display: none;} " } return r } String getCSSForPreferencesToHide(List preferences) { String r = "" preferences.each { r += getCSSForPreferenceHiding(it) } return r } String getCSSForHidingLastPreference() { return getCSSForPreferenceHiding(null, overrideIndex=-1) } // END: getHelperFunctions('driver-metadata') // BEGIN:getLoggingFunction(specialDebugLevel=True) private boolean logging(message, level) { boolean didLogging = false Integer logLevelLocal = 0 if (infoLogging == null || infoLogging == true) { logLevelLocal = 100 } if (debugLogging == true) { logLevelLocal = 1 } if (logLevelLocal != 0){ switch (logLevelLocal) { case 1: if (level >= 1 && level < 99) { log.debug "$message" didLogging = true } else if (level == 100) { log.info "$message" didLogging = true } break case 100: if (level == 100 ) { log.info "$message" didLogging = true } break } } return didLogging } // END: getLoggingFunction(specialDebugLevel=True) // BEGIN:getHelperFunctions('all-default') boolean isDriver() { try { getDeviceDataByName('_unimportant') logging("This IS a driver!", 1) return true } catch (MissingMethodException e) { logging("This is NOT a driver!", 1) return false } } void deviceCommand(String cmd) { def jsonSlurper = new JsonSlurper() def cmds = jsonSlurper.parseText(cmd) r = this."${cmds['cmd']}"(*cmds['args']) updateDataValue('appReturn', JsonOutput.toJson(r)) } void setLogsOffTask(boolean noLogWarning=false) { if (debugLogging == true) { if(noLogWarning==false) { if(runReset != "DEBUG") { log.warn "Debug logging will be disabled in 30 minutes..." } else { log.warn "Debug logging will NOT BE AUTOMATICALLY DISABLED!" } } runIn(1800, "logsOff") } } void toggle() { if(device.currentValue('switch') == 'on') { off() } else { on() } } void logsOff() { if(runReset != "DEBUG") { log.warn "Debug logging disabled... " if(isDriver()) { device.clearSetting("logLevel") device.removeSetting("logLevel") device.updateSetting("logLevel", "0") state?.settings?.remove("logLevel") device.clearSetting("debugLogging") device.removeSetting("debugLogging") device.updateSetting("debugLogging", "false") state?.settings?.remove("debugLogging") } else { app.removeSetting("logLevel") app.updateSetting("logLevel", "0") app.removeSetting("debugLogging") app.updateSetting("debugLogging", "false") } } else { log.warn "OVERRIDE: Disabling Debug logging will not execute with 'DEBUG' set..." if (logLevel != "0" && logLevel != "100") runIn(1800, "logsOff") } } boolean isDeveloperHub() { return generateMD5(location.hub.zigbeeId as String) == "125fceabd0413141e34bb859cd15e067_disabled" } def getEnvironmentObject() { if(isDriver()) { return device } else { return app } } private def getFilteredDeviceDriverName() { def deviceDriverName = getDeviceInfoByName('name') if(deviceDriverName.toLowerCase().endsWith(' (parent)')) { deviceDriverName = deviceDriverName.substring(0, deviceDriverName.length()-9) } return deviceDriverName } private def getFilteredDeviceDisplayName() { def deviceDisplayName = device.displayName.replace(' (parent)', '').replace(' (Parent)', '') return deviceDisplayName } BigDecimal round2(BigDecimal number, Integer scale) { Integer pow = 10; for (Integer i = 1; i < scale; i++) pow *= 10; BigDecimal tmp = number * pow; return ( (Float) ( (Integer) ((tmp - (Integer) tmp) >= 0.5f ? tmp + 1 : tmp) ) ) / pow; } String generateMD5(String s) { if(s != null) { return MessageDigest.getInstance("MD5").digest(s.bytes).encodeHex().toString() } else { return "null" } } Integer extractInt(String input) { return input.replaceAll("[^0-9]", "").toInteger() } String hexToASCII(String hexValue) { StringBuilder output = new StringBuilder("") for (int i = 0; i < hexValue.length(); i += 2) { String str = hexValue.substring(i, i + 2) output.append((char) Integer.parseInt(str, 16) + 30) logging("${Integer.parseInt(str, 16)}", 10) } return output.toString() } // END: getHelperFunctions('all-default')