Universal ST/HE Driver code, differentiating between

OK, so this seems to work...thanks for the pointer...

private getHubPlatform() {
    def p = physicalgraph?.device?.HubAction ? 'SmartThings' : 'Hubitat'
    if (debug) log.debug "getHubPlatform: ${p}"
    return p
}
1 Like

That's some nice looking code there. Better than try/catch :stuck_out_tongue:

2 Likes

EDIT: See below for the finalized library --> Universal ST/HE Driver code, differentiating between

For others wanting to take advantage of this (apparently) efficient way of determining the hub platform:

def getHubPlatform() { physicalgraph?.device?.HubAction ? 'SmartThings' : 'Hubitat' }
Boolean getIsST() { (hubPlatform == 'SmartThings') }
Boolean getIsHE() { (hubPlatform == 'Hubitat') }

With these you can safely do any of these, anywhere in your device code (including in tile definitions):

if (isST) { do_something }
if (isHE) { do_something }

The Hub Platform effectively gets determined at the time you save your code, so the run time code is effectively if (true) { do_something } or if (false) { do_something }

1 Like

just please make the one call on app install to [ whatever we decide to do here, that may or may not need a try catch], then assign that result to a state variable, then use that state variable in all the if blocks...

2 Likes

Unfortunately, that doesn't work if you are trying to determine the platform type during compile time or while defining capabilities and attributes, collecting/saving preferences or in tile definitions - anywhere before installed() is called. This fails on both platforms because state.* has not been initialized during code save/compile or when the capabilities/attributes/tiles/preferences metadata is being processed (like when you save the preferences).

So, you may need to use the above directly before installed() , and then have installed() set the state variable for those things you need to know the platform for during normal runtime operations.

FYI - if (state?.platform == null) will also throw the error until installed() runs (in some cases, you'll even get a compile error). The above declarations are safe to use everywhere.

I'm doing some performace testing between the various methods on both platforms, with some interesting results. I need to run more tests to get a sufficient sample size, but it appears that the following is fastest for both platforms at runtime, with the added bonus of being universally applicable (including within metadata definitions):

Boolean SmartThings() { (physicalgraph?.device?.HubAction) }
Boolean Hubitat() { (hubitat?.device?.HubAction) }

def p1 = SmartThings()
def p2 = Hubitat()

using the getIsST()/getIsHE() approach (invoked by p1 = isST and p1 = isHE) is about 2x slower on both platforms than calling the routines directly (e.g. p1 = getIsST()).

While using state variables on Hubitat is very fast, they are still slower than the direct approach above (and on ST, state variables are the slowest approach).

I'll update my findings later today, and post my test code for others to try...

2 Likes

After much testing, I have concluded that the following is the most efficient implementation on both platforms:

// **************************************************************************************************************************
// SmartThings/Hubitat Portability Library (SHPL)
// Copyright (c) 2019, Barry A. Burke (storageanarchy@gmail.com)
//
// The following 3 calls are safe to use anywhere within a Device Handler or Application
//  - these can be called (e.g., if (getPlatform() == 'SmartThings'), or referenced (i.e., if (platform == 'Hubitat') )
//  - performance of the non-native platform is horrendous, so it is best to use these only in the metadata{} section of a
//    Device Handler or Application
//
private String  getPlatform() { (physicalgraph?.device?.HubAction ? 'SmartThings' : 'Hubitat') }	// if (platform == 'SmartThings') ...
private Boolean getIsST()     { (physicalgraph?.device?.HubAction ? true : false) }					// if (isST) ...
private Boolean getIsHE()     { (hubitat?.device?.HubAction ? true : false) }						// if (isHE) ...
//
// The following 3 calls are ONLY for use within the Device Handler or Application runtime
//  - they will throw an error at compile time if used within metadata, usually complaining that "state" is not defined
//  - getHubPlatform() ***MUST*** be called from the installed() method, then use "state.hubPlatform" elsewhere
//  - "if (state.isST)" is more efficient than "if (isSTHub)"
//
private String getHubPlatform() {
    if (state?.hubPlatform == null) {
        state.hubPlatform = getPlatform()						// if (hubPlatform == 'Hubitat') ... or if (state.hubPlatform == 'SmartThings')...
        state.isST = state.hubPlatform.startsWith('S')			// if (state.isST) ...
        state.isHE = state.hubPlatform.startsWith('H')			// if (state.isHE) ...
    }
    return state.hubPlatform
}
private Boolean getIsSTHub() { (state.isST) }					// if (isSTHub) ...
private Boolean getIsHEHub() { (state.isHE) }					// if (isHEHub) ...
//
// **************************************************************************************************************************
3 Likes

@storageanarchy I hope you can figure this out because I need some sort of Java/Groovy dynamic compile time code generation for a device's communication logic.

When I worked with IBM Assembler, it was called a Macro. In Groovy I found compile and dynamic metacoding, but I fried many brain cells attempting to make sense of it.

I have a DTH I want to make compatible with both HE and ST. The following code segment will only execute in ST, but it (obviously) wont compile in HE.
def hubAction = new physicalgraph.device.HubAction(

What I want to do is something at compile time that changes "physicalgraph" to "hubitat" on the HE platform, allowing it to compile, but my Groovy skills are not up to the task. I tried this but it throws an error on the def hubAction at Save in HE and ST. However, provides an example of what I want to do
def getHubPlatform=(physicalgraph?.device?.HubAction ? 'physicalgraph' : 'hubitat')
def hubAction = new "${gethubPlatform}".device.HubAction(

A better way of doing this that I can't figure out, is for something that would include or exclude chunks of code at compile time based upon the platform. So in some meta language

if ST
include SmartThings_Code
else
include Hubitat_Code

Any suggestions appreciated

See my new SmartThings/Hubitat Portability Library that I just posted here:

The git provides some coding examples, one of which specifically addresses your desired use case.

All feedback is most welcome...

Thank you! That was most helpful.

I changed

def hubAction = new physicalgraph.device.HubAction(

to

def hubAction = physicalgraph.device.HubAction.newInstance(

It now Saves on ST and HE, executes on HE, but throws the following "grails.validation" error in ST. That during save is misleading, it's executing. Strange looking error message, I'll track it down. It does the same thing on ST when the HE communications code is included but not executed. Works fine without inclusion of the HE code.

error grails.validation.ValidationException: Validation Error(s) occurred during save():

  • Field error in object 'physicalgraph.device.Device' on field 'deviceNetworkId': rejected value [C0A80072:0913]; codes [physicalgraph.device.Device.deviceNetworkId.unique.error.physicalgraph.device.Device.deviceNetworkId,physicalgraph.device.Device.deviceNetworkId.unique.error.deviceNetworkId,physicalgraph.device.Device.deviceNetworkId.unique.error.java.lang.String,physicalgraph.device.Device.deviceNetworkId.unique.error,device.deviceNetworkId.unique.error.physicalgraph.device.Device.deviceNetworkId,device.deviceNetworkId.unique.error.deviceNetworkId,device.deviceNetworkId.unique.error.java.lang.String,device.deviceNetworkId.unique.error,physicalgraph.device.Device.deviceNetworkId.unique.physicalgraph.device.Device.deviceNetworkId,physicalgraph.device.Device.deviceNetworkId.unique.deviceNetworkId,physicalgraph.device.Device.deviceNetworkId.unique.java.lang.String,physicalgraph.device.Device.deviceNetworkId.unique,device.deviceNetworkId.unique.physicalgraph.device.Device.deviceNetworkId,device.deviceNetworkId.unique.deviceNetworkId,device.deviceNetworkId.unique.java.lang.String,device.deviceNetworkId.unique,unique.physicalgraph.device.Device.deviceNetworkId,unique.deviceNetworkId,unique.java.lang.String,unique]; arguments [deviceNetworkId,class physicalgraph.device.Device,C0A80072:0913]; default message [{0} must be unique]

FWIW, I have this library working in my Meteobridge Weather Station, same code on both platforms. Maybe you'll find something in my code that helps...

Should anyone else bump into this in ST it is caused by having two devices with the same Network Device Id. In my case: C0A80072:0913 or 192.168.0.114:2323.

Did not seem to be an issue in HE.

I am pretty sure that both platforms expect that network IDs for all devices are unique, even if the error was only flagged on SmartThings.

I concur. What happened in ST was my test device's manually coded unique Device Network Id was overwritten with its ip:port address, matching a live device.