Eliminating driver code mess

Hi,

I have an issue with DTHs that kills me.

I have numerous Xiaomi Zigbee devices. Needless to say, they all need their specific DTHs, and differ greatly in capabilities.

Due to the static nature of capabilities, I have to create and maintain 1 DTH for each capability-set; e.g. 1 have 1 DTH with pushableButton, 1 DTH with Pushable-, Holdable-, Releasable-, DoubleTapableButton capabilities, 1 with the same, but with an additional Switch... but the codebase is mostly the same, and every time I change something in the "common" codebase, I have to copy the changes to all the related DTHs. It makes development a PITA, and is a perfect source of copy-paste-related bugs...

I think (looking at fellow developers' DTH codes on github it's much more than a feeling) I'm not the only one who has this issue, so there should be some kind of solution for this. My ideas:

  • Making capabilities dynamic. The ability to add/remove capablities from code would solve this problem, this way I could create 1 mega-DTH that adjusts its capabilites based on the type of device it's assigned to.
  • Making capabilities conditional. Capabilities and fingerprints are both in the metadata, they could be linked somehow... The capabilities depend on the device type that is represented by the fingerprint, so having a relation between them seems logical.
  • Provide a way to create custom libraries. The most straightforward solution, I think no expanation needed.

Right now the only way (I'm aware of) to achieve my goal is to create a "parent device" that is basically a container for shared code, and maintains a list of "child devices" that are just capability-set wrappers and call their parent... Might work (if multi-level nesting - i.e. creating grandchildren - is possible), but I don't like the idea of having to maintain an otherwise unnecessary device hierarchy just to make codebase maintenance easier... they're simply not - or shouldn't be - related.

Any thoughts about it?

Thanks!

1 Like

Exactly the problem OOP was designed to solve.

Yeah. And luckily, groovy is an object oriented language. :slight_smile:

But is there a way to create custom classes that I can use from my DTHs? (my third proposal)

Nope. Not at this time, I think.

Curious, but wouldn't this mean the introduction of yet another layer of "capability", to determine the "type of device"? That is the purpose of capability now, to distinguish types of devices. I don't see how dynamic capabilities would work. What would the conditions be based on?

if(some_condition) capability "PushableButton"

Where would some_condition come from? How would it be set?

There is not a one to one correspondence between capability and fingerprint. The purpose of a driver is to create some organization from a soup of differing fingerprints, to offer an abstraction to the world of apps and user interfaces for automations. At one dysfunctional extreme, every different type of device would be offered in the UI for selection. Instead of dimmers, there would be at least 20 different flavors of dimmer. This would create a UI nightmare from both a user and app author perspective. So capability "Switch Level" is an abstraction that sweeps in many disparate fingerprints. Of course, capability "Switch" sweeps in an even larger variety.

In one sense you are describing the very problem that capabilities were designed to solve. I don't claim they are the best possible solution (we certainly didn't come up with this design!), but they are a functional approach to a difficult problem.

It seems that what you are describing falls more in the realm of source code management than the design of capabilities. With the right source code management facility you could have both dynamic and conditional capabilities handled at the source code level, as well as custom libraries. It is outside the scope of our business to provide such tools as part of Hubitat Elevation; it is not our business objective to create and support a developer platform. Our primary business objective is to create a home automation platform upon which people can create the automations to elevate their homes.

We develop and maintain Hubitat apps and drivers within the same set of constraints you are faced with. As such, it is an existence proof that these tools are sufficient to the task at hand.

3 Likes

I wrote a novel, but deleted. TL;DR and... honestly, I'm - and lots of us, I think - in need for the third solution. The static and the dynamic capability theory was just included for the sake of completeness... :slight_smile:

You're right that dynamic and conditional capabilities would add complexity to the framework, and are not flexible enough to solve every imaginable problems.

But the ability to create reusable code seems to be handful. Half of my life is C++, so #include is okay, but the other half is C#, and public classes are useful, too... unintended access can be prevented by using namespaces... the wheel doesn't have to be reinvented, as @doug said above: that's what OOP is for.

In my case I'd make a xiaomiLeastDenominator class of 400 lines, and use it from 3, 5, 15 DTHs of 50 lines... as many DTHs, as capability-sets I have. That's totally acceptable, and keeps the current rules.
One bugfix or added feature in the xiaomiLeastDenominator would immediately appear in all my DTHs, and maybe my Xiaomi DTHs wouldn't use the usefulZigbeeClusters class that handles e.g. the Identify cluster, but others would... okay, I stop explaining the usefulness of OOP for you, I don't want to embarrass myself. :slight_smile:

PS: in 1 month I got to the point where everything is working as it should, it's stable and fast, my biggest issue is the lack of convenience during DTH development... it's a pretty awesome stuff you guys brought together, so I'll remain a happy HE user even if I have to live keep on living without shared code... :+1:

Sorry about reviving an old thread... But this was just bothering me earlier today and I was about to post a related question before I finally found this thread.

Has there been any changes to support this more? I am also making a few drivers at this time and trying to keep my code base as similar as possible. Being able to change a capability/commands (add/remove) dynamically (for example, based on the device's model which is know prior to that point) Seems like it would be useful.

Most of my code is the same across devices but a couple things vary and it could multiply quickly (one example is some detect tampering, some do not, but unless I separate it I need to have a command to clear it shown for all).

Or am I looking at this entirely wrong and everybody has come up with alternatives that I could learn from?

1 Like

Yeah, we have discussed this numerous times, dynamic capabilities and attributes aren't going to happen.

To be honest, there isn't that much to be gained by it, dynamic capabilities aside, implementing a universal driver would load the thing up with all sorts of cruft that will do nothing but bloat the driver and make it much more difficult to debug and regression test.

If a device has a unique feature set it should have a dedicated driver, they are easier to write, debug and manage going forward.

3 Likes

Ok. Best to know for sure I guess.

None of the devices I am dealing with most have a unique feature set, or this would not have even mattered. Most are ZigBee or HTTP (Virtual) devices with largely the same base code. For example, I have my Securifi Sensors which handles most of their sensors (still do not have a water sensor to try) but I also built in their keyfob and Almond Click devices. Works well and I do not have to keep separate ZigBee parsing and such, except the keyfobs and Click lack the tamper switch. Oops... The other devices in it are a mix of motion, contact, and temperature capabilities. I COULD separate and have 2 drivers... but that doubles the maintenance.

I also have my Securifi Peanut Plug driver that I want to incorporate into it, since the majority of code (ZigBee parsing and more) is common.

Same sort of thing with my HTTP drivers... Obviously I am working on similar things because of the commonality between them.

I do not think a "universal" driver is reasonable but there are many cases where MOST of a driver would be the same and the few differences could be handled, if there was a way to handle them.

1 Like

That's why I recommended that we should be able to create shared libraries. We could put our common codebase into these libraries, and use them in our DTHs. It would make maintenance of similar DTHs WAY easier.

This way we'd have e.g. 1 1200-line library, and 25 30-line DTHs instead of 25 1230-line DTHs, where replicating every single bugfix or feature extension into 25 different DTHs is an inevitable source of bugs.

2 Likes

Capabilities define attributes and commands if any.
It's not a good idea to advertise a given capability but not manage the associated attributes.
It's a worse idea to populate an attribute value that's not actually maintained by the device.

Perhaps, however making a bad change in one of these library's will break every device using it.
I'm all for code reuse where it makes sense.

arg.. why is everyone talking about DTHs,, I'm pretty sure you mean drivers!

5 Likes

I agree it is not good to list a capability a device does not have and a command that will do nothing... But do I want to maintain 3 (at least) different drivers to handle the sensors and buttons alone? Not really. Not for 8 different devices.

Libraries make sense (I could toss all that ZigBee parsing in one) but then it becomes a case of how to maintain the library as @mike.maxwell mentioned, but I would be happy to maintain my own library at least. It already botches up multiple things if I break it now and even when I do have separate drivers (the Peanut Plug for example) it breaks when I copy code between them as I often do.

If there is a common way for processing zigbee messages, I would be fine with putting that into the Zigbee helper class. It would have to be generic enough that it could be widely used. If there is some code that you think meets that criteria, feel free to send it to me.

Yes, there is a risk, I agree. Maintainance of libraries is a much more complex task than hacking simple classes, but many of us here are comfortable with OOP, and backporting (in this case) is the most boring, non-productive part of our job where we do most of our mistakes.

1 Like

I thought DTHs and drivers are the same thing. :slight_smile:

Dth is a smartthings acronym, we avoid using it in reference to our driver code.

My story: I created drivers for quite a few Aqara devices. The reason I needed custom drivers is that they're non-standard devices.
They have a method they use in all their devices to provide additional info, and it's the same in all devices, but as @mike.maxwell declared previously, you don't want to support nonstandard devices. By the way I totally understand it. So to extract these info I had to create some code to process it. This code is the same for all these devices, making driver maintenance a nightmare.

Oh, and the reason I have so many drivers is that all these devices differ in capabilities, but @mike.maxwell convinced me that dynamic capabilities would cause so many confusion, it's not worth it.

Ah, sorry, I wasn't aware of that. I'll stick to "driver" in the future.

Sounds like someone could come up with a cool gradle plugin that would allow you to build your drivers by having a separate set of methods that get "imported" into those drivers.. put all the common methods into one file, have gradle take your individual driver "stubs" add the common methods and spit out the complete driver to be added to the system. That way you would only have one file to maintain your common code in. I'm sure there are a number of people on here that would use something like that.

1 Like