- Update SHIP and EEBUS libraries to latest dev code - Support IPv6 connections and devices only reporting IPv6 mDNS addresses - Re-Add support for overwriting the IP address in the charger config, also added to the meter config (just in case)
308 lines
7.6 KiB
Go
308 lines
7.6 KiB
Go
package eebus
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
|
|
"dario.cat/mergo"
|
|
eebusapi "github.com/enbility/eebus-go/api"
|
|
service "github.com/enbility/eebus-go/service"
|
|
ucapi "github.com/enbility/eebus-go/usecases/api"
|
|
"github.com/enbility/eebus-go/usecases/cem/evcc"
|
|
"github.com/enbility/eebus-go/usecases/cem/evcem"
|
|
"github.com/enbility/eebus-go/usecases/cem/evsecc"
|
|
"github.com/enbility/eebus-go/usecases/cem/evsoc"
|
|
"github.com/enbility/eebus-go/usecases/cem/opev"
|
|
"github.com/enbility/eebus-go/usecases/cem/oscev"
|
|
"github.com/enbility/eebus-go/usecases/cs/lpc"
|
|
"github.com/enbility/eebus-go/usecases/cs/lpp"
|
|
"github.com/enbility/eebus-go/usecases/ma/mgcp"
|
|
shipapi "github.com/enbility/ship-go/api"
|
|
"github.com/enbility/ship-go/mdns"
|
|
shiputil "github.com/enbility/ship-go/util"
|
|
spineapi "github.com/enbility/spine-go/api"
|
|
"github.com/enbility/spine-go/model"
|
|
"github.com/evcc-io/evcc/util"
|
|
"github.com/evcc-io/evcc/util/machine"
|
|
)
|
|
|
|
type Device interface {
|
|
Connect(connected bool)
|
|
UseCaseEvent(device spineapi.DeviceRemoteInterface, entity spineapi.EntityRemoteInterface, event eebusapi.EventType)
|
|
}
|
|
|
|
// EVSE UseCases
|
|
type UseCasesEVSE struct {
|
|
EvseCC ucapi.CemEVSECCInterface
|
|
EvCC ucapi.CemEVCCInterface
|
|
EvCem ucapi.CemEVCEMInterface
|
|
EvSoc ucapi.CemEVSOCInterface
|
|
OpEV ucapi.CemOPEVInterface
|
|
OscEV ucapi.CemOSCEVInterface
|
|
}
|
|
type UseCasesCS struct {
|
|
LPC ucapi.CsLPCInterface
|
|
LPP ucapi.CsLPPInterface
|
|
MGCP ucapi.MaMGCPInterface
|
|
}
|
|
|
|
type EEBus struct {
|
|
service eebusapi.ServiceInterface
|
|
|
|
evseUC UseCasesEVSE
|
|
csUC UseCasesCS
|
|
|
|
mux sync.Mutex
|
|
log *util.Logger
|
|
|
|
SKI string
|
|
|
|
clients map[string][]Device
|
|
}
|
|
|
|
var Instance *EEBus
|
|
|
|
func NewServer(other Config) (*EEBus, error) {
|
|
cc := Config{
|
|
URI: ":4712",
|
|
}
|
|
|
|
if err := mergo.Merge(&cc, other, mergo.WithOverride); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
log := util.NewLogger("eebus")
|
|
|
|
protectedID, err := machine.ProtectedID("evcc-eebus")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
serial := fmt.Sprintf("%s-%0x", "EVCC", protectedID[:8])
|
|
|
|
if len(cc.ShipID) != 0 {
|
|
serial = cc.ShipID
|
|
}
|
|
|
|
certificate, err := tls.X509KeyPair([]byte(cc.Certificate.Public), []byte(cc.Certificate.Private))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
_, portValue, err := net.SplitHostPort(cc.URI)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
port, err := strconv.Atoi(portValue)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// TODO: get the voltage from the site
|
|
configuration, err := eebusapi.NewConfiguration(
|
|
BrandName, BrandName, Model, serial,
|
|
model.DeviceTypeTypeEnergyManagementSystem,
|
|
[]model.EntityTypeType{model.EntityTypeTypeCEM},
|
|
port, certificate, time.Second*4,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// use avahi if available, otherwise use go based zeroconf
|
|
configuration.SetMdnsProviderSelection(mdns.MdnsProviderSelectionAll)
|
|
|
|
// for backward compatibility
|
|
configuration.SetAlternateMdnsServiceName(DeviceCode)
|
|
configuration.SetAlternateIdentifier(serial)
|
|
configuration.SetInterfaces(cc.Interfaces)
|
|
|
|
ski, err := SkiFromCert(certificate)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
c := &EEBus{
|
|
log: log,
|
|
SKI: ski,
|
|
clients: make(map[string][]Device),
|
|
}
|
|
|
|
c.service = service.NewService(configuration, c)
|
|
c.service.SetLogging(c)
|
|
if err := c.service.Setup(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
localEntity := c.service.LocalDevice().EntityForType(model.EntityTypeTypeCEM)
|
|
|
|
// evse
|
|
c.evseUC = UseCasesEVSE{
|
|
EvseCC: evsecc.NewEVSECC(localEntity, c.ucCallback),
|
|
EvCC: evcc.NewEVCC(c.service, localEntity, c.ucCallback),
|
|
EvCem: evcem.NewEVCEM(c.service, localEntity, c.ucCallback),
|
|
OpEV: opev.NewOPEV(localEntity, c.ucCallback),
|
|
OscEV: oscev.NewOSCEV(localEntity, c.ucCallback),
|
|
EvSoc: evsoc.NewEVSOC(localEntity, c.ucCallback),
|
|
}
|
|
|
|
// controllable system
|
|
c.csUC = UseCasesCS{
|
|
LPC: lpc.NewLPC(localEntity, c.ucCallback),
|
|
LPP: lpp.NewLPP(localEntity, c.ucCallback),
|
|
MGCP: mgcp.NewMGCP(localEntity, c.ucCallback),
|
|
}
|
|
|
|
// register use cases
|
|
for _, uc := range []eebusapi.UseCaseInterface{
|
|
c.evseUC.EvseCC, c.evseUC.EvCC,
|
|
c.evseUC.EvCem, c.evseUC.OpEV,
|
|
c.evseUC.OscEV, c.evseUC.EvSoc,
|
|
c.csUC.LPC, c.csUC.LPP, c.csUC.MGCP,
|
|
} {
|
|
c.service.AddUseCase(uc)
|
|
}
|
|
|
|
return c, nil
|
|
}
|
|
|
|
func (c *EEBus) RegisterDevice(ski, ip string, device Device) error {
|
|
ski = shiputil.NormalizeSKI(ski)
|
|
c.log.TRACE.Printf("registering ski: %s", ski)
|
|
|
|
if ski == c.SKI {
|
|
return errors.New("device ski can not be identical to host ski")
|
|
}
|
|
|
|
if len(ip) > 0 {
|
|
c.service.RemoteServiceForSKI(ski).SetIPv4(ip)
|
|
}
|
|
c.service.RegisterRemoteSKI(ski)
|
|
|
|
c.mux.Lock()
|
|
defer c.mux.Unlock()
|
|
c.clients[ski] = append(c.clients[ski], device)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *EEBus) Evse() *UseCasesEVSE {
|
|
return &c.evseUC
|
|
}
|
|
|
|
func (c *EEBus) ControllableSystem() *UseCasesCS {
|
|
return &c.csUC
|
|
}
|
|
|
|
func (c *EEBus) Run() {
|
|
c.service.Start()
|
|
}
|
|
|
|
func (c *EEBus) Shutdown() {
|
|
c.service.Shutdown()
|
|
}
|
|
|
|
// Use case callback
|
|
func (c *EEBus) ucCallback(ski string, device spineapi.DeviceRemoteInterface, entity spineapi.EntityRemoteInterface, event eebusapi.EventType) {
|
|
c.mux.Lock()
|
|
defer c.mux.Unlock()
|
|
|
|
c.log.DEBUG.Printf("ski %s event %s", ski, event)
|
|
|
|
if clients, ok := c.clients[ski]; ok {
|
|
for _, client := range clients {
|
|
client.UseCaseEvent(device, entity, event)
|
|
}
|
|
}
|
|
}
|
|
|
|
// EEBUSServiceHandler
|
|
|
|
func (c *EEBus) connect(ski string, connected bool) {
|
|
action := map[bool]string{true: "connected", false: "disconnected"}[connected]
|
|
c.log.DEBUG.Printf("ski %s %s", ski, action)
|
|
|
|
c.mux.Lock()
|
|
defer c.mux.Unlock()
|
|
|
|
if clients, ok := c.clients[ski]; ok {
|
|
for _, client := range clients {
|
|
client.Connect(connected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *EEBus) RemoteSKIConnected(service eebusapi.ServiceInterface, ski string) {
|
|
c.connect(ski, true)
|
|
}
|
|
|
|
func (c *EEBus) RemoteSKIDisconnected(service eebusapi.ServiceInterface, ski string) {
|
|
c.connect(ski, false)
|
|
}
|
|
|
|
// report all currently visible EEBUS services
|
|
// this is needed to provide an UI for pairing with other devices
|
|
// if not all incoming pairing requests should be accepted
|
|
func (c *EEBus) VisibleRemoteServicesUpdated(service eebusapi.ServiceInterface, entries []shipapi.RemoteService) {
|
|
}
|
|
|
|
// Provides the SHIP ID the remote service reported during the handshake process
|
|
// This needs to be persisted and passed on for future remote service connections
|
|
// when using `PairRemoteService`
|
|
func (c *EEBus) ServiceShipIDUpdate(ski string, shipdID string) {}
|
|
|
|
// Provides the current pairing state for the remote service
|
|
// This is called whenever the state changes and can be used to
|
|
// provide user information for the pairing/connection process
|
|
func (c *EEBus) ServicePairingDetailUpdate(ski string, detail *shipapi.ConnectionStateDetail) {
|
|
if detail.State() != shipapi.ConnectionStateReceivedPairingRequest {
|
|
return
|
|
}
|
|
|
|
c.mux.Lock()
|
|
defer c.mux.Unlock()
|
|
|
|
if clients, ok := c.clients[ski]; !ok || len(clients) == 0 {
|
|
// this is an unknown SKI, so deny pairing
|
|
c.service.CancelPairingWithSKI(ski)
|
|
}
|
|
}
|
|
|
|
// EEBUS Logging interface
|
|
|
|
func (c *EEBus) Trace(args ...interface{}) {
|
|
c.log.TRACE.Println(args...)
|
|
}
|
|
|
|
func (c *EEBus) Tracef(format string, args ...interface{}) {
|
|
c.log.TRACE.Printf(format, args...)
|
|
}
|
|
|
|
func (c *EEBus) Debug(args ...interface{}) {
|
|
c.log.DEBUG.Println(args...)
|
|
}
|
|
|
|
func (c *EEBus) Debugf(format string, args ...interface{}) {
|
|
c.log.DEBUG.Printf(format, args...)
|
|
}
|
|
|
|
func (c *EEBus) Info(args ...interface{}) {
|
|
c.log.INFO.Println(args...)
|
|
}
|
|
|
|
func (c *EEBus) Infof(format string, args ...interface{}) {
|
|
c.log.INFO.Printf(format, args...)
|
|
}
|
|
|
|
func (c *EEBus) Error(args ...interface{}) {
|
|
c.log.ERROR.Println(args...)
|
|
}
|
|
|
|
func (c *EEBus) Errorf(format string, args ...interface{}) {
|
|
c.log.ERROR.Printf(format, args...)
|
|
}
|