Add battery meter support (#83)
* Add battery meter support * Clarify configuration
This commit is contained in:
parent
7c62c219af
commit
532e6bdfe5
4 changed files with 75 additions and 35 deletions
18
README.md
18
README.md
|
|
@ -25,7 +25,7 @@ EVCC is an extensible EV Charge Controller with PV integration implemented in [G
|
|||
- [Charge Modes](#charge-modes)
|
||||
- [PV generator configuration](#pv-generator-configuration)
|
||||
- [Charger configuration](#charger-configuration)
|
||||
- [Implementation](#implementation)
|
||||
- [Configuration](#configuration)
|
||||
- [Charger](#charger)
|
||||
- [Wallbe hardware preparation](#wallbe-hardware-preparation)
|
||||
- [OpenWB slave mode](#openwb-slave-mode)
|
||||
|
|
@ -99,6 +99,18 @@ For both PV modes, EVCC needs to assess how much residual PV power is available
|
|||
|
||||
In this setup, *residual power* is used as margin to account for fluctuations in PV production that may be faster than EVCC's control loop.
|
||||
|
||||
- **Battery meter**: *battery meter* is used if a home battery is installed and you want charging the EV take priority over charging the home battery. As the home battery would otherwise "grab" all available PV power, this meter measures the home battery charging power.
|
||||
|
||||
With *grid meter* the charger is then allowed to consume:
|
||||
|
||||
Charge Power = Current Charge Power - Grid Meter Power + Battery Meter Power - Residual Power
|
||||
|
||||
or without *grid meter*
|
||||
|
||||
Charge Power = PV Meter Power + Battery Meter Power - Residual Power
|
||||
|
||||
The *battery meter* is expected to deliver negative values when charging, positives values signal discharging and are ignored.
|
||||
|
||||
### Charger configuration
|
||||
|
||||
When using a *grid meter* for accurate control of PV utilization, EVCC needs to be able to determine the current charge power. There are two configurations for determining the *current charge power*:
|
||||
|
|
@ -109,9 +121,9 @@ If *total energy* is supplied, it can be used to calculate the *charged energy*
|
|||
- **No charge meter**: If no charge meter is installed, *charge power* is deducted from *charge current* as controlled by the charger. This method is less accurate than using a *charge meter* since the EV may chose to use less power than EVCC has allowed for consumption.
|
||||
If the charger supplies *total energy* for the charging cycle this value is preferred over the *charge meter*'s value (if present).
|
||||
|
||||
## Implementation
|
||||
## Configuration
|
||||
|
||||
EVCC consists of four basic elements: *Charger*, *Meter*, *SoC* and *Loadpoint*. Their APIs are described in [api/api.go](https://github.com/andig/evcc/blob/master/api/api.go).
|
||||
The EVCC consists of four basic elements: *Charger*, *Meter* and *Vehicle* individually configured and attached to *Loadpoints*.
|
||||
|
||||
### Charger
|
||||
|
||||
|
|
|
|||
14
core/api.go
14
core/api.go
|
|
@ -63,20 +63,18 @@ func (lp *LoadPoint) hasChargeMeter() bool {
|
|||
|
||||
// Dump loadpoint configuration
|
||||
func (lp *LoadPoint) Dump() {
|
||||
vehicle := lp.vehicle != nil
|
||||
grid := lp.gridMeter != nil
|
||||
pv := lp.pvMeter != nil
|
||||
log.INFO.Printf("%s config: vehicle %s grid %s pv %s charge %s", lp.Name,
|
||||
presence[vehicle],
|
||||
presence[grid],
|
||||
presence[pv],
|
||||
log.INFO.Printf("%s loadpoint config: vehicle %s grid %s pv %s battery %s charge %s", lp.Name,
|
||||
presence[lp.vehicle != nil],
|
||||
presence[lp.gridMeter != nil],
|
||||
presence[lp.pvMeter != nil],
|
||||
presence[lp.batteryMeter != nil],
|
||||
presence[lp.hasChargeMeter()],
|
||||
)
|
||||
|
||||
_, power := lp.charger.(api.Meter)
|
||||
_, energy := lp.charger.(api.ChargeRater)
|
||||
_, timer := lp.charger.(api.ChargeTimer)
|
||||
log.INFO.Printf("%s charger: power %s energy %s timer %s", lp.Name,
|
||||
log.INFO.Printf("%s charger config: power %s energy %s timer %s", lp.Name,
|
||||
presence[power],
|
||||
presence[energy],
|
||||
presence[timer],
|
||||
|
|
|
|||
|
|
@ -47,15 +47,21 @@ type Config struct {
|
|||
Voltage float64 // Operating voltage. 230V for Germany.
|
||||
ResidualPower float64 // PV meter only: household usage. Grid meter: household safety margin
|
||||
|
||||
ChargerRef string `mapstructure:"charger"` // Charger reference
|
||||
GridMeterRef string `mapstructure:"gridmeter"` // Grid usage meter reference
|
||||
PVMeterRef string `mapstructure:"pvmeter"` // PV generation meter reference
|
||||
ChargeMeterRef string `mapstructure:"chargemeter"` // Charger usage meter reference
|
||||
VehicleRef string `mapstructure:"vehicle"` // Vehicle reference
|
||||
ChargerRef string `mapstructure:"charger"` // Charger reference
|
||||
VehicleRef string `mapstructure:"vehicle"` // Vehicle reference
|
||||
Meters MetersConfig // Meter references
|
||||
|
||||
GuardDuration time.Duration // charger enable/disable minimum holding time
|
||||
}
|
||||
|
||||
// MetersConfig contains the loadpoint's meter configuration
|
||||
type MetersConfig struct {
|
||||
GridMeterRef string `mapstructure:"grid"` // Grid usage meter reference
|
||||
ChargeMeterRef string `mapstructure:"charge"` // Charger usage meter reference
|
||||
PVMeterRef string `mapstructure:"pv"` // PV generation meter reference
|
||||
BatteryMeterRef string `mapstructure:"battery"` // Battery charging meter reference
|
||||
}
|
||||
|
||||
// LoadPoint is responsible for controlling charge depending on
|
||||
// SoC needs and power availability.
|
||||
type LoadPoint struct {
|
||||
|
|
@ -72,11 +78,12 @@ type LoadPoint struct {
|
|||
chargeRater api.ChargeRater
|
||||
|
||||
// meters
|
||||
charger api.Charger // Charger
|
||||
gridMeter api.Meter // Grid usage meter
|
||||
pvMeter api.Meter // PV generation meter
|
||||
chargeMeter api.Meter // Charger usage meter
|
||||
vehicle api.Vehicle // Vehicle
|
||||
charger api.Charger // Charger
|
||||
gridMeter api.Meter // Grid usage meter
|
||||
pvMeter api.Meter // PV generation meter
|
||||
batteryMeter api.Meter // Battery charging meter
|
||||
chargeMeter api.Meter // Charger usage meter
|
||||
vehicle api.Vehicle // Vehicle
|
||||
|
||||
// cached state
|
||||
status api.ChargeStatus // Charger status
|
||||
|
|
@ -85,6 +92,7 @@ type LoadPoint struct {
|
|||
charging bool // Charging cycle
|
||||
gridPower float64 // Grid power
|
||||
pvPower float64 // PV power
|
||||
batteryPower float64 // Battery charge power
|
||||
chargePower float64 // Charging power
|
||||
|
||||
// contactor switch guard
|
||||
|
|
@ -108,17 +116,20 @@ func NewLoadPointFromConfig(log *util.Logger, cp configProvider, other map[strin
|
|||
} else {
|
||||
log.FATAL.Fatal("config: missing charger")
|
||||
}
|
||||
if lp.PVMeterRef == "" && lp.GridMeterRef == "" {
|
||||
if lp.Meters.PVMeterRef == "" && lp.Meters.GridMeterRef == "" {
|
||||
log.FATAL.Fatal("config: missing either pv or grid meter")
|
||||
}
|
||||
if lp.GridMeterRef != "" {
|
||||
lp.gridMeter = cp.Meter(lp.GridMeterRef)
|
||||
if lp.Meters.GridMeterRef != "" {
|
||||
lp.gridMeter = cp.Meter(lp.Meters.GridMeterRef)
|
||||
}
|
||||
if lp.ChargeMeterRef != "" {
|
||||
lp.chargeMeter = cp.Meter(lp.ChargeMeterRef)
|
||||
if lp.Meters.ChargeMeterRef != "" {
|
||||
lp.chargeMeter = cp.Meter(lp.Meters.ChargeMeterRef)
|
||||
}
|
||||
if lp.PVMeterRef != "" {
|
||||
lp.pvMeter = cp.Meter(lp.PVMeterRef)
|
||||
if lp.Meters.PVMeterRef != "" {
|
||||
lp.pvMeter = cp.Meter(lp.Meters.PVMeterRef)
|
||||
}
|
||||
if lp.Meters.BatteryMeterRef != "" {
|
||||
lp.batteryMeter = cp.Meter(lp.Meters.BatteryMeterRef)
|
||||
}
|
||||
if lp.VehicleRef != "" {
|
||||
lp.vehicle = cp.Vehicle(lp.VehicleRef)
|
||||
|
|
@ -201,7 +212,7 @@ func (lp *LoadPoint) evChargeCurrentHandler(m *wrapper.ChargeMeter) func(para ..
|
|||
}
|
||||
if current > 0 {
|
||||
// limit available power to generation plus consumption/ minus delivery
|
||||
availablePower := math.Abs(lp.pvPower) + lp.gridPower
|
||||
availablePower := math.Abs(lp.pvPower) + lp.availableBatteryPower() + lp.gridPower
|
||||
availableCurrent := int64(powerToCurrent(availablePower, lp.Voltage, lp.Phases))
|
||||
current = min(current, availableCurrent)
|
||||
}
|
||||
|
|
@ -209,6 +220,15 @@ func (lp *LoadPoint) evChargeCurrentHandler(m *wrapper.ChargeMeter) func(para ..
|
|||
}
|
||||
}
|
||||
|
||||
// availableBatteryPower delivers the battery charging power as additional available power at the grid connection point
|
||||
func (lp *LoadPoint) availableBatteryPower() float64 {
|
||||
if lp.batteryPower < 0 {
|
||||
return math.Abs(lp.batteryPower)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// Prepare loadpoint configuration by adding missing helper elements
|
||||
func (lp *LoadPoint) Prepare(uiChan chan<- Param, notificationChan chan<- push.Event) {
|
||||
lp.notificationChan = notificationChan
|
||||
|
|
@ -436,7 +456,7 @@ func (lp *LoadPoint) rampOn(target int64) error {
|
|||
// updateModePV sets "minpv" or "pv" load modes
|
||||
func (lp *LoadPoint) updateModePV(mode api.ChargeMode) error {
|
||||
// grid meter will always be available, if as wrapped pv meter
|
||||
targetPower := lp.chargePower - lp.gridPower - lp.ResidualPower
|
||||
targetPower := lp.chargePower - lp.gridPower + lp.availableBatteryPower() - lp.ResidualPower
|
||||
log.DEBUG.Printf("%s target power: %.0fW = %.0fW charge - %.0fW grid - %.0fW residual", lp.Name, targetPower, lp.chargePower, lp.gridPower, lp.ResidualPower)
|
||||
|
||||
// get max charge current
|
||||
|
|
@ -496,6 +516,7 @@ func (lp *LoadPoint) updateMeters() (err error) {
|
|||
// read PV meter before charge meter
|
||||
retryMeter("grid", lp.gridMeter, &lp.gridPower)
|
||||
retryMeter("pv", lp.pvMeter, &lp.pvPower)
|
||||
retryMeter("battery", lp.batteryMeter, &lp.batteryPower)
|
||||
retryMeter("charge", lp.chargeMeter, &lp.chargePower)
|
||||
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -55,6 +55,12 @@ meters:
|
|||
type: mqtt
|
||||
topic: mbmd/sdm1-2/Power
|
||||
timeout: 10s # don't use older values
|
||||
- name: battery
|
||||
type: default
|
||||
power:
|
||||
type: mqtt
|
||||
topic: mbmd/sma1-1/Power
|
||||
timeout: 10s # don't use older values
|
||||
- name: charge
|
||||
type: default
|
||||
power:
|
||||
|
|
@ -79,8 +85,9 @@ chargers:
|
|||
uri: 192.168.0.8:502 # ModBus address
|
||||
# legacy: true # enable for older Wallbes with Phoenix EV-CC-AC1-M3-CBC-RCM controller
|
||||
- name: phoenix
|
||||
type: phoenix # Charger with Phoenix Contact controller
|
||||
type: phoenix-emcp # Charger with Phoenix Contact controller
|
||||
uri: 192.168.0.8:502 # ModBus address
|
||||
id: 1
|
||||
- name: simpleevse-tcp
|
||||
type: simpleevse # Charger with Phoenix Contact controller
|
||||
uri: 192.168.0.8:502 # TCP ModBus address
|
||||
|
|
@ -169,10 +176,12 @@ loadpoints:
|
|||
- name: main # name for logging
|
||||
vehicle: audi
|
||||
charger: wallbe # charger
|
||||
gridmeter: grid # grid meter
|
||||
pvmeter: pv # pv meter
|
||||
chargemeter: charge # charge meter
|
||||
sensitivity: 1 # current raise/lower steps size (default 1A)
|
||||
meters:
|
||||
grid: grid # grid meter
|
||||
pv: pv # pv meter
|
||||
battery: battery # battery meter
|
||||
charge: charge # charge meter
|
||||
guardduration: 10m # switch charger contactor not more often than this (default 10m)
|
||||
maxcurrent: 16 # maximum charge current (default 16A)
|
||||
phases: 3 # ev phases (default 3)
|
||||
sensitivity: 1 # current raise/lower step size (default 10A)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue