Xantrex GT Inverter Logging using ESP8266 via ESPHome

Xantrex GT Inverter Logging using ESP8266 via ESPHome

Story Time:

So here is my Xantrex GT. It has been sitting on the side of my wall now since the 30th of January 2014.

Here are the stats:

Solar Panels 250W
Solar Panels Strings 1
Inverter Size 2.8Kw
PV Array Tilt 25°
PV Array Orientation N
Breaker Size (String 1) 5A
Flash Version 03.08.03
Rom Version 03.00.07
Last Tested (Schneider Electric) February 6th 2012 @ 18:40:07

I have wanted to connect to this inverter to log the output and compare power being used vs power being generated. However, the software called GT-View no longer exists, and I couldn't interface with the unit properly anyway because there seemed to be no way to understand the output it was giving me via the RS-232 connection.

Here's what the manual said:

So this is where I had been for many years. Eventually Home Assistant came along and introduced me to the world of the ESP chips, ESPHome and obviously Home Assistant.

Today, I connected the serial cable between the ESP8266 and the inverter, and the magic of the data showed! You probably would have heard a very loud "YES!" in the surounding suburbs to me. I was very happy to finally see this work!

Parts

Parts

Picture Part Link Cost
XC3802-wifi-mini-esp8266-main-boardImageMain-515 ESP8266 D1 Mini https://www.jaycar.com.au/wifi-mini-esp8266-main-board/p/XC3802 $24.95
XC3724-rs-232-to-ttl-uart-converter-moduleImageMain-515 RS-232 to TTL UART Converter Module https://www.jaycar.com.au/rs-232-to-ttl-uart-converter-module/p/XC3724?pos=2&queryId=1589a28a2bce8b24e073a4f29e4178c3 $9.95
WC7535-d9-male-to-d9-male-computer-cable-1-8mImageMain-515 D9 Male to D9 Male Computer Cable (Cross Over/ NULL Modem Cable) - 1.8m https://www.jaycar.com.au/d9-male-to-d9-male-computer-cable-1-8m/p/WC7535?pos=5&queryId=7a6eb415cee001cba373b5cba70c22a0 $10.95
PA0900-d9-male-to-d9-male-computer-adaptorImageMain-515 D9 Male to D9 Male Computer Adapter x2 https://www.jaycar.com.au/d9-male-to-d9-male-computer-adaptor/p/PA0900?pos=2&queryId=71ad745cefde361f4d539e028b0cd05e $5.95
Grand Total: $57.75

Optional

Picture Part Link Cost
XC4464-arduino-compatible-usb-to-serial-adaptor-moduleImageMain-515 Arduino Compatible USB to Serial Adapter Module https://www.jaycar.com.au/arduino-compatible-usb-to-serial-adaptor-module/p/XC4464?pos=2&queryId=3990617724fb52242c9154608980e7bb $19.95

Setup

The following gear will run via ESPHome on the ESP8266 D1 Mini chipset. The RS-232 UART module allows you to interface with the Xantrex GT to read the values.

Note: The Xantrex unit is quite an old and slow unit. It will take about 2-3 seconds to pool data for each command thats run.

ESPHome:

The ESPHome yaml config:

xantrex.yaml

esphome:
  name: xantrex
  platform: ESP8266
  board: d1_mini
  includes:
  - xantrex.h

wifi:
  ssid: !secret esphome_ssid
  password: !secret esphome_ssid_password
  domain: .esphome.local
  manual_ip:
    static_ip: 192.168.xxx.xxx
    gateway: 192.168.xxx.xxx
    subnet: 255.255.255.0

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "xantrex"
    password: "password"
    
web_server: 
  port: 80    

captive_portal:

# Enable logging
logger:
  level: DEBUG
  esp8266_store_log_strings_in_flash: False

# Enable Home Assistant API
api:
  password: !secret esphome_api_ota_password

ota:
  password: !secret esphome_api_ota_password
  
uart:
  id: uart_bus
  baud_rate: 9600
  tx_pin: D4
  rx_pin: D3

sensor:
  - platform: custom 
    lambda: |-
      auto my_xantrex = new Xantrex(id(uart_bus));
      App.register_component(my_xantrex);
      return {my_xantrex->kwhlife_sensor, my_xantrex->kwhtoday_sensor,
        my_xantrex->pin_sensor, my_xantrex->pout_sensor,
        my_xantrex->vin_sensor, my_xantrex->vout_sensor,
        my_xantrex->iin_sensor, my_xantrex->iout_sensor,
        my_xantrex->freq_sensor, my_xantrex->time_sensor, my_xantrex->temp_sensor,
        };
      
    sensors:
    - name: "PV kWh life"
      unit_of_measurement: "kWh"
      accuracy_decimals: 0
    - name: "PV kWh today"
      unit_of_measurement: "kWh"
      accuracy_decimals: 3
    - name: "PV Power In"
      unit_of_measurement: "W"
      accuracy_decimals: 0
      id: 'watts_in'
    - name: "PV Power Out"
      unit_of_measurement: "W"
      accuracy_decimals: 0
    - name: "PV Voltage In"
      unit_of_measurement: "V"
      accuracy_decimals: 1
    - name: "PV Voltage Out"
      unit_of_measurement: "V"
      accuracy_decimals: 1
    - name: "PV Current In"
      unit_of_measurement: "A"
      accuracy_decimals: 2
    - name: "PV Current Out"
      unit_of_measurement: "A"
      accuracy_decimals: 2
    - name: "PV Frequency"
      unit_of_measurement: "Hz"
      accuracy_decimals: 3
    - name: "PV Time"
      unit_of_measurement: "seconds"
      accuracy_decimals: 0
    - name: "PV Temperature"
      unit_of_measurement: "°C"
      accuracy_decimals: 1  
      
#Calculate kWh of Solar Generation
  - platform: template
    name: Solar kWh Generation
    id: solar_kWh
    lambda: return id(watts_in).state * 1 / 1000;
    accuracy_decimals: 2
    unit_of_measurement: kWh
    update_interval: 60s    
    

xantrex.h

#include "esphome.h"


class Xantrex : public Component, public UARTDevice {

 public:
  Sensor *kwhlife_sensor = new Sensor();
  Sensor *kwhtoday_sensor = new Sensor();
  Sensor *pin_sensor = new Sensor();
  Sensor *pout_sensor = new Sensor();
  Sensor *vin_sensor = new Sensor();
  Sensor *vout_sensor = new Sensor();
  Sensor *iin_sensor = new Sensor();
  Sensor *iout_sensor = new Sensor();
  Sensor *freq_sensor = new Sensor();
  Sensor *time_sensor = new Sensor();
  Sensor *temp_sensor = new Sensor();

  Xantrex(UARTComponent *parent) : Component(), UARTDevice(parent) {}

  bool response_pending = false;
  unsigned long poll_time, response_time;
  const int RESPONSE_WAIT = 2500; // Wait for reply to each rs232 command
  const int POLL_WAIT = 300000;   // Overall poll cycle for Xantrex sensor values
                                  // Should be at least as long as RESPONSE_WAIT * number of sensors
  int elapsed_time;

  const char *queries[11] = { "KWHLIFE?\r", "KWHTODAY?\r", "PIN?\r", "POUT?\r",
    "VIN?\r", "VOUT?\r", "IIN?\r", "IOUT?\r",
    "FREQ?\r", "TIME?\r", "MEASTEMP?\r" };
  int queryNum = 0;

  void setup() override {
    // Start the timers
    poll_time = millis()-POLL_WAIT;
    response_time = poll_time;
    response_pending = false;
    // So the readStringUntil doesn't block if no response
    setTimeout(30);
  }

  void loop() override {

    String line = "";

    if (response_pending) {
      // wait after command before checking for a response
      elapsed_time = (millis() - response_time);
      if (elapsed_time > RESPONSE_WAIT) {
        if (available()>0) {
          line = readStringUntil('\r');
          int space = 0;
          String celsius = "";
          switch(queryNum) {
            case 0:
              kwhlife_sensor->publish_state(line.toFloat());
              break;
            case 1:
              kwhtoday_sensor->publish_state(line.toFloat());
              break;
            case 2:
              pin_sensor->publish_state(line.toFloat());
              break;
            case 3:
              pout_sensor->publish_state(line.toFloat());
              break;
            case 4:
              vin_sensor->publish_state(line.toFloat());
              break;
            case 5:
              vout_sensor->publish_state(line.toFloat());
              break;
            case 6:
              iin_sensor->publish_state(line.toFloat());
              break;
            case 7:
              iout_sensor->publish_state(line.toFloat());
              break;
            case 8:
              freq_sensor->publish_state(line.toFloat());
              break;
            case 9:
              time_sensor->publish_state(line.toFloat());
              break;
            case 10:
              // Temp response format is: C:0.0 F:32.0
              // Extract the celsius part
              space = line.indexOf(" ");
              celsius = line.substring(2, space-2);
              temp_sensor->publish_state(celsius.toFloat());
              break;
            default:
              break;
          }
          line = "";
        }
        queryNum++;
        if (queryNum > 10) {
          queryNum=0;
        }
        response_pending = false;
      }
    }

    // check if polling wait time has elapsed for 1st command or 
    // subsequent command is due to be issued
    elapsed_time = (millis() - poll_time);
    if (elapsed_time > POLL_WAIT || (queryNum > 0 && !response_pending)) {
      // Issue next command
      write_str(queries[queryNum]);
      flush();
      response_pending = true;
      // Reset poll timer and start response timer
      response_time = millis();
      // Keep the overall cycle timer aligned to the 1st command
      if (queryNum == 0) {
        poll_time = millis();
      }
    }

  }
};

At the end of the yaml config, there is a template called Solar kWh Generation. This is to determine the running total of kWh to use with power consumption vs solar generation cards in HomeAssistant.

There is also an additional file xantrex.h in the includes at the top. This is the magic that allows you to poll the data. This custom component inplements two timers, delay between command cycles (5 minutes) and delay after issuing each command before attempting to read the response (2.5 seconds). Subsequent commands are issues immediatly after a response is read unless it is the latest command in the group.

Note: The xantrex.h file needs to live in the root directory of ESPHome folder.

Flashing the ESP8266:

Flashing the ESP8266 is quite simple. I used the ESPHome-Flasher project to flash my ESP chips.

Wiring

Pin (USB to Serial Module) Pin ESP8266 (D1 Mini) Description
Tx Rx
Rx Tx
5v 5V
Ground Ground
Ground GPIO0 Ground GPIO0 to Flash the chip with ESPHome

Connecting the components:

Wiring

Pin (ESP8266 D1 Mini) Pin RS-232 Module
5v 5v
Ground Ground
D4 Tx
D3 Rx

Final install:

Understanding the data:

So as you saw above, there are a couple of data points this unit collects. But what do they mean??

Value Description Command
PV kWh Life The kWh generated in the total lifetime of the inverter KWHLIFE
PV kWh Today The kWh generated today KWHTODAY
PV Power In Input Power? PIN
PV Power Out Invert Output Power (W) POUT
PV Voltage In Input Voltage VIN
PV Voltage Out Output Voltage VOUT
PV Current In Input Current IIN
PV Current Out Output Current IOUT
PV Frequency Frequency in Hz FREQ
PV Time Time online today TIME
PV Temperature Heatsink temperature MEASTEMP

End result:

Home Assistant Card (https://github.com/gurbyz/power-wheel-card)
Total Solar Generation Since Install