Make vehicles config global instead of per loadpoint (BC) (#3827)

This commit is contained in:
andig 2022-08-13 14:03:53 +02:00 • committed by GitHub
parent 3db40acfff
commit 1464b755cd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 297 additions and 178 deletions

View file

@ -0,0 +1,36 @@
package coordinator
import (
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/core/loadpoint"
)
type adapter struct {
lp loadpoint.API
c *Coordinator
}
// NewAdapter exposes the coordinator for a given loadpoint.
// Using an adapter simplifies the method signatures seen from the loadpoint.
func NewAdapter(lp loadpoint.API, c *Coordinator) API {
return &adapter{
lp: lp,
c: c,
}
}
func (a *adapter) GetVehicles() []api.Vehicle {
return a.c.GetVehicles()
}
func (a *adapter) Acquire(v api.Vehicle) {
a.c.acquire(a.lp, v)
}
func (a *adapter) Release(v api.Vehicle) {
a.c.release(v)
}
func (a *adapter) IdentifyVehicleByStatus() api.Vehicle {
return a.c.identifyVehicleByStatus(a.lp)
}

11
core/coordinator/api.go Normal file
View file

@ -0,0 +1,11 @@
package coordinator
import "github.com/evcc-io/evcc/api"
// API is the coordinator API
type API interface {
GetVehicles() []api.Vehicle
Acquire(api.Vehicle)
Release(api.Vehicle)
IdentifyVehicleByStatus() api.Vehicle
}

View file

@ -0,0 +1,84 @@
package coordinator
import (
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/core/loadpoint"
"github.com/evcc-io/evcc/util"
)
// Coordinator coordinates vehicle access between loadpoints
type Coordinator struct {
log *util.Logger
vehicles []api.Vehicle
tracked map[api.Vehicle]loadpoint.API
}
// New creates a coordinator for a set of vehicles
func New(log *util.Logger, vehicles []api.Vehicle) *Coordinator {
return &Coordinator{
log: log,
vehicles: vehicles,
tracked: make(map[api.Vehicle]loadpoint.API),
}
}
func (c *Coordinator) GetVehicles() []api.Vehicle {
return c.vehicles
}
func (c *Coordinator) acquire(owner loadpoint.API, vehicle api.Vehicle) {
if o, ok := c.tracked[vehicle]; ok && o != owner {
o.SetVehicle(nil)
}
c.tracked[vehicle] = owner
}
func (c *Coordinator) release(vehicle api.Vehicle) {
delete(c.tracked, vehicle)
}
// availableDetectibleVehicles is the list of vehicles that are currently not
// associated to another loadpoint and have a status api that allows for detection
func (c *Coordinator) availableDetectibleVehicles(owner loadpoint.API) []api.Vehicle {
var res []api.Vehicle
for _, vv := range c.vehicles {
if _, ok := vv.(api.ChargeState); ok {
if o, ok := c.tracked[vv]; o == owner || !ok {
res = append(res, vv)
}
}
}
return res
}
// identifyVehicleByStatus finds active vehicle by charge state
func (c *Coordinator) identifyVehicleByStatus(owner loadpoint.API) api.Vehicle {
available := c.availableDetectibleVehicles(owner)
var res api.Vehicle
for _, vehicle := range available {
if vs, ok := vehicle.(api.ChargeState); ok {
status, err := vs.Status()
if err != nil {
c.log.ERROR.Println("vehicle status:", err)
continue
}
c.log.DEBUG.Printf("vehicle status: %s (%s)", status, vehicle.Title())
// vehicle is plugged or charging, so it should be the right one
if status == api.StatusB || status == api.StatusC {
if res != nil {
c.log.WARN.Println("vehicle status: >1 matches, giving up")
return nil
}
res = vehicle
}
}
}
return res
}

View file

@ -0,0 +1,63 @@
package coordinator
import (
"testing"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/core/loadpoint"
"github.com/evcc-io/evcc/mock"
"github.com/evcc-io/evcc/util"
"github.com/golang/mock/gomock"
)
func TestVehicleDetectByStatus(t *testing.T) {
ctrl := gomock.NewController(t)
type vehicle struct {
*mock.MockVehicle
*mock.MockChargeState
}
v1 := &vehicle{mock.NewMockVehicle(ctrl), mock.NewMockChargeState(ctrl)}
v2 := &vehicle{mock.NewMockVehicle(ctrl), mock.NewMockChargeState(ctrl)}
type testcase struct {
string
v1, v2 api.ChargeStatus
res api.Vehicle
}
tc := []testcase{
{"A/A->0", api.StatusA, api.StatusA, nil},
{"B/A->1", api.StatusB, api.StatusA, v1},
{"B/A->1", api.StatusB, api.StatusA, v1},
{"A/B->2", api.StatusA, api.StatusB, v2},
{"A/B->2", api.StatusA, api.StatusB, v2},
{"B/B->1", api.StatusB, api.StatusB, nil},
}
log := util.NewLogger("foo")
vehicles := []api.Vehicle{v1, v2}
var lp loadpoint.API
c := New(log, vehicles)
for _, tc := range tc {
t.Logf("%+v", tc)
v1.MockChargeState.EXPECT().Status().Return(tc.v1, nil)
v2.MockChargeState.EXPECT().Status().Return(tc.v2, nil)
v1.MockVehicle.EXPECT().Title().Return("v1").AnyTimes()
v2.MockVehicle.EXPECT().Title().Return("v2").AnyTimes()
res := c.identifyVehicleByStatus(lp)
if tc.res != res {
t.Errorf("expected %v, got %v", tc.res, res)
}
if res != nil {
c.acquire(lp, res)
} else {
c.release(res)
}
}
}

24
core/coordinator/dummy.go Normal file
View file

@ -0,0 +1,24 @@
package coordinator
import (
"github.com/evcc-io/evcc/api"
)
type dummy struct{}
// NewDummy creates a dummy coordinator without vehicles
func NewDummy() API {
return &dummy{}
}
func (a *dummy) GetVehicles() []api.Vehicle {
return nil
}
func (a *dummy) Acquire(v api.Vehicle) {}
func (a *dummy) Release(v api.Vehicle) {}
func (a *dummy) IdentifyVehicleByStatus() api.Vehicle {
return nil
}