EEbus: upgrade libraries (#13998)
This updates the eebus charger implementation to use the latest version of cemd, eebus, spine and ship. Only use OSCEV if the a wallbox with ISO15118-2 VW VAS is connected, and the EV is using this. This also enables full charging control and putting the EV to sleep. All other current implementations of the usecase are basically wrong and are no different than using OPEV.
This commit is contained in:
parent
737ca58db1
commit
b2f68162bb
8 changed files with 517 additions and 577 deletions
377
charger/eebus.go
377
charger/eebus.go
|
|
@ -4,11 +4,15 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"slices"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/enbility/cemd/emobility"
|
||||
"github.com/enbility/eebus-go/features"
|
||||
eebusapi "github.com/enbility/eebus-go/api"
|
||||
ucapi "github.com/enbility/eebus-go/usecases/api"
|
||||
"github.com/enbility/eebus-go/usecases/cem/evcc"
|
||||
spineapi "github.com/enbility/spine-go/api"
|
||||
"github.com/enbility/spine-go/model"
|
||||
"github.com/evcc-io/evcc/api"
|
||||
"github.com/evcc-io/evcc/charger/eebus"
|
||||
"github.com/evcc-io/evcc/core/loadpoint"
|
||||
|
|
@ -16,8 +20,6 @@ import (
|
|||
"github.com/evcc-io/evcc/util"
|
||||
)
|
||||
|
||||
//go:generate mockgen -package charger -destination eebus_test_mock.go github.com/enbility/cemd/emobility EmobilityI
|
||||
|
||||
const (
|
||||
maxIdRequestTimespan = time.Second * 120
|
||||
idleFactor = 0.6
|
||||
|
|
@ -29,20 +31,21 @@ type minMax struct {
|
|||
}
|
||||
|
||||
type EEBus struct {
|
||||
ski string
|
||||
emobility emobility.EmobilityI
|
||||
ski string
|
||||
|
||||
uc *eebus.UseCasesEVSE
|
||||
ev spineapi.EntityRemoteInterface
|
||||
|
||||
log *util.Logger
|
||||
lp loadpoint.API
|
||||
minMaxG func() (minMax, error)
|
||||
|
||||
communicationStandard emobility.EVCommunicationStandardType
|
||||
communicationStandard model.DeviceConfigurationKeyValueStringType
|
||||
vasVW bool // wether the EVSE supports VW VAS with ISO15118-2
|
||||
|
||||
expectedEnableUnpluggedState bool
|
||||
current float64
|
||||
|
||||
// connection tracking for api.CurrentGetter
|
||||
evConnected bool
|
||||
currentLimit float64
|
||||
|
||||
lastIsChargingCheck time.Time
|
||||
|
|
@ -52,7 +55,8 @@ type EEBus struct {
|
|||
connectedC chan bool
|
||||
connectedTime time.Time
|
||||
|
||||
mux sync.Mutex
|
||||
muxEntity sync.Mutex
|
||||
mux sync.Mutex
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
@ -63,9 +67,10 @@ func init() {
|
|||
func NewEEBusFromConfig(other map[string]interface{}) (api.Charger, error) {
|
||||
cc := struct {
|
||||
Ski string
|
||||
Ip string
|
||||
Ip_ string `mapstructure:"ip"` // deprecated
|
||||
Meter bool
|
||||
ChargedEnergy bool
|
||||
VasVW bool
|
||||
}{
|
||||
ChargedEnergy: true,
|
||||
}
|
||||
|
|
@ -74,13 +79,13 @@ func NewEEBusFromConfig(other map[string]interface{}) (api.Charger, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return NewEEBus(cc.Ski, cc.Ip, cc.Meter, cc.ChargedEnergy)
|
||||
return NewEEBus(cc.Ski, cc.Meter, cc.ChargedEnergy, cc.VasVW)
|
||||
}
|
||||
|
||||
//go:generate go run ../cmd/tools/decorate.go -f decorateEEBus -b *EEBus -r api.Charger -t "api.Meter,CurrentPower,func() (float64, error)" -t "api.PhaseCurrents,Currents,func() (float64, float64, float64, error)" -t "api.ChargeRater,ChargedEnergy,func() (float64, error)"
|
||||
|
||||
// NewEEBus creates EEBus charger
|
||||
func NewEEBus(ski, ip string, hasMeter, hasChargedEnergy bool) (api.Charger, error) {
|
||||
func NewEEBus(ski string, hasMeter, hasChargedEnergy, vasVW bool) (api.Charger, error) {
|
||||
log := util.NewLogger("eebus")
|
||||
|
||||
if eebus.Instance == nil {
|
||||
|
|
@ -88,14 +93,14 @@ func NewEEBus(ski, ip string, hasMeter, hasChargedEnergy bool) (api.Charger, err
|
|||
}
|
||||
|
||||
c := &EEBus{
|
||||
ski: ski,
|
||||
log: log,
|
||||
connectedC: make(chan bool, 1),
|
||||
communicationStandard: emobility.EVCommunicationStandardTypeUnknown,
|
||||
current: 6,
|
||||
ski: ski,
|
||||
log: log,
|
||||
connectedC: make(chan bool, 1),
|
||||
current: 6,
|
||||
vasVW: vasVW,
|
||||
}
|
||||
|
||||
c.emobility = eebus.Instance.RegisterEVSE(ski, ip, c.onConnect, c.onDisconnect, nil)
|
||||
c.uc = eebus.Instance.RegisterEVSE(ski, c)
|
||||
|
||||
c.minMaxG = provider.Cached(c.minMax, time.Second)
|
||||
|
||||
|
|
@ -129,28 +134,60 @@ func (c *EEBus) waitForConnection() error {
|
|||
}
|
||||
}
|
||||
|
||||
func (c *EEBus) onConnect(ski string) {
|
||||
c.log.TRACE.Println("connect ski:", ski)
|
||||
func (c *EEBus) setEvEntity(entity spineapi.EntityRemoteInterface) {
|
||||
c.muxEntity.Lock()
|
||||
defer c.muxEntity.Unlock()
|
||||
|
||||
c.ev = entity
|
||||
}
|
||||
|
||||
func (c *EEBus) evEntity() spineapi.EntityRemoteInterface {
|
||||
c.muxEntity.Lock()
|
||||
defer c.muxEntity.Unlock()
|
||||
|
||||
return c.ev
|
||||
}
|
||||
|
||||
// EEBUSDeviceInterface
|
||||
|
||||
func (c *EEBus) DeviceConnect() {
|
||||
c.log.TRACE.Println("connect ski:", c.ski)
|
||||
|
||||
c.expectedEnableUnpluggedState = false
|
||||
c.setDefaultValues()
|
||||
c.setConnected(true)
|
||||
}
|
||||
|
||||
func (c *EEBus) onDisconnect(ski string) {
|
||||
c.log.TRACE.Println("disconnect ski:", ski)
|
||||
func (c *EEBus) DeviceDisconnect() {
|
||||
c.log.TRACE.Println("disconnect ski:", c.ski)
|
||||
|
||||
c.expectedEnableUnpluggedState = false
|
||||
c.setConnected(false)
|
||||
c.setDefaultValues()
|
||||
}
|
||||
|
||||
// UseCase specific events
|
||||
func (c *EEBus) UseCaseEventCB(device spineapi.DeviceRemoteInterface, entity spineapi.EntityRemoteInterface, event eebusapi.EventType) {
|
||||
switch event {
|
||||
// EV
|
||||
case evcc.EvConnected:
|
||||
c.log.TRACE.Println("EV Connected")
|
||||
c.setEvEntity(entity)
|
||||
c.currentLimit = -1
|
||||
case evcc.EvDisconnected:
|
||||
c.log.TRACE.Println("EV Disconnected")
|
||||
c.setEvEntity(nil)
|
||||
c.currentLimit = -1
|
||||
}
|
||||
}
|
||||
|
||||
func (c *EEBus) setDefaultValues() {
|
||||
c.communicationStandard = emobility.EVCommunicationStandardTypeUnknown
|
||||
c.communicationStandard = evcc.EVCCCommunicationStandardUnknown
|
||||
c.lastIsChargingCheck = time.Now().Add(-time.Hour * 1)
|
||||
c.lastIsChargingResult = false
|
||||
}
|
||||
|
||||
// set wether the EVSE is connected
|
||||
func (c *EEBus) setConnected(connected bool) {
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
|
|
@ -177,9 +214,14 @@ func (c *EEBus) isConnected() bool {
|
|||
var _ api.CurrentLimiter = (*EEBus)(nil)
|
||||
|
||||
func (c *EEBus) minMax() (minMax, error) {
|
||||
minLimits, maxLimits, _, err := c.emobility.EVCurrentLimits()
|
||||
evEntity := c.evEntity()
|
||||
if !c.uc.EvCC.EVConnected(evEntity) {
|
||||
return minMax{}, errors.New("no ev connected")
|
||||
}
|
||||
|
||||
minLimits, maxLimits, _, err := c.uc.OpEV.CurrentLimits(evEntity)
|
||||
if err != nil {
|
||||
if err == features.ErrDataNotAvailable {
|
||||
if err == eebusapi.ErrDataNotAvailable {
|
||||
err = api.ErrNotAvailable
|
||||
}
|
||||
return minMax{}, err
|
||||
|
|
@ -198,7 +240,12 @@ func (c *EEBus) GetMinMaxCurrent() (float64, float64, error) {
|
|||
}
|
||||
|
||||
// we assume that if any phase current value is > idleFactor * min Current, then charging is active and enabled is true
|
||||
func (c *EEBus) isCharging() bool { // d *communication.EVSEClientDataType
|
||||
func (c *EEBus) isCharging() bool {
|
||||
evEntity := c.evEntity()
|
||||
if !c.uc.EvCC.EVConnected(evEntity) {
|
||||
return false
|
||||
}
|
||||
|
||||
// check if an external physical meter is assigned
|
||||
// we only want this for configured meters and not for internal meters!
|
||||
// right now it works as expected
|
||||
|
|
@ -218,11 +265,11 @@ func (c *EEBus) isCharging() bool { // d *communication.EVSEClientDataType
|
|||
}
|
||||
|
||||
// The above doesn't (yet) work for built in meters, so check the EEBUS measurements also
|
||||
currents, err := c.emobility.EVCurrentsPerPhase()
|
||||
currents, err := c.uc.EvCem.CurrentPerPhase(evEntity)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
limitsMin, _, _, err := c.emobility.EVCurrentLimits()
|
||||
limitsMin, _, _, err := c.uc.OpEV.CurrentLimits(evEntity)
|
||||
if err != nil || limitsMin == nil || len(limitsMin) == 0 {
|
||||
return false
|
||||
}
|
||||
|
|
@ -241,38 +288,33 @@ func (c *EEBus) isCharging() bool { // d *communication.EVSEClientDataType
|
|||
|
||||
// Status implements the api.Charger interface
|
||||
func (c *EEBus) Status() (api.ChargeStatus, error) {
|
||||
evEntity := c.evEntity()
|
||||
if !c.isConnected() {
|
||||
return api.StatusNone, api.ErrTimeout
|
||||
}
|
||||
|
||||
if !c.emobility.EVConnected() {
|
||||
if !c.uc.EvCC.EVConnected(evEntity) {
|
||||
c.expectedEnableUnpluggedState = false
|
||||
c.evConnected = false
|
||||
return api.StatusA, nil
|
||||
}
|
||||
|
||||
if !c.evConnected {
|
||||
c.evConnected = true
|
||||
c.currentLimit = -1
|
||||
}
|
||||
|
||||
currentState, err := c.emobility.EVCurrentChargeState()
|
||||
currentState, err := c.uc.EvCC.ChargeState(evEntity)
|
||||
if err != nil {
|
||||
return api.StatusNone, err
|
||||
}
|
||||
|
||||
switch currentState {
|
||||
case emobility.EVChargeStateTypeUnknown, emobility.EVChargeStateTypeUnplugged: // Unplugged
|
||||
case ucapi.EVChargeStateTypeUnknown, ucapi.EVChargeStateTypeUnplugged: // Unplugged
|
||||
c.expectedEnableUnpluggedState = false
|
||||
return api.StatusA, nil
|
||||
case emobility.EVChargeStateTypeFinished, emobility.EVChargeStateTypePaused: // Finished, Paused
|
||||
case ucapi.EVChargeStateTypeFinished, ucapi.EVChargeStateTypePaused: // Finished, Paused
|
||||
return api.StatusB, nil
|
||||
case emobility.EVChargeStateTypeActive: // Active
|
||||
case ucapi.EVChargeStateTypeActive: // Active
|
||||
if c.isCharging() {
|
||||
return api.StatusC, nil
|
||||
}
|
||||
return api.StatusB, nil
|
||||
case emobility.EVChargeStateTypeError: // Error
|
||||
case ucapi.EVChargeStateTypeError: // Error
|
||||
return api.StatusF, nil
|
||||
default:
|
||||
return api.StatusNone, fmt.Errorf("%s properties unknown result: %s", c.ski, currentState)
|
||||
|
|
@ -283,8 +325,9 @@ func (c *EEBus) Status() (api.ChargeStatus, error) {
|
|||
// should return true if the charger allows the EV to draw power
|
||||
func (c *EEBus) Enabled() (bool, error) {
|
||||
// when unplugged there is no overload limit data available
|
||||
evEntity := c.evEntity()
|
||||
state, err := c.Status()
|
||||
if err != nil || state == api.StatusA {
|
||||
if err != nil || state == api.StatusA || evEntity == nil {
|
||||
return c.expectedEnableUnpluggedState, nil
|
||||
}
|
||||
|
||||
|
|
@ -293,9 +336,27 @@ func (c *EEBus) Enabled() (bool, error) {
|
|||
return true, nil
|
||||
}
|
||||
|
||||
limits, err := c.emobility.EVLoadControlObligationLimits()
|
||||
// if the VW VAS PV mode is active, use PV limits
|
||||
if c.hasActiveVASVW() {
|
||||
limits, err := c.uc.OscEV.LoadControlLimits(evEntity)
|
||||
if err != nil {
|
||||
// there are no limits available, e.g. because the data was not received yet
|
||||
return true, nil
|
||||
}
|
||||
|
||||
for _, limit := range limits {
|
||||
// check if there is an active limit set
|
||||
if limit.IsActive && limit.Value >= 1 {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
limits, err := c.uc.OpEV.LoadControlLimits(evEntity)
|
||||
if err != nil {
|
||||
// there are no overload protection limits available, e.g. because the data was not received yet
|
||||
// there are limits available, e.g. because the data was not received yet
|
||||
return true, nil
|
||||
}
|
||||
|
||||
|
|
@ -303,7 +364,9 @@ func (c *EEBus) Enabled() (bool, error) {
|
|||
// for IEC61851 the pause limit is 0A, for ISO15118-2 it is 0.1A
|
||||
// instead of checking for the actual data, hardcode this, so we might run into less
|
||||
// timing issues as the data might not be received yet
|
||||
if limit >= 1 {
|
||||
// if the limit is not active, then the maximum possible current is permitted
|
||||
if (limit.IsActive && limit.Value >= 1) ||
|
||||
!limit.IsActive {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
|
@ -322,8 +385,8 @@ func (c *EEBus) Enable(enable bool) error {
|
|||
// if we disable charging with a potential but not yet known communication standard ISO15118
|
||||
// this would set allowed A value to be 0. And this would trigger ISO connections to switch to IEC!
|
||||
if !enable {
|
||||
comStandard, err := c.emobility.EVCommunicationStandard()
|
||||
if err != nil || comStandard == emobility.EVCommunicationStandardTypeUnknown {
|
||||
comStandard, err := c.uc.EvCC.CommunicationStandard(c.evEntity())
|
||||
if err != nil || comStandard == evcc.EVCCCommunicationStandardUnknown {
|
||||
return api.ErrMustRetry
|
||||
}
|
||||
}
|
||||
|
|
@ -339,37 +402,172 @@ func (c *EEBus) Enable(enable bool) error {
|
|||
|
||||
// send current charging power limits to the EV
|
||||
func (c *EEBus) writeCurrentLimitData(currents []float64) error {
|
||||
comStandard, err := c.emobility.EVCommunicationStandard()
|
||||
if err != nil {
|
||||
return err
|
||||
evEntity := c.evEntity()
|
||||
if !c.uc.EvCC.EVConnected(evEntity) {
|
||||
return errors.New("no ev connected")
|
||||
}
|
||||
|
||||
// Only send currents smaller than 6A if the communication standard is known.
|
||||
// Otherwise this could cause ISO15118 capable OBCs to stick with IEC61851 when plugging
|
||||
// the charge cable in. Or even worse show an error and the cable then needs to be unplugged,
|
||||
// wait for the car to go into sleep and plug it back in.
|
||||
// So if there are currents smaller than 6A with unknown communication standard change them to 6A.
|
||||
// Keep in mind that this will still confuse evcc as it thinks charging is stopped, but it hasn't yet.
|
||||
if comStandard == emobility.EVCommunicationStandardTypeUnknown {
|
||||
minLimits, _, _, err := c.emobility.EVCurrentLimits()
|
||||
if err == nil {
|
||||
for index, current := range currents {
|
||||
if index < len(minLimits) && current < minLimits[index] {
|
||||
currents[index] = minLimits[index]
|
||||
}
|
||||
_, maxLimits, _, err := c.uc.OpEV.CurrentLimits(evEntity)
|
||||
if err != nil {
|
||||
return errors.New("no limits available")
|
||||
}
|
||||
|
||||
// setup the limit data structure
|
||||
limits := []ucapi.LoadLimitsPhase{}
|
||||
for phase, current := range currents {
|
||||
if phase >= len(maxLimits) || phase >= len(ucapi.PhaseNameMapping) {
|
||||
continue
|
||||
}
|
||||
|
||||
limit := ucapi.LoadLimitsPhase{
|
||||
Phase: ucapi.PhaseNameMapping[phase],
|
||||
IsActive: true,
|
||||
Value: current,
|
||||
}
|
||||
|
||||
// if the limit equals to the max allowed, then the obligation limit is actually inactive
|
||||
if current >= maxLimits[phase] {
|
||||
limit.IsActive = false
|
||||
}
|
||||
|
||||
limits = append(limits, limit)
|
||||
}
|
||||
|
||||
// if VAS VW is available, limits are completely covered by it
|
||||
// this way evcc can fully control the charging behaviour
|
||||
if c.writeLoadControlLimitsVASVW(limits) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// make sure the recommendations are inactive, otherwise the EV won't go to sleep
|
||||
if recommendations, err := c.uc.OscEV.LoadControlLimits(evEntity); err == nil {
|
||||
var writeNeeded bool
|
||||
|
||||
for _, item := range recommendations {
|
||||
if item.IsActive {
|
||||
item.IsActive = false
|
||||
writeNeeded = true
|
||||
}
|
||||
}
|
||||
|
||||
if writeNeeded {
|
||||
_, _ = c.uc.OscEV.WriteLoadControlLimits(evEntity, recommendations, nil)
|
||||
}
|
||||
}
|
||||
|
||||
// Set overload protection limits and self consumption limits to identical values,
|
||||
// so if the EV supports self consumption it will be used automatically.
|
||||
if err = c.emobility.EVWriteLoadControlLimits(currents, currents); err == nil {
|
||||
// Set overload protection limits
|
||||
if _, err = c.uc.OpEV.WriteLoadControlLimits(evEntity, limits, nil); err == nil {
|
||||
c.currentLimit = currents[0]
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// returns if the connected EV has an active VW PV mode
|
||||
// in this mode, the EV does not have an active charging demand
|
||||
func (c *EEBus) hasActiveVASVW() bool {
|
||||
// EVSE has to support VW VAS
|
||||
if !c.vasVW {
|
||||
return false
|
||||
}
|
||||
|
||||
evEntity := c.evEntity()
|
||||
if evEntity == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// ISO15118-2 has to be used between EVSE and EV
|
||||
if comStandard, err := c.uc.EvCC.CommunicationStandard(evEntity); err != nil || comStandard != model.DeviceConfigurationKeyValueStringTypeISO151182ED2 {
|
||||
return false
|
||||
}
|
||||
|
||||
// SoC has to be available, otherwise it is plain ISO15118-2
|
||||
if _, err := c.Soc(); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Optimization of self consumption use case support has to be available
|
||||
if !c.uc.EvSoc.IsScenarioAvailableAtEntity(evEntity, 1) {
|
||||
return false
|
||||
}
|
||||
|
||||
// the use case has to be reported as active
|
||||
// only then the EV has no active charging demand and will charge based on OSCEV recommendations
|
||||
// this is a workaround for EVSE changing isActive to false, even though they should
|
||||
// not announce the usecase at all in that case
|
||||
ucs := evEntity.Device().UseCases()
|
||||
for _, item := range ucs {
|
||||
// check if the referenced entity address is identical to the ev entity address
|
||||
// the address may not exist, as it only available since SPINE 1.3
|
||||
if item.Address != nil &&
|
||||
evEntity.Address() != nil &&
|
||||
slices.Compare(item.Address.Entity, evEntity.Address().Entity) != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, uc := range item.UseCaseSupport {
|
||||
if uc.UseCaseName != nil &&
|
||||
*uc.UseCaseName == model.UseCaseNameTypeOptimizationOfSelfConsumptionDuringEVCharging &&
|
||||
uc.UseCaseAvailable != nil &&
|
||||
*uc.UseCaseAvailable == true {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// provides support for the special VW VAS ISO15118-2 charging behaviour if supported
|
||||
// will return false if it isn't supported or successful
|
||||
//
|
||||
// this functionality allows to fully control charging without the EV actually having a
|
||||
// charging demand by itself
|
||||
func (c *EEBus) writeLoadControlLimitsVASVW(limits []ucapi.LoadLimitsPhase) bool {
|
||||
if !c.hasActiveVASVW() {
|
||||
return false
|
||||
}
|
||||
|
||||
evEntity := c.evEntity()
|
||||
if evEntity == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// on OSCEV all limits have to be active except they are set to the default value
|
||||
minLimit, _, _, err := c.uc.OscEV.CurrentLimits(evEntity)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for index, item := range limits {
|
||||
limits[index].IsActive = item.Value >= minLimit[index]
|
||||
}
|
||||
|
||||
// send the write command
|
||||
if _, err := c.uc.OscEV.WriteLoadControlLimits(evEntity, limits, nil); err != nil {
|
||||
return false
|
||||
}
|
||||
c.currentLimit = limits[0].Value
|
||||
|
||||
// make sure the obligations are inactive, otherwise the EV won't go to sleep
|
||||
if obligations, err := c.uc.OpEV.LoadControlLimits(evEntity); err == nil {
|
||||
writeNeeded := false
|
||||
|
||||
for index, item := range obligations {
|
||||
if item.IsActive {
|
||||
obligations[index].IsActive = false
|
||||
writeNeeded = true
|
||||
}
|
||||
}
|
||||
|
||||
if writeNeeded {
|
||||
_, _ = c.uc.OpEV.WriteLoadControlLimits(evEntity, obligations, nil)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// MaxCurrent implements the api.Charger interface
|
||||
func (c *EEBus) MaxCurrent(current int64) error {
|
||||
return c.MaxCurrentMillis(float64(current))
|
||||
|
|
@ -379,7 +577,7 @@ var _ api.ChargerEx = (*EEBus)(nil)
|
|||
|
||||
// MaxCurrentMillis implements the api.ChargerEx interface
|
||||
func (c *EEBus) MaxCurrentMillis(current float64) error {
|
||||
if !c.connected || !c.emobility.EVConnected() {
|
||||
if !c.connected || c.evEntity() == nil {
|
||||
return errors.New("can't set new current as ev is unplugged")
|
||||
}
|
||||
|
||||
|
|
@ -392,25 +590,30 @@ func (c *EEBus) MaxCurrentMillis(current float64) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
var _ api.CurrentGetter = (*Easee)(nil)
|
||||
var _ api.CurrentGetter = (*EEBus)(nil)
|
||||
|
||||
// GetMaxCurrent implements the api.CurrentGetter interface
|
||||
func (c *EEBus) GetMaxCurrent() (float64, error) {
|
||||
if c.currentLimit == -1 {
|
||||
return 0, api.ErrNotAvailable
|
||||
}
|
||||
|
||||
return c.currentLimit, nil
|
||||
}
|
||||
|
||||
// CurrentPower implements the api.Meter interface
|
||||
func (c *EEBus) currentPower() (float64, error) {
|
||||
if !c.emobility.EVConnected() {
|
||||
evEntity := c.evEntity()
|
||||
if evEntity == nil {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
connectedPhases, err := c.emobility.EVConnectedPhases()
|
||||
connectedPhases, err := c.uc.EvCem.PhasesConnected(evEntity)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
powers, err := c.emobility.EVPowerPerPhase()
|
||||
powers, err := c.uc.EvCem.PowerPerPhase(evEntity)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
|
@ -428,11 +631,12 @@ func (c *EEBus) currentPower() (float64, error) {
|
|||
|
||||
// ChargedEnergy implements the api.ChargeRater interface
|
||||
func (c *EEBus) chargedEnergy() (float64, error) {
|
||||
if !c.emobility.EVConnected() {
|
||||
evEntity := c.evEntity()
|
||||
if evEntity == nil {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
energy, err := c.emobility.EVChargedEnergy()
|
||||
energy, err := c.uc.EvCem.EnergyCharged(evEntity)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
|
@ -442,13 +646,14 @@ func (c *EEBus) chargedEnergy() (float64, error) {
|
|||
|
||||
// Currents implements the api.PhaseCurrents interface
|
||||
func (c *EEBus) currents() (float64, float64, float64, error) {
|
||||
if !c.emobility.EVConnected() {
|
||||
evEntity := c.evEntity()
|
||||
if evEntity == nil {
|
||||
return 0, 0, 0, nil
|
||||
}
|
||||
|
||||
res, err := c.emobility.EVCurrentsPerPhase()
|
||||
res, err := c.uc.EvCem.CurrentPerPhase(evEntity)
|
||||
if err != nil {
|
||||
if err == features.ErrDataNotAvailable {
|
||||
if err == eebusapi.ErrDataNotAvailable {
|
||||
err = api.ErrNotAvailable
|
||||
}
|
||||
return 0, 0, 0, err
|
||||
|
|
@ -466,18 +671,18 @@ var _ api.Identifier = (*EEBus)(nil)
|
|||
|
||||
// Identify implements the api.Identifier interface
|
||||
func (c *EEBus) Identify() (string, error) {
|
||||
if !c.isConnected() || !c.emobility.EVConnected() {
|
||||
evEntity := c.evEntity()
|
||||
if !c.isConnected() || evEntity == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if !c.emobility.EVConnected() {
|
||||
return "", nil
|
||||
}
|
||||
if identification, _ := c.emobility.EVIdentification(); identification != "" {
|
||||
return identification, nil
|
||||
if identification, err := c.uc.EvCC.Identifications(evEntity); err == nil && len(identification) > 0 {
|
||||
// return the first identification for now
|
||||
// later this could be multiple, e.g. MAC Address and PCID
|
||||
return identification[0].Value, nil
|
||||
}
|
||||
|
||||
if comStandard, _ := c.emobility.EVCommunicationStandard(); comStandard == emobility.EVCommunicationStandardTypeIEC61851 {
|
||||
if comStandard, _ := c.uc.EvCC.CommunicationStandard(evEntity); comStandard == model.DeviceConfigurationKeyValueStringTypeIEC61851 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
|
|
@ -492,11 +697,13 @@ var _ api.Battery = (*EEBus)(nil)
|
|||
|
||||
// Soc implements the api.Vehicle interface
|
||||
func (c *EEBus) Soc() (float64, error) {
|
||||
if socSupported, err := c.emobility.EVSoCSupported(); err != nil || !socSupported {
|
||||
evEntity := c.evEntity()
|
||||
|
||||
if !c.uc.EvSoc.IsScenarioAvailableAtEntity(evEntity, 1) {
|
||||
return 0, api.ErrNotAvailable
|
||||
}
|
||||
|
||||
soc, err := c.emobility.EVSoC()
|
||||
soc, err := c.uc.EvSoc.StateOfCharge(evEntity)
|
||||
if err != nil {
|
||||
return 0, api.ErrNotAvailable
|
||||
}
|
||||
|
|
|
|||
77
charger/eebus/certificate.go
Normal file
77
charger/eebus/certificate.go
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
package eebus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/enbility/ship-go/cert"
|
||||
)
|
||||
|
||||
// CreateCertificate returns a newly created EEBUS compatible certificate
|
||||
func CreateCertificate() (tls.Certificate, error) {
|
||||
return cert.CreateCertificate("", EEBUSBrandName, "DE", EEBUSDeviceCode)
|
||||
}
|
||||
|
||||
// pemBlockForKey marshals private key into pem block
|
||||
func pemBlockForKey(priv interface{}) (*pem.Block, error) {
|
||||
switch k := priv.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)}, nil
|
||||
case *ecdsa.PrivateKey:
|
||||
b, err := x509.MarshalECPrivateKey(k)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to marshal ECDSA private key: %w", err)
|
||||
}
|
||||
return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}, nil
|
||||
default:
|
||||
return nil, errors.New("unknown private key type")
|
||||
}
|
||||
}
|
||||
|
||||
// GetX509KeyPair saves returns the cert and key string values
|
||||
func GetX509KeyPair(cert tls.Certificate) (string, string, error) {
|
||||
var certValue, keyValue string
|
||||
|
||||
out := new(bytes.Buffer)
|
||||
err := pem.Encode(out, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Certificate[0]})
|
||||
if err == nil {
|
||||
certValue = out.String()
|
||||
}
|
||||
|
||||
if len(certValue) > 0 {
|
||||
var pb *pem.Block
|
||||
if pb, err = pemBlockForKey(cert.PrivateKey); err == nil {
|
||||
out.Reset()
|
||||
err = pem.Encode(out, pb)
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
keyValue = out.String()
|
||||
}
|
||||
|
||||
return certValue, keyValue, err
|
||||
}
|
||||
|
||||
// SkiFromX509 extracts SKI from certificate
|
||||
func skiFromX509(leaf *x509.Certificate) (string, error) {
|
||||
if len(leaf.SubjectKeyId) == 0 {
|
||||
return "", errors.New("missing SubjectKeyId")
|
||||
}
|
||||
return fmt.Sprintf("%0x", leaf.SubjectKeyId), nil
|
||||
}
|
||||
|
||||
// SkiFromCert extracts SKI from certificate
|
||||
func SkiFromCert(cert tls.Certificate) (string, error) {
|
||||
leaf, err := x509.ParseCertificate(cert.Certificate[0])
|
||||
if err != nil {
|
||||
return "", errors.New("failed parsing certificate: " + err.Error())
|
||||
}
|
||||
return skiFromX509(leaf)
|
||||
}
|
||||
|
|
@ -1,24 +1,27 @@
|
|||
package eebus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"dario.cat/mergo"
|
||||
"github.com/enbility/cemd/cem"
|
||||
"github.com/enbility/cemd/emobility"
|
||||
"github.com/enbility/eebus-go/service"
|
||||
"github.com/enbility/eebus-go/spine/model"
|
||||
eebusapi "github.com/enbility/eebus-go/api"
|
||||
service "github.com/enbility/eebus-go/service"
|
||||
ucapi "github.com/enbility/eebus-go/usecases/api"
|
||||
"github.com/enbility/eebus-go/usecases/cem/evcc"
|
||||
"github.com/enbility/eebus-go/usecases/cem/evcem"
|
||||
"github.com/enbility/eebus-go/usecases/cem/evsecc"
|
||||
"github.com/enbility/eebus-go/usecases/cem/evsoc"
|
||||
"github.com/enbility/eebus-go/usecases/cem/opev"
|
||||
"github.com/enbility/eebus-go/usecases/cem/oscev"
|
||||
shipapi "github.com/enbility/ship-go/api"
|
||||
shiputil "github.com/enbility/ship-go/util"
|
||||
spineapi "github.com/enbility/spine-go/api"
|
||||
"github.com/enbility/spine-go/model"
|
||||
"github.com/evcc-io/evcc/util"
|
||||
"github.com/evcc-io/evcc/util/machine"
|
||||
)
|
||||
|
|
@ -43,20 +46,33 @@ func (c Config) Configured() bool {
|
|||
return len(c.Certificate.Public) > 0 && len(c.Certificate.Private) > 0
|
||||
}
|
||||
|
||||
type EEBusClientCBs struct {
|
||||
onConnect func(string) // , ship.Conn) error
|
||||
onDisconnect func(string)
|
||||
type EEBUSDeviceInterface interface {
|
||||
DeviceConnect()
|
||||
DeviceDisconnect()
|
||||
UseCaseEventCB(device spineapi.DeviceRemoteInterface, entity spineapi.EntityRemoteInterface, event eebusapi.EventType)
|
||||
}
|
||||
|
||||
// EVSE UseCases
|
||||
type UseCasesEVSE struct {
|
||||
EvseCC ucapi.CemEVSECCInterface
|
||||
EvCC ucapi.CemEVCCInterface
|
||||
EvCem ucapi.CemEVCEMInterface
|
||||
EvSoc ucapi.CemEVSOCInterface
|
||||
OpEV ucapi.CemOPEVInterface
|
||||
OscEV ucapi.CemOSCEVInterface
|
||||
}
|
||||
|
||||
type EEBus struct {
|
||||
Cem *cem.CemImpl
|
||||
service eebusapi.ServiceInterface
|
||||
|
||||
evseUC *UseCasesEVSE
|
||||
|
||||
mux sync.Mutex
|
||||
log *util.Logger
|
||||
|
||||
SKI string
|
||||
|
||||
clients map[string]EEBusClientCBs
|
||||
clients map[string]EEBUSDeviceInterface
|
||||
}
|
||||
|
||||
var Instance *EEBus
|
||||
|
|
@ -98,19 +114,20 @@ func NewServer(other Config) (*EEBus, error) {
|
|||
}
|
||||
|
||||
// TODO: get the voltage from the site
|
||||
configuration, err := service.NewConfiguration(
|
||||
configuration, err := eebusapi.NewConfiguration(
|
||||
EEBUSBrandName, EEBUSBrandName, EEBUSModel, serial,
|
||||
model.DeviceTypeTypeEnergyManagementSystem, port, certificate, 230,
|
||||
model.DeviceTypeTypeEnergyManagementSystem,
|
||||
[]model.EntityTypeType{model.EntityTypeTypeCEM},
|
||||
port, certificate, 230, time.Second*4,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// for backward compatibility
|
||||
configuration.SetAlternateMdnsServiceName("EVCC_HEMS_01")
|
||||
configuration.SetAlternateMdnsServiceName(EEBUSDeviceCode)
|
||||
configuration.SetAlternateIdentifier(serial)
|
||||
configuration.SetInterfaces(cc.Interfaces)
|
||||
configuration.SetRegisterAutoAccept(true)
|
||||
|
||||
ski, err := SkiFromCert(certificate)
|
||||
if err != nil {
|
||||
|
|
@ -119,81 +136,123 @@ func NewServer(other Config) (*EEBus, error) {
|
|||
|
||||
c := &EEBus{
|
||||
log: log,
|
||||
clients: make(map[string]EEBusClientCBs),
|
||||
clients: make(map[string]EEBUSDeviceInterface),
|
||||
SKI: ski,
|
||||
}
|
||||
|
||||
c.Cem = cem.NewCEM(configuration, c, c)
|
||||
if err := c.Cem.Setup(); err != nil {
|
||||
c.service = service.NewService(configuration, c)
|
||||
c.service.SetLogging(c)
|
||||
if err := c.service.Setup(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Cem.EnableEmobility(emobility.EmobilityConfiguration{
|
||||
CoordinatedChargingEnabled: false,
|
||||
})
|
||||
|
||||
localEntity := c.service.LocalDevice().EntityForType(model.EntityTypeTypeCEM)
|
||||
|
||||
c.evseUC = &UseCasesEVSE{
|
||||
EvseCC: evsecc.NewEVSECC(localEntity, c.evseUsecaseCB),
|
||||
EvCC: evcc.NewEVCC(c.service, localEntity, c.evseUsecaseCB),
|
||||
EvCem: evcem.NewEVCEM(c.service, localEntity, c.evseUsecaseCB),
|
||||
OpEV: opev.NewOPEV(localEntity, c.evseUsecaseCB),
|
||||
OscEV: oscev.NewOSCEV(localEntity, c.evseUsecaseCB),
|
||||
EvSoc: evsoc.NewEVSOC(localEntity, c.evseUsecaseCB),
|
||||
}
|
||||
|
||||
// register use cases
|
||||
for _, uc := range []eebusapi.UseCaseInterface{
|
||||
c.evseUC.EvseCC, c.evseUC.EvCC,
|
||||
c.evseUC.EvCem, c.evseUC.OpEV,
|
||||
c.evseUC.OscEV, c.evseUC.EvSoc,
|
||||
} {
|
||||
c.service.AddUseCase(uc)
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *EEBus) RegisterEVSE(ski, ip string, connectHandler func(string), disconnectHandler func(string), dataProvider emobility.EmobilityDataProvider) *emobility.EMobilityImpl {
|
||||
ski = strings.ReplaceAll(ski, "-", "")
|
||||
ski = strings.ReplaceAll(ski, " ", "")
|
||||
ski = strings.ToLower(ski)
|
||||
func (c *EEBus) RegisterEVSE(ski string, device EEBUSDeviceInterface) *UseCasesEVSE {
|
||||
ski = shiputil.NormalizeSKI(ski)
|
||||
c.log.TRACE.Printf("registering ski: %s", ski)
|
||||
|
||||
if ski == c.SKI {
|
||||
c.log.FATAL.Fatal("The charger SKI can not be identical to the SKI of evcc!")
|
||||
}
|
||||
|
||||
serviceDetails := service.NewServiceDetails(ski)
|
||||
serviceDetails.SetIPv4(ip)
|
||||
c.service.RegisterRemoteSKI(ski)
|
||||
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
c.clients[ski] = EEBusClientCBs{onConnect: connectHandler, onDisconnect: disconnectHandler}
|
||||
c.clients[ski] = device
|
||||
|
||||
return c.Cem.RegisterEmobilityRemoteDevice(serviceDetails, dataProvider)
|
||||
return c.evseUC
|
||||
}
|
||||
|
||||
func (c *EEBus) Run() {
|
||||
c.Cem.Start()
|
||||
c.service.Start()
|
||||
}
|
||||
|
||||
func (c *EEBus) Shutdown() {
|
||||
c.Cem.Shutdown()
|
||||
c.service.Shutdown()
|
||||
}
|
||||
|
||||
// EVSE/EV UseCase CB
|
||||
func (c *EEBus) evseUsecaseCB(ski string, device spineapi.DeviceRemoteInterface, entity spineapi.EntityRemoteInterface, event eebusapi.EventType) {
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
|
||||
if client, ok := c.clients[ski]; ok {
|
||||
client.UseCaseEventCB(device, entity, event)
|
||||
}
|
||||
}
|
||||
|
||||
// EEBUSServiceHandler
|
||||
|
||||
// report the Ship ID of a newly trusted connection
|
||||
func (c *EEBus) RemoteServiceShipIDReported(service *service.EEBUSService, ski string, shipID string) {
|
||||
// we should associated the Ship ID with the SKI and store it
|
||||
// so the next connection can start trusted
|
||||
c.log.DEBUG.Println("SKI", ski, "has Ship ID:", shipID)
|
||||
}
|
||||
|
||||
func (c *EEBus) RemoteSKIConnected(service *service.EEBUSService, ski string) {
|
||||
// no implementation needed, handled in CEM events
|
||||
func (c *EEBus) RemoteSKIConnected(service eebusapi.ServiceInterface, ski string) {
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
|
||||
client, exists := c.clients[ski]
|
||||
if !exists {
|
||||
return
|
||||
if client, ok := c.clients[ski]; ok {
|
||||
client.DeviceConnect()
|
||||
}
|
||||
client.onConnect(ski)
|
||||
}
|
||||
|
||||
func (c *EEBus) RemoteSKIDisconnected(service *service.EEBUSService, ski string) {
|
||||
// no implementation needed, handled in CEM events
|
||||
func (c *EEBus) RemoteSKIDisconnected(service eebusapi.ServiceInterface, ski string) {
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
|
||||
client, exists := c.clients[ski]
|
||||
if !exists {
|
||||
return
|
||||
if client, ok := c.clients[ski]; ok {
|
||||
client.DeviceConnect()
|
||||
}
|
||||
client.onDisconnect(ski)
|
||||
}
|
||||
|
||||
func (h *EEBus) ReportServiceShipID(ski string, shipdID string) {}
|
||||
// report all currently visible EEBUS services
|
||||
// this is needed to provide an UI for pairing with other devices
|
||||
// if not all incoming pairing requests should be accepted
|
||||
func (c *EEBus) VisibleRemoteServicesUpdated(service eebusapi.ServiceInterface, entries []shipapi.RemoteService) {
|
||||
}
|
||||
|
||||
// Provides the SHIP ID the remote service reported during the handshake process
|
||||
// This needs to be persisted and passed on for future remote service connections
|
||||
// when using `PairRemoteService`
|
||||
func (c *EEBus) ServiceShipIDUpdate(ski string, shipdID string) {}
|
||||
|
||||
// Provides the current pairing state for the remote service
|
||||
// This is called whenever the state changes and can be used to
|
||||
// provide user information for the pairing/connection process
|
||||
func (c *EEBus) ServicePairingDetailUpdate(ski string, detail *shipapi.ConnectionStateDetail) {
|
||||
if detail.State() != shipapi.ConnectionStateReceivedPairingRequest {
|
||||
return
|
||||
}
|
||||
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
|
||||
if _, ok := c.clients[ski]; !ok {
|
||||
// this is an unknown SKI, so deny pairing
|
||||
c.service.CancelPairingWithSKI(ski)
|
||||
}
|
||||
}
|
||||
|
||||
// EEBUS Logging interface
|
||||
|
||||
|
|
@ -228,68 +287,3 @@ func (c *EEBus) Error(args ...interface{}) {
|
|||
func (c *EEBus) Errorf(format string, args ...interface{}) {
|
||||
c.log.ERROR.Printf(format, args...)
|
||||
}
|
||||
|
||||
// Certificate helpers
|
||||
|
||||
// CreateCertificate returns a newly created EEBUS compatible certificate
|
||||
func CreateCertificate() (tls.Certificate, error) {
|
||||
return service.CreateCertificate("", EEBUSBrandName, "DE", EEBUSDeviceCode)
|
||||
}
|
||||
|
||||
// pemBlockForKey marshals private key into pem block
|
||||
func pemBlockForKey(priv interface{}) (*pem.Block, error) {
|
||||
switch k := priv.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)}, nil
|
||||
case *ecdsa.PrivateKey:
|
||||
b, err := x509.MarshalECPrivateKey(k)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to marshal ECDSA private key: %w", err)
|
||||
}
|
||||
return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}, nil
|
||||
default:
|
||||
return nil, errors.New("unknown private key type")
|
||||
}
|
||||
}
|
||||
|
||||
// GetX509KeyPair saves returns the cert and key string values
|
||||
func GetX509KeyPair(cert tls.Certificate) (string, string, error) {
|
||||
var certValue, keyValue string
|
||||
|
||||
out := new(bytes.Buffer)
|
||||
err := pem.Encode(out, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Certificate[0]})
|
||||
if err == nil {
|
||||
certValue = out.String()
|
||||
}
|
||||
|
||||
if len(certValue) > 0 {
|
||||
var pb *pem.Block
|
||||
if pb, err = pemBlockForKey(cert.PrivateKey); err == nil {
|
||||
out.Reset()
|
||||
err = pem.Encode(out, pb)
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
keyValue = out.String()
|
||||
}
|
||||
|
||||
return certValue, keyValue, err
|
||||
}
|
||||
|
||||
// SkiFromX509 extracts SKI from certificate
|
||||
func skiFromX509(leaf *x509.Certificate) (string, error) {
|
||||
if len(leaf.SubjectKeyId) == 0 {
|
||||
return "", errors.New("missing SubjectKeyId")
|
||||
}
|
||||
return fmt.Sprintf("%0x", leaf.SubjectKeyId), nil
|
||||
}
|
||||
|
||||
// SkiFromCert extracts SKI from certificate
|
||||
func SkiFromCert(cert tls.Certificate) (string, error) {
|
||||
leaf, err := x509.ParseCertificate(cert.Certificate[0])
|
||||
if err != nil {
|
||||
return "", errors.New("failed parsing certificate: " + err.Error())
|
||||
}
|
||||
return skiFromX509(leaf)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@ package charger
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/enbility/eebus-go/usecases/mocks"
|
||||
spinemocks "github.com/enbility/spine-go/mocks"
|
||||
"github.com/evcc-io/evcc/charger/eebus"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
|
|
@ -116,9 +119,19 @@ func TestEEBusIsCharging(t *testing.T) {
|
|||
for index, m := range tc.measurements {
|
||||
ctrl := gomock.NewController(t)
|
||||
|
||||
emobilityMock := NewMockEmobilityI(ctrl)
|
||||
evcc := mocks.NewCemEVCCInterface(t)
|
||||
evcem := mocks.NewCemEVCEMInterface(t)
|
||||
opev := mocks.NewCemOPEVInterface(t)
|
||||
|
||||
uc := &eebus.UseCasesEVSE{
|
||||
EvCC: evcc,
|
||||
EvCem: evcem,
|
||||
OpEV: opev,
|
||||
}
|
||||
evEntity := spinemocks.NewEntityRemoteInterface(t)
|
||||
eebus := &EEBus{
|
||||
emobility: emobilityMock,
|
||||
uc: uc,
|
||||
ev: evEntity,
|
||||
}
|
||||
|
||||
currents := make([]float64, 0)
|
||||
|
|
@ -127,8 +140,9 @@ func TestEEBusIsCharging(t *testing.T) {
|
|||
currents = append(currents, d.current)
|
||||
}
|
||||
|
||||
emobilityMock.EXPECT().EVCurrentsPerPhase().Return(currents, nil).AnyTimes()
|
||||
emobilityMock.EXPECT().EVCurrentLimits().Return(limitsMin, limitsMax, limitsDefault, nil)
|
||||
evcc.EXPECT().EVConnected(evEntity).Return(true)
|
||||
evcem.EXPECT().CurrentPerPhase(evEntity).Return(currents, nil)
|
||||
opev.EXPECT().CurrentLimits(evEntity).Return(limitsMin, limitsMax, limitsDefault, nil)
|
||||
|
||||
result := eebus.isCharging()
|
||||
if result != m.expected {
|
||||
|
|
|
|||
|
|
@ -1,350 +0,0 @@
|
|||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/enbility/cemd/emobility (interfaces: EmobilityI)
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -package charger -destination eebus_test_mock.go github.com/enbility/cemd/emobility EmobilityI
|
||||
//
|
||||
|
||||
// Package charger is a generated GoMock package.
|
||||
package charger
|
||||
|
||||
import (
|
||||
reflect "reflect"
|
||||
|
||||
emobility "github.com/enbility/cemd/emobility"
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockEmobilityI is a mock of EmobilityI interface.
|
||||
type MockEmobilityI struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockEmobilityIMockRecorder
|
||||
}
|
||||
|
||||
// MockEmobilityIMockRecorder is the mock recorder for MockEmobilityI.
|
||||
type MockEmobilityIMockRecorder struct {
|
||||
mock *MockEmobilityI
|
||||
}
|
||||
|
||||
// NewMockEmobilityI creates a new mock instance.
|
||||
func NewMockEmobilityI(ctrl *gomock.Controller) *MockEmobilityI {
|
||||
mock := &MockEmobilityI{ctrl: ctrl}
|
||||
mock.recorder = &MockEmobilityIMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockEmobilityI) EXPECT() *MockEmobilityIMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// EVChargeStrategy mocks base method.
|
||||
func (m *MockEmobilityI) EVChargeStrategy() emobility.EVChargeStrategyType {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "EVChargeStrategy")
|
||||
ret0, _ := ret[0].(emobility.EVChargeStrategyType)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// EVChargeStrategy indicates an expected call of EVChargeStrategy.
|
||||
func (mr *MockEmobilityIMockRecorder) EVChargeStrategy() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EVChargeStrategy", reflect.TypeOf((*MockEmobilityI)(nil).EVChargeStrategy))
|
||||
}
|
||||
|
||||
// EVChargedEnergy mocks base method.
|
||||
func (m *MockEmobilityI) EVChargedEnergy() (float64, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "EVChargedEnergy")
|
||||
ret0, _ := ret[0].(float64)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// EVChargedEnergy indicates an expected call of EVChargedEnergy.
|
||||
func (mr *MockEmobilityIMockRecorder) EVChargedEnergy() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EVChargedEnergy", reflect.TypeOf((*MockEmobilityI)(nil).EVChargedEnergy))
|
||||
}
|
||||
|
||||
// EVCommunicationStandard mocks base method.
|
||||
func (m *MockEmobilityI) EVCommunicationStandard() (emobility.EVCommunicationStandardType, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "EVCommunicationStandard")
|
||||
ret0, _ := ret[0].(emobility.EVCommunicationStandardType)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// EVCommunicationStandard indicates an expected call of EVCommunicationStandard.
|
||||
func (mr *MockEmobilityIMockRecorder) EVCommunicationStandard() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EVCommunicationStandard", reflect.TypeOf((*MockEmobilityI)(nil).EVCommunicationStandard))
|
||||
}
|
||||
|
||||
// EVConnected mocks base method.
|
||||
func (m *MockEmobilityI) EVConnected() bool {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "EVConnected")
|
||||
ret0, _ := ret[0].(bool)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// EVConnected indicates an expected call of EVConnected.
|
||||
func (mr *MockEmobilityIMockRecorder) EVConnected() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EVConnected", reflect.TypeOf((*MockEmobilityI)(nil).EVConnected))
|
||||
}
|
||||
|
||||
// EVConnectedPhases mocks base method.
|
||||
func (m *MockEmobilityI) EVConnectedPhases() (uint, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "EVConnectedPhases")
|
||||
ret0, _ := ret[0].(uint)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// EVConnectedPhases indicates an expected call of EVConnectedPhases.
|
||||
func (mr *MockEmobilityIMockRecorder) EVConnectedPhases() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EVConnectedPhases", reflect.TypeOf((*MockEmobilityI)(nil).EVConnectedPhases))
|
||||
}
|
||||
|
||||
// EVCoordinatedChargingSupported mocks base method.
|
||||
func (m *MockEmobilityI) EVCoordinatedChargingSupported() (bool, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "EVCoordinatedChargingSupported")
|
||||
ret0, _ := ret[0].(bool)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// EVCoordinatedChargingSupported indicates an expected call of EVCoordinatedChargingSupported.
|
||||
func (mr *MockEmobilityIMockRecorder) EVCoordinatedChargingSupported() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EVCoordinatedChargingSupported", reflect.TypeOf((*MockEmobilityI)(nil).EVCoordinatedChargingSupported))
|
||||
}
|
||||
|
||||
// EVCurrentChargeState mocks base method.
|
||||
func (m *MockEmobilityI) EVCurrentChargeState() (emobility.EVChargeStateType, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "EVCurrentChargeState")
|
||||
ret0, _ := ret[0].(emobility.EVChargeStateType)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// EVCurrentChargeState indicates an expected call of EVCurrentChargeState.
|
||||
func (mr *MockEmobilityIMockRecorder) EVCurrentChargeState() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EVCurrentChargeState", reflect.TypeOf((*MockEmobilityI)(nil).EVCurrentChargeState))
|
||||
}
|
||||
|
||||
// EVCurrentLimits mocks base method.
|
||||
func (m *MockEmobilityI) EVCurrentLimits() ([]float64, []float64, []float64, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "EVCurrentLimits")
|
||||
ret0, _ := ret[0].([]float64)
|
||||
ret1, _ := ret[1].([]float64)
|
||||
ret2, _ := ret[2].([]float64)
|
||||
ret3, _ := ret[3].(error)
|
||||
return ret0, ret1, ret2, ret3
|
||||
}
|
||||
|
||||
// EVCurrentLimits indicates an expected call of EVCurrentLimits.
|
||||
func (mr *MockEmobilityIMockRecorder) EVCurrentLimits() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EVCurrentLimits", reflect.TypeOf((*MockEmobilityI)(nil).EVCurrentLimits))
|
||||
}
|
||||
|
||||
// EVCurrentsPerPhase mocks base method.
|
||||
func (m *MockEmobilityI) EVCurrentsPerPhase() ([]float64, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "EVCurrentsPerPhase")
|
||||
ret0, _ := ret[0].([]float64)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// EVCurrentsPerPhase indicates an expected call of EVCurrentsPerPhase.
|
||||
func (mr *MockEmobilityIMockRecorder) EVCurrentsPerPhase() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EVCurrentsPerPhase", reflect.TypeOf((*MockEmobilityI)(nil).EVCurrentsPerPhase))
|
||||
}
|
||||
|
||||
// EVEnergyDemand mocks base method.
|
||||
func (m *MockEmobilityI) EVEnergyDemand() (emobility.EVDemand, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "EVEnergyDemand")
|
||||
ret0, _ := ret[0].(emobility.EVDemand)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// EVEnergyDemand indicates an expected call of EVEnergyDemand.
|
||||
func (mr *MockEmobilityIMockRecorder) EVEnergyDemand() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EVEnergyDemand", reflect.TypeOf((*MockEmobilityI)(nil).EVEnergyDemand))
|
||||
}
|
||||
|
||||
// EVGetIncentiveConstraints mocks base method.
|
||||
func (m *MockEmobilityI) EVGetIncentiveConstraints() emobility.EVIncentiveSlotConstraints {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "EVGetIncentiveConstraints")
|
||||
ret0, _ := ret[0].(emobility.EVIncentiveSlotConstraints)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// EVGetIncentiveConstraints indicates an expected call of EVGetIncentiveConstraints.
|
||||
func (mr *MockEmobilityIMockRecorder) EVGetIncentiveConstraints() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EVGetIncentiveConstraints", reflect.TypeOf((*MockEmobilityI)(nil).EVGetIncentiveConstraints))
|
||||
}
|
||||
|
||||
// EVGetPowerConstraints mocks base method.
|
||||
func (m *MockEmobilityI) EVGetPowerConstraints() emobility.EVTimeSlotConstraints {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "EVGetPowerConstraints")
|
||||
ret0, _ := ret[0].(emobility.EVTimeSlotConstraints)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// EVGetPowerConstraints indicates an expected call of EVGetPowerConstraints.
|
||||
func (mr *MockEmobilityIMockRecorder) EVGetPowerConstraints() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EVGetPowerConstraints", reflect.TypeOf((*MockEmobilityI)(nil).EVGetPowerConstraints))
|
||||
}
|
||||
|
||||
// EVIdentification mocks base method.
|
||||
func (m *MockEmobilityI) EVIdentification() (string, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "EVIdentification")
|
||||
ret0, _ := ret[0].(string)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// EVIdentification indicates an expected call of EVIdentification.
|
||||
func (mr *MockEmobilityIMockRecorder) EVIdentification() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EVIdentification", reflect.TypeOf((*MockEmobilityI)(nil).EVIdentification))
|
||||
}
|
||||
|
||||
// EVLoadControlObligationLimits mocks base method.
|
||||
func (m *MockEmobilityI) EVLoadControlObligationLimits() ([]float64, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "EVLoadControlObligationLimits")
|
||||
ret0, _ := ret[0].([]float64)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// EVLoadControlObligationLimits indicates an expected call of EVLoadControlObligationLimits.
|
||||
func (mr *MockEmobilityIMockRecorder) EVLoadControlObligationLimits() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EVLoadControlObligationLimits", reflect.TypeOf((*MockEmobilityI)(nil).EVLoadControlObligationLimits))
|
||||
}
|
||||
|
||||
// EVOptimizationOfSelfConsumptionSupported mocks base method.
|
||||
func (m *MockEmobilityI) EVOptimizationOfSelfConsumptionSupported() (bool, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "EVOptimizationOfSelfConsumptionSupported")
|
||||
ret0, _ := ret[0].(bool)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// EVOptimizationOfSelfConsumptionSupported indicates an expected call of EVOptimizationOfSelfConsumptionSupported.
|
||||
func (mr *MockEmobilityIMockRecorder) EVOptimizationOfSelfConsumptionSupported() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EVOptimizationOfSelfConsumptionSupported", reflect.TypeOf((*MockEmobilityI)(nil).EVOptimizationOfSelfConsumptionSupported))
|
||||
}
|
||||
|
||||
// EVPowerPerPhase mocks base method.
|
||||
func (m *MockEmobilityI) EVPowerPerPhase() ([]float64, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "EVPowerPerPhase")
|
||||
ret0, _ := ret[0].([]float64)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// EVPowerPerPhase indicates an expected call of EVPowerPerPhase.
|
||||
func (mr *MockEmobilityIMockRecorder) EVPowerPerPhase() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EVPowerPerPhase", reflect.TypeOf((*MockEmobilityI)(nil).EVPowerPerPhase))
|
||||
}
|
||||
|
||||
// EVSoC mocks base method.
|
||||
func (m *MockEmobilityI) EVSoC() (float64, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "EVSoC")
|
||||
ret0, _ := ret[0].(float64)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// EVSoC indicates an expected call of EVSoC.
|
||||
func (mr *MockEmobilityIMockRecorder) EVSoC() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EVSoC", reflect.TypeOf((*MockEmobilityI)(nil).EVSoC))
|
||||
}
|
||||
|
||||
// EVSoCSupported mocks base method.
|
||||
func (m *MockEmobilityI) EVSoCSupported() (bool, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "EVSoCSupported")
|
||||
ret0, _ := ret[0].(bool)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// EVSoCSupported indicates an expected call of EVSoCSupported.
|
||||
func (mr *MockEmobilityIMockRecorder) EVSoCSupported() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EVSoCSupported", reflect.TypeOf((*MockEmobilityI)(nil).EVSoCSupported))
|
||||
}
|
||||
|
||||
// EVWriteIncentives mocks base method.
|
||||
func (m *MockEmobilityI) EVWriteIncentives(arg0 []emobility.EVDurationSlotValue) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "EVWriteIncentives", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// EVWriteIncentives indicates an expected call of EVWriteIncentives.
|
||||
func (mr *MockEmobilityIMockRecorder) EVWriteIncentives(arg0 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EVWriteIncentives", reflect.TypeOf((*MockEmobilityI)(nil).EVWriteIncentives), arg0)
|
||||
}
|
||||
|
||||
// EVWriteLoadControlLimits mocks base method.
|
||||
func (m *MockEmobilityI) EVWriteLoadControlLimits(arg0, arg1 []float64) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "EVWriteLoadControlLimits", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// EVWriteLoadControlLimits indicates an expected call of EVWriteLoadControlLimits.
|
||||
func (mr *MockEmobilityIMockRecorder) EVWriteLoadControlLimits(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EVWriteLoadControlLimits", reflect.TypeOf((*MockEmobilityI)(nil).EVWriteLoadControlLimits), arg0, arg1)
|
||||
}
|
||||
|
||||
// EVWritePowerLimits mocks base method.
|
||||
func (m *MockEmobilityI) EVWritePowerLimits(arg0 []emobility.EVDurationSlotValue) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "EVWritePowerLimits", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// EVWritePowerLimits indicates an expected call of EVWritePowerLimits.
|
||||
func (mr *MockEmobilityIMockRecorder) EVWritePowerLimits(arg0 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EVWritePowerLimits", reflect.TypeOf((*MockEmobilityI)(nil).EVWritePowerLimits), arg0)
|
||||
}
|
||||
13
go.mod
13
go.mod
|
|
@ -24,8 +24,9 @@ require (
|
|||
github.com/dmarkham/enumer v1.5.10
|
||||
github.com/dylanmei/iso8601 v0.1.0
|
||||
github.com/eclipse/paho.mqtt.golang v1.4.3
|
||||
github.com/enbility/cemd v0.5.0
|
||||
github.com/enbility/eebus-go v0.5.0
|
||||
github.com/enbility/eebus-go v0.6.0
|
||||
github.com/enbility/ship-go v0.5.1
|
||||
github.com/enbility/spine-go v0.6.0
|
||||
github.com/evcc-io/tesla-proxy-client v0.0.0-20240221194046-4168b3759701
|
||||
github.com/fatih/structs v1.1.0
|
||||
github.com/glebarez/sqlite v1.11.0
|
||||
|
|
@ -119,6 +120,7 @@ require (
|
|||
github.com/cstockton/go-conv v1.0.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/enbility/zeroconf/v2 v2.0.0-20240210101930-d0004078577b // indirect
|
||||
github.com/fatih/color v1.17.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
|
|
@ -131,7 +133,7 @@ require (
|
|||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/golang/mock v1.6.0 // indirect
|
||||
github.com/golanguzb70/lrucache v1.2.0 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/grid-x/serial v0.0.0-20211107191517-583c7356b3aa // indirect
|
||||
|
|
@ -174,6 +176,7 @@ require (
|
|||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spali/go-slicereader v0.0.0-20201122145524-8e262e1a5127 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/teivah/onecontext v1.3.0 // indirect
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
|
||||
|
|
@ -193,7 +196,3 @@ require (
|
|||
)
|
||||
|
||||
replace gopkg.in/yaml.v3 => github.com/andig/yaml v0.0.0-20240531135838-1ff5761ab467
|
||||
|
||||
replace github.com/enbility/cemd => github.com/enbility/cemd v0.2.2
|
||||
|
||||
replace github.com/enbility/eebus-go => github.com/enbility/eebus-go v0.2.0
|
||||
|
|
|
|||
22
go.sum
22
go.sum
|
|
@ -121,10 +121,14 @@ github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFP
|
|||
github.com/eclipse/paho.mqtt.golang v1.4.3 h1:2kwcUGn8seMUfWndX0hGbvH8r7crgcJguQNCyp70xik=
|
||||
github.com/eclipse/paho.mqtt.golang v1.4.3/go.mod h1:CSYvoAlsMkhYOXh/oKyxa8EcBci6dVkLCbo5tTC1RIE=
|
||||
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
||||
github.com/enbility/cemd v0.2.2 h1:NrN7DCxv7C6YD5CaYgiebS/iPA3QmQeugF/hvNFKHNA=
|
||||
github.com/enbility/cemd v0.2.2/go.mod h1:BZoHbJQJ9/7le4WMFAJWRSgKCCTfNVEOM0c1E3H1JxE=
|
||||
github.com/enbility/eebus-go v0.2.0 h1:znQUfG1QYk0Q+vOacrsSNtXmitF1F2Rx9+ohwcRNlRw=
|
||||
github.com/enbility/eebus-go v0.2.0/go.mod h1:Ozg1eDUfSbHfQ1dWfyAUa3h8dMtgM/01eO30kHca5zk=
|
||||
github.com/enbility/eebus-go v0.6.0 h1:TiX397ON3bCPnn49BqkzEd+GTWI4rj0USmRuWxbdr5s=
|
||||
github.com/enbility/eebus-go v0.6.0/go.mod h1:6ka3OsfqJDiXAWMMYO1LkKQa7xSIgqrhwOeP5kO/yao=
|
||||
github.com/enbility/ship-go v0.5.1 h1:8Vax1MpyI/C+kQlMFAzQ7/h/xJ7fuumSLJy9FYgEkcE=
|
||||
github.com/enbility/ship-go v0.5.1/go.mod h1:jewJWYQ10jNhsnhS1C4jESx3CNmDa5HNWZjBhkTug5Y=
|
||||
github.com/enbility/spine-go v0.6.0 h1:sd5x9ZckvhI9b07HrdxKEZAcpF1Jt+xMC46o3VpuwoY=
|
||||
github.com/enbility/spine-go v0.6.0/go.mod h1:1SJ6+ihCkjzVALXXbB3xdjDGp2ITqq9gnJQiPsF3fb0=
|
||||
github.com/enbility/zeroconf/v2 v2.0.0-20240210101930-d0004078577b h1:sg3c6LJ4eWffwtt9SW0lgcIX4Oh274vwdJnNFNNrDco=
|
||||
github.com/enbility/zeroconf/v2 v2.0.0-20240210101930-d0004078577b/go.mod h1:BjzRRiYX6mWdOgku1xxDE+NsV8PijTby7Q7BkYVdfDU=
|
||||
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
|
|
@ -214,8 +218,6 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU
|
|||
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
|
|
@ -229,6 +231,8 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
|
|||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golanguzb70/lrucache v1.2.0 h1:VjpjmB4VTf9VXBtZTJGcgcN0CNFM5egDrrSjkGyQOlg=
|
||||
github.com/golanguzb70/lrucache v1.2.0/go.mod h1:zc2GD26KwGEDdTHsCCTcJorv/11HyKwQVS9gqg2bizc=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
|
|
@ -664,7 +668,6 @@ github.com/writeas/go-strip-markdown/v2 v2.1.1 h1:hAxUM21Uhznf/FnbVGiJciqzska6iL
|
|||
github.com/writeas/go-strip-markdown/v2 v2.1.1/go.mod h1:UvvgPJgn1vvN8nWuE5e7v/+qmDu3BSVnKAB6Gl7hFzA=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
gitlab.com/bboehmke/sunny v0.16.0 h1:arcU5MNupJ9KELOcSC82mYPGP/friBop+PxtybhqRwo=
|
||||
gitlab.com/bboehmke/sunny v0.16.0/go.mod h1:F5AIuL7kYteSJFR5E+YEocxIdpyCXmtDciFmMQVjP88=
|
||||
|
|
@ -712,7 +715,6 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl
|
|||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
||||
|
|
@ -738,7 +740,6 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R
|
|||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
|
|
@ -794,10 +795,8 @@ golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210426080607-c94f62235c83/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
|
@ -846,7 +845,6 @@ golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapK
|
|||
golang.org/x/tools v0.0.0-20200221224223-e1da425f72fd/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200925191224-5d1fdd8fa346/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
||||
|
|
|
|||
|
|
@ -11,3 +11,4 @@ params:
|
|||
render: |
|
||||
{{ include "eebus" . }}
|
||||
meter: true
|
||||
vasvw: true
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue