Rest API for Quells Q.HOME ESS+ HYB G3 rsp. SOLAX X3 G4:
After a lot of research and reading forum contributions I’ll summarize my findings here:
The sensor values are gotten by calling:
http://<ip>/optType=ReadRealTimeData&pwd=<password>
where ìp
is the IP address of the WIFI adapter and password
is the serial number of the WIFI adapter.
This litte Ruby program documents the sensor value positions and it’s interpretation.
#!/usr/bin/ruby
# Extract and explain the status data from the Solax X3 G4 Hybrid inverter using the WiFI adapter
# This inverter is also branded as QCells Q.HOME+ ESS HYB-G3
# Peter Ramm, 2023-11-23
require 'net/http'
require 'uri'
require 'json'
puts "\nexplain_solax_x3_g4.rb, Peter Ramm, 2023-11-23"
puts "All attributes whose interpretation is known or which have a value other than 0 or 1 are displayed.\n\n"
# Check if there are any command line arguments
if ARGV.empty? || ARGV.count != 2
puts "Error: Exactly two paramaters are required: <IP address> <serial number>"
exit 1
end
ip_address = ARGV[0]
serial_number = ARGV[1]
puts "Time : #{Time.now}\n"
puts "IP address : #{ip_address}\n"
puts "WiFi serial number : #{serial_number}\n"
# Set the URL of the endpoint you want to send the POST request to
url = URI.parse("http://#{ip_address}/")
# Create a new HTTP POST request
request = Net::HTTP::Post.new(url.path)
# Set the request body if needed
request.body = "optType=ReadRealTimeData&pwd=#{serial_number}"
# Set any additional headers if required
request['Content-Type'] = 'application/x-www-form-urlencoded'
# Send the POST request
response = Net::HTTP.start(url.host, url.port, use_ssl: url.scheme == 'https') do |http|
http.request(request)
end
# Print the response
body = JSON.parse(response.body)
@data = body['Data']
info = body['Information']
puts "Firmware version : #{body['ver']}\n"
puts "Inverter max. power : #{info[0]} KW\n\n"
puts "Inverter serial no. : #{info[2]}\n\n"
puts "Data attributes :\n"
puts "---------------------------------------------\n"
def to_signed16(val)
val -= 2**16 if val > 32767
val
end
def to_signed32(val)
val -= 2**32 if val > 2147483647
val
end
def print(index, raw, parsed, unit, name)
puts "#{index.to_s.rjust(3)}: #{raw.to_s.rjust(6)} #{parsed.to_s.rjust(8)} #{unit.ljust(4)} #{name}"
end
# print parsed value, unit and text representation of the response
def parse(index, divisor=nil, unit='', name = '', signed_16: false)
raw = @data[index]
raw = to_signed16(raw) if signed_16
parsed = divisor.nil? ? nil : raw / divisor.to_f
print(index, raw, parsed, unit, name) if name != '' || (raw != 0 && raw != 1)
end
def parse_32(index, divisor, low_value, high_value, unit, name, signed_32: false)
raw = @data[index]
sum = high_value * 2**16 + low_value
sum = to_signed32(sum) if signed_32
parsed = divisor.nil? ? nil : sum / divisor.to_f
print(index, raw, parsed, unit, name) if name != '' || raw != 0
end
parse(0, 10, 'V', 'Inverter AC voltage phase 1')
parse(1, 10, 'V', 'Inverter AC voltage phase 2')
parse(2, 10, 'V', 'Inverter AC voltage phase 3')
parse(3, 10, 'A', 'Inverter AC current phase 1 (inaccurate)', signed_16: true)
parse(4, 10, 'A', 'Inverter AC current phase 2 (inaccurate)', signed_16: true)
parse(5, 10, 'A', 'Inverter AC current phase 3 (inaccurate)', signed_16: true)
parse(6, 1, 'W', 'Inverter AC power phase 1', signed_16: true)
parse(7, 1, 'W', 'Inverter AC power phase 2', signed_16: true)
parse(8, 1, 'W', 'Inverter AC power phase 3', signed_16: true)
parse(9, 1, 'W', 'Inverter AC power all phases, negativ = import from grid', signed_16: true)
parse(10, 10, 'V', 'PV1 Voltage')
parse(11, 10, 'V', 'PV2 Voltage')
parse(12, 10, 'A', 'PV1 Current')
parse(13, 10, 'A', 'PV2 Current')
parse(14, 1, 'W', 'PV1 Power')
parse(15, 1, 'W', 'PV2 Power')
parse(16, 100, 'Hz', 'Grid Frequency Phase 1')
parse(17, 100, 'Hz', 'Grid Frequency Phase 2')
parse(18, 100, 'Hz', 'Grid Frequency Phase 3')
parse(19, 1, '', 'Inverter Operation mode')
parse(20)
parse(21)
parse(22)
parse(23, 10, 'Y', 'EPS 1 Voltage')
parse(24, 10, 'Y', 'EPS 2 Voltage')
parse(25, 10, 'Y', 'EPS 3 Voltage')
parse(26, 10, 'A', 'EPS 1 Current', signed_16: true)
parse(27, 10, 'A', 'EPS 2 Current', signed_16: true)
parse(28, 10, 'A', 'EPS 3 Current', signed_16: true)
parse(29, 1, 'W', 'EPS 1 Power', signed_16: true)
parse(30, 1, 'W', 'EPS 2 Power', signed_16: true)
parse(31, 1, 'W', 'EPS 3 Power', signed_16: true)
parse(32)
parse(33)
parse_32(34, 1, @data[34], @data[35], 'W', 'Grid AC power: + export, - import', signed_32: true)
parse_32(35, 1, @data[34], @data[35], 'W', 'Grid AC power: + export, - import', signed_32: true)
parse(36)
parse(37)
parse(38)
parse(39, 100, 'V', 'Battery Voltage')
parse(40, 100, 'A', 'Battery Current, + charge, - discharge', signed_16: true)
parse(41, 1, 'W', 'Battery Power, + charge, - discharge', signed_16: true)
parse(42)
parse(43)
parse(44)
parse(45, 1, '', 'Battery BMS status (1=ok)')
parse(46, 1, '°C', 'Inverter inner temperature, 0 if shut off')
parse(47, 1, 'W', 'AC house consumption now')
parse(48)
parse(49)
parse(50)
parse(51)
parse(52)
parse(53)
parse(54, 1, '°C', 'Inverter radiator temperature, 0 if shut off')
parse(55)
parse(56)
parse(57)
parse(58)
parse(59)
parse(60)
parse(61)
parse(62)
parse(63)
parse(64)
parse(65)
parse(66)
parse(67)
parse_32(68, 10, @data[68], @data[69], 'kWh', 'Energy yield total: PV - battery charge + battery discharge')
parse_32(69, 10, @data[68], @data[69], 'kWh', 'Energy yield total: PV - battery charge + battery discharge')
parse(70, 10, 'kWh', 'Energy yield today: PV - battery charge + battery discharge')
parse(71)
parse(72)
parse(73)
parse_32(74, 10, @data[74], @data[75], 'kWh', 'Total Battery Discharge Energy')
parse_32(75, 10, @data[74], @data[75], 'kWh', 'Total Battery Discharge Energy')
parse_32(76, 10, @data[76], @data[77], 'kWh', 'Total Battery Charge Energy')
parse_32(77, 10, @data[76], @data[77], 'kWh', 'Total Battery Charge Energy')
parse(78, 10, 'kWh', 'Battery Discharge Energy today')
parse(79, 10, 'kWh', 'Battery Charge Energy today')
parse_32(80, 10, @data[80], @data[81], 'kWh', 'Total PV Energy')
parse_32(81, 10, @data[80], @data[81], 'kWh', 'Total PV Energy')
parse(82,10, 'kWh', 'PV Energy today, not matter if loaded into battery or feed into grid or consumed by house')
parse(83)
parse(84)
parse(85)
parse_32(86, 100, @data[86], @data[87], 'kWh', 'Total Feed-in Energy')
parse_32(87, 100, @data[86], @data[87], 'kWh', 'Total Feed-in Energy')
parse_32(88, 100, @data[88], @data[89], 'kWh', 'Total energy consumption from grid')
parse_32(89, 100, @data[88], @data[89], 'kWh', 'Total energy consumption from grid')
parse(90, 100, 'kWh', 'Feed-in energy into grid today')
parse(91)
parse(92, 100, 'kWh', 'Energy consumption from grid today')
parse(93)
parse(94)
parse(95)
parse(96)
parse(97)
parse(98)
parse(99)
parse(100)
parse(101)
parse(102)
parse(103, 1, '%', 'Battery Remaining Capacity')
parse(104)
parse(105, 1, '°C', 'Battery Temperature')
parse(106, 10, 'kWh', 'Battery remaining energy')
parse(107)
parse(108)
parse(109)
parse(110)
parse(111)
parse(112)
parse(113)
parse(114)
parse(115)
parse(116)
parse(117)
parse(118)
parse(119)
parse(120)
parse(121)
parse(122)
parse(123)
parse(124)
parse(125)
parse(126)
parse(127)
parse(128)
parse(129)
parse(130)
parse(131)
parse(132)
parse(133)
parse(134)
parse(135)
parse(136)
parse(137)
parse(138)
parse(139)
parse(140)
parse(141)
parse(142)
parse(143)
parse(144)
parse(145)
parse(146)
parse(147)
parse(148)
parse(149)
parse(150)
parse(151)
parse(152)
parse(153)
parse(154)
parse(155)
parse(156)
parse(157)
parse(158)
parse(159)
parse(160)
parse(161)
parse(162)
parse(163)
parse(164)
parse(165)
parse(166)
parse(167)
parse(168, 1, '', 'Battery operation mode: 0=Self Use Mode, 1=Force Time Use, 2=Back Up Mode, 3=Feed-in Priority')
parse_32(169, 100, @data[169], @data[170], 'V', 'Battery voltage')
parse_32(170, 100, @data[169], @data[170], 'V', 'Battery voltage')
parse(171)
parse(172)
parse(173)
parse(174)
parse(175)
parse(176)
Store the Ruby file as explain_solax_x3_g4.rb
and call with parameter “IP address” and “Serial no”.
> ruby explain_solax_x3_g4.rb <ip> <serialno>
This shows the current values for all sensors whose interpretation is known or which have a value other than 0 or 1. Here’s an example result:
explain_solax_x3_g4.rb, Peter Ramm, 2023-11-23
All attributes whose interpretation is known or which have a value other than 0 or 1 are displayed.
Time : 2023-12-10 15:57:12 +0100
IP address : qhome.fritz.box
WiFi serial number : SRK22XXDDZ
Firmware version : 3.006.04
Inverter max. power : 8.0 KW
Inverter serial no. : H34C08I9162381
Data attributes :
---------------------------------------------
0: 2338 233.8 V Inverter AC voltage phase 1
1: 2360 236.0 V Inverter AC voltage phase 2
2: 2376 237.6 V Inverter AC voltage phase 3
3: 15 1.5 A Inverter AC current phase 1 (inaccurate)
4: 15 1.5 A Inverter AC current phase 2 (inaccurate)
5: 15 1.5 A Inverter AC current phase 3 (inaccurate)
6: 261 261.0 W Inverter AC power phase 1
7: 265 265.0 W Inverter AC power phase 2
8: 262 262.0 W Inverter AC power phase 3
9: 788 788.0 W Inverter AC power all phases, negativ = import from grid
10: 2782 278.2 V PV1 Voltage
11: 2557 255.7 V PV2 Voltage
12: 1 0.1 A PV1 Current
13: 0 0.0 A PV2 Current
14: 52 52.0 W PV1 Power
15: 0 0.0 W PV2 Power
16: 4998 49.98 Hz Grid Frequency Phase 1
17: 4997 49.97 Hz Grid Frequency Phase 2
18: 4997 49.97 Hz Grid Frequency Phase 3
19: 2 2.0 Inverter Operation mode
23: 0 0.0 Y EPS 1 Voltage
24: 0 0.0 Y EPS 2 Voltage
25: 0 0.0 Y EPS 3 Voltage
26: 0 0.0 A EPS 1 Current
27: 0 0.0 A EPS 2 Current
28: 0 0.0 A EPS 3 Current
29: 0 0.0 W EPS 1 Power
30: 0 0.0 W EPS 2 Power
31: 0 0.0 W EPS 3 Power
34: 65490 -46.0 W Grid AC power: + export, - import
35: 65535 -46.0 W Grid AC power: + export, - import
39: 29810 298.1 V Battery Voltage
40: -250 -2.5 A Battery Current, + charge, - discharge
41: -745 -745.0 W Battery Power, + charge, - discharge
42: 2938
43: 65511
44: 64802
45: 1 1.0 Battery BMS status (1=ok)
46: 41 41.0 °C Inverter inner temperature, 0 if shut off
47: 834 834.0 W AC house consumption now
48: 256
49: 14385
50: 2575
51: 5900
52: 100
54: 27 27.0 °C Inverter radiator temperature, 0 if shut off
68: 1111 111.1 kWh Energy yield total: PV - battery charge + battery discharge
69: 0 111.1 kWh Energy yield total: PV - battery charge + battery discharge
70: 37 3.7 kWh Energy yield today: PV - battery charge + battery discharge
71: 55
74: 501 50.1 kWh Total Battery Discharge Energy
75: 0 50.1 kWh Total Battery Discharge Energy
76: 605 60.5 kWh Total Battery Charge Energy
77: 0 60.5 kWh Total Battery Charge Energy
78: 8 0.8 kWh Battery Discharge Energy today
79: 11 1.1 kWh Battery Charge Energy today
80: 1268 126.8 kWh Total PV Energy
81: 0 126.8 kWh Total PV Energy
82: 43 4.3 kWh PV Energy today, not matter if loaded into battery or feed into grid or consumed by house
86: 241 2.41 kWh Total Feed-in Energy
87: 0 2.41 kWh Total Feed-in Energy
88: 25719 257.19 kWh Total energy consumption from grid
89: 0 257.19 kWh Total energy consumption from grid
90: 0 0.0 kWh Feed-in energy into grid today
92: 392 3.92 kWh Energy consumption from grid today
103: 11 11.0 % Battery Remaining Capacity
105: 22 22.0 °C Battery Temperature
106: 10 1.0 kWh Battery remaining energy
107: 256
108: 3504
109: 2400
110: 140
111: 183
112: 156
113: 148
114: 31
115: 31
116: 9
117: 1620
118: 778
119: 15163
120: 14906
121: 15163
125: 3107
126: 3094
127: 16915
128: 2
129: 20564
130: 12339
131: 18753
132: 12599
133: 18736
134: 12612
135: 12345
136: 20564
137: 12339
138: 18754
139: 12610
140: 18740
141: 13895
142: 13881
143: 20564
144: 12339
145: 18754
146: 12856
147: 18742
148: 12614
149: 13618
150: 20564
151: 12339
152: 18754
153: 12610
154: 18740
155: 13127
156: 14647
164: 4098
165: 4867
166: 769
167: 258
168: 0 0.0 Battery operation mode: 0=Self Use Mode, 1=Force Time Use, 2=Back Up Mode, 3=Feed-in Priority
169: 29810 298.1 V Battery voltage
170: 0 298.1 V Battery voltage
Due to posting limits the configuration.yaml
follows in next post.