Getting the data from a solar panel inverter
Purpose and setup
Heating your water directly with the power of your solar panels is an effective way to store energy and is less costly than using batteries.
To use the solar energy direct while it is produced, some sockets of our power users (like our electric boiler) are switched "on" or "off" by a programmable microcontroller.
In this described case I use an Arduino Uno microcontroller and a Solax X1 inverter.
We use remote controlled (433MHz) sockets which are controlled by a small transmitter on the Arduino board, but you can also run a cable to the socket to control the relay in the socket.
To know when to switch the users, internal data of the inverter is used.
My main interest is the delivered power in Watts for the switching and the temperature to provide cooling with an external fan.


The inverter
To transform the 50–430 V DC from the solar panels to the 220-240 V AC of the power grid, a Solax X1 inverter is used.
During the process heat is generated .
The internal data of the inverter can be reached via a RS485 communication port with a RJ 45 Jack.

Only pin 4 and 5 are used. The RS485 data transfer is a differential system with two data lines A and B. (Pin 4 and 5 on the RJ45 plug) They are not grounded to be more resistant to interference. If line A is 5 volts, line B is 0 volts and vice versa.
In our system, a MAX 485 module, connected to our Arduino microprocessor, converts the data stream into TTL with 5 volts relative to ground.
Communication protocol
The inverter uses a MODBUS protocol and has the be asked for its data. The master (our Arduino microprocessor) sends a request for the data and the slave (The inverter) replies. The data packets contain a sender- and a destination- address.
The Solax protocol can be downloaded from the web and I used this version:
"SolaxPower_Single_Phase_External_Communication_Protocol_X1_V1.8.pdf"
In this protocol AP stands for access point: The master initiating the data transfer.
Request the serial number from the inverter
To communicate I define an array of bytes which is send through the max module.
For starters (only for the first time) the serial number of the inverter is asked as it is needed in the address assignment.
SoftwareSerial RS485Serial(SSerialRX, SSerialTX);
byte InByte = 0;
byte byteInput[25];
byte RequestSerialNumber[]=
{0xAA,0x55,0x01,0x00, 0x00,0x00, 0x10, 0x00, 0x00, 0x01,0x10};
// Header ,Source , Solax ,Contr ,Func,Length,Checksum
The request is send as follows:
RS485Serial.write(RequestSerialNumber,sizeof(RequestSerialNumber));
And the response from the Inverter is stored in the byteInput[25]:
int index = 0;
while(RS485Serial.available())
{
InByte = (byte)RS485Serial.read();
byteInput[index] = InByte;
index++;
}
Assigning the address to the inverter
The received serial number is used to assign an address to the inverter:
byte InByte = 0;
byte ConformationInput[12]; // the conformation send by the inverter is 12 bytes long
byte AddressInput[] = {0xAA,0x55,0x00,0x00, 0x00,0x00, 0x10, 0x01, 0x0F,
0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x37,0x36,0x35,0x34,0x33,0x32,0x31, 0x0A,0x04,0x01};
In the main controlling program I use this array in an "AssignAddress()" function :
void AssignAddress()
{
digitalWrite(REPin, RS485Transmit);
digitalWrite(DEPin, RS485Transmit);
RS485Serial.write(AddressInput,sizeof(AddressInput));
delay(100);
digitalWrite(REPin, RS485Receive);
digitalWrite(DEPin, RS485Receive);
int index = 0;
while(RS485Serial.available())
{
InByte = (byte)RS485Serial.read();
ConformationInput[index] = InByte;
index++;
}
}
The address assignment has to be done each time the inverter has been switched off (which happens at night). So if I want to use inverter data, I put the address assignment in the setup function of the program and start the Arduino in the morning when the inverter is "awake".
Requesting the data
The RequestData[] array and DataInput are defined as:
byte RequestData[]={0xAA,0x55, 0x00,0x00, 0x00,0x0A , 0x11, 0x02, 0x00, 0x01,0x1C};
byte DataInput[63];
// the defined data to be updated by the inverter's data:
int temp_Solax = 33; // start with the fan running
int power_Solax = 0;
float voltage_Grid = 0.0;
float yield_Day = 0.0;
float yield_Total = 0.0;
long hours_Total = 0;
In the loop() of the program I use a function to make my data requests and receive the data packet:
void GetSolaxData()
{
// * * * SEND A DATA REQUEST TO THE INVERTER * * *
digitalWrite(REPin, RS485Transmit);
digitalWrite(DEPin, RS485Transmit);
RS485Serial.write(RequestData,sizeof(RequestData));
// * * * SWITCH TO RECEIVE MODE AND UPDATE THE DATAPACKET * * *
digitalWrite(REPin, RS485Receive);
digitalWrite(DEPin, RS485Receive);
int index = 0;
while(RS485Serial.available())
{
InByte = (byte)RS485Serial.read();
DataInput[index] = InByte;
index++;
}
// update the data values of interest
temp_Solax = DataInput[10];
voltage_Grid = (DataInput[24] + 256 * DataInput[23]) / 10.0;
yield_Day = (DataInput[12] + 256 * DataInput[11]) / 10.0;
yield_Total = (DataInput[34] + 256 * DataInput[33] + 65536 * DataInput[32] + 16777216 * DataInput[31]) / 10.0;
hours_Total = DataInput[38] + 256 * DataInput[37] + 65536 * DataInput[36] + 16777216 * DataInput[35];
// make the data visible on the serial monitor
Serial.println();
Serial.print("Watts: ");
Serial.print(power_Solax);
Serial.println();
Serial.print("Temperature: ");
Serial.print(temp_Solax);
Serial.println();
Serial.print("gridVoltage: ");
Serial.print(voltage_Grid);
Serial.println();
Serial.print("kWh this day: ");
Serial.print(yield_Day);
Serial.println();
Serial.print("kWh total: ");
Serial.print(yield_Total);
Serial.println();
Serial.print("Hours total: ");
Serial.print(hours_Total);
Serial.println();
}
The data values are each 2 or 4 bytes HEX values. Some data fit in one byte like the temperature in DataInput[10].
The power is given in Watts and will need both bytes as it mostly exceeds 256 watts.
For example:
DataInput[27] = 0x06 = Decimal 6
DataInput[28] = 0xBF = Decimal 191
The power_Solax = 6 x 256 + 191 = 1747 Watts.
This is plenty to run our electric boiler of 1500 Watts so I can switch the socket of the boiler "on".
The complete program listings are written in a way that they provide info on your serial monitor while running.
They can easily be adopted to your own situation and needs;
_250226_A_Solax_RequestSerialNumber
_250226_B_Solax_AssignAddress Sends address to Inverter and gets conformation
_250226_C_Solax_RequestData Shows the full data packet on your monitor
Important note:
_250226_C_Solax_RequestData and the assigning program _250226_B_Solax_AssignAddress should run on the same day as the Solax inverter doesn't keep the address when it's off.
In my controlling program the assignment is done during the setup() of the program. Be aware that you have to start the program each day at a time that the inverter is on. You can do this manually or put the Arduino on a timer.
I also used a small solar panel and monitored it's voltage to see if there's enough sun for the inverter to be awake and called the AssignAddress function at a certain pV of the small panel. However as we don't need hot water while away for longer times we prefer to switch on the Arduino in the morning when the inverter is awake.
Note on saving memory space
For clarity (also my own ) I defined for example:
int temp_Solax = 33; // start with the fan running
int power_Solax = 0;
float voltage_Grid = 0.0;
float yield_Day = 0.0;
float yield_Total = 0.0;
long hours_Total = 0; etc. and assiged them a value from the DataInput[63] array.
If you are short of memory space however, you can also use the DataInput[] array direct
For instance:
((DataInput[34] + 256 * DataInput[33] + 65536 * DataInput[32] + 16777216 * DataInput[31]) / 10.0)
in stead of yield_Total.
This might be akward to do but it will save you 4 bytes of used dynamic memory in this last example alone.
Jeroen Droogh bootprojecten@gmail.com