Repository: evcc-io/evcc
Pull request #12160 (2024-02-09 09:06:40)

Tronity: update api endpoints and oauth scopes
The tronity api endpoints and the oauth scopes have changed.

### Endpoint

Set api endpoints to https://api.tronity.tech

### OAuth Scopes

Updated to the scope names documented here: https://app.tronity.tech/docs#section/Scopes

andig: Danke für den Hinweis- ich kann in der Doku leider nix zur Änderung des Endpunktes finden. Könntest Du den Link bitte nochmal teilen? Insbesondere- ab wann ist das gültig?

Patch:
diff --git a/core/loadpoint.go b/core/loadpoint.go
index 54ca2927..d19ff444 100644
--- a/core/loadpoint.go
+++ b/core/loadpoint.go
@@ -1184,9 +1184,15 @@ func (lp *Loadpoint) pvMaxCurrent(mode api.ChargeMode, sitePower float64, batter
 	}
 
 	if mode == api.ModePV && lp.enabled && targetCurrent < minCurrent {
+		projectedSitePower := sitePower
+		if !lp.phaseTimer.IsZero() {
+			// calculate site power after a phase switch from activePhases phases -> 1 phase
+			// notes: activePhases can be 1, 2 or 3 and phaseTimer can only be active if lp current is already at minCurrent
+			projectedSitePower -= Voltage * minCurrent * float64(activePhases-1)
+		}
 		// kick off disable sequence
-		if sitePower >= lp.Disable.Threshold && lp.phaseTimer.IsZero() {
-			lp.log.DEBUG.Printf("site power %.0fW >= %.0fW disable threshold", sitePower, lp.Disable.Threshold)
+		if projectedSitePower >= lp.Disable.Threshold {
+			lp.log.DEBUG.Printf("projected site power %.0fW >= %.0fW disable threshold", projectedSitePower, lp.Disable.Threshold)
 
 			if lp.pvTimer.IsZero() {
 				lp.log.DEBUG.Printf("pv disable timer start: %v", lp.Disable.Delay)
diff --git a/vehicle/tronity.go b/vehicle/tronity.go
index ae2571b8..c1f194cf 100644
--- a/vehicle/tronity.go
+++ b/vehicle/tronity.go
@@ -165,7 +165,7 @@ func (v *Tronity) RefreshToken(_ *oauth2.Token) (*oauth2.Token, error) {
 
 // vehicles implements the vehicles api
 func (v *Tronity) vehicles() ([]tronity.Vehicle, error) {
-	uri := fmt.Sprintf("%s/v1/vehicles", tronity.URI)
+	uri := fmt.Sprintf("%s/tronity/vehicles", tronity.URI)
 
 	var res tronity.Vehicles
 	err := v.GetJSON(uri, &res)
@@ -175,7 +175,7 @@ func (v *Tronity) vehicles() ([]tronity.Vehicle, error) {
 
 // bulk implements the bulk api
 func (v *Tronity) bulk() (tronity.Bulk, error) {
-	uri := fmt.Sprintf("%s/v1/vehicles/%s/bulk", tronity.URI, v.vid)
+	uri := fmt.Sprintf("%s/tronity/vehicles/%s/last_record", tronity.URI, v.vid)
 
 	var res tronity.Bulk
 	err := v.GetJSON(uri, &res)
@@ -235,12 +235,12 @@ func (v *Tronity) post(uri string) error {
 
 // startCharge implements the api.VehicleChargeController interface
 func (v *Tronity) startCharge() error {
-	uri := fmt.Sprintf("%s/v1/vehicles/%s/charge_start", tronity.URI, v.vid)
+	uri := fmt.Sprintf("%s/tronity/vehicles/%s/start_charging", tronity.URI, v.vid)
 	return v.post(uri)
 }
 
 // stopCharge implements the api.VehicleChargeController interface
 func (v *Tronity) stopCharge() error {
-	uri := fmt.Sprintf("%s/v1/vehicles/%s/charge_stop", tronity.URI, v.vid)
+	uri := fmt.Sprintf("%s/tronity/vehicles/%s/stop_charging", tronity.URI, v.vid)
 	return v.post(uri)
 }
diff --git a/vehicle/tronity/auth.go b/vehicle/tronity/auth.go
index 00257ddd..0a027c3b 100644
--- a/vehicle/tronity/auth.go
+++ b/vehicle/tronity/auth.go
@@ -4,15 +4,15 @@ import (
 	"golang.org/x/oauth2"
 )
 
-const URI = "https://api-eu.tronity.io"
+const URI = "https://api.tronity.tech"
 
 func OAuth2Config(id, secret string) (*oauth2.Config, error) {
 	return &oauth2.Config{
 		ClientID:     id,
 		ClientSecret: secret,
 		Endpoint: oauth2.Endpoint{
-			AuthURL:  "https://api-eu.tronity.io/oauth/authorize",
-			TokenURL: "https://api-eu.tronity.io/oauth/authentication",
+			AuthURL:  "https://auth.tronity.io/oauth/v2/authorize",
+			TokenURL: "https://api.tronity.tech/authentication",
 		},
 		Scopes: []string{"read_vin", "read_vehicle_info", "read_odometer", "read_charge", "read_charge", "read_battery", "read_location", "write_charge_start_stop", "write_wake_up"},
 	}, nil
diff --git a/vehicle/tronity/types.go b/vehicle/tronity/types.go
index 3bb20924..8748a073 100644
--- a/vehicle/tronity/types.go
+++ b/vehicle/tronity/types.go
@@ -1,16 +1,11 @@
 package tronity
 
-// https://app.platform.tronity.io/docs#operation
+// https://app.tronity.tech/docs#section/Authentication-Flow
 
 const (
-	ReadBattery          = "read_battery"            // Read EV battery's capacity and state of charge
-	ReadCharge           = "read_charge"             // Know whether vehicle is charging
-	ReadLocation         = "read_location"           // Access location
-	ReadOdometer         = "read_odometer"           // Retrieve total distance traveled
-	ReadVehicleInfo      = "read_vehicle_info"       // Know make, model, and year
-	ReadVIN              = "read_vin"                // Read VIN
-	WriteChargeStartStop = "write_charge_start_stop" // Start or stop your vehicle's charging
-	WriteWakeUp          = "write_wake_up"           // Wake up car. Only valid for Tesla
+	ReadCharge           = "tronity_charging" // Know whether vehicle is charging
+	ReadOdometer         = "tronity_odometer" // Retrieve total distance traveled
+	WriteChargeStartStop = "tronity_control_charging"
 )
 
 type Vehicles struct {
@@ -26,7 +21,6 @@ type Vehicle struct {
 }
 
 type Bulk struct {
-	VIN      string
 	Odometer float64
 	Range    float64
 	Level    float64


Test patch:
diff --git a/core/loadpoint_test.go b/core/loadpoint_test.go
index 548920ab..53c9b8a8 100644
--- a/core/loadpoint_test.go
+++ b/core/loadpoint_test.go
@@ -527,10 +527,7 @@ func cacheExpecter(t *testing.T, lp *Loadpoint) (*util.Cache, func(key string, v
 		time.Sleep(100 * time.Millisecond) // wait for cache to catch up
 
 		p := cache.Get(key)
-		t.Logf("%s: %v", key, p.Val) // REMOVE
-		if p.Val != val {
-			t.Errorf("%s wanted: %v, got %v", key, val, p.Val)
-		}
+		assert.Equal(t, val, p.Val, key)
 	}
 	return cache, expect
 }
@@ -614,98 +611,6 @@ func TestChargedEnergyAtDisconnect(t *testing.T) {
 	ctrl.Finish()
 }
 
-// func TestTargetSoc(t *testing.T) {
-// 	ctrl := gomock.NewController(t)
-// 	vhc := api.NewMockVehicle(ctrl)
-
-// 	// TODO make vehicle settings mockable
-// 	// make vehicle settings discoverable
-// 	config.Vehicles().Add(config.NewStaticDevice[api.Vehicle](config.Named{}, vhc))
-
-// 	tc := []struct {
-// 		vehicle    api.Vehicle
-// 		limitSoc   int
-// 		vehicleSoc float64
-// 		res        bool
-// 	}{
-// 		{nil, 0, 0, false},     // never reached without vehicle
-// 		{nil, 0, 10, false},    // never reached without vehicle
-// 		{nil, 80, 0, false},    // never reached without vehicle
-// 		{nil, 80, 80, false},   // never reached without vehicle
-// 		{nil, 80, 100, false},  // never reached without vehicle
-// 		{vhc, 0, 0, false},     // target disabled
-// 		{vhc, 0, 10, false},    // target disabled
-// 		{vhc, 80, 0, false},    // target not reached
-// 		{vhc, 80, 80, true},    // target reached
-// 		{vhc, 80, 100, true},   // target reached
-// 		{vhc, 100, 100, false}, // target reached, let ev control deactivation
-// 	}
-
-// 	for _, tc := range tc {
-// 		t.Logf("%+v", tc)
-
-// 		lp := &Loadpoint{
-// 			vehicle:    tc.vehicle,
-// 			limitSoc:   tc.limitSoc,
-// 			vehicleSoc: tc.vehicleSoc,
-// 		}
-
-// 		if res := lp.limitSocReached(); tc.res != res {
-// 			t.Errorf("expected %v, got %v", tc.res, res)
-// 		}
-// 	}
-// }
-
-// func TestMinSoc(t *testing.T) {
-// 	ctrl := gomock.NewController(t)
-// 	vhc := api.NewMockVehicle(ctrl)
-
-// 	// TODO make vehicle settings mockable
-// 	// make vehicle settings discoverable
-// 	config.Vehicles().Add(config.NewStaticDevice[api.Vehicle](config.Named{}, vhc))
-
-// 	tc := []struct {
-// 		vehicle *api.MockVehicle
-// 		min     int
-// 		soc     float64
-// 		energy  float64
-// 		res     bool
-// 	}{
-// 		{nil, 0, 0, 0, false},    // never reached without vehicle
-// 		{nil, 0, 10, 0, false},   // never reached without vehicle
-// 		{nil, 80, 0, 0, false},   // never reached without vehicle
-// 		{nil, 80, 80, 0, false},  // never reached without vehicle
-// 		{nil, 80, 100, 0, false}, // never reached without vehicle
-// 		{vhc, 0, 0, 0, false},    // min disabled
-// 		{vhc, 0, 10, 0, false},   // min disabled
-// 		{vhc, 80, 0, 0, true},    // min not reached
-// 		{vhc, 80, 10, 0, true},   // min not reached
-// 		{vhc, 80, 0, 8.0, true},  // min energy not reached
-// 		{vhc, 80, 0, 9.0, false}, // min energy reached
-// 		{vhc, 80, 80, 0, false},  // min reached
-// 		{vhc, 80, 100, 0, false}, // min reached
-// 	}
-
-// 	for _, tc := range tc {
-// 		lp := &Loadpoint{
-// 			log: util.NewLogger("foo"),
-// 			Soc: SocConfig{
-// 				min: tc.min,
-// 			},
-// 			vehicleSoc:    tc.soc,
-// 			sessionEnergy: NewEnergyMetrics(),
-// 		}
-// 		lp.sessionEnergy.Update(tc.energy / 1e3)
-
-// 		if v := tc.vehicle; v != nil {
-// 			lp.vehicle = tc.vehicle // avoid assigning nil to interface
-// 			v.EXPECT().Capacity().Return(10.0).MaxTimes(1)
-// 		}
-
-// 		assert.Equal(t, tc.res, lp.minSocNotReached(), tc)
-// 	}
-// }
-
 func TestSocPoll(t *testing.T) {
 	clock := clock.NewMock()
 	tRefresh := pollInterval
@@ -787,9 +692,7 @@ func TestSocPoll(t *testing.T) {
 			lp.socUpdated = clock.Now()
 		}
 
-		if tc.res != res {
-			t.Errorf("expected %v, got %v", tc.res, res)
-		}
+		assert.Equal(t, tc.res, res)
 	}
 }
 
@@ -917,3 +820,71 @@ func TestGuardPublishOnDisableAbort(t *testing.T) {
 		clock.Add(time.Minute)
 	}
 }
+
+// test PV hysteresis after phase switch down, depending on remaining energy
+func TestPVHysteresisAfterPhaseSwitch(t *testing.T) {
+	const dt = time.Minute
+	type se struct {
+		site    float64
+		delay   time.Duration // test case delay since start
+		current float64
+	}
+	tc := []struct {
+		series []se
+	}{
+		// immediately disable when threshold met after phase switch
+		{[]se{
+			{1200, 0, minA},
+			{1200, 1, minA},
+			{1200, dt - 1, minA},
+			{1200, dt + 1, 0},
+		}},
+		// stay enabled when threshold not met after phase switch
+		{[]se{
+			{500, 0, minA},
+			{500, 1, minA},
+			{500, dt - 1, minA},
+			{500, dt + 1, minA},
+		}},
+	}
+
+	for _, tc := range tc {
+		t.Log(tc)
+
+		clck := clock.NewMock()
+		ctrl := gomock.NewController(t)
+
+		switcher := api.NewMockPhaseSwitcher(ctrl)
+		switcher.EXPECT().Phases1p3p(1)
+
+		charger := struct {
+			*api.MockCharger
+			*api.MockPhaseSwitcher
+		}{
+			api.NewMockCharger(ctrl), switcher,
+		}
+
+		Voltage = 100
+		lp := &Loadpoint{
+			log:        util.NewLogger("foo"),
+			clock:      clck,
+			charger:    charger,
+			minCurrent: minA,
+			maxCurrent: maxA,
+			Disable: ThresholdConfig{
+				Delay: dt,
+			},
+			status:  api.StatusC,
+			enabled: true,
+		}
+
+		start := clck.Now()
+
+		for step, se := range tc.series {
+			clck.Set(start.Add(se.delay))
+			assert.Equal(t, se.current, lp.pvMaxCurrent(api.ModePV, se.site, false, false), step)
+		}
+
+		ctrl.Finish()
+	}
+}