Improve loadpoint api (#1402)
Breaking change: chargeEstimate becomes chargeRemainingDuration
This commit is contained in:
parent
b624e5ae44
commit
c4298410d1
13 changed files with 117 additions and 60 deletions
|
|
@ -64,7 +64,7 @@ export default {
|
|||
vehiclePresent: Boolean,
|
||||
climater: String,
|
||||
range: Number,
|
||||
chargeEstimate: Number,
|
||||
chargeRemainingDuration: Number,
|
||||
|
||||
// other information
|
||||
phases: Number,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
4
dist/index.html
vendored
|
|
@ -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>
|
||||
1
dist/js/index.71929600.js.map
vendored
1
dist/js/index.71929600.js.map
vendored
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
1
dist/js/index.b7609394.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue