Skip to content

Commit

Permalink
Dynamically add/remove vehicles (#9903)
Browse files Browse the repository at this point in the history
  • Loading branch information
andig authored Sep 17, 2023
1 parent abfefb7 commit dd668c3
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 18 deletions.
6 changes: 3 additions & 3 deletions cmd/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
49 changes: 49 additions & 0 deletions core/coordinator/coordinator.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package coordinator

import (
"sync"

"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 {
mu sync.Mutex
log *util.Logger
vehicles []api.Vehicle
tracked map[api.Vehicle]loadpoint.API
Expand All @@ -24,17 +27,56 @@ 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)
}
c.tracked[vehicle] = owner
}

func (c *Coordinator) release(vehicle api.Vehicle) {
c.mu.Lock()
defer c.mu.Unlock()

delete(c.tracked, vehicle)
}

Expand All @@ -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 {
Expand All @@ -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()
Expand Down
9 changes: 0 additions & 9 deletions core/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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()
})
}
2 changes: 1 addition & 1 deletion core/loadpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions core/site.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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)

Expand Down
29 changes: 29 additions & 0 deletions core/site_vehicles.go
Original file line number Diff line number Diff line change
@@ -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()))
}
16 changes: 16 additions & 0 deletions util/config/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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
}
Expand All @@ -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
}
}
Expand Down
10 changes: 7 additions & 3 deletions util/config/instance.go
Original file line number Diff line number Diff line change
@@ -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
Expand Down

0 comments on commit dd668c3

Please sign in to comment.