Writing a custom driver

Hi Everyone,

I need a little help/advice how to create a clickable button for entering value.
I need to create a simplest possible driver for my HubDuino NeoPixel project.
The existing driver created by @ogiewon works. It does a lot of things but it
is missing an ability for entering individual R G B values in a 0-255 range.
I went through tons of docs and examples and I guess, I got a general idea
what to do. But I could not figure what creates a Button on a Device Driver
page? All what I need - is a clickable Button with a Name and ability to
enter a value in 0-255 range. The driver will/should have 3 buttons (one
per color) and maybe general On/Off Buttons (I guess, I know how to deal
with On/Off buttons).
Any pointer to whatever doc or real example will be very helpful.

Thank you,

  • Vitaliy

Buttons on device pages represent device commands. These could be "stock" commands, meaning ones that are assumed to be in your driver based on which capabilities you implement. For example, if you implement capability "colorTemperature", the setColorTemperature() command is required as part of that, and the device page will display a button representing this command at the top.

In general, I think it's preferable to use "standard" (capability) commands when you can. This increases compatibility with apps, since most apps are written to accommodate the capability model. However, no standard command accepts RGB values. Thus, you may need to use a custom command. In your definition() (same place you'd put which capabilies you imeplement, etc.), you can do something like:

command "setColorByRGB", [[name:"red*",type:"NUMBER", description:"R value for RGB color"], [name:"green*", type:"NUMBER", description:"G value for RGB color"], [name:"blue*",type:"NUMBER", description:"B value for RGB color"]]

Elsewhere in your driver, you will then need a setColorByRGB() method that takes three parameters and does whatever you want this command to do. Your driver must have a Groovy method matching the command name, as that is what will be executed when the command is run.

This assumes you want RGB as all one command; if not, you could make three commands like setRed(), setGreen(), etc., or you could keep it as a single command but make each parameter optional (remove the asterisk, and make sure you have a method signature that matches any possibility, perhaps also using optional/default parameters).

For your "on" and "off" commands, those come as part of capability "Switch", so you should not need to do anything special for those (to make the commands/buttons appear on the device page), assuming you specify this capability. You will, again, just need on() and off() methods, which will be run when these commands are called.

2 Likes

Yes,
I figured this out - declaring whatever "Capability" will create a set of Commands and
related Buttons. But the problem is - I don't need all that Buttons and/or Commands.
Unless I am missing something - I have to create a supporting functions for all that
Buttons and/or Commands, declared by whatever "Capability" object.
And in my case - this is over killer ( at least for whatever I am planning to implement).
Just in case here is my little history ( I am sorry for the long post).
I am an experiences EE designing various custom FPGA-based HW.
I am dealing with embedded CPUs from latest 70s.
My strong opinion is - the Device Driver should/must provide only a support for
the HW capabilities. All the "intelligence" should/must not be a part of the Driver.
It should/must be a responsibility of the Application or whatever SW layer above
the Driver. But the Device Driver itself should/must be as simple as possible.
All these RGB strips (including NeoPixel and whatever Color Bulbs) on a physical
layer are nothing more than 3, 4 or 5 LEDs per each location. The full LED Location
control is required only RGB(W)(W) values usually in a range 0-255 (another words - one
byte per each physically present LED). This one byte value is nothing more than
PWM control where 0 means - Pulse Width is 0% duty cycle (represents LED Off condition)
and 255 means 100% Pulse Width (represents full On condition).
Any value in a middle will be nothing more than LED Brightness Control.
So the Driver for the RGB(W)(W) strip/bulb must provide a very simple ability to set
these values for a physical device. What I am seeing in every RGB Driver I checked - is
a lot of added intelligence attempting to control Colors on a Driver level.
I think (and this is my opinion) the Driver is a very wrong place for all this "intelligence".
All intelligence should be on the SW layer above the Driver (App or Extended Driver, etc).
That is why I cannot use any of the existing/provided Capabilities.
All they are simply over killers.

As I mentioned, all what I need - is a very simple ability to enter an integer value.
I will try what you suggested:

command "setColorByRGB", [[name:"red*",type:"NUMBER", description:"R value for RGB color"], [name:"green*", type:"NUMBER", description:"G value for RGB color"], [name:"blue*",type:"NUMBER", description:"B value for RGB color"]]

Yes, on the HE Driver level all what I will need - is simple individual control for each physical channel.
The rest will be done somewhere else ( in this my specific case - in the HubDuino
(Arduino) ESP8266 Sketch).

Thank you very much for your very valuable advice(s).

Right, that is how Hubitat's capabiltiy model works. Why would your driver declare a capabiltiy that it doesn't actually make sense to implement? This "problem" sounds avoidable to me.

The job of drivers on Hubitat is, basically, to "abstract" the real-world details of the device into Hubitat's standard capability model. This lets Hubitat apps use standard commands and attributes to interact with the device without knowing any details about the device--e.g., whether it's Z-Wave or Zigbee (or LAN), what underyling command classes/clusters/network messages it sends or responds to, and whatnot. I'm not sure if this is what you are referring to or not, but if so, it seems like your vision is not in line with Hubitat's model.

Not saying either is right or wrong, just that this is how it works. :slight_smile: (Personally, I think it has a lot of advantages--the app doesn't need to know anything about the particular devices it's working with--if it says it's a "switch," then it knows off() and on() are available, as is the switch attribute it can read for a current state).

Good luck with the driver!

2 Likes

OK,
I added the above command and commented out all unwanted "capabilities".

metadata {
definition (name: "Child RGB Switch NP VK", namespace: "ogiewon", author: "Allan (vseven) - based on code by Dan Ogorchock", importUrl: "https://raw.githubusercontent.com/DanielOgorchock/ST_Anything/master/HubDuino/Drivers/child-rgb-switch.groovy") {
capability "Switch"
//capability "Switch Level"
//capability "Actuator"
//capability "Color Control"
//capability "Sensor"
//capability "Light"

command "setColorByRGB", [[name:"red*",type:"NUMBER", description:"R value for RGB color"], [name:"green*", type:"NUMBER", description:"G value for RGB color"], [name:"blue*",type:"NUMBER", description:"B value for RGB color"]]

}

nd now on the Device Driver page I can see only Buttons I want to see:

But what variable holds entered R, G, B values?

For the testing this new command I simply copied a body from the "off" command

def setColorByRGB(){
toggleTiles("off")
sendEvent(name: "switch", value: "off")
//if (logEnable) log.debug("Off pressed. Update parent device.")
sendData("off")
}

def off() {
toggleTiles("off")
sendEvent(name: "switch", value: "off")
//if (logEnable) log.debug("Off pressed. Update parent device.")
sendData("off")
}

My assumption was - clicking on the "setColorByRGB" button should send
an "off" command but unfortunately this is not working.
"off" button of course works.
What I am missing?

You need to specify method parameters. They will hold the values. For example:

def setColorByRGB(red, green, blue) {
  //...
}
1 Like

I'm not sure if you have sendData() defined anywhere, but it's not a standard Hubitat driver method, so it won't do anything on its on. To run a command in your driver from your driver code, just call the method (including appropriate parameters, if any): off()

Yes, I 1000+% agree with this statement and approach.
Drivers should provide and abstraction layer but should not be too smart.
I could be wrong, but to my eyes drivers specifically for the RGB(WW) control
are over complicated and taking actions what naturally belongs to the App level.
Frankly, I played with many different RGB(WW) controllers (Zooz, Fibaro, etc,.)
Neither one was doing right job because either device itself or driver (I don't know
exactly which one) was/is too smart. As a result - I was not able to setup
color and or brightness the way I wanted. This somewhat worked for simple RGB
bulbs/strips but absolutely did not work for the RGBW(W) devices.
There was absolutely impossible to turn on R, G, B and W(W) channels at the same
time. You can control either RGB or W(W) channels but not both.
And I saw many complains regarding this "feature".

I am actually trying to modify a "child device driver".

The command sendData() is defined and it works for everything else:

def sendData(String value) {
def name = device.deviceNetworkId.split("-")[-1]
parent.sendData("${name} ${value}")
}

I agree with this. :slight_smile: If you're able to use Hubitat's standard ColorControl and ColorTemperature capabilities, then you probably wouldn't need to use this custom command. For example, RGB is easily converted to HSB/HSV, and that is exactly what Hubitat uses for its standard setColor() command (part of the ColorControl capability).

At the app level, you could then ask the user for RGB input if that's really what you want instead of HS.

If your device lets the (tuneable?) white and color pieces work independently, you might need to make two child devices, one for the RGB portion and one for the CT (or white?) portion. Hubitat's standard capabilities assume that a single device can operate in only one of these modes at a time, not both at the same time.

If this isn't working, I'd first suggest looking at your logs for errors. Otherwise, add logging to your parent device to see what it's doing (if anything). Inserting your own logging during development is probably the best debugging "tool" we have on Hubitat.

Thank you so much for you help.

The method:

def setColorByRGB(red, green, blue) {

sendData("off")

}

works! It does send an "off" command just for the testing.

Now I have to combine R, G, B values into a HEX string and send it out.
I guess, I know how to deal with this.

I learned a lot from yours examples
I was confused what actually creates a visible and clickable Button
on the Device Driver page. Somehow I did not find this in a docs
and drivers I checked out.

Once again - BIG THANK YOU!

1 Like

Each RGB(WW) Device on a physical layer has completely independent control
for each channel. Number of channels depend on number of LEDs.

Well, I figured this out in a hard way but this is very bad assumption and better to be
reconsidered. The only potential problem with this - current may go too high.
For the RGBWW Bulbs this should not be a big deal - all LEDs are close.
For the strips this could be a big problem because of voltage drop across the
long strip. But this problem also not a show stopper.
Bottom line - on a physical layer all channels are completely independent.
So Hubitat better to treat them independently as well.

It's not ultimately a limitiation, just a limit for a single a device. The solution would be to use child devices. You could have one for each channel, maybe each implementing the Switch (on()/off()) and SwitchLevel (setLevel()) capabilities, or whatever makes sense for your device and the way you want to use it. The parent device can implement whatever commands you want, or it can do basically nothing at all except act as a "container" to group these child devices.

Also, this "limitation" is, in this case, mostly related to the fact that attributes not related to the mode that colorMode suggests the device is in may be ignored by an app. If you're only using your own apps with the device, you can treat the attributes however you want. You'll just need to be aware that, say, capturing the device in a Hubitat scene or the "capture" actions in Rule Machine will assume the standard behavior.

1 Like

Well,
As I already mentioned, I played with many standard RGB(WW) Devices and related Drivers
(few custom drivers as well). I forgot which one (Fibaro?) offered a dedicated Child Device
for the White LED Channel. My first impression was - "yes, this is exactly what I need".
But NO, this Toy and/or its Driver did not work as expected. I am not sure was it just Driver
or maybe even Device itself had unnecessary built-in intelligence.
The result was/is - as soon as you turn On White Channel, RGB goes Off and vice versa.
Finally I gave up on all these out of the shelf solutions for the RGBWW control for Strips
(I am not using Color Bulbs at all but do need few Color Strips) and switched to 100%
custom solution. Thanks to the HubDuino project, created by @ogiewon I can build
Custom HW Controller based on cheap ESP8266 Boards (I already created RFID Reader
and NeoPixel Strip HW Controllers, few more inline) and many BIG Thanks to you,
now I learned how to create a Custom Drivers for all these toys.
The final result is - now I am very happy HE user because now I can build virtually
anything I want (HW and/or SW).
Writing Custom Apps maybe a next step but as of today I am very happy just
with RM (so far I did not have any case for writing custom app but this may change).

1 Like