Skip to content

Commit

Permalink
Add battery meter support (#83)
Browse files Browse the repository at this point in the history
* Add battery meter support

* Clarify configuration
  • Loading branch information
andig committed Apr 30, 2020
1 parent 7c62c21 commit 532e6bd
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 35 deletions.
18 changes: 15 additions & 3 deletions README.md
Expand Up @@ -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)
Expand Down Expand Up @@ -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*:
Expand All @@ -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

Expand Down
14 changes: 6 additions & 8 deletions core/api.go
Expand Up @@ -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],
Expand Down
59 changes: 40 additions & 19 deletions core/loadpoint.go
Expand Up @@ -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 {
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -201,14 +212,23 @@ 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)
}
m.SetChargeCurrent(current)
}
}

// 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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
19 changes: 14 additions & 5 deletions evcc.dist.yaml
Expand Up @@ -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:
Expand All @@ -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
Expand Down Expand Up @@ -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)

0 comments on commit 532e6bd

Please sign in to comment.