Make vehicles config global instead of per loadpoint (BC) (#3827)
This commit is contained in:
parent
3db40acfff
commit
1464b755cd
21 changed files with 297 additions and 178 deletions
36
core/coordinator/adapter.go
Normal file
36
core/coordinator/adapter.go
Normal 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
11
core/coordinator/api.go
Normal 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
|
||||
}
|
||||
84
core/coordinator/coordinator.go
Normal file
84
core/coordinator/coordinator.go
Normal 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
|
||||
}
|
||||
63
core/coordinator/coordinator_test.go
Normal file
63
core/coordinator/coordinator_test.go
Normal 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
24
core/coordinator/dummy.go
Normal 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
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue