Follow SMA Application Note EVCharger (#356)

This commit is contained in:
andig 2020-09-27 10:06:24 +02:00 • committed by GitHub
parent 46411cc602
commit 3b5ec989a2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 61 additions and 67 deletions

View file

@ -597,6 +597,9 @@ func (lp *LoadPoint) publishSoC() {
}
lp.publish("chargeEstimate", chargeEstimate)
chargeRemainingEnergy := 1e3 * lp.socEstimator.RemainingChargeEnergy(lp.TargetSoC)
lp.publish("chargeRemainingEnergy", chargeRemainingEnergy)
return
}

View file

@ -74,6 +74,18 @@ func (s *SocEstimator) RemainingChargeDuration(chargePower float64, targetSoC in
return -1
}
// RemainingChargeEnergy returns the remaining charge energy in kWh
func (s *SocEstimator) RemainingChargeEnergy(targetSoC int) float64 {
percentRemaining := float64(targetSoC) - s.socCharge
if percentRemaining <= 0 {
return 0
}
// estimate remaining energy
whRemaining := percentRemaining / 100 * s.virtualCapacity
return whRemaining / 1e3
}
// SoC implements Vehicle.ChargeState with addition of given charged energy
func (s *SocEstimator) SoC(chargedEnergy float64) (float64, error) {
f, err := s.vehicle.ChargeState()
@ -99,7 +111,7 @@ func (s *SocEstimator) SoC(chargedEnergy float64) (float64, error) {
if socDelta > 1 && energyDelta > 0 && s.prevSoC > 0 {
s.energyPerSocStep = energyDelta / socDelta
s.virtualCapacity = s.energyPerSocStep * 100
s.log.TRACE.Printf("soc gradient updated: energyPerSocStep: %0.0fWh, virtualCapacity: %0.1fkWh", s.energyPerSocStep, s.virtualCapacity)
s.log.TRACE.Printf("soc gradient updated: energyPerSocStep: %0.0fWh, virtualCapacity: %0.0fWh", s.energyPerSocStep, s.virtualCapacity)
}
// sample charged energy at soc change, reset energy delta

View file

@ -76,32 +76,16 @@ type Characteristics struct {
MinOffTime int `xml:",omitempty"`
}
type Capabilities struct {
CurrentPower CurrentPower
Timestamps Timestamps
Interruptions Interruptions
Requests Requests
}
const (
MethodMeasurement = "Measurement"
MethodEstimation = "Estimation"
)
type CurrentPower struct {
Method string
}
type Timestamps struct {
AbsoluteTimestamps bool
}
type Interruptions struct {
InterruptionsAllowed bool
}
type Requests struct {
OptionalEnergy bool
type Capabilities struct {
CurrentPowerMethod string `xml:"CurrentPower>Method"`
AbsoluteTimestamps bool `xml:"Timestamps>AbsoluteTimestamps"`
InterruptionsAllowed bool `xml:"Interruptions>InterruptionsAllowed"`
OptionalEnergy bool `xml:"Requests>OptionalEnergy"`
}
const (
@ -113,11 +97,7 @@ type DeviceStatus struct {
DeviceID string `xml:"DeviceId"`
EMSignalsAccepted bool
Status string
PowerConsumption PowerConsumption
}
type PowerConsumption struct {
PowerInfo PowerInfo
PowerInfo PowerInfo `xml:"PowerConsumption>PowerInfo"`
}
type PowerInfo struct {
@ -127,28 +107,30 @@ type PowerInfo struct {
}
type PlanningRequest struct {
Timeframe Timeframe
Timeframe []Timeframe
}
type Timeframe struct {
DeviceID string `xml:"DeviceId"`
EarliestStart int
LatestEnd int
MinRunningTime int
MaxRunningTime int `xml:",omitempty"`
MinRunningTime *int `xml:",omitempty"`
MaxRunningTime *int `xml:",omitempty"`
MinEnergy *int `xml:",omitempty"` // AN EVCharger
MaxEnergy *int `xml:",omitempty"` // AN EVCharger
}
// EM2Device is the EM to device message
type EM2Device struct {
Xmlns string `xml:"xmlns,attr"`
DeviceControl []DeviceControl `xml:",omitempty"`
PlanningRequest []PlanningRequest `xml:",omitempty"`
Xmlns string `xml:"xmlns,attr"`
DeviceControl []DeviceControl `xml:",omitempty"`
}
type DeviceControl struct {
DeviceID string `xml:"DeviceId"`
On bool
Timestamp int
DeviceID string `xml:"DeviceId"`
On bool
RecommendedPowerConsumption int // AN EVCharger
Timestamp int
}
// Device2EMMsg is the XML message container

View file

@ -286,7 +286,7 @@ func (s *SEMP) devicePlanningQuery(w http.ResponseWriter, r *http.Request) {
continue
}
if pr := s.planningRequest(id, lp); pr.Timeframe.DeviceID != "" {
if pr := s.planningRequest(id, lp); len(pr.Timeframe) > 0 {
msg.PlanningRequest = append(msg.PlanningRequest, pr)
}
}
@ -331,15 +331,9 @@ func (s *SEMP) deviceInfo(id int, lp *core.LoadPoint) DeviceInfo {
DeviceVendor: "github.com/andig/evcc",
},
Capabilities: Capabilities{
CurrentPower: CurrentPower{
Method: method,
},
Interruptions: Interruptions{
InterruptionsAllowed: true,
},
Requests: Requests{
OptionalEnergy: true,
},
CurrentPowerMethod: method,
InterruptionsAllowed: true,
OptionalEnergy: true,
},
Characteristics: Characteristics{
MinPowerConsumption: 230 * int(lp.MinCurrent),
@ -374,11 +368,9 @@ func (s *SEMP) deviceStatus(id int, lp *core.LoadPoint) DeviceStatus {
res := DeviceStatus{
DeviceID: s.deviceID(id),
EMSignalsAccepted: true,
PowerConsumption: PowerConsumption{
PowerInfo: PowerInfo{
AveragePower: int(chargePower),
AveragingInterval: 60,
},
PowerInfo: PowerInfo{
AveragePower: int(chargePower),
AveragingInterval: 60,
},
Status: status,
}
@ -410,30 +402,35 @@ func (s *SEMP) planningRequest(id int, lp *core.LoadPoint) (res PlanningRequest)
chargeEstimate = chargeEstimateP.Val.(time.Duration)
}
maxDuration := int(chargeEstimate / time.Second)
if chargeEstimate <= 0 {
maxDuration = 10 * 60 // 10min
latestEnd := int(chargeEstimate / time.Second)
if mode == api.ModePV || latestEnd <= 0 {
latestEnd = 24 * 3600
}
minDuration := maxDuration
if mode == api.ModePV {
minDuration = 0
var maxEnergy int
if chargeRemainingEnergyP, err := s.cacheGet(id, "chargeRemainingEnergy"); err == nil {
maxEnergy = int(chargeRemainingEnergyP.Val.(float64))
}
latestEnd := maxDuration
// add 1kWh in case we're charging but battery claims full
if maxEnergy == 0 {
maxEnergy = 1e3 // 1kWh
}
minEnergy := maxEnergy
if mode == api.ModePV {
latestEnd = 2 * maxDuration
minEnergy = 0
}
if charging {
res = PlanningRequest{
Timeframe: Timeframe{
DeviceID: s.deviceID(id),
EarliestStart: 0,
LatestEnd: latestEnd,
MinRunningTime: minDuration,
MaxRunningTime: maxDuration,
},
Timeframe: []Timeframe{Timeframe{
DeviceID: s.deviceID(id),
EarliestStart: 0,
LatestEnd: latestEnd,
MinEnergy: &minEnergy,
MaxEnergy: &maxEnergy,
}},
}
}
@ -442,7 +439,7 @@ func (s *SEMP) planningRequest(id int, lp *core.LoadPoint) (res PlanningRequest)
func (s *SEMP) allPlanningRequest() (res []PlanningRequest) {
for id, lp := range s.site.LoadPoints() {
if pr := s.planningRequest(id, lp); pr.Timeframe.DeviceID != "" {
if pr := s.planningRequest(id, lp); len(pr.Timeframe) > 0 {
res = append(res, pr)
}
}