Looking for someone that knows C / C+ / C++ to help me over a hurdle with an include file

I’m currently working on a HA solution for a solar controller (thread here) and I have most of it complete but I know ZERO C, C+ or C++. I need to integrate some code that will pull a byte value coming in from modbus (that part is done) and break it out into bits so I can publish the bits as sensors in HA.

I’ve done a couple of tutorials but I’m at the point where I realise that I have to learn C development to do this myself.

I know C / C++ (there is no such thing as C+, FYI ;))
Please state your request in details…

Appreciate it, thank you.

General direction:
I’ve been following in the footsteps of a few guys on here that are creating HA interfaces for commonly available solar controllers but the work applies to just about anything that uses standard and non-standard modbus protocol for communication such as heat pumps, pool pumps etc. While modbus is a standard in itself, the reality is that the manufacturers customise it heavily so a lot of devices need their own version. I plan to provide a couple of these, one for the Ampinvt solar controller and then use that base to build a solution for the Renogy Rover controllers.

The premise is a C file as an include which parses hex values coming from the UART read and then sends the values to a yaml config file in Esphome to render in HA. I’m OK with the yaml and with the help of others I have figured out most of the C file.

I get a 37 byte hex string from the controller but bytes 3-5 need to be split into their bit values, with each containing 8 binary settings that need to be split out (24 sensors in total). This is what I cannot figure out and need help with. I have tried numerous ways to integrate code I found through bitmasking searches but all fail and I’m really just poking around trying to stumble across a solution. I appreciate that the ‘right’ way to do this is to learn C but it feels a bit like learning to be a brain surgeon in order to cure a headache.

My C code is as follows:

// *****************************************************************
// *          ESPHome Custom Component Modbus sniffer for          *
// *              Ampinvt MPPT Solar Controller                    *
// *                  Original code credits:                       *
// *                https://github.com/htvekov/                    *
// *               https://github.com/assembly12/                  *
// *****************************************************************

#include "esphome.h"

class ampinvtcomponent : public PollingComponent, public Sensor, public UARTDevice {
  public:
    ampinvtcomponent(UARTComponent *parent) : PollingComponent(600), UARTDevice(parent) {}

  //37 bytes total - 25 bytes used, 12 bytes unused
  Sensor *op_status           = new Sensor(); // 1 byte
  Sensor *chg_status          = new Sensor(); // 1 byte
  Sensor *ctl_status          = new Sensor(); // 1 byte
  Sensor *pv_voltage          = new Sensor(); // 2 byte
  Sensor *battery_voltage     = new Sensor(); // 2 byte
  Sensor *charge_current      = new Sensor(); // 2 byte
  Sensor *mppt_temperature    = new Sensor(); // 2 byte
  Sensor *battery_temperature = new Sensor(); // 2 byte
  Sensor *today_yield         = new Sensor(); // 4 byte
  Sensor *generation_total    = new Sensor(); // 4 byte
  
  void setup() override {
  }

  std::vector<int> bytes;

  void update() {
    while(available() > 0) {
      bytes.push_back(read());      
      if(bytes.size() < 37){
        continue;  
      }
    
      else {
      }
	    if(bytes.size() == 37) {

        int16_t op_status_value;
          op_status_value = int(
            (signed char)(bytes[3]));
        id(op_status).publish_state(op_status_value);

        int16_t chg_status_value;
          chg_status_value = int(
            (signed char)(bytes[4]));
        id(chg_status).publish_state(chg_status_value);

        int16_t ctl_status_value;
          ctl_status_value = int(
            (signed char)(bytes[5]));
        id(ctl_status).publish_state(ctl_status_value);

        TwoByte pv_voltage_value;
        pv_voltage_value.Byte[1] = bytes[6]; // PV Voltage high byte
        pv_voltage_value.Byte[0] = bytes[7]; // PV Voltage low byte
        id(pv_voltage).publish_state(pv_voltage_value.UInt16);
        
        TwoByte battery_voltage_value;
        battery_voltage_value.Byte[1] = bytes[8]; // Battery Voltage high byte
        battery_voltage_value.Byte[0] = bytes[9]; // Battery Voltage low byte
        id(battery_voltage).publish_state(battery_voltage_value.UInt16);
        
        TwoByte charge_current_value;
        charge_current_value.Byte[1] = bytes[10]; // Charge Current high byte
        charge_current_value.Byte[0] = bytes[11]; // Charge Current low byte
        id(charge_current).publish_state(charge_current_value.UInt16);

        // Necessary to accomodate negative celcius temps in unheated installations        
        int16_t mppt_temperature_value;
          if (bytes[12] >= 0x10) {
            mppt_temperature_value = int(
              (signed char)(bytes[13] * -1));
          } else {
            mppt_temperature_value = int(
              (signed char)(bytes[13]));
          }
        id(mppt_temperature).publish_state(mppt_temperature_value);
        
        // Necessary to accomodate negative celcius temps in unheated installations
        int16_t battery_temperature_value;
          if (bytes[16] >= 0x10) {
            battery_temperature_value = int(
              (signed char)(bytes[17] * -1));
          } else {
            battery_temperature_value = int(
              (signed char)(bytes[17]));
          }
        id(battery_temperature).publish_state(battery_temperature_value);

        uint32_t today_yield_value = int(
            (unsigned char)(bytes[20]) << 24 |
            (unsigned char)(bytes[21]) << 16 |
            (unsigned char)(bytes[22]) << 8 |
            (unsigned char)(bytes[23]));
        id(today_yield).publish_state(today_yield_value);

        uint32_t generation_total_value = int(
            (unsigned char)(bytes[24]) << 24 |
            (unsigned char)(bytes[25]) << 16 |
            (unsigned char)(bytes[26]) << 8 |
            (unsigned char)(bytes[27]));
        id(generation_total).publish_state(generation_total_value);

        bytes.clear();
      }
      else {
      }    
    }    
  }

  typedef union
  {
    unsigned char Byte[2];
    int16_t Int16;
    uint16_t UInt16;
    unsigned char UChar;
    char Char;
  }TwoByte;};

Lines 43 to 56 are where I am at currently, trying to pass the hex values directly to yaml so that I can try and break them out in a lambda in esphome but I sense that the ‘right’ way, or cleaner way at least, is to process the bytes to bits in C then pass the bit values to yaml instead.

The protocol doc showing the breakout for the bytes is here.

Thanks again.

Some initial remarks

  • Maybe int has a size of 8 bits on ESP (IDK), but to be explicit, just use uint8_t everywhere, e.g.
std::vector<uint8_t> bytes

same for all the signed char, unsigned char

  • From the doc, your bit are booleans, so you should probably have a bunch of additional binary sensor,s e.g. operating status, battery status, …

  • To get the actual value, use something like

#define BIT_OPERATING_STATUS  0x1 // 00000001
#define BIT_BATTERY_STATUS    0x2 // 00000010
[...]
unint8_t op_status_byte = (uint8_t)(bytes[3]);
id(op_status).publish_state((bool)((op_status_byte & BIT_OPERATING_STATUS) == 1));
id(battery_status).publish_state((bool)((op_status_byte & BIT_BATTERY_STATUS) == 1));

I read a little about uint8_t as it seemed an obvious match for a binary value but most posts rejected it in favour of 16 or 32. I didn’t understand why but your response makes more sense.

The uint16_t and unit32_t are there - at least to my knowledge level - because some of the other values that are non-binary are made up of 2 byte vales and a couple are 4 byte values.

If I understand your code a little bit, I’m defining each field of the byte by bit position, pulling the relative byte position and then publishing the bit value, correct? If so then with 3 bytes to split the same way, how does the code know the difference between bit 0x1 in byte 3 and 0x1 in byte 4 etc?

So, if I understand correctly, this would be the result:

// *****************************************************************
// *          ESPHome Custom Component Modbus sniffer for          *
// *              Ampinvt MPPT Solar Controller                    *
// *                  Original code credits:                       *
// *                https://github.com/htvekov/                    *
// *               https://github.com/assembly12/                  *
// *****************************************************************

#include "esphome.h"

class ampinvtcomponent : public PollingComponent, public Sensor, public UARTDevice {
  public:
    ampinvtcomponent(UARTComponent *parent) : PollingComponent(600), UARTDevice(parent) {}

  //37 bytes total - 25 bytes used, 12 bytes unused
  Sensor *op_status              = new Sensor(); // 1 bit ~ byte 3 (0=Normal, 1=Abnormal - Battery Automatic Recognition Error)
  Sensor *battery_status         = new Sensor(); // 1 bit ~ byte 3 (0=Normal, 1=Over Discharge Protection)
  Sensor *fan_status             = new Sensor(); // 1 bit ~ byte 3 (0=Normal, 1=Fan Failure)
  Sensor *temp_status            = new Sensor(); // 1 bit ~ byte 3 (0=Normal, 1=Over Temperature Protection)
  Sensor *dcoutput_status        = new Sensor(); // 1 bit ~ byte 3 (0=Normal, 1=DC Output SHort / Over Current Protection)
  Sensor *inttemp1_status        = new Sensor(); // 1 bit ~ byte 3 (0=Close, 1=Fault)
  Sensor *inttemp2_status        = new Sensor(); // 1 bit ~ byte 3 (0=Close, 1=Fault)
  Sensor *exttemp_status         = new Sensor(); // 1 bit ~ byte 3 (0=Close, 1=Fault)
  Sensor *chg_status             = new Sensor(); // 1 bit ~ byte 4 (0=Not Charging, 1=Charging)
  Sensor *equalchg_status        = new Sensor(); // 1 bit ~ byte 4 (1=True)
  Sensor *track_status           = new Sensor(); // 1 bit ~ byte 4 (1=True)
  Sensor *floatchg_status        = new Sensor(); // 1 bit ~ byte 4 (1=True)
  Sensor *chgcurrentlimit_status = new Sensor(); // 1 bit ~ byte 4 (1=True)
  Sensor *chgderating_status     = new Sensor(); // 1 bit ~ byte 4 (1=True)
  Sensor *remoteprohibchg_status = new Sensor(); // 1 bit ~ byte 4 (1=True)
  Sensor *pvovervolt_status      = new Sensor(); // 1 bit ~ byte 4 (1=True)
  Sensor *chgoutputrelay_status  = new Sensor(); // 1 bit ~ byte 5 (0=Close, 1=Open)
  Sensor *loadoutput_status      = new Sensor(); // 1 bit ~ byte 5 (0=Close, 1=Open)
  Sensor *fanrelay_status        = new Sensor(); // 1 bit ~ byte 5 (0=Close, 1=Open)
  Sensor *spare1_status          = new Sensor(); // 1 bit ~ byte 5 (not used)
  Sensor *overchgprotect_status  = new Sensor(); // 1 bit ~ byte 5 (0=Normal, 1=Overcharge Protection)
  Sensor *overvoltprotect_status = new Sensor(); // 1 bit ~ byte 5 (0=Normal, 1=Overvoltage Protection)
  Sensor *spare2_status          = new Sensor(); // 1 bit ~ byte 5 (not used)
  Sensor *spare3_status          = new Sensor(); // 1 bit ~ byte 5 (not used)

  Sensor *pv_voltage          = new Sensor(); // 2 byte
  Sensor *battery_voltage     = new Sensor(); // 2 byte
  Sensor *charge_current      = new Sensor(); // 2 byte
  Sensor *mppt_temperature    = new Sensor(); // 2 byte
  Sensor *battery_temperature = new Sensor(); // 2 byte
  Sensor *today_yield         = new Sensor(); // 4 byte
  Sensor *generation_total    = new Sensor(); // 4 byte
  
  void setup() override {
  }

  std::vector<int> bytes;

  void update() {
    while(available() > 0) {
      bytes.push_back(read());      
      if(bytes.size() < 37){
        continue;  
      }
    
      else {
      }
	    if(bytes.size() == 37) {

        #define BIT_OPERATING_STATUS       0x1 // 00000001
        #define BIT_BATTERY_STATUS         0x2 // 00000010
        #define BIT_FAN_STATUS             0x3 // 00000100
        #define BIT_TEMPERATURE_STATUS     0x4 // 00001000
        #define BIT_DCOUTPUT_STATUS        0x5 // 00010000
        #define BIT_INTTEMP1_STATUS        0x6 // 00100000
        #define BIT_INTTEMP2_STATUS        0x7 // 01000000
        #define BIT_EXTTEMP_STATUS         0x8 // 10000000

        unint8_t op_status_byte = (uint8_t)(bytes[3]);
        id(op_status).publish_state((bool)((op_status_byte & BIT_OPERATING_STATUS) == 1));
        id(battery_status).publish_state((bool)((op_status_byte & BIT_BATTERY_STATUS) == 1));
        id(fan_status).publish_state((bool)((op_status_byte & BIT_FAN_STATUS) == 1));
        id(temp_status).publish_state((bool)((op_status_byte & BIT_TEMPERATURE_STATUS) == 1));
        id(dcoutput_status).publish_state((bool)((op_status_byte & BIT_DCOUTPUT_STATUS) == 1));
        id(inttemp1_status).publish_state((bool)((op_status_byte & BIT_INTTEMP1_STATUS) == 1));
        id(inttemp2_status).publish_state((bool)((op_status_byte & BIT_INTTEMP2_STATUS) == 1));
        id(exttemp_status).publish_state((bool)((op_status_byte & BIT_EXTTEMP_STATUS) == 1));

        #define BIT_CHARGING_STATUS        0x1 // 00000001
        #define BIT_EQUALCHG_STATUS        0x2 // 00000010
        #define BIT_TRACK_STATUS           0x3 // 00000100
        #define BIT_FLOATCHG_STATUS        0x4 // 00001000
        #define BIT_CHGCURRENTLIMIT_STATUS 0x5 // 00010000
        #define BIT_CHGDERATING_STATUS     0x6 // 00100000
        #define BIT_REMOTEPROHIBCHG_STATUS 0x7 // 01000000
        #define BIT_PVOVERVOLT_STATUS      0x8 // 10000000

        unint8_t op_status_byte = (uint8_t)(bytes[4]);
        id(chg_status).publish_state((bool)((op_status_byte & BIT_CHARGING_STATUS) == 1));
        id(equalchg_status).publish_state((bool)((op_status_byte & BIT_EQUALCHG_STATUS) == 1));
        id(track_status).publish_state((bool)((op_status_byte & BIT_TRACK_STATUS) == 1));
        id(floatchg_status).publish_state((bool)((op_status_byte & BIT_FLOATCHG_STATUS) == 1));
        id(chgcurrentlimit_status).publish_state((bool)((op_status_byte & BIT_CHGCURRENTLIMIT_STATUS) == 1));
        id(chgderating_status).publish_state((bool)((op_status_byte & BIT_CHGDERATING_STATUS) == 1));
        id(remoteprohibchg_status).publish_state((bool)((op_status_byte & BIT_REMOTEPROHIBCHG_STATUS) == 1));
        id(pvovervolt_status).publish_state((bool)((op_status_byte & BIT_PVOVERVOLT_STATUS) == 1));

        #define BIT_CHGOUTRLY_STATUS       0x1 // 00000001
        #define BIT_LOADOUTPUT_STATUS      0x2 // 00000010
        #define BIT_FANRLY_STATUS          0x3 // 00000100
        #define BIT_SPARE1_STATUS          0x4 // 00001000
        #define BIT_OVERCHGPROTECT_STATUS  0x5 // 00010000
        #define BIT_OVERVOLTPROTECT_STATUS 0x6 // 00100000
        #define BIT_SPARE2_STATUS          0x7 // 01000000
        #define BIT_SPARE3_STATUS          0x8 // 10000000

        unint8_t op_status_byte = (uint8_t)(bytes[5]);
        id(chgoutputrelay_status).publish_state((bool)((op_status_byte & BIT_CHGOUTRLY_STATUS) == 1));
        id(loadoutput_status).publish_state((bool)((op_status_byte & BIT_LOADOUTPUT_STATUS) == 1));
        id(fanrelay_status).publish_state((bool)((op_status_byte & BIT_FANRLY_STATUS) == 1));
        id(overchgprotect_status).publish_state((bool)((op_status_byte & BIT_OVERCHGPROTECT_STATUS) == 1));
        id(overvoltprotect_status).publish_state((bool)((op_status_byte & BIT_OVERVOLTPROTECT_STATUS) == 1));

        TwoByte pv_voltage_value;
        pv_voltage_value.Byte[1] = bytes[6]; // PV Voltage high byte
        pv_voltage_value.Byte[0] = bytes[7]; // PV Voltage low byte
        id(pv_voltage).publish_state(pv_voltage_value.UInt16);
        
        TwoByte battery_voltage_value;
        battery_voltage_value.Byte[1] = bytes[8]; // Battery Voltage high byte
        battery_voltage_value.Byte[0] = bytes[9]; // Battery Voltage low byte
        id(battery_voltage).publish_state(battery_voltage_value.UInt16);
        
        TwoByte charge_current_value;
        charge_current_value.Byte[1] = bytes[10]; // Charge Current high byte
        charge_current_value.Byte[0] = bytes[11]; // Charge Current low byte
        id(charge_current).publish_state(charge_current_value.UInt16);

        // Necessary to accomodate negative celcius temps in unheated installations        
        int16_t mppt_temperature_value;
          if (bytes[12] >= 0x10) {
            mppt_temperature_value = int(
              (signed char)(bytes[13] * -1));
          } else {
            mppt_temperature_value = int(
              (signed char)(bytes[13]));
          }
        id(mppt_temperature).publish_state(mppt_temperature_value);
        
        // Necessary to accomodate negative celcius temps in unheated installations
        int16_t battery_temperature_value;
          if (bytes[16] >= 0x10) {
            battery_temperature_value = int(
              (signed char)(bytes[17] * -1));
          } else {
            battery_temperature_value = int(
              (signed char)(bytes[17]));
          }
        id(battery_temperature).publish_state(battery_temperature_value);

        uint32_t today_yield_value = int(
            (unsigned char)(bytes[20]) << 24 |
            (unsigned char)(bytes[21]) << 16 |
            (unsigned char)(bytes[22]) << 8 |
            (unsigned char)(bytes[23]));
        id(today_yield).publish_state(today_yield_value);

        uint32_t generation_total_value = int(
            (unsigned char)(bytes[24]) << 24 |
            (unsigned char)(bytes[25]) << 16 |
            (unsigned char)(bytes[26]) << 8 |
            (unsigned char)(bytes[27]));
        id(generation_total).publish_state(generation_total_value);

        bytes.clear();
      }
      else {
      }    
    }    
  }

  typedef union
  {
    unsigned char Byte[2];
    int16_t Int16;
    uint16_t UInt16;
    unsigned char UChar;
    char Char;
  }TwoByte;};

I’m still not sure how the dine statements work for the same bits in different bytes so I figured that by referencing them after each publish block, it wouldn’t matter if they got overwritten. I am curious though.

Just need to add the extra sensors into the yaml and I’ll load 'er up and see.

Ignore the above code.

It occurred to me that I was now trying to use both sensors AND binary sensors in a Custom Sensor. Logic suggests that I now need 2 include files, one with public Sensor and one with public BinarySensor so that what I did.

Code for the Custom Sensor is here:

// *****************************************************************
// *          ESPHome Custom Component Modbus sniffer for          *
// *              Ampinvt MPPT Solar Controller                    *
// *                  Original code credits:                       *
// *                https://github.com/htvekov/                    *
// *               https://github.com/assembly12/                  *
// *****************************************************************

#include "esphome.h"

class ampinvtbinarysensor : public PollingComponent, public BinarySensor, public UARTDevice {
  public:
    ampinvtbinarysensor(UARTComponent *parent) : PollingComponent(600), UARTDevice(parent) {}

  //37 bytes total - 25 bytes used, 12 bytes unused
  Sensor *op_status              = new Sensor(); // 1 bit ~ byte 3 (0=Normal, 1=Abnormal - Battery Automatic Recognition Error)
  Sensor *battery_status         = new Sensor(); // 1 bit ~ byte 3 (0=Normal, 1=Over Discharge Protection)
  Sensor *fan_status             = new Sensor(); // 1 bit ~ byte 3 (0=Normal, 1=Fan Failure)
  Sensor *temp_status            = new Sensor(); // 1 bit ~ byte 3 (0=Normal, 1=Over Temperature Protection)
  Sensor *dcoutput_status        = new Sensor(); // 1 bit ~ byte 3 (0=Normal, 1=DC Output SHort / Over Current Protection)
  Sensor *inttemp1_status        = new Sensor(); // 1 bit ~ byte 3 (0=Close, 1=Fault)
  Sensor *inttemp2_status        = new Sensor(); // 1 bit ~ byte 3 (0=Close, 1=Fault)
  Sensor *exttemp_status         = new Sensor(); // 1 bit ~ byte 3 (0=Close, 1=Fault)
  Sensor *chg_status             = new Sensor(); // 1 bit ~ byte 4 (0=Not Charging, 1=Charging)
  Sensor *equalchg_status        = new Sensor(); // 1 bit ~ byte 4 (1=True)
  Sensor *track_status           = new Sensor(); // 1 bit ~ byte 4 (1=True)
  Sensor *floatchg_status        = new Sensor(); // 1 bit ~ byte 4 (1=True)
  Sensor *chgcurrentlimit_status = new Sensor(); // 1 bit ~ byte 4 (1=True)
  Sensor *chgderating_status     = new Sensor(); // 1 bit ~ byte 4 (1=True)
  Sensor *remoteprohibchg_status = new Sensor(); // 1 bit ~ byte 4 (1=True)
  Sensor *pvovervolt_status      = new Sensor(); // 1 bit ~ byte 4 (1=True)
  Sensor *chgoutputrelay_status  = new Sensor(); // 1 bit ~ byte 5 (0=Close, 1=Open)
  Sensor *loadoutput_status      = new Sensor(); // 1 bit ~ byte 5 (0=Close, 1=Open)
  Sensor *fanrelay_status        = new Sensor(); // 1 bit ~ byte 5 (0=Close, 1=Open)
  Sensor *overchgprotect_status  = new Sensor(); // 1 bit ~ byte 5 (0=Normal, 1=Overcharge Protection)
  Sensor *overvoltprotect_status = new Sensor(); // 1 bit ~ byte 5 (0=Normal, 1=Overvoltage Protection)
  
  void setup() override {
  }

  std::vector<uint8_t> bytes;

  void update() {
    while(available() > 0) {
      bytes.push_back(read());      
      if(bytes.size() < 37){
        continue;  
      }
    
      else {
      }
	    if(bytes.size() == 37) {

        #define BIT_OPERATING_STATUS       0x1 // 00000001
        #define BIT_BATTERY_STATUS         0x2 // 00000010
        #define BIT_FAN_STATUS             0x3 // 00000100
        #define BIT_TEMPERATURE_STATUS     0x4 // 00001000
        #define BIT_DCOUTPUT_STATUS        0x5 // 00010000
        #define BIT_INTTEMP1_STATUS        0x6 // 00100000
        #define BIT_INTTEMP2_STATUS        0x7 // 01000000
        #define BIT_EXTTEMP_STATUS         0x8 // 10000000

        uint8_t op_status_byte = (uint8_t)(bytes[3]);
        id(op_status).publish_state((bool)((op_status_byte & BIT_OPERATING_STATUS) == 1));
        id(battery_status).publish_state((bool)((op_status_byte & BIT_BATTERY_STATUS) == 1));
        id(fan_status).publish_state((bool)((op_status_byte & BIT_FAN_STATUS) == 1));
        id(temp_status).publish_state((bool)((op_status_byte & BIT_TEMPERATURE_STATUS) == 1));
        id(dcoutput_status).publish_state((bool)((op_status_byte & BIT_DCOUTPUT_STATUS) == 1));
        id(inttemp1_status).publish_state((bool)((op_status_byte & BIT_INTTEMP1_STATUS) == 1));
        id(inttemp2_status).publish_state((bool)((op_status_byte & BIT_INTTEMP2_STATUS) == 1));
        id(exttemp_status).publish_state((bool)((op_status_byte & BIT_EXTTEMP_STATUS) == 1));

        #define BIT_CHARGING_STATUS        0x1 // 00000001
        #define BIT_EQUALCHG_STATUS        0x2 // 00000010
        #define BIT_TRACK_STATUS           0x3 // 00000100
        #define BIT_FLOATCHG_STATUS        0x4 // 00001000
        #define BIT_CHGCURRENTLIMIT_STATUS 0x5 // 00010000
        #define BIT_CHGDERATING_STATUS     0x6 // 00100000
        #define BIT_REMOTEPROHIBCHG_STATUS 0x7 // 01000000
        #define BIT_PVOVERVOLT_STATUS      0x8 // 10000000

        uint8_t chg_status_byte = (uint8_t)(bytes[4]);
        id(chg_status).publish_state((bool)((chg_status_byte & BIT_CHARGING_STATUS) == 1));
        id(equalchg_status).publish_state((bool)((chg_status_byte & BIT_EQUALCHG_STATUS) == 1));
        id(track_status).publish_state((bool)((chg_status_byte & BIT_TRACK_STATUS) == 1));
        id(floatchg_status).publish_state((bool)((chg_status_byte & BIT_FLOATCHG_STATUS) == 1));
        id(chgcurrentlimit_status).publish_state((bool)((chg_status_byte & BIT_CHGCURRENTLIMIT_STATUS) == 1));
        id(chgderating_status).publish_state((bool)((chg_status_byte & BIT_CHGDERATING_STATUS) == 1));
        id(remoteprohibchg_status).publish_state((bool)((chg_status_byte & BIT_REMOTEPROHIBCHG_STATUS) == 1));
        id(pvovervolt_status).publish_state((bool)((chg_status_byte & BIT_PVOVERVOLT_STATUS) == 1));

        #define BIT_CHGOUTRLY_STATUS       0x1 // 00000001
        #define BIT_LOADOUTPUT_STATUS      0x2 // 00000010
        #define BIT_FANRLY_STATUS          0x3 // 00000100
        #define BIT_SPARE1_STATUS          0x4 // 00001000
        #define BIT_OVERCHGPROTECT_STATUS  0x5 // 00010000
        #define BIT_OVERVOLTPROTECT_STATUS 0x6 // 00100000
        #define BIT_SPARE2_STATUS          0x7 // 01000000
        #define BIT_SPARE3_STATUS          0x8 // 10000000

        uint8_t ctl_status_byte = (uint8_t)(bytes[5]);
        id(chgoutputrelay_status).publish_state((bool)((ctl_status_byte & BIT_CHGOUTRLY_STATUS) == 1));
        id(loadoutput_status).publish_state((bool)((ctl_status_byte & BIT_LOADOUTPUT_STATUS) == 1));
        id(fanrelay_status).publish_state((bool)((ctl_status_byte & BIT_FANRLY_STATUS) == 1));
        id(overchgprotect_status).publish_state((bool)((ctl_status_byte & BIT_OVERCHGPROTECT_STATUS) == 1));
        id(overvoltprotect_status).publish_state((bool)((ctl_status_byte & BIT_OVERVOLTPROTECT_STATUS) == 1));

        bytes.clear();
      }
      else {
      }    
    }    
  }

  typedef union
  {
    unsigned char Byte[2];
    uint8_t UInt8;
    unsigned char UChar;
    char Char;
  }TwoByte;};

Code from the yaml is here:

binary_sensor:
  - platform: custom
    lambda: |-
      auto ampinvtbinarysensors = new ampinvtbinarysensor(id(uart_bus));
      App.register_component(ampinvtbinarysensors);
      return {\
      ampinvtbinarysensors->op_status, \
      ampinvtbinarysensors->battery_status, \
      ampinvtbinarysensors->fan_status, \
      ampinvtbinarysensors->temp_status, \
      ampinvtbinarysensors->dcoutput_status, \
      ampinvtbinarysensors->inttemp1_status, \
      ampinvtbinarysensors->inttemp2_status, \
      ampinvtbinarysensors->exttemp_status, \
      ampinvtbinarysensors->chg_status, \
      ampinvtbinarysensors->equalchg_status, \
      ampinvtbinarysensors->track_status, \
      ampinvtbinarysensors->floatchg_status, \
      ampinvtbinarysensors->chgcurrentlimit_status, \
      ampinvtbinarysensors->chgderating_status, \
      ampinvtbinarysensors->remoteprohibchg_status, \
      ampinvtbinarysensors->pvovervolt_status, \
      ampinvtbinarysensors->chgoutputrelay_status, \
      ampinvtbinarysensors->loadoutput_status, \
      ampinvtbinarysensors->fanrelay_status, \
      ampinvtbinarysensors->overchgprotect_status, \
      ampinvtbinarysensors->overvoltprotect_status, \
      };

    binary_sensors:
    - name: "Operating Status"
      id: "op_status"
    - name: "Battery Status"
      id: "battery_status"
    - name: "Fan Status"
      id: "fan_status"
    - name: "DC Output Status"
      id: "dcoutput_status"
    - name: "Internal Temperature 1 Status"
      id: "inttemp1_status"
    - name: "Internal Temperature 2 Status"
      id: "inttemp2_status"
    - name: "External Temperature Status"
      id: "exttemp_status"
    - name: "Charging Status"
      id: "chg_status"
    - name: "Equal Charging Status"
      id: "equalchg_status"
    - name: "MPPT Tracking Status"
      id: "track_status"
    - name: "Float Charging Status"
      id: "floatchg_status"
    - name: "Charge Current Limit Status"
      id: "chgcurrentlimit_status"
    - name: "Charge Derating Status"
      id: "chgderating_status"
    - name: "Remote Prohibit Charging Status"
      id: "remoteprohibchg_status"
    - name: "Panel Overvoltage Status"
      id: "pvovervolt_status"
    - name: "Charging Output Relay Status"
      id: "chgoutputrelay_status"
    - name: "Load Output Status"
      id: "loadoutput_status"
    - name: "Fan Relay Status"
      id: "fanrelay_status"
    - name: "Overcharge Protection Status"
      id: "overchargeprotect_status"
    - name: "Overvoltage Protection Status"
      id: "overvoltprotect_status"

But when I try to load it up, I get these errors:

/config/esphome/esp32_barn_controller.yaml: In lambda function:
/config/esphome/esp32_barn_controller.yaml:166:7: error: could not convert '{ampinvtbinarysensors->ampinvtbinarysensor::op_status, ampinvtbinarysensors->ampinvtbinarysensor::battery_status, ampinvtbinarysensors->ampinvtbinarysensor::fan_status, ampinvtbinarysensors->ampinvtbinarysensor::temp_status, ampinvtbinarysensors->ampinvtbinarysensor::dcoutput_status, ampinvtbinarysensors->ampinvtbinarysensor::inttemp1_status, ampinvtbinarysensors->ampinvtbinarysensor::inttemp2_status, ampinvtbinarysensors->ampinvtbinarysensor::exttemp_status, ampinvtbinarysensors->ampinvtbinarysensor::chg_status, ampinvtbinarysensors->ampinvtbinarysensor::equalchg_status, ampinvtbinarysensors->ampinvtbinarysensor::track_status, ampinvtbinarysensors->ampinvtbinarysensor::floatchg_status, ampinvtbinarysensors->ampinvtbinarysensor::chgcurrentlimit_status, ampinvtbinarysensors->ampinvtbinarysensor::chgderating_status, ampinvtbinarysensors->ampinvtbinarysensor::remoteprohibchg_status, ampinvtbinarysensors->ampinvtbinarysensor::pvovervolt_status, ampinvtbinarysensors->ampinvtbinarysensor::chgoutputrelay_status, ampinvtbinarysensors->ampinvtbinarysensor::loadoutput_status, ampinvtbinarysensors->ampinvtbinarysensor::fanrelay_status, ampinvtbinarysensors->ampinvtbinarysensor::overchgprotect_status, ampinvtbinarysensors->ampinvtbinarysensor::overvoltprotect_status}' from '<brace-enclosed initializer list>' to 'std::vector<esphome::binary_sensor::BinarySensor*>'
       };
       ^
*** [/data/esp32-barn-controller/.pioenvs/esp32-barn-controller/src/main.cpp.o] Error 1
========================== [FAILED] Took 5.24 seconds ==========================

The errors suggest to me that the cause is in the breakout in the code in the include file but I can’t see why.

Mmm… Esphome lambdas are a bit of a mystery to me :wink:
Maybe try replacing curly braces with square ones (ie. returning an array rather than an initializer).

      return [\
      ampinvtbinarysensors->op_status, \
      ampinvtbinarysensors->battery_status, \
      ampinvtbinarysensors->fan_status, \
      ampinvtbinarysensors->temp_status, \
      ampinvtbinarysensors->dcoutput_status, \
      ampinvtbinarysensors->inttemp1_status, \
      ampinvtbinarysensors->inttemp2_status, \
      ampinvtbinarysensors->exttemp_status, \
      ampinvtbinarysensors->chg_status, \
      ampinvtbinarysensors->equalchg_status, \
      ampinvtbinarysensors->track_status, \
      ampinvtbinarysensors->floatchg_status, \
      ampinvtbinarysensors->chgcurrentlimit_status, \
      ampinvtbinarysensors->chgderating_status, \
      ampinvtbinarysensors->remoteprohibchg_status, \
      ampinvtbinarysensors->pvovervolt_status, \
      ampinvtbinarysensors->chgoutputrelay_status, \
      ampinvtbinarysensors->loadoutput_status, \
      ampinvtbinarysensors->fanrelay_status, \
      ampinvtbinarysensors->overchgprotect_status, \
      ampinvtbinarysensors->overvoltprotect_status, \
      ];

Hi @koying, I’m just circling back to this after some other setbacks.

I’ve made reasonable progress but wanted your opinion on what may be occurring in the bitmasking. In summary, byte positions 3, 4 and 5 are 00:0D:00 so only byte 4 is of interest in this post. As far as I can see, the masking should produce bit value of 00001101 but this is what I’m getting:

I can’t figure out why. Code is:

// *****************************************************************
// *          ESPHome Custom Component Modbus sniffer for          *
// *              Ampinvt MPPT Solar Controller                    *
// *                  Original code credits:                       *
// *                https://github.com/htvekov/                    *
// *               https://github.com/assembly12/                  *
// *****************************************************************

#include "esphome.h"

class ampinvtsensor : public PollingComponent, public Sensor, public UARTDevice {
  public:
    ampinvtsensor(UARTComponent *parent) : PollingComponent(600), UARTDevice(parent) {}

  //37 bytes total - 25 bytes used, 12 bytes unused
  // Bit sensors follow (bytes 3, 4, 5)
  Sensor *ampinvt_op_status              = new Sensor(); // 1 bit ~ byte 3 (0=Normal, 1=Abnormal - Battery Automatic Recognition Error)
  Sensor *ampinvt_battery_status         = new Sensor(); // 1 bit ~ byte 3 (0=Normal, 1=Over Discharge Protection)
  Sensor *ampinvt_fan_status             = new Sensor(); // 1 bit ~ byte 3 (0=Normal, 1=Fan Failure)
  Sensor *ampinvt_overheat_status        = new Sensor(); // 1 bit ~ byte 3 (0=Normal, 1=Over Temperature Protection)
  Sensor *ampinvt_dcoutput_status        = new Sensor(); // 1 bit ~ byte 3 (0=Normal, 1=DC Output SHort / Over Current Protection)
  Sensor *ampinvt_inttemp1_status        = new Sensor(); // 1 bit ~ byte 3 (0=Close, 1=Fault)
  Sensor *ampinvt_inttemp2_status        = new Sensor(); // 1 bit ~ byte 3 (0=Close, 1=Fault)
  Sensor *ampinvt_exttemp_status         = new Sensor(); // 1 bit ~ byte 3 (0=Close, 1=Fault)
  Sensor *ampinvt_chg_status             = new Sensor(); // 1 bit ~ byte 4 (0=Not Charging, 1=Charging)
  Sensor *ampinvt_equalchg_status        = new Sensor(); // 1 bit ~ byte 4 (1=True)
  Sensor *ampinvt_track_status           = new Sensor(); // 1 bit ~ byte 4 (1=True)
  Sensor *ampinvt_floatchg_status        = new Sensor(); // 1 bit ~ byte 4 (1=True)
  Sensor *ampinvt_chgcurrentlimit_status = new Sensor(); // 1 bit ~ byte 4 (1=True)
  Sensor *ampinvt_chgderating_status     = new Sensor(); // 1 bit ~ byte 4 (1=True)
  Sensor *ampinvt_remoteprohibchg_status = new Sensor(); // 1 bit ~ byte 4 (1=True)
  Sensor *ampinvt_pvovervolt_status      = new Sensor(); // 1 bit ~ byte 4 (1=True)
  Sensor *ampinvt_chgoutputrelay_status  = new Sensor(); // 1 bit ~ byte 5 (0=Close, 1=Open)
  Sensor *ampinvt_loadoutput_status      = new Sensor(); // 1 bit ~ byte 5 (0=Close, 1=Open)
  Sensor *ampinvt_fanrelay_status        = new Sensor(); // 1 bit ~ byte 5 (0=Close, 1=Open)
  Sensor *ampinvt_overchgprotect_status  = new Sensor(); // 1 bit ~ byte 5 (0=Normal, 1=Overcharge Protection)
  Sensor *ampinvt_overvoltprotect_status = new Sensor(); // 1 bit ~ byte 5 (0=Normal, 1=Overvoltage Protection)
  // Byte sensors follow
  Sensor *ampinvt_pv_voltage             = new Sensor(); // 2 byte
  Sensor *ampinvt_battery_voltage        = new Sensor(); // 2 byte
  Sensor *ampinvt_charge_current         = new Sensor(); // 2 byte
  Sensor *ampinvt_mppt_temperature       = new Sensor(); // 2 byte
  Sensor *ampinvt_battery_temperature    = new Sensor(); // 2 byte
  Sensor *ampinvt_today_yield            = new Sensor(); // 4 byte
  Sensor *ampinvt_generation_total       = new Sensor(); // 4 byte
  
  void setup() override {
  }

  std::vector<int> bytes;

  void update() {
    while(available() > 0) {
      bytes.push_back(read());      
      if(bytes.size() < 37){
        continue;  
      }
    
      else {
      }
	    if(bytes.size() == 37) {

        uint8_t op_status_byte = (uint8_t)(bytes[3]);
        #define BIT_OPERATING_STATUS       0x0 // 00000001
        #define BIT_BATTERY_STATUS         0x1 // 00000010
        #define BIT_FAN_STATUS             0x2 // 00000100
        #define BIT_TEMPERATURE_STATUS     0x3 // 00001000
        #define BIT_DCOUTPUT_STATUS        0x4 // 00010000
        #define BIT_INTTEMP1_STATUS        0x5 // 00100000
        #define BIT_INTTEMP2_STATUS        0x6 // 01000000
        #define BIT_EXTTEMP_STATUS         0x7 // 10000000
        id(ampinvt_op_status).publish_state((bool)(op_status_byte & BIT_OPERATING_STATUS));
        id(ampinvt_battery_status).publish_state((bool)(op_status_byte & BIT_BATTERY_STATUS));
        id(ampinvt_fan_status).publish_state((bool)(op_status_byte & BIT_FAN_STATUS));
        id(ampinvt_overheat_status).publish_state((bool)(op_status_byte & BIT_TEMPERATURE_STATUS));
        id(ampinvt_dcoutput_status).publish_state((bool)(op_status_byte & BIT_DCOUTPUT_STATUS));
        id(ampinvt_inttemp1_status).publish_state((bool)(op_status_byte & BIT_INTTEMP1_STATUS));
        id(ampinvt_inttemp2_status).publish_state((bool)(op_status_byte & BIT_INTTEMP2_STATUS));
        id(ampinvt_exttemp_status).publish_state((bool)(op_status_byte & BIT_EXTTEMP_STATUS));
        // #undef BIT_OPERATING_STATUS       
        // #undef BIT_BATTERY_STATUS         
        // #undef BIT_FAN_STATUS             
        // #undef BIT_TEMPERATURE_STATUS     
        // #undef BIT_DCOUTPUT_STATUS        
        // #undef BIT_INTTEMP1_STATUS        
        // #undef BIT_INTTEMP2_STATUS        
        // #undef BIT_EXTTEMP_STATUS         

        uint8_t chg_status_byte = (uint8_t)(bytes[4]);
        #define BIT_CHARGING_STATUS        0x0 // 00000001
        #define BIT_EQUALCHG_STATUS        0x1 // 00000010
        #define BIT_TRACK_STATUS           0x2 // 00000100
        #define BIT_FLOATCHG_STATUS        0x3 // 00001000
        #define BIT_CHGCURRENTLIMIT_STATUS 0x4 // 00010000
        #define BIT_CHGDERATING_STATUS     0x5 // 00100000
        #define BIT_REMOTEPROHIBCHG_STATUS 0x6 // 01000000
        #define BIT_PVOVERVOLT_STATUS      0x7 // 10000000
        id(ampinvt_chg_status).publish_state((bool)(chg_status_byte & BIT_CHARGING_STATUS));
        id(ampinvt_equalchg_status).publish_state((bool)(chg_status_byte & BIT_EQUALCHG_STATUS));
        id(ampinvt_track_status).publish_state((bool)(chg_status_byte & BIT_TRACK_STATUS));
        id(ampinvt_floatchg_status).publish_state((bool)(chg_status_byte & BIT_FLOATCHG_STATUS));
        id(ampinvt_chgcurrentlimit_status).publish_state((bool)(chg_status_byte & BIT_CHGCURRENTLIMIT_STATUS));
        id(ampinvt_chgderating_status).publish_state((bool)(chg_status_byte & BIT_CHGDERATING_STATUS));
        id(ampinvt_remoteprohibchg_status).publish_state((bool)(chg_status_byte & BIT_REMOTEPROHIBCHG_STATUS));
        id(ampinvt_pvovervolt_status).publish_state((bool)(chg_status_byte & BIT_PVOVERVOLT_STATUS));
        // #undef BIT_CHARGING_STATUS        
        // #undef BIT_EQUALCHG_STATUS        
        // #undef BIT_TRACK_STATUS           
        // #undef BIT_FLOATCHG_STATUS        
        // #undef BIT_CHGCURRENTLIMIT_STATUS 
        // #undef BIT_CHGDERATING_STATUS     
        // #undef BIT_REMOTEPROHIBCHG_STATUS 
        // #undef BIT_PVOVERVOLT_STATUS      

        uint8_t ctl_status_byte = (uint8_t)(bytes[5]);
        #define BIT_CHGOUTRLY_STATUS       0x0 // 00000001
        #define BIT_LOADOUTPUT_STATUS      0x1 // 00000010
        #define BIT_FANRLY_STATUS          0x2 // 00000100
        #define BIT_SPARE1_STATUS          0x3 // 00001000
        #define BIT_OVERCHGPROTECT_STATUS  0x4 // 00010000
        #define BIT_OVERVOLTPROTECT_STATUS 0x5 // 00100000
        #define BIT_SPARE2_STATUS          0x6 // 01000000
        #define BIT_SPARE3_STATUS          0x7 // 10000000
        id(ampinvt_chgoutputrelay_status).publish_state((bool)(ctl_status_byte & BIT_CHGOUTRLY_STATUS));
        id(ampinvt_loadoutput_status).publish_state((bool)(ctl_status_byte & BIT_LOADOUTPUT_STATUS));
        id(ampinvt_fanrelay_status).publish_state((bool)(ctl_status_byte & BIT_FANRLY_STATUS));
        id(ampinvt_overchgprotect_status).publish_state((bool)(ctl_status_byte & BIT_OVERCHGPROTECT_STATUS));
        id(ampinvt_overvoltprotect_status).publish_state((bool)(ctl_status_byte & BIT_OVERVOLTPROTECT_STATUS));
        // #undef BIT_CHGOUTRLY_STATUS       
        // #undef BIT_LOADOUTPUT_STATUS      
        // #undef BIT_FANRLY_STATUS         
        // #undef BIT_SPARE1_STATUS          
        // #undef BIT_OVERCHGPROTECT_STATUS  
        // #undef BIT_OVERVOLTPROTECT_STATUS 
        // #undef BIT_SPARE2_STATUS          
        // #undef BIT_SPARE3_STATUS   
        
        TwoByte pv_voltage_value;
        pv_voltage_value.Byte[1] = bytes[6]; // PV Voltage high byte
        pv_voltage_value.Byte[0] = bytes[7]; // PV Voltage low byte
        id(ampinvt_pv_voltage).publish_state(pv_voltage_value.UInt16);
        
        TwoByte battery_voltage_value;
        battery_voltage_value.Byte[1] = bytes[8]; // Battery Voltage high byte
        battery_voltage_value.Byte[0] = bytes[9]; // Battery Voltage low byte
        id(ampinvt_battery_voltage).publish_state(battery_voltage_value.UInt16);
        
        TwoByte charge_current_value;
        charge_current_value.Byte[1] = bytes[10]; // Charge Current high byte
        charge_current_value.Byte[0] = bytes[11]; // Charge Current low byte
        id(ampinvt_charge_current).publish_state(charge_current_value.UInt16);

        // Necessary to accomodate negative celcius temps in unheated installations        
        int16_t mppt_temperature_value;
          if (bytes[12] >= 0x10) {
            mppt_temperature_value = int(
              (signed char)(bytes[13] * -1));
          } else {
            mppt_temperature_value = int(
              (signed char)(bytes[13]));
          }
        id(ampinvt_mppt_temperature).publish_state(mppt_temperature_value);
        
        // Necessary to accomodate negative celcius temps in unheated installations
        int16_t battery_temperature_value;
          if (bytes[16] >= 0x10) {
            battery_temperature_value = int(
              (signed char)(bytes[17] * -1));
          } else {
            battery_temperature_value = int(
              (signed char)(bytes[17]));
          }
        id(ampinvt_battery_temperature).publish_state(battery_temperature_value);

        uint32_t today_yield_value = int(
            (unsigned char)(bytes[20]) << 24 |
            (unsigned char)(bytes[21]) << 16 |
            (unsigned char)(bytes[22]) << 8 |
            (unsigned char)(bytes[23]));
        id(ampinvt_today_yield).publish_state(today_yield_value);

        uint32_t generation_total_value = int(
            (unsigned char)(bytes[24]) << 24 |
            (unsigned char)(bytes[25]) << 16 |
            (unsigned char)(bytes[26]) << 8 |
            (unsigned char)(bytes[27]));
        id(ampinvt_generation_total).publish_state(generation_total_value);

        bytes.clear();
      }
      else {
      }    
    }    
  }

  typedef union
  {
    unsigned char Byte[2];
    int16_t Int16;
    uint16_t UInt16;
    unsigned char UChar;
    char Char;
  }TwoByte;};

I did research the initial code snippet you posted. I took out the ==1 as I need the code to post the value, (0 or 1) and not just values that are 1. Does anything stand out as an obvious cause to you?

Not sure what you mean by that. A byte is 2 digits of 4 bits in hexadecimal, e.g. 0xD0 is 11010000
Keep in mind a byte is made of 8 bits. Maybe some wording issues…

EDIT: oh, I guess you mean byte 3 is 00, byte 4 is 0x0D, byte 5 is 0…

Not sure what you mea, either. As far as I can see, you’re only test signle bits.

That was wrong, anyway :wink: Should have been > 0. The way it is coded, it return a true is the bit is set, and falseif it isn’t. It does not only returns values that are 1
The way you did it should achieve the same.

EDIT:
Your binary to hexadecimal is wrong

        #define BIT_CHARGING_STATUS        0x1 // 00000001
        #define BIT_EQUALCHG_STATUS        0x2 // 00000010
        #define BIT_TRACK_STATUS           0x4 // 00000100
        #define BIT_FLOATCHG_STATUS        0x8 // 00001000
        #define BIT_CHGCURRENTLIMIT_STATUS 0x10 // 00010000
        #define BIT_CHGDERATING_STATUS     0x20 // 00100000
        #define BIT_REMOTEPROHIBCHG_STATUS 0x40 // 01000000
        #define BIT_PVOVERVOLT_STATUS      0x80 // 10000000
1 Like

Thanks for the response, especially the edit which solved the problem. I created what I thought was the suitable code from your snippet in post #4 but looking at your edit, I can see how I went off track. The last edit you provided solved the issues I was seeing as a result. Very much appreciated.

For the bytes/bits part, your edit there is correct. When I send a command string of hex to the controller, it returns a string of 2 digits in hex separated by colons. While it uses modbus, this controller is different in that the vales are not retrieved by reading registers (the ‘normal’ way) but in request/response exchanges. A request is sent and the register values are all sent back in a string. Use caution, I am probably using the term string incorrectly. Most of the hex byte values translate directly to a decimal value but these 3 must be broken out to bits for their boolean value.

Thanks again for your help here. Once I figure out Github, I’ll be making the solution available there.