272 lines
8.2 KiB
Go
272 lines
8.2 KiB
Go
package eebus
|
|
|
|
import (
|
|
"time"
|
|
|
|
eebusapi "github.com/enbility/eebus-go/api"
|
|
ucapi "github.com/enbility/eebus-go/usecases/api"
|
|
"github.com/enbility/eebus-go/usecases/cs/lpc"
|
|
"github.com/enbility/eebus-go/usecases/cs/lpp"
|
|
spineapi "github.com/enbility/spine-go/api"
|
|
"github.com/enbility/spine-go/model"
|
|
"github.com/evcc-io/evcc/server/eebus"
|
|
)
|
|
|
|
var _ eebus.Device = (*EEBus)(nil)
|
|
|
|
// UseCaseEvent implements the eebus.Device interface
|
|
func (c *EEBus) UseCaseEvent(_ spineapi.DeviceRemoteInterface, entity spineapi.EntityRemoteInterface, event eebusapi.EventType) {
|
|
switch event {
|
|
// Load control obligation limit data update received
|
|
//
|
|
// Use `ConsumptionLimit` to get the current data
|
|
//
|
|
// Use Case LPC, Scenario 1
|
|
case lpc.DataUpdateLimit:
|
|
c.updateConsumptionLimit()
|
|
|
|
// An incoming load control obligation limit needs to be approved or denied
|
|
//
|
|
// Use `PendingConsumptionLimits` to get the currently pending write approval requests
|
|
// and invoke `ApproveOrDenyConsumptionLimit` for each
|
|
//
|
|
// Use Case LPC, Scenario 1
|
|
case lpc.LimitWriteApprovalRequired:
|
|
c.consumptionWriteApprovalRequired()
|
|
|
|
// An incoming device configuration write (e.g. failsafe values) needs to be
|
|
// approved or denied. eebus-go <= v0.7.0 applied these automatically, so we
|
|
// keep that behaviour by approving all pending configuration writes.
|
|
//
|
|
// Use Case LPC, Scenario 2
|
|
case lpc.ConfigurationWriteApprovalRequired:
|
|
c.approveDeviceConfigurations(c.cs.CsLPCInterface.PendingDeviceConfigurations(), c.cs.CsLPCInterface.ApproveOrDenyDeviceConfiguration)
|
|
|
|
// Failsafe limit for the consumed active (real) power of the
|
|
// Controllable System data update received
|
|
//
|
|
// Use `FailsafeConsumptionActivePowerLimit` to get the current data
|
|
//
|
|
// Use Case LPC, Scenario 2
|
|
case lpc.DataUpdateFailsafeConsumptionActivePowerLimit:
|
|
c.updateFailsafeConsumptionActivePowerLimit()
|
|
|
|
// Minimum time the Controllable System remains in "failsafe state" unless conditions
|
|
// specified in this Use Case permit leaving the "failsafe state" data update received
|
|
//
|
|
// Use `FailsafeDurationMinimum` to get the current data
|
|
//
|
|
// Use Case LPC, Scenario 2
|
|
case lpc.DataUpdateFailsafeDurationMinimum:
|
|
c.updateFailsafeConsumptionDurationMinimum()
|
|
|
|
// Indicates a notify heartbeat event the application should care of.
|
|
// E.g. going into or out of the Failsafe state
|
|
//
|
|
// Use Case LPC, Scenario 3
|
|
case lpc.DataUpdateHeartbeat:
|
|
c.updateHeartbeat()
|
|
|
|
// Load control obligation limit data update received
|
|
//
|
|
// Use `ProductionLimit` to get the current data
|
|
//
|
|
// Use Case LPC, Scenario 1
|
|
case lpp.DataUpdateLimit:
|
|
c.updateProductionLimit()
|
|
|
|
// An incoming load control obligation limit needs to be approved or denied
|
|
//
|
|
// Use `PendingProductionLimits` to get the currently pending write approval requests
|
|
// and invoke `ApproveOrDenyProductionLimit` for each
|
|
//
|
|
// Use Case LPP, Scenario 1
|
|
case lpp.LimitWriteApprovalRequired:
|
|
c.productionWriteApprovalRequired()
|
|
|
|
// An incoming device configuration write (e.g. failsafe values) needs to be
|
|
// approved or denied. eebus-go <= v0.7.0 applied these automatically, so we
|
|
// keep that behaviour by approving all pending configuration writes.
|
|
//
|
|
// Use Case LPP, Scenario 2
|
|
case lpp.ConfigurationWriteApprovalRequired:
|
|
c.approveDeviceConfigurations(c.cs.CsLPPInterface.PendingDeviceConfigurations(), c.cs.CsLPPInterface.ApproveOrDenyDeviceConfiguration)
|
|
|
|
// Failsafe limit for the produced active (real) power of the
|
|
// Controllable System data update received
|
|
//
|
|
// Use `FailsafeProductionActivePowerLimit` to get the current data
|
|
//
|
|
// Use Case LPP, Scenario 2
|
|
case lpp.DataUpdateFailsafeProductionActivePowerLimit:
|
|
c.updateFailsafeProductionActivePowerLimit()
|
|
|
|
// Minimum time the Controllable System remains in "failsafe state" unless conditions
|
|
// specified in this Use Case permit leaving the "failsafe state" data update received
|
|
//
|
|
// Use `FailsafeDurationMinimum` to get the current data
|
|
//
|
|
// Use Case LPP, Scenario 2
|
|
case lpp.DataUpdateFailsafeDurationMinimum:
|
|
c.updateFailsafeProductionDurationMinimum()
|
|
|
|
// Indicates a notify heartbeat event the application should care of.
|
|
// E.g. going into or out of the Failsafe state
|
|
//
|
|
// Use Case LPP, Scenario 3
|
|
case lpp.DataUpdateHeartbeat:
|
|
c.updateHeartbeat()
|
|
}
|
|
}
|
|
|
|
// setConsumptionLimitData stores a freshly received LPC limit. eebus-go reports a
|
|
// stated duration as the time *remaining* (spine stores an absolute end time), so an
|
|
// already-active limit must restart the clock run() measures the duration against.
|
|
// Caller holds the mutex.
|
|
func (c *EEBus) setConsumptionLimitData(limit ucapi.LoadLimit) {
|
|
c.consumptionLimit = limit
|
|
c.limitReceived = time.Now()
|
|
|
|
if limitActive(c.consumptionLimitActivated) {
|
|
*c.consumptionLimitActivated = c.limitReceived
|
|
}
|
|
}
|
|
|
|
// setProductionLimitData stores a freshly received LPP limit, see setConsumptionLimitData.
|
|
// Caller holds the mutex.
|
|
func (c *EEBus) setProductionLimitData(limit ucapi.LoadLimit) {
|
|
c.productionLimit = limit
|
|
c.limitReceived = time.Now()
|
|
|
|
if limitActive(c.productionLimitActivated) {
|
|
*c.productionLimitActivated = c.limitReceived
|
|
}
|
|
}
|
|
|
|
func (c *EEBus) updateConsumptionLimit() {
|
|
limit, err := c.cs.CsLPCInterface.ConsumptionLimit()
|
|
if err != nil {
|
|
c.log.ERROR.Println("CS LPC ConsumptionLimit:", err)
|
|
return
|
|
}
|
|
|
|
c.mux.Lock()
|
|
defer c.mux.Unlock()
|
|
|
|
c.setConsumptionLimitData(limit)
|
|
}
|
|
|
|
func (c *EEBus) updateProductionLimit() {
|
|
limit, err := c.cs.CsLPPInterface.ProductionLimit()
|
|
if err != nil {
|
|
c.log.ERROR.Println("CS LPP ProductionLimit:", err)
|
|
return
|
|
}
|
|
|
|
c.mux.Lock()
|
|
defer c.mux.Unlock()
|
|
|
|
c.setProductionLimitData(limit)
|
|
}
|
|
|
|
func (c *EEBus) consumptionWriteApprovalRequired() {
|
|
for msg, limit := range c.cs.CsLPCInterface.PendingConsumptionLimits() {
|
|
c.log.DEBUG.Println("CS LPC PendingConsumptionLimit:", msg, limit)
|
|
if limit.Value < 0 {
|
|
c.cs.CsLPCInterface.ApproveOrDenyConsumptionLimit(msg, false, "negative limit")
|
|
continue
|
|
}
|
|
|
|
c.cs.CsLPCInterface.ApproveOrDenyConsumptionLimit(msg, true, "")
|
|
|
|
c.mux.Lock()
|
|
c.setConsumptionLimitData(limit)
|
|
c.mux.Unlock()
|
|
}
|
|
}
|
|
|
|
func (c *EEBus) productionWriteApprovalRequired() {
|
|
for msg, limit := range c.cs.CsLPPInterface.PendingProductionLimits() {
|
|
c.log.DEBUG.Println("CS LPP PendingProductionLimit:", msg, limit)
|
|
if limit.Value > 0 {
|
|
c.cs.CsLPPInterface.ApproveOrDenyProductionLimit(msg, false, "positive limit")
|
|
continue
|
|
}
|
|
|
|
c.cs.CsLPPInterface.ApproveOrDenyProductionLimit(msg, true, "")
|
|
c.mux.Lock()
|
|
c.setProductionLimitData(limit)
|
|
c.mux.Unlock()
|
|
}
|
|
}
|
|
|
|
// approveDeviceConfigurations approves all pending device configuration writes,
|
|
// preserving the automatic behaviour of eebus-go <= v0.7.0
|
|
func (c *EEBus) approveDeviceConfigurations(
|
|
pending map[model.MsgCounterType][]ucapi.PendingDeviceConfiguration,
|
|
approve func(msgCounter model.MsgCounterType, approve bool, reason string),
|
|
) {
|
|
for msg, configs := range pending {
|
|
c.log.DEBUG.Println("approving device configuration write:", msg, configs)
|
|
approve(msg, true, "")
|
|
}
|
|
}
|
|
|
|
func (c *EEBus) updateFailsafeConsumptionActivePowerLimit() {
|
|
limit, _, err := c.cs.CsLPCInterface.FailsafeConsumptionActivePowerLimit()
|
|
if err != nil {
|
|
c.log.ERROR.Println("CS LPC FailsafeConsumptionActivePowerLimit:", err)
|
|
return
|
|
}
|
|
|
|
c.mux.Lock()
|
|
defer c.mux.Unlock()
|
|
|
|
c.failsafeConsumptionLimit = limit
|
|
}
|
|
|
|
func (c *EEBus) updateFailsafeProductionActivePowerLimit() {
|
|
limit, _, err := c.cs.CsLPPInterface.FailsafeProductionActivePowerLimit()
|
|
if err != nil {
|
|
c.log.ERROR.Println("CS LPP FailsafeProductionActivePowerLimit:", err)
|
|
return
|
|
}
|
|
|
|
c.mux.Lock()
|
|
defer c.mux.Unlock()
|
|
|
|
c.failsafeProductionLimit = new(limit)
|
|
}
|
|
|
|
func (c *EEBus) updateFailsafeConsumptionDurationMinimum() {
|
|
duration, _, err := c.cs.CsLPCInterface.FailsafeDurationMinimum()
|
|
if err != nil {
|
|
c.log.ERROR.Println("CS LPC FailsafeDurationMinimum:", err)
|
|
return
|
|
}
|
|
|
|
c.mux.Lock()
|
|
defer c.mux.Unlock()
|
|
|
|
c.failsafeDuration = duration
|
|
}
|
|
|
|
func (c *EEBus) updateFailsafeProductionDurationMinimum() {
|
|
duration, _, err := c.cs.CsLPPInterface.FailsafeDurationMinimum()
|
|
if err != nil {
|
|
c.log.ERROR.Println("CS LPP FailsafeDurationMinimum:", err)
|
|
return
|
|
}
|
|
|
|
c.mux.Lock()
|
|
defer c.mux.Unlock()
|
|
|
|
c.failsafeDuration = duration
|
|
}
|
|
|
|
func (c *EEBus) updateHeartbeat() {
|
|
c.mux.Lock()
|
|
defer c.mux.Unlock()
|
|
|
|
c.heartbeat.Set(struct{}{})
|
|
}
|