Dynamically add/remove vehicles (#9903)

This commit is contained in:
andig 2023-09-17 11:37:17 +02:00 • committed by GitHub
parent abfefb7c01
commit dd668c3007
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 109 additions and 18 deletions

View file

@ -633,11 +633,11 @@ func configureSiteAndLoadpoints(conf globalConfig) (*core.Site, error) {
return nil, err
}
return configureSite(conf.Site, loadpoints, config.Instances(config.Vehicles().Devices()), tariffs)
return configureSite(conf.Site, loadpoints, tariffs)
}
func configureSite(conf map[string]interface{}, loadpoints []*core.Loadpoint, vehicles []api.Vehicle, tariffs tariff.Tariffs) (*core.Site, error) {
site, err := core.NewSiteFromConfig(log, conf, loadpoints, vehicles, tariffs)
func configureSite(conf map[string]interface{}, loadpoints []*core.Loadpoint, tariffs tariff.Tariffs) (*core.Site, error) {
site, err := core.NewSiteFromConfig(log, conf, loadpoints, tariffs)
if err != nil {
return nil, fmt.Errorf("failed configuring site: %w", err)
}

View file

@ -1,6 +1,8 @@
package coordinator
import (
"sync"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/core/loadpoint"
"github.com/evcc-io/evcc/util"
@ -8,6 +10,7 @@ import (
// Coordinator coordinates vehicle access between loadpoints
type Coordinator struct {
mu sync.Mutex
log *util.Logger
vehicles []api.Vehicle
tracked map[api.Vehicle]loadpoint.API
@ -24,10 +27,46 @@ func New(log *util.Logger, vehicles []api.Vehicle) *Coordinator {
// GetVehicles returns the list of all vehicles
func (c *Coordinator) GetVehicles() []api.Vehicle {
c.mu.Lock()
defer c.mu.Unlock()
return c.vehicles
}
func (c *Coordinator) Add(vehicle api.Vehicle) {
c.mu.Lock()
defer c.mu.Unlock()
c.vehicles = append(c.vehicles, vehicle)
}
func (c *Coordinator) Delete(vehicle api.Vehicle) {
c.mu.Lock()
for i, v := range c.vehicles {
if v == vehicle {
c.vehicles = append(c.vehicles[:i], c.vehicles[i+1:]...)
if o, ok := c.tracked[vehicle]; ok {
// defer call to SetVehicle to avoid deadlock on c.mu
defer func(o loadpoint.API) {
o.SetVehicle(nil)
}(o)
}
delete(c.tracked, vehicle)
break
}
}
// unlock before deferred SetVehicle executes a this will round-trip back here
c.mu.Unlock()
}
func (c *Coordinator) acquire(owner loadpoint.API, vehicle api.Vehicle) {
c.mu.Lock()
defer c.mu.Unlock()
if o, ok := c.tracked[vehicle]; ok && o != owner {
o.SetVehicle(nil)
}
@ -35,6 +74,9 @@ func (c *Coordinator) acquire(owner loadpoint.API, vehicle api.Vehicle) {
}
func (c *Coordinator) release(vehicle api.Vehicle) {
c.mu.Lock()
defer c.mu.Unlock()
delete(c.tracked, vehicle)
}
@ -43,6 +85,9 @@ func (c *Coordinator) release(vehicle api.Vehicle) {
func (c *Coordinator) availableDetectibleVehicles(owner loadpoint.API) []api.Vehicle {
var res []api.Vehicle
c.mu.Lock()
defer c.mu.Unlock()
for _, vv := range c.vehicles {
// status api available
if _, ok := vv.(api.ChargeState); ok {
@ -59,6 +104,10 @@ func (c *Coordinator) availableDetectibleVehicles(owner loadpoint.API) []api.Veh
// identifyVehicleByStatus finds active vehicle by charge state
func (c *Coordinator) identifyVehicleByStatus(available []api.Vehicle) api.Vehicle {
var res api.Vehicle
c.mu.Lock()
defer c.mu.Unlock()
for _, vehicle := range available {
if vs, ok := vehicle.(api.ChargeState); ok {
status, err := vs.Status()

View file

@ -2,9 +2,7 @@ package core
import (
"github.com/avast/retry-go/v4"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/util"
"github.com/samber/lo"
)
var (
@ -39,10 +37,3 @@ func sitePower(log *util.Logger, maxGrid, grid, battery, residual float64) float
return grid + battery + residual
}
// vehicleTitles returns a list of vehicle titles
func vehicleTitles(vehicles []api.Vehicle) []string {
return lo.Map(vehicles, func(v api.Vehicle, _ int) string {
return v.Title()
})
}

View file

@ -788,7 +788,7 @@ func (lp *Loadpoint) remainingChargeEnergy() (float64, bool) {
}
func (lp *Loadpoint) vehicleHasSoc() bool {
return lp.vehicle != nil && !lp.vehicleHasFeature(api.Offline)
return lp.GetVehicle() != nil && !lp.vehicleHasFeature(api.Offline)
}
// targetEnergyReached checks if target is configured and reached

View file

@ -104,7 +104,6 @@ func NewSiteFromConfig(
log *util.Logger,
other map[string]interface{},
loadpoints []*Loadpoint,
vehicles []api.Vehicle,
tariffs tariff.Tariffs,
) (*Site, error) {
site := NewSite()
@ -115,7 +114,10 @@ func NewSiteFromConfig(
Voltage = site.Voltage
site.loadpoints = loadpoints
site.tariffs = tariffs
site.coordinator = coordinator.New(log, vehicles)
site.coordinator = coordinator.New(log, config.Instances(config.Vehicles().Devices()))
config.Vehicles().Subscribe(site.updateVehicles)
site.prioritizer = prioritizer.New(log)
site.savings = NewSavings(tariffs)

29
core/site_vehicles.go Normal file
View file

@ -0,0 +1,29 @@
package core
import (
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/util/config"
"github.com/samber/lo"
)
// vehicleTitles returns a list of vehicle titles
func vehicleTitles(vehicles []api.Vehicle) []string {
return lo.Map(vehicles, func(v api.Vehicle, _ int) string {
return v.Title()
})
}
// updateVehicles adds or removes a vehicle asynchronously
func (site *Site) updateVehicles(op config.Operation, dev config.Device[api.Vehicle]) {
vehicle := dev.Instance()
switch op {
case config.OpAdd:
site.coordinator.Add(vehicle)
case config.OpDelete:
site.coordinator.Delete(vehicle)
}
site.publish("vehicles", vehicleTitles(site.GetVehicles()))
}

View file

@ -8,9 +8,23 @@ import (
type handler[T any] struct {
mu sync.Mutex
topic string
devices []Device[T]
}
type Operation string
const (
OpAdd Operation = "add"
OpDelete Operation = "del"
)
func (cp *handler[T]) Subscribe(fn func(Operation, Device[T])) {
if err := bus.Subscribe(cp.topic, fn); err != nil {
panic(err)
}
}
// Devices returns the handlers devices
func (cp *handler[T]) Devices() []Device[T] {
cp.mu.Lock()
@ -35,6 +49,7 @@ func (cp *handler[T]) Add(dev Device[T]) error {
defer cp.mu.Unlock()
cp.devices = append(cp.devices, dev)
bus.Publish(cp.topic, OpAdd, dev)
return nil
}
@ -47,6 +62,7 @@ func (cp *handler[T]) Delete(name string) error {
for i, dev := range cp.devices {
if name == dev.Config().Name {
cp.devices = append(cp.devices[:i], cp.devices[i+1:]...)
bus.Publish(cp.topic, OpDelete, dev)
return nil
}
}

View file

@ -1,20 +1,24 @@
package config
import (
evbus "github.com/asaskevich/EventBus"
"github.com/evcc-io/evcc/api"
)
var bus = evbus.New()
var instance = struct {
meters *handler[api.Meter]
chargers *handler[api.Charger]
vehicles *handler[api.Vehicle]
}{
meters: new(handler[api.Meter]),
chargers: new(handler[api.Charger]),
vehicles: new(handler[api.Vehicle]),
meters: &handler[api.Meter]{topic: "meter"},
chargers: &handler[api.Charger]{topic: "charger"},
vehicles: &handler[api.Vehicle]{topic: "vehicle"},
}
type Handler[T any] interface {
Subscribe(fn func(Operation, Device[T]))
Devices() []Device[T]
Add(dev Device[T]) error
Delete(name string) error