Foxess Inverter Modbus

You can use uart debugging to get the raw message in hex.

Alternatively you could try

18
17

I’m pretty sure it has to be that. Not much more left :wink:

Hi all,

with which firmware is this here now finally working? Do I have to request a new firmware / old firmware from FoxESS? Somewhere i read that with everything >1.20 the RS485 port is dead? Thanks a lot in advance!

  • @xndz - did you have success with reading out gridpower and other stuff? Which FW are you on now? Thanks for any help (our Solar system will go online tomorrow latest I hope ! :wink: )

Yes, it works fine for me. I have following versions:
Master : 1.35
Slave : 1.01
Manager : 1.21

Total Generation was on 16/15 and grid power 176/175. All the rest was just shift by 6 from original code.

Awesome! Would you mind sharing your new code with me? Would be highest appreciated! Thank you so much !Would this also work with 1.22? I think so right? Hopefully at least … :smiley:

I’m not sure if 1.22 would work, and not really willing to try to upgrade at this stage :smiley:
This is the custom code that works for me (slightly modified, i.e. commented out pv3/pv4 because I don’t have that):

#include "esphome.h"

unsigned long millis_lastmessage = 0;
const long inverter_timeout = 300000;
int inverter_mode = 99; //0=Offline, 1=Online, 2=Error, 99=Waiting for response...

class foxesscomponent : public PollingComponent, public Sensor, public UARTDevice {
  public:
    foxesscomponent(UARTComponent *parent) : PollingComponent(600), UARTDevice(parent) {}
  
  Sensor *frame_header = new Sensor;
  Sensor *function_code = new Sensor;
  Sensor *time_stamp = new Sensor;
  Sensor *data_length = new Sensor;
  Sensor *grid_power = new Sensor;
  Sensor *generation_power = new Sensor;
  Sensor *loads_power = new Sensor;
  Sensor *grid_voltage_r = new Sensor;
  Sensor *grid_current_r = new Sensor;
  Sensor *grid_frequency_r = new Sensor;
  Sensor *grid_power_r = new Sensor;
  Sensor *grid_voltage_s = new Sensor;
  Sensor *grid_current_s = new Sensor;
  Sensor *grid_frequency_s = new Sensor;
  Sensor *grid_power_s = new Sensor;
  Sensor *grid_voltage_T = new Sensor;
  Sensor *grid_current_T = new Sensor;
  Sensor *grid_frequency_T = new Sensor;
  Sensor *grid_power_T = new Sensor;
  Sensor *pv1_voltage = new Sensor;
  Sensor *pv1_current = new Sensor;
  Sensor *pv1_power = new Sensor;
  Sensor *pv2_voltage = new Sensor;
  Sensor *pv2_current = new Sensor;
  Sensor *pv2_power = new Sensor;
  Sensor *pv3_voltage = new Sensor;
  Sensor *pv3_current = new Sensor;
  Sensor *pv3_power = new Sensor;
  Sensor *pv4_voltage = new Sensor;
  Sensor *pv4_current = new Sensor;
  Sensor *pv4_power = new Sensor;
  Sensor *boost_temperature = new Sensor;
  Sensor *inverter_temperature = new Sensor;
  Sensor *ambient_temperature = new Sensor;
  Sensor *today_yield = new Sensor;
  Sensor *generation_total = new Sensor;
  Sensor *from_grid_yield_generation = new Sensor;
  Sensor *inverter_state = new Sensor;

  void setup() override {
    id(inverter_state).publish_state(inverter_mode);
    millis_lastmessage = millis();
  }

  std::vector<int> bytes;

  //void loop() override {
  void update() {
    if(millis_lastmessage + inverter_timeout < millis()) {
      if(inverter_mode != 0){
        inverter_mode = 0; //offline
        id(inverter_state).publish_state(inverter_mode);
        id(generation_power).publish_state(0);
        id(grid_current_r).publish_state(0);
        id(grid_power_r).publish_state(0);
        id(grid_current_T).publish_state(0);
        id(grid_power_T).publish_state(0);
        id(grid_current_s).publish_state(0);
        id(grid_power_s).publish_state(0);
        id(pv1_current).publish_state(0);
        id(pv1_power).publish_state(0);
        id(pv2_current).publish_state(0);
        id(pv2_power).publish_state(0);
        id(pv3_current).publish_state(0);
        id(pv3_power).publish_state(0);
        id(pv4_current).publish_state(0);
        id(pv4_power).publish_state(0);
      }
    }
    while(available() > 0) {
      bytes.push_back(read());
      //ESP_LOGD("custom", "reading bytes");

      //make sure at least 9 header bytes are available to check
      if(bytes.size() < 9){
        continue;
      }

      if(bytes[0] != 0x7E || bytes[1] != 0x7E || bytes[2] != 0x02) {
        bytes.erase(bytes.begin()); //remove first byte from buffer
        //buffer will never get above 9 until the header is correct
        continue;
      }
      else {
      }

      //if(bytes.size() == 9) { //>=
        TwoByte message_length;
        message_length.Byte[0] = bytes[8];
        message_length.Byte[1] = bytes[7];
        uint16_t total_message_length = message_length.UInt16 + 13;
      //}

      if(bytes.size() == total_message_length) { //>=
      //if(bytes.size() == 165) { //>=
        if(bytes[total_message_length-1] != 0xE7 || bytes[total_message_length-2] != 0xE7) {
        //if(bytes[164] != 0xE7 || bytes[163] != 0xE7) {
          bytes.clear();
          ESP_LOGD("custom", "error in reading message");
          continue;
        }
        ESP_LOGD("custom", "succesfully read realtime data");
        ESP_LOGI("custom", "User data length: %i", message_length.UInt16);
        ESP_LOGI("custom", "Total message length: %i", total_message_length);
        millis_lastmessage = millis();
        inverter_mode = 1;

        TwoByte grid_power_value;
        grid_power_value.Byte[0] = bytes[176];
        grid_power_value.Byte[1] = bytes[175];
        id(grid_power).publish_state(grid_power_value.UInt16);
        delay(50);

        TwoByte generation_power_value;
        generation_power_value.Byte[0] = bytes[16];
        generation_power_value.Byte[1] = bytes[15];
        id(generation_power).publish_state(generation_power_value.UInt16);
        delay(50);

        TwoByte loads_power_value;
        loads_power_value.Byte[0] = bytes[20];
        loads_power_value.Byte[1] = bytes[19];
        id(loads_power).publish_state(loads_power_value.UInt16);
        delay(50);

        TwoByte grid_voltage_r_value;
        grid_voltage_r_value.Byte[0] = bytes[22];
        grid_voltage_r_value.Byte[1] = bytes[21];
        id(grid_voltage_r).publish_state(grid_voltage_r_value.UInt16);
        delay(50);

        TwoByte grid_current_r_value;
        grid_current_r_value.Byte[0] = bytes[24];
        grid_current_r_value.Byte[1] = bytes[23];
        id(grid_current_r).publish_state(grid_current_r_value.UInt16);
        delay(50);

        TwoByte grid_frequency_r_value;
        grid_frequency_r_value.Byte[0] = bytes[26];
        grid_frequency_r_value.Byte[1] = bytes[25];
        id(grid_frequency_r).publish_state(grid_frequency_r_value.UInt16);
        delay(50);

        TwoByte grid_power_r_value;
        grid_power_r_value.Byte[0] = bytes[28];
        grid_power_r_value.Byte[1] = bytes[27];
        id(grid_power_r).publish_state(grid_power_r_value.UInt16);
        delay(50);

        TwoByte grid_voltage_s_value;
        grid_voltage_s_value.Byte[0] = bytes[30];
        grid_voltage_s_value.Byte[1] = bytes[29];
        id(grid_voltage_s).publish_state(grid_voltage_s_value.UInt16);
        delay(50);

        TwoByte grid_current_s_value;
        grid_current_s_value.Byte[0] = bytes[32];
        grid_current_s_value.Byte[1] = bytes[31];
        id(grid_current_s).publish_state(grid_current_s_value.UInt16);
        delay(50);

        TwoByte grid_frequency_s_value;
        grid_frequency_s_value.Byte[0] = bytes[34];
        grid_frequency_s_value.Byte[1] = bytes[33];
        id(grid_frequency_s).publish_state(grid_frequency_s_value.UInt16);
        delay(50);

        TwoByte grid_power_s_value;
        grid_power_s_value.Byte[0] = bytes[36];
        grid_power_s_value.Byte[1] = bytes[35];
        id(grid_power_s).publish_state(grid_power_s_value.UInt16);
        delay(50);

        TwoByte grid_voltage_T_value;
        grid_voltage_T_value.Byte[0] = bytes[38];
        grid_voltage_T_value.Byte[1] = bytes[37];
        id(grid_voltage_T).publish_state(grid_voltage_T_value.UInt16);
        delay(50);

        TwoByte grid_current_T_value;
        grid_current_T_value.Byte[0] = bytes[40];
        grid_current_T_value.Byte[1] = bytes[39];
        id(grid_current_T).publish_state(grid_current_T_value.UInt16);
        delay(50);

        TwoByte grid_frequency_T_value;
        grid_frequency_T_value.Byte[0] = bytes[42];
        grid_frequency_T_value.Byte[1] = bytes[41];
        id(grid_frequency_T).publish_state(grid_frequency_T_value.UInt16);
        delay(50);

        TwoByte grid_power_T_value;
        grid_power_T_value.Byte[0] = bytes[44];
        grid_power_T_value.Byte[1] = bytes[43];
        id(grid_power_T).publish_state(grid_power_T_value.UInt16);
        delay(50);

        TwoByte pv1_voltage_value;
        pv1_voltage_value.Byte[0] = bytes[46];
        pv1_voltage_value.Byte[1] = bytes[45];
        id(pv1_voltage).publish_state(pv1_voltage_value.UInt16);
        //ESP_LOGI("custom", "pv1_voltage: %i", pv1_voltage_value.UInt16);
        delay(50);

        TwoByte pv1_current_value;
        pv1_current_value.Byte[0] = bytes[48];
        pv1_current_value.Byte[1] = bytes[47];
        id(pv1_current).publish_state(pv1_current_value.UInt16);
        //ESP_LOGI("custom", "pv1_current: %i", pv1_current_value.UInt16);
        delay(50);

        TwoByte pv2_voltage_value;
        pv2_voltage_value.Byte[0] = bytes[52];
        pv2_voltage_value.Byte[1] = bytes[51];
        id(pv2_voltage).publish_state(pv2_voltage_value.UInt16);
        //ESP_LOGI("custom", "pv2_voltage: %i", pv2_voltage_value.UInt16);
        delay(50);

        TwoByte pv2_current_value;
        pv2_current_value.Byte[0] = bytes[54];
        pv2_current_value.Byte[1] = bytes[53];
        id(pv2_current).publish_state(pv2_current_value.UInt16);
        //ESP_LOGI("custom", "pv2_current: %i", pv2_current_value.UInt16);
        delay(50);

        TwoByte pv3_voltage_value;
        pv3_voltage_value.Byte[0] = bytes[58];
        pv3_voltage_value.Byte[1] = bytes[57];
        id(pv3_voltage).publish_state(pv3_voltage_value.UInt16);
        delay(50);

        TwoByte pv3_current_value;
        pv3_current_value.Byte[0] = bytes[60];
        pv3_current_value.Byte[1] = bytes[59];
        id(pv3_current).publish_state(pv3_current_value.UInt16);
        delay(50);

        TwoByte pv4_voltage_value;
        pv4_voltage_value.Byte[0] = bytes[64];
        pv4_voltage_value.Byte[1] = bytes[63];
        id(pv4_voltage).publish_state(pv4_voltage_value.UInt16);
        delay(50);

        TwoByte pv4_current_value;
        pv4_current_value.Byte[0] = bytes[66];
        pv4_current_value.Byte[1] = bytes[65];
        id(pv4_current).publish_state(pv4_current_value.UInt16);
        delay(50);

        TwoByte boost_temperature_value;
        boost_temperature_value.Byte[0] = bytes[70];
        boost_temperature_value.Byte[1] = bytes[69];
        id(boost_temperature).publish_state(boost_temperature_value.UInt16);
        delay(50);

        TwoByte inverter_temperature_value;
        inverter_temperature_value.Byte[0] = bytes[72];
        inverter_temperature_value.Byte[1] = bytes[71];
        id(inverter_temperature).publish_state(inverter_temperature_value.UInt16);
        delay(50);

        TwoByte ambient_temperature_value;
        ambient_temperature_value.Byte[0] = bytes[74];
        ambient_temperature_value.Byte[1] = bytes[73];
        id(ambient_temperature).publish_state(ambient_temperature_value.UInt16);
        delay(50);

        TwoByte today_yield_value;
        today_yield_value.Byte[0] = bytes[76];
        today_yield_value.Byte[1] = bytes[75];
        id(today_yield).publish_state(today_yield_value.UInt16);
        //ESP_LOGI("custom", "today_yield: %i", today_yield_value.UInt16);
        delay(50);

        uint32_t generation_total_value = int(
            (unsigned char)(bytes[77]) << 24 |
            (unsigned char)(bytes[78]) << 16 |
            (unsigned char)(bytes[79]) << 8 |
            (unsigned char)(bytes[80]));
        id(generation_total).publish_state(generation_total_value);
        //ESP_LOGI("custom", "Generation total: %i", generation_total_value);
        delay(50);

        uint32_t FaultMessage1_value = int( 
            (unsigned char)(bytes[131]) << 24 | 
            (unsigned char)(bytes[132]) << 16 | 
            (unsigned char)(bytes[133]) << 8 | 
            (unsigned char)(bytes[134])); 
            delay(50);

        uint32_t FaultMessage2_value = int( 
            (unsigned char)(bytes[135]) << 24 | 
            (unsigned char)(bytes[136]) << 16 | 
            (unsigned char)(bytes[137]) << 8 | 
            (unsigned char)(bytes[138]));  
            delay(50);

        uint32_t FaultMessage3_value = int( 
            (unsigned char)(bytes[139]) << 24 | 
            (unsigned char)(bytes[140]) << 16 | 
            (unsigned char)(bytes[141]) << 8 | 
            (unsigned char)(bytes[142])); 
            delay(50);

        uint32_t FaultMessage4_value = int( 
            (unsigned char)(bytes[143]) << 24 | 
            (unsigned char)(bytes[144]) << 16 | 
            (unsigned char)(bytes[145]) << 8 | 
            (unsigned char)(bytes[146]));
            delay(50);

        uint32_t FaultMessage5_value = int( 
            (unsigned char)(bytes[147]) << 24 | 
            (unsigned char)(bytes[148]) << 16 | 
            (unsigned char)(bytes[149]) << 8 | 
            (unsigned char)(bytes[150])); 
            delay(50);

        uint32_t FaultMessage6_value = int( 
            (unsigned char)(bytes[151]) << 24 | 
            (unsigned char)(bytes[152]) << 16 | 
            (unsigned char)(bytes[153]) << 8 | 
            (unsigned char)(bytes[154]));
            delay(50);

        uint32_t FaultMessage7_value = int( 
            (unsigned char)(bytes[155]) << 24 | 
            (unsigned char)(bytes[156]) << 16 | 
            (unsigned char)(bytes[157]) << 8 | 
            (unsigned char)(bytes[158]));
            delay(50);

        uint32_t FaultMessage8_value = int( 
            (unsigned char)(bytes[159]) << 24 | 
            (unsigned char)(bytes[160]) << 16 | 
            (unsigned char)(bytes[161]) << 8 | 
            (unsigned char)(bytes[162]));
            delay(50);

        if(FaultMessage1_value != 0 || FaultMessage2_value != 0 || FaultMessage3_value != 0 || FaultMessage4_value != 0 || FaultMessage5_value != 0 || FaultMessage6_value != 0 || FaultMessage7_value != 0 || FaultMessage8_value != 0) {
          ESP_LOGI("custom", "Fault message 1: %i", FaultMessage1_value);
          ESP_LOGI("custom", "Fault message 2: %i", FaultMessage2_value);
          ESP_LOGI("custom", "Fault message 3: %i", FaultMessage3_value);
          ESP_LOGI("custom", "Fault message 4: %i", FaultMessage4_value);
          ESP_LOGI("custom", "Fault message 5: %i", FaultMessage5_value);
          ESP_LOGI("custom", "Fault message 6: %i", FaultMessage6_value);
          ESP_LOGI("custom", "Fault message 7: %i", FaultMessage7_value);
          ESP_LOGI("custom", "Fault message 8: %i", FaultMessage8_value);
          inverter_mode = 2; //error
        }
        else {
          inverter_mode = 1; //online
        }

        //calculate PV power from pv current and pv voltage:
        uint32_t pv1_power_value = pv1_current_value.UInt16 * pv1_voltage_value.UInt16 * 0.01;
        id(pv1_power).publish_state(pv1_power_value);
        delay(50);
        uint32_t pv2_power_value = pv2_current_value.UInt16 * pv2_voltage_value.UInt16 * 0.01;
        id(pv2_power).publish_state(pv2_power_value);
        delay(50);
        //uint32_t pv3_power_value = pv3_current_value.UInt16 * pv3_voltage_value.UInt16 * 0.01;
        //id(pv3_power).publish_state(pv3_power_value);
        //delay(50);
        //uint32_t pv4_power_value = pv4_current_value.UInt16 * pv4_voltage_value.UInt16 * 0.01;
        //id(pv4_power).publish_state(pv4_power_value);
        //delay(50);

        id(inverter_state).publish_state(inverter_mode);

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

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

ESPHome yaml is pretty much the same as in repo, just changed names of entities

2 Likes

Wow thank you @xndz awesome! I will try it if my inverter is starting tomorrow! Or I ask for a downgrade to 1.21 from foxESS.

Thanks!

Thanks @xndz !
@Sweti3: please let us know if you get it to work with 1.22. But I fear you’ll have to downgrade to 1.20 or 1.21.

I will try it! Do I need some kind of password in the worst case to perform the downgrade of the inveter? My solar guys wont give me their login details … Thanks!

P.S.: Neighbours are on Manager 1.16 - is this too old to be supported? Thanks in advance!

Ok everything is running fine, thanks so much!

Hi All!
First of all I would like to say thank you to @assembly for this great job.
It is really usefull.
Last Thuersday my inverter was upgrade to ver. 1.22.
Everything works fine with version:
Master : 1.27 Slave : 1.00 Manager : 1.22
Bellow I marked 6 new bytes.
7E 7E 02 65 24 39 3E 00 EE 00 00 07 A6 00 00 00 41 00 00 07 E7 09 23 00 04 13 84 …

BR

Hey guys! How are you doing?
I just started creating a proper integration for the solution @Safs proposed, with an Elfin EW11, but eliminating node-red from the equation, as I personally don’t use it, and I imagine many more don’t
Here is the repo: GitHub - LucasTor/FoxESS-T-series
The only tests I’ve done were using an Arduino to send the strings to the Elfin EW11 bridge, as my inverter is yet to be installed, but I will keep you guys updated on any further tests/improvements (In my case I will be using a WEG SIW200G M050 W0, as I live in Brazil and FoxESS inverters are rebranded by WEG here)
Thanks all!

First of all, many thanks to the developers and all those who support us with tips and tricks.
I have successfully integrated the values from my FoxEss T10-G3 into Home-Assistant.
The values are sent to me every 2 minutes, with my H3 I get them via USB almost in real time.
How can I reduce the update time of the T10, i. in other words, query it more frequently via ESP?

Sadly, the protocol of T-series doesn’t allow a more frequent update interval. The values aren’t polled, but send out by the inverter in an update interval which is set in the firmware of the inverter.

1 Like

Even better then using cloudbased Info - former time: 5min, now: not working… :slight_smile:
thx for information

@xndz
Thanks for your update for the 1.21 version. It works basically, but some values are not correct imo.
Today Yield is always 0
Generation Total rises over the day and lowers in the afternoon.
Is this the same for your setup?

And can someone explain me the value of loads Power? Is it the power I’m consuming from the grid?

Hello guys!
I tried reading the RS485 using an Elfin EW11, but the only message I get is 0x0104004A0002501D, at a rate of ~3 per second, is my inverter not sending the proper data? Do I need to have the wifi dongle setup for that? As it is, it’s not connected to the cloud yet
The Elfin EW11 config was copied from @Safs
Thanks!

Maybe problem is that inventor not connected to cloud? Because sendet data is same for cloud and RS485… If inventor not connected to cloud, it do not generate data… Only guessing…

The inverter broadcasts regardless whether the cloud is connected or not.

This was my thought as well
In the end the problem was that WEG (the company that rebrands FoxESS in my country), or maybe even FoxESS in the newer models, removed the main RS485 port from the main connector from the inverter, and left it only for the wifi dongle, which means I had to get a little creative, but with a little ingenuity I got it to work :slight_smile:
By the way, the integration is ready to test in case you guys wanna check out
FoxESS T Series Integration - GitHub