Repository: evcc-io/evcc
Pull request #10679 (2023-11-06 19:12:24)

SAJ: add ModbusTCP


Patch:
diff --git a/api/rates.go b/api/rates.go
index 2a942a03..afe05fd3 100644
--- a/api/rates.go
+++ b/api/rates.go
@@ -2,6 +2,7 @@ package api
 
 import (
 	"errors"
+	"slices"
 	"time"
 )
 
@@ -20,6 +21,13 @@ func (r Rate) IsEmpty() bool {
 // Rates is a slice of (future) tariff rates
 type Rates []Rate
 
+// Sort rates by start time
+func (r Rates) Sort() {
+	slices.SortStableFunc(r, func(i, j Rate) int {
+		return i.Start.Compare(j.Start)
+	})
+}
+
 // Current returns the rates current rate or error
 func (r Rates) Current(now time.Time) (Rate, error) {
 	for _, rr := range r {
diff --git a/charger/ocpp.go b/charger/ocpp.go
index d54e47d3..9b599edc 100644
--- a/charger/ocpp.go
+++ b/charger/ocpp.go
@@ -1,11 +1,11 @@
 package charger
 
 import (
+	"cmp"
 	"errors"
 	"fmt"
 	"math"
 	"slices"
-	"sort"
 	"strconv"
 	"strings"
 	"time"
@@ -178,12 +178,12 @@ func NewOCPP(id string, connector int, idtag string,
 			if err == nil {
 				// log unsupported configuration keys
 				if len(resp.UnknownKey) > 0 {
-					c.log.ERROR.Printf("unsupported keys: %v", sort.StringSlice(resp.UnknownKey))
+					c.log.ERROR.Printf("unsupported keys: %v", resp.UnknownKey)
 				}
 
 				// sort configuration keys for printing
-				sort.Slice(resp.ConfigurationKey, func(i, j int) bool {
-					return resp.ConfigurationKey[i].Key < resp.ConfigurationKey[j].Key
+				slices.SortFunc(resp.ConfigurationKey, func(i, j core.ConfigurationKey) int {
+					return cmp.Compare(i.Key, j.Key)
 				})
 
 				rw := map[bool]string{false: "r/w", true: "r/o"}
diff --git a/core/loadpoint_plan.go b/core/loadpoint_plan.go
index 914f2308..cadaa585 100644
--- a/core/loadpoint_plan.go
+++ b/core/loadpoint_plan.go
@@ -2,7 +2,6 @@ package core
 
 import (
 	"fmt"
-	"slices"
 	"time"
 
 	"github.com/evcc-io/evcc/api"
@@ -61,7 +60,7 @@ func (lp *Loadpoint) GetPlan(targetTime time.Time, maxPower float64) (time.Durat
 	plan, err := lp.planner.Plan(requiredDuration, targetTime)
 
 	// sort plan by time
-	slices.SortStableFunc(plan, planner.SortByTime)
+	plan.Sort()
 
 	return requiredDuration, plan, err
 }
diff --git a/core/planner/sort.go b/core/planner/sort.go
index fa0813bb..e0b967c2 100644
--- a/core/planner/sort.go
+++ b/core/planner/sort.go
@@ -2,11 +2,6 @@ package planner
 
 import "github.com/evcc-io/evcc/api"
 
-// SortByTime is a sortFunc for slices.Sort
-func SortByTime(i, j api.Rate) int {
-	return i.Start.Compare(j.Start)
-}
-
 // sortByCost is a sortFunc for slices.Sort
 func sortByCost(i, j api.Rate) int {
 	switch {
diff --git a/tariff/awattar.go b/tariff/awattar.go
index 5b94f43c..83f35954 100644
--- a/tariff/awattar.go
+++ b/tariff/awattar.go
@@ -85,6 +85,7 @@ func (t *Awattar) run(done chan error) {
 			}
 			t.data = append(t.data, ar)
 		}
+		t.data.Sort()
 
 		t.mux.Unlock()
 	}
diff --git a/tariff/electricitymaps.go b/tariff/electricitymaps.go
index 95ff072b..b45a52f2 100644
--- a/tariff/electricitymaps.go
+++ b/tariff/electricitymaps.go
@@ -3,6 +3,7 @@ package tariff
 import (
 	"errors"
 	"fmt"
+	"slices"
 	"strings"
 	"sync"
 	"time"
@@ -20,7 +21,7 @@ type ElectricityMaps struct {
 	mux     sync.Mutex
 	uri     string
 	zone    string
-	data    []CarbonIntensitySlot
+	data    api.Rates
 	updated time.Time
 }
 
@@ -102,7 +103,18 @@ func (t *ElectricityMaps) run(done chan error) {
 
 		t.mux.Lock()
 		t.updated = time.Now()
-		t.data = res.Forecast
+
+		t.data = make(api.Rates, 0, len(res.Forecast))
+		for _, r := range res.Forecast {
+			ar := api.Rate{
+				Start: r.Datetime.Local(),
+				End:   r.Datetime.Add(time.Hour).Local(),
+				Price: r.CarbonIntensity,
+			}
+			t.data = append(t.data, ar)
+		}
+		t.data.Sort()
+
 		t.mux.Unlock()
 	}
 }
@@ -110,18 +122,7 @@ func (t *ElectricityMaps) run(done chan error) {
 func (t *ElectricityMaps) Rates() (api.Rates, error) {
 	t.mux.Lock()
 	defer t.mux.Unlock()
-
-	res := make(api.Rates, 0, len(t.data))
-	for _, r := range t.data {
-		ar := api.Rate{
-			Start: r.Datetime.Local(),
-			End:   r.Datetime.Add(time.Hour).Local(),
-			Price: r.CarbonIntensity,
-		}
-		res = append(res, ar)
-	}
-
-	return res, outdatedError(t.updated, time.Hour)
+	return slices.Clone(t.data), outdatedError(t.updated, time.Hour)
 }
 
 // Type implements the api.Tariff interface
diff --git a/tariff/elering.go b/tariff/elering.go
index 3dd1a766..262e0825 100644
--- a/tariff/elering.go
+++ b/tariff/elering.go
@@ -98,6 +98,7 @@ func (t *Elering) run(done chan error) {
 			}
 			t.data = append(t.data, ar)
 		}
+		t.data.Sort()
 
 		t.mux.Unlock()
 	}
diff --git a/tariff/energinet.go b/tariff/energinet.go
index c5ec71c1..886db485 100644
--- a/tariff/energinet.go
+++ b/tariff/energinet.go
@@ -95,6 +95,7 @@ func (t *Energinet) run(done chan error) {
 			}
 			t.data = append(t.data, ar)
 		}
+		t.data.Sort()
 
 		t.mux.Unlock()
 	}
diff --git a/tariff/entsoe.go b/tariff/entsoe.go
index ba144042..38c0239f 100644
--- a/tariff/entsoe.go
+++ b/tariff/entsoe.go
@@ -169,6 +169,7 @@ func (t *Entsoe) run(done chan error) {
 			}
 			t.data = append(t.data, ar)
 		}
+		t.data.Sort()
 
 		t.mux.Unlock()
 	}
diff --git a/tariff/gruenstromindex.go b/tariff/gruenstromindex.go
index 5615a08d..5a64eb58 100644
--- a/tariff/gruenstromindex.go
+++ b/tariff/gruenstromindex.go
@@ -128,6 +128,7 @@ func (t *GrünStromIndex) run(done chan error) {
 				End:   time.UnixMilli(r.Timeframe.End).Local(),
 			})
 		}
+		t.data.Sort()
 
 		t.mux.Unlock()
 	}
diff --git a/tariff/ngeso.go b/tariff/ngeso.go
index 5db8de87..19c58018 100644
--- a/tariff/ngeso.go
+++ b/tariff/ngeso.go
@@ -3,6 +3,7 @@ package tariff
 import (
 	"errors"
 	"net/http"
+	"slices"
 	"sync"
 	"time"
 
@@ -11,7 +12,6 @@ import (
 	"github.com/evcc-io/evcc/tariff/ngeso"
 	"github.com/evcc-io/evcc/util"
 	"github.com/evcc-io/evcc/util/request"
-	"slices"
 )
 
 type Ngeso struct {
@@ -110,6 +110,7 @@ func (t *Ngeso) run(done chan error) {
 			}
 			t.data = append(t.data, ar)
 		}
+		t.data.Sort()
 
 		t.mux.Unlock()
 	}
diff --git a/tariff/octopus.go b/tariff/octopus.go
index 33ff2278..0dbb61ca 100644
--- a/tariff/octopus.go
+++ b/tariff/octopus.go
@@ -90,6 +90,7 @@ func (t *Octopus) run(done chan error) {
 			}
 			t.data = append(t.data, ar)
 		}
+		t.data.Sort()
 
 		t.mux.Unlock()
 	}
diff --git a/tariff/tibber.go b/tariff/tibber.go
index b1978b0e..f2866cb5 100644
--- a/tariff/tibber.go
+++ b/tariff/tibber.go
@@ -109,6 +109,7 @@ func (t *Tibber) run(done chan error) {
 		pi := res.Viewer.Home.CurrentSubscription.PriceInfo
 		t.data = make(api.Rates, 0, len(pi.Today)+len(pi.Tomorrow))
 		t.data = append(t.rates(pi.Today), t.rates(pi.Tomorrow)...)
+		t.data.Sort()
 
 		t.mux.Unlock()
 	}
diff --git a/templates/definition/meter/saj-h2.yaml b/templates/definition/meter/saj-h2.yaml
index 60b50cd8..6270c390 100644
--- a/templates/definition/meter/saj-h2.yaml
+++ b/templates/definition/meter/saj-h2.yaml
@@ -7,7 +7,7 @@ params:
   - name: usage
     choice: ["grid", "pv", "battery"]
   - name: modbus
-    choice: ["rs485"]
+    choice: ["rs485", "tcpip"]
     baudrate: 115200
     comset: 8N1
   - name: capacity
diff --git a/vehicle/vw/provider.go b/vehicle/vw/provider.go
index 9aa7af6f..853b7f84 100644
--- a/vehicle/vw/provider.go
+++ b/vehicle/vw/provider.go
@@ -1,9 +1,10 @@
 package vw
 
 import (
+	"cmp"
 	"fmt"
 	"os"
-	"sort"
+	"slices"
 	"strconv"
 	"strings"
 	"text/tabwriter"
@@ -186,8 +187,8 @@ func (v *Provider) Diagnose() {
 
 	tw := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
 
-	sort.Slice(rr.OperationList.ServiceInfo, func(i, j int) bool {
-		return rr.OperationList.ServiceInfo[i].ServiceId < rr.OperationList.ServiceInfo[j].ServiceId
+	slices.SortFunc(rr.OperationList.ServiceInfo, func(i, j ServiceInfo) int {
+		return cmp.Compare(i.ServiceId, j.ServiceId)
 	})
 
 	for _, si := range rr.OperationList.ServiceInfo {


Test patch:
diff --git a/core/planner/sort_test.go b/core/planner/sort_test.go
index 6059dd6a..bac3dd48 100644
--- a/core/planner/sort_test.go
+++ b/core/planner/sort_test.go
@@ -32,7 +32,7 @@ func TestRatesSortByTime(t *testing.T) {
 
 	r := testRates(clock)
 
-	slices.SortStableFunc(r, SortByTime)
+	r.Sort()
 	assert.Equal(t, clock.Now(), r[0].Start)
 	assert.Equal(t, clock.Now().Add(time.Hour), r[1].Start) // late slots first
 	assert.Equal(t, clock.Now().Add(2*time.Hour), r[2].Start)