Skip to content

Commit

Permalink
Merge branch 'master' into smartEVSE
Browse files Browse the repository at this point in the history
  • Loading branch information
Adminius authored Jan 30, 2025
2 parents fb751a5 + 89cf8ce commit 4e5e855
Show file tree
Hide file tree
Showing 14 changed files with 458 additions and 16 deletions.
1 change: 1 addition & 0 deletions TeslaLogger/Car.cs
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ public bool Virtual_key
internal string FleetApiAddress = "";
public string _access_type;
public bool _virtual_key;
internal bool vehicle_location = true;

[MethodImpl(MethodImplOptions.Synchronized)]
internal TeslaAPIState GetTeslaAPIState() { return teslaAPIState; }
Expand Down
89 changes: 75 additions & 14 deletions TeslaLogger/ElectricityMeterEVCC.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ class ElectricityMeterEVCC : ElectricityMeterBase
{
string host;
string parameter;
string loadpointname;
string loadpointcarname;
internal string api_state;

Guid guid; // defaults to new Guid();
static WebClient client;
Expand All @@ -30,14 +31,19 @@ public ElectricityMeterEVCC(string host, string parameter)

if(parameter != null)
{
loadpointname = parameter;
loadpointcarname = parameter;
}
}

string GetCurrentData()
{
try
{
if (api_state != null)
{
return api_state;
}

string cacheKey = "evcc_" + guid.ToString();
object o = MemoryCache.Default.Get(cacheKey);

Expand Down Expand Up @@ -70,6 +76,39 @@ string GetCurrentData()
return "";
}

JToken getLoadPointJson(dynamic json)
{
JToken loadpoint = null;
// loadpointcarname can be vehicle title ("TestCar1", vehicle name ("tsla") or loadpoint name ("Wallbox1")
// Maybe vehicle name?
loadpoint = json.SelectToken($"$.result.loadpoints[?(@.vehicleName == '{loadpointcarname}')]");

if (loadpoint == null)
{
// it's not a vehicle name, maybe vehicle title?
foreach (var vehicle in json.result.vehicles)
{
string vehicleTitle = vehicle.Value.title;
string vehicleName = vehicle.Name;
if (vehicleTitle == loadpointcarname)
{
loadpoint = json.SelectToken($"$.result.loadpoints[?(@.vehicleName == '{vehicleName}')]");
continue;
}
}
// it's also not a vehicle title. Maybe loadpoint title?
if (loadpoint == null)
{

loadpoint = json.SelectToken($"$.result.loadpoints[?(@.title == '{loadpointcarname}')]");
if (loadpoint == null)
{
return null;
}
}
}
return loadpoint;
}

public override double? GetUtilityMeterReading_kWh()
{
Expand All @@ -86,18 +125,31 @@ string GetCurrentData()
if (!Tools.IsPropertyExist(jsonResult, "result"))
return null;

Dictionary<string, object> r1 = jsonResult["result"].ToObject<Dictionary<string, object>>();

if (r1.ContainsKey("gridEnergy"))
JToken grid = jsonResult.SelectToken($"$.result.grid");
if (grid != null)
{
double.TryParse(r1["gridEnergy"].ToString(), out double value);
Dictionary<string, object> r1 = grid.ToObject<Dictionary<string, object>>();

return value;
if (r1.ContainsKey("energy"))
{
double.TryParse(r1["energy"].ToString(), out double value);
return value;
}
}
else
{
return null;
Dictionary<string, object> r1 = jsonResult["result"].ToObject<Dictionary<string, object>>();

if (r1.ContainsKey("gridEnergy"))
{
double.TryParse(r1["gridEnergy"].ToString(), out double value);

return value;
}
}
return null;


}
catch (Exception ex)
{
Expand All @@ -122,9 +174,12 @@ string GetCurrentData()
if (!Tools.IsPropertyExist(jsonResult, "result"))
return null;

JToken acme = jsonResult.SelectToken($"$.result.loadpoints[?(@.title == '{loadpointname}')]");
JToken loadpoint = getLoadPointJson(jsonResult);

if (loadpoint == null)
return null;

Dictionary<string, object> r1 = acme.ToObject<Dictionary<string, object>>();
Dictionary<string, object> r1 = loadpoint.ToObject<Dictionary<string, object>>();

if (r1.ContainsKey("chargeTotalImport"))
{
Expand Down Expand Up @@ -160,9 +215,12 @@ string GetCurrentData()
if (!Tools.IsPropertyExist(jsonResult, "result"))
return null;

JToken acme = jsonResult.SelectToken($"$.result.loadpoints[?(@.title == '{loadpointname}')]");
JToken loadpoint = getLoadPointJson(jsonResult);

Dictionary<string, object> r1 = acme.ToObject<Dictionary<string, object>>();
if (loadpoint == null)
return null;

Dictionary<string, object> r1 = loadpoint.ToObject<Dictionary<string, object>>();

if (r1.ContainsKey("sessionPrice") && r1["sessionPrice"] != null)
{
Expand Down Expand Up @@ -200,9 +258,12 @@ string GetCurrentData()
if (!Tools.IsPropertyExist(jsonResult, "result"))
return null;

JToken acme = jsonResult.SelectToken($"$.result.loadpoints[?(@.title == '{loadpointname}')]");
JToken loadpoint = getLoadPointJson(jsonResult);

if (loadpoint == null)
return null;

Dictionary<string, object> r1 = acme.ToObject<Dictionary<string, object>>();
Dictionary<string, object> r1 = loadpoint.ToObject<Dictionary<string, object>>();

if (r1.ContainsKey("charging"))
{
Expand Down
4 changes: 2 additions & 2 deletions TeslaLogger/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
// Sie können alle Werte angeben oder Standardwerte für die Build- und Revisionsnummern verwenden,
// übernehmen, indem Sie "*" eingeben:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.62.12.0")]
[assembly: AssemblyFileVersion("1.62.12.0")]
[assembly: AssemblyVersion("1.62.13.0")]
[assembly: AssemblyFileVersion("1.62.13.0")]

[assembly: InternalsVisibleTo("UnitTestsTeslaloggerNET8")]
[assembly: InternalsVisibleTo("UnitTestsTeslalogger")]
58 changes: 58 additions & 0 deletions TeslaLogger/WebHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5677,9 +5677,67 @@ internal string Tesla_token
set
{
tesla_token = StringCipher.Decrypt(value);
if (car.FleetAPI)
{
try
{
var ok = CheckJWT(tesla_token, out bool vehicle_location, out bool offline_access);
if (ok)
{
car.Log("vehicle_location: " + vehicle_location);
car.Log("offline_access: " + offline_access);
car.vehicle_location = vehicle_location;
}
}
catch (Exception ex)
{
ex.ToExceptionless().Submit();
Logfile.Log(ex.ToString());
}
}
}
}

public static bool CheckJWT(string jwt, out bool vehicle_location, out bool offline_access)
{
vehicle_location = false;
offline_access = false;

if (jwt == "NULL")
return false;

try
{
var j = jwt.Split('.');
var pl = j[1].Replace('-', '+').Replace('_', '/');
switch (pl.Length % 4)
{
case 2: pl += "=="; break;
case 3: pl += "="; break;
}
var payload = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(pl));
dynamic d = JsonConvert.DeserializeObject(payload);
JArray scp = d["scp"];

List<string> scopes = scp.ToObject<List<string>>();

if (scopes.Contains("vehicle_location"))
vehicle_location = true;

if (scopes.Contains("offline_access"))
offline_access = true;

return true;
}
catch (Exception ex)
{
ex.ToExceptionless().Submit();
Logfile.Log(ex.ToString());
}

return false;
}

private void Log(string text)
{
car.Log(text);
Expand Down
4 changes: 4 additions & 0 deletions TeslaLogger/WebServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3272,14 +3272,18 @@ private static void Admin_GetAllCars(HttpListenerRequest request, HttpListenerRe

dt.Columns.Add("SupportedByFleetTelemetry");
dt.Columns.Add("inactive");
dt.Columns.Add("vehicle_location");

foreach (DataRow dr in dt.Rows)
{
try
{
Car c = Car.GetCarByID(Convert.ToInt32(dr["id"]));
if (c != null)
{
dr["SupportedByFleetTelemetry"] = c.SupportedByFleetTelemetry() ? 1 : 0;
dr["vehicle_location"] = c.vehicle_location;
}
else
dr["inactive"] = 1;
}
Expand Down
Binary file modified TeslaLogger/bin/TeslaLogger.exe
Binary file not shown.
4 changes: 4 additions & 0 deletions TeslaLogger/bin/changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# Version 1.62.13
- Tesla has introduced more granular control over data access: [LINK](https://developer.tesla.com/docs/fleet-api/announcements#2024-11-26-introducing-a-new-oauth-scope-vehicle-location)
Third-party apps can request permission to access location information. Starting in March 2025, no location information will be shared unless you grant the necessary permission.

# Version 1.62.12
- Fleet API: new signals: ExpectedEnergyPercentAtTripArrival and ExpectedEnergyPercentAtTripEnd

Expand Down
1 change: 1 addition & 0 deletions TeslaLogger/bin/language-de.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
INFO_important="Wichtige Info"
INFO_FLEETAPI="Tesla wird ab dem 01.01.2025 die offizielle Fleet API in ein Bezahlmodell umwandeln ({LINK1}). Aus diesem Grund bin ich gezwungen, auf ein Abomodell umzusteigen und monatlich eine geringe Gebühr zu verlangen, um meine Ausgaben für Tesla, Steuern und den Zahlungsdienstleister zu decken.<br>Gleichzeitig verpflichten mich Teslas AGBs ({LINK2}), die inoffizielle Owners API aufzugeben, da Tesla ansonsten rechtliche Schritte gegen mich einleiten könnte (§5.6.6 und §5.6.11). Bitte wechselt eure Fahrzeuge von der Owners API zur Fleet API, indem ihr eure Zugangsdaten erneut eingebt.<br><br>Für die älteren Model S/X (Baujahr vor 2021) gibt es jedoch keine Lösung für die Nutzung der Fleet API. Diese Fahrzeuge können weiterhin über die alte Owners API betrieben werden. Ich gehe davon aus, dass Tesla in diesen Fällen keine rechtlichen Schritte einleiten wird. Für diese Fahrzeuge könnt ihr dennoch ein Abo abschließen, was jedoch eher als freiwillige Spende zu verstehen ist, da ich dafür keine Gebühren an Tesla zahlen muss.<br><br>Da Tesla mit der Fleet API sowohl ihre Server entlasten als auch Einnahmen generieren möchte, besteht die Möglichkeit, dass die inoffizielle Owners API am 01.01.2025 abgeschaltet wird. Wir hoffen, dass die älteren Model S/X davon nicht betroffen sein werden."
INFO_RESTRICTED="Ohne ein aktives Abonnement können wir die Kosten für deinen Tesla leider nicht tragen. Daher sehen wir uns gezwungen, die Funktionalität und insbesondere die Übermittlungsfrequenz der Positionsdaten erheblich einzuschränken."
INFO_VEHICLE_LOCATION="Tesla hat eine feinere Kontrolle des Datenzugriffs eingeführt: {LINK1} <br>Drittanbieter APPs können das Recht der Standortinformationen beantragen. Du hast das Rechnt noch nicht erteilt. Ab März 2025 werden keine Standortinformationen mehr verschickt, wenn du das Recht nicht erteilst. Bitte melde dich nochmal an: {LINK2}."

#Allgemein
Delete=Löschen
Expand Down
1 change: 1 addition & 0 deletions TeslaLogger/bin/language-en.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
INFO_important="Important Info"
INFO_FLEETAPI="Starting January 1, 2025, Tesla will transition the official Fleet API to a paid model ({LINK1}). As a result, I am forced to switch to a subscription model and charge a small monthly fee to cover my expenses for Tesla, taxes, and the payment service provider.<br><br>At the same time, Tesla’s Terms of Service ({LINK2}) compel me to discontinue the use of the unofficial Owners API; otherwise, they could take legal action against me (§5.6.6 and §5.6.11). Please migrate your vehicles from the Owners API to the Fleet API by re-entering your login credentials.<br><br>Unfortunately, for older Model S/X vehicles (manufactured before 2021), there is no solution to use the Fleet API. These vehicles can continue to operate via the old Owners API, and I don’t expect Tesla to take any legal action in such cases. However, you can still choose to subscribe for these vehicles as a form of donation, since I don’t have to pay Tesla for their usage.<br><br>Since Tesla aims to reduce server load and generate revenue with the Fleet API, it is likely that the unofficial Owners API will be shut down on January 1, 2025. We hope that older Model S/X vehicles will not be affected by this change."
INFO_RESTRICTED="Without an active subscription, we unfortunately cannot cover the costs for your Tesla. Therefore, we are forced to significantly limit the functionality and, in particular, the frequency of position updates."
INFO_VEHICLE_LOCATION="Tesla has introduced more granular control over data access: {LINK1} <br>Third-party apps can request permission to access location information. You have not yet granted this permission. Starting in March 2025, no location information will be shared unless you grant the necessary permission. Please log in again: {LINK2}."

#Akku Trips.json
Akku Trips=Battery Usage (Driving)
Expand Down
21 changes: 21 additions & 0 deletions TeslaLogger/www/admin/info.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ function ShowInfo()
session_start();
}
global $fleetapiinfo;
global $vehicle_location;
global $carVIN;

$fileinfofleetapi = "/tmp/fleetapiinfo".date("Y-m-d").".txt";
$filevehicle_location = "/tmp/vehicle_location-$carid-".date("Y-m-d").".txt";
$prefix = "/etc/teslalogger/";
if (isDocker())
$prefix = "/tmp/";
Expand All @@ -34,6 +37,24 @@ function ShowInfo()

<?php
}
else if ($vehicle_location !== true && !file_exists($filevehicle_location))
{
file_put_contents($filevehicle_location, '');
$passwordlink = "password_fleet.php?id=$carid&vin=$carVIN";
$tinfo = get_text("INFO_VEHICLE_LOCATION");
$tinfo=str_replace("{LINK1}", "<a href='https://developer.tesla.com/docs/fleet-api/announcements#2024-11-26-introducing-a-new-oauth-scope-vehicle-location' target='_blank'>LINK</a>", $tinfo);
$tinfo=str_replace("{LINK2}", "<a href='$passwordlink' target='_blank'>LINK</a>", $tinfo);
?>

$("#InfoText").html("<h1><?php t("INFO_important"); ?></h1><p><?php echo($tinfo); ?></p>");
$(".HeaderT").show();
$("#PositiveButton").text("<?php t("OK"); ?>");
$("#PositiveButton").click(function(){location.reload();});
$("#NegativeButton").text("<?php t("Credentials"); ?>");
$("#NegativeButton").click(function(){window.location.href='<?php echo $passwordlink; ?>';});

<?php
}
else if (file_exists($prefix."cmd_gosleep_$carid.txt"))
{?>
$("#InfoText").html("<h1><?php t("TextSuspendTeslalogger"); ?></h1>");
Expand Down
2 changes: 2 additions & 0 deletions TeslaLogger/www/admin/menu.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ function menu($title, $prefix = "")
global $carVIN;
global $fleetapiinfo;
global $car_inactive;
global $vehicle_location;

$current_carid = $_SESSION["carid"];
if (!isset($current_carid))
Expand All @@ -52,6 +53,7 @@ function menu($title, $prefix = "")
$car = $v->{"model_name"};
$carVIN = $v->{"vin"};
$car_inactive = $v->{"inactive"};
$vehicle_location = $v->{"vehicle_location"};

if (strlen($display_name) == 0)
$display_name = "Car ".$v->{"id"};
Expand Down
17 changes: 17 additions & 0 deletions UnitTestsTeslalogger/UnitTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1470,5 +1470,22 @@ public void IsInUnitTest()
var inTest = Tools.IsUnitTest();
Assert.IsTrue(inTest, "Should detect that we are in unit test");
}

[TestMethod]
public void TestJWT1()
{
string token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InFEc3NoM2FTV0cyT05YTTdLMzFWV0VVRW5BNCJ9.eyJpc3MiOiJodHRwczovL2F1dGgudGVzbGEuY29tL29hdXRoMi92My9udHMiLCJhenAiOiI5MWQ0ZGEyYTM0NjgtNGI4MS1hZGJhLWQyYjI3MWJlZGZhMiIsInN1YiI6ImVhZTU4YzE4LWRiODEtNGZhZC05NDU2LTJiZDJhOWYyZjg0MCIsImF1ZCI6WyJodHRwczovL2ZsZWV0LWFwaS5wcmQubmEudm4uY2xvdWQudGVzbGEuY29tIiwiaHR0cHM6Ly9mbGVldC1hcGkucHJkLmV1LnZuLmNsb3VkLnRlc2xhLmNvbSIsImh0dHBzOi8vYXV0aC50ZXNsYS5jb20vb2F1dGgyL3YzL3VzZXJpbmZvIl0sInNjcCI6WyJvZmZsaW5lX2FjY2VzcyIsIm9wZW5pZCIsInVzZXJfZGF0YSIsInZlaGljbGVfZGV2aWNlX2RhdGEiLCJ2ZWhpY2xlX2NtZHMiLCJ2ZWhpY2xlX2NoYXJnaW5nX2NtZHMiXSwiYW1yIjpbInB3ZCIsIm1mYSIsIm90cCJdLCJleHAiOjE3MzgwNzA4ODQsImlhdCI6MTczODA0MjA4NCwib3VfY29kZSI6IkVVIiwibG9jYWxlIjoiZGEtREsiLCJhY2NvdW50X3R5cGUiOiJidXNpbmVzcyIsIm9wZW5fc291cmNlIjpudWxsLCJhY2NvdW50X2lkIjoiNGZjZTM3OWMtMmU0NS00Y2Y0LTkwYmMtNDZhMzE5MTZkZDE0IiwiYXV0aF90aW1lIjoxNzM4MDQyMDg0LCJub25jZSI6bnVsbH0.XXXXXXXXXXXXXX";
WebHelper.CheckJWT(token, out bool vehicle_location, out bool offline_access);
Assert.IsFalse(vehicle_location);
Assert.IsTrue(offline_access);
}
[TestMethod]
public void TestJWT2()
{
string token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InFEc3NoM2FTV0cyT05YTTdLMzFWV0VVRW5BNCJ9.eyJpc3MiOiJodHRwczovL2F1dGgudGVzbGEuY29tL29hdXRoMi92My9udHMiLCJhenAiOiI5MWQ0ZGEyYTM0NjgtNGI4MS1hZGJhLWQyYjI3MWJlZGZhMiIsInN1YiI6IjQ0ZjBjYTA5LTk0OGQtNDgxNy1hMWQ4LWMzNjM0YWY3MmNhYiIsImF1ZCI6WyJodHRwczovL2ZsZWV0LWFwaS5wcmQubmEudm4uY2xvdWQudGVzbGEuY29tIiwiaHR0cHM6Ly9mbGVldC1hcGkucHJkLmV1LnZuLmNsb3VkLnRlc2xhLmNvbSIsImh0dHBzOi8vYXV0aC50ZXNsYS5jb20vb2F1dGgyL3YzL3VzZXJpbmZvIl0sInNjcCI6WyJ1c2VyX2RhdGEiLCJ2ZWhpY2xlX2RldmljZV9kYXRhIiwidmVoaWNsZV9sb2NhdGlvbiIsInZlaGljbGVfY21kcyIsInZlaGljbGVfY2hhcmdpbmdfY21kcyIsIm9mZmxpbmVfYWNjZXNzIiwib3BlbmlkIl0sImFtciI6WyJwd2QiXSwiZXhwIjoxNzM4MDgxMzIzLCJpYXQiOjE3MzgwNTI1MjMsIm91X2NvZGUiOiJFVSIsImxvY2FsZSI6ImRlLURFIiwiYWNjb3VudF90eXBlIjoiYnVzaW5lc3MiLCJvcGVuX3NvdXJjZSI6bnVsbCwiYWNjb3VudF9pZCI6IjRmY2UzNzljLTJlNDUtNGNmNC05MGJjLTQ2YTMxOTE2ZGQxNCIsImF1dGhfdGltZSI6MTczODA1MjUyMywibm9uY2UiOm51bGx9.XXXXXXXXXX";
WebHelper.CheckJWT(token, out bool vehicle_location, out bool offline_access);
Assert.IsTrue(vehicle_location);
Assert.IsTrue(offline_access);
}
}
}
Loading

0 comments on commit 4e5e855

Please sign in to comment.