Follow SMA Application Note EVCharger (#356)
This commit is contained in:
parent
46411cc602
commit
3b5ec989a2
4 changed files with 61 additions and 67 deletions
|
|
@ -597,6 +597,9 @@ func (lp *LoadPoint) publishSoC() {
|
|||
}
|
||||
lp.publish("chargeEstimate", chargeEstimate)
|
||||
|
||||
chargeRemainingEnergy := 1e3 * lp.socEstimator.RemainingChargeEnergy(lp.TargetSoC)
|
||||
lp.publish("chargeRemainingEnergy", chargeRemainingEnergy)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue