205 lines
5.5 KiB
Go
205 lines
5.5 KiB
Go
package core
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/evcc-io/evcc/api"
|
|
"github.com/evcc-io/evcc/core/keys"
|
|
)
|
|
|
|
// setPhasesConfigured sets the default phase configuration
|
|
func (lp *Loadpoint) setPhasesConfigured(phases int) {
|
|
lp.phasesConfigured = phases
|
|
lp.publish(keys.PhasesConfigured, lp.phasesConfigured)
|
|
lp.settings.SetInt(keys.PhasesConfigured, int64(lp.phasesConfigured))
|
|
|
|
// configured phases are actual phases for non-1p3p charger
|
|
// for 1p3p charger, configuration does not mean that the physical state has changed, so don't touch it
|
|
if !lp.hasPhaseSwitching() {
|
|
lp.setPhases(phases)
|
|
}
|
|
}
|
|
|
|
// SetPhases sets the number of enabled phases without modifying the charger
|
|
func (lp *Loadpoint) SetPhases(phases int) {
|
|
lp.Lock()
|
|
defer lp.Unlock()
|
|
lp.setPhases(phases)
|
|
}
|
|
|
|
// setPhases sets the number of enabled phases without modifying the charger
|
|
func (lp *Loadpoint) setPhases(phases int) {
|
|
if lp.phases != phases {
|
|
lp.phases = phases
|
|
|
|
// reset timer to disabled state
|
|
lp.resetPhaseTimer()
|
|
|
|
// measure phases after switching
|
|
lp.resetMeasuredPhases()
|
|
}
|
|
}
|
|
|
|
// ResetMeasuredPhases resets measured phases to unknown on vehicle disconnect, phase switch or phase api call
|
|
func (lp *Loadpoint) ResetMeasuredPhases() {
|
|
lp.Lock()
|
|
defer lp.Unlock()
|
|
lp.resetMeasuredPhases()
|
|
}
|
|
|
|
// resetMeasuredPhases resets measured phases to unknown on vehicle disconnect, phase switch or phase api call
|
|
func (lp *Loadpoint) resetMeasuredPhases() {
|
|
lp.measuredPhases = 0
|
|
lp.publish(keys.PhasesActive, lp.activePhases())
|
|
}
|
|
|
|
// GetMeasuredPhases provides synchronized access to measuredPhases
|
|
func (lp *Loadpoint) GetMeasuredPhases() int {
|
|
lp.RLock()
|
|
defer lp.RUnlock()
|
|
return lp.getMeasuredPhases()
|
|
}
|
|
|
|
// getMeasuredPhases provides synchronized access to measuredPhases
|
|
func (lp *Loadpoint) getMeasuredPhases() int {
|
|
return lp.measuredPhases
|
|
}
|
|
|
|
// assume 3p for switchable charger during startup
|
|
const unknownPhases = 3
|
|
|
|
func expect(phases int) int {
|
|
if phases > 0 {
|
|
return phases
|
|
}
|
|
return unknownPhases
|
|
}
|
|
|
|
// ActivePhases returns the number of expectedly active phases for the meter.
|
|
// If unknown for 1p3p chargers during startup it will assume 3p.
|
|
func (lp *Loadpoint) ActivePhases() int {
|
|
lp.Lock()
|
|
defer lp.Unlock()
|
|
return lp.activePhases()
|
|
}
|
|
|
|
// activePhases returns the number of expectedly active phases for the meter.
|
|
// If unknown for 1p3p chargers during startup it will assume 3p.
|
|
func (lp *Loadpoint) activePhases() int {
|
|
physical := lp.phases
|
|
vehicle := lp.getVehiclePhases()
|
|
measured := lp.getMeasuredPhases()
|
|
charger := lp.getChargerPhysicalPhases()
|
|
|
|
active := min(expect(vehicle), expect(physical), expect(measured), expect(charger))
|
|
|
|
// sanity check - we should not assume less active phases than actually measured
|
|
if measured > 0 && active < measured {
|
|
lp.log.WARN.Printf("phase mismatch between %dp measured for %dp vehicle and %dp charger", measured, vehicle, physical)
|
|
}
|
|
|
|
return active
|
|
}
|
|
|
|
// MinActivePhases returns the minimum number of active phases for the loadpoint.
|
|
func (lp *Loadpoint) MinActivePhases() int {
|
|
lp.RLock()
|
|
defer lp.RUnlock()
|
|
return lp.minActivePhases()
|
|
}
|
|
|
|
// minActivePhases returns the minimum number of active phases for the loadpoint.
|
|
func (lp *Loadpoint) minActivePhases() int {
|
|
if lp.hasPhaseSwitching() || lp.phasesConfigured == 1 {
|
|
return 1
|
|
}
|
|
|
|
return lp.maxActivePhases()
|
|
}
|
|
|
|
// MaxActivePhases returns the maximum number of active phases for the loadpoint.
|
|
func (lp *Loadpoint) MaxActivePhases() int {
|
|
lp.RLock()
|
|
defer lp.RUnlock()
|
|
return lp.maxActivePhases()
|
|
}
|
|
|
|
// maxActivePhases returns the maximum number of active phases for the loadpoint.
|
|
func (lp *Loadpoint) maxActivePhases() int {
|
|
physical := lp.phases
|
|
measured := lp.getMeasuredPhases()
|
|
vehicle := lp.getVehiclePhases()
|
|
charger := lp.getChargerPhysicalPhases()
|
|
|
|
// during 1p or unknown config, 1p measured is not a restriction
|
|
if physical <= 1 || vehicle == 1 || charger == 1 {
|
|
measured = 0
|
|
}
|
|
|
|
// if 1p3p supported then assume configured limit or 3p
|
|
if lp.hasPhaseSwitching() {
|
|
physical = lp.phasesConfigured
|
|
}
|
|
|
|
return min(expect(vehicle), expect(physical), expect(measured), expect(charger))
|
|
}
|
|
|
|
func (lp *Loadpoint) getVehiclePhases() int {
|
|
if v := lp.GetVehicle(); v != nil {
|
|
return v.Phases()
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
func (lp *Loadpoint) getChargerPhysicalPhases() int {
|
|
if cc, ok := api.Cap[api.PhaseDescriber](lp.charger); ok {
|
|
return cc.Phases()
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
func (lp *Loadpoint) hasPhaseSwitching() bool {
|
|
return api.HasCap[api.PhaseSwitcher](lp.charger)
|
|
}
|
|
|
|
// syncChargerPhases synchronizes the assumed phase state with the charger's actual state.
|
|
// Chargers may reconfigure phases internally, i.e. when the vehicle is (dis)connected.
|
|
func (lp *Loadpoint) syncChargerPhases() error {
|
|
phases := lp.GetPhases()
|
|
if !lp.hasPhaseSwitching() || phases <= 0 {
|
|
return nil
|
|
}
|
|
|
|
if pg, ok := api.Cap[api.PhaseGetter](lp.charger); ok {
|
|
chargerPhases, err := pg.GetPhases()
|
|
if err != nil {
|
|
if errors.Is(err, api.ErrNotAvailable) {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("charger get phases: %w", err)
|
|
}
|
|
|
|
if chargerPhases > 0 && chargerPhases != phases {
|
|
lp.log.WARN.Printf("charger logic error: phases mismatch (got %d, expected %d)", chargerPhases, phases)
|
|
lp.SetPhases(chargerPhases)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// use measured phase currents for active phases as fallback if charger does not provide phases
|
|
chargerPhases := lp.GetMeasuredPhases()
|
|
if chargerPhases == 2 {
|
|
chargerPhases = 3
|
|
}
|
|
|
|
if chargerPhases > phases {
|
|
lp.log.WARN.Printf("charger logic error: phases mismatch (got %d measured, expected %d)", chargerPhases, phases)
|
|
lp.SetPhases(chargerPhases)
|
|
}
|
|
|
|
return nil
|
|
}
|