Can Libraries Be Used in Driver Metadata? Yes They Can :-)

I have started to setup a library that includes some methods I commonly use in my drivers to conditionally record different types of logging. I was able to add this library to the end of a new driver I am writing and see the code added to the end of a downloaded copy of the driver.

These methods also make use of preferences for the device, allowing the user to turn the different types of logging on or off. I expected I would need to setup a separate library, so I did this and added a second include statement to the meta-data section of the driver. I can see a reference in the libraries code page indicating this logging preferences library is being used by the driver, but no text is output in the driver.

Is this expected? If so, is there an alternative I could use?

To provide some more context, here are the two libraries:

library (
 author: "Simon Burke",
 category: "logging",
 description: "Logging methods",
 name: "loggingMethods",
 namespace: "simnet",
 documentationLink: ""
)

//Logging Utility methods
def debugLog(debugMessage) {
	if (DebugLogging == true) {log.debug(debugMessage)}	
}

def errorLog(errorMessage) {
    if (ErrorLogging == true) { log.error(errorMessage)}  
}

def infoLog(infoMessage) {
    if(InfoLogging == true) {log.info(infoMessage)}    
}

def warnLog(warnMessage) {
    if(WarnLogging == true) {log.warn(warnMessage)}    
}

def debugOff() {

   log.warn("Disabling debug logging");
   device.updateSetting("DebugLogging", [value:"false", type:"bool"])
}

def updated_debugTimout() {
    if (DebugLogging) {
     log.debug "updated: Debug logging will be automatically disabled in ${debugAutoDisableMinutes} minutes"
     runIn(debugAutoDisableMinutes*60, "debugOff")
   }
   else { unschedule("debugOff") }
}
library (
 author: "Simon Burke",
 category: "logging",
 description: "Logging preference settings",
 name: "loggingPreferences",
 namespace: "simnet",
 documentationLink: ""
)

preferences {
// Logging Preferences
input(name: "DebugLogging", type: "bool", title:"Enable Debug Logging",                   displayDuringSetup: true, defaultValue: false)
input(name: "WarnLogging",  type: "bool", title:"Enable Warning Logging",                 displayDuringSetup: true, defaultValue: true )
input(name: "ErrorLogging", type: "bool", title:"Enable Error Logging",                   displayDuringSetup: true, defaultValue: true )
input(name: "InfoLogging",  type: "bool", title:"Enable Description Text (Info) Logging", displayDuringSetup: true, defaultValue: false)
}
2 Likes

Actually, I see now that all the libraries are actually importing, just at the very end of the driver, regardless of where the include statement is included. I only noticed this after I tried to add one for some standard imports and static variable definitions I wanted to add to the start of the driver. Once I scrolled down to the bottom of the downloaded file I then saw that both this third "imports" library and the preferences one I posted about earlier, all appear at the end of the driver.

So my question is more.... Can the library code be added where they are included in the driver?

I have successfully imported some of the metadata data into a some of my capability libraries. This data then is included in the display and works accordingly.

I will do some more testing today on the preferences (while finishing up a washer driver set). I know you can include them as a method called in the metadata preferences and you may be able to do what you are thinking. IF SO, I would combine your two libraries above.

Example of simple library with metadata import and working. The library is used in a parent-child driver set (main and flex compartment) for a Samsung Dryer that share a lot of the same commands and capabilities. Edited for simplicity.

Summary
library (
	name: "samsungDryerCommon",
	namespace: "replica",
	author: "Dave Gutheinz",
	description: "Common Methods for replica Samsung Oven parent/children",
	category: "utilities",
	documentationLink: ""
)
//	Version 1.0

//	===== Common Capabilities, Commands, and Attributes =====
capability "Refresh"
attribute "remoteControlEnabled", "boolean"
attribute "switch", "string"
attribute "completionTime", "string"
attribute "machineState", "string"
attribute "dryerJobState", "string"
command "start"
command "pause"
command "stop"
attribute "timeRemaining", "string"

//	===== Device Commands =====
def start() { setMachineState("run") }
def pause() { setMachineState("pause") }
def stop() { setMachineState("stop") }
def setMachineState(machState) {
	def oldState = device.currentValue("machineState")
	Map cmdStatus = [oldState: oldState, newState: machState]
	if (oldState != machState) {
		cmdStatus << sendRawCommand(getDataValue("componentId"), "dryerOperatingState", 
									"setMachineState", [machState])
	} else {
		cmdStatus << [FAILED: "no change in state"]
		runIn(10, checkAttribute, [data: ["setMachineState", "machineState", machState]])
	}
	logInfo("setMachineState: ${cmdStatus}")
}
1 Like

That hasn't been my experience, but if you have / can get it working, that would be great, thanks @djgutheinz

Finished quick test. Modified logging library to include preferences section. Then modified main driver to delete the logging preferences.

Resultant main code metadata section:

metadata {
	definition (name: appliance(),
				namespace: "replica",
				author: "David Gutheinz",
				importUrl: "https://raw.githubusercontent.com/DaveGut/HubithingsReplica/main/Drivers/${appliance()}.groovy"
			   ){
		capability "Configuration"
		attribute "healthStatus", "enum", ["offline", "online"]
		capability "Refresh"
		attribute "jobBeginningStatus", "string"
	}
	preferences {
		input ("TEST", "bool",  title: "BOGUS", defaultValue: false)
false)
	}
}

Resultant logging library (truncated) section:

library (
	name: "Logging",
	namespace: "davegut",
	author: "Dave Gutheinz",
	description: "Common Logging Methods",
	category: "utilities",
	documentationLink: ""
)

preferences {
	input ("logEnable", "bool",  title: "Enable debug logging for 30 minutes", defaultValue: false)
	input ("infoLog", "bool", title: "Enable information logging${helpLogo()}",defaultValue: true)
	input ("traceLog", "bool", title: "Enable trace logging as directed by developer", defaultValue: false)
}

def logTrace(msg){
	if (traceLog == true) {
		log.trace "${device.displayName}-${driverVer()}: ${msg}"
	}
}

Screen capture of device's edit page:

3 Likes

Hmmm...

So what does the driver look like where you did the include? I.e. not the final output? Or us that the import?

Also, I probably should have mentioned I may want to add other preferences in the driver where I include the library...

At bottom of driver (can be anywhere, but I put at bottom) would be the following:

// ===== Libraries =====
#include replica.samsungWasherCommon
#include replica.samsungReplicaCommon
#include davegut.Logging

I have been doing this in my Kasa Devices since shortly after libraries came out.

2 Likes

Ok, maybe I just hadn't tried to use the driver yet... I was basing my question on what I saw it the downloaded code. My only concern would be if I didn't see it in the final code how it will be treated if I try to use that in code I package up in hpm. I'll do some more testing through the week, thanks @djgutheinz

1 Like

Each library is imported into the code you generate from Hubitat using the export icon on the driver's edit page.

icon is;
image

2 Likes

You were thorough and on the money, as always, @djgutheinz :slight_smile:

I was worried, originally, that I was not seeing the library code in the downloaded groovy file where I added the "#include" statement for the library, i.e. where I would normally write the code manually but had replaced that with the include statement. In fact the library code was being appended at the end of the groovy driver code file, even Preference Settings. When I setup a new virtual device using my driver it did in fact include the preference settings that were in my library. So crisis averted :slight_smile: And you are right, I should be able to combine the contents of the two separate library files, which I will now test. I will also look at whether import statements at the end rather than the beginning of the final groovy driver cause any issues....

2 Likes

Beyond testing imports, I can confirm things work as I would expect.

@bertabcd1234 - Feels like something that may be of use in the doco to hint at the fact that all includes will appears at the end of then final driver code, including Preference settings, which is fine....

3 Likes

The docs now explicitly state that #include-d code all appears at the end of the original app or driver.

4 Likes