Victron: add phase switching (#28408)

This commit is contained in:
Eric Nieuwenhuijsen 2026-03-22 11:07:14 +01:00 • committed by GitHub
parent 4a41ad99d1
commit 420a0e9f9b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 135 additions and 21 deletions

View file

@ -36,13 +36,15 @@ type Victron struct {
}
type victronRegs struct {
Mode uint16
Energy uint16
Power uint16
Status uint16
SetCurrent uint16
Enabled uint16
isGX bool
Mode uint16
Energy uint16
Power uint16
Status uint16
SetCurrent uint16
Enabled uint16
PhaseSwitch uint16
ThreePhaseEnabled uint16
isGX bool
}
var (
@ -57,13 +59,15 @@ var (
}
victronEVCS = victronRegs{
Mode: 5009,
Energy: 5021,
Power: 5014,
Status: 5015,
SetCurrent: 5016,
Enabled: 5010,
isGX: false,
Mode: 5009,
Energy: 5021,
Power: 5014,
Status: 5015,
SetCurrent: 5016,
Enabled: 5010,
PhaseSwitch: 5055,
ThreePhaseEnabled: 5100,
isGX: false,
}
)
@ -72,17 +76,19 @@ func init() {
registry.AddCtx("victron-evcs", NewVictronEVCSFromConfig)
}
// NewVictronGXFromConfig creates a ABB charger from generic config
//go:generate go tool decorate -f decorateVictron -b *Victron -r api.Charger -t api.PhaseSwitcher,api.PhaseGetter
// NewVictronGXFromConfig creates a Victron charger from generic config
func NewVictronGXFromConfig(ctx context.Context, other map[string]any) (api.Charger, error) {
return NewVictronFromConfig(ctx, other, victronGX)
}
// NewVictronEVCSFromConfig creates a ABB charger from generic config
// NewVictronEVCSFromConfig creates a Victron charger from generic config
func NewVictronEVCSFromConfig(ctx context.Context, other map[string]any) (api.Charger, error) {
return NewVictronFromConfig(ctx, other, victronEVCS)
}
// NewVictronFromConfig creates a ABB charger from generic config
// NewVictronFromConfig creates a Victron charger from generic config
func NewVictronFromConfig(ctx context.Context, other map[string]any, regs victronRegs) (api.Charger, error) {
cc := modbus.TcpSettings{
ID: cast.ToUint8(regs.isGX) * 100,
@ -119,7 +125,23 @@ func NewVictron(ctx context.Context, uri string, slaveID uint8, regs victronRegs
return nil, errors.New("charger must be in manual mode")
}
return wb, nil
// phase switching (EVCS only, requires hardware modification with second contactor)
var phasesS func(int) error
var phasesG func() (int, error)
if !regs.isGX {
b, err := wb.conn.ReadHoldingRegisters(wb.regs.ThreePhaseEnabled, 1)
if err != nil {
return nil, err
}
if binary.BigEndian.Uint16(b) == 1 {
phasesS = wb.phases1p3p
phasesG = wb.getPhases
}
}
return decorateVictron(wb, phasesS, phasesG), nil
}
// Status implements the api.Charger interface
@ -132,7 +154,7 @@ func (wb *Victron) Status() (api.ChargeStatus, error) {
u := binary.BigEndian.Uint16(b)
switch u {
case 0, 1, 2, 3:
return api.ChargeStatusString(string('A' + rune(binary.BigEndian.Uint16(b))))
return api.ChargeStatusString(string('A' + rune(u)))
case 5, 6, 21:
return api.StatusB, nil
default:
@ -194,3 +216,28 @@ func (wb *Victron) ChargedEnergy() (float64, error) {
return float64(binary.BigEndian.Uint16(b)) / 100, nil
}
// phases1p3p implements the api.PhaseSwitcher interface
func (wb *Victron) phases1p3p(phases int) error {
// register 5055: 1 = single phase, 0 = three phase
var u uint16
if phases == 1 {
u = 1
}
_, err := wb.conn.WriteSingleRegister(wb.regs.PhaseSwitch, u)
return err
}
// getPhases implements the api.PhaseGetter interface
func (wb *Victron) getPhases() (int, error) {
b, err := wb.conn.ReadHoldingRegisters(wb.regs.PhaseSwitch, 1)
if err != nil {
return 0, err
}
if binary.BigEndian.Uint16(b) == 1 {
return 1, nil
}
return 3, nil
}

View file

@ -0,0 +1,58 @@
package charger
// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT.
import (
"github.com/evcc-io/evcc/api"
)
func decorateVictron(base *Victron, phaseSwitcher func(int) error, phaseGetter func() (int, error)) api.Charger {
switch {
case phaseSwitcher == nil:
return base
case phaseGetter == nil && phaseSwitcher != nil:
return &struct {
*Victron
api.PhaseSwitcher
}{
Victron: base,
PhaseSwitcher: &decorateVictronPhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
case phaseGetter != nil && phaseSwitcher != nil:
return &struct {
*Victron
api.PhaseGetter
api.PhaseSwitcher
}{
Victron: base,
PhaseGetter: &decorateVictronPhaseGetterImpl{
phaseGetter: phaseGetter,
},
PhaseSwitcher: &decorateVictronPhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
}
return nil
}
type decorateVictronPhaseGetterImpl struct {
phaseGetter func() (int, error)
}
func (impl *decorateVictronPhaseGetterImpl) GetPhases() (int, error) {
return impl.phaseGetter()
}
type decorateVictronPhaseSwitcherImpl struct {
phaseSwitcher func(int) error
}
func (impl *decorateVictronPhaseSwitcherImpl) Phases1p3p(p0 int) error {
return impl.phaseSwitcher(p0)
}

View file

@ -3,10 +3,19 @@ products:
- brand: Victron
description:
generic: EV Charging Station
capabilities: ["1p3p"]
requirements:
description:
en: Enter the host of the charger (not the GX device) and ensure that the charger is in manual mode.
de: Trage den Host der Wallbox (nicht des GX-Geräts) ein und stelle sicher, dass die Wallbox sich im Modus "Manual" befindet.
en: |
Enter the host of the charger (not the GX device) and ensure that the charger is in manual mode.
To enable 1P/3P phase switching a hardware modification is required: the 4P contactor must be replaced with 2×2P contactors. The first contactor switches L1+N, the second switches L2+L3 and is controlled by the second relay on the PCB. Register 5100 must be set to 1 to enable phase switching.
[More info](https://community.victronenergy.com/t/charging-an-ev-with-excess-solar-switching-between-1p-and-3p/21181)
de: |
Trage den Host der Wallbox (nicht des GX-Geräts) ein und stelle sicher, dass die Wallbox sich im Modus "Manual" befindet.
Für die 1P/3P Phasenumschaltung ist eine Hardware-Modifikation erforderlich: Der 4P-Schütz muss durch 2×2P-Schütze ersetzt werden. Der erste Schütz schaltet L1+N, der zweite L2+L3 und wird über das zweite Relais auf der Platine gesteuert. Register 5100 muss auf 1 gesetzt werden, um die Phasenumschaltung zu aktivieren.
[Mehr Infos](https://community.victronenergy.com/t/charging-an-ev-with-excess-solar-switching-between-1p-and-3p/21181)
params:
- name: modbus
choice: ["tcpip"]