Dynamically add/remove vehicles (#9903)
This commit is contained in:
parent
abfefb7c01
commit
dd668c3007
8 changed files with 109 additions and 18 deletions
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
29
core/site_vehicles.go
Normal 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()))
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue