EEBUS updates and fixes (#7173)
* Adopt EEBUS v0.2 dev changes * Fix filling up currents * Improve enabled state handling when unplugged * Use ErrTimeout if disconnected
This commit is contained in:
parent
95b9cadc87
commit
4f4edba5ba
5 changed files with 100 additions and 159 deletions
113
charger/eebus.go
113
charger/eebus.go
|
|
@ -29,8 +29,8 @@ type EEBus struct {
|
|||
|
||||
communicationStandard emobility.EVCommunicationStandardType
|
||||
|
||||
maxCurrent float64
|
||||
expectedEnableState bool
|
||||
expectedEnableUnpluggedState bool
|
||||
current float64
|
||||
|
||||
lastIsChargingCheck time.Time
|
||||
lastIsChargingResult bool
|
||||
|
|
@ -79,9 +79,10 @@ func NewEEBus(ski, ip string, hasMeter, hasChargedEnergy bool) (api.Charger, err
|
|||
log: log,
|
||||
connectedC: make(chan bool, 1),
|
||||
communicationStandard: emobility.EVCommunicationStandardTypeUnknown,
|
||||
current: 6,
|
||||
}
|
||||
|
||||
c.emobility = eebus.Instance.Register(ski, ip, c.onConnect, c.onDisconnect)
|
||||
c.emobility = eebus.Instance.RegisterEVSE(ski, ip, c.onConnect, c.onDisconnect, nil)
|
||||
|
||||
err := c.waitForConnection()
|
||||
|
||||
|
|
@ -114,6 +115,7 @@ func (c *EEBus) waitForConnection() error {
|
|||
func (c *EEBus) onConnect(ski string) {
|
||||
c.log.TRACE.Println("!! onConnect invoked on ski ", ski)
|
||||
|
||||
c.expectedEnableUnpluggedState = false
|
||||
c.setDefaultValues()
|
||||
c.setConnected(true)
|
||||
}
|
||||
|
|
@ -121,13 +123,12 @@ func (c *EEBus) onConnect(ski string) {
|
|||
func (c *EEBus) onDisconnect(ski string) {
|
||||
c.log.TRACE.Println("!! onDisconnect invoked on ski ", ski)
|
||||
|
||||
c.expectedEnableState = false
|
||||
c.expectedEnableUnpluggedState = false
|
||||
c.setConnected(false)
|
||||
c.setDefaultValues()
|
||||
}
|
||||
|
||||
func (c *EEBus) setDefaultValues() {
|
||||
c.expectedEnableState = false
|
||||
c.communicationStandard = emobility.EVCommunicationStandardTypeUnknown
|
||||
c.lastIsChargingCheck = time.Now().Add(-time.Hour * 1)
|
||||
c.lastIsChargingResult = false
|
||||
|
|
@ -227,32 +228,30 @@ func (c *EEBus) updateState() (api.ChargeStatus, error) {
|
|||
}
|
||||
|
||||
if !c.isConnected() {
|
||||
return api.StatusNone, fmt.Errorf("%s disconnected", c.ski)
|
||||
return api.StatusNone, api.ErrTimeout
|
||||
}
|
||||
|
||||
switch currentState {
|
||||
case emobility.EVChargeStateTypeUnknown, emobility.EVChargeStateTypeUnplugged: // Unplugged
|
||||
c.expectedEnableState = false
|
||||
c.expectedEnableUnpluggedState = false
|
||||
return api.StatusA, nil
|
||||
case emobility.EVChargeStateTypeFinished, emobility.EVChargeStateTypePaused: // Finished, Paused
|
||||
return api.StatusB, nil
|
||||
case emobility.EVChargeStateTypeActive: // Active
|
||||
if c.isCharging() {
|
||||
// we might already be enabled and charging due to connection issues
|
||||
c.expectedEnableState = true
|
||||
return api.StatusC, nil
|
||||
}
|
||||
return api.StatusB, nil
|
||||
case emobility.EVChargeStateTypeError: // Error
|
||||
return api.StatusF, nil
|
||||
default:
|
||||
return api.StatusNone, fmt.Errorf("%s properties unknown result: %s", c.ski, currentState)
|
||||
}
|
||||
|
||||
return api.StatusNone, fmt.Errorf("%s properties unknown result: %s", c.ski, currentState)
|
||||
}
|
||||
|
||||
// Status implements the api.Charger interface
|
||||
func (c *EEBus) Status() (api.ChargeStatus, error) {
|
||||
// check the current limits and update if necesarry
|
||||
// check the current limits and update if necessary
|
||||
c.setLoadpointMinMaxLimits()
|
||||
|
||||
return c.updateState()
|
||||
|
|
@ -261,63 +260,55 @@ func (c *EEBus) Status() (api.ChargeStatus, error) {
|
|||
// Enabled implements the api.Charger interface
|
||||
// should return true if the charger allows the EV to draw power
|
||||
func (c *EEBus) Enabled() (bool, error) {
|
||||
_, err := c.updateState()
|
||||
return c.expectedEnableState, err
|
||||
// when unplugged there is no overload limit data available
|
||||
state, err := c.updateState()
|
||||
if err != nil || state == api.StatusA {
|
||||
return c.expectedEnableUnpluggedState, nil
|
||||
}
|
||||
|
||||
limits, err := c.emobility.EVLoadControlObligationLimits()
|
||||
if err != nil {
|
||||
// there are no overload protection limits available, e.g. because the data was not received yet
|
||||
return true, nil
|
||||
}
|
||||
|
||||
for _, limit := range limits {
|
||||
// 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 {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Enable implements the api.Charger interface
|
||||
func (c *EEBus) Enable(enable bool) error {
|
||||
currentState, err := c.emobility.EVCurrentChargeState()
|
||||
if err != nil || currentState == emobility.EVChargeStateTypeUnplugged {
|
||||
// if the ev is unplugged, we do not need to disable charging by setting a current of 0 as it already is
|
||||
if !enable {
|
||||
return nil
|
||||
}
|
||||
// if the ev is unplugged, we can not enable charging
|
||||
return errors.New("can not enable charging as ev is unplugged")
|
||||
// if the ev is unplugged or the state is unknown, there is nothing to be done
|
||||
state, err := c.updateState()
|
||||
if err != nil || state == api.StatusA {
|
||||
c.expectedEnableUnpluggedState = enable
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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!
|
||||
comStandard, err := c.emobility.EVCommunicationStandard()
|
||||
if err != nil || comStandard == emobility.EVCommunicationStandardTypeUnknown {
|
||||
return api.ErrMustRetry
|
||||
}
|
||||
|
||||
// we have to know the limits
|
||||
minLimits, maxLimits, _, err := c.emobility.EVCurrentLimits()
|
||||
if err != nil {
|
||||
return api.ErrMustRetry
|
||||
}
|
||||
|
||||
c.expectedEnableState = enable
|
||||
|
||||
if !enable {
|
||||
// Important notes on enabling/disabling!!
|
||||
// ISO15118 mode:
|
||||
// non-asymmetric or all phases set to 0: the OBC will wait for 1 minute, if the values remain after 1 min, it will pause then
|
||||
// asymmetric and only some phases set to 0: no pauses or waiting for changes required
|
||||
// asymmetric mode requires Plug & Charge (PnC) and Value Added Services (VAS)
|
||||
// IEC61851 mode:
|
||||
// switching between 1/3 phases: stop charging, pause for 2 minutes, change phases, resume charging
|
||||
// frequent switching should be avoided by all means!
|
||||
c.maxCurrent = 0
|
||||
return c.writeCurrentLimitData([]float64{0, 0, 0})
|
||||
comStandard, err := c.emobility.EVCommunicationStandard()
|
||||
if err != nil || comStandard == emobility.EVCommunicationStandardTypeUnknown {
|
||||
return api.ErrMustRetry
|
||||
}
|
||||
}
|
||||
|
||||
// if we set MaxCurrent > Min value and then try to enable the charger, it would reset it to min
|
||||
if c.maxCurrent > 0 {
|
||||
return c.writeCurrentLimitData([]float64{c.maxCurrent, c.maxCurrent, c.maxCurrent})
|
||||
var current float64
|
||||
|
||||
if enable {
|
||||
current = c.current
|
||||
}
|
||||
|
||||
// we need to check if the mode is set to now as the currents won't be adjusted afterwards any more in all cases
|
||||
if c.lp.GetMode() == api.ModeNow {
|
||||
return c.writeCurrentLimitData(maxLimits)
|
||||
}
|
||||
|
||||
// in non now mode only enable with min settings, so we don't excessively consume power in case it has to be turned of in the next cycle anyways
|
||||
|
||||
return c.writeCurrentLimitData(minLimits)
|
||||
return c.writeCurrentLimitData([]float64{current, current, current})
|
||||
}
|
||||
|
||||
// send current charging power limits to the EV
|
||||
|
|
@ -366,10 +357,12 @@ func (c *EEBus) MaxCurrentMillis(current float64) error {
|
|||
return errors.New("can't set new current as ev is unplugged")
|
||||
}
|
||||
|
||||
c.maxCurrent = current
|
||||
err = c.writeCurrentLimitData([]float64{current, current, current})
|
||||
if err == nil {
|
||||
c.current = current
|
||||
}
|
||||
|
||||
currents := []float64{current, current, current}
|
||||
return c.writeCurrentLimitData(currents)
|
||||
return err
|
||||
}
|
||||
|
||||
// CurrentPower implements the api.Meter interface
|
||||
|
|
@ -441,7 +434,7 @@ func (c *EEBus) currents() (float64, float64, float64, error) {
|
|||
|
||||
count := len(currents)
|
||||
if count < 3 {
|
||||
for fill := 0; fill < count-3; fill++ {
|
||||
for fill := 0; fill < 3-count; fill++ {
|
||||
currents = append(currents, 0)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,14 +117,17 @@ func NewServer(other map[string]interface{}) (*EEBus, error) {
|
|||
}
|
||||
|
||||
c.Cem = cem.NewCEM(configuration, c, c)
|
||||
if err := c.Cem.Setup(true); err != nil {
|
||||
if err := c.Cem.Setup(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Cem.EnableEmobility(emobility.EmobilityConfiguration{
|
||||
CoordinatedChargingEnabled: false,
|
||||
})
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *EEBus) Register(ski, ip string, connectHandler func(string), disconnectHandler func(string)) *emobility.EMobilityImpl {
|
||||
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)
|
||||
|
|
@ -141,7 +144,7 @@ func (c *EEBus) Register(ski, ip string, connectHandler func(string), disconnect
|
|||
defer c.mux.Unlock()
|
||||
c.clients[ski] = EEBusClientCBs{onConnect: connectHandler, onDisconnect: disconnectHandler}
|
||||
|
||||
return c.Cem.RegisterEmobilityRemoteDevice(serviceDetails)
|
||||
return c.Cem.RegisterEmobilityRemoteDevice(serviceDetails, dataProvider)
|
||||
}
|
||||
|
||||
func (c *EEBus) Run() {
|
||||
|
|
|
|||
|
|
@ -4,85 +4,25 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/enbility/cemd/emobility"
|
||||
"github.com/golang/mock/gomock"
|
||||
)
|
||||
|
||||
type limitStruct struct {
|
||||
phase uint
|
||||
min, max, pause float64
|
||||
}
|
||||
|
||||
type measurementStruct struct {
|
||||
phase uint
|
||||
current float64
|
||||
}
|
||||
|
||||
type testMeasurementStruct struct {
|
||||
expected bool
|
||||
data []measurementStruct
|
||||
}
|
||||
|
||||
// Emobility mock
|
||||
|
||||
type EmobilityMock struct {
|
||||
connectedPhases uint
|
||||
currents, limitsMin, limitsMax, limitsDefault []float64
|
||||
}
|
||||
|
||||
func (e *EmobilityMock) EVCurrentChargeState() (emobility.EVChargeStateType, error) {
|
||||
return emobility.EVChargeStateTypeUnknown, nil
|
||||
}
|
||||
|
||||
func (e *EmobilityMock) EVConnectedPhases() (uint, error) {
|
||||
return e.connectedPhases, nil
|
||||
}
|
||||
|
||||
func (e *EmobilityMock) EVChargedEnergy() (float64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (e *EmobilityMock) EVPowerPerPhase() ([]float64, error) {
|
||||
return []float64{}, nil
|
||||
}
|
||||
|
||||
func (e *EmobilityMock) EVCurrentsPerPhase() ([]float64, error) {
|
||||
return e.currents, nil
|
||||
}
|
||||
|
||||
func (e *EmobilityMock) EVCurrentLimits() ([]float64, []float64, []float64, error) {
|
||||
return e.limitsMin, e.limitsMax, e.limitsDefault, nil
|
||||
}
|
||||
|
||||
func (e *EmobilityMock) EVWriteLoadControlLimits(obligations, recommendations []float64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *EmobilityMock) EVCommunicationStandard() (emobility.EVCommunicationStandardType, error) {
|
||||
return emobility.EVCommunicationStandardTypeUnknown, nil
|
||||
}
|
||||
|
||||
func (e *EmobilityMock) EVIdentification() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (e *EmobilityMock) EVOptimizationOfSelfConsumptionSupported() (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (e *EmobilityMock) EVSoCSupported() (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (e *EmobilityMock) EVSoC() (float64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (e *EmobilityMock) EVCoordinatedChargingSupported() (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
var _ emobility.EmobilityI = (*EmobilityMock)(nil)
|
||||
|
||||
func TestEEBusIsCharging(t *testing.T) {
|
||||
type limitStruct struct {
|
||||
phase uint
|
||||
min, max, pause float64
|
||||
}
|
||||
|
||||
type measurementStruct struct {
|
||||
phase uint
|
||||
current float64
|
||||
}
|
||||
|
||||
type testMeasurementStruct struct {
|
||||
expected bool
|
||||
data []measurementStruct
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
limits []limitStruct
|
||||
|
|
@ -162,35 +102,40 @@ func TestEEBusIsCharging(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
emobilityMock := &EmobilityMock{}
|
||||
eebus := &EEBus{
|
||||
emobility: emobilityMock,
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
emobilityMock.connectedPhases = 3
|
||||
emobilityMock.limitsMin = make([]float64, 0)
|
||||
emobilityMock.limitsMax = make([]float64, 0)
|
||||
emobilityMock.limitsDefault = make([]float64, 0)
|
||||
limitsMin := make([]float64, 0)
|
||||
limitsMax := make([]float64, 0)
|
||||
limitsDefault := make([]float64, 0)
|
||||
|
||||
for _, limit := range tc.limits {
|
||||
emobilityMock.limitsMin = append(emobilityMock.limitsMin, limit.min)
|
||||
emobilityMock.limitsMax = append(emobilityMock.limitsMax, limit.max)
|
||||
emobilityMock.limitsDefault = append(emobilityMock.limitsDefault, limit.pause)
|
||||
limitsMin = append(limitsMin, limit.min)
|
||||
limitsMax = append(limitsMax, limit.max)
|
||||
limitsDefault = append(limitsDefault, limit.pause)
|
||||
}
|
||||
|
||||
for index, m := range tc.measurements {
|
||||
emobilityMock.currents = make([]float64, 0)
|
||||
ctrl := gomock.NewController(t)
|
||||
|
||||
emobilityMock := emobility.NewMockEmobilityI(ctrl)
|
||||
eebus := &EEBus{
|
||||
emobility: emobilityMock,
|
||||
}
|
||||
|
||||
currents := make([]float64, 0)
|
||||
|
||||
for _, d := range m.data {
|
||||
emobilityMock.currents = append(emobilityMock.currents, d.current)
|
||||
currents = append(currents, d.current)
|
||||
}
|
||||
|
||||
emobilityMock.EXPECT().EVCurrentsPerPhase().Return(currents, nil).AnyTimes()
|
||||
emobilityMock.EXPECT().EVCurrentLimits().Return(limitsMin, limitsMax, limitsDefault, nil)
|
||||
|
||||
result := eebus.isCharging()
|
||||
if result != m.expected {
|
||||
t.Errorf("Failure: test %s, series %d, expected %v, got %v", tc.name, index, m.expected, result)
|
||||
}
|
||||
ctrl.Finish()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
4
go.mod
4
go.mod
|
|
@ -27,8 +27,8 @@ require (
|
|||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/dylanmei/iso8601 v0.1.0
|
||||
github.com/eclipse/paho.mqtt.golang v1.4.2
|
||||
github.com/enbility/cemd v0.1.7
|
||||
github.com/enbility/eebus-go v0.1.7
|
||||
github.com/enbility/cemd v0.2.0
|
||||
github.com/enbility/eebus-go v0.2.0
|
||||
github.com/fatih/structs v1.1.0
|
||||
github.com/foogod/go-powerwall v0.2.0
|
||||
github.com/glebarez/sqlite v1.7.0
|
||||
|
|
|
|||
8
go.sum
8
go.sum
|
|
@ -346,10 +346,10 @@ github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFP
|
|||
github.com/eclipse/paho.mqtt.golang v1.4.2 h1:66wOzfUHSSI1zamx7jR6yMEI5EuHnT1G6rNA5PM12m4=
|
||||
github.com/eclipse/paho.mqtt.golang v1.4.2/go.mod h1:JGt0RsEwEX+Xa/agj90YJ9d9DH2b7upDZMK9HRbFvCA=
|
||||
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
||||
github.com/enbility/cemd v0.1.7 h1:PuofIXdILw3rXe1JMWHW7bA2ritaGp/3wy8rBRC8png=
|
||||
github.com/enbility/cemd v0.1.7/go.mod h1:yjDruIQ9LkQI9dEvmYcp6y0QeVRPC9P8tli3Ehp8Ecs=
|
||||
github.com/enbility/eebus-go v0.1.7 h1:iPOp3oJ12cExOfS9tcBbsHRSOyr+uKGypjAjRP8SdaE=
|
||||
github.com/enbility/eebus-go v0.1.7/go.mod h1:Ozg1eDUfSbHfQ1dWfyAUa3h8dMtgM/01eO30kHca5zk=
|
||||
github.com/enbility/cemd v0.2.0 h1:T+XbqX2PYKmbIgtdNAtKk1DVZaXjOAsIt+NqlIs4Ajo=
|
||||
github.com/enbility/cemd v0.2.0/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/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue