Repository: evcc-io/evcc
Pull request #4591 (2022-09-26 11:30:43)

Replace machine-id errors by configuration hint
Follow-up to https://github.com/evcc-io/evcc/pull/4581


Patch:
diff --git a/cmd/setup.go b/cmd/setup.go
index b1dcfd1a..a4cbd5c3 100644
--- a/cmd/setup.go
+++ b/cmd/setup.go
@@ -20,6 +20,7 @@ import (
 	"github.com/evcc-io/evcc/server"
 	"github.com/evcc-io/evcc/tariff"
 	"github.com/evcc-io/evcc/util"
+	"github.com/evcc-io/evcc/util/machine"
 	"github.com/evcc-io/evcc/util/pipe"
 	"github.com/evcc-io/evcc/util/sponsor"
 	"github.com/evcc-io/evcc/util/telemetry"
@@ -54,6 +55,11 @@ func loadConfigFile(conf *config) error {
 }
 
 func configureEnvironment(conf config) (err error) {
+	// setup machine id
+	if conf.Plant != "" {
+		err = machine.CustomID(conf.Plant)
+	}
+
 	// setup sponsorship
 	if conf.SponsorToken != "" {
 		err = sponsor.ConfigureSponsorship(conf.SponsorToken)
diff --git a/core/site.go b/core/site.go
index 09296274..dc976d5b 100644
--- a/core/site.go
+++ b/core/site.go
@@ -469,7 +469,7 @@ func (site *Site) update(lp Updater) {
 	// update savings and aggregate telemetry
 	// TODO: use energy instead of current power for better results
 	deltaCharged, deltaSelf := site.savings.Update(site, site.gridPower, site.pvPower, site.batteryPower, totalChargePower)
-	if telemetry.Enabled && totalChargePower > standbyPower {
+	if telemetry.Enabled() && totalChargePower > standbyPower {
 		go telemetry.UpdateChargeProgress(site.log, totalChargePower, deltaCharged, deltaSelf)
 	}
 }
diff --git a/go.mod b/go.mod
index cf77d4e6..298082a0 100644
--- a/go.mod
+++ b/go.mod
@@ -19,6 +19,7 @@ require (
 	github.com/containrrr/shoutrrr v0.6.1
 	github.com/coreos/go-oidc/v3 v3.3.0
 	github.com/deepmap/oapi-codegen v1.11.0
+	github.com/denisbrodbeck/machineid v1.0.1
 	github.com/dmarkham/enumer v1.5.6
 	github.com/dustin/go-humanize v1.0.0
 	github.com/dylanmei/iso8601 v0.1.0
@@ -62,7 +63,6 @@ require (
 	github.com/nicksnyder/go-i18n/v2 v2.2.0
 	github.com/nirasan/go-oauth-pkce-code-verifier v0.0.0-20220510032225-4f9f17eaec4c
 	github.com/olekukonko/tablewriter v0.0.5
-	github.com/panta/machineid v1.0.1
 	github.com/philippseith/signalr v0.6.1-0.20220829124759-bd5ffb679356
 	github.com/prometheus/client_golang v1.13.0
 	github.com/prometheus/common v0.37.0
@@ -101,7 +101,6 @@ require (
 	github.com/cespare/xxhash/v2 v2.1.2 // indirect
 	github.com/chzyer/readline v1.5.1 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
-	github.com/denisbrodbeck/machineid v1.0.1 // indirect
 	github.com/fatih/color v1.13.0 // indirect
 	github.com/felixge/httpsnoop v1.0.3 // indirect
 	github.com/fsnotify/fsnotify v1.5.4 // indirect
diff --git a/go.sum b/go.sum
index 4962a29e..6df5528e 100644
--- a/go.sum
+++ b/go.sum
@@ -693,8 +693,6 @@ github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJ
 github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
 github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
 github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
-github.com/panta/machineid v1.0.1 h1:IUIF47trYWR813A3cO/MnTEpesPFOhvejyauCGh1KI0=
-github.com/panta/machineid v1.0.1/go.mod h1:JXdlT3pyUqvDz7Pf1VeU2hRa1DIfOu0zBrj+xSvVbDg=
 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
 github.com/pascaldekloe/name v1.0.0/go.mod h1:Z//MfYJnH4jVpQ9wkclwu2I2MkHmXTlT9wR5UZScttM=
 github.com/pascaldekloe/name v1.0.1 h1:9lnXOHeqeHHnWLbKfH6X98+4+ETVqFqxN09UXSjcMb0=
diff --git a/hems/ocpp/ocpp.go b/hems/ocpp/ocpp.go
index 520ea77b..8bb4d27a 100644
--- a/hems/ocpp/ocpp.go
+++ b/hems/ocpp/ocpp.go
@@ -10,11 +10,11 @@ import (
 	"github.com/evcc-io/evcc/core/site"
 	"github.com/evcc-io/evcc/hems/ocpp/profile"
 	"github.com/evcc-io/evcc/util"
+	"github.com/evcc-io/evcc/util/machine"
 
 	ocpp16 "github.com/lorenzodonini/ocpp-go/ocpp1.6"
 	ocppcore "github.com/lorenzodonini/ocpp-go/ocpp1.6/core"
 	"github.com/lorenzodonini/ocpp-go/ws"
-	"github.com/panta/machineid"
 )
 
 // OCPP is an OCPP client
@@ -40,7 +40,7 @@ func New(conf map[string]interface{}, site site.API) (*OCPP, error) {
 	log := util.NewLogger("ocpp")
 
 	if cc.StationID == "" {
-		id, err := machineid.ProtectedID("evcc-ocpp")
+		id, err := machine.ProtectedID("evcc-ocpp")
 		if err == nil {
 			cc.StationID = fmt.Sprintf("evcc-%s", strings.ToLower(id))
 		} else {
diff --git a/hems/semp/semp.go b/hems/semp/semp.go
index 333d183e..3217fd90 100644
--- a/hems/semp/semp.go
+++ b/hems/semp/semp.go
@@ -17,10 +17,10 @@ import (
 	"github.com/evcc-io/evcc/core/site"
 	"github.com/evcc-io/evcc/server"
 	"github.com/evcc-io/evcc/util"
+	"github.com/evcc-io/evcc/util/machine"
 	"github.com/google/uuid"
 	"github.com/gorilla/mux"
 	"github.com/koron/go-ssdp"
-	"github.com/panta/machineid"
 )
 
 const (
@@ -344,7 +344,7 @@ func (s *SEMP) serialNumber(id int) string {
 func UniqueDeviceID() ([]byte, error) {
 	bytes := 6
 
-	mid, err := machineid.ProtectedID("evcc-semp")
+	mid, err := machine.ProtectedID("evcc-semp")
 	if err != nil {
 		return nil, err
 	}
diff --git a/util/helper.go b/util/helper.go
index b9754dc9..14158a48 100644
--- a/util/helper.go
+++ b/util/helper.go
@@ -4,7 +4,7 @@ import (
 	"math/rand"
 )
 
-// RandomString creates random string of N integers
+// RandomString creates random string of N characters
 func RandomString(n int) string {
 	letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
 	s := make([]rune, n)
diff --git a/util/machine/machine.go b/util/machine/machine.go
new file mode 100644
index 00000000..761dd619
--- /dev/null
+++ b/util/machine/machine.go
@@ -0,0 +1,66 @@
+package machine
+
+import (
+	"crypto/hmac"
+	"crypto/sha256"
+	"encoding/hex"
+	"fmt"
+	"strings"
+
+	"github.com/evcc-io/evcc/util"
+
+	"github.com/denisbrodbeck/machineid"
+)
+
+var id string
+
+// CustomID sets the machine id to a custom value
+func CustomID(cid string) error {
+	if id != "" {
+		panic("machine id already generated")
+	}
+
+	cid = strings.TrimSpace(cid)
+	if l := len(cid); l != 32 {
+		return fmt.Errorf("expected 32 characters machine id, got %d", l)
+	}
+
+	id = cid
+
+	return nil
+}
+
+// ID returns the platform specific machine id of the current host OS.
+// If ID cannot be generated, a random value is suggested.
+func ID() (string, error) {
+	if id == "" {
+		var err error
+		if id, err = machineid.ID(); err != nil {
+			rnd := util.RandomString(512)
+			mac := hmac.New(sha256.New, []byte(rnd))
+			rid := hex.EncodeToString(mac.Sum(nil))
+
+			return "", fmt.Errorf("could not get %w; for manual configuration use plant: %s", err, rid)
+		}
+	}
+
+	return id, nil
+}
+
+// ProtectedID returns a hashed version of the machine id
+// using a fixed, application-specific key.
+func ProtectedID(key string) (string, error) {
+	id, err := machineid.ID()
+	if err != nil {
+		return id, err
+	}
+
+	return protect(key, id), nil
+}
+
+// protect calculates HMAC-SHA256 of the id, keyed by key and returns a hex-encoded string
+func protect(key, id string) string {
+	mac := hmac.New(sha256.New, []byte(id))
+	mac.Write([]byte(key))
+	return hex.EncodeToString(mac.Sum(nil))
+}
diff --git a/util/telemetry/charging.go b/util/telemetry/charge.go
similarity index 79%
rename from util/telemetry/charging.go
rename to util/telemetry/charge.go
index 88e889ad..27751190 100644
--- a/util/telemetry/charging.go
+++ b/util/telemetry/charge.go
@@ -6,30 +6,33 @@ import (
 	"net/http"
 
 	"github.com/evcc-io/evcc/util"
+	"github.com/evcc-io/evcc/util/machine"
 	"github.com/evcc-io/evcc/util/request"
 	"github.com/evcc-io/evcc/util/sponsor"
-	"github.com/panta/machineid"
 )
 
 const api = "https://api.evcc.io"
 
-var (
-	Enabled    bool
-	instanceID string
-)
+var instanceID string
+
+func Enabled() bool {
+	return instanceID != ""
+}
 
-func Create(token, instance string) error {
+func Create(token, machineID string) error {
 	if token == "" {
 		return errors.New("telemetry requires sponsorship")
 	}
 
-	Enabled = true
-	instanceID = instance
-
-	if mid, err := machineid.ProtectedID("evcc-api"); err == nil && instanceID == "" {
-		instanceID = mid
+	if machineID == "" {
+		var err error
+		if machineID, err = machine.ProtectedID("evcc-api"); err != nil {
+			return err
+		}
 	}
 
+	instanceID = machineID
+
 	return nil
 }
 


Test patch:
diff --git a/util/machine/machine_test.go b/util/machine/machine_test.go
new file mode 100644
index 00000000..43a852af
--- /dev/null
+++ b/util/machine/machine_test.go
@@ -0,0 +1,22 @@
+package machine
+
+import (
+	"testing"
+
+	"github.com/denisbrodbeck/machineid"
+)
+
+func TestProtectedMachineId(t *testing.T) {
+	const key = "foo"
+
+	if mid, err := machineid.ProtectedID(key); err == nil {
+		id, err := ProtectedID(key)
+		if err != nil {
+			t.Error(err)
+		}
+
+		if mid != id {
+			t.Errorf("machine id mismatch. expected %s, got %s", mid, id)
+		}
+	}
+}