Improve loadpoint api (#1402)

Breaking change: chargeEstimate becomes chargeRemainingDuration
This commit is contained in:
andig 2021-09-05 11:28:28 +02:00 • committed by GitHub
parent b624e5ae44
commit c4298410d1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 117 additions and 60 deletions

View file

@ -64,7 +64,7 @@ export default {
vehiclePresent: Boolean,
climater: String,
range: Number,
chargeEstimate: Number,
chargeRemainingDuration: Number,
// other information
phases: Number,

View file

@ -32,7 +32,7 @@ Vehicle.args = {
chargedEnergy: 11e3,
chargeDuration: 95 * 60,
vehiclePresent: true,
chargeEstimate: 5 * 3600,
chargeRemainingDuration: 5 * 3600,
};
export const VehicleRange = Template.bind({});
@ -42,7 +42,7 @@ VehicleRange.args = {
chargeDuration: 95 * 60,
vehiclePresent: true,
range: 240.123,
chargeEstimate: 5 * 3600,
chargeRemainingDuration: 5 * 3600,
};
export const VehicleClimater = Template.bind({});
@ -52,7 +52,7 @@ VehicleClimater.args = {
chargeDuration: 95 * 60,
vehiclePresent: true,
range: 240.123,
chargeEstimate: 5 * 3600,
chargeRemainingDuration: 5 * 3600,
climater: "on",
};
@ -63,7 +63,7 @@ VehicleTimer.args = {
chargeDuration: 95 * 60,
vehiclePresent: true,
range: 240.123,
chargeEstimate: 5 * 3600,
chargeRemainingDuration: 5 * 3600,
timerSet: true,
timerActive: true,
};

View file

@ -53,8 +53,10 @@
<div class="col-6 col-sm-3 col-lg-2 mt-3" v-if="vehiclePresent">
<div class="mb-2 value">{{ $t("main.loadpointDetails.remaining") }}</div>
<h3 class="value">
{{ fmtShortDuration(chargeEstimate) }}
<small class="text-muted">{{ fmtShortDurationUnit(chargeEstimate) }}</small>
{{ fmtShortDuration(chargeRemainingDuration) }}
<small class="text-muted">{{
fmtShortDurationUnit(chargeRemainingDuration)
}}</small>
</h3>
</div>
</div>
@ -70,7 +72,7 @@ export default {
props: {
chargedEnergy: Number,
chargeDuration: Number,
chargeEstimate: Number,
chargeRemainingDuration: Number,
chargePower: Number,
climater: String,
vehiclePresent: Boolean,

View file

@ -47,7 +47,7 @@ Single.args = {
vehicleSoc: 66,
targetSoC: 90,
range: 344,
chargeEstimate: 999,
chargeRemainingDuration: 999,
chargePower: 11232,
chargeDuration: 123982,
chargedEnergy: 23213,
@ -76,7 +76,7 @@ Multi.args = {
vehicleSoc: 66,
targetSoC: 90,
range: 344,
chargeEstimate: 999,
chargeRemainingDuration: 999,
chargePower: 11232,
chargeDuration: 123982,
chargedEnergy: 23213,
@ -96,7 +96,7 @@ Multi.args = {
socCharge: 22,
targetSoC: 100,
range: 34,
chargeEstimate: 22,
chargeRemainingDuration: 22,
chargePower: 1423,
chargeDuration: 243,
chargedEnergy: 235,

View file

@ -129,9 +129,12 @@ type LoadPoint struct {
pvTimer time.Time // PV enabled/disable timer
phaseTimer time.Time // 1p3p switch timer
vehicleSoc float64 // Vehicle SoC
chargedEnergy float64 // Charged energy while connected in Wh
chargeDuration time.Duration // Charge duration
// charge progress
vehicleSoc float64 // Vehicle SoC
chargeDuration time.Duration // Charge duration
chargedEnergy float64 // Charged energy while connected in Wh
chargeRemainingDuration time.Duration // Remaining charge duration
chargeRemainingEnergy float64 // Remaining charge energy in Wh
tasks []func() error // task list for repeated execution
}
@ -1158,14 +1161,13 @@ func (lp *LoadPoint) publishSoCAndRange() {
lp.log.DEBUG.Printf("vehicle soc: %.0f%%", lp.vehicleSoc)
lp.publish("vehicleSoc", lp.vehicleSoc)
chargeEstimate := time.Duration(-1)
if lp.charging() {
chargeEstimate = lp.socEstimator.RemainingChargeDuration(lp.chargePower, lp.SoC.Target)
lp.setRemainingDuration(lp.socEstimator.RemainingChargeDuration(lp.chargePower, lp.SoC.Target))
} else {
lp.setRemainingDuration(-1)
}
lp.publish("chargeEstimate", chargeEstimate)
chargeRemainingEnergy := 1e3 * lp.socEstimator.RemainingChargeEnergy(lp.SoC.Target)
lp.publish("chargeRemainingEnergy", chargeRemainingEnergy)
lp.setRemainingEnergy(1e3 * lp.socEstimator.RemainingChargeEnergy(lp.SoC.Target))
} else {
if errors.Is(err, api.ErrMustRetry) {
lp.socUpdated = time.Time{}
@ -1188,7 +1190,7 @@ func (lp *LoadPoint) publishSoCAndRange() {
// reset if poll: connected/charging and not connected
if lp.SoC.Poll.Mode != pollAlways && !lp.connected() {
lp.publish("vehicleSoc", -1)
lp.publish("chargeEstimate", time.Duration(-1))
lp.publish("chargeRemainingDuration", time.Duration(-1))
// range
lp.publish("range", -1)

View file

@ -13,31 +13,69 @@ type Controller interface {
// API is the external loadpoint API
type API interface {
// Name returns the defined loadpoint name
Name() string
// HasChargeMeter determines if a physical charge meter is attached
HasChargeMeter() bool
//
// status
//
// GetStatus returns the charging status
GetStatus() api.ChargeStatus
//
// settings
//
// GetMode returns the charge mode
GetMode() api.ChargeMode
// SetMode sets the charge mode
SetMode(api.ChargeMode)
// GetTargetSoC returns the charge target soc
GetTargetSoC() int
// SetTargetSoC sets the charge target soc
SetTargetSoC(int) error
// GetMinSoC returns the charge minimum soc
GetMinSoC() int
// SetMinSoC sets the charge minimum soc
SetMinSoC(int) error
// GetPhases returns the enabled phases
GetPhases() int
// SetPhases sets the enabled phases
SetPhases(int) error
// SetTargetCharge sets the charge targetSoC
SetTargetCharge(time.Time, int)
// RemoteControl sets remote status demand
RemoteControl(string, RemoteDemand)
// energy
//
// power and energy
//
// GetChargePower returns the current charging power
GetChargePower() float64
// GetMinCurrent returns the min charging current
GetMinCurrent() float64
// SetMinCurrent returns the min charging current
SetMinCurrent(float64)
// GetMaxCurrent returns the max charging current
GetMaxCurrent() float64
// SetMaxCurrent returns the max charging current
SetMaxCurrent(float64)
// GetMinPower returns the min charging power for a single phase
GetMinPower() float64
// GetMaxPower returns the max charging power taking active phases into account
GetMaxPower() float64
//
// charge progress
//
// GetRemainingDuration is the estimated remaining charging duration
GetRemainingDuration() time.Duration
// GetRemainingEnergy is the remaining charge energy in Wh
GetRemainingEnergy() float64
}

View file

@ -206,3 +206,39 @@ func (lp *LoadPoint) GetMinPower() float64 {
func (lp *LoadPoint) GetMaxPower() float64 {
return Voltage * lp.GetMaxCurrent() * float64(lp.GetPhases())
}
// setRemainingDuration sets the estimated remaining charging duration
func (lp *LoadPoint) setRemainingDuration(chargeRemainingDuration time.Duration) {
lp.Lock()
defer lp.Unlock()
if lp.chargeRemainingDuration != chargeRemainingDuration {
lp.chargeRemainingDuration = chargeRemainingDuration
lp.publish("chargeRemainingDuration", chargeRemainingDuration)
}
}
// GetRemainingDuration is the estimated remaining charging duration
func (lp *LoadPoint) GetRemainingDuration() time.Duration {
lp.Lock()
defer lp.Unlock()
return lp.chargeRemainingDuration
}
// setRemainingEnergy sets the remaining charge energy in Wh
func (lp *LoadPoint) setRemainingEnergy(chargeRemainingEnergy float64) {
lp.Lock()
defer lp.Unlock()
if lp.chargeRemainingEnergy != chargeRemainingEnergy {
lp.chargeRemainingEnergy = chargeRemainingEnergy
lp.publish("chargeRemainingEnergy", chargeRemainingEnergy)
}
}
// GetRemainingEnergy is the remaining charge energy in Wh
func (lp *LoadPoint) GetRemainingEnergy() float64 {
lp.Lock()
defer lp.Unlock()
return lp.chargeRemainingEnergy
}

4
dist/index.html vendored
View file

@ -1,4 +1,4 @@
<!DOCTYPE html><html lang="de"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="description" content="EV Charge Controller"><meta name="author" content="andig"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black"><link rel="apple-touch-icon" sizes="180x180" href="ico/apple-touch-icon.png?[[.Version]]"><link rel="icon" type="image/png" sizes="32x32" href="ico/favicon-32x32.png?[[.Version]]"><link rel="icon" type="image/png" sizes="16x16" href="ico/favicon-16x16.png?[[.Version]]"><link rel="manifest" href="ico/site.webmanifest"><link rel="mask-icon" href="ico/safari-pinned-tab.svg?[[.Version]]" color="#18191a"><link rel="shortcut icon" href="ico/favicon.ico?[[.Version]]"><meta name="apple-mobile-web-app-title" content="evcc"><meta name="application-name" content="evcc"><meta name="msapplication-TileColor" content="#18191a"><meta name="msapplication-config" content="ico/browserconfig.xml"><meta name="theme-color" content="#18191a"><title>evcc</title><link href="css/chunk-vendors.63c5e34a.css" rel="preload" as="style"><link href="css/index.93d569c8.css" rel="preload" as="style"><link href="js/chunk-vendors.0dc6e0ff.js" rel="preload" as="script"><link href="js/index.71929600.js" rel="preload" as="script"><link href="css/chunk-vendors.63c5e34a.css" rel="stylesheet"><link href="css/index.93d569c8.css" rel="stylesheet"></head><body><script>window.evcc = {
<!DOCTYPE html><html lang="de"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="description" content="EV Charge Controller"><meta name="author" content="andig"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black"><link rel="apple-touch-icon" sizes="180x180" href="ico/apple-touch-icon.png?[[.Version]]"><link rel="icon" type="image/png" sizes="32x32" href="ico/favicon-32x32.png?[[.Version]]"><link rel="icon" type="image/png" sizes="16x16" href="ico/favicon-16x16.png?[[.Version]]"><link rel="manifest" href="ico/site.webmanifest"><link rel="mask-icon" href="ico/safari-pinned-tab.svg?[[.Version]]" color="#18191a"><link rel="shortcut icon" href="ico/favicon.ico?[[.Version]]"><meta name="apple-mobile-web-app-title" content="evcc"><meta name="application-name" content="evcc"><meta name="msapplication-TileColor" content="#18191a"><meta name="msapplication-config" content="ico/browserconfig.xml"><meta name="theme-color" content="#18191a"><title>evcc</title><link href="css/chunk-vendors.63c5e34a.css" rel="preload" as="style"><link href="css/index.93d569c8.css" rel="preload" as="style"><link href="js/chunk-vendors.0dc6e0ff.js" rel="preload" as="script"><link href="js/index.b7609394.js" rel="preload" as="script"><link href="css/chunk-vendors.63c5e34a.css" rel="stylesheet"><link href="css/index.93d569c8.css" rel="stylesheet"></head><body><script>window.evcc = {
version: "[[.Version]]",
configured: "[[.Configured]]",
};</script><div id="app"></div><script src="js/chunk-vendors.0dc6e0ff.js"></script><script src="js/index.71929600.js"></script></body></html>
};</script><div id="app"></div><script src="js/chunk-vendors.0dc6e0ff.js"></script><script src="js/index.b7609394.js"></script></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
dist/js/index.b7609394.js.map vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -403,27 +403,25 @@ func (s *SEMP) allDeviceInfo() (res []DeviceInfo) {
func (s *SEMP) deviceStatus(id int, lp loadpoint.API) DeviceStatus {
chargePower := lp.GetChargePower()
status := lp.GetStatus()
mode := lp.GetMode()
isPV := mode == api.ModeMinPV || mode == api.ModePV
status := StatusOff
if lp.GetStatus() == api.StatusC {
status = StatusOn
deviceStatus := StatusOff
if status == api.StatusC {
deviceStatus = StatusOn
}
var vehiclePresent bool
if vehiclePresentP, err := s.cache.GetChecked(id, "vehiclePresent"); err == nil {
vehiclePresent = vehiclePresentP.Val.(bool)
}
connected := status == api.StatusB || status == api.StatusC
res := DeviceStatus{
DeviceID: s.deviceID(id),
EMSignalsAccepted: s.controllable && isPV && vehiclePresent,
EMSignalsAccepted: s.controllable && isPV && connected,
PowerInfo: PowerInfo{
AveragePower: int(chargePower),
AveragingInterval: 60,
},
Status: status,
Status: deviceStatus,
}
return res
@ -437,27 +435,21 @@ func (s *SEMP) allDeviceStatus() (res []DeviceStatus) {
return res
}
// TODO remove GetChecked function
func (s *SEMP) planningRequest(id int, lp loadpoint.API) (res PlanningRequest) {
mode := lp.GetMode()
charging := lp.GetStatus() == api.StatusC
connected := charging || lp.GetStatus() == api.StatusB
chargeEstimate := time.Duration(-1)
if chargeEstimateP, err := s.cache.GetChecked(id, "chargeEstimate"); err == nil {
chargeEstimate = chargeEstimateP.Val.(time.Duration)
}
latestEnd := int(chargeEstimate / time.Second)
// remaining max demand duration in seconds
chargeRemainingDuration := lp.GetRemainingDuration()
latestEnd := int(chargeRemainingDuration / time.Second)
if mode == api.ModeMinPV || mode == api.ModePV || latestEnd <= 0 {
latestEnd = 24 * 3600
}
var maxEnergy int
if chargeRemainingEnergyP, err := s.cache.GetChecked(id, "chargeRemainingEnergy"); err == nil {
maxEnergy = int(chargeRemainingEnergyP.Val.(float64))
}
// remaining max energy demand in Wh
chargeRemainingEnergy := lp.GetRemainingEnergy()
maxEnergy := int(chargeRemainingEnergy)
// add 1kWh in case we're charging but battery claims full
if charging && maxEnergy == 0 {

View file

@ -1,7 +1,6 @@
package util
import (
"errors"
"sync"
)
@ -92,15 +91,3 @@ func (c *Cache) Get(key string) Param {
return Param{}
}
// GetChecked returns checked value from cache
func (c *Cache) GetChecked(id int, key string) (res Param, err error) {
pid := Param{LoadPoint: &id, Key: key}
res = c.Get(pid.UniqueID())
if res.Key == "" {
err = errors.New("not found")
}
return res, err
}