Skip to content

Commit e3febd8

Browse files
authored
Solax: add phase switching (#26730)
1 parent 854d0a8 commit e3febd8

File tree

3 files changed

+185
-29
lines changed

3 files changed

+185
-29
lines changed

charger/solax.go

Lines changed: 124 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"context"
2222
"encoding/binary"
2323
"fmt"
24+
"strings"
2425

2526
"github.com/evcc-io/evcc/api"
2627
"github.com/evcc-io/evcc/util"
@@ -38,31 +39,44 @@ type Solax struct {
3839

3940
const (
4041
// holding (FC 0x03, 0x06, 0x10)
41-
solaxRegDeviceMode = 0x060D // uint16
42-
solaxRegStartChargeMode = 0x0610 // uint16
43-
solaxRegPhases = 0x0625 // uint16
44-
solaxRegCommandControl = 0x0627 // uint16
45-
solaxRegMaxCurrent = 0x0628 // uint16 0.01A
42+
solaxRegSerialNumber = 0x0600 // 7x string
43+
solaxRegDeviceMode = 0x060D // uint16
44+
solaxRegCommandControl = 0x0627 // uint16
45+
solaxRegMaxCurrent = 0x0628 // uint16 0.01A
46+
solaxRegPhaseSwitch = 0xA105 // uint16
4647

4748
// input (FC 0x04)
48-
solaxRegVoltages = 0x0000 // 3x uint16 0.01V
49-
solaxRegCurrents = 0x0004 // 3x uint16 0.01A
50-
solaxRegActivePower = 0x000B // uint16 1W
51-
solaxRegTotalEnergy = 0x0010 // uint32s 0.1kWh
52-
solaxRegState = 0x001D // uint16
53-
49+
solaxRegVoltages = 0x0000 // 3x uint16 0.01V
50+
solaxRegCurrents = 0x0004 // 3x uint16 0.01A
51+
solaxRegActivePower = 0x000B // uint16 1W
52+
solaxRegTotalEnergy = 0x0010 // uint32 0.1kWh
53+
solaxRegState = 0x001D // uint16
54+
solaxRegFaultCode = 0x001E // 2x uint32
55+
solaxRegFirmwareVersion = 0x0025 // uint16 Vx.xx
56+
solaxRegConnectionStrength = 0x0027 // uint16 1%
57+
solaxRegLockState = 0x002D // uint16
58+
solaxRegPhases = 0xA02A // uint16
59+
60+
// commands
5461
solaxCmdStop = 3
5562
solaxCmdStart = 4
5663

64+
// modes
5765
solaxModeStop = 0
5866
solaxModeFast = 1
67+
solaxModeECO = 2
68+
69+
// minimum firmware version for phase switching support
70+
solaxFirmwarePhaseSwitching = 905
5971
)
6072

6173
func init() {
6274
registry.AddCtx("solax", NewSolaxG1FromConfig)
6375
registry.AddCtx("solax-g2", NewSolaxG2FromConfig)
6476
}
6577

78+
//go:generate go tool decorate -f decorateSolax -b *Solax -r api.Charger -t "api.PhaseSwitcher,Phases1p3p,func(int) error" -t "api.PhaseGetter,GetPhases,func() (int, error)"
79+
6680
func NewSolaxG1FromConfig(ctx context.Context, other map[string]any) (api.Charger, error) {
6781
return NewSolaxFromConfig(ctx, other, true)
6882
}
@@ -104,7 +118,18 @@ func NewSolax(ctx context.Context, uri, device, comset string, baudrate int, pro
104118
isLegacyHw: isLegacyHw,
105119
}
106120

107-
return wb, err
121+
var phases1p3p func(int) error
122+
var phasesG func() (int, error)
123+
124+
if b, err := wb.conn.ReadInputRegisters(solaxRegFirmwareVersion, 1); err == nil {
125+
v := encoding.Uint16(b)
126+
if !wb.isLegacyHw && v >= solaxFirmwarePhaseSwitching {
127+
phases1p3p = wb.phases1p3p
128+
phasesG = wb.getPhases
129+
}
130+
}
131+
132+
return decorateSolax(wb, phases1p3p, phasesG), nil
108133
}
109134

110135
// getPhaseValues returns 3 sequential register values
@@ -135,13 +160,19 @@ func (wb *Solax) Status() (api.ChargeStatus, error) {
135160
5: // "Unavailable"
136161
return api.StatusA, nil
137162
case
138-
1, // "Preparing"
139-
8, // "SuspendedEVSE"
140-
7, // "SuspendedEV"
141-
3: // "Finishing"
163+
1, // "Preparing"
164+
3, // "Finishing"
165+
7, // "SuspendedEV"
166+
8, // "SuspendedEVSE"
167+
11, // "StartDelay"
168+
12, // "ChargPause"
169+
13, // "Stopping"
170+
17: // "PhaseSwitching"
142171
return api.StatusB, nil
143172
case 2: // "Charging"
144173
return api.StatusC, nil
174+
case 4: // "Fault"
175+
return api.StatusE, nil
145176
default:
146177
return api.StatusNone, fmt.Errorf("invalid status: %d", s)
147178
}
@@ -228,19 +259,86 @@ func (wb *Solax) Voltages() (float64, float64, float64, error) {
228259
return wb.getPhaseValues(solaxRegVoltages)
229260
}
230261

231-
/* https://github.com/evcc-io/evcc/pull/14108
232-
var _ api.PhaseSwitcher = (*Solax)(nil)
262+
// phases1p3p implements the api.PhaseSwitcher interface
263+
func (wb *Solax) phases1p3p(phases int) error {
264+
u := uint16(1)
265+
if phases == 3 {
266+
u = 2
267+
}
268+
269+
_, err := wb.conn.WriteSingleRegister(solaxRegPhaseSwitch, u)
233270

234-
// Phases1p3p implements the api.PhaseSwitcher interface
235-
func (wb *Solax) Phases1p3p(phases int) error {
236-
var u uint16
271+
return err
272+
}
237273

238-
if phases == 1 {
239-
u = 1
274+
// getPhases implements the api.PhaseGetter interface
275+
func (wb *Solax) getPhases() (int, error) {
276+
b, err := wb.conn.ReadInputRegisters(solaxRegPhases, 1)
277+
if err != nil {
278+
return 0, err
240279
}
241280

242-
_, err := wb.conn.WriteSingleRegister(solaxRegPhases, u)
281+
switch binary.BigEndian.Uint16(b) {
282+
case 1:
283+
return 1, nil
284+
case 2:
285+
return 3, nil
286+
default:
287+
return 0, nil
288+
}
289+
}
243290

244-
return err
291+
var _ api.Diagnosis = (*Solax)(nil)
292+
293+
// Diagnose implements the api.Diagnosis interface
294+
func (wb *Solax) Diagnose() {
295+
if b, err := wb.conn.ReadHoldingRegisters(solaxRegSerialNumber, 7); err == nil {
296+
fmt.Printf("\tSerial Number:\t%s\n", bytesAsString(b))
297+
}
298+
if b, err := wb.conn.ReadInputRegisters(solaxRegFirmwareVersion, 1); err == nil {
299+
v := encoding.Uint16(b)
300+
fmt.Printf("\tFirmware Version:\tV%d.%02d\n", v/100, v%100)
301+
}
302+
if b, err := wb.conn.ReadInputRegisters(solaxRegConnectionStrength, 1); err == nil {
303+
fmt.Printf("\tConnection Strength (RSSI):\t%d%%\n", encoding.Uint16(b))
304+
}
305+
if b, err := wb.conn.ReadInputRegisters(solaxRegFaultCode, 2); err == nil {
306+
code := binary.BigEndian.Uint32(b)
307+
fmt.Printf("\tFault Code:\t0x%08X", code)
308+
309+
// Collect all set bits
310+
var setBits []string
311+
for bitIndex := range 32 {
312+
if (code & (1 << bitIndex)) != 0 { // Check if the bit is set
313+
setBits = append(setBits, fmt.Sprintf("%d", bitIndex+1)) // Add the 1-based bit number
314+
}
315+
}
316+
if len(setBits) > 0 {
317+
fmt.Printf(", Set Bits: %s\n", strings.Join(setBits, ","))
318+
} else {
319+
fmt.Printf(", Set Bits: None\n")
320+
}
321+
}
322+
if b, err := wb.conn.ReadInputRegisters(solaxRegLockState, 1); err == nil {
323+
switch state := encoding.Uint16(b); state {
324+
case 0:
325+
fmt.Printf("\tLock State:\tUnlocked (%d)\n", state)
326+
case 1:
327+
fmt.Printf("\tLock State:\tLocked (%d)\n", state)
328+
default:
329+
fmt.Printf("\tLock State:\tUnknown (%d)\n", state)
330+
}
331+
}
332+
if b, err := wb.conn.ReadHoldingRegisters(solaxRegDeviceMode, 1); err == nil {
333+
switch state := encoding.Uint16(b); state {
334+
case solaxModeStop:
335+
fmt.Printf("\tDevice Mode:\tStop (%d)\n", state)
336+
case solaxModeFast:
337+
fmt.Printf("\tDevice Mode:\tFast (%d)\n", state)
338+
case solaxModeECO:
339+
fmt.Printf("\tDevice Mode:\tECO (%d)\n", state)
340+
default:
341+
fmt.Printf("\tDevice Mode:\tUnknown (%d)\n", state)
342+
}
343+
}
245344
}
246-
*/

charger/solax_decorators.go

Lines changed: 58 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

templates/definition/charger/solax-g2.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ products:
33
- brand: Solax
44
description:
55
generic: X3-HAC
6-
capabilities: ["mA"]
6+
capabilities: ["1p3p", "mA"]
77
requirements:
88
evcc: ["sponsorship"]
99
description:
10-
de: Die Wallbox muss sich im Modus "Schnell" befinden und vom Wechselrichtersystem entkoppelt sein.
11-
en: The charger must be in “Fast” mode and decoupled from the inverter system.
10+
de: Die Wallbox muss sich im Modus "Schnell" befinden und vom Wechselrichtersystem entkoppelt sein. Die Wallbox muss Firmware Version V9.05 oder höher installiert haben, damit die Phasenumschaltung funktioniert.
11+
en: The charger must be in “Fast” mode and decoupled from the inverter system. For the phase switching to work, the charger must have firmware version V9.05 or higher installed.
1212
params:
1313
- name: modbus
1414
choice: ["rs485"]

0 commit comments

Comments
 (0)