Bitwise operations in a modbus register

I’ve covered this before, but it got a bit lost in a general thread. @kazemiprince flushed out my errors, thankyou :smiley: so we can present a tested solution here.

Problem
There are few “rules” in Modbus, each manufacturer decides their own way how to present device data and controls.
Even though Modbus provides ‘discrete’ inputs and ‘coil’ outputs for binary controls, such as go/nogo limit switches or on/off relays, sometimes a designer opts to present binary values as individual bits packed into a standard 16-bit Modbus register.
The version 2 Modbus binding provides a convenient means to extract bits to openHAB Items when reading, but writing bits to a register in a device is complicated. More on why later.

Reading packed bits
Let us imagine a Modbus device where the manual tells us

Holding register 8 represents temperature in degrees C
Holding register 10 bit 0 represents “Fan running”
Holding register 10 bit 1 represents “Vent open”
Holding register 10 bit 2 represents “Damper open”

Always take care with Modbus manuals; sometimes they talk register numbers (which start at “1”) and sometimes register addresses (which start at “0”)
Our binding Things configuration uses addresses (starting at 0), so sometimes you will need to “take one off” to hit the correct register.

Our binding counts the least significant bit in a byte/register/word as address 0, luckily there is usually less confusion about this part.

Well, we have enough to configure some Modbus Things, as per the binding docs. I give the example in text files, but you could use PaperUI if you prefer.

modbus.things

Bridge modbus:tcp:HVAC [ host="192.168.0.12", port=502, id=2 ]
{
	Bridge poller hvacpoll [ start=8, length=3, refresh=800, type="holding" ]
        {
           Thing data reg08 [ readStart="8", readValueType="int16" ]
           Thing data di0 [ readStart="10.0", readValueType="bit" ]
           Thing data di1 [ readStart="10.1", readValueType="bit" ]
           Thing data di2 [ readStart="10.2", readValueType="bit" ]
         }
}

The slave Bridge is configured in the usual way.
The poller also, to fetch registers 8-10.
The data Things introduce a new address notation, “10.0” means register 10 bit 0
Note - that we can mix how we use different registers from one poller
Note - that do not have to “use up” all the registers the poller fetches; in this case we ignore register 9 but use 8 and 10.

hvac.items

Number HVAC_Temp  "Temp [%d]" { channel="modbus:data:HVAC:hvacpoll:reg08:number" }
Switch HVAC_Fan   "Fan [%s]"  { channel="modbus:data:HVAC:hvacpoll:di0:switch" }
Contact HVAC_Vent   "Vent [%s]"  { channel="modbus:data:HVAC:hvacpoll:di1:contact" }
Contact HVAC_Damper   "Damper [%s]"  { channel="modbus:data:HVAC:hvacpoll:di2:contact" }

So, that is reading packed bits, not too difficult.

Writing packed bits
Modbus protocol specifies a complicated “write mask register” method with FC22 that is capable of writing to individual bits in a slave register.
Unfortunately openHAB binding does not support FC22 - but I’ve never seen a device yet that actually supports it either.

That leaves no other way to write to a single bit in a 16-bit holding register. We must (re)write the whole register to get the bit you want set or unset.
Now, we have to make a decision about what to do about the rest of the bits in that register - should we read how the register is set just before we write it? Or should we keep a record in openHAB of how we think it ought to be set, and use that?

EDIT - from openHAB version 3, the Modbus binding now includes bitwise writing, see the binding docs.

For older versions, the method presented here is to maintain an image of the whole register in openHAB. It does get read from the slave by polling, so should be well up to date.

Using our HVAC example, let us now imagine we want to write to those bits to control the fan on or off etc.

modbus.things

Bridge modbus:tcp:HVAC [ host="192.168.0.12", port=502, id=2 ]
{
	Bridge poller hvacpoll [ start=8, length=3, refresh=800, type="holding" ]
        {
           Thing data reg08 [ readStart="8", readValueType="int16" ]
           Thing data reg10 [ readStart="10", readValueType="uint16", writeStart="10", writeValueType="uint16", writeType="holding" ]
           Thing data di0 [ readStart="10.0", readValueType="bit" ]
           Thing data di1 [ readStart="10.1", readValueType="bit" ]
           Thing data di2 [ readStart="10.2", readValueType="bit" ]
         }
}

What we’ve done here is add a Thing to represent the whole register, as well as keeping the bitwise Things.
The whole register Thing also has a write ability, but the bit Things can remain read-only.

hvac.items

Number Reg_Image  "HVAC register [%d]" { channel="modbus:data:HVAC:hvacpoll:reg10:number" }
Group HvacBits
Switch HVAC_Fan   "Fan [%s]"  (HvacBits) { channel="modbus:data:HVAC:hvacpoll:di0:switch", autoupdate="false" }
Switch HVAC_Vent   "Vent [%s]"  (HvacBits) { channel="modbus:data:HVAC:hvacpoll:di1:switch", autoupdate="false" }
Switch HVAC_Damper   "Damper [%s]"  (HvacBits) { channel="modbus:data:HVAC:hvacpoll:di2:switch", autoupdate="false"  }

So we add a Number Item to be our whole register image. This will get updated from Modbus polling regularly.
We’ve kept the individual Items (all Switch type now, so that we can send commands from UI or rules) but have put them in a Group to make it easier to handle them together. These Switches we’d put into a sitemap.

A non-obvious trick we have used is to disable autoupdate for the writable Items; because Modbus is regularly updating them, we don’t want autoupdate interfering with its own state predictions.
EDIT - the exception is the image Item, if we do allow that to autoupdate our “cache” will work better.

At this stage, our Switch Items should show what state the HVAC controls are in. But nothing happens when we command them ON/OFF from openHAB UI or rule, the Modbus bit channels are read-only - we need a rule to do that part.

hvac.rules

import java.lang.Math    // we need this for testBit
                                      // or we may need to use another import

rule "update hvac register"
when
   Member of HvacBits received command
then
   var BigInteger register = 0  // we need BigInteger type to use testBit
   if ( Reg_Image.state != NULL && Reg_Image.state != UNDEF) { // avoid using invalid register image
      register = (Reg_Image.state  as DecimalType).toBigDecimal.toBigInteger   // use existing image
   }
   switch triggeringItem.name {    // act on switched Item name to change only one bit
      case "HVAC_Fan" : {
         if  (receivedCommand == ON) {
            register = register.setBit(0)
         } else if (receivedCommand == OFF) { // by testing both, we will ignore REFRESH commands
            register = register.clearBit(0)
         }
      }
      case "HVAC_Vent" : {
         if  (receivedCommand == ON) {
            register = register.setBit(1)
         } else if (receivedCommand == OFF) {
            register = register.clearBit(1)
         }
      }
      case "HVAC_Damper" : {
         if  (receivedCommand == ON) {
            register = register.setBit(2)
         } else if (receivedCommand == OFF) {
            register = register.clearBit(2)
         }
      }
   }
    // at last, write to Modbus
    Reg_Image.sendCommand(register)
end

The rule simply sets or resets bits, according to which Item received what command, then sends the whole register image as a command to be dealt with by the Modbus binding.

There is no synchronizing here, and the rule could mess up if you send multiple commands in quick succession from a rule. Safest to have them wait a little more than one polling period between commands.

EDIT - fixed some typos in rule

3 Likes

Well, this is odd. Despite previous testing, the rule given above no longer works for me - the import does not work… Not sure which version of what changed, but here I give an alternative that works. Note, the rule needs a change, as well as the import.

import java.math.BigInteger // we need this for testBit

rule "update hvac register"
when
    Member of HvacBits received command
then
   var register = BigInteger.valueOf(0)  // we need BigInteger type to use testBit
   if ( Reg_Image.state != NULL && Reg_Image.state != UNDEF) { // avoid using invalid register image
       register = (Reg_Image.state as DecimalType).toBigDecimal.toBigInteger // use existing image
   }
// etc etc

Comment, the binding may put WARN messages in your openhab.log when sending commands to your read-only “bit” channels; these can safely be ignored.

Hi, Please help me …
I can’t handle the assignment of holding register to switch in openHAB.
.things

`vim: syntax=Xtend

Bridge modbus:tcp:localhostTCP [ host="192.168.1.130" , port=502, id=1 ]
{
ridge poller holding [ start=00001, length=10, refresh=1000, type="holding" ] {
        Thing data holding00001_1 [ readStart="00001.1", readValueType="bit", writeStart="00001.1", writeValueType="bit", writeType="holding" ]
        Thing data holding00002_1 [ readStart="00002.1", readValueType="bit", writeStart="00002.1", writeValueType="bit", writeType="holding" ]
}
}`

.sitemap

'sitemap dom label="dom"
{
    Frame {
        Switch item=DO1
        Switch item=DO2
    
    }

}

Please give me a hint if things and sitemap are ok, it should look like the syntax of iteams so that I can read / write holding register

Can’t see your Items, can’t comment on them.

You clearly haven’t understood the above about writing bits over Modbus to holding registers. - you can’t.

Take out your write params, and get stuff working as read to begin with.

Now i have it :
items:

Switch reg40001_bit_1_2 “Open Pool [%s]” (reg40001_Bits) { channel=“modbus:data:id2:poolpoll:bit_1_2:switch” }

things

vim: syntax=Xtend

Bridge modbus:tcp:id2 [ host="192.168.1.130" , port=502, id=1 , timeBetweenTransactionsMillis=100 ]
{
Bridge poller poolpoll [ start=1, length=10, refresh=1000, type=“holding” ]
{
Thing data reg40001 [ readStart=“1”, readValueType=“uint16”, writeStart=“1”, writeValueType=“uint16”, writeType=“holding”, writeMultipleEvenWithSingleRegisterOrCoil = “true” ]
Thing data bit_1_2 [ readStart=“1.2”, readValueType=“bit” ]
}
}

rules

import java.math.BigInteger // we need this for testBit

rule "update reg40001 register"
when
    Member of HvacBits received command
then
   var register = BigInteger.valueOf(0)  // we need BigInteger type to use testBit
   if ( Reg_Image.state != NULL && Reg_Image.state != UNDEF) { // avoid using invalid register image
       register = (Reg_Image.state as DecimalType).toBigDecimal.toBigInteger // use existing image
   }

switch triggeringItem.name { // act on switched Item name to change only one bit
case “reg40001_bit_1_0” : {
if (receivedCommand = ON) {
register = register.setBit(0)
} else if (receivedCommand = OFF) { // by testing both, we will ignore REFRESH commands
register = register.clearBit(0)
}
}
case “reg40001_bit_1_1” : {
if (receivedCommand = ON) {
register = register.setBit(1)
} else if (receivedCommand = OFF) {
register = register.clearBit(1)
}
}
case “reg40001_bit_1_2” : {
if (receivedCommand = ON) {
register = register.setBit(2)
} else if (receivedCommand = OFF) {
register = register.clearBit(2)
}
}
}
// at last, write to Modbus
reg40001.sendCommand(register)
end

and not work,
what i’m doing wrong…?

Would you expand on “not work”?

Have you a Group Item called HvacBits ?

bit Read work but write doesn’t
things

Bridge modbus:tcp:id2 [ host="192.168.1.130" , port=502, id=1 , timeBetweenTransactionsMillis=100 ]
{
Bridge poller poolpoll [ start=1, length=10, refresh=1000, type="holding" ]
{
Thing data reg40001 [ readStart="1", readValueType="uint16", writeStart="1", writeValueType="uint16", writeType="holding", writeMultipleEvenWithSingleRegisterOrCoil = "true" ]
Thing data bit_1_2 [ readStart="1.2", readValueType="bit"]
}
}

items

Number reg40001 "reg40001 register [%d]" { channel="modbus:data:id2:poolpoll:reg40001:number"}
Group reg40001_Bits
Switch reg40001_bit_1_2 "Open Pool [%s]" (reg40001_Bits) {channel="modbus:data:id2:poolpoll:bit_1_2:switch"}

rules

import java.lang.Math // we need this for testBit

rule "update reg40001 register"
when
Member of reg40001_Bits received command
then
var BigInteger register = 0 // we need BigInteger type to use testBit
if ( reg40001.state != NULL && reg40001.state != UNDEF) { // avoid using invalid register image
register = (reg40001.state as DecimalType).toBigDecimal.toBigInteger // use existing image
}
switch triggeringItem.name { // act on switched Item name to change only one bit
case "reg40001_bit_1_0" : {
if (receivedCommand = ON) {
register = register.setBit(0)
} else if (receivedCommand = OFF) { // by testing both, we will ignore REFRESH commands
register = register.clearBit(0)
}
}
case "reg40001_bit_1_1" : {
if (receivedCommand = ON) {
register = register.setBit(1)
} else if (receivedCommand = OFF) {
register = register.clearBit(1)
}
}
case "reg40001_bit_1_2" : {
if (receivedCommand = ON) {
register = register.setBit(2)
} else if (receivedCommand = OFF) {
register = register.clearBit(2)
}
}
}
// at last, write to Modbus
reg40001.sendCommand(register)
end
`

Did you see post two above?
Post one shows the general method, and a rule that works with one import.
Post two shows you an alternate import and a rule change.
I don’t know which one works in your environment.

I changed java.lang.Math to java.math.BigIntege

further error:

2020-01-02 21:32:08.633 [WARN ] [ernal.handler.ModbusDataThingHandler] - Thing modbus:data:id2:poolpoll:bit_1_2 'Modbus Data': not processing command OFF since writeStart is missing and transformation output is not a JSON
2020-01-02 21:32:08.768 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule 'update reg40001 register': An error occurred during the script execution: Couldn't invoke 'assignValueTo' for feature param receivedCommand

You cannot set the receivedCommand implicit variable to ON
You’ll be wanting to test if it is equal to ON …
if (receivedCommand == ON)

Rossko57 thank you for the tips and help.
Many thanks again

1 Like

I hope everything you needed was actually in the first two posts, please point out anything that needs highlighting.

Dear @rossko57,
thanks for your work, it’s a really interesting solution!

Can I ask if is possible to measure the impact of those setup on the performance?

I would like to use openhab with a building automation setup based on 4 core controller that work like a big plc with many in and outs, that can communicate with the rest of the world as a Modbus TCP slave.
Sadly one of the most useful item to control, the virtual points (boolean variable usable in the logic) are accessible only as a register of 16 each, so this is the exact that need the use this bitwise operation on a word.
The problem is that I need to control about 250 of those virtual points for each core controller, that are randomly distributed in the various registers (usually 3 to 7 in the same register), so I have to configure a big amount of single bit for reading, support register and rules.

So before starting doing a big work like that, there’s any way I can predict if the performance are acceptable or if there’s better solutions? (like for example, working under the hood and writing an optimized “driver” to do that work?

Thanks
Cheers Mix

No idea, you’d have to run it and see. It should be easy enough to create a simulation.
Bear in mind the rule only runs when you write to a bit - how often does that happen? If you’re only turning lights on and off, it’s likely no impact at all.

You’ll note that it would have to do essentially the same thing - build a register image from bits.
There is certainly the possibility to optimize, but removing the function to an external script makes it harder to get at the OH data that you need to build the register image.
The code might be more efficient rewritten in another OH rules language, like jython, I couldn’t say, but that does leave you the ease of access in place.

It’s a poor design to start with.
Modbus itself can interface to tens of thousands of individual binary registers (“coils”). If your “PLC” doesn’t implement that, they have done you no favours.

There is a Modbus protocol feature allowing for individual bit writing in a holding register - FC22. Find out if your PLC supports that.
If it does, it would require an openHAB binding enhancement to implement at “our” end - there’s never been a need before, it is so rarely used.

EDIT - if your “PLC” is independently updating changing bits in the registers, you might need to use a different strategy. Else you run the risk of openHAB writing a register image based on its last poll, before the PLC changed a bit.

Hey rossko57
as per PM, thanks for this!

I have two questions / problems. I will start with the easier one following your guide. I receive the following error

2020-09-19 21:52:32.545 [ome.event.ItemCommandEvent] - Item 'HidroCoverEnable' received command OFF

==> /var/log/openhab2/openhab.log <==
2020-09-19 21:52:32.573 [WARN ] [ernal.handler.ModbusDataThingHandler] - Thing modbus:data:oxilifepool:register1068:HidroCoverEnable 'Modbus Data': not processing command OFF since writeStart is missing and transformation output is not a JSON

==> /var/log/openhab2/events.log <==
2020-09-19 21:52:32.578 [ome.event.ItemCommandEvent] - Item 'HidroCover_Image' received command 0

Thing

Bridge modbus:serial:oxilifepool [port="/dev/ttyS0", baud=19200, dataBits=8, id=1, parity="none", stopBits="1.0", encoding="rtu", timeBetweenTransactionsMillis=1000 ] {
   Bridge poller register1068 [ start=1068, length=2, refesh=5000, type="holding", maxTries=10 ] {
      Thing data reg1068 [ readStart="1068", readValueType="uint16", writeStart="1068", writeValueType="uint16", writeType="holding" ]
      Thing data HidroCoverEnable [ readStart="1068.0", readValueType="bit" ]
   }
}

reg1068 = 1
HidroCoverEnable = 1

Items
(also tried using number instead of switch, result is the same)

// HidroCover Config
Number HidroCover_Image "HidroCover register [%d]" { channel="modbus:data:oxilifepool:register1068:reg1068:number", autoupdate="false" }
Group HidroCover
Switch HidroCoverEnable "Abdeckung ist [%s]" (HidroCover) { channel="modbus:data:oxilifepool:register1068:HidroCoverEnable:switch", autoupdate="false" }

Sitemap
Basic…
Switch item=HidroCoverEnable

hidrocover.rules

import java.math.BigInteger    // we need this for testBit

rule "update hidrocover register"
when
   Member of HidroCover received command
then
   var BigInteger register = BigInteger.valueOf(0)  // we need BigInteger type to use testBit
   if ( HidroCover_Image.state != NULL && HidroCover_Image.state != UNDEF) { // avoid using invalid register image
      register = (HidroCover_Image.state as DecimalType).toBigDecimal.toBigInteger   // use existing image
   }
   switch triggeringItem.name {    // act on switched Item name to change only one bit
      case "HidroCoverEnable" : {
         if (receivedCommand == ON) {
            register = register.setBit(0)
         } else if (receivedCommand == OFF) { // by testing both, we will ignore REFRESH commands
            register = register.clearBit(0)
         }
      }
   }
    // at last, write to Modbus
    HidroCover_Image.sendCommand(register)
end

Register description

Register : 0x042C
Name : MBF_PAR_HIDRO_COVER_ENABLE
Description : This register holds the options for the hydrolysis/electrolysis module.
Bits Máscara Descripción
0    0x0001  MBMSK_HIDRO_COVER_ENABLE
1    0x0002  MBMSK_HIDRO_TEMPERATURE_SHUTDOWN_ENABLE

EDIT: Nevermind, I forgot to add writeMultipleEvenWithSingleRegisterOrCoil=true

It’s only a WARN. The binding complains that a channel has received a command, but the channel is not configured for writing.
In fact that is a perfectly reasonable way to configure, just as it’s perfectly reasonable for the binding to warn us in case we forgot something.

But as it comes as surprise to folk this message has been changed to [DEBUG] from version 2.5.9 i.e. you won’t see it unless you look for it.

Seeing as this cropped up, additional to go with “bitwise”

Byte wise operations.

A variation of the bitwise problem can arise when dealing with 8-bit bytes packed into standard Modbus 16-bit registers.
The binding provides convenient tools to extract byte values.
But, as with bits, the Modbus protocol itself gives no easy way to write individual bytes, and we are compelled to write the whole register.

Reading packed bytes
Let us imagine a Modbus device where the manual tells us
Holding register 100 ‘top’ 8 bits are a counter for “red”
Holding register 100 ‘bottom’ 8 bits are a counter for “blue”

Our binding counts the least significant byte in a register/word as byte 0, luckily there is usually less confusion about this part. Address notation is using a dot in address, like bits.
So we can make some data Things –

           Thing data lobyte [ readStart="100.0", readValueType="uint8" ]
           Thing data hibyte [ readStart="100.1", readValueType="uint8" ]

And some Items to suit

Number red_Count "Count red stuff [%d]" { channel="modbus:data:gubbins:somepoll:hibyte:number" }
Number blue_Count "Count blue stuff [%d]"  { channel="modbus:data:gubbins:somepoll:lobyte:number" }

And that’s it, reading out the two bytes from one register into two numeric Items.

Writing packed bytes
There is no simple way to write to a single byte in a 16-bit holding register. We must (re)write the whole register to get the byte you want set.
Once again, a decision about what to do about the other byte in that register - should we read how the register is set just before we write it? Or should we keep a record in openHAB of how we think it ought to be set, and use that? As with bits, we will show a method to maintain an image of the whole register in openHAB. If you configure so it does get read from the slave by polling, so should be well up to date.
So we’ll add a whole-register data Thing to the hi/lo bytes

           Thing data bothbytes [ readStart="100", readValueType="uint16", writeStart="100", writeValueType="uint16", writeType="holding" ]

And an Item for the whole image

Number Reg_Image  "whole register [%d]" { channel="modbus:data:gubbins:somepoll:bothbytes:number", autoupdate="false" }

And a rule to write individual bytes by merging into the image, or rather we can simply use two rules for the two bytes.
No import needed this time.


rule "write lobyte"
when
   Item blue_Count received command
Then
      // get target, ensure 0-255 
   val target = (receivedCommand as Number).intValue.bitwiseAnd(0x00FF)
      // get image low byte
   val imagelo = (Reg_Image.state as Number).intValue.bitwiseAnd(0x00FF)
   if (imagelo != target) {
         //need to update, get high byte
      val imagehi = (Reg_Image.state as Number).intValue.bitwiseAnd(0xFF00)
          // merge and write
      Reg_Image.sendCommand(imagehi + target)
   }
end

rule "write hibyte"
when
   Item red_Count received command
Then
      // get target, ensure 0-255 
   var target = (receivedCommand as Number).intValue.bitwiseAnd(0x00FF)
      // now shift bits 0-7 to 8-15
   target = target * 256
      // get image high byte
   val imagehi = (Reg_Image.state as Number).intValue.bitwiseAnd(0xFF00)
   if (imagehi != target) {
         //need to update, get low byte
      val imagelo = (Reg_Image.state as Number).intValue.bitwiseAnd(0x00FF)
         // merge and write
      Reg_Image.sendCommand(imagelo + target)
   }
end

I’ve left out “nice” things like state validity checks for this demo.

1 Like