diff --git a/.github/workflows/default.yml b/.github/workflows/default.yml index 9b9eb0e6..b23b8935 100644 --- a/.github/workflows/default.yml +++ b/.github/workflows/default.yml @@ -7,6 +7,7 @@ on: push: branches: - dev + - main pull_request: workflow_call: @@ -36,3 +37,8 @@ jobs: - name: Test run: go test -race -v -coverprofile=coverage.out -covermode=atomic ./... + + - name: Send coverage + uses: shogo82148/actions-goveralls@v1 + with: + path-to-profile: coverage.out \ No newline at end of file diff --git a/README.md b/README.md index 2b399c8c..53fea743 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ # eebus-go -![Build Status](https://github.com/enbility/eebus-go/actions/workflows/default.yml/badge.svg?branch=main) +[![Build Status](https://github.com/enbility/eebus-go/actions/workflows/default.yml/badge.svg?branch=main)](https://github.com/enbility/eebus-go/actions/workflows/default.yml/badge.svg?branch=main) +[![GoDoc](https://img.shields.io/badge/godoc-reference-5272B4)](https://godoc.org/github.com/enbility/eebus-go) +[![Coverage Status](https://coveralls.io/repos/github/enbility/eebus-go/badge.svg?branch=main)](https://coveralls.io/github/enbility/eebus-go?branch=main) +[![Go report](https://goreportcard.com/badge/github.com/enbility/eebus-go)](https://goreportcard.com/report/github.com/enbility/eebus-go) This library provides a complete foundation for implementing [EEBUS](https://eebus.org) use cases. The use cases define various functional scenarios for different device categories, e.g. energy management systems, charging stations, heat pumps, and more. @@ -238,6 +241,7 @@ Use `SetLogger` on `Service` to set the logger which needs to conform to the `lo Example: ```go -h.myService = service.NewEEBUSService(serviceDescription, h) +configuration = service.NewConfiguration(...) +h.myService = service.NewEEBUSService(configuration, h) h.myService.SetLogging(h) ``` diff --git a/cmd/evse/main.go b/cmd/evse/main.go index 9af50967..f92bcb62 100644 --- a/cmd/evse/main.go +++ b/cmd/evse/main.go @@ -60,15 +60,15 @@ func (h *evse) run() { log.Fatal(err) } - serviceDescription, err := service.NewServiceDescription( + configuration, err := service.NewConfiguration( "Demo", "Demo", "EVSE", "234567890", - model.DeviceTypeTypeChargingStation, port, certificate) + model.DeviceTypeTypeChargingStation, port, certificate, 230) if err != nil { log.Fatal(err) } - serviceDescription.SetAlternateIdentifier("Demo-EVSE-234567890") + configuration.SetAlternateIdentifier("Demo-EVSE-234567890") - h.myService = service.NewEEBUSService(serviceDescription, h) + h.myService = service.NewEEBUSService(configuration, h) h.myService.SetLogging(h) if err = h.myService.Setup(); err != nil { diff --git a/cmd/hems/main.go b/cmd/hems/main.go index 647544e1..daf24dcc 100644 --- a/cmd/hems/main.go +++ b/cmd/hems/main.go @@ -60,15 +60,15 @@ func (h *hems) run() { log.Fatal(err) } - serviceDescription, err := service.NewServiceDescription( + configuration, err := service.NewConfiguration( "Demo", "Demo", "HEMS", "123456789", - model.DeviceTypeTypeEnergyManagementSystem, port, certificate) + model.DeviceTypeTypeEnergyManagementSystem, port, certificate, 230) if err != nil { log.Fatal(err) } - serviceDescription.SetAlternateIdentifier("Demo-HEMS-123456789") + configuration.SetAlternateIdentifier("Demo-HEMS-123456789") - h.myService = service.NewEEBUSService(serviceDescription, h) + h.myService = service.NewEEBUSService(configuration, h) h.myService.SetLogging(h) if err = h.myService.Setup(); err != nil { diff --git a/features/measurement.go b/features/measurement.go index c7661de9..47fe5930 100644 --- a/features/measurement.go +++ b/features/measurement.go @@ -70,17 +70,17 @@ func (m *Measurement) Request() (*model.MsgCounterType, error) { // return current value of a defined scope func (m *Measurement) GetValueForScope(scope model.ScopeTypeType, electricalConnection *ElectricalConnection) (float64, error) { if m.featureRemote == nil { - return 0.0, ErrDataNotAvailable + return 0, ErrDataNotAvailable } descRef, err := m.GetDescription() if err != nil { - return 0.0, ErrMetadataNotAvailable + return 0, ErrMetadataNotAvailable } rData := m.featureRemote.Data(model.FunctionTypeMeasurementListData) if rData == nil { - return 0.0, ErrDataNotAvailable + return 0, ErrDataNotAvailable } data := rData.(*model.MeasurementListDataType) @@ -218,17 +218,17 @@ func (m *Measurement) GetDescriptionForScope(scope model.ScopeTypeType) (measure // return current SoC for measurements func (m *Measurement) GetSoC() (float64, error) { if m.featureRemote == nil { - return 0.0, ErrDataNotAvailable + return 0, ErrDataNotAvailable } descRef, err := m.GetDescription() if err != nil { - return 0.0, ErrMetadataNotAvailable + return 0, ErrMetadataNotAvailable } rData := m.featureRemote.Data(model.FunctionTypeMeasurementListData) if rData == nil { - return 0.0, ErrDataNotAvailable + return 0, ErrDataNotAvailable } data := rData.(*model.MeasurementListDataType) @@ -251,7 +251,7 @@ func (m *Measurement) GetSoC() (float64, error) { } } - return 0.0, ErrDataNotAvailable + return 0, ErrDataNotAvailable } type measurementConstraintMap map[model.MeasurementIdType]model.MeasurementConstraintsDataType diff --git a/service/hub.go b/service/hub.go index f995726f..4d8324a9 100644 --- a/service/hub.go +++ b/service/hub.go @@ -26,7 +26,7 @@ const shipWebsocketPath = "/ship/" const shipZeroConfServiceType = "_ship._tcp" const shipZeroConfDomain = "local." -// used for randomizing the connection initation delay +// used for randomizing the connection initiation delay // this limits the possibility of concurrent connection attempts from both sides type connectionInitiationDelayTimeRange struct { // defines the minimum and maximum wait time for when to try to initate an connection @@ -65,8 +65,8 @@ type connectionsHub struct { connectionAttemptCounter map[string]int connectionAttemptRunning map[string]bool - serviceDescription *ServiceDescription - localService *ServiceDetails + configuration *Configuration + localService *ServiceDetails serviceProvider serviceProvider @@ -88,7 +88,7 @@ type connectionsHub struct { muxMdns sync.Mutex } -func newConnectionsHub(serviceProvider serviceProvider, spineLocalDevice *spine.DeviceLocalImpl, serviceDescription *ServiceDescription, localService *ServiceDetails) (*connectionsHub, error) { +func newConnectionsHub(serviceProvider serviceProvider, spineLocalDevice *spine.DeviceLocalImpl, configuration *Configuration, localService *ServiceDetails) (*connectionsHub, error) { hub := &connectionsHub{ connections: make(map[string]*ship.ShipConnection), connectionAttemptCounter: make(map[string]int), @@ -96,13 +96,13 @@ func newConnectionsHub(serviceProvider serviceProvider, spineLocalDevice *spine. pairedServices: make([]ServiceDetails, 0), serviceProvider: serviceProvider, spineLocalDevice: spineLocalDevice, - serviceDescription: serviceDescription, + configuration: configuration, localService: localService, } localService.SKI = util.NormalizeSKI(localService.SKI) - mdns, err := newMDNS(localService.SKI, serviceDescription) + mdns, err := newMDNS(localService.SKI, configuration) if err != nil { return nil, err } @@ -251,14 +251,14 @@ func (h *connectionsHub) isSkiConnected(ski string) bool { // start the ship websocket server func (h *connectionsHub) startWebsocketServer() error { - addr := fmt.Sprintf(":%d", h.serviceDescription.port) + addr := fmt.Sprintf(":%d", h.configuration.port) logging.Log.Debug("starting websocket server on", addr) h.httpServer = &http.Server{ Addr: addr, Handler: h, TLSConfig: &tls.Config{ - Certificates: []tls.Certificate{h.serviceDescription.certificate}, + Certificates: []tls.Certificate{h.configuration.certificate}, ClientAuth: tls.RequireAnyClientCert, // SHIP 9: Client authentication is required CipherSuites: ciperSuites, VerifyPeerCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { @@ -372,7 +372,7 @@ func (h *connectionsHub) connectFoundService(remoteService *ServiceDetails, host Proxy: http.ProxyFromEnvironment, HandshakeTimeout: 5 * time.Second, TLSClientConfig: &tls.Config{ - Certificates: []tls.Certificate{h.serviceDescription.certificate}, + Certificates: []tls.Certificate{h.configuration.certificate}, InsecureSkipVerify: true, CipherSuites: ciperSuites, }, diff --git a/service/mdns.go b/service/mdns.go index a3c6b859..db7eab6a 100644 --- a/service/mdns.go +++ b/service/mdns.go @@ -34,8 +34,8 @@ type MdnsSearch interface { } type mdns struct { - serviceDescription *ServiceDescription - ski string + configuration *Configuration + ski string isAnnounced bool isSearchingServices bool @@ -58,12 +58,12 @@ type mdns struct { mux sync.Mutex } -func newMDNS(ski string, serviceDescription *ServiceDescription) (*mdns, error) { +func newMDNS(ski string, configuration *Configuration) (*mdns, error) { m := &mdns{ - ski: ski, - serviceDescription: serviceDescription, - entries: make(map[string]MdnsEntry), - cancelChan: make(chan bool), + ski: ski, + configuration: configuration, + entries: make(map[string]MdnsEntry), + cancelChan: make(chan bool), } if av, err := m.setupAvahi(); err == nil { @@ -112,10 +112,10 @@ func (m *mdns) interfaces() ([]net.Interface, []int32, error) { var ifaces []net.Interface var ifaceIndexes []int32 - if len(m.serviceDescription.interfaces) > 0 { - ifaces = make([]net.Interface, len(m.serviceDescription.interfaces)) - ifaceIndexes = make([]int32, len(m.serviceDescription.interfaces)) - for i, ifaceName := range m.serviceDescription.interfaces { + if len(m.configuration.interfaces) > 0 { + ifaces = make([]net.Interface, len(m.configuration.interfaces)) + ifaceIndexes = make([]int32, len(m.configuration.interfaces)) + for i, ifaceName := range m.configuration.interfaces { iface, err := net.InterfaceByName(ifaceName) if err != nil { return nil, nil, err @@ -146,26 +146,26 @@ func (m *mdns) Announce() error { return err } - serviceIdentifier := m.serviceDescription.Identifier() + serviceIdentifier := m.configuration.Identifier() txt := []string{ // SHIP 7.3.2 "txtvers=1", "path=" + shipWebsocketPath, "id=" + serviceIdentifier, "ski=" + m.ski, - "brand=" + m.serviceDescription.deviceBrand, - "model=" + m.serviceDescription.deviceModel, - "type=" + string(m.serviceDescription.deviceType), - "register=" + fmt.Sprintf("%v", m.serviceDescription.registerAutoAccept), + "brand=" + m.configuration.deviceBrand, + "model=" + m.configuration.deviceModel, + "type=" + string(m.configuration.deviceType), + "register=" + fmt.Sprintf("%v", m.configuration.registerAutoAccept), } logging.Log.Debug("mdns: announce") - serviceName := m.serviceDescription.MdnsServiceName() + serviceName := m.configuration.MdnsServiceName() if m.av == nil { // use Zeroconf library if avahi is not available - mDNSServer, err := zeroconf.Register(serviceName, shipZeroConfServiceType, shipZeroConfDomain, m.serviceDescription.port, txt, ifaces) + mDNSServer, err := zeroconf.Register(serviceName, shipZeroConfServiceType, shipZeroConfDomain, m.configuration.port, txt, ifaces) if err == nil { m.zc = mDNSServer @@ -188,7 +188,7 @@ func (m *mdns) Announce() error { } for _, iface := range ifaceIndexes { - err = entryGroup.AddService(iface, avahi.ProtoUnspec, 0, serviceName, shipZeroConfServiceType, shipZeroConfDomain, "", uint16(m.serviceDescription.port), btxt) + err = entryGroup.AddService(iface, avahi.ProtoUnspec, 0, serviceName, shipZeroConfServiceType, shipZeroConfDomain, "", uint16(m.configuration.port), btxt) if err != nil { return err } @@ -317,7 +317,7 @@ func (m *mdns) resolveEntries() { case <-m.cancelChan: ctx.Done() case service := <-zcEntries: - // Zeroconf has issues with merging mDNS data and sometimes reports non complety records + // Zeroconf has issues with merging mDNS data and sometimes reports incomplete records if len(service.Text) == 0 { continue } diff --git a/service/service.go b/service/service.go index 33230f95..80c2b167 100644 --- a/service/service.go +++ b/service/service.go @@ -29,7 +29,7 @@ type EEBUSServiceHandler interface { // A service is the central element of an EEBUS service // including its websocket server and a zeroconf service. type EEBUSService struct { - ServiceDescription *ServiceDescription + Configuration *Configuration // The local service details LocalService *ServiceDetails @@ -46,10 +46,10 @@ type EEBUSService struct { } // creates a new EEBUS service -func NewEEBUSService(ServiceDescription *ServiceDescription, serviceHandler EEBUSServiceHandler) *EEBUSService { +func NewEEBUSService(configuration *Configuration, serviceHandler EEBUSServiceHandler) *EEBUSService { return &EEBUSService{ - ServiceDescription: ServiceDescription, - serviceHandler: serviceHandler, + Configuration: configuration, + serviceHandler: serviceHandler, } } @@ -81,11 +81,11 @@ func (s *EEBUSService) SetLogging(logger logging.Logging) { // Starts the service by initializeing mDNS and the server. func (s *EEBUSService) Setup() error { - if s.ServiceDescription.port == 0 { - s.ServiceDescription.port = defaultPort + if s.Configuration.port == 0 { + s.Configuration.port = defaultPort } - sd := s.ServiceDescription + sd := s.Configuration leaf, err := x509.ParseCertificate(sd.certificate.Certificate[0]) if err != nil { @@ -155,7 +155,7 @@ func (s *EEBUSService) Setup() error { s.spineLocalDevice.AddEntity(entity) // Setup connections hub with mDNS and websocket connection handling - hub, err := newConnectionsHub(s, s.spineLocalDevice, s.ServiceDescription, s.LocalService) + hub, err := newConnectionsHub(s, s.spineLocalDevice, s.Configuration, s.LocalService) if err != nil { return err } diff --git a/service/types.go b/service/types.go index 1489b767..c1ffd430 100644 --- a/service/types.go +++ b/service/types.go @@ -34,7 +34,7 @@ type ServiceDetails struct { } // defines requires meta information about this service -type ServiceDescription struct { +type Configuration struct { // The vendors IANA PEN, optional but highly recommended. // If not set, brand will be used instead // Used for the Device Address: SPINE - Protocol Specification 7.1.1.2 @@ -90,10 +90,15 @@ type ServiceDescription struct { // the spec defines that this should have a timeout and be activate // e.g via a physical button registerAutoAccept bool + + // The sites grid voltage + // This is useful when e.g. power values are not available and therefor + // need to be calculated using the current values + voltage float64 } -// Setup a ServiceDescription with the required parameters -func NewServiceDescription( +// Setup a Configuration with the required parameters +func NewConfiguration( vendorCode, deviceBrand, deviceModel, @@ -101,10 +106,12 @@ func NewServiceDescription( deviceType model.DeviceTypeType, port int, certificate tls.Certificate, -) (*ServiceDescription, error) { - serviceDescription := &ServiceDescription{ + voltage float64, +) (*Configuration, error) { + configuration := &Configuration{ certificate: certificate, port: port, + voltage: voltage, } isRequired := "is required" @@ -112,63 +119,63 @@ func NewServiceDescription( if len(vendorCode) == 0 { return nil, fmt.Errorf("vendorCode %s", isRequired) } else { - serviceDescription.vendorCode = vendorCode + configuration.vendorCode = vendorCode } if len(deviceBrand) == 0 { return nil, fmt.Errorf("brand %s", isRequired) } else { - serviceDescription.deviceBrand = deviceBrand + configuration.deviceBrand = deviceBrand } if len(deviceModel) == 0 { return nil, fmt.Errorf("model %s", isRequired) } else { - serviceDescription.deviceModel = deviceModel + configuration.deviceModel = deviceModel } if len(serialNumber) == 0 { return nil, fmt.Errorf("serialNumber %s", isRequired) } else { - serviceDescription.deviceSerialNumber = serialNumber + configuration.deviceSerialNumber = serialNumber } if len(deviceType) == 0 { return nil, fmt.Errorf("deviceType %s", isRequired) } else { - serviceDescription.deviceType = deviceType + configuration.deviceType = deviceType } // set default - serviceDescription.featureSet = model.NetworkManagementFeatureSetTypeSmart + configuration.featureSet = model.NetworkManagementFeatureSetTypeSmart - return serviceDescription, nil + return configuration, nil } // define an alternative mDNS and SHIP identifier // usually this is only used when no deviceCode is available or identical to the brand // if this is not set, generated identifier is used -func (s *ServiceDescription) SetAlternateIdentifier(identifier string) { +func (s *Configuration) SetAlternateIdentifier(identifier string) { s.alternateIdentifier = identifier } // define an alternative mDNS service name // this is normally not needed or used -func (s *ServiceDescription) SetAlternateMdnsServiceName(name string) { +func (s *Configuration) SetAlternateMdnsServiceName(name string) { s.alternateMdnsServiceName = name } // define which network interfaces should be considered instead of all existing // expects a list of network interface names -func (s *ServiceDescription) SetInterfaces(ifaces []string) { +func (s *Configuration) SetInterfaces(ifaces []string) { s.interfaces = ifaces } // define wether this service should announce auto accept // TODO: this needs to be redesigned! -func (s *ServiceDescription) SetRegisterAutoAccept(auto bool) { +func (s *Configuration) SetRegisterAutoAccept(auto bool) { s.registerAutoAccept = auto } // generates a standard identifier used for mDNS ID and SHIP ID // Brand-Model-SerialNumber -func (s *ServiceDescription) generateIdentifier() string { +func (s *Configuration) generateIdentifier() string { return fmt.Sprintf("%s-%s-%s", s.deviceBrand, s.deviceModel, s.deviceSerialNumber) } @@ -176,7 +183,7 @@ func (s *ServiceDescription) generateIdentifier() string { // returns in this order: // - alternateIdentifier // - generateIdentifier -func (s *ServiceDescription) Identifier() string { +func (s *Configuration) Identifier() string { // SHIP identifier is identical to the mDNS ID if len(s.alternateIdentifier) > 0 { return s.alternateIdentifier @@ -189,7 +196,7 @@ func (s *ServiceDescription) Identifier() string { // returns in this order: // - alternateMdnsServiceName // - generateIdentifier -func (s *ServiceDescription) MdnsServiceName() string { +func (s *Configuration) MdnsServiceName() string { // SHIP identifier is identical to the mDNS ID if len(s.alternateMdnsServiceName) > 0 { return s.alternateMdnsServiceName @@ -197,3 +204,8 @@ func (s *ServiceDescription) MdnsServiceName() string { return s.generateIdentifier() } + +// return the sites predefined grid voltage +func (s *Configuration) Voltage() float64 { + return s.voltage +} diff --git a/spine/feature_local.go b/spine/feature_local.go index 24541ae2..6ed07c23 100644 --- a/spine/feature_local.go +++ b/spine/feature_local.go @@ -93,7 +93,7 @@ func (r *FeatureLocalImpl) Entity() *EntityLocalImpl { return r.entity } -// Add supported function to the feature if its role is Server or Speical +// Add supported function to the feature if its role is Server or Special func (r *FeatureLocalImpl) AddFunctionType(function model.FunctionType, read, write bool) { if r.role != model.RoleTypeServer && r.role != model.RoleTypeSpecial { return diff --git a/spine/feature_local_test.go b/spine/feature_local_test.go index 5d0fcb50..a7296b3e 100644 --- a/spine/feature_local_test.go +++ b/spine/feature_local_test.go @@ -87,7 +87,7 @@ func (suite *DeviceClassificationTestSuite) TestDeviceClassification_Request_Err suite.senderMock.On("Request", model.CmdClassifierTypeRead, suite.sut.Address(), suite.remoteFeature.Address(), false, mock.AnythingOfType("[]model.CmdType")).Return(&suite.msgCounter, nil) const errorNumber = model.ErrorNumberTypeGeneralError - const errorDescription = "error occured" + const errorDescription = "error occurred" // send data request msgCounter, err := suite.sut.RequestData(suite.function, nil, nil, suite.remoteFeature) diff --git a/spine/model/commondatatypes_additions_test.go b/spine/model/commondatatypes_additions_test.go index eaa93649..73e39eb5 100644 --- a/spine/model/commondatatypes_additions_test.go +++ b/spine/model/commondatatypes_additions_test.go @@ -194,11 +194,11 @@ func TestNewScaledNumberType(t *testing.T) { number int64 scale int }{ - {0.0, 0, 0}, + {0, 0, 0}, {0.1, 1, -1}, {1.0, 1, 0}, {6.25, 625, -2}, - {10.0, 10, 0}, + {10, 10, 0}, {12.5952, 125952, -4}, {13.1637, 131637, -4}, } @@ -245,7 +245,7 @@ func TestFeatureAddressTypeString(t *testing.T) { got := f.String() if got != tc.out { - t.Errorf("TestFeatureAddressTypeString(), got %s, expectes %s", got, tc.out) + t.Errorf("TestFeatureAddressTypeString(), got %s, expects %s", got, tc.out) } } } diff --git a/spine/pending_requests_test.go b/spine/pending_requests_test.go index 4a6a8ec1..b148b683 100644 --- a/spine/pending_requests_test.go +++ b/spine/pending_requests_test.go @@ -111,7 +111,7 @@ func (suite *PendingRequestsTestSuite) TestPendingRequests_SetData_GetData() { func (suite *PendingRequestsTestSuite) TestPendingRequests_SetResult_GetData() { errNo := model.ErrorNumberTypeTimeout - errDesc := "Timeout occured" + errDesc := "Timeout occurred" _ = suite.sut.SetResult(suite.ski, suite.counter, NewErrorType(errNo, errDesc)) // Act