Sunday, 30 May 2021

Using the Velleman K8055 USB Experiment board with OpenHAB 3.x

The venerable Velleman K8055 USB Experimenter's Board is a neat way to interface a modern computer with a selection of analog and digital I/O ports. Unfortunately, support for using it within the OpenHAB home-automation framework (where it seems like a natural fit for tinkering) has fallen by the wayside - it had a proper binding in OpenHAB v1, and kinda-sorta still worked in OpenHAB v2, but it's a non-starter in 2021 with OpenHAB v3.x.

If you're running your OpenHAB (and a connected K8055) off a Raspberry Pi however, you're in luck. Simply head over to Github where some outstanding humans have done all the hard work to get your K8055 working again. It's all nicely-documented, so go ahead and give it a try. Come back here when you've got the k8055 command-line program running and making the LEDs go on and off; I'll wait.

Right. Let's get it cooking with OpenHAB, but without going through the hoops of building a new binding. Instead, we'll harness the power of OpenHAB's exec binding, and use OpenHAB's built-in state management to get persistent control of the K8055's outputs. What do I mean by that? Well, the k8055 program is completely stateless; whenever you tell it to set the digital outputs to 147 (i.e. 8, 5, 2 and 1 HIGH), it does just that, ignoring how bits 7, 6, 4 and 3 were set before - they're going to be LOW. It doesn't OR them or mask them with the current state. In fact, it can't even tell you the current state, only what it should be AFTER executing your instructions...

On your Pi, head to ${OPENHAB_CONF}, and add:

items/velleman.items
Group:Number:SUM gVellemanOutputs "Velleman output sum"
Number VellemanD1 (gVellemanOutputs)
Number VellemanD2 (gVellemanOutputs)
Number VellemanD3 (gVellemanOutputs)
Number VellemanD4 (gVellemanOutputs)
Number VellemanD5 (gVellemanOutputs)
Number VellemanD6 (gVellemanOutputs)
Number VellemanD7 (gVellemanOutputs)
Number VellemanD8 (gVellemanOutputs)

Number VellemanA1
Number VellemanA2

// Arguments to be placed for '%2$s' in command line
String VellemanOutputArgs {channel="exec:command:setoutputs:input"}
things/velleman.things
Thing exec:command:setoutputs [command="/usr/local/bin/k8055 %2$s", interval=0, autorun=true]
misc/exec.whitelist
/usr/local/bin/k8055 %2$s
(In OpenHAB 3, for security, you've got to allow-list all the commands that exec can run)

Now we can add a stanza to our sitemap, to get some UI controls:

sitemaps/default.sitemap
...
Frame label="Velleman Outputs" {
  Switch item=VellemanD1 label="Digital 1" mappings=[0="OFF",1="ON"]
  Switch item=VellemanD2 label="Digital 2" mappings=[0="OFF",2="ON"]
  Switch item=VellemanD3 label="Digital 3" mappings=[0="OFF",4="ON"]
  Switch item=VellemanD4 label="Digital 4" mappings=[0="OFF",8="ON"]
  Switch item=VellemanD5 label="Digital 5" mappings=[0="OFF",16="ON"]
  Switch item=VellemanD6 label="Digital 6" mappings=[0="OFF",32="ON"]
  Switch item=VellemanD7 label="Digital 7" mappings=[0="OFF",64="ON"]
  Switch item=VellemanD8 label="Digital 8" mappings=[0="OFF",128="ON"]
  Slider item=VellemanA1 label="Analog 1" minValue=0 maxValue=255
  Slider item=VellemanA2 label="Analog 2" minValue=0 maxValue=255
}
...

This gives us all the controls for all of the outputs the Velleman K8055 supports:

Now we're ready to write a rule that ties everything together and makes it work persistently:

rules/velleman.rules
rule "Velleman Hardware Sync"
when
   Item gVellemanDigitals changed or Member of gVellemanAnalogs changed
then
  val dState = if (gVellemanDigitals.state == NULL) "" else "-d:" + gVellemanDigitals.state
  val a1State = if (VellemanA1.state == NULL) "" else "-a1:" + VellemanA1.state
  val a2State = if (VellemanA2.state == NULL) "" else "-a2:" + VellemanA2.state

    val formattedCommand = dState + " " + a1State + " " + a2State
   // logInfo("velleman.rules", formattedCommand) // Diagnostics if needed
    VellemanOutputArgs.sendCommand(formattedCommand)
end

So the neat "tricks" here I think are:

  • Baking the bit-twiddling logic into each Switch - some might object to putting values like 32 or 128 directly into the sitemap. I figure, you're going to have repetitive code up here in the UI, may as well extract some value out of it if it makes the logic in the rules simpler ... and it really does
  • Using the Group:Number:SUM derived group state to generate the final output byte - the other part of the solution that keeps the rule really clean - OpenHAB itself recalculates the sum of all the switch values that belong to the group gVellemanOutputs
  • Using Member of gVellemanAnalogs to reduce repetition in the when clause on the analog outputs - it's only a little thing, but I like it
It's also refreshed my memory on how powerful OpenHAB Item Groups (as opposed to the confusingly-similar Sitemap Groups) can be. When I consider how many lines of rules code my initial attempt was, and how readable the final result is, I think they are a massive win.