@@ -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
3940const (
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
6173func 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+
6680func 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 ("\t Serial 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 ("\t Firmware Version:\t V%d.%02d\n " , v / 100 , v % 100 )
301+ }
302+ if b , err := wb .conn .ReadInputRegisters (solaxRegConnectionStrength , 1 ); err == nil {
303+ fmt .Printf ("\t Connection 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 ("\t Fault Code:\t 0x%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 ("\t Lock State:\t Unlocked (%d)\n " , state )
326+ case 1 :
327+ fmt .Printf ("\t Lock State:\t Locked (%d)\n " , state )
328+ default :
329+ fmt .Printf ("\t Lock State:\t Unknown (%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 ("\t Device Mode:\t Stop (%d)\n " , state )
336+ case solaxModeFast :
337+ fmt .Printf ("\t Device Mode:\t Fast (%d)\n " , state )
338+ case solaxModeECO :
339+ fmt .Printf ("\t Device Mode:\t ECO (%d)\n " , state )
340+ default :
341+ fmt .Printf ("\t Device Mode:\t Unknown (%d)\n " , state )
342+ }
343+ }
245344}
246- */
0 commit comments