I’m a little more informed than I was earlier but…that’s all relative.
I’ve been through the solutions posted by @assembly and @htvekov and for the data that is coming in, I understand maybe 75% of what is going on with the processing. I see that the additional *.h file is handling the uart read and breaking out the bytes into individual data that are then sent to the sensor in the .yaml file. I’ve taken a ‘first pass’ at adapting the files created for my own data but my developer skills and knowledge leave much to be desired, especially in a language like C+.
Much like @assembly posted after his “Yahoo!” moment, my data coming from the controller to the ESP32 log file looks like this:
[18:40:01][D][uart_debug:114]: <<< 01:B3:01:00:00:00:02:41:04:C3:00:5D:00:39:00:00:10:20:00:00:00:00:00:22:00:00:05:6C:00:00:00:00:00:00:00:00:18
Mapped out, it looks like this:
Byte 1 > 37
MPPT Address - 1 byte (01)
Command Code - 1 byte (B3)
Control Code - 1 byte (01)
Operating Status - 1 byte (00) [uses 8 bits for status]
Charging Status - 1 byte (00) [uses 8 bits for status]
Control Status - 1 byte (00) [uses 8 bits for status]
PV Voltage - 2 bytes (02 & 41) **
Battery Voltage - 2 bytes (04 & C3) **
Charge Current - 2 bytes (00 & 5D) **
MPPT Temp - 2 bytes (00 & 39) **
Not used - 1 byte (00)
Not used - 1 byte (00)
Battery Temp - 2 bytes (10 & 20) ** !This will be tricky due to negative temps
Not used - 1 byte (00)
Not used - 1 byte (00)
Daily power - 4 bytes (00 & 00 & 00 & 22)
Total Power - 4 bytes (00 & 00 & 05 & 6C)
Not used - 1 byte (00)
Not used - 1 byte (00)
Not used - 1 byte (00)
Not used - 1 byte (00)
Not used - 1 byte (00)
Not used - 1 byte (00)
Not used - 1 byte (00)
Not used - 1 byte (00)
Checksum - 1 byte (18)
There is no timestamp in the data but I assume (read, hope) that I can add that in yaml code later. The ** denote the data that I want to focus on getting into HA first.
The adaptation of @htvekov solivia.h code is as follows:
#include "esphome.h"
class ampinvt : public PollingComponent, public Sensor, public UARTDevice {
public:
ampinvt(UARTComponent *parent) : PollingComponent(400), UARTDevice(parent) {}
// 37 bytes total - 25 bytes used, 12 bytes unused
// Sensor *time_stamp = new Sensor(); // from logging?
// Sensor *operating_status = new Sensor(); // binary (1 byte) - future implementation/low priority
// Sensor *charging_status = new Sensor(); // binary (1 byte) - future implementation/low priority
// Sensor *control_status = new Sensor(); // binary (1 byte) - future implementation/low priority
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 - future implementation/low priority
// Sensor *generation_total = new Sensor(); //4 byte - future implementation/low priority
void setup() override {
}
std::vector<int> bytes;
int count = 15;
//void loop() override {
void update() {
while(available() > 0) {
bytes.push_back(read());
//make sure at least 8 header bytes are available for check
if(bytes.size() < 8)
{
continue;
}
if(bytes[0] != 0x01 || bytes[1] != 0xB3 || bytes[2] != 0x01) {
bytes.erase(bytes.begin()); //remove first byte from buffer
//buffer will never get above 8 until the response is a match
continue;
}
if (bytes.size() == 37) {
// OneByte operating_status;
// operating_status.Byte[0] = bytes[0x4B +6]; // Operating Status (binary)
// OneByte command_code;
// command_code.Byte[0] = bytes[0x4B +6]; // Command Code (binary)
// OneByte control_status;
// control_status.Byte[0] = bytes[0x4B +6]; // Control Status (binary)
TwoByte pv_voltage;
pv_voltage.Byte[0] = bytes[0x4B +6]; // PV Voltage high byte
pv_voltage.Byte[1] = bytes[0x4A +6]; // PV Voltage low byte
TwoByte battery_voltage;
battery_voltage.Byte[0] = bytes[0x4D +6]; // Battery Voltage high byte
battery_voltage.Byte[1] = bytes[0x4C +6]; // Battery Voltage high byte
TwoByte charge_current;
charge_currrent.Byte[0] = bytes[0x4F +6]; // Charge Current high byte
charge_current.Byte[1] = bytes[0x4E +6]; // Charge Current low byte
TwoByte mptt_temperature;
mptt_temperature.Byte[0] = bytes[0x5D + 6]; // MPPT Controller Temp high byte (Temp should always be positive)
mptt_temperature.Byte[1] = bytes[0x5C + 6]; // MPPT Controller Temp low byte
TwoByte battery_temperature;
battery_temperature.Byte[0] = bytes[0x5F + 6]; // Battery Temp high byte (Temp could be positive or negative)
battery_temperature.Byte[1] = bytes[0x5E + 6]; // Battery Temp low byte
// FourByte today_yeild;
// today_yeild.Byte[0] = bytes[0x61 + 6]; // Daily Power Generation Yield
// today_yeild.Byte[1] = bytes[0x60 + 6]; // Daily Power Generation Yield
// today_yeild.Byte[2] = bytes[0x60 + 6]; // Daily Power Generation Yield
// today_yeild.Byte[3] = bytes[0x60 + 6]; // Daily Power Generation Yield
// FourByte generation_total;
// generation_total.Byte[0] = bytes[0x63 +6]; // Total Power Generation Yield
// generation_total.Byte[1] = bytes[0x62 +6]; // Total Power Generation Yield
// generation_total.Byte[2] = bytes[0x62 +6]; // Total Power Generation Yield
// generation_total.Byte[3] = bytes[0x62 +6]; // Total Power Generation Yield
// 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)
// char etx;
// etx = bytes[37]; // ETX byte (last byte)
// Quick and dirty check for package integrity is done, in order to avoid irratic sensor value updates
// This effectively blocks out any erroneous sensor updates due to rx package corruption
// Check if ETX = 3. If not (invalid package), ditch whole package, clear buffer and continue
// if (etx != 0x03) {
// ESP_LOGI("custom", "ETX check failure - NO sensor update! ETX: %i", etx);
// bytes.clear();
// continue;
// }
// operating_status->publish_state(operating_status.UInt16);
// charging_status->publish_state(charging_status.UInt16);
// control_status->publish_state(control_status.UInt16);
pv_voltage->publish_state(pv_voltage.UInt16);
battery_voltage->publish_state(battery_voltage.UInt16);
charge_current->publish_state(charge_current.UInt16);
mptt_temperature->publish_state(mptt_temperature.UInt16);
battery_temperature->publish_state(battery_temperature.UInt16);
// today_yeild->publish_state(today_yeild.UInt16);
// generation_total->publish_state(generation_total.Int16);
// ESP_LOGI("custom", "ETX check OK: %i - ESPHome sensors updated", etx);
bytes.clear();
}
else {
}
}
}
typedef union
{
unsigned char Byte[2];
int16_t Int16;
uint16_t UInt16;
unsigned char UChar;
char Char;
}TwoByte;};
I have commented out the stuff that isn’t vital for a first stage implementation and I can circle back to that later when I understand more about what is going on. The code coming in from the Solivia is in a different format to the code that I have coming back; rather than individual registers, I have a string dump of hex values so I’m not sure how to adapt the byte locations used in the original file but I utilised the approach from @assembly as that made more sense.
And that leaves the 25% of code there that I don’t understand. If you guys could look it over and make suggestions, I’d be deeply grateful. Thanks so much for your help so far.