Solax Hybrid X1/X3 Inverter RealTime Data and control locally

Here is tutorial to complete control and read data from Solax Inverter X1/X3 (maybe others, but data can be wrong). It is connected directly to lan by RJ45 cable (No wifi required). Labels are in czech, but I hope, you can easily translate it.

Don´t change settings frequently, because EEPROM of Solax has limited number of writings (I think 1 million) so 7 changes a day should be ok, do not use more.

There is a lot more settings and data for your Solax available via modbus, but I didn’t put that here because you could damage the inverter.

Requirements:
Modbus binding
JS transformation
Rules and transform files included here

Transform files add to transform/Solax

Solax_mode.map


-=missing
NULL=NULL

//Battery Mode
0=Self Use
1=ForceTimeUse 
2=Back Up Mode
3=Feedin Priority
OFF=OFF

//Inverter Mode
100=Wait Mode
101=Check Mode
102=Normal Mode
103=Fault Mode
104=Permanent Fault Mode
105=Update Mode
106=EPS Check Mode
107=EPS Mode
108=Self-Test Mode
109=Idle Mode
110=Standby Mode
111=Pv Wake Up Bat Mode
112=Gen Check Mode
113=Gen Run Mode

Solax_RunMODE.map

-=missing
NULL=NULL

0=Wait Mode
1=Check Mode
2=Normal Mode
3=Fault Mode
4=Permanent Fault Mode
5=Update Mode
6=EPS check mode
7=EPS mode
8=Self Test
9=Idle Mode 

Javascript need to paste to transform/

divide_by_10.js file

(function(i) {
   return parseFloat(i) / 10;
})(input)

divide_by_100.js file

(function(i) {
   return parseFloat(i) / 100;
})(input)

Thing file (you have to change IP of inverter and you can set refresh value, now is 10s, minimum is 0,5s):

Bridge modbus:tcp:localhostTCP [ host="IP_ADDRESS_OF_INVERTER", port=502, id=2 ] {

  
  
    Bridge poller inputRegisters [ start=0, length=99, refresh=10000, type="input" ] {
	
        Thing data NetworkVoltage [ readStart="0" , readValueType="uint16", readTransform="JS(divide_by_10.js)"]
		Thing data OutputCurrent [ readStart="1", readValueType="int16", readTransform="JS(divide_by_10.js)" ]
		Thing data ACPower [ readStart="2", readValueType="int16" ]  
		Thing data PV1Voltage [ readStart="3", readValueType="uint16", readTransform="JS(divide_by_10.js)"  ]
		Thing data PV2Voltage [ readStart="4", readValueType="uint16", readTransform="JS(divide_by_10.js)"  ]
		Thing data PV1Current [ readStart="5", readValueType="uint16", readTransform="JS(divide_by_10.js)"  ]
		Thing data PV2Current [ readStart="6", readValueType="uint16", readTransform="JS(divide_by_10.js)"  ]
		Thing data RunMODE [ readStart="9", readValueType="uint16"]
		Thing data PowerDC1 [ readStart="10", readValueType="uint16" ]
		Thing data PowerDC2 [ readStart="11", readValueType="uint16" ]
		Thing data BatteryPowerCharge [ readStart="22", readValueType="int16" ]
		Thing data BatteryTemp [ readStart="24", readValueType="int16" ]
	    Thing data BatterySOC [ readStart="28", readValueType="uint16" ]
		Thing data BatteryOutputEnergyToday [ readStart="32", readValueType="uint16", readTransform="JS(divide_by_10.js)"  ]
		Thing data FeedInPower [ readStart="70", readValueType="int16" ]
		Thing data FeedInTotal [ readStart="72", readValueType="int16" ]
		Thing data DailyYield [ readStart="80", readValueType="int16", readTransform="JS(divide_by_10.js)"]	 
       
    
    }
	
	Bridge poller inputRegisters100 [ start=100, length=99, refresh=10000, type="input" ] {
		Thing data feedin_energy_today [ readStart="152", readValueType="uint16", readTransform="JS(divide_by_100.js)"]
        Thing data consum_energy_today [ readStart="154", readValueType="uint16", readTransform="JS(divide_by_100.js)"]
		Thing data GridCurrent_R1 [ readStart="107", readValueType="int16", readTransform="JS(divide_by_10.js)" ]
		Thing data GridPower_R1 [ readStart="108", readValueType="int16" ]
		Thing data GridCurrent_S2 [ readStart="111", readValueType="int16", readTransform="JS(divide_by_10.js)" ]
		Thing data GridPower_S2 [ readStart="112", readValueType="int16" ]
        Thing data GridCurrent_T3 [ readStart="115", readValueType="int16", readTransform="JS(divide_by_10.js)" ]
		Thing data GridPower_T3[ readStart="116", readValueType="int16" ]
		Thing data Temperature [ readStart="190", readValueType="int16"  ]
       
    
    }
	
	 
	
	Bridge poller holdingRegisters100 [ start=100, length=99, refresh=10000, type="holding" ] {
	
		Thing data Battery1_MinCapacity [ readStart="140", readValueType="uint16", writeStart="32", writeValueType="uint16", writeType="holding"]
		Thing data Language [ readStart="187", readValueType="uint16", writeStart="71", writeValueType="uint16", writeType="holding"]
		Thing data Export_Control_User_Limit [ readStart="182", readValueType="uint16", writeStart="66", writeValueType="uint16", writeType="holding"]
		Thing data EPS_Mute [ readStart="183", readValueType="uint16", writeStart="67", writeValueType="uint16", writeType="holding"]
		Thing data WorkingMode [ readStart="139", readValueType="uint16", writeStart="31", writeValueType="uint16", writeType="holding"]
	}
	
	Bridge poller holdingRegisters200 [ start=200, length=99, refresh=1000, type="holding" ] {
        
		Thing data GridTiedLimit [ readStart="274", readValueType="uint16", writeStart="167", writeValueType="uint16", writeType="holding" ]
		
    }
	
	
}
   

Item file:

Group gSolax "Solax Inverter"
Group:Number:SUM gPowerDC "Výkon panelů [%.0f W]"   (gSolax)
Number Solax_PowerDC1     "Výkon panelů DC1 [%.0f W]" (gPowerDC)   { channel="modbus:data:localhostTCP:inputRegisters:PowerDC1:number" }
Number Solax_PowerDC2     "Výkon panelů DC2 [%.0f W]" (gPowerDC)   { channel="modbus:data:localhostTCP:inputRegisters:PowerDC2:number" }
Number Solax_BatterySOC            "Nabití baterie [%.0f%%]"  (gSolax)  { channel="modbus:data:localhostTCP:inputRegisters:BatterySOC:number" }
Number Solax_TempRadiator            "Teplota měniče [%.0f °C]"  (gSolax)  { channel="modbus:data:localhostTCP:inputRegisters100:Temperature:number" }
Number Solax_ACPower            "Aktuální Výkon měniče [%.0f W]" (gSolax)   { channel="modbus:data:localhostTCP:inputRegisters:ACPower:number" }
Number Solax_NetworkVoltage     "Napětí v síti [%.2f V]"  (gSolax)  { channel="modbus:data:localhostTCP:inputRegisters:NetworkVoltage:number" }
Number Solax_OutputCurrent     "Výstupní proud měniče [%.2f A]"  (gSolax)  { channel="modbus:data:localhostTCP:inputRegisters:OutputCurrent:number" }
Number Solax_PV1Voltage     "Napětí panelů PV1 [%.2f V]" (gSolax)   { channel="modbus:data:localhostTCP:inputRegisters:PV1Voltage:number" }
Number Solax_PV2Voltage     "Napětí panelů PV2 [%.2f V]"  (gSolax)  { channel="modbus:data:localhostTCP:inputRegisters:PV2Voltage:number" }
Number Solax_PV1Current     "Proud panelů PV1 [%.2f A]"  (gSolax)  { channel="modbus:data:localhostTCP:inputRegisters:PV1Current:number" }
Number Solax_PV2Current     "Proud panelů PV2 [%.2f A]" (gSolax)   { channel="modbus:data:localhostTCP:inputRegisters:PV2Current:number" }


Number Solax_RunMODE     "Provozní režim [MAP(Solax/Solax_RunMODE.map):%s]" (gSolax)   { channel="modbus:data:localhostTCP:inputRegisters:RunMODE:number" }

Number Solax_ConsumeFromGridToday     "Dnešní Spotřeba ze sítě [%.2f kWh]" (gSolax)   { channel="modbus:data:localhostTCP:inputRegisters100:consum_energy_today:number" }


Number Solax_FeedInPower     "Elektřina --ze/do++ sítě [%.0f W]" (gSolax)   { channel="modbus:data:localhostTCP:inputRegisters:FeedInPower:number" }
Number Solax_FeedInTotal     "Feed in total [%.2f W]" (gSolax)   { channel="modbus:data:localhostTCP:inputRegisters:FeedInTotal:number" }
Number Solax_FeedinEnergyToday     "Dnešní Dodávka do sítě [%.2f kWh]" (gSolax)   { channel="modbus:data:localhostTCP:inputRegisters100:feedin_energy_today:number" }
Number Solax_Language     "Jazyk" (gSolax)   { channel="modbus:data:localhostTCP:holdingRegisters100:Language:number" }
Number Solax_HouseConsumption "Spotřeba domu [%.0f W]" (gSolax)

Number Solax_DailyYield           "Výroba dnes [%.2f kWh]"  (gSolax)  { channel="modbus:data:localhostTCP:inputRegisters:DailyYield:number" }

Number solax_ExportControlUserLimit            "Export Control User Limit [%.0f W]"  (gSolax)  { channel="modbus:data:localhostTCP:holdingRegisters100:Export_Control_User_Limit:number" }
Number solax_WorkingMODE            "Working Mode [MAP(Solax/Solax_mode.map):%s]"  (gSolax)  { channel="modbus:data:localhostTCP:holdingRegisters100:WorkingMode:number" }
Switch solax_EPS_Mute            "EPS Mute"  (gSolax)  { channel="modbus:data:localhostTCP:holdingRegisters100:EPS_Mute:switch" }

Number Solax_BatteryPowerCharge            "Nabíjení++/--Vybíjení Baterie [%.0f W]"  (gSolax)  { channel="modbus:data:localhostTCP:inputRegisters:BatteryPowerCharge:number" }
Number Solax_BatteryTemp            "Teplota baterie [%.0f °C]"  (gSolax)  { channel="modbus:data:localhostTCP:inputRegisters:BatteryTemp:number" }
Number Solax_BatteryMinCapacity            "Minimální nabití baterie [%.0f%% ]"  (gSolax)  { channel="modbus:data:localhostTCP:holdingRegisters100:Battery1_MinCapacity:number" }
Number Solax_BatteryOutputEnergyToday            "Dnešní výdej baterie [%.2f kWh ]"  (gSolax)  { channel="modbus:data:localhostTCP:inputRegisters:BatteryOutputEnergyToday:number" }
Number Solax_GridTiedMinSetCapacity            "Min. kapacity baterie při napájení ze sítě [%.0f%% ]"  (gSolax)  { channel="modbus:data:localhostTCP:holdingRegisters200:GridTiedLimit:number" }


Group:Number:SUM gGridCurrentSum "Proud procházející fázemi [%.0f A]"   (gSolax)
Group:Number:SUM gGridPowerSum "Spotřeba procházející fázemi [%.0f W]"   (gSolax)

Number Solax_GridCurrent_R1            "Proud 1.Fáze [%.1f A]"   (gGridCurrentSum) { channel="modbus:data:localhostTCP:inputRegisters100:GridCurrent_R1:number" }
Number Solax_GridPower_R1            "Spotřeba 1. Fáze [%.0f W]" (gGridPowerSum)   { channel="modbus:data:localhostTCP:inputRegisters100:GridPower_R1:number" }

Number Solax_GridCurrent_S2            "Proud 2.Fáze [%.1f A]"  (gGridCurrentSum)  { channel="modbus:data:localhostTCP:inputRegisters100:GridCurrent_S2:number" }
Number Solax_GridPower_S2          "Spotřeba 2. Fáze [%.0f W]"  (gGridPowerSum)  { channel="modbus:data:localhostTCP:inputRegisters100:GridPower_S2:number" }

Number Solax_GridCurrent_T3            "Proud 3.Fáze [%.1f A]" (gGridCurrentSum)   { channel="modbus:data:localhostTCP:inputRegisters100:GridCurrent_T3:number" }
Number Solax_GridPower_T3          "Spotřeba 3. Fáze [%.0f W]"  (gGridPowerSum)  { channel="modbus:data:localhostTCP:inputRegisters100:GridPower_T3:number" }


For House Load you have to use rule:

rule "Null correction"
when 
    Item Solax_ACPower received update or
	Item Solax_FeedInPower received update
	
then

if (Solax_PowerDC1.state == NULL) {Solax_PowerDC1.sendCommand(0)}
if (Solax_PowerDC2.state == NULL) {Solax_PowerDC2.sendCommand(0)}
end



rule "House Load"
when 
    Item Solax_ACPower received update or
	Item Solax_FeedInPower received update
	
then

var Number Consumption

Consumption=(Solax_ACPower.state as Number - Solax_FeedInPower.state as Number)

Solax_HouseConsumption.sendCommand(Consumption)

					
					
end

Sitemap:

Text item=gPowerDC  icon="solar" label="Solax Inverter"  {
		
		Frame label="Basic Data" {
		Group item=gPowerDC label="Výkon panelů" {
		Text item=Solax_PowerDC1 label="Výkon panelů 1"
		Text item=Solax_PowerDC2 label="Výkon panelů 2"
		}
		Text item=Solax_BatterySOC label="Kapacita baterie"
		Text item=Solax_FeedInPower label="Elektřina --ze/do++ sítě" valuecolor=[Solax_FeedInPower==Uninitialized="lightgray", >=1="green", <=0="red"]
		Text item=Solax_BatteryPowerCharge label="Nabíjení++/--Vybíjení Baterie" valuecolor=[Solax_BatteryPowerCharge==Uninitialized="lightgray", >=1="green", <=0="red"]
		Text item=Solax_HouseConsumption label="Spotřeba domu" 
		}
		Frame label="Enhanced Data" {
		Text item=Solax_FeedinEnergyToday label="Dnešní dodávka do sítě"
		Text item=Solax_ConsumeFromGridToday label="Dnešní odběr ze sítě"
		Text item=Solax_DailyYield  label="Dnešní výroba"
		Text item=Solax_ACPower label="Aktuální Výkon měniče"
		Text item=Solax_TempRadiator label="Teplota chladiče měniče"
		Text item=Solax_RunMODE label="Režim měniče"
		
		Group item=gGridCurrentSum  icon="solar" label="Proud fází"	{
		Text item=Solax_GridCurrent_R1 label="Proud 1. fáze"
		Text item=Solax_GridCurrent_S2 label="Proud 2. fáze"
		Text item=Solax_GridCurrent_T3 label="Proud 3. fáze"
		}
		
		Group item=gGridPowerSum  icon="solar" label="Výkon fází"	{
		Text item=Solax_GridPower_R1 label="Výkon 1. fáze"
		Text item=Solax_GridPower_S2 label="Výkon 2. fáze"
		Text item=Solax_GridPower_T3 label="Výkon 3. fáze"
		}
		}
		
		Frame label="Battery data" {
		Text item=Solax_BatterySOC label="Kapacita baterie"
		Text item=Solax_BatteryPowerCharge label="Aktuální Nab++/--Vyb Baterie"  valuecolor=[Solax_BatteryPowerCharge==Uninitialized="lightgray", >=1="green", <=0="red"]
		Text item=Solax_BatteryTemp label="Teplota baterie"
		Text item=Solax_BatteryMinCapacity  label="Minimální kapacita baterie"
		Text item=Solax_BatteryOutputEnergyToday label="Dnešní výdej z baterie"
				
		
		}
		
		Frame label="Settings" {
		Selection item=Solax_Language label="Language" icon="chicken" mappings=[ 0="English", 1="Deutsch", 2="Français", 3="Polski", 4="Italiano" ]
		Selection item=solax_WorkingMODE label="Working Mode" icon="chicken" mappings=[ 0="Self Use", 1="ForceTime Use", 2="Back Up Mode", 3="Feedin Priority" ]
		Switch  item=solax_EPS_Mute label="Vypnout pípání EPS"	
		Setpoint item=Solax_GridTiedMinSetCapacity label="Minimální kapacita baterie při připojení sítě" minValue=0 maxValue=100 step=1
		Setpoint item=solax_ExportControlUserLimit label="Nastavení max. toku do sítě" minValue=0 maxValue=60000 step=1000
		}
		
		
		
		
		}
2 Likes

Cool ill test it as soon as i get back from vacation

Thanks Mads

Great, thanks!
Could you please elaborate on how battery modes work ?
How can I tell the inverter to charge or discharge a specific power value ?

Also, could you please let me know the source of your information ? I have found Solax Modbus spec V3.21 but it’s just the registers, I would want something more verbose that explains how modes work etc.

Hi, try to look to Solax manual. On the page 13 there you can find, how batterry modes work and also the other information.

Grid Tied Limit is the minimum percentage of battery capacity when connected to the grid. After an outage, the capacity may drop below this value. This value is used to ensure that the battery does not discharge too much and that energy remains in it in case of outage.

Export Control limit - you can set maximum value of energy exported to the grid. 60000 is maximum, 0 - no energy to the grid.

If you want to charge or discharge specific value, the easiest way would be create a rule in openhab to change battery mode or change GridTiedLimit. It depends on, want you need to do.

Thanks. Would you be interested in testing my Energy Management System when I’ve added Solax support ? You’d need a spare RPi.

1 Like

HI @zlomennypez

I have implemented your work and i must say this was very easy. NICE work :+1: :+1:

The only things i had to do:
1.Copy your work :wink:
2.Install modbus
3.Fill in inverter IP address

Now everything is basically working :slight_smile:
4.Change labels from czech to danish

In Solax_mode.map and Solax_RunMODE.map i added (to make openhab log happy)
-=missing
NULL=NULL

All of these seems to be empty, maybe because i’m running a Solax X3-Hybrid-G1/G2 ?

	Bridge poller holdingRegisters100 [ start=100, length=99, refresh=10000, type="holding" ] {
	
		Thing data Battery1_MinCapacity [ readStart="140", readValueType="uint16", writeStart="32", writeValueType="uint16", writeType="holding"]
		Thing data Language [ readStart="187", readValueType="uint16", writeStart="71", writeValueType="uint16", writeType="holding"]
		Thing data Export_Control_User_Limit [ readStart="182", readValueType="uint16", writeStart="66", writeValueType="uint16", writeType="holding"]
		Thing data EPS_Mute [ readStart="183", readValueType="uint16", writeStart="67", writeValueType="uint16", writeType="holding"]
		Thing data WorkingMode [ readStart="139", readValueType="uint16", writeStart="31", writeValueType="uint16", writeType="holding"]
	}
	
	Bridge poller holdingRegisters200 [ start=200, length=99, refresh=1000, type="holding" ] {
        
		Thing data GridTiedLimit [ readStart="274", readValueType="uint16", writeStart="167", writeValueType="uint16", writeType="holding" ]
		
    }

Thanks Mads

Thanks for your answer. Generation of inverter is the reason, why it doesnt work with controlling. We have to find correct address.

Try to look here: homeassistant-solax-modbus/const.py at main · wills106/homeassistant-solax-modbus · GitHub

Or try to find documentation of modbus rtu for gen2

HI @zlomennypez

I have been trying to figure out what registers to use
i have tried using CAS modbus scanner, when i change work mode from solax app i can see that holding register 140 changes accordantly 0-3

If i look in https://secondlifestorage.com/index.php?attachments/hybrid-x1-x3-g3-modbustcp-rtu-v3-21-english-pdf.24841/ it looks like 180 is read holding reg and 64 is write single input reg

i have no clue where to look

You have another version. Try to ask Solax for correct TCP version with write registers. If you find that Workmode is 140, try

Thing data WorkingMode [ readStart=“140”, readValueType=“int16”, writeStart=“31”, writeValueType=“int16”, writeType=“holding”]

but you have to get correct registers