Trying to convert Hexadecimal to decimal or float from PLC Modbus data

I stated the PLC data appeared to be in hexadecimal and the conversion from hex to decimal was the right number. Scaling would not work in that case I would assume. Maybe to move the decimal. I am interested but your not giving me much to work with here. I am not a YAML coder. Default imperial.

Thanks

I’m a volunteer like everyone else here who offers assistance. In fact, I don’t even use the Modbus integration but read its documentation in order to help you. However, I do agree that using scale alone probably won’t produce the result you want. That’s why I am leaving the discussion and hopefully someone with far more Modbus experience will help you.

Blockquote
I’m a volunteer like everyone else here who offers assistance.

I understand. There are also people that don’t know where to look for the answers to the questions you are asking, like myself. I had to dig around.
Thanks for trying anyway.

I posted where to look:

I also supplied a link to the scale option.

FWIW, you might want to investigate the structure option. Unfortunately, there’s only one example of its usage in the documentation but it seems to suggest that you can indicate the data’s type. Sorry I can’t be of more help.

I didn’t know it was in the HA GUI front end. I was looking at YAML file names or coding entrys. I found it after a few post later.

I’ll take a look at structure.

I searched the forum for “Modbus structure” and found this example:

        data_type: custom
        structure: ">1f"

However, you might also need to use the precision option to specify the number of decimal places. Here’s the source of the information (maybe its author can help you):

Not clue about the modbus hex/dec mismatch, but here is a crude way to go back from 1909 (i.e. 0x775) to 775:
Tested in template debugger.

{% set hex = 1909 %}


{% set m = hex // 4096 %}
{% set hex = hex - m *4096 %}
{% set c = hex // 256 %}
{% set hex = hex - c *256 %}
{% set d = hex // 16 %}
{% set u = hex - d *16 %}
{{ m*1000 + c*100 + d*10 + u }}

Blockquote
here is a crude way to go back from 1909 (i.e. 0x775) to 775:

Cool. How do I add that code to be able to display it in the front end?

modbus: 
  - name: hub1
    type: tcp
    host: 192.168.1.243   
    port: 502

    sensors:
      - name: Temp1
        address: 1024      
        data_type: int 
        unit_of_measurement: "°F"

{% set hex = 1909 %}


{% set m = hex // 4096 %}
{% set hex = hex - m *4096 %}
{% set c = hex // 256 %}
{% set hex = hex - c *256 %}
{% set d = hex // 16 %}
{% set u = hex - d *16 %}
{{ m*1000 + c*100 + d*10 + u }}

If you prepend 0x to an integer, the Jinja2 interpreter understands how to convert it to decimal.

Screenshot 2021-07-19 092943

You can’t add it to the Modbus configuration. You would have to create a Template Sensor to convert the value of sensor.temp1 to display the desired value. However, none of that is needed if you can get data_type and structure configured correctly.

I don’t think it’s a matter of format. An int is an int, ie.a bunch of bits at the end of the day. You can display it in base 2, 10 or 16, it’s still the same value.

There is likely a logic error somewhere, maybe in the PLC, that did a wrong conversion.

My reply was in reference to the example you posted containing several lines of calculations in Jinja2. It isn’t necessary to use all of that to make the Jinja2 interpreter convert a hex value into decimal (it just needs a hint that the value is hexadecimal).

As for the Modbus sensor, based on reading related posts, it’s not uncommon to receive hex values that must be converted to decimal. The solution lies in the correct use of the available options (notably data_type and structure) but I can’t experiment with them because I don’t have anything based on Modbus. It should not be necessary to employ a Template Sensor to perform the conversion.

Not sure I follow. How would you convert a decimal 1909 to decimal 775 more simply?

If the sensor reports 775, this prepends 0x to ensure it’s understood to be a hex value and then converted to an integer but informing the int function that it’s dealing with a base 16 (hex) value.

{{ ('0x' ~ states('sensor.temp1')) | int(base=16) }}

In this case, the resulting value needs to be divided by 10 because the original hex value has 1 decimal place.

{{ ('0x' ~ states('sensor.temp1')) | int(base=16) / 10 }}

Further experimentation revealed that I didn’t even need to prepend the 0x because the base=16 in the int function was sufficient.

{{ states('sensor.temp1') | int(base=16) }}

1 Like

Thanks for the help. I will start experimenting.

Well, unless I missed the point entirely, the sensor reports 1909, result of a bogus hex to dec conversion of 775, and the point was to get 775 back

Converting decimal to hex can be done with the format function. Two ways to do it:

The second way was suggested to the OP in another post (where I also presented a hex to decimal conversion :man_facepalming:).

Sheesh, I’m really off my game today. Time to push away from the keyboard!

1 Like

Thanks for the efforts. I appreciate all the ways and options to try different things.

Hey there!

Thanks for mentioning my post. Looking at initial question and my configs I found similar sensor in my network - there is no need to convert it in jinja or with math function as modbus already does it.

- name: hex_sensor
  slave: 2
  address: 0
  count: 2
  input_type: input
  data_type: custom
  structure: ">2h"
  scan_interval: 20

If you look in to the source then you’ll find out that its actually straight call to the ‘struct’ Python function. Its actually powerful and compact utility function to work with network data. Take a look at documentation - struct — Interpret bytes as packed binary data — Python 3.9.6 documentation
Not sure why Jan or someone who contributes to the modbus module did not put a link to the functions in HA documentation.

Then you have converted value without decimal that I validate and divide by 10 in a template value:

foo_v:
      friendly_name: Foo Voltage
      value_template: >-
        {% set valid = not is_state('sensor.hex_sensor', 'unavailable') %}
        {% set values = states('sensor.hex_sensor').split(',') %}
        {{ (values[0]|float / 10) if valid else 'unavailable' }}
      icon_template: mdi:sine-wave
      unit_of_measurement: V

This approach is also good for debug - when you see wrong data you can dig in to original value received by modbus before conversion. And for the end user you have nice value from template.

I usually keep separate hidden tab with all values from RS485 network. This help to identify RS485 problems by network segment.

Hope this helps.
Rostislav

Sorry, but I don’t think struct would help here.
It is used for unpacking C structs. You seem to be reading 2 registers and interpreting that as 2 short integers.

$ python3 
Python 3.8.10 (default, Jun  2 2021, 10:49:15) 
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
+ >>> import struct
+ >>> struct.unpack(">2h", b'\x00\x03\x01\x01')
(3, 257)

Unless I’m missing something, this won’t help us convert the number to hex.