MODBUS help needed - Ampinvt solar controller to Home Assistant via ESPhome

Exactly !! :slightly_smiling_face: The possibilities are endless it seems for that code sniplet :laughing::sunglasses:

Byte count (index) starts at zero [0]
So the three fixed preceeding bytes are at pos[0], [1] and [2]

Checking for the bytes read using if (bytes.size() == 262) is the quantity of bytes read. So nothing extra has to be added here

Understood, I recognise where they are in the string but in the C== code, does the count to that point start at 0 or 1? If 0, my first set of data is at bytes 6 and 7. If 1, it is at 7 and 8.

As the datasheet bytes also start with byte 0, the byte number will match the index no.
Meaning PV Voltage is at index[6] (msb) and [7] (lsb)

image

That’s how I figured it too. So, my battery voltage which is pretty static (11.8v) is driven from bytes 8 & 9, 04:9F which, when divided by 100 to get 2 decimal places delivers 11.8v. The sensor is reporting 404.5V and I can’t figure out why.

Do I need to reverse the 0 and 1 byte indexes?

battery_voltage_value.Byte[0] = bytes[8]; // Battery Voltage high byte
battery_voltage_value.Byte[1] = bytes[9]; // Battery Voltage low byte

Heh! That was the issue…

You got it I see :slight_smile:

Yep, msb before lsb.
049Fh = 1183 dec. = 11,83 v

Looking more like it should!

Indeed. You still have an issue with the temp readings.
Your battery is about to go ballistic :rocket::slightly_smiling_face:

Thats the ‘interesting’ challenge that comes next. The hex reading for that is at position 16 & 17 so, currently, that is 10:25 in hex byte values. It seems that there is a curiosity in hex temperature values where the high byte (MSB) denotes positive or negative and the low byte (LSB) then denotes the temperature.

With the code I currently have, the bytes 10:25 are being calculated to deliver the int16 value of 4133 which is them being processed to 413.3C.

With the MSB setting the ±, changing byte 16 to 00 from 10 results in an int16 value of 3.7C and my actual temperature is currently -3.7*C

Do you have any suggestions on how to account for this in the code?

Hmm… That’s completely against their own documentation if this is true.
But, most likely not the first time such have happened :wink:

I would monitor both temp sensors for some time to establish data validation.
You could set msb to = 0 for some hours/days, while testing in order to get some proper sensor measurements.

It should be fairly easy to do a sensor lambda in ESPHome that checked msb value. And if msb=0x10 then recalculate lsb by deducting lsb value twice from the original value.

I have the data validation and I researched solutions but still don’t have anything I understand. It seems to be a common question in C++ forums at places like StackOverflow and, to my own surprise, I understand how it is occurring and why. What I can’t seem to figure is how to deal with it.

Your last paragraph is intriguing as I figured this would have to occur in the C++ .h file code as that is where the MSB and LSB are being combined into a value. Am I incorrect in thinking that by the time it reaches the YAML code, it is already in the format of the decimal representation of the hex value?

@htvekov - Could I ask you to explain what is happening with these two sections of code from your file?

        TwoByte d_yield_data;
        d_yield_data.Byte[0] = bytes[0xB5 +6]; // Daily yield lsb
        d_yield_data.Byte[1] = bytes[0xB4 +6]; // Daily yield msb
        uint32_t t_yield_data = int(
            (unsigned char)(bytes[0x86 +6]) << 24 |
            (unsigned char)(bytes[0x87 +6]) << 16 |
            (unsigned char)(bytes[0x88 +6]) << 8 |
            (unsigned char)(bytes[0x89 +6]));  // Total yield (4 bytes float)

        TwoByte status_1_data;
        status_1_data.Byte[0] = bytes[0x91 +6]; // Status 1 (bit 0-7)
        status_1_data.Byte[1] = 0;
        TwoByte status_2_data;
        status_2_data.Byte[0] = 0;
        status_2_data.Byte[1] = bytes[0x94 +6]; // Status 2 (bit 8-15)

In the first block, I’m curious about the use of the uint32_t part.
In the second block, I see you annotate bits rather than bytes but I can’t figure out how they function.

Well, it’s most likely easier to do it in the custom code.
I’ve fiddled a bit and tested this below in my own prod environment.
You’ll have to replace your temperatur sensors TwoByte declare section with this instead.
Replace status_1 to the ‘real’ temp sensor name and rename status_1_data if you wish (for easy readability). Note that the publish state line also has changed as we’re publishing signed integer for this sensor.

One thing that springs to mind now is that this will only work down to -25,5 degrees (lsb = 255). Check should most likely be done on the msb bit 4 instead if you need lower negative temperatures.

EDIT: Well, an easy patch would be to just check if msb is higher or equal to 0x10 instead.
Code below revised now.

int16_t status_1_data;
        if (bytes[16] >= 0x10) {
          status_1_data = int(
            (signed char)(bytes[17] * -1));
        } else {
          status_1_data = int(
            (signed char)(bytes[17]));
        }
        status_1->publish_state(status_1_data);
1 Like

That uint32_t declared block is a pure copy/paste from somewhere else :slight_smile:
I searched for how to create an Uint32 byte from four bytes in an index and that’s what came up.
Didn’t really check what math was involved as it works.

Looking at it now it seems just to direct most significant bits at top from bit 24 to 31 ending with the less significant bits at bit 0 to 7.

You, Sir, are a certified genius! I am deeply grateful; this works perfectly!

It may be worth considering this in your own code at some point. I think I stumbled across the problem because my solar setup is in an unheated barn, in Canada, in winter. For installs in heated living accommodation, this problem would likely never arise, but I think there are many opportunities for users to experience it, such as in a vacation cabin etc.

Thank you! This has progressed so quickly with your collective help. I need to figure out how to split out bit values from a single byte into individual sensors now.

1 Like

Thank you. But I’m afraid I’m just a ‘tinkerer’ - I’ve no c++ programming experience at all :wink:
I’m just stubborn :laughing:

I haven’t really checked exactly if my Solivia inverter uses unsigned or signed values for the temp sensors ? Both sensors are internal, so upon inverter start-up they would rapidly become positive ayway.

I would once again do the bit compare in the custom component and create the sensor from that output.
Not done any c++ bit compare before, but it should be faily easy it seems:

Note that I’ve revised the msb check from if (bytes[16] == 0x10) to if (bytes[16] >= 0x10) in order for values lower than -25,5 degrees (If inverter supports it)

1 Like

Quick question for you guys…

I’m working on these binary conversions and having some issues when a thought occurred to me. Once I break out those 3 bytes to bits, the sensor surely become a ‘binary_sensor’ and not a ‘sensor’, right?

If so, wouldn’t I need to create a separate include file for the binary sensors and not try to combine them all into one include file?

Honestly, I would just skip the conversion to boolean altogether.
You probably want to convert to text. Example: “Operating status = Normal” makes more sense than “Operating status = true”.

You don’t need an additional .h-file to achieve this. Just publish the bit value (either 0 or 1, integer) to an esphome sensor set to “internal”, the same way you do it for all the other sensors. Then in yaml you can convert it to text by doing something like this:

    on_value:
      then:
        - lambda: |-
            if(x==0) {
              id(operating status_text_sensor).publish_state("Normal");
            }
            else  {
              id(operating_status_text_sensor).publish_state("Abnormal");
            }

And then offcourse add a text sensor with the id “operating_status_text_sensor”

I actually use this method in the Foxess component for the Inverter state.