Help with bitmasking a modbus address

Hello!

I’m a newbie on HA so please be patient with me :slight_smile:

I have a rpi4 with modbus rtu hat, and a SRNE solar inverter connected + a few other sensors.

Normal holding register readings is working fine! And i have learned me the code. But i have some addresses for “state of charge”, “current fault” etc… and those addresses give a feedback in eatch bit.

Example from modbus protocoll:
Function: Holding, Address: 10B, Lenght: 1, Name: Charge State
Bits:
0x0000: Charge off
0x0001: Quick charge
0x0002: Const voltage charge
0x0003 Reserved (not in use)
0x0004: Float charge
0x0005: Reserved
0x0006: Li battery acitvate
0x0007: Reserved (not in use)

Can someone please help me with this?

I have looked around the forum but nothing i can use with my skill level.
And feel free to explain the code for me, so i can use it with other addresses to :slight_smile:

So summarized its binary sensors inside one adress, is my tought.
And it would be nice to name them in code, so the text shows as active or not.

Here is a snip of my config, that is working:

#Modbus RTU HUB
modbus:
  - name: "hub_rtu"
    type: serial
    method: rtu
    port: /dev/ttyS0
    baudrate: 9600
    stopbits: 1
    bytesize: 8
    parity: N
    delay: 5
    retries: 20
    retry_on_empty: true
    close_comm_on_error: false

#SENSORER
#Temp inngangsmodul på RTU
    sensors:
      - name: "Temp modul inngang 1" # 0x0200
        unique_id: 4512
        slave: 4
        address: 512
        input_type: holding
        count: 1
        precision: 1
        scale: 0.1
        unit_of_measurement: °C
        device_class: temperature
        state_class: measurement
        scan_interval: 10

I have tried a couple of code snippets i’ve found, but with no success.
Eks:

binary_sensor:
      - platform: template
        sensors:
        e3dc_emergency_power_available:
        friendly_name: “E3DC Emergency Power available”
        value_template: “{{ states(‘sensor.Inverter_Charge_state’)|int|bitwise_and(0) > 0 }}”

Thank you in advance,
Marius

Hi, sorry I don’t understand completely, but if you are trying to do bitwise operations on a hex string value, here is something you may want to play with in DevTools->Template:

{% set hexstring = '0x0004' %}
{{ hexstring | int(base=16) | bitwise_and(4) }}

The result is 4. You can do similarly for other hex values.

Your return value consist of 8 bits.
Each bit can be 0 or 1 and each bit represent one of your read outs.

00000001 will be Charge off
00000010 will be Quick charge
00000100 will be Const voltage charge
00001000 is reserved
and so on

Multiple of the readouts might be true at the same time, so Quick charge + Float charge will be
The bytes are just added to each other.

00000010
00010000
--------
00010010

If you look at the values of these in the decimal system (10 digits) then it goes like this.
00000001 = 1
00000010 = 2
00000100 = 4
00001000 = 8
00010000 = 16
00100000 = 32
01000000 = 64
10000000 = 128

And the added bytes from before
00010010 = 2 + 16 = 18

The good part about this is that if you select a bit and add all the numbers to the right of it, then it will sum up to the bit you have selected -1.
You can use this if you want to check if a byte have bits set.
First convert it to decimal system (10 digit) then test for the highest value first (128).

Is it at or above 128, then bit 8 is set.
If it is set then subtract 128 from the number and move to on
If it is not set the just move on.

Now is it at or above 64 then bit 7 is set.
If it is set then subtract 64 from the number and move to on
If it is not set the just move on.

Now is it at or above 32 then bit 6 is set.
If it is set then subtract 32 from the number and move to on
If it is not set the just move on.
and so on.

If you want to do it in a loop then
128 = 2^(8-1)
64 = 2^(7-1)
32 = 2^(6-1)
and so on.

I hope this make sense.
If you want a tool to do the conversions, then set the Windows Calculator to Programmer in the settings. That is if you sue Windows. :slight_smile:

If you want it as bit operations, then AND is the solution.
The added number from before was 00010010 and if we test it with and AND operation with the readout we want to test like Charge off (0x0000 = 00000001)

00010010 <- Readout
00000001 <- Test value
------------
00000000 <- Result

Here the result is 0, because there were no places where both values had a 1.
If we test it with Float charge (0x0004 = 00010000)

00010010 <- Readout
00010000 <- Test value
------------
00010000 <- Result

Here the result will be 16 (= 00010000 =0x0004)
So if result is equal to test value, then you know those bits are set in the byte.

1 Like

Thanks for the long reply! I think i’m on to something. And i leard a lot!

Here is a snippet of the code, im a on to somthing :slight_smile:

      - name: Inverter_Charge_state # 0x010B
        unique_id: 1267
        slave: 1
        address: 267
        input_type: holding
        data_type: uint
        count: 1
        precision: 1
        scale: 1
        scan_interval: 10
        
    binary_sensor:
      - platform: template
        sensors:
        Inverter_Charge_Off:
        friendly_name: “Inverter_Charge_Off”
        value_template: “{{ states(‘sensor.Inverter_Charge_state’)|int|bitwise_and(0) > 0 }}”
        Inverter_Charge_Quick:
        friendly_name: “Inverter_Charge_Quick”
        value_template: “{{ states(‘sensor.Inverter_Charge_state’)|int|bitwise_and(1) > 0 }}”
        Inverter_Charge_Const_Voltage:
        friendly_name: “Inverter_Charge_Const_Voltage”
        value_template: “{{ states(‘sensor.Inverter_Charge_state’)|int|bitwise_and(2) > 0 }}”
        Inverter_Charge_Float:
        friendly_name: “Inverter_Charge_Float”
        value_template: “{{ states(‘sensor.Inverter_Charge_state’)|int|bitwise_and(4) > 0 }}”

Thanks!

Well, I had to come around both decimal and binary system, but the fact that you use an AND operation shows that it was worth it. Any programmer will say that is the “correct” way of doing it. :slight_smile:

@WallyR if I have a modbus register if i have a modbus register with a 2nd byte how can i read the bits?

i was able to read the first 8 bits but i don’t know how to get those of byte1 adding attributes on existing sensor

template:
  - sensor:
      - name: zcs_faults
        state: "{{ states('sensor.Modbus_ZCS_Voltaggio_Rete') }}"
        attributes:
          grip_ovp: "{% if states('sensor.ZCS_Fault_1') | int | bitwise_and(1) %}true{%
          else %}false{% endif %}"
          grid_uvp: "{% if states('sensor.ZCS_Fault_1') | int | bitwise_and(2) %}true{%
          else %}false{% endif %}"
          grid_ofp: "{% if states('sensor.ZCS_Fault_1') | int | bitwise_and(4) %}true{%
          else %}false{% endif %}"
          grid_ufp: "{% if states('sensor.ZCS_Fault_1') | int | bitwise_and(8) %}true{%
          else %}false{% endif %}"
          island_fault: "{% if states('sensor.ZCS_Fault_1') | int | bitwise_and(128) %}true{%
          else %}false{% endif %}"

How did you read the first 8 bits?
If there is an address, then add 8 in hexadecimal to it or if there is a number of bits being read then increase it to 16.

I’ve added attributes to an existing sensor, as you can see from the code above.
The table consists of 18 faults, each fault, as you can see, has 2 bytes with 8 bits each. Each fault has its own address: fault 1 address is 0405, fault 2 is 0406, and so on. In modbus.yaml I have the sensors for this registers

      - name: "ZCS Fault 1"
        slave: 1
        address: 0x0405
        input_type: holding
        data_type: uint16
        scan_interval: 60
        lazy_error_count: 3

Can you give me an example please? I’m no expert

The uint16 should mean you read16 bits

Sorry but I don’t understand, could you explain me with an example how to read the 2nd byte?

Ahh, I see now what you need.

In the first post you have some code with a bitwise_qnd command followed by a number.
Just continue that serie to read more values out.

1 Like