From 8ef06e9ad8a7445af40b2017264fe08ad4becf1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Sun, 17 Dec 2023 15:19:51 +0100 Subject: [PATCH 01/50] fix(TeslaFleetApiService): increase counter on each api call --- TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs b/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs index a3cb3dfa8..41fddafe0 100644 --- a/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs +++ b/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs @@ -4,11 +4,9 @@ using System.Net; using System.Net.Http.Headers; using TeslaSolarCharger.Model.Contracts; -using TeslaSolarCharger.Model.Entities.TeslaMate; using TeslaSolarCharger.Model.Entities.TeslaSolarCharger; using TeslaSolarCharger.Model.Enums; using TeslaSolarCharger.Server.Contracts; -using TeslaSolarCharger.Server.Dtos; using TeslaSolarCharger.Server.Dtos.TeslaFleetApi; using TeslaSolarCharger.Server.Dtos.TscBackend; using TeslaSolarCharger.Server.Services.ApiServices.Contracts; @@ -16,7 +14,6 @@ using TeslaSolarCharger.Shared.Contracts; using TeslaSolarCharger.Shared.Dtos; using TeslaSolarCharger.Shared.Dtos.Contracts; -using TeslaSolarCharger.Shared.Dtos.Settings; using TeslaSolarCharger.Shared.Enums; using TeslaSolarCharger.SharedBackend.Contracts; using Car = TeslaSolarCharger.Shared.Dtos.Settings.Car; @@ -246,6 +243,7 @@ private async Task WakeUpCarIfNeeded(int carId, CarStateEnum? carState) _ => throw new NotImplementedException($"Region {accessToken.Region} is not implemented."), }; var requestUri = $"https://fleet-api.prd.{regionCode}.vn.cloud.tesla.com/api/1/vehicles/{id}/{commandName}"; + _settings.TeslaApiRequestCounter++; var response = await httpClient.PostAsync(requestUri, content).ConfigureAwait(false); var responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); if (!response.IsSuccessStatusCode) From dd327dd57af61e2960796e9113deabd5e7cd9753 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Sun, 17 Dec 2023 15:44:13 +0100 Subject: [PATCH 02/50] feat(chore): update all projects to .NET8 --- Plugins.Modbus/Plugins.Modbus.csproj | 2 +- Plugins.SmaEnergymeter/Plugins.SmaEnergymeter.csproj | 2 +- Plugins.SolarEdge/Plugins.SolarEdge.csproj | 2 +- Plugins.Solax/Plugins.Solax.csproj | 2 +- .../TeslaSolarCharger.GridPriceProvider.csproj | 2 +- TeslaSolarCharger.Model/TeslaSolarCharger.Model.csproj | 2 +- .../TeslaSolarCharger.SharedBackend.csproj | 2 +- TeslaSolarCharger.Tests/TeslaSolarCharger.Tests.csproj | 2 +- TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj | 2 +- TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj | 2 +- TeslaSolarCharger/Shared/TeslaSolarCharger.Shared.csproj | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Plugins.Modbus/Plugins.Modbus.csproj b/Plugins.Modbus/Plugins.Modbus.csproj index 2c1f728af..be6904ca2 100644 --- a/Plugins.Modbus/Plugins.Modbus.csproj +++ b/Plugins.Modbus/Plugins.Modbus.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 enable enable Linux diff --git a/Plugins.SmaEnergymeter/Plugins.SmaEnergymeter.csproj b/Plugins.SmaEnergymeter/Plugins.SmaEnergymeter.csproj index 2c7f97774..09eea2409 100644 --- a/Plugins.SmaEnergymeter/Plugins.SmaEnergymeter.csproj +++ b/Plugins.SmaEnergymeter/Plugins.SmaEnergymeter.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 enable enable 679cf201-5d19-45a7-8272-b515fe89525f diff --git a/Plugins.SolarEdge/Plugins.SolarEdge.csproj b/Plugins.SolarEdge/Plugins.SolarEdge.csproj index 1eedf85e5..227b54ed6 100644 --- a/Plugins.SolarEdge/Plugins.SolarEdge.csproj +++ b/Plugins.SolarEdge/Plugins.SolarEdge.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 enable enable Linux diff --git a/Plugins.Solax/Plugins.Solax.csproj b/Plugins.Solax/Plugins.Solax.csproj index c88c97df6..6b32705fc 100644 --- a/Plugins.Solax/Plugins.Solax.csproj +++ b/Plugins.Solax/Plugins.Solax.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 enable enable Linux diff --git a/TeslaSolarCharger.GridPriceProvider/TeslaSolarCharger.GridPriceProvider.csproj b/TeslaSolarCharger.GridPriceProvider/TeslaSolarCharger.GridPriceProvider.csproj index 514075da5..56505d283 100644 --- a/TeslaSolarCharger.GridPriceProvider/TeslaSolarCharger.GridPriceProvider.csproj +++ b/TeslaSolarCharger.GridPriceProvider/TeslaSolarCharger.GridPriceProvider.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 enable enable diff --git a/TeslaSolarCharger.Model/TeslaSolarCharger.Model.csproj b/TeslaSolarCharger.Model/TeslaSolarCharger.Model.csproj index 59adf1234..5724695a5 100644 --- a/TeslaSolarCharger.Model/TeslaSolarCharger.Model.csproj +++ b/TeslaSolarCharger.Model/TeslaSolarCharger.Model.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 enable enable diff --git a/TeslaSolarCharger.SharedBackend/TeslaSolarCharger.SharedBackend.csproj b/TeslaSolarCharger.SharedBackend/TeslaSolarCharger.SharedBackend.csproj index 4cc30127c..a58ef2b53 100644 --- a/TeslaSolarCharger.SharedBackend/TeslaSolarCharger.SharedBackend.csproj +++ b/TeslaSolarCharger.SharedBackend/TeslaSolarCharger.SharedBackend.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 enable enable diff --git a/TeslaSolarCharger.Tests/TeslaSolarCharger.Tests.csproj b/TeslaSolarCharger.Tests/TeslaSolarCharger.Tests.csproj index 2a78f998f..a14a3e1ab 100644 --- a/TeslaSolarCharger.Tests/TeslaSolarCharger.Tests.csproj +++ b/TeslaSolarCharger.Tests/TeslaSolarCharger.Tests.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 enable false diff --git a/TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj b/TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj index bc514b61c..ea9d44e89 100644 --- a/TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj +++ b/TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 enable enable service-worker-assets.js diff --git a/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj b/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj index 9185b9f5b..77a1fc3a1 100644 --- a/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj +++ b/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 enable enable Linux diff --git a/TeslaSolarCharger/Shared/TeslaSolarCharger.Shared.csproj b/TeslaSolarCharger/Shared/TeslaSolarCharger.Shared.csproj index 7bd51bc2a..6ecc3fffc 100644 --- a/TeslaSolarCharger/Shared/TeslaSolarCharger.Shared.csproj +++ b/TeslaSolarCharger/Shared/TeslaSolarCharger.Shared.csproj @@ -1,7 +1,7 @@ - net7.0 + net8.0 enable enable From fa214551a05cef2211dd980d5ce9fee583ad85ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Sun, 17 Dec 2023 15:51:39 +0100 Subject: [PATCH 03/50] fix(Docker): update Dockerfiles to .NET8 --- Plugins.Modbus/Dockerfile | 4 ++-- Plugins.SmaEnergymeter/Dockerfile | 4 ++-- Plugins.SolarEdge/Dockerfile | 4 ++-- Plugins.Solax/Dockerfile | 4 ++-- TeslaSolarCharger/Server/Dockerfile | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Plugins.Modbus/Dockerfile b/Plugins.Modbus/Dockerfile index 6f8e641bd..7ab3e7f08 100644 --- a/Plugins.Modbus/Dockerfile +++ b/Plugins.Modbus/Dockerfile @@ -1,11 +1,11 @@ #See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. -FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base WORKDIR /app EXPOSE 80 EXPOSE 443 -FROM mcr.microsoft.com/dotnet/sdk:7.0-bullseye-slim-amd64 AS build +FROM mcr.microsoft.com/dotnet/sdk:8.0-bullseye-slim-amd64 AS build WORKDIR /src COPY ["Plugins.Modbus/Plugins.Modbus.csproj", "Plugins.Modbus/"] RUN dotnet restore "Plugins.Modbus/Plugins.Modbus.csproj" diff --git a/Plugins.SmaEnergymeter/Dockerfile b/Plugins.SmaEnergymeter/Dockerfile index 970ccc276..d50e64538 100644 --- a/Plugins.SmaEnergymeter/Dockerfile +++ b/Plugins.SmaEnergymeter/Dockerfile @@ -1,11 +1,11 @@ #See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. -FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base WORKDIR /app EXPOSE 80 EXPOSE 443 -FROM mcr.microsoft.com/dotnet/sdk:7.0-bullseye-slim-amd64 AS build +FROM mcr.microsoft.com/dotnet/sdk:8.0-bullseye-slim-amd64 AS build WORKDIR /src COPY ["Plugins.SmaEnergymeter/Plugins.SmaEnergymeter.csproj", "Plugins.SmaEnergymeter/"] RUN dotnet restore "Plugins.SmaEnergymeter/Plugins.SmaEnergymeter.csproj" diff --git a/Plugins.SolarEdge/Dockerfile b/Plugins.SolarEdge/Dockerfile index 182196077..6d5531187 100644 --- a/Plugins.SolarEdge/Dockerfile +++ b/Plugins.SolarEdge/Dockerfile @@ -1,11 +1,11 @@ #See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. -FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base WORKDIR /app EXPOSE 80 EXPOSE 443 -FROM mcr.microsoft.com/dotnet/sdk:7.0-bullseye-slim-amd64 AS build +FROM mcr.microsoft.com/dotnet/sdk:8.0-bullseye-slim-amd64 AS build WORKDIR /src COPY ["Plugins.SolarEdge/Plugins.SolarEdge.csproj", "Plugins.SolarEdge/"] RUN dotnet restore "Plugins.SolarEdge/Plugins.SolarEdge.csproj" diff --git a/Plugins.Solax/Dockerfile b/Plugins.Solax/Dockerfile index 5d818aea1..d9f6cd344 100644 --- a/Plugins.Solax/Dockerfile +++ b/Plugins.Solax/Dockerfile @@ -1,10 +1,10 @@ #See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. -FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base WORKDIR /app EXPOSE 80 -FROM mcr.microsoft.com/dotnet/sdk:7.0-bullseye-slim-amd64 AS build +FROM mcr.microsoft.com/dotnet/sdk:8.0-bullseye-slim-amd64 AS build WORKDIR /src COPY ["Plugins.Solax/Plugins.Solax.csproj", "Plugins.Solax/"] RUN dotnet restore "Plugins.Solax/Plugins.Solax.csproj" diff --git a/TeslaSolarCharger/Server/Dockerfile b/TeslaSolarCharger/Server/Dockerfile index 1af398299..157b68105 100644 --- a/TeslaSolarCharger/Server/Dockerfile +++ b/TeslaSolarCharger/Server/Dockerfile @@ -1,11 +1,11 @@ #See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. -FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base ARG MINVERVERSIONOVERRIDE WORKDIR /app EXPOSE 80 -FROM mcr.microsoft.com/dotnet/sdk:7.0-bullseye-slim-amd64 AS build +FROM mcr.microsoft.com/dotnet/sdk:8.0-bullseye-slim-amd64 AS build ARG MINVERVERSIONOVERRIDE WORKDIR /src COPY ["TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj", "TeslaSolarCharger/Server/"] From 28aa79d531ddfd2458653f2c76a2c7a59e392999 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Sun, 17 Dec 2023 15:54:32 +0100 Subject: [PATCH 04/50] fix(Docker): update image tags --- Plugins.Modbus/Dockerfile | 2 +- Plugins.SmaEnergymeter/Dockerfile | 2 +- Plugins.SolarEdge/Dockerfile | 2 +- Plugins.Solax/Dockerfile | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Plugins.Modbus/Dockerfile b/Plugins.Modbus/Dockerfile index 7ab3e7f08..b9842760a 100644 --- a/Plugins.Modbus/Dockerfile +++ b/Plugins.Modbus/Dockerfile @@ -5,7 +5,7 @@ WORKDIR /app EXPOSE 80 EXPOSE 443 -FROM mcr.microsoft.com/dotnet/sdk:8.0-bullseye-slim-amd64 AS build +FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim-amd64 AS build WORKDIR /src COPY ["Plugins.Modbus/Plugins.Modbus.csproj", "Plugins.Modbus/"] RUN dotnet restore "Plugins.Modbus/Plugins.Modbus.csproj" diff --git a/Plugins.SmaEnergymeter/Dockerfile b/Plugins.SmaEnergymeter/Dockerfile index d50e64538..8b41da982 100644 --- a/Plugins.SmaEnergymeter/Dockerfile +++ b/Plugins.SmaEnergymeter/Dockerfile @@ -5,7 +5,7 @@ WORKDIR /app EXPOSE 80 EXPOSE 443 -FROM mcr.microsoft.com/dotnet/sdk:8.0-bullseye-slim-amd64 AS build +FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim-amd64 AS build WORKDIR /src COPY ["Plugins.SmaEnergymeter/Plugins.SmaEnergymeter.csproj", "Plugins.SmaEnergymeter/"] RUN dotnet restore "Plugins.SmaEnergymeter/Plugins.SmaEnergymeter.csproj" diff --git a/Plugins.SolarEdge/Dockerfile b/Plugins.SolarEdge/Dockerfile index 6d5531187..7fcc12ace 100644 --- a/Plugins.SolarEdge/Dockerfile +++ b/Plugins.SolarEdge/Dockerfile @@ -5,7 +5,7 @@ WORKDIR /app EXPOSE 80 EXPOSE 443 -FROM mcr.microsoft.com/dotnet/sdk:8.0-bullseye-slim-amd64 AS build +FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim-amd64 AS build WORKDIR /src COPY ["Plugins.SolarEdge/Plugins.SolarEdge.csproj", "Plugins.SolarEdge/"] RUN dotnet restore "Plugins.SolarEdge/Plugins.SolarEdge.csproj" diff --git a/Plugins.Solax/Dockerfile b/Plugins.Solax/Dockerfile index d9f6cd344..c88a1bbf8 100644 --- a/Plugins.Solax/Dockerfile +++ b/Plugins.Solax/Dockerfile @@ -4,7 +4,7 @@ FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base WORKDIR /app EXPOSE 80 -FROM mcr.microsoft.com/dotnet/sdk:8.0-bullseye-slim-amd64 AS build +FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim-amd64 AS build WORKDIR /src COPY ["Plugins.Solax/Plugins.Solax.csproj", "Plugins.Solax/"] RUN dotnet restore "Plugins.Solax/Plugins.Solax.csproj" From d73e45c661e76ff0f4ba1bee1992a929e6941bfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Sun, 17 Dec 2023 15:57:07 +0100 Subject: [PATCH 05/50] fix(CICD): update .NET SD to v8 --- .github/workflows/alphaRelease.yml | 2 +- .github/workflows/edgeRelease.yml | 2 +- .github/workflows/versionRelease.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/alphaRelease.yml b/.github/workflows/alphaRelease.yml index 4e531e9a1..58fcf48c3 100644 --- a/.github/workflows/alphaRelease.yml +++ b/.github/workflows/alphaRelease.yml @@ -18,7 +18,7 @@ jobs: - name: Setup dotnet uses: actions/setup-dotnet@v2 with: - dotnet-version: '7.0.x' + dotnet-version: '8.0.x' - name: Install minver CLI run: dotnet tool install --tool-path ./minver minver-cli --version 4.3.0 diff --git a/.github/workflows/edgeRelease.yml b/.github/workflows/edgeRelease.yml index 266e3d839..e26477d01 100644 --- a/.github/workflows/edgeRelease.yml +++ b/.github/workflows/edgeRelease.yml @@ -23,7 +23,7 @@ jobs: - name: Setup dotnet uses: actions/setup-dotnet@v2 with: - dotnet-version: '7.0.x' + dotnet-version: '8.0.x' - name: Install minver CLI run: dotnet tool install --tool-path ./minver minver-cli --version 4.3.0 diff --git a/.github/workflows/versionRelease.yml b/.github/workflows/versionRelease.yml index 8af08abcf..942843485 100644 --- a/.github/workflows/versionRelease.yml +++ b/.github/workflows/versionRelease.yml @@ -32,7 +32,7 @@ jobs: - name: Setup dotnet uses: actions/setup-dotnet@v2 with: - dotnet-version: '7.0.x' + dotnet-version: '8.0.x' - name: Install minver CLI run: dotnet tool install --tool-path ./minver minver-cli --version 4.3.0 From 40116924c445c3815d0858994c5d4dd275afe327 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Sun, 17 Dec 2023 16:13:28 +0100 Subject: [PATCH 06/50] fix(Dockerfile): update TSC Dockerfile tag --- TeslaSolarCharger/Server/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TeslaSolarCharger/Server/Dockerfile b/TeslaSolarCharger/Server/Dockerfile index 157b68105..d6fcc74ea 100644 --- a/TeslaSolarCharger/Server/Dockerfile +++ b/TeslaSolarCharger/Server/Dockerfile @@ -5,7 +5,7 @@ ARG MINVERVERSIONOVERRIDE WORKDIR /app EXPOSE 80 -FROM mcr.microsoft.com/dotnet/sdk:8.0-bullseye-slim-amd64 AS build +FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim-amd64 AS build ARG MINVERVERSIONOVERRIDE WORKDIR /src COPY ["TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj", "TeslaSolarCharger/Server/"] From 7af86ebc7f6f0a7a4fbce088ffad88f4166dcecb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Sun, 17 Dec 2023 16:42:51 +0100 Subject: [PATCH 07/50] feat(chore): update dependecies --- Plugins.Modbus/Plugins.Modbus.csproj | 6 +++--- .../Plugins.SmaEnergymeter.csproj | 8 ++++---- Plugins.SolarEdge/Plugins.SolarEdge.csproj | 6 +++--- Plugins.Solax/Plugins.Solax.csproj | 8 ++++---- ...TeslaSolarCharger.GridPriceProvider.csproj | 20 +++++++++---------- .../TeslaSolarCharger.Model.csproj | 8 ++++---- .../TeslaSolarCharger.Tests.csproj | 12 +++++------ .../Client/TeslaSolarCharger.Client.csproj | 4 ++-- .../Server/TeslaSolarCharger.Server.csproj | 20 +++++++++---------- .../Shared/TeslaSolarCharger.Shared.csproj | 8 ++++---- 10 files changed, 50 insertions(+), 50 deletions(-) diff --git a/Plugins.Modbus/Plugins.Modbus.csproj b/Plugins.Modbus/Plugins.Modbus.csproj index be6904ca2..abac59d0b 100644 --- a/Plugins.Modbus/Plugins.Modbus.csproj +++ b/Plugins.Modbus/Plugins.Modbus.csproj @@ -11,9 +11,9 @@ - - - + + + diff --git a/Plugins.SmaEnergymeter/Plugins.SmaEnergymeter.csproj b/Plugins.SmaEnergymeter/Plugins.SmaEnergymeter.csproj index 09eea2409..d89802d80 100644 --- a/Plugins.SmaEnergymeter/Plugins.SmaEnergymeter.csproj +++ b/Plugins.SmaEnergymeter/Plugins.SmaEnergymeter.csproj @@ -11,10 +11,10 @@ - - - - + + + + diff --git a/Plugins.SolarEdge/Plugins.SolarEdge.csproj b/Plugins.SolarEdge/Plugins.SolarEdge.csproj index 227b54ed6..954730290 100644 --- a/Plugins.SolarEdge/Plugins.SolarEdge.csproj +++ b/Plugins.SolarEdge/Plugins.SolarEdge.csproj @@ -11,9 +11,9 @@ - - - + + + diff --git a/Plugins.Solax/Plugins.Solax.csproj b/Plugins.Solax/Plugins.Solax.csproj index 6b32705fc..9e8c822b6 100644 --- a/Plugins.Solax/Plugins.Solax.csproj +++ b/Plugins.Solax/Plugins.Solax.csproj @@ -8,11 +8,11 @@ - + - - - + + + diff --git a/TeslaSolarCharger.GridPriceProvider/TeslaSolarCharger.GridPriceProvider.csproj b/TeslaSolarCharger.GridPriceProvider/TeslaSolarCharger.GridPriceProvider.csproj index 56505d283..555bf694c 100644 --- a/TeslaSolarCharger.GridPriceProvider/TeslaSolarCharger.GridPriceProvider.csproj +++ b/TeslaSolarCharger.GridPriceProvider/TeslaSolarCharger.GridPriceProvider.csproj @@ -7,16 +7,16 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/TeslaSolarCharger.Model/TeslaSolarCharger.Model.csproj b/TeslaSolarCharger.Model/TeslaSolarCharger.Model.csproj index 5724695a5..e34d61b58 100644 --- a/TeslaSolarCharger.Model/TeslaSolarCharger.Model.csproj +++ b/TeslaSolarCharger.Model/TeslaSolarCharger.Model.csproj @@ -7,16 +7,16 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/TeslaSolarCharger.Tests/TeslaSolarCharger.Tests.csproj b/TeslaSolarCharger.Tests/TeslaSolarCharger.Tests.csproj index a14a3e1ab..af730df01 100644 --- a/TeslaSolarCharger.Tests/TeslaSolarCharger.Tests.csproj +++ b/TeslaSolarCharger.Tests/TeslaSolarCharger.Tests.csproj @@ -12,14 +12,14 @@ - - - + + + - + - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj b/TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj index ea9d44e89..fe93edbd7 100644 --- a/TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj +++ b/TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj @@ -18,8 +18,8 @@ - - + + diff --git a/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj b/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj index 77a1fc3a1..0ecb83187 100644 --- a/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj +++ b/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj @@ -34,25 +34,25 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - - - - - + + + + + diff --git a/TeslaSolarCharger/Shared/TeslaSolarCharger.Shared.csproj b/TeslaSolarCharger/Shared/TeslaSolarCharger.Shared.csproj index 6ecc3fffc..141a2ab5d 100644 --- a/TeslaSolarCharger/Shared/TeslaSolarCharger.Shared.csproj +++ b/TeslaSolarCharger/Shared/TeslaSolarCharger.Shared.csproj @@ -16,10 +16,10 @@ - - - + + + - + From f2383d36bb8e57d40f907d5f049b4e50c84a56dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Thu, 21 Dec 2023 17:05:40 +0100 Subject: [PATCH 08/50] fix(modbusplugin) do not expose port 443 --- Plugins.Modbus/Dockerfile | 1 - 1 file changed, 1 deletion(-) diff --git a/Plugins.Modbus/Dockerfile b/Plugins.Modbus/Dockerfile index b9842760a..c4e616b6a 100644 --- a/Plugins.Modbus/Dockerfile +++ b/Plugins.Modbus/Dockerfile @@ -3,7 +3,6 @@ FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base WORKDIR /app EXPOSE 80 -EXPOSE 443 FROM mcr.microsoft.com/dotnet/sdk:8.0-bookworm-slim-amd64 AS build WORKDIR /src From 2ca3249903cced405753bcf47ee40b708b6991b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Thu, 21 Dec 2023 17:14:13 +0100 Subject: [PATCH 09/50] fix(ModbusPlugin): update asp Port --- Plugins.Modbus/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Plugins.Modbus/Dockerfile b/Plugins.Modbus/Dockerfile index c4e616b6a..b06e9b6e9 100644 --- a/Plugins.Modbus/Dockerfile +++ b/Plugins.Modbus/Dockerfile @@ -17,5 +17,6 @@ RUN dotnet publish "Plugins.Modbus.csproj" -c Release -o /app/publish FROM base AS final WORKDIR /app +ENV ASPNETCORE_URLS=http://+:80 COPY --from=publish /app/publish . ENTRYPOINT ["dotnet", "Plugins.Modbus.dll"] \ No newline at end of file From bb8603c8709286bc1abfc6037b8e57896ed334ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Thu, 21 Dec 2023 17:18:31 +0100 Subject: [PATCH 10/50] feat(chore): update ASP Ports in Dockerfiles --- Plugins.SolarEdge/Dockerfile | 1 + Plugins.Solax/Dockerfile | 1 + TeslaSolarCharger/Server/Dockerfile | 1 + 3 files changed, 3 insertions(+) diff --git a/Plugins.SolarEdge/Dockerfile b/Plugins.SolarEdge/Dockerfile index 7fcc12ace..717cafb3f 100644 --- a/Plugins.SolarEdge/Dockerfile +++ b/Plugins.SolarEdge/Dockerfile @@ -18,5 +18,6 @@ RUN dotnet publish "Plugins.SolarEdge.csproj" -c Release -o /app/publish FROM base AS final WORKDIR /app +ENV ASPNETCORE_URLS=http://+:80 COPY --from=publish /app/publish . ENTRYPOINT ["dotnet", "Plugins.SolarEdge.dll"] \ No newline at end of file diff --git a/Plugins.Solax/Dockerfile b/Plugins.Solax/Dockerfile index c88a1bbf8..8a1bad01c 100644 --- a/Plugins.Solax/Dockerfile +++ b/Plugins.Solax/Dockerfile @@ -17,5 +17,6 @@ RUN dotnet publish "Plugins.Solax.csproj" -c Release -o /app/publish /p:UseAppHo FROM base AS final WORKDIR /app +ENV ASPNETCORE_URLS=http://+:80 COPY --from=publish /app/publish . ENTRYPOINT ["dotnet", "Plugins.Solax.dll"] \ No newline at end of file diff --git a/TeslaSolarCharger/Server/Dockerfile b/TeslaSolarCharger/Server/Dockerfile index d6fcc74ea..92e4e9a30 100644 --- a/TeslaSolarCharger/Server/Dockerfile +++ b/TeslaSolarCharger/Server/Dockerfile @@ -23,5 +23,6 @@ RUN dotnet publish "TeslaSolarCharger.Server.csproj" -c Release -o /app/publish FROM base AS final ARG MINVERVERSIONOVERRIDE WORKDIR /app +ENV ASPNETCORE_URLS=http://+:80 COPY --from=publish /app/publish . ENTRYPOINT ["dotnet", "TeslaSolarCharger.Server.dll"] \ No newline at end of file From 2b3297a869c10a45d28a12b7e8e99c2aeab9372e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Thu, 21 Dec 2023 23:53:57 +0100 Subject: [PATCH 11/50] feat(TeslaFleetApiService): use Proxy address when needed --- .../Dtos/DtoFleetApiRequest.cs | 7 ++ .../Server/Services/TeslaFleetApiService.cs | 114 +++++++++++++----- TeslaSolarCharger/Server/appsettings.json | 1 + .../Shared/Contracts/IConfigurationWrapper.cs | 1 + .../Shared/Wrappers/ConfigurationWrapper.cs | 7 ++ 5 files changed, 98 insertions(+), 32 deletions(-) create mode 100644 TeslaSolarCharger.SharedBackend/Dtos/DtoFleetApiRequest.cs diff --git a/TeslaSolarCharger.SharedBackend/Dtos/DtoFleetApiRequest.cs b/TeslaSolarCharger.SharedBackend/Dtos/DtoFleetApiRequest.cs new file mode 100644 index 000000000..ecdce4aa2 --- /dev/null +++ b/TeslaSolarCharger.SharedBackend/Dtos/DtoFleetApiRequest.cs @@ -0,0 +1,7 @@ +namespace TeslaSolarCharger.SharedBackend.Dtos; + +public class DtoFleetApiRequest +{ + public string RequestUrl { get; set; } + public bool NeedsProxy { get; set; } +} diff --git a/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs b/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs index 41fddafe0..972dfd5c5 100644 --- a/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs +++ b/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs @@ -16,6 +16,7 @@ using TeslaSolarCharger.Shared.Dtos.Contracts; using TeslaSolarCharger.Shared.Enums; using TeslaSolarCharger.SharedBackend.Contracts; +using TeslaSolarCharger.SharedBackend.Dtos; using Car = TeslaSolarCharger.Shared.Dtos.Settings.Car; namespace TeslaSolarCharger.Server.Services; @@ -33,12 +34,37 @@ public class TeslaFleetApiService : ITeslaService, ITeslaFleetApiService private readonly IBackendApiService _backendApiService; private readonly ISettings _settings; - private readonly string _chargeStartComand = "command/charge_start"; - private readonly string _chargeStopComand = "command/charge_stop"; - private readonly string _setChargingAmps = "command/set_charging_amps"; - private readonly string _setScheduledCharging = "command/set_scheduled_charging"; - private readonly string _setSocLimit = "command/set_charge_limit"; - private readonly string _wakeUpComand = "wake_up"; + + private DtoFleetApiRequest ChargeStartRequest => new() + { + RequestUrl = "command/charge_start", + NeedsProxy = true, + }; + private DtoFleetApiRequest ChargeStopRequest => new() + { + RequestUrl = "command/charge_stop", + NeedsProxy = true, + }; + private DtoFleetApiRequest SetChargingAmpsRequest => new() + { + RequestUrl = "command/set_charging_amps", + NeedsProxy = true, + }; + private DtoFleetApiRequest SetScheduledChargingRequest => new() + { + RequestUrl = "command/set_scheduled_charging", + NeedsProxy = true, + }; + private DtoFleetApiRequest SetChargeLimitRequest => new() + { + RequestUrl = "command/set_charge_limit", + NeedsProxy = true, + }; + private DtoFleetApiRequest WakeUpRequest => new() + { + RequestUrl = "wake_up", + NeedsProxy = false, + }; public TeslaFleetApiService(ILogger logger, ITeslaSolarChargerContext teslaSolarChargerContext, IDateTimeProvider dateTimeProvider, ITeslamateContext teslamateContext, IConfigurationWrapper configurationWrapper, @@ -67,18 +93,18 @@ public async Task StartCharging(int carId, int startAmp, CarStateEnum? carState) } await WakeUpCarIfNeeded(carId, carState).ConfigureAwait(false); - var id = await _teslamateContext.Cars.Where(c => c.Id == carId).Select(c => c.Eid).FirstAsync().ConfigureAwait(false); + var vin = await GetVinByCarId(carId).ConfigureAwait(false); await SetAmp(carId, startAmp).ConfigureAwait(false); - var result = await SendCommandToTeslaApi(id, _chargeStartComand).ConfigureAwait(false); + var result = await SendCommandToTeslaApi(vin, ChargeStartRequest).ConfigureAwait(false); } public async Task WakeUpCar(int carId) { _logger.LogTrace("{method}({carId})", nameof(WakeUpCar), carId); - var id = await _teslamateContext.Cars.Where(c => c.Id == carId).Select(c => c.Eid).FirstAsync().ConfigureAwait(false); - var result = await SendCommandToTeslaApi(id, _wakeUpComand).ConfigureAwait(false); + var vin = await GetVinByCarId(carId).ConfigureAwait(false); + var result = await SendCommandToTeslaApi(vin, WakeUpRequest).ConfigureAwait(false); await _teslamateApiService.ResumeLogging(carId).ConfigureAwait(false); await Task.Delay(TimeSpan.FromSeconds(20)).ConfigureAwait(false); @@ -87,22 +113,22 @@ public async Task WakeUpCar(int carId) public async Task StopCharging(int carId) { _logger.LogTrace("{method}({carId})", nameof(StopCharging), carId); - var id = await _teslamateContext.Cars.Where(c => c.Id == carId).Select(c => c.Eid).FirstAsync().ConfigureAwait(false); - var result = await SendCommandToTeslaApi(id, _chargeStopComand).ConfigureAwait(false); + var vin = await GetVinByCarId(carId).ConfigureAwait(false); + var result = await SendCommandToTeslaApi(vin, ChargeStopRequest).ConfigureAwait(false); } public async Task SetAmp(int carId, int amps) { _logger.LogTrace("{method}({carId}, {amps})", nameof(SetAmp), carId, amps); - var id = await _teslamateContext.Cars.Where(c => c.Id == carId).Select(c => c.Eid).FirstAsync().ConfigureAwait(false); + var vin = await GetVinByCarId(carId).ConfigureAwait(false); var commandData = $"{{\"charging_amps\":{amps}}}"; - var result = await SendCommandToTeslaApi(id, _setChargingAmps, commandData).ConfigureAwait(false); + var result = await SendCommandToTeslaApi(vin, SetChargingAmpsRequest, commandData).ConfigureAwait(false); } public async Task SetScheduledCharging(int carId, DateTimeOffset? chargingStartTime) { _logger.LogTrace("{method}({param1}, {param2})", nameof(SetScheduledCharging), carId, chargingStartTime); - var id = await _teslamateContext.Cars.Where(c => c.Id == carId).Select(c => c.Eid).FirstAsync().ConfigureAwait(false); + var vin = await GetVinByCarId(carId).ConfigureAwait(false); var car = _settings.Cars.First(c => c.Id == carId); if (!IsChargingScheduleChangeNeeded(chargingStartTime, _dateTimeProvider.DateTimeOffSetNow(), car, out var parameters)) { @@ -112,7 +138,7 @@ public async Task SetScheduledCharging(int carId, DateTimeOffset? chargingStartT await WakeUpCarIfNeeded(carId, car.CarState.State).ConfigureAwait(false); - var result = await SendCommandToTeslaApi(id, _setScheduledCharging, JsonConvert.SerializeObject(parameters)).ConfigureAwait(false); + var result = await SendCommandToTeslaApi(vin, SetScheduledChargingRequest, JsonConvert.SerializeObject(parameters)).ConfigureAwait(false); //assume update was sucessfull as update is not working after mosquitto restart (or wrong cached State) if (parameters["enable"] == "false") { @@ -123,14 +149,26 @@ public async Task SetScheduledCharging(int carId, DateTimeOffset? chargingStartT public async Task SetChargeLimit(int carId, int limitSoC) { _logger.LogTrace("{method}({param1}, {param2})", nameof(SetChargeLimit), carId, limitSoC); - var id = await _teslamateContext.Cars.Where(c => c.Id == carId).Select(c => c.Eid).FirstAsync().ConfigureAwait(false); + var vin = await GetVinByCarId(carId).ConfigureAwait(false); var car = _settings.Cars.First(c => c.Id == carId); await WakeUpCarIfNeeded(carId, car.CarState.State).ConfigureAwait(false); - var parameters = new Dictionary() + var parameters = new Dictionary() { - { "percent", limitSoC.ToString() }, + { "percent", limitSoC }, }; - await SendCommandToTeslaApi(id, _setSocLimit, JsonConvert.SerializeObject(parameters)).ConfigureAwait(false); + await SendCommandToTeslaApi(vin, SetChargeLimitRequest, JsonConvert.SerializeObject(parameters)).ConfigureAwait(false); + } + + private async Task GetVinByCarId(int carId) + { + var vin = await _teslamateContext.Cars.Where(c => c.Id == carId).Select(c => c.Vin).FirstAsync().ConfigureAwait(false); + if (string.IsNullOrEmpty(vin)) + { + _logger.LogError("Could not get VIN for car ID {carId}", carId); + throw new InvalidOperationException("Could not find VIN"); + } + + return vin; } internal bool IsChargingScheduleChangeNeeded(DateTimeOffset? chargingStartTime, DateTimeOffset currentDate, Car car, out Dictionary parameters) @@ -229,32 +267,28 @@ private async Task WakeUpCarIfNeeded(int carId, CarStateEnum? carState) } } - private async Task SendCommandToTeslaApi(long id, string commandName, string contentData = "{}") + private async Task SendCommandToTeslaApi(string vin, DtoFleetApiRequest fleetApiRequest, string contentData = "{}") { - _logger.LogTrace("{method}({id}, {commandName}, {contentData})", nameof(SendCommandToTeslaApi), id, commandName, contentData); + _logger.LogTrace("{method}({vin}, {@fleetApiRequest}, {contentData})", nameof(SendCommandToTeslaApi), vin, fleetApiRequest, contentData); var accessToken = await GetAccessTokenAndRefreshWhenNeededAsync().ConfigureAwait(false); using var httpClient = new HttpClient(); httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken.AccessToken); var content = new StringContent(contentData, System.Text.Encoding.UTF8, "application/json"); - var regionCode = accessToken.Region switch - { - TeslaFleetApiRegion.Emea => "eu", - TeslaFleetApiRegion.NorthAmerica => "na", - _ => throw new NotImplementedException($"Region {accessToken.Region} is not implemented."), - }; - var requestUri = $"https://fleet-api.prd.{regionCode}.vn.cloud.tesla.com/api/1/vehicles/{id}/{commandName}"; + + var baseUrl = GetFleetApiBaseUrl(accessToken.Region, fleetApiRequest.NeedsProxy); + var requestUri = $"{baseUrl}api/1/vehicles/{vin}/{fleetApiRequest.RequestUrl}"; _settings.TeslaApiRequestCounter++; var response = await httpClient.PostAsync(requestUri, content).ConfigureAwait(false); var responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); if (!response.IsSuccessStatusCode) { await _backendApiService.PostErrorInformation(nameof(TeslaFleetApiService), nameof(SendCommandToTeslaApi), - $"Sending command to Tesla API resulted in non succes status code: {response.StatusCode} : Command name:{commandName}, Content data:{contentData}. Response string: {responseString}").ConfigureAwait(false); + $"Sending command to Tesla API resulted in non succes status code: {response.StatusCode} : Command name:{fleetApiRequest.RequestUrl}, Content data:{contentData}. Response string: {responseString}").ConfigureAwait(false); await HandleNonSuccessTeslaApiStatusCodes(response.StatusCode, accessToken, responseString).ConfigureAwait(false); } _logger.LogDebug("Response: {responseString}", responseString); //Wake up command returns another result which is irrelevant - if (commandName == _wakeUpComand) + if (fleetApiRequest.RequestUrl == WakeUpRequest.RequestUrl) { return null; } @@ -263,11 +297,27 @@ await _backendApiService.PostErrorInformation(nameof(TeslaFleetApiService), name if (result?.Result == false) { await _backendApiService.PostErrorInformation(nameof(TeslaFleetApiService), nameof(SendCommandToTeslaApi), - $"Result of command request is false: {commandName}, {contentData}. Response string: {responseString}").ConfigureAwait(false); + $"Result of command request is false: {fleetApiRequest.RequestUrl}, {contentData}. Response string: {responseString}").ConfigureAwait(false); } return result ?? null; } + private string GetFleetApiBaseUrl(TeslaFleetApiRegion region, bool useProxyBaseUrl) + { + if (useProxyBaseUrl) + { + var configUrl = _configurationWrapper.GetFleetApiBaseUrl(); + return configUrl ?? throw new KeyNotFoundException("Could not get Tesla HTTP proxy address"); + } + var regionCode = region switch + { + TeslaFleetApiRegion.Emea => "eu", + TeslaFleetApiRegion.NorthAmerica => "na", + _ => throw new NotImplementedException($"Region {region} is not implemented."), + }; + return $"https://fleet-api.prd.{regionCode}.vn.cloud.tesla.com/"; + } + public async Task RefreshTokenAsync() { _logger.LogTrace("{method}()", nameof(RefreshTokenAsync)); diff --git a/TeslaSolarCharger/Server/appsettings.json b/TeslaSolarCharger/Server/appsettings.json index ad2703489..aa9d88136 100644 --- a/TeslaSolarCharger/Server/appsettings.json +++ b/TeslaSolarCharger/Server/appsettings.json @@ -62,6 +62,7 @@ "UseFleetApi": false, "FleetApiClientId": "f29f71d6285a-4873-8b6b-80f15854892e", "BackendApiBaseUrl": "https://www.teslasolarcharger.de/api/", + "TeslaFleetApiBaseUrl": "https://www.teslasolarcharger.de/teslaproxy/", "AwattarBaseUrl": "https://api.awattar.de/v1/marketdata", "GridPriceProvider": { "EnergyProvider": "FixedPrice", diff --git a/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs b/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs index d2c679ca8..008bd722d 100644 --- a/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs +++ b/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs @@ -91,4 +91,5 @@ public interface IConfigurationWrapper string BackendApiBaseUrl(); bool IsDevelopmentEnvironment(); string GetAwattarBaseUrl(); + string? GetFleetApiBaseUrl(); } diff --git a/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs b/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs index 0aad5d1ab..1578aa476 100644 --- a/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs +++ b/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs @@ -92,6 +92,13 @@ internal string ConfigFileDirectory() return path; } + public string? GetFleetApiBaseUrl() + { + var environmentVariableName = "TeslaFleetApiBaseUrl"; + var value = _configuration.GetValue(environmentVariableName); + return value; + } + public string GetAwattarBaseUrl() { var environmentVariableName = "AwattarBaseUrl"; From 41da4b775986c01118a06bc3f818d0d9f46ba7c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Fri, 22 Dec 2023 20:47:55 +0100 Subject: [PATCH 12/50] feat(TeslaFleetApi): can handle public key not registered --- .../Contracts/IConstants.cs | 1 + .../Values/Constants.cs | 1 + TeslaSolarCharger/Client/Pages/Index.razor | 25 +- .../Server/Controllers/IndexController.cs | 3 + .../TeslaFleetApi/DtoGenericTeslaResponse.cs | 6 +- ...eUpResult.cs => DtoVehicleWakeUpResult.cs} | 2 +- .../ApiServices/Contracts/IIndexService.cs | 1 + .../Services/ApiServices/IndexService.cs | 26 +- .../Server/Services/TeslaFleetApiService.cs | 290 +++++++++--------- .../IndexRazor/CarValues/DtoCarBaseStates.cs | 1 + 10 files changed, 200 insertions(+), 156 deletions(-) rename TeslaSolarCharger/Server/Dtos/TeslaFleetApi/{DtoWakeUpResult.cs => DtoVehicleWakeUpResult.cs} (97%) diff --git a/TeslaSolarCharger.SharedBackend/Contracts/IConstants.cs b/TeslaSolarCharger.SharedBackend/Contracts/IConstants.cs index 7c669fd2d..74c67720e 100644 --- a/TeslaSolarCharger.SharedBackend/Contracts/IConstants.cs +++ b/TeslaSolarCharger.SharedBackend/Contracts/IConstants.cs @@ -15,4 +15,5 @@ public interface IConstants string FleetApiTokenRequested { get; } string TokenRefreshUnauthorized { get; } string TokenMissingScopes { get; } + string VehicleNotPaired { get; } } diff --git a/TeslaSolarCharger.SharedBackend/Values/Constants.cs b/TeslaSolarCharger.SharedBackend/Values/Constants.cs index b38907b48..cc6e08c7c 100644 --- a/TeslaSolarCharger.SharedBackend/Values/Constants.cs +++ b/TeslaSolarCharger.SharedBackend/Values/Constants.cs @@ -14,4 +14,5 @@ public class Constants : IConstants public string FleetApiTokenRequested => "FleetApiTokenRequested"; public string TokenRefreshUnauthorized => "TokenRefreshUnauthorized"; public string TokenMissingScopes => "TokenMissingScopes"; + public string VehicleNotPaired => "VehicleNotPaired_{0}"; } diff --git a/TeslaSolarCharger/Client/Pages/Index.razor b/TeslaSolarCharger/Client/Pages/Index.razor index 775f082b0..e8a4ac2b8 100644 --- a/TeslaSolarCharger/Client/Pages/Index.razor +++ b/TeslaSolarCharger/Client/Pages/Index.razor @@ -14,19 +14,20 @@ @inject HttpClient HttpClient @inject IToastService ToastService @inject ToolTipTextKeys ToolTipTextKeys +@inject NavigationManager NavigationManager Tesla Solar Charger -@if(_serverTime != default) +@if (_serverTime != default) { if (_serverTime.Value is { Day: 24, Month: 12, Hour: > 15 } or { Day: 25, Month: 12 }) { - + } - if(_serverTime.Value is { Day: 31, Month: 12, Hour: > 15 } or { Day: 1, Month: 1}) + if (_serverTime.Value is { Day: 31, Month: 12, Hour: > 15 } or { Day: 1, Month: 1 }) { + @if (car.VehicleNotPaired) + { + + }
@if (car.IsHealthy == false) { @@ -258,7 +266,7 @@ else HelpText=""> + @onchange="@(e => _carBaseSettings[car.CarId].LatestTimeToReachStateOfCharge = DateTime.Parse(e.Value?.ToString() ?? DateTime.MaxValue.ToString(CultureInfo.CurrentCulture)).Date.AddHours(_carBaseSettings[car.CarId].LatestTimeToReachStateOfCharge.Hour).AddMinutes(_carBaseSettings[car.CarId].LatestTimeToReachStateOfCharge.Minute))"> } @@ -268,7 +276,7 @@ else HelpText=""> + @onchange="@(e => _carBaseSettings[car.CarId].LatestTimeToReachStateOfCharge = _carBaseSettings[car.CarId].LatestTimeToReachStateOfCharge.Date.AddHours(TimeSpan.Parse(e.Value?.ToString() ?? "00:00").Hours).AddMinutes(TimeSpan.Parse(e.Value?.ToString() ?? "00:00").Minutes))"> @@ -632,4 +640,11 @@ else } StateHasChanged(); } + + private async Task OpenRegisterPublicKeyPage() + { + var response = await HttpClient.PostAsync("api/Index/ResetVehicleNotPaired", null).ConfigureAwait(false); + NavigationManager.NavigateTo("https://tesla.com/_ak/www.teslasolarcharger.de"); + } + } \ No newline at end of file diff --git a/TeslaSolarCharger/Server/Controllers/IndexController.cs b/TeslaSolarCharger/Server/Controllers/IndexController.cs index 26ee91603..15fb9a57d 100644 --- a/TeslaSolarCharger/Server/Controllers/IndexController.cs +++ b/TeslaSolarCharger/Server/Controllers/IndexController.cs @@ -40,4 +40,7 @@ public DtoCarTopicValues CarDetails(int carId) [HttpGet] public List GetChargingSlots(int carId) => _indexService.GetChargingSlots(carId); + + [HttpPost] + public Task ResetVehicleNotPaired() => _indexService.ResetVehicleNotPaired(); } diff --git a/TeslaSolarCharger/Server/Dtos/TeslaFleetApi/DtoGenericTeslaResponse.cs b/TeslaSolarCharger/Server/Dtos/TeslaFleetApi/DtoGenericTeslaResponse.cs index af25751f3..503a1c014 100644 --- a/TeslaSolarCharger/Server/Dtos/TeslaFleetApi/DtoGenericTeslaResponse.cs +++ b/TeslaSolarCharger/Server/Dtos/TeslaFleetApi/DtoGenericTeslaResponse.cs @@ -5,5 +5,9 @@ namespace TeslaSolarCharger.Server.Dtos.TeslaFleetApi; public class DtoGenericTeslaResponse where T : class { [JsonProperty("response")] - public T Response { get; set; } + public T? Response { get; set; } + [JsonProperty("error")] + public string? Error { get; set; } + [JsonProperty("error_description")] + public string? ErrorDescription { get; set; } } diff --git a/TeslaSolarCharger/Server/Dtos/TeslaFleetApi/DtoWakeUpResult.cs b/TeslaSolarCharger/Server/Dtos/TeslaFleetApi/DtoVehicleWakeUpResult.cs similarity index 97% rename from TeslaSolarCharger/Server/Dtos/TeslaFleetApi/DtoWakeUpResult.cs rename to TeslaSolarCharger/Server/Dtos/TeslaFleetApi/DtoVehicleWakeUpResult.cs index 034b6571b..24110c4ff 100644 --- a/TeslaSolarCharger/Server/Dtos/TeslaFleetApi/DtoWakeUpResult.cs +++ b/TeslaSolarCharger/Server/Dtos/TeslaFleetApi/DtoVehicleWakeUpResult.cs @@ -2,7 +2,7 @@ namespace TeslaSolarCharger.Server.Dtos.TeslaFleetApi; -public class DtoWakeUpResult +public class DtoVehicleWakeUpResult { [JsonProperty("id")] public int Id { get; set; } diff --git a/TeslaSolarCharger/Server/Services/ApiServices/Contracts/IIndexService.cs b/TeslaSolarCharger/Server/Services/ApiServices/Contracts/IIndexService.cs index b837a5930..3d3a59038 100644 --- a/TeslaSolarCharger/Server/Services/ApiServices/Contracts/IIndexService.cs +++ b/TeslaSolarCharger/Server/Services/ApiServices/Contracts/IIndexService.cs @@ -15,4 +15,5 @@ public interface IIndexService DtoCarTopicValues GetCarDetails(int carId); List RecalculateAndGetChargingSlots(int carId); List GetChargingSlots(int carId); + Task ResetVehicleNotPaired(); } diff --git a/TeslaSolarCharger/Server/Services/ApiServices/IndexService.cs b/TeslaSolarCharger/Server/Services/ApiServices/IndexService.cs index 80653febf..5315d398e 100644 --- a/TeslaSolarCharger/Server/Services/ApiServices/IndexService.cs +++ b/TeslaSolarCharger/Server/Services/ApiServices/IndexService.cs @@ -28,12 +28,14 @@ public class IndexService : IIndexService private readonly IConstants _constants; private readonly IConfigurationWrapper _configurationWrapper; private readonly IDateTimeProvider _dateTimeProvider; + private readonly ITeslaSolarChargerContext _teslaSolarChargerContext; public IndexService(ILogger logger, ISettings settings, ITeslamateContext teslamateContext, IChargingCostService chargingCostService, ToolTipTextKeys toolTipTextKeys, ILatestTimeToReachSocUpdateService latestTimeToReachSocUpdateService, IConfigJsonService configJsonService, IChargeTimeCalculationService chargeTimeCalculationService, - IConstants constants, IConfigurationWrapper configurationWrapper, IDateTimeProvider dateTimeProvider) + IConstants constants, IConfigurationWrapper configurationWrapper, IDateTimeProvider dateTimeProvider, + ITeslaSolarChargerContext teslaSolarChargerContext) { _logger = logger; _settings = settings; @@ -46,6 +48,7 @@ public IndexService(ILogger logger, ISettings settings, ITeslamate _constants = constants; _configurationWrapper = configurationWrapper; _dateTimeProvider = dateTimeProvider; + _teslaSolarChargerContext = teslaSolarChargerContext; } public DtoPvValues GetPvValues() @@ -101,6 +104,17 @@ public async Task> GetCarBaseStatesOfEnabledCars() await _chargeTimeCalculationService.IsLatestTimeToReachSocAfterLatestKnownChargePrice(enabledCar.Id).ConfigureAwait(false); } + if (_configurationWrapper.UseFleetApi()) + { + var vin = await _teslamateContext.Cars.Where(c => c.Id == enabledCar.Id).Select(c => c.Vin).FirstOrDefaultAsync().ConfigureAwait(false); + var key = string.Format(_constants.VehicleNotPaired, vin); + if (!string.IsNullOrEmpty(vin) && + (await _teslaSolarChargerContext.TscConfigurations.AnyAsync(c => c.Key == key).ConfigureAwait(false))) + { + dtoCarBaseValues.VehicleNotPaired = true; + } + } + dtoCarBaseValues.ChargeInformation = GenerateChargeInformation(enabledCar); carBaseValues.Add(dtoCarBaseValues); @@ -304,6 +318,16 @@ public List GetChargingSlots(int carId) return car.CarState.PlannedChargingSlots; } + public async Task ResetVehicleNotPaired() + { + var key = string.Format(_constants.VehicleNotPaired, string.Empty); + var configs = await _teslaSolarChargerContext.TscConfigurations + .Where(c => c.Key.StartsWith(key)) + .ToListAsync().ConfigureAwait(false); + _teslaSolarChargerContext.TscConfigurations.RemoveRange(configs); + await _teslaSolarChargerContext.SaveChangesAsync().ConfigureAwait(false); + } + string AddSpacesBeforeCapitalLetters(string text) { if (string.IsNullOrWhiteSpace(text)) diff --git a/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs b/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs index 972dfd5c5..2a230dc94 100644 --- a/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs +++ b/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs @@ -21,20 +21,19 @@ namespace TeslaSolarCharger.Server.Services; -public class TeslaFleetApiService : ITeslaService, ITeslaFleetApiService +public class TeslaFleetApiService( + ILogger logger, + ITeslaSolarChargerContext teslaSolarChargerContext, + IDateTimeProvider dateTimeProvider, + ITeslamateContext teslamateContext, + IConfigurationWrapper configurationWrapper, + ITeslamateApiService teslamateApiService, + IConstants constants, + ITscConfigurationService tscConfigurationService, + IBackendApiService backendApiService, + ISettings settings) + : ITeslaService, ITeslaFleetApiService { - private readonly ILogger _logger; - private readonly ITeslaSolarChargerContext _teslaSolarChargerContext; - private readonly IDateTimeProvider _dateTimeProvider; - private readonly ITeslamateContext _teslamateContext; - private readonly IConfigurationWrapper _configurationWrapper; - private readonly ITeslamateApiService _teslamateApiService; - private readonly IConstants _constants; - private readonly ITscConfigurationService _tscConfigurationService; - private readonly IBackendApiService _backendApiService; - private readonly ISettings _settings; - - private DtoFleetApiRequest ChargeStartRequest => new() { RequestUrl = "command/charge_start", @@ -66,29 +65,12 @@ public class TeslaFleetApiService : ITeslaService, ITeslaFleetApiService NeedsProxy = false, }; - public TeslaFleetApiService(ILogger logger, ITeslaSolarChargerContext teslaSolarChargerContext, - IDateTimeProvider dateTimeProvider, ITeslamateContext teslamateContext, IConfigurationWrapper configurationWrapper, - ITeslamateApiService teslamateApiService, IConstants constants, ITscConfigurationService tscConfigurationService, - IBackendApiService backendApiService, ISettings settings) - { - _logger = logger; - _teslaSolarChargerContext = teslaSolarChargerContext; - _dateTimeProvider = dateTimeProvider; - _teslamateContext = teslamateContext; - _configurationWrapper = configurationWrapper; - _teslamateApiService = teslamateApiService; - _constants = constants; - _tscConfigurationService = tscConfigurationService; - _backendApiService = backendApiService; - _settings = settings; - } - public async Task StartCharging(int carId, int startAmp, CarStateEnum? carState) { - _logger.LogTrace("{method}({carId}, {startAmp}, {carState})", nameof(StartCharging), carId, startAmp, carState); + logger.LogTrace("{method}({carId}, {startAmp}, {carState})", nameof(StartCharging), carId, startAmp, carState); if (startAmp == 0) { - _logger.LogDebug("Should start charging with 0 amp. Skipping charge start."); + logger.LogDebug("Should start charging with 0 amp. Skipping charge start."); return; } await WakeUpCarIfNeeded(carId, carState).ConfigureAwait(false); @@ -96,49 +78,49 @@ public async Task StartCharging(int carId, int startAmp, CarStateEnum? carState) var vin = await GetVinByCarId(carId).ConfigureAwait(false); await SetAmp(carId, startAmp).ConfigureAwait(false); - var result = await SendCommandToTeslaApi(vin, ChargeStartRequest).ConfigureAwait(false); + var result = await SendCommandToTeslaApi(vin, ChargeStartRequest).ConfigureAwait(false); } public async Task WakeUpCar(int carId) { - _logger.LogTrace("{method}({carId})", nameof(WakeUpCar), carId); + logger.LogTrace("{method}({carId})", nameof(WakeUpCar), carId); var vin = await GetVinByCarId(carId).ConfigureAwait(false); - var result = await SendCommandToTeslaApi(vin, WakeUpRequest).ConfigureAwait(false); - await _teslamateApiService.ResumeLogging(carId).ConfigureAwait(false); + var result = await SendCommandToTeslaApi(vin, WakeUpRequest).ConfigureAwait(false); + await teslamateApiService.ResumeLogging(carId).ConfigureAwait(false); await Task.Delay(TimeSpan.FromSeconds(20)).ConfigureAwait(false); } public async Task StopCharging(int carId) { - _logger.LogTrace("{method}({carId})", nameof(StopCharging), carId); + logger.LogTrace("{method}({carId})", nameof(StopCharging), carId); var vin = await GetVinByCarId(carId).ConfigureAwait(false); - var result = await SendCommandToTeslaApi(vin, ChargeStopRequest).ConfigureAwait(false); + var result = await SendCommandToTeslaApi(vin, ChargeStopRequest).ConfigureAwait(false); } public async Task SetAmp(int carId, int amps) { - _logger.LogTrace("{method}({carId}, {amps})", nameof(SetAmp), carId, amps); + logger.LogTrace("{method}({carId}, {amps})", nameof(SetAmp), carId, amps); var vin = await GetVinByCarId(carId).ConfigureAwait(false); var commandData = $"{{\"charging_amps\":{amps}}}"; - var result = await SendCommandToTeslaApi(vin, SetChargingAmpsRequest, commandData).ConfigureAwait(false); + var result = await SendCommandToTeslaApi(vin, SetChargingAmpsRequest, commandData).ConfigureAwait(false); } public async Task SetScheduledCharging(int carId, DateTimeOffset? chargingStartTime) { - _logger.LogTrace("{method}({param1}, {param2})", nameof(SetScheduledCharging), carId, chargingStartTime); + logger.LogTrace("{method}({param1}, {param2})", nameof(SetScheduledCharging), carId, chargingStartTime); var vin = await GetVinByCarId(carId).ConfigureAwait(false); - var car = _settings.Cars.First(c => c.Id == carId); - if (!IsChargingScheduleChangeNeeded(chargingStartTime, _dateTimeProvider.DateTimeOffSetNow(), car, out var parameters)) + var car = settings.Cars.First(c => c.Id == carId); + if (!IsChargingScheduleChangeNeeded(chargingStartTime, dateTimeProvider.DateTimeOffSetNow(), car, out var parameters)) { - _logger.LogDebug("No change in updating scheduled charging needed."); + logger.LogDebug("No change in updating scheduled charging needed."); return; } await WakeUpCarIfNeeded(carId, car.CarState.State).ConfigureAwait(false); - var result = await SendCommandToTeslaApi(vin, SetScheduledChargingRequest, JsonConvert.SerializeObject(parameters)).ConfigureAwait(false); + var result = await SendCommandToTeslaApi(vin, SetScheduledChargingRequest, JsonConvert.SerializeObject(parameters)).ConfigureAwait(false); //assume update was sucessfull as update is not working after mosquitto restart (or wrong cached State) if (parameters["enable"] == "false") { @@ -148,23 +130,23 @@ public async Task SetScheduledCharging(int carId, DateTimeOffset? chargingStartT public async Task SetChargeLimit(int carId, int limitSoC) { - _logger.LogTrace("{method}({param1}, {param2})", nameof(SetChargeLimit), carId, limitSoC); + logger.LogTrace("{method}({param1}, {param2})", nameof(SetChargeLimit), carId, limitSoC); var vin = await GetVinByCarId(carId).ConfigureAwait(false); - var car = _settings.Cars.First(c => c.Id == carId); + var car = settings.Cars.First(c => c.Id == carId); await WakeUpCarIfNeeded(carId, car.CarState.State).ConfigureAwait(false); var parameters = new Dictionary() { { "percent", limitSoC }, }; - await SendCommandToTeslaApi(vin, SetChargeLimitRequest, JsonConvert.SerializeObject(parameters)).ConfigureAwait(false); + await SendCommandToTeslaApi(vin, SetChargeLimitRequest, JsonConvert.SerializeObject(parameters)).ConfigureAwait(false); } private async Task GetVinByCarId(int carId) { - var vin = await _teslamateContext.Cars.Where(c => c.Id == carId).Select(c => c.Vin).FirstAsync().ConfigureAwait(false); + var vin = await teslamateContext.Cars.Where(c => c.Id == carId).Select(c => c.Vin).FirstAsync().ConfigureAwait(false); if (string.IsNullOrEmpty(vin)) { - _logger.LogError("Could not get VIN for car ID {carId}", carId); + logger.LogError("Could not get VIN for car ID {carId}", carId); throw new InvalidOperationException("Could not find VIN"); } @@ -173,22 +155,22 @@ private async Task GetVinByCarId(int carId) internal bool IsChargingScheduleChangeNeeded(DateTimeOffset? chargingStartTime, DateTimeOffset currentDate, Car car, out Dictionary parameters) { - _logger.LogTrace("{method}({startTime}, {currentDate}, {carId}, {parameters})", nameof(IsChargingScheduleChangeNeeded), chargingStartTime, currentDate, car.Id, nameof(parameters)); + logger.LogTrace("{method}({startTime}, {currentDate}, {carId}, {parameters})", nameof(IsChargingScheduleChangeNeeded), chargingStartTime, currentDate, car.Id, nameof(parameters)); parameters = new Dictionary(); if (chargingStartTime != null) { - _logger.LogTrace("{chargingStartTime} is not null", nameof(chargingStartTime)); + logger.LogTrace("{chargingStartTime} is not null", nameof(chargingStartTime)); chargingStartTime = RoundToNextQuarterHour(chargingStartTime.Value); } if (car.CarState.ScheduledChargingStartTime == chargingStartTime) { - _logger.LogDebug("Correct charging start time already set."); + logger.LogDebug("Correct charging start time already set."); return false; } if (chargingStartTime == null) { - _logger.LogDebug("Set chargingStartTime to null."); + logger.LogDebug("Set chargingStartTime to null."); parameters = new Dictionary() { { "enable", "false" }, @@ -204,29 +186,29 @@ internal bool IsChargingScheduleChangeNeeded(DateTimeOffset? chargingStartTime, if (car.CarState.ScheduledChargingStartTime == chargingStartTime) { - _logger.LogDebug("Correct charging start time already set."); + logger.LogDebug("Correct charging start time already set."); return true; } //ToDo: maybe disable scheduled charge in this case. if (timeUntilChargeStart <= TimeSpan.Zero || timeUntilChargeStart.TotalHours > 24) { - _logger.LogDebug("Charge schedule should not be changed, as time until charge start is higher than 24 hours or lower than zero."); + logger.LogDebug("Charge schedule should not be changed, as time until charge start is higher than 24 hours or lower than zero."); return false; } if (car.CarState.ScheduledChargingStartTime == null && !scheduledChargeShouldBeSet) { - _logger.LogDebug("No charge schedule set and no charge schedule should be set."); + logger.LogDebug("No charge schedule set and no charge schedule should be set."); return true; } - _logger.LogDebug("Normal parameter set."); + logger.LogDebug("Normal parameter set."); parameters = new Dictionary() { { "enable", scheduledChargeShouldBeSet ? "true" : "false" }, { "time", minutesFromMidNight.ToString() }, }; - _logger.LogTrace("{@parameters}", parameters); + logger.LogTrace("{@parameters}", parameters); return true; } @@ -248,7 +230,7 @@ internal DateTimeOffset RoundToNextQuarterHour(DateTimeOffset chargingStartTime) var newNotRoundedDateTime = chargingStartTime.AddHours(additionalHours); chargingStartTime = new DateTimeOffset(newNotRoundedDateTime.Year, newNotRoundedDateTime.Month, newNotRoundedDateTime.Day, newNotRoundedDateTime.Hour, roundedMinutes, 0, newNotRoundedDateTime.Offset); - _logger.LogDebug("Rounded charging Start time: {chargingStartTime}", chargingStartTime); + logger.LogDebug("Rounded charging Start time: {chargingStartTime}", chargingStartTime); return chargingStartTime; } @@ -257,19 +239,19 @@ private async Task WakeUpCarIfNeeded(int carId, CarStateEnum? carState) switch (carState) { case CarStateEnum.Offline or CarStateEnum.Asleep: - _logger.LogInformation("Wakeup car."); + logger.LogInformation("Wakeup car."); await WakeUpCar(carId).ConfigureAwait(false); break; case CarStateEnum.Suspended: - _logger.LogInformation("Resume logging as is suspended"); - await _teslamateApiService.ResumeLogging(carId).ConfigureAwait(false); + logger.LogInformation("Resume logging as is suspended"); + await teslamateApiService.ResumeLogging(carId).ConfigureAwait(false); break; } } - private async Task SendCommandToTeslaApi(string vin, DtoFleetApiRequest fleetApiRequest, string contentData = "{}") + private async Task?> SendCommandToTeslaApi(string vin, DtoFleetApiRequest fleetApiRequest, string contentData = "{}") where T : class { - _logger.LogTrace("{method}({vin}, {@fleetApiRequest}, {contentData})", nameof(SendCommandToTeslaApi), vin, fleetApiRequest, contentData); + logger.LogTrace("{method}({vin}, {@fleetApiRequest}, {contentData})", nameof(SendCommandToTeslaApi), vin, fleetApiRequest, contentData); var accessToken = await GetAccessTokenAndRefreshWhenNeededAsync().ConfigureAwait(false); using var httpClient = new HttpClient(); httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken.AccessToken); @@ -277,36 +259,35 @@ private async Task WakeUpCarIfNeeded(int carId, CarStateEnum? carState) var baseUrl = GetFleetApiBaseUrl(accessToken.Region, fleetApiRequest.NeedsProxy); var requestUri = $"{baseUrl}api/1/vehicles/{vin}/{fleetApiRequest.RequestUrl}"; - _settings.TeslaApiRequestCounter++; + settings.TeslaApiRequestCounter++; var response = await httpClient.PostAsync(requestUri, content).ConfigureAwait(false); var responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + var teslaCommandResultResponse = JsonConvert.DeserializeObject>(responseString); if (!response.IsSuccessStatusCode) { - await _backendApiService.PostErrorInformation(nameof(TeslaFleetApiService), nameof(SendCommandToTeslaApi), + await backendApiService.PostErrorInformation(nameof(TeslaFleetApiService), nameof(SendCommandToTeslaApi), $"Sending command to Tesla API resulted in non succes status code: {response.StatusCode} : Command name:{fleetApiRequest.RequestUrl}, Content data:{contentData}. Response string: {responseString}").ConfigureAwait(false); - await HandleNonSuccessTeslaApiStatusCodes(response.StatusCode, accessToken, responseString).ConfigureAwait(false); - } - _logger.LogDebug("Response: {responseString}", responseString); - //Wake up command returns another result which is irrelevant - if (fleetApiRequest.RequestUrl == WakeUpRequest.RequestUrl) - { - return null; + await HandleNonSuccessTeslaApiStatusCodes(response.StatusCode, accessToken, responseString, vin).ConfigureAwait(false); } - var teslaCommandResultResponse = JsonConvert.DeserializeObject>(responseString); - var result = teslaCommandResultResponse?.Response; - if (result?.Result == false) + + if (response.IsSuccessStatusCode && (teslaCommandResultResponse?.Response is DtoVehicleCommandResult vehicleCommandResult)) { - await _backendApiService.PostErrorInformation(nameof(TeslaFleetApiService), nameof(SendCommandToTeslaApi), - $"Result of command request is false: {fleetApiRequest.RequestUrl}, {contentData}. Response string: {responseString}").ConfigureAwait(false); + if (vehicleCommandResult.Result != true) + { + await backendApiService.PostErrorInformation(nameof(TeslaFleetApiService), nameof(SendCommandToTeslaApi), + $"Result of command request is false {fleetApiRequest.RequestUrl}, {contentData}. Response string: {responseString}") + .ConfigureAwait(false); + } } - return result ?? null; + logger.LogDebug("Response: {responseString}", responseString); + return teslaCommandResultResponse; } private string GetFleetApiBaseUrl(TeslaFleetApiRegion region, bool useProxyBaseUrl) { if (useProxyBaseUrl) { - var configUrl = _configurationWrapper.GetFleetApiBaseUrl(); + var configUrl = configurationWrapper.GetFleetApiBaseUrl(); return configUrl ?? throw new KeyNotFoundException("Could not get Tesla HTTP proxy address"); } var regionCode = region switch @@ -320,43 +301,43 @@ private string GetFleetApiBaseUrl(TeslaFleetApiRegion region, bool useProxyBaseU public async Task RefreshTokenAsync() { - _logger.LogTrace("{method}()", nameof(RefreshTokenAsync)); + logger.LogTrace("{method}()", nameof(RefreshTokenAsync)); var tokenState = (await GetFleetApiTokenState().ConfigureAwait(false)).Value; switch (tokenState) { case FleetApiTokenState.NotNeeded: - _logger.LogDebug("Refreshing token not needed."); + logger.LogDebug("Refreshing token not needed."); return; case FleetApiTokenState.NotRequested: - _logger.LogDebug("No token has been requested, yet."); + logger.LogDebug("No token has been requested, yet."); return; case FleetApiTokenState.TokenRequestExpired: - _logger.LogError("Your token request has expired, create a new one."); + logger.LogError("Your token request has expired, create a new one."); return; case FleetApiTokenState.TokenUnauthorized: - _logger.LogError("Your refresh token is unauthorized, create a new token."); + logger.LogError("Your refresh token is unauthorized, create a new token."); return; case FleetApiTokenState.NotReceived: break; case FleetApiTokenState.Expired: break; case FleetApiTokenState.UpToDate: - _logger.LogDebug("Token is up to date."); + logger.LogDebug("Token is up to date."); break; default: throw new ArgumentOutOfRangeException(); } - var token = await _teslaSolarChargerContext.TeslaTokens.FirstOrDefaultAsync().ConfigureAwait(false); + var token = await teslaSolarChargerContext.TeslaTokens.FirstOrDefaultAsync().ConfigureAwait(false); if (token == null) { using var httpClient = new HttpClient(); - var installationId = await _tscConfigurationService.GetInstallationId().ConfigureAwait(false); - var url = _configurationWrapper.BackendApiBaseUrl() + $"Tsc/DeliverAuthToken?installationId={installationId}"; + var installationId = await tscConfigurationService.GetInstallationId().ConfigureAwait(false); + var url = configurationWrapper.BackendApiBaseUrl() + $"Tsc/DeliverAuthToken?installationId={installationId}"; var response = await httpClient.GetAsync(url).ConfigureAwait(false); var responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); if (!response.IsSuccessStatusCode) { - await _backendApiService.PostErrorInformation(nameof(TeslaFleetApiService), nameof(SendCommandToTeslaApi), + await backendApiService.PostErrorInformation(nameof(TeslaFleetApiService), nameof(SendCommandToTeslaApi), $"Getting token from TscBackend. Response status code: {response.StatusCode} Response string: {responseString}").ConfigureAwait(false); } response.EnsureSuccessStatusCode(); @@ -368,47 +349,47 @@ await _backendApiService.PostErrorInformation(nameof(TeslaFleetApiService), name public async Task AddNewTokenAsync(DtoTeslaTscDeliveryToken token) { - var currentTokens = await _teslaSolarChargerContext.TeslaTokens.ToListAsync().ConfigureAwait(false); - _teslaSolarChargerContext.TeslaTokens.RemoveRange(currentTokens); - await _teslaSolarChargerContext.SaveChangesAsync().ConfigureAwait(false); - _teslaSolarChargerContext.TeslaTokens.Add(new TeslaToken + var currentTokens = await teslaSolarChargerContext.TeslaTokens.ToListAsync().ConfigureAwait(false); + teslaSolarChargerContext.TeslaTokens.RemoveRange(currentTokens); + await teslaSolarChargerContext.SaveChangesAsync().ConfigureAwait(false); + teslaSolarChargerContext.TeslaTokens.Add(new TeslaToken { AccessToken = token.AccessToken, RefreshToken = token.RefreshToken, IdToken = token.IdToken, - ExpiresAtUtc = _dateTimeProvider.UtcNow().AddSeconds(token.ExpiresIn), + ExpiresAtUtc = dateTimeProvider.UtcNow().AddSeconds(token.ExpiresIn), Region = token.Region, }); - await _teslaSolarChargerContext.SaveChangesAsync().ConfigureAwait(false); + await teslaSolarChargerContext.SaveChangesAsync().ConfigureAwait(false); } public async Task> GetFleetApiTokenState() { - if (!_configurationWrapper.UseFleetApi()) + if (!configurationWrapper.UseFleetApi()) { return new DtoValue(FleetApiTokenState.NotNeeded); } - var isCurrentRefreshTokenUnauthorized = await _teslaSolarChargerContext.TscConfigurations - .Where(c => c.Key == _constants.TokenRefreshUnauthorized) + var isCurrentRefreshTokenUnauthorized = await teslaSolarChargerContext.TscConfigurations + .Where(c => c.Key == constants.TokenRefreshUnauthorized) .AnyAsync().ConfigureAwait(false); if (isCurrentRefreshTokenUnauthorized) { return new DtoValue(FleetApiTokenState.TokenUnauthorized); } - var hasCurrentTokenMissingScopes = await _teslaSolarChargerContext.TscConfigurations - .Where(c => c.Key == _constants.TokenMissingScopes) + var hasCurrentTokenMissingScopes = await teslaSolarChargerContext.TscConfigurations + .Where(c => c.Key == constants.TokenMissingScopes) .AnyAsync().ConfigureAwait(false); if (hasCurrentTokenMissingScopes) { return new DtoValue(FleetApiTokenState.MissingScopes); } - var token = await _teslaSolarChargerContext.TeslaTokens.FirstOrDefaultAsync().ConfigureAwait(false); + var token = await teslaSolarChargerContext.TeslaTokens.FirstOrDefaultAsync().ConfigureAwait(false); if (token != null) { - return new DtoValue(token.ExpiresAtUtc < _dateTimeProvider.UtcNow() ? FleetApiTokenState.Expired : FleetApiTokenState.UpToDate); + return new DtoValue(token.ExpiresAtUtc < dateTimeProvider.UtcNow() ? FleetApiTokenState.Expired : FleetApiTokenState.UpToDate); } - var tokenRequestedDateString = await _teslaSolarChargerContext.TscConfigurations - .Where(c => c.Key == _constants.FleetApiTokenRequested) + var tokenRequestedDateString = await teslaSolarChargerContext.TscConfigurations + .Where(c => c.Key == constants.FleetApiTokenRequested) .Select(c => c.Value) .FirstOrDefaultAsync().ConfigureAwait(false); if (tokenRequestedDateString == null) @@ -416,7 +397,7 @@ public async Task> GetFleetApiTokenState() return new DtoValue(FleetApiTokenState.NotRequested); } var tokenRequestedDate = DateTime.Parse(tokenRequestedDateString, null, DateTimeStyles.RoundtripKind); - var currentDate = _dateTimeProvider.UtcNow(); + var currentDate = dateTimeProvider.UtcNow(); if (tokenRequestedDate < currentDate.AddMinutes(-5)) { return new DtoValue(FleetApiTokenState.TokenRequestExpired); @@ -426,28 +407,28 @@ public async Task> GetFleetApiTokenState() private async Task GetAccessTokenAndRefreshWhenNeededAsync() { - _logger.LogTrace("{method}()", nameof(GetAccessTokenAndRefreshWhenNeededAsync)); - var token = await _teslaSolarChargerContext.TeslaTokens + logger.LogTrace("{method}()", nameof(GetAccessTokenAndRefreshWhenNeededAsync)); + var token = await teslaSolarChargerContext.TeslaTokens .OrderByDescending(t => t.ExpiresAtUtc) .FirstAsync().ConfigureAwait(false); var minimumTokenLifeTime = TimeSpan.FromMinutes(5); - var isCurrentRefreshTokenUnauthorized = await _teslaSolarChargerContext.TscConfigurations - .Where(c => c.Key == _constants.TokenRefreshUnauthorized) + var isCurrentRefreshTokenUnauthorized = await teslaSolarChargerContext.TscConfigurations + .Where(c => c.Key == constants.TokenRefreshUnauthorized) .AnyAsync().ConfigureAwait(false); if (isCurrentRefreshTokenUnauthorized) { - _logger.LogError("Token is unauthorized"); + logger.LogError("Token is unauthorized"); throw new InvalidDataException("Current Tesla Fleet Api Token is unauthorized"); } - if (token.ExpiresAtUtc < (_dateTimeProvider.UtcNow() + minimumTokenLifeTime)) + if (token.ExpiresAtUtc < (dateTimeProvider.UtcNow() + minimumTokenLifeTime)) { - _logger.LogInformation("Token is expired. Getting new token."); + logger.LogInformation("Token is expired. Getting new token."); using var httpClient = new HttpClient(); var tokenUrl = "https://auth.tesla.com/oauth2/v3/token"; var requestData = new Dictionary { { "grant_type", "refresh_token" }, - { "client_id", _configurationWrapper.FleetApiClientId() }, + { "client_id", configurationWrapper.FleetApiClientId() }, { "refresh_token", token.RefreshToken }, }; var encodedContent = new FormUrlEncodedContent(requestData); @@ -456,7 +437,7 @@ private async Task GetAccessTokenAndRefreshWhenNeededAsync() var responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); if (!response.IsSuccessStatusCode) { - await _backendApiService.PostErrorInformation(nameof(TeslaFleetApiService), nameof(SendCommandToTeslaApi), + await backendApiService.PostErrorInformation(nameof(TeslaFleetApiService), nameof(SendCommandToTeslaApi), $"Refreshing token did result in non success status code. Response status code: {response.StatusCode} Response string: {responseString}").ConfigureAwait(false); await HandleNonSuccessTeslaApiStatusCodes(response.StatusCode, token, responseString).ConfigureAwait(false); } @@ -465,45 +446,58 @@ await _backendApiService.PostErrorInformation(nameof(TeslaFleetApiService), name token.AccessToken = newToken.AccessToken; token.RefreshToken = newToken.RefreshToken; token.IdToken = newToken.IdToken; - token.ExpiresAtUtc = _dateTimeProvider.UtcNow().AddSeconds(newToken.ExpiresIn); - await _teslaSolarChargerContext.SaveChangesAsync().ConfigureAwait(false); - _logger.LogInformation("New Token saved to database."); + token.ExpiresAtUtc = dateTimeProvider.UtcNow().AddSeconds(newToken.ExpiresIn); + await teslaSolarChargerContext.SaveChangesAsync().ConfigureAwait(false); + logger.LogInformation("New Token saved to database."); } return token; } private async Task HandleNonSuccessTeslaApiStatusCodes(HttpStatusCode statusCode, TeslaToken token, - string responseString) + string responseString, string? vin = null) { - _logger.LogTrace("{method}({statusCode}, {token}, {responseString})", nameof(HandleNonSuccessTeslaApiStatusCodes), statusCode, token, responseString); - switch (statusCode) - { - case HttpStatusCode.Unauthorized: - _logger.LogError( - "Your token or refresh token is invalid. Very likely you have changed your Tesla password."); - _teslaSolarChargerContext.TeslaTokens.Remove(token); - _teslaSolarChargerContext.TscConfigurations.Add(new TscConfiguration() - { - Key = _constants.TokenRefreshUnauthorized, - Value = responseString, - }); - break; - case HttpStatusCode.Forbidden: - _logger.LogError("You did not select all scopes, so TSC can't send commands to your car."); - _teslaSolarChargerContext.TeslaTokens.Remove(token); - _teslaSolarChargerContext.TscConfigurations.Add(new TscConfiguration() - { - Key = _constants.TokenMissingScopes, - Value = responseString, - }); - break; - default: - _logger.LogWarning( - "Staus Code {statusCode} is currently not handled, look into https://developer.tesla.com/docs/fleet-api#response-codes to check status code information", - statusCode); - return; + logger.LogTrace("{method}({statusCode}, {token}, {responseString})", nameof(HandleNonSuccessTeslaApiStatusCodes), statusCode, token, responseString); + if (statusCode == HttpStatusCode.Unauthorized) + { + logger.LogError( + "Your token or refresh token is invalid. Very likely you have changed your Tesla password."); + teslaSolarChargerContext.TeslaTokens.Remove(token); + teslaSolarChargerContext.TscConfigurations.Add(new TscConfiguration() + { + Key = constants.TokenRefreshUnauthorized, Value = responseString, + }); + } + else if (statusCode == HttpStatusCode.Forbidden) + { + logger.LogError("You did not select all scopes, so TSC can't send commands to your car."); + teslaSolarChargerContext.TeslaTokens.Remove(token); + teslaSolarChargerContext.TscConfigurations.Add(new TscConfiguration() + { + Key = constants.TokenMissingScopes, Value = responseString, + }); + } + else if (statusCode == HttpStatusCode.InternalServerError + && responseString.Contains("vehicle rejected request: your public key has not been paired with the vehicle")) + { + logger.LogError("Vehicle {vin} is not paired with TSC. Add The public key to the vehicle", vin); + var notPairedKey = string.Format(constants.VehicleNotPaired, vin); + var tscConfig = await teslaSolarChargerContext.TscConfigurations + .FirstOrDefaultAsync(c => c.Key == notPairedKey).ConfigureAwait(false); + if (tscConfig == default) + { + tscConfig = new TscConfiguration() { Key = notPairedKey, }; + teslaSolarChargerContext.TscConfigurations.Add(tscConfig); + } + tscConfig.Value = dateTimeProvider.UtcNow().ToString("o"); + } + else + { + logger.LogWarning( + "Staus Code {statusCode} is currently not handled, look into https://developer.tesla.com/docs/fleet-api#response-codes to check status code information", + statusCode); + return; } - await _teslaSolarChargerContext.SaveChangesAsync().ConfigureAwait(false); + await teslaSolarChargerContext.SaveChangesAsync().ConfigureAwait(false); } } diff --git a/TeslaSolarCharger/Shared/Dtos/IndexRazor/CarValues/DtoCarBaseStates.cs b/TeslaSolarCharger/Shared/Dtos/IndexRazor/CarValues/DtoCarBaseStates.cs index ca8b94d99..631eed220 100644 --- a/TeslaSolarCharger/Shared/Dtos/IndexRazor/CarValues/DtoCarBaseStates.cs +++ b/TeslaSolarCharger/Shared/Dtos/IndexRazor/CarValues/DtoCarBaseStates.cs @@ -19,6 +19,7 @@ public class DtoCarBaseStates public bool IsAutoFullSpeedCharging { get; set; } public bool? IsHealthy { get; set; } public bool ChargingNotPlannedDueToNoSpotPricesAvailable { get; set; } + public bool VehicleNotPaired { get; set; } public List ChargeInformation { get; set; } = new(); public CarStateEnum? State { get; set; } public List ChargingSlots { get; set; } = new(); From 276a5fc9ea4deb6149446c8bb120e6078826ae20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Fri, 22 Dec 2023 20:57:23 +0100 Subject: [PATCH 13/50] feat(TeslaFleetApiService): make using tesla proxy optional --- TeslaSolarCharger.Model/Enums/TeslaFleetApiRegion.cs | 1 + TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs | 8 +++++++- TeslaSolarCharger/Server/appsettings.Development.json | 1 + TeslaSolarCharger/Server/appsettings.json | 1 + .../Shared/Contracts/IConfigurationWrapper.cs | 1 + TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs | 7 +++++++ 6 files changed, 18 insertions(+), 1 deletion(-) diff --git a/TeslaSolarCharger.Model/Enums/TeslaFleetApiRegion.cs b/TeslaSolarCharger.Model/Enums/TeslaFleetApiRegion.cs index f58546ba3..a7a5079ad 100644 --- a/TeslaSolarCharger.Model/Enums/TeslaFleetApiRegion.cs +++ b/TeslaSolarCharger.Model/Enums/TeslaFleetApiRegion.cs @@ -4,4 +4,5 @@ public enum TeslaFleetApiRegion { Emea, NorthAmerica, + China, } diff --git a/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs b/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs index 2a230dc94..297c9bb56 100644 --- a/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs +++ b/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs @@ -17,6 +17,7 @@ using TeslaSolarCharger.Shared.Enums; using TeslaSolarCharger.SharedBackend.Contracts; using TeslaSolarCharger.SharedBackend.Dtos; +using static System.Net.WebRequestMethods; using Car = TeslaSolarCharger.Shared.Dtos.Settings.Car; namespace TeslaSolarCharger.Server.Services; @@ -285,11 +286,16 @@ await backendApiService.PostErrorInformation(nameof(TeslaFleetApiService), nameo private string GetFleetApiBaseUrl(TeslaFleetApiRegion region, bool useProxyBaseUrl) { - if (useProxyBaseUrl) + if (useProxyBaseUrl && configurationWrapper.UseFleetApiProxy()) { var configUrl = configurationWrapper.GetFleetApiBaseUrl(); return configUrl ?? throw new KeyNotFoundException("Could not get Tesla HTTP proxy address"); } + + if (region == TeslaFleetApiRegion.China) + { + return "https://fleet-api.prd.cn.vn.cloud.tesla.cn"; + } var regionCode = region switch { TeslaFleetApiRegion.Emea => "eu", diff --git a/TeslaSolarCharger/Server/appsettings.Development.json b/TeslaSolarCharger/Server/appsettings.Development.json index 9eb11a115..dc36ba250 100644 --- a/TeslaSolarCharger/Server/appsettings.Development.json +++ b/TeslaSolarCharger/Server/appsettings.Development.json @@ -57,6 +57,7 @@ "AllowCORS": true, "DisplayApiRequestCounter": true, "UseFleetApi": true, + "UseFleetApiProxy": true, "GridPriceProvider": { "EnergyProvider": "Tibber", "Octopus": { diff --git a/TeslaSolarCharger/Server/appsettings.json b/TeslaSolarCharger/Server/appsettings.json index aa9d88136..c974f9a03 100644 --- a/TeslaSolarCharger/Server/appsettings.json +++ b/TeslaSolarCharger/Server/appsettings.json @@ -63,6 +63,7 @@ "FleetApiClientId": "f29f71d6285a-4873-8b6b-80f15854892e", "BackendApiBaseUrl": "https://www.teslasolarcharger.de/api/", "TeslaFleetApiBaseUrl": "https://www.teslasolarcharger.de/teslaproxy/", + "UseFleetApiProxy": false, "AwattarBaseUrl": "https://api.awattar.de/v1/marketdata", "GridPriceProvider": { "EnergyProvider": "FixedPrice", diff --git a/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs b/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs index 008bd722d..6ab290657 100644 --- a/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs +++ b/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs @@ -92,4 +92,5 @@ public interface IConfigurationWrapper bool IsDevelopmentEnvironment(); string GetAwattarBaseUrl(); string? GetFleetApiBaseUrl(); + bool UseFleetApiProxy(); } diff --git a/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs b/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs index 1578aa476..f8f4591bf 100644 --- a/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs +++ b/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs @@ -120,6 +120,13 @@ public bool UseFleetApi() return value; } + public bool UseFleetApiProxy() + { + var environmentVariableName = "UseFleetApiProxy"; + var value = _configuration.GetValue(environmentVariableName); + return value; + } + public string BackendApiBaseUrl() { var environmentVariableName = "BackendApiBaseUrl"; From 4b8c98391ab399104be391ea9c9055fb5f9f2d16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Fri, 22 Dec 2023 22:20:30 +0100 Subject: [PATCH 14/50] feat(BackendIssueValidation): use bootstrap alerts --- .../Client/Components/BackendIssueValidation.razor | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/TeslaSolarCharger/Client/Components/BackendIssueValidation.razor b/TeslaSolarCharger/Client/Components/BackendIssueValidation.razor index 8b05e2091..b9c9ef6d8 100644 --- a/TeslaSolarCharger/Client/Components/BackendIssueValidation.razor +++ b/TeslaSolarCharger/Client/Components/BackendIssueValidation.razor @@ -11,12 +11,12 @@

Issues:

@foreach (var issue in _issues) { -
"yellow", - _ => "white", - })"> +
"warning", + _ => "info", + })" role="alert">
@issue.IssueMessage
@if (issue.PossibleSolutions.Length > 0) { From 6698c325b9b4fde374b83d87b2348ae3ad9c058e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Fri, 22 Dec 2023 22:40:57 +0100 Subject: [PATCH 15/50] feat(TeslaFleetApiService): add test API button --- TeslaSolarCharger/Client/Pages/Index.razor | 52 +++++++++++++++++++ .../Server/Controllers/FleetApiController.cs | 5 ++ .../Contracts/ITeslaFleetApiService.cs | 8 +-- .../Server/Services/TeslaFleetApiService.cs | 42 ++++++++++++++- 4 files changed, 102 insertions(+), 5 deletions(-) diff --git a/TeslaSolarCharger/Client/Pages/Index.razor b/TeslaSolarCharger/Client/Pages/Index.razor index e8a4ac2b8..64cda3048 100644 --- a/TeslaSolarCharger/Client/Pages/Index.razor +++ b/TeslaSolarCharger/Client/Pages/Index.razor @@ -155,6 +155,42 @@ else @car.HomeChargePower W
+ @if (_usingFleetApi == true) + { +
+
+ +
+
+ @if (_isFleetApiWorkingForCar.Any(d => d.Key == car.CarId)) + { + @if (_isFleetApiWorkingForCar[car.CarId] == true) + { + + } + else + { + + } + } +
+
+ + } @if (car.VehicleNotPaired) { \ No newline at end of file diff --git a/TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj b/TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj index dcae85739..713b3ecb0 100644 --- a/TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj +++ b/TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj @@ -16,7 +16,6 @@ - diff --git a/TeslaSolarCharger/Client/_Imports.razor b/TeslaSolarCharger/Client/_Imports.razor index 835888fdf..0be342952 100644 --- a/TeslaSolarCharger/Client/_Imports.razor +++ b/TeslaSolarCharger/Client/_Imports.razor @@ -8,7 +8,5 @@ @using Microsoft.JSInterop @using TeslaSolarCharger.Client @using TeslaSolarCharger.Client.Shared -@using Blazored.Toast -@using Blazored.Toast.Services @using TeslaSolarCharger.Client.Components @using MudBlazor \ No newline at end of file diff --git a/TeslaSolarCharger/Client/wwwroot/index.html b/TeslaSolarCharger/Client/wwwroot/index.html index caa4acc41..0b3fa73bf 100644 --- a/TeslaSolarCharger/Client/wwwroot/index.html +++ b/TeslaSolarCharger/Client/wwwroot/index.html @@ -6,6 +6,7 @@ TeslaSolarCharger + @@ -15,7 +16,6 @@ - From d214c65746fd8581743ce8483668fc67c3bdc2c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Mon, 1 Jan 2024 17:12:17 +0100 Subject: [PATCH 25/50] feat(BaseConfigruationRazor): use MudExpansion Panel --- .../Client/Pages/BaseConfiguration.razor | 81 +++++-------------- 1 file changed, 22 insertions(+), 59 deletions(-) diff --git a/TeslaSolarCharger/Client/Pages/BaseConfiguration.razor b/TeslaSolarCharger/Client/Pages/BaseConfiguration.razor index 230cb21a3..1ce2a070a 100644 --- a/TeslaSolarCharger/Client/Pages/BaseConfiguration.razor +++ b/TeslaSolarCharger/Client/Pages/BaseConfiguration.razor @@ -447,30 +447,8 @@ else
-
The following values need no change. Please only change values here if you know what you are doing.
- - -
- Expand - -
-
- -
- Collapse - -
-
- -
+ + + LabelText="Solar plant adjustment interval" + UnitText="s" + HelpText=""> @@ -506,7 +484,7 @@ else -
+
@if (_fleetApiTokenState == FleetApiTokenState.NotNeeded) { @@ -515,12 +493,12 @@ else UnitText="" HelpText="You can use the name of the container and the default port even though you changed the external port."> - +
-
+
} - + + LabelText="TeslaMate Database Server Port" + UnitText="" + HelpText="You can use the internal port of the TeslaMate database container"> -
+
+ LabelText="Mosquito servername" + UnitText="" + HelpText=""> @@ -557,19 +535,18 @@ else -
+
+ LabelText="HomeBatteryPowerInversion Url" + UnitText="" + HelpText="Use this if you have to dynamically invert the home battery power. Note: Only 0 and 1 are allowed as response. As far as I know this is only needed with Sungrow Inverters."> -
-
-
+ + @@ -617,20 +594,6 @@ else } } - private string _collapsedColor = "LightGray"; - private string _expandedColor = "LightGray"; - private string _hoverColor = "LightGray"; - private bool _isAnimated = true; - private bool _isCollapseDisabled = false; - private bool _isCollapsed = true; - private bool _showOverflow = false; - private int _height = 0; private string _correctionFactorHelpText = "Use this to correct the returned value. E.g. if the returned value is 1 but should bei -1 insert -1"; - - private Task OnCollapsed(bool state) - { - _isCollapsed = state; - return Task.CompletedTask; - } } From 6fbc9c3a44b9b82d12b5f8552926dd80f8fcad02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Mon, 1 Jan 2024 17:23:06 +0100 Subject: [PATCH 26/50] feat(IndexRazor): use MusExpansionPanel for car details --- .../Client/Pages/BaseConfiguration.razor | 1 - TeslaSolarCharger/Client/Pages/Index.razor | 41 +++++-------------- .../Client/TeslaSolarCharger.Client.csproj | 1 - 3 files changed, 10 insertions(+), 33 deletions(-) diff --git a/TeslaSolarCharger/Client/Pages/BaseConfiguration.razor b/TeslaSolarCharger/Client/Pages/BaseConfiguration.razor index 1ce2a070a..a53341f91 100644 --- a/TeslaSolarCharger/Client/Pages/BaseConfiguration.razor +++ b/TeslaSolarCharger/Client/Pages/BaseConfiguration.razor @@ -3,7 +3,6 @@ @using TeslaSolarCharger.Shared.Dtos.BaseConfiguration @using TeslaSolarCharger.Shared @using TeslaSolarCharger.Shared.Enums -@using Majorsoft.Blazor.Components.Collapse @using TeslaSolarCharger.Shared.Dtos @inject HttpClient HttpClient @inject NavigationManager NavigationManager diff --git a/TeslaSolarCharger/Client/Pages/Index.razor b/TeslaSolarCharger/Client/Pages/Index.razor index 18ad17cc8..6fad3948c 100644 --- a/TeslaSolarCharger/Client/Pages/Index.razor +++ b/TeslaSolarCharger/Client/Pages/Index.razor @@ -7,7 +7,6 @@ @using TeslaSolarCharger.Shared.Dtos.IndexRazor.PvValues @using TeslaSolarCharger.Shared.Enums @using TeslaSolarCharger.Shared.Resources -@using Majorsoft.Blazor.Components.Collapse @using TeslaSolarCharger.Shared.Dtos.Settings @using TeslaSolarCharger.Shared.Dtos.Table @using System.Diagnostics @@ -363,35 +362,15 @@ else

} - - - -
- More car details: - -
-
- -
- Collapse - -
-
- + + + @if (_newCarDetailStates.Any(c => c.Key == car.CarId)) { } - -
+ +
}
@@ -615,16 +594,16 @@ else _serverTimeZoneDisplayName = dtoServerTimeZoneId?.Value; } - private async Task OnCollapseChanged(bool changedToCollapse, int carId) + private async Task OnCollapseChanged(bool changedToExpanded, int carId) { - if (changedToCollapse) + if (changedToExpanded) { - _collapsedCarDetails.Add(carId); + _collapsedCarDetails.Remove(carId); + _newCarDetailStates.Remove(carId); } else { - _collapsedCarDetails.Remove(carId); - _newCarDetailStates.Remove(carId); + _collapsedCarDetails.Add(carId); } await RefreshAllVisableCarDetails().ConfigureAwait(false); } diff --git a/TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj b/TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj index 713b3ecb0..f56101854 100644 --- a/TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj +++ b/TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj @@ -16,7 +16,6 @@ - From 2d750a338ad7269cda09d04370f1c9be871b2f89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Tue, 2 Jan 2024 01:46:27 +0100 Subject: [PATCH 27/50] refactor(chore): improve enable proxy experience --- .../Contracts/ITeslaSolarChargerContext.cs | 1 + .../Entities/TeslaSolarCharger/Car.cs | 11 + .../TeslaSolarChargerContext.cs | 1 + .../20240102001022_AddCarsTable.Designer.cs | 246 ++++++++++++++++++ .../Migrations/20240102001022_AddCarsTable.cs | 35 +++ .../TeslaSolarChargerContextModelSnapshot.cs | 19 +- .../TeslaSolarCharger.Model.csproj | 4 - .../Contracts/IConstants.cs | 1 - .../Values/Constants.cs | 1 - .../Components/BackendIssueValidation.razor | 3 +- TeslaSolarCharger/Client/Pages/Index.razor | 91 ++++--- .../Client/TeslaSolarCharger.Client.csproj | 1 + TeslaSolarCharger/Client/wwwroot/css/app.css | 4 + .../Server/Contracts/IConfigJsonService.cs | 1 + .../Server/Contracts/ITeslaService.cs | 3 +- .../Server/Controllers/FleetApiController.cs | 37 ++- .../Server/Controllers/IndexController.cs | 3 +- TeslaSolarCharger/Server/Program.cs | 1 + .../PossibleIssues/PossibleIssues.cs | 2 +- .../ApiServices/Contracts/IIndexService.cs | 3 +- .../Services/ApiServices/IndexService.cs | 24 +- .../Services/ChargeTimeCalculationService.cs | 1 + .../Server/Services/ConfigJsonService.cs | 28 ++ .../Contracts/ITeslaFleetApiService.cs | 1 + .../Server/Services/TeslaFleetApiService.cs | 30 ++- .../Server/Services/TeslamateApiService.cs | 1 - .../IndexRazor/CarValues/DtoCarBaseStates.cs | 2 +- TeslaSolarCharger/Shared/Dtos/Settings/Car.cs | 1 + .../Shared/Enums/TeslaCarFleetApiState.cs | 9 + .../Shared/Wrappers/ConfigurationWrapper.cs | 87 +++---- 30 files changed, 491 insertions(+), 161 deletions(-) create mode 100644 TeslaSolarCharger.Model/Entities/TeslaSolarCharger/Car.cs create mode 100644 TeslaSolarCharger.Model/Migrations/20240102001022_AddCarsTable.Designer.cs create mode 100644 TeslaSolarCharger.Model/Migrations/20240102001022_AddCarsTable.cs create mode 100644 TeslaSolarCharger/Shared/Enums/TeslaCarFleetApiState.cs diff --git a/TeslaSolarCharger.Model/Contracts/ITeslaSolarChargerContext.cs b/TeslaSolarCharger.Model/Contracts/ITeslaSolarChargerContext.cs index 67cff6814..3cca7c22d 100644 --- a/TeslaSolarCharger.Model/Contracts/ITeslaSolarChargerContext.cs +++ b/TeslaSolarCharger.Model/Contracts/ITeslaSolarChargerContext.cs @@ -17,5 +17,6 @@ public interface ITeslaSolarChargerContext DbSet SpotPrices { get; set; } DbSet TeslaTokens { get; set; } DbSet TscConfigurations { get; set; } + DbSet Cars { get; set; } void RejectChanges(); } diff --git a/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/Car.cs b/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/Car.cs new file mode 100644 index 000000000..00f012c34 --- /dev/null +++ b/TeslaSolarCharger.Model/Entities/TeslaSolarCharger/Car.cs @@ -0,0 +1,11 @@ +using TeslaSolarCharger.Model.Enums; +using TeslaSolarCharger.Shared.Enums; + +namespace TeslaSolarCharger.Model.Entities.TeslaSolarCharger; + +public class Car +{ + public int Id { get; set; } + public int TeslaMateCarId { get; set; } + public TeslaCarFleetApiState TeslaFleetApiState { get; set; } = TeslaCarFleetApiState.NotConfigured; +} diff --git a/TeslaSolarCharger.Model/EntityFramework/TeslaSolarChargerContext.cs b/TeslaSolarCharger.Model/EntityFramework/TeslaSolarChargerContext.cs index 86d618e2d..8fad4748a 100644 --- a/TeslaSolarCharger.Model/EntityFramework/TeslaSolarChargerContext.cs +++ b/TeslaSolarCharger.Model/EntityFramework/TeslaSolarChargerContext.cs @@ -14,6 +14,7 @@ public class TeslaSolarChargerContext : DbContext, ITeslaSolarChargerContext public DbSet SpotPrices { get; set; } = null!; public DbSet TeslaTokens { get; set; } = null!; public DbSet TscConfigurations { get; set; } = null!; + public DbSet Cars { get; set; } = null!; // ReSharper disable once UnassignedGetOnlyAutoProperty public string DbPath { get; } diff --git a/TeslaSolarCharger.Model/Migrations/20240102001022_AddCarsTable.Designer.cs b/TeslaSolarCharger.Model/Migrations/20240102001022_AddCarsTable.Designer.cs new file mode 100644 index 000000000..5579af392 --- /dev/null +++ b/TeslaSolarCharger.Model/Migrations/20240102001022_AddCarsTable.Designer.cs @@ -0,0 +1,246 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using TeslaSolarCharger.Model.EntityFramework; + +#nullable disable + +namespace TeslaSolarCharger.Model.Migrations +{ + [DbContext(typeof(TeslaSolarChargerContext))] + [Migration("20240102001022_AddCarsTable")] + partial class AddCarsTable + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.0"); + + modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.CachedCarState", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CarId") + .HasColumnType("INTEGER"); + + b.Property("CarStateJson") + .HasColumnType("TEXT"); + + b.Property("Key") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("LastUpdated") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("CachedCarStates"); + }); + + modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.Car", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("TeslaFleetApiState") + .HasColumnType("INTEGER"); + + b.Property("TeslaMateCarId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Cars"); + }); + + modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.ChargePrice", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AddSpotPriceToGridPrice") + .HasColumnType("INTEGER"); + + b.Property("EnergyProvider") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(6); + + b.Property("EnergyProviderConfiguration") + .HasColumnType("TEXT"); + + b.Property("GridPrice") + .HasColumnType("TEXT"); + + b.Property("SolarPrice") + .HasColumnType("TEXT"); + + b.Property("SpotPriceCorrectionFactor") + .HasColumnType("TEXT"); + + b.Property("ValidSince") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("ChargePrices"); + }); + + modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.HandledCharge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AverageSpotPrice") + .HasColumnType("TEXT"); + + b.Property("CalculatedPrice") + .HasColumnType("TEXT"); + + b.Property("CarId") + .HasColumnType("INTEGER"); + + b.Property("ChargingProcessId") + .HasColumnType("INTEGER"); + + b.Property("UsedGridEnergy") + .HasColumnType("TEXT"); + + b.Property("UsedSolarEnergy") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("HandledCharges"); + }); + + modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.PowerDistribution", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChargingPower") + .HasColumnType("INTEGER"); + + b.Property("GridProportion") + .HasColumnType("REAL"); + + b.Property("HandledChargeId") + .HasColumnType("INTEGER"); + + b.Property("PowerFromGrid") + .HasColumnType("INTEGER"); + + b.Property("TimeStamp") + .HasColumnType("TEXT"); + + b.Property("UsedWattHours") + .HasColumnType("REAL"); + + b.HasKey("Id"); + + b.HasIndex("HandledChargeId"); + + b.ToTable("PowerDistributions"); + }); + + modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.SpotPrice", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("EndDate") + .HasColumnType("TEXT"); + + b.Property("Price") + .HasColumnType("TEXT"); + + b.Property("StartDate") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("SpotPrices"); + }); + + modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.TeslaToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AccessToken") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ExpiresAtUtc") + .HasColumnType("TEXT"); + + b.Property("IdToken") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RefreshToken") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Region") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("TeslaTokens"); + }); + + modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.TscConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Key") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Key") + .IsUnique(); + + b.ToTable("TscConfigurations"); + }); + + modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.PowerDistribution", b => + { + b.HasOne("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.HandledCharge", "HandledCharge") + .WithMany("PowerDistributions") + .HasForeignKey("HandledChargeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("HandledCharge"); + }); + + modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.HandledCharge", b => + { + b.Navigation("PowerDistributions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/TeslaSolarCharger.Model/Migrations/20240102001022_AddCarsTable.cs b/TeslaSolarCharger.Model/Migrations/20240102001022_AddCarsTable.cs new file mode 100644 index 000000000..3f0842e1c --- /dev/null +++ b/TeslaSolarCharger.Model/Migrations/20240102001022_AddCarsTable.cs @@ -0,0 +1,35 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace TeslaSolarCharger.Model.Migrations +{ + /// + public partial class AddCarsTable : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Cars", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + TeslaMateCarId = table.Column(type: "INTEGER", nullable: false), + TeslaFleetApiState = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Cars", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Cars"); + } + } +} diff --git a/TeslaSolarCharger.Model/Migrations/TeslaSolarChargerContextModelSnapshot.cs b/TeslaSolarCharger.Model/Migrations/TeslaSolarChargerContextModelSnapshot.cs index 3bc817e36..0aff8881b 100644 --- a/TeslaSolarCharger.Model/Migrations/TeslaSolarChargerContextModelSnapshot.cs +++ b/TeslaSolarCharger.Model/Migrations/TeslaSolarChargerContextModelSnapshot.cs @@ -15,7 +15,7 @@ partial class TeslaSolarChargerContextModelSnapshot : ModelSnapshot protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "7.0.11"); + modelBuilder.HasAnnotation("ProductVersion", "8.0.0"); modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.CachedCarState", b => { @@ -41,6 +41,23 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("CachedCarStates"); }); + modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.Car", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("TeslaFleetApiState") + .HasColumnType("INTEGER"); + + b.Property("TeslaMateCarId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Cars"); + }); + modelBuilder.Entity("TeslaSolarCharger.Model.Entities.TeslaSolarCharger.ChargePrice", b => { b.Property("Id") diff --git a/TeslaSolarCharger.Model/TeslaSolarCharger.Model.csproj b/TeslaSolarCharger.Model/TeslaSolarCharger.Model.csproj index e34d61b58..ef9fbd6ae 100644 --- a/TeslaSolarCharger.Model/TeslaSolarCharger.Model.csproj +++ b/TeslaSolarCharger.Model/TeslaSolarCharger.Model.csproj @@ -24,8 +24,4 @@ - - - - diff --git a/TeslaSolarCharger.SharedBackend/Contracts/IConstants.cs b/TeslaSolarCharger.SharedBackend/Contracts/IConstants.cs index 74c67720e..7c669fd2d 100644 --- a/TeslaSolarCharger.SharedBackend/Contracts/IConstants.cs +++ b/TeslaSolarCharger.SharedBackend/Contracts/IConstants.cs @@ -15,5 +15,4 @@ public interface IConstants string FleetApiTokenRequested { get; } string TokenRefreshUnauthorized { get; } string TokenMissingScopes { get; } - string VehicleNotPaired { get; } } diff --git a/TeslaSolarCharger.SharedBackend/Values/Constants.cs b/TeslaSolarCharger.SharedBackend/Values/Constants.cs index cc6e08c7c..b38907b48 100644 --- a/TeslaSolarCharger.SharedBackend/Values/Constants.cs +++ b/TeslaSolarCharger.SharedBackend/Values/Constants.cs @@ -14,5 +14,4 @@ public class Constants : IConstants public string FleetApiTokenRequested => "FleetApiTokenRequested"; public string TokenRefreshUnauthorized => "TokenRefreshUnauthorized"; public string TokenMissingScopes => "TokenMissingScopes"; - public string VehicleNotPaired => "VehicleNotPaired_{0}"; } diff --git a/TeslaSolarCharger/Client/Components/BackendIssueValidation.razor b/TeslaSolarCharger/Client/Components/BackendIssueValidation.razor index b9c9ef6d8..993771022 100644 --- a/TeslaSolarCharger/Client/Components/BackendIssueValidation.razor +++ b/TeslaSolarCharger/Client/Components/BackendIssueValidation.razor @@ -20,7 +20,8 @@
@issue.IssueMessage
@if (issue.PossibleSolutions.Length > 0) { -
Possible solutions:
+
+
Possible solutions:
    @foreach (var solution in issue.PossibleSolutions) { diff --git a/TeslaSolarCharger/Client/Pages/Index.razor b/TeslaSolarCharger/Client/Pages/Index.razor index 6fad3948c..8d508d6a5 100644 --- a/TeslaSolarCharger/Client/Pages/Index.razor +++ b/TeslaSolarCharger/Client/Pages/Index.razor @@ -154,49 +154,50 @@ else @car.HomeChargePower W
- @if (_usingFleetApi == true) + + @if (_usingFleetApi == true && _usingFleetApiProxy == true) { -
-
- -
-
- @if (_isFleetApiWorkingForCar.Any(d => d.Key == car.CarId)) + @if (_testingFleetApiCarIds.Any(i => i == car.CarId)) + { + + Testing Fleet API access... + } + +
+ @if (_isFleetApiWorkingForCar.Any(d => d.Key == car.CarId)) + { + @if (_isFleetApiWorkingForCar[car.CarId] == true) { - @if (_isFleetApiWorkingForCar[car.CarId] == true) - { - - } - else - { - - } + } -
-
- - } - @if (car.VehicleNotPaired) - { - } +
@if (car.IsHealthy == false) { @@ -429,19 +430,13 @@ else private readonly List _savingCarIds = new(); private string _version = ""; private HashSet _collapsedCarDetails = new HashSet(); - private string _collapsedColor = "LightGray"; - private string _expandedColor = "LightGray"; - private string _hoverColor = "LightGray"; - private bool _isAnimated = true; - private bool _isCollapseDisabled = false; - private bool _showOverflow = false; - private int _height = 0; private DateTime? _serverTime; private string? _serverTimeZoneDisplayName; private bool? _shouldDisplayApiRequestCounter; private int? _apiRequestCount; private string _installationId = ""; private bool? _usingFleetApi; + private bool? _usingFleetApiProxy; private Dictionary _isFleetApiWorkingForCar = new(); private readonly HashSet _testingFleetApiCarIds = new(); @@ -461,6 +456,8 @@ else _isSolarEdgeInstallation = dtoSolarChargerInstallation?.Value; var usingFleetApi = await HttpClient.GetFromJsonAsync>("api/FleetApi/IsFleetApiEnabled").ConfigureAwait(false); _usingFleetApi = usingFleetApi?.Value; + var usingFleetApiProxy = await HttpClient.GetFromJsonAsync>("api/FleetApi/IsFleetApiProxyEnabled").ConfigureAwait(false); + _usingFleetApiProxy = usingFleetApiProxy?.Value; _version = await HttpClient.GetStringAsync("api/Hello/ProductVersion").ConfigureAwait(false); _installationId = await HttpClient.GetStringAsync("api/Hello/GetInstallationId").ConfigureAwait(false); foreach (var carBaseState in _carBaseStates!) @@ -661,9 +658,9 @@ else StateHasChanged(); } - private async Task OpenRegisterPublicKeyPage() + private async Task OpenRegisterPublicKeyPage(int carId) { - var response = await HttpClient.PostAsync("api/Index/ResetVehicleNotPaired", null).ConfigureAwait(false); + var response = await HttpClient.PostAsync($"api/Index/UpdateCarFleetApiState?carId={carId}&fleetApiState={TeslaCarFleetApiState.OpenedLinkButNotTested}", null).ConfigureAwait(false); NavigationManager.NavigateTo("https://tesla.com/_ak/www.teslasolarcharger.de"); } diff --git a/TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj b/TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj index f56101854..5b5c13c1e 100644 --- a/TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj +++ b/TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj @@ -30,6 +30,7 @@ + diff --git a/TeslaSolarCharger/Client/wwwroot/css/app.css b/TeslaSolarCharger/Client/wwwroot/css/app.css index 80c3aebf0..b49c63420 100644 --- a/TeslaSolarCharger/Client/wwwroot/css/app.css +++ b/TeslaSolarCharger/Client/wwwroot/css/app.css @@ -3,6 +3,10 @@ html, body { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; } +/*This is needed as MudBlazor somehow overrides default user agent list stype types*/ +ul { + list-style-type: disc; +} h1:focus { outline: none; diff --git a/TeslaSolarCharger/Server/Contracts/IConfigJsonService.cs b/TeslaSolarCharger/Server/Contracts/IConfigJsonService.cs index d8b1b503d..7f7e9d976 100644 --- a/TeslaSolarCharger/Server/Contracts/IConfigJsonService.cs +++ b/TeslaSolarCharger/Server/Contracts/IConfigJsonService.cs @@ -9,4 +9,5 @@ public interface IConfigJsonService Task AddCarIdsToSettings(); Task UpdateCarConfiguration(); Task UpdateAverageGridVoltage(); + Task AddCarsToTscDatabase(); } diff --git a/TeslaSolarCharger/Server/Contracts/ITeslaService.cs b/TeslaSolarCharger/Server/Contracts/ITeslaService.cs index ea364af13..21256b46c 100644 --- a/TeslaSolarCharger/Server/Contracts/ITeslaService.cs +++ b/TeslaSolarCharger/Server/Contracts/ITeslaService.cs @@ -1,4 +1,5 @@ -using TeslaSolarCharger.Shared.Enums; +using TeslaSolarCharger.Shared.Dtos; +using TeslaSolarCharger.Shared.Enums; namespace TeslaSolarCharger.Server.Contracts; diff --git a/TeslaSolarCharger/Server/Controllers/FleetApiController.cs b/TeslaSolarCharger/Server/Controllers/FleetApiController.cs index 3c71b36fd..ee4403095 100644 --- a/TeslaSolarCharger/Server/Controllers/FleetApiController.cs +++ b/TeslaSolarCharger/Server/Controllers/FleetApiController.cs @@ -8,30 +8,21 @@ namespace TeslaSolarCharger.Server.Controllers; -public class FleetApiController : ApiBaseController +public class FleetApiController( + ITeslaFleetApiService fleetApiService, + IBackendApiService backendApiService, + ITeslaService teslaService, + IConfigurationWrapper configurationWrapper) + : ApiBaseController { - private readonly ITeslaFleetApiService _fleetApiService; - private readonly IBackendApiService _backendApiService; - private readonly ITeslaService _teslaService; - private readonly IConfigurationWrapper _configurationWrapper; - - public FleetApiController(ITeslaFleetApiService fleetApiService, IBackendApiService backendApiService, ITeslaService teslaService, - IConfigurationWrapper configurationWrapper) - { - _fleetApiService = fleetApiService; - _backendApiService = backendApiService; - _teslaService = teslaService; - _configurationWrapper = configurationWrapper; - } - [HttpGet] - public Task> FleetApiTokenState() => _fleetApiService.GetFleetApiTokenState(); + public Task> FleetApiTokenState() => fleetApiService.GetFleetApiTokenState(); [HttpGet] - public Task> GetOauthUrl(string locale) => _backendApiService.StartTeslaOAuth(locale); + public Task> GetOauthUrl(string locale) => backendApiService.StartTeslaOAuth(locale); [HttpGet] - public Task RefreshFleetApiToken() => _fleetApiService.RefreshTokenAsync(); + public Task RefreshFleetApiToken() => fleetApiService.RefreshTokenAsync(); /// /// Note: This endpoint is only available in development environment @@ -40,15 +31,17 @@ public FleetApiController(ITeslaFleetApiService fleetApiService, IBackendApiServ [HttpGet] public Task SetChargeLimit(int carId, int percent) { - if (!_configurationWrapper.IsDevelopmentEnvironment()) + if (!configurationWrapper.IsDevelopmentEnvironment()) { throw new InvalidOperationException("This method is only available in development environment"); } - return _teslaService.SetChargeLimit(carId, percent); + return teslaService.SetChargeLimit(carId, percent); } [HttpGet] - public Task> TestFleetApiAccess(int carId) => _fleetApiService.TestFleetApiAccess(carId); + public Task> TestFleetApiAccess(int carId) => fleetApiService.TestFleetApiAccess(carId); + [HttpGet] + public DtoValue IsFleetApiEnabled() => fleetApiService.IsFleetApiEnabled(); [HttpGet] - public DtoValue IsFleetApiEnabled() => _fleetApiService.IsFleetApiEnabled(); + public DtoValue IsFleetApiProxyEnabled() => fleetApiService.IsFleetApiProxyEnabled(); } diff --git a/TeslaSolarCharger/Server/Controllers/IndexController.cs b/TeslaSolarCharger/Server/Controllers/IndexController.cs index 15fb9a57d..8b0a451f9 100644 --- a/TeslaSolarCharger/Server/Controllers/IndexController.cs +++ b/TeslaSolarCharger/Server/Controllers/IndexController.cs @@ -3,6 +3,7 @@ using TeslaSolarCharger.Shared.Dtos.IndexRazor.CarValues; using TeslaSolarCharger.Shared.Dtos.IndexRazor.PvValues; using TeslaSolarCharger.Shared.Dtos.Settings; +using TeslaSolarCharger.Shared.Enums; using TeslaSolarCharger.SharedBackend.Abstracts; namespace TeslaSolarCharger.Server.Controllers; @@ -42,5 +43,5 @@ public DtoCarTopicValues CarDetails(int carId) public List GetChargingSlots(int carId) => _indexService.GetChargingSlots(carId); [HttpPost] - public Task ResetVehicleNotPaired() => _indexService.ResetVehicleNotPaired(); + public Task UpdateCarFleetApiState(int carId, TeslaCarFleetApiState fleetApiState) => _indexService.UpdateCarFleetApiState(carId, fleetApiState); } diff --git a/TeslaSolarCharger/Server/Program.cs b/TeslaSolarCharger/Server/Program.cs index a933d2973..9a6cec98f 100644 --- a/TeslaSolarCharger/Server/Program.cs +++ b/TeslaSolarCharger/Server/Program.cs @@ -89,6 +89,7 @@ var configJsonService = app.Services.GetRequiredService(); await configJsonService.AddCarIdsToSettings().ConfigureAwait(false); + await configJsonService.AddCarsToTscDatabase().ConfigureAwait(false); await configJsonService.UpdateAverageGridVoltage().ConfigureAwait(false); diff --git a/TeslaSolarCharger/Server/Resources/PossibleIssues/PossibleIssues.cs b/TeslaSolarCharger/Server/Resources/PossibleIssues/PossibleIssues.cs index 0b6255f2d..ae65b3a96 100644 --- a/TeslaSolarCharger/Server/Resources/PossibleIssues/PossibleIssues.cs +++ b/TeslaSolarCharger/Server/Resources/PossibleIssues/PossibleIssues.cs @@ -162,7 +162,7 @@ public PossibleIssues(IssueKeys issueKeys) }, { issueKeys.FleetApiTokenNotReceived, CreateIssue("The Tesla token was not received, yet.", - IssueType.Error, + IssueType.Warning, "Getting the Token can take up to five minutes after submitting your password.", "If waiting five minutes does not help, open the Base Configuration and request a new token." ) diff --git a/TeslaSolarCharger/Server/Services/ApiServices/Contracts/IIndexService.cs b/TeslaSolarCharger/Server/Services/ApiServices/Contracts/IIndexService.cs index 3d3a59038..aa7b67e40 100644 --- a/TeslaSolarCharger/Server/Services/ApiServices/Contracts/IIndexService.cs +++ b/TeslaSolarCharger/Server/Services/ApiServices/Contracts/IIndexService.cs @@ -1,6 +1,7 @@ using TeslaSolarCharger.Shared.Dtos.IndexRazor.CarValues; using TeslaSolarCharger.Shared.Dtos.IndexRazor.PvValues; using TeslaSolarCharger.Shared.Dtos.Settings; +using TeslaSolarCharger.Shared.Enums; namespace TeslaSolarCharger.Server.Services.ApiServices.Contracts; @@ -15,5 +16,5 @@ public interface IIndexService DtoCarTopicValues GetCarDetails(int carId); List RecalculateAndGetChargingSlots(int carId); List GetChargingSlots(int carId); - Task ResetVehicleNotPaired(); + Task UpdateCarFleetApiState(int carId, TeslaCarFleetApiState fleetApiState); } diff --git a/TeslaSolarCharger/Server/Services/ApiServices/IndexService.cs b/TeslaSolarCharger/Server/Services/ApiServices/IndexService.cs index 5315d398e..3b79e1bd7 100644 --- a/TeslaSolarCharger/Server/Services/ApiServices/IndexService.cs +++ b/TeslaSolarCharger/Server/Services/ApiServices/IndexService.cs @@ -103,17 +103,9 @@ public async Task> GetCarBaseStatesOfEnabledCars() dtoCarBaseValues.ChargingNotPlannedDueToNoSpotPricesAvailable = await _chargeTimeCalculationService.IsLatestTimeToReachSocAfterLatestKnownChargePrice(enabledCar.Id).ConfigureAwait(false); } - - if (_configurationWrapper.UseFleetApi()) - { - var vin = await _teslamateContext.Cars.Where(c => c.Id == enabledCar.Id).Select(c => c.Vin).FirstOrDefaultAsync().ConfigureAwait(false); - var key = string.Format(_constants.VehicleNotPaired, vin); - if (!string.IsNullOrEmpty(vin) && - (await _teslaSolarChargerContext.TscConfigurations.AnyAsync(c => c.Key == key).ConfigureAwait(false))) - { - dtoCarBaseValues.VehicleNotPaired = true; - } - } + var vin = await _teslamateContext.Cars.Where(c => c.Id == enabledCar.Id).Select(c => c.Vin).FirstOrDefaultAsync().ConfigureAwait(false); + dtoCarBaseValues.FleetApiState = + await _teslaSolarChargerContext.Cars.Where(c => c.TeslaMateCarId == enabledCar.Id).Select(c => c.TeslaFleetApiState).SingleAsync().ConfigureAwait(false); dtoCarBaseValues.ChargeInformation = GenerateChargeInformation(enabledCar); @@ -318,13 +310,11 @@ public List GetChargingSlots(int carId) return car.CarState.PlannedChargingSlots; } - public async Task ResetVehicleNotPaired() + public async Task UpdateCarFleetApiState(int carId, TeslaCarFleetApiState fleetApiState) { - var key = string.Format(_constants.VehicleNotPaired, string.Empty); - var configs = await _teslaSolarChargerContext.TscConfigurations - .Where(c => c.Key.StartsWith(key)) - .ToListAsync().ConfigureAwait(false); - _teslaSolarChargerContext.TscConfigurations.RemoveRange(configs); + _logger.LogTrace("{method}({carId}, {fleetApiState})", nameof(UpdateCarFleetApiState), carId, fleetApiState); + var car = _teslaSolarChargerContext.Cars.First(c => c.TeslaMateCarId == carId); + car.TeslaFleetApiState = fleetApiState; await _teslaSolarChargerContext.SaveChangesAsync().ConfigureAwait(false); } diff --git a/TeslaSolarCharger/Server/Services/ChargeTimeCalculationService.cs b/TeslaSolarCharger/Server/Services/ChargeTimeCalculationService.cs index a3f7107c0..c9142609e 100644 --- a/TeslaSolarCharger/Server/Services/ChargeTimeCalculationService.cs +++ b/TeslaSolarCharger/Server/Services/ChargeTimeCalculationService.cs @@ -9,6 +9,7 @@ using TeslaSolarCharger.Shared.Dtos.Settings; using TeslaSolarCharger.Shared.Enums; using TeslaSolarCharger.SharedBackend.Contracts; +using Car = TeslaSolarCharger.Shared.Dtos.Settings.Car; namespace TeslaSolarCharger.Server.Services; diff --git a/TeslaSolarCharger/Server/Services/ConfigJsonService.cs b/TeslaSolarCharger/Server/Services/ConfigJsonService.cs index fba973b1a..4da74150d 100644 --- a/TeslaSolarCharger/Server/Services/ConfigJsonService.cs +++ b/TeslaSolarCharger/Server/Services/ConfigJsonService.cs @@ -10,6 +10,7 @@ using TeslaSolarCharger.Shared.Dtos.Settings; using TeslaSolarCharger.Shared.Enums; using TeslaSolarCharger.SharedBackend.Contracts; +using Car = TeslaSolarCharger.Shared.Dtos.Settings.Car; [assembly: InternalsVisibleTo("TeslaSolarCharger.Tests")] namespace TeslaSolarCharger.Server.Services; @@ -74,6 +75,7 @@ public async Task> GetCarsFromConfiguration() cars.Add(new Car() { Id = databaseCarConfiguration.CarId, + Vin = _teslamateContext.Cars.FirstOrDefault(c => c.Id == databaseCarConfiguration.CarId)?.Vin ?? string.Empty, CarConfiguration = configuration, CarState = new CarState(), }); @@ -188,6 +190,11 @@ public async Task UpdateCarConfiguration() } databaseConfig.CarStateJson = JsonConvert.SerializeObject(car.CarConfiguration); databaseConfig.LastUpdated = _dateTimeProvider.UtcNow(); + var databaseCar = await _teslaSolarChargerContext.Cars.FirstOrDefaultAsync(c => c.TeslaMateCarId == car.Id).ConfigureAwait(false); + if (databaseCar == default) + { + _teslaSolarChargerContext.Cars.Add(new Model.Entities.TeslaSolarCharger.Car() { TeslaMateCarId = car.Id, }); + } } await _teslaSolarChargerContext.SaveChangesAsync().ConfigureAwait(false); } @@ -231,6 +238,27 @@ public async Task AddCarIdsToSettings() _logger.LogDebug("All unset car configurations set."); } + public async Task AddCarsToTscDatabase() + { + var carsToManage = _settings.Cars.Where(c => c.CarConfiguration.ShouldBeManaged == true).ToList(); + foreach (var car in carsToManage) + { + var databaseCar = await _teslaSolarChargerContext.Cars.FirstOrDefaultAsync(c => c.Id == car.Id).ConfigureAwait(false); + if (databaseCar != default) + { + continue; + } + + databaseCar = new Model.Entities.TeslaSolarCharger.Car() + { + Id = car.Id, + TeslaMateCarId = car.Id, + }; + _teslaSolarChargerContext.Cars.Add(databaseCar); + } + await _teslaSolarChargerContext.SaveChangesAsync().ConfigureAwait(false); + } + private async Task AddCachedCarStatesToCars(List cars) { foreach (var car in cars) diff --git a/TeslaSolarCharger/Server/Services/Contracts/ITeslaFleetApiService.cs b/TeslaSolarCharger/Server/Services/Contracts/ITeslaFleetApiService.cs index eb5fb874c..7ff3e9394 100644 --- a/TeslaSolarCharger/Server/Services/Contracts/ITeslaFleetApiService.cs +++ b/TeslaSolarCharger/Server/Services/Contracts/ITeslaFleetApiService.cs @@ -12,4 +12,5 @@ public interface ITeslaFleetApiService Task OpenChargePortDoor(int carId); Task> TestFleetApiAccess(int carId); DtoValue IsFleetApiEnabled(); + DtoValue IsFleetApiProxyEnabled(); } diff --git a/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs b/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs index c0fa906e3..d464ca401 100644 --- a/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs +++ b/TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs @@ -94,8 +94,8 @@ public async Task WakeUpCar(int carId) var vin = await GetVinByCarId(carId).ConfigureAwait(false); var result = await SendCommandToTeslaApi(vin, WakeUpRequest).ConfigureAwait(false); await teslamateApiService.ResumeLogging(carId).ConfigureAwait(false); - - await Task.Delay(TimeSpan.FromSeconds(20)).ConfigureAwait(true); + //ToDo: Next line is never executed + await Task.Delay(TimeSpan.FromSeconds(20)).ConfigureAwait(false); } public async Task StopCharging(int carId) @@ -151,12 +151,15 @@ public async Task> TestFleetApiAccess(int carId) { logger.LogTrace("{method}({carId})", nameof(TestFleetApiAccess), carId); var vin = await GetVinByCarId(carId).ConfigureAwait(false); - var car = settings.Cars.First(c => c.Id == carId); + var inMemoryCar = settings.Cars.First(c => c.Id == carId); try { - await WakeUpCarIfNeeded(carId, car.CarState.State).ConfigureAwait(false); + await WakeUpCarIfNeeded(carId, inMemoryCar.CarState.State).ConfigureAwait(false); var result = await SendCommandToTeslaApi(vin, OpenChargePortDoorRequest).ConfigureAwait(false); var successResult = result?.Response?.Result == true; + var car = teslaSolarChargerContext.Cars.First(c => c.TeslaMateCarId == carId); + car.TeslaFleetApiState = successResult ? TeslaCarFleetApiState.Ok : TeslaCarFleetApiState.NotWorking; + await teslaSolarChargerContext.SaveChangesAsync().ConfigureAwait(false); return new DtoValue(successResult); } catch (Exception ex) @@ -175,6 +178,13 @@ public DtoValue IsFleetApiEnabled() return new DtoValue(isEnabled); } + public DtoValue IsFleetApiProxyEnabled() + { + logger.LogTrace("{method}", nameof(IsFleetApiProxyEnabled)); + var isEnabled = configurationWrapper.UseFleetApiProxy(); + return new DtoValue(isEnabled); + } + public async Task OpenChargePortDoor(int carId) { logger.LogTrace("{method}({carId})", nameof(OpenChargePortDoor), carId); @@ -526,15 +536,9 @@ private async Task HandleNonSuccessTeslaApiStatusCodes(HttpStatusCode statusCode && responseString.Contains("vehicle rejected request: your public key has not been paired with the vehicle")) { logger.LogError("Vehicle {vin} is not paired with TSC. Add The public key to the vehicle", vin); - var notPairedKey = string.Format(constants.VehicleNotPaired, vin); - var tscConfig = await teslaSolarChargerContext.TscConfigurations - .FirstOrDefaultAsync(c => c.Key == notPairedKey).ConfigureAwait(false); - if (tscConfig == default) - { - tscConfig = new TscConfiguration() { Key = notPairedKey, }; - teslaSolarChargerContext.TscConfigurations.Add(tscConfig); - } - tscConfig.Value = dateTimeProvider.UtcNow().ToString("o"); + var teslaMateCarId = teslamateContext.Cars.First(c => c.Vin == vin).Id; + var car = teslaSolarChargerContext.Cars.First(c => c.TeslaMateCarId == teslaMateCarId); + car.TeslaFleetApiState = TeslaCarFleetApiState.NotWorking; } else { diff --git a/TeslaSolarCharger/Server/Services/TeslamateApiService.cs b/TeslaSolarCharger/Server/Services/TeslamateApiService.cs index 19b9d52f2..6abb71465 100644 --- a/TeslaSolarCharger/Server/Services/TeslamateApiService.cs +++ b/TeslaSolarCharger/Server/Services/TeslamateApiService.cs @@ -242,7 +242,6 @@ public async Task ResumeLogging(int carId) var url = $"{_teslaMateBaseUrl}/api/v1/cars/{carId}/logging/resume"; using var httpClient = new HttpClient(); var response = await httpClient.PutAsync(url, null).ConfigureAwait(false); - response.EnsureSuccessStatusCode(); } private async Task SendPostToTeslaMate(string url, Dictionary? parameters = null) diff --git a/TeslaSolarCharger/Shared/Dtos/IndexRazor/CarValues/DtoCarBaseStates.cs b/TeslaSolarCharger/Shared/Dtos/IndexRazor/CarValues/DtoCarBaseStates.cs index 631eed220..d7d8b6d42 100644 --- a/TeslaSolarCharger/Shared/Dtos/IndexRazor/CarValues/DtoCarBaseStates.cs +++ b/TeslaSolarCharger/Shared/Dtos/IndexRazor/CarValues/DtoCarBaseStates.cs @@ -19,7 +19,7 @@ public class DtoCarBaseStates public bool IsAutoFullSpeedCharging { get; set; } public bool? IsHealthy { get; set; } public bool ChargingNotPlannedDueToNoSpotPricesAvailable { get; set; } - public bool VehicleNotPaired { get; set; } + public TeslaCarFleetApiState FleetApiState { get; set; } public List ChargeInformation { get; set; } = new(); public CarStateEnum? State { get; set; } public List ChargingSlots { get; set; } = new(); diff --git a/TeslaSolarCharger/Shared/Dtos/Settings/Car.cs b/TeslaSolarCharger/Shared/Dtos/Settings/Car.cs index 1f2ff9e18..d1128dc37 100644 --- a/TeslaSolarCharger/Shared/Dtos/Settings/Car.cs +++ b/TeslaSolarCharger/Shared/Dtos/Settings/Car.cs @@ -10,6 +10,7 @@ public Car() CarConfiguration = new CarConfiguration(); } public int Id { get; set; } + public string Vin { get; set; } public CarConfiguration CarConfiguration { get; set; } diff --git a/TeslaSolarCharger/Shared/Enums/TeslaCarFleetApiState.cs b/TeslaSolarCharger/Shared/Enums/TeslaCarFleetApiState.cs new file mode 100644 index 000000000..c07c41124 --- /dev/null +++ b/TeslaSolarCharger/Shared/Enums/TeslaCarFleetApiState.cs @@ -0,0 +1,9 @@ +namespace TeslaSolarCharger.Shared.Enums; + +public enum TeslaCarFleetApiState +{ + NotConfigured, + NotWorking, + OpenedLinkButNotTested, + Ok, +} diff --git a/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs b/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs index f8f4591bf..d4979d930 100644 --- a/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs +++ b/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs @@ -12,31 +12,22 @@ [assembly: InternalsVisibleTo("TeslaSolarCharger.Tests")] namespace TeslaSolarCharger.Shared.Wrappers; -public class ConfigurationWrapper : IConfigurationWrapper +public class ConfigurationWrapper( + ILogger logger, + IConfiguration configuration, + INodePatternTypeHelper nodePatternTypeHelper, + IDateTimeProvider dateTimeProvider, + ISettings settings) + : IConfigurationWrapper { - private readonly ILogger _logger; - private readonly IConfiguration _configuration; - private readonly INodePatternTypeHelper _nodePatternTypeHelper; - private readonly IDateTimeProvider _dateTimeProvider; - private readonly ISettings _settings; private readonly string _baseConfigurationMemoryCacheName = "baseConfiguration"; - public ConfigurationWrapper(ILogger logger, IConfiguration configuration, INodePatternTypeHelper nodePatternTypeHelper, - IDateTimeProvider dateTimeProvider, ISettings settings) - { - _logger = logger; - _configuration = configuration; - _nodePatternTypeHelper = nodePatternTypeHelper; - _dateTimeProvider = dateTimeProvider; - _settings = settings; - } - public string CarConfigFileFullName() { var configFileDirectory = ConfigFileDirectory(); var environmentVariableName = "CarConfigFilename"; var value = GetNotNullableConfigurationValue(environmentVariableName); - _logger.LogTrace("Config value extracted: [{key}]: {value}", environmentVariableName, value); + logger.LogTrace("Config value extracted: [{key}]: {value}", environmentVariableName, value); return Path.Combine(configFileDirectory, value); } @@ -45,7 +36,7 @@ public string BackupCopyDestinationDirectory() var configFileDirectory = ConfigFileDirectory(); var environmentVariableName = "BackupCopyDestinationDirectory"; var value = GetNotNullableConfigurationValue(environmentVariableName); - _logger.LogTrace("Config value extracted: [{key}]: {value}", environmentVariableName, value); + logger.LogTrace("Config value extracted: [{key}]: {value}", environmentVariableName, value); return Path.Combine(configFileDirectory, value); } @@ -54,7 +45,7 @@ public string BackupZipDirectory() var configFileDirectory = ConfigFileDirectory(); var environmentVariableName = "BackupZipDirectory"; var value = GetNotNullableConfigurationValue(environmentVariableName); - _logger.LogTrace("Config value extracted: [{key}]: {value}", environmentVariableName, value); + logger.LogTrace("Config value extracted: [{key}]: {value}", environmentVariableName, value); return Path.Combine(configFileDirectory, value); } @@ -69,7 +60,7 @@ public string GetSqliteFileNameWithoutPath() { var environmentVariableName = "SqliteFileName"; var value = GetNotNullableConfigurationValue(environmentVariableName); - _logger.LogTrace("Config value extracted: [{key}]: {value}", environmentVariableName, value); + logger.LogTrace("Config value extracted: [{key}]: {value}", environmentVariableName, value); return value; } @@ -78,7 +69,7 @@ public string BaseConfigFileFullName() var configFileDirectory = ConfigFileDirectory(); var environmentVariableName = "BaseConfigFileName"; var value = GetNotNullableConfigurationValue(environmentVariableName); - _logger.LogTrace("Config value extracted: [{key}]: {value}", environmentVariableName, value); + logger.LogTrace("Config value extracted: [{key}]: {value}", environmentVariableName, value); return Path.Combine(configFileDirectory, value); } @@ -86,7 +77,7 @@ internal string ConfigFileDirectory() { var environmentVariableName = "ConfigFileLocation"; var value = GetNotNullableConfigurationValue(environmentVariableName); - _logger.LogTrace("Config value extracted: [{key}]: {value}", environmentVariableName, value); + logger.LogTrace("Config value extracted: [{key}]: {value}", environmentVariableName, value); var path = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory?.FullName; path = Path.Combine(path ?? throw new InvalidOperationException("Could not get Assembly directory"), value); return path; @@ -95,7 +86,7 @@ internal string ConfigFileDirectory() public string? GetFleetApiBaseUrl() { var environmentVariableName = "TeslaFleetApiBaseUrl"; - var value = _configuration.GetValue(environmentVariableName); + var value = configuration.GetValue(environmentVariableName); return value; } @@ -109,28 +100,28 @@ public string GetAwattarBaseUrl() public bool AllowCors() { var environmentVariableName = "AllowCORS"; - var value = _configuration.GetValue(environmentVariableName); + var value = configuration.GetValue(environmentVariableName); return value; } public bool UseFleetApi() { var environmentVariableName = "UseFleetApi"; - var value = _configuration.GetValue(environmentVariableName); + var value = configuration.GetValue(environmentVariableName); return value; } public bool UseFleetApiProxy() { var environmentVariableName = "UseFleetApiProxy"; - var value = _configuration.GetValue(environmentVariableName); + var value = configuration.GetValue(environmentVariableName); return value; } public string BackendApiBaseUrl() { var environmentVariableName = "BackendApiBaseUrl"; - var value = _configuration.GetValue(environmentVariableName); + var value = configuration.GetValue(environmentVariableName); return value; } @@ -463,7 +454,7 @@ public int PowerBuffer(bool getInMemoryValueIfAvailable) { if (getInMemoryValueIfAvailable) { - var settingsPowerBuffer = _settings.PowerBuffer; + var settingsPowerBuffer = settings.PowerBuffer; if (settingsPowerBuffer != null) { return settingsPowerBuffer.Value; @@ -494,27 +485,27 @@ internal T GetNotNullableConfigurationValue(string environmentVariableName) { var exception = new NullReferenceException($"Configuration value {environmentVariableName} is null or empty"); - _logger.LogError(exception, "Error getting configuration value"); + logger.LogError(exception, "Error getting configuration value"); throw exception; } - _logger.LogTrace("Config value extracted: [{key}]: {value}", environmentVariableName, value); + logger.LogTrace("Config value extracted: [{key}]: {value}", environmentVariableName, value); return value; } internal T? GetNullableConfigurationValue(string environmentVariableName) { - return _configuration.GetValue(environmentVariableName); + return configuration.GetValue(environmentVariableName); } internal TimeSpan GetSecondsConfigurationValueIfGreaterThanMinumum(string environmentVariableName, TimeSpan minimum) { - var value = TimeSpan.FromSeconds(_configuration.GetValue(environmentVariableName)); + var value = TimeSpan.FromSeconds(configuration.GetValue(environmentVariableName)); return GetValueIfGreaterThanMinimum(value, minimum); } internal TimeSpan GetMinutesConfigurationValueIfGreaterThanMinumum(string environmentVariableName, TimeSpan minimum) { - var value = TimeSpan.FromMinutes(_configuration.GetValue(environmentVariableName)); + var value = TimeSpan.FromMinutes(configuration.GetValue(environmentVariableName)); return GetValueIfGreaterThanMinimum(value, minimum); } @@ -522,7 +513,7 @@ private TimeSpan GetValueIfGreaterThanMinimum(TimeSpan value, TimeSpan minimum) { if (value < minimum) { - _logger.LogTrace("Replace value {value} with minumum value {minimum}", value, minimum); + logger.LogTrace("Replace value {value} with minumum value {minimum}", value, minimum); return minimum; } else @@ -539,7 +530,7 @@ private DtoBaseConfiguration GetBaseConfiguration() public async Task GetBaseConfigurationAsync() { - _logger.LogTrace("{method}()", nameof(GetBaseConfiguration)); + logger.LogTrace("{method}()", nameof(GetBaseConfiguration)); var jsonFileContent = await BaseConfigurationJsonFileContent().ConfigureAwait(false); var dtoBaseConfiguration = JsonConvert.DeserializeObject(jsonFileContent)!; @@ -591,7 +582,7 @@ private void SetInverterDefaultFrontendConfiguration(DtoBaseConfiguration dtoBas } dtoBaseConfiguration.FrontendConfiguration.InverterPowerNodePatternType = - _nodePatternTypeHelper.DecideNodePatternType(dtoBaseConfiguration.CurrentInverterPowerJsonPattern, + nodePatternTypeHelper.DecideNodePatternType(dtoBaseConfiguration.CurrentInverterPowerJsonPattern, dtoBaseConfiguration.CurrentInverterPowerXmlPattern); } @@ -616,7 +607,7 @@ private void SetGridDefaultFrontendConfiguration(DtoBaseConfiguration dtoBaseCon } dtoBaseConfiguration.FrontendConfiguration.GridPowerNodePatternType = - _nodePatternTypeHelper.DecideNodePatternType(dtoBaseConfiguration.CurrentPowerToGridJsonPattern, + nodePatternTypeHelper.DecideNodePatternType(dtoBaseConfiguration.CurrentPowerToGridJsonPattern, dtoBaseConfiguration.CurrentPowerToGridXmlPattern); } @@ -647,19 +638,19 @@ private void SetHomeBatteryDefaultConfiguration(DtoBaseConfiguration dtoBaseConf } dtoBaseConfiguration.FrontendConfiguration.HomeBatteryPowerNodePatternType = - _nodePatternTypeHelper.DecideNodePatternType(dtoBaseConfiguration.HomeBatteryPowerJsonPattern, + nodePatternTypeHelper.DecideNodePatternType(dtoBaseConfiguration.HomeBatteryPowerJsonPattern, dtoBaseConfiguration.HomeBatteryPowerXmlPattern); dtoBaseConfiguration.FrontendConfiguration.HomeBatterySocNodePatternType = - _nodePatternTypeHelper.DecideNodePatternType(dtoBaseConfiguration.HomeBatterySocJsonPattern, + nodePatternTypeHelper.DecideNodePatternType(dtoBaseConfiguration.HomeBatterySocJsonPattern, dtoBaseConfiguration.HomeBatterySocXmlPattern); } public bool ShouldIgnoreSslErrors() { - _logger.LogTrace("{method}()", nameof(ShouldIgnoreSslErrors)); + logger.LogTrace("{method}()", nameof(ShouldIgnoreSslErrors)); var environmentVariableName = "IgnoreSslErrors"; - var value = _configuration.GetValue(environmentVariableName); + var value = configuration.GetValue(environmentVariableName); return value; } @@ -699,7 +690,7 @@ public async Task TryAutoFillUrls() } catch (Exception ex) { - _logger.LogWarning(ex, "Could not load values from SolarEdge Plugin"); + logger.LogWarning(ex, "Could not load values from SolarEdge Plugin"); } try @@ -714,7 +705,7 @@ public async Task TryAutoFillUrls() } catch (Exception ex) { - _logger.LogWarning(ex, "Could not load values from Modbus Plugin"); + logger.LogWarning(ex, "Could not load values from Modbus Plugin"); } } @@ -759,7 +750,7 @@ public async Task SaveBaseConfiguration(DtoBaseConfiguration baseConfiguration) var configDirectoryFullName = fileInfo.Directory?.FullName; if (!Directory.Exists(configDirectoryFullName)) { - _logger.LogDebug("Config directory {directoryname} does not exist.", configDirectoryFullName); + logger.LogDebug("Config directory {directoryname} does not exist.", configDirectoryFullName); Directory.CreateDirectory(configDirectoryFullName ?? throw new InvalidOperationException()); } @@ -772,11 +763,11 @@ private async Task UpdateJsonFile(string configFileLocation, string jsonFileCont { try { - File.Copy(configFileLocation, configFileLocation + _dateTimeProvider.DateTimeOffSetNow().ToUnixTimeSeconds(), true); + File.Copy(configFileLocation, configFileLocation + dateTimeProvider.DateTimeOffSetNow().ToUnixTimeSeconds(), true); } catch (Exception ex) { - _logger.LogWarning(ex, "Could not backup baseConfig.json"); + logger.LogWarning(ex, "Could not backup baseConfig.json"); } } await File.WriteAllTextAsync(configFileLocation, jsonFileContent).ConfigureAwait(false); @@ -798,7 +789,7 @@ public async Task UpdateBaseConfigurationAsync(DtoBaseConfiguration dtoBaseConfi { throw new InvalidOperationException("Could not deserialize dtoBaseConfiguration to baseconfigurationJson"); } - baseConfigurationJson.LastEditDateTime = _dateTimeProvider.UtcNow(); + baseConfigurationJson.LastEditDateTime = dateTimeProvider.UtcNow(); var baseConfigurationJsonString = JsonConvert.SerializeObject(baseConfigurationJson); @@ -807,6 +798,6 @@ public async Task UpdateBaseConfigurationAsync(DtoBaseConfiguration dtoBaseConfi public bool ShouldDisplayApiRequestCounter() { - return _configuration.GetValue("DisplayApiRequestCounter"); + return configuration.GetValue("DisplayApiRequestCounter"); } } From 3583548b2ec18f9ff34bc472cca5b1b850d89c8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Tue, 2 Jan 2024 01:52:13 +0100 Subject: [PATCH 28/50] feat(IndexRazor): improve text for non working API access --- TeslaSolarCharger/Client/Pages/Index.razor | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/TeslaSolarCharger/Client/Pages/Index.razor b/TeslaSolarCharger/Client/Pages/Index.razor index 8d508d6a5..9f44e088f 100644 --- a/TeslaSolarCharger/Client/Pages/Index.razor +++ b/TeslaSolarCharger/Client/Pages/Index.razor @@ -175,7 +175,13 @@ else else { } } From 9ab9e8ee83f4cf3b9931e3bc699c756231f430d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Tue, 2 Jan 2024 01:57:32 +0100 Subject: [PATCH 29/50] feat(IndexRazor): add info that chargeport might open --- TeslaSolarCharger/Client/Pages/Index.razor | 96 +++++++++++++--------- 1 file changed, 55 insertions(+), 41 deletions(-) diff --git a/TeslaSolarCharger/Client/Pages/Index.razor b/TeslaSolarCharger/Client/Pages/Index.razor index 9f44e088f..2a47dbb7b 100644 --- a/TeslaSolarCharger/Client/Pages/Index.razor +++ b/TeslaSolarCharger/Client/Pages/Index.razor @@ -24,8 +24,8 @@ - } + if (_serverTime.Value is { Day: 31, Month: 12, Hour: > 15 } or { Day: 1, Month: 1 }) { + diff --git a/TeslaSolarCharger/Client/wwwroot/js/fileDownload.js b/TeslaSolarCharger/Client/wwwroot/js/fileDownload.js new file mode 100644 index 000000000..63a2578f6 --- /dev/null +++ b/TeslaSolarCharger/Client/wwwroot/js/fileDownload.js @@ -0,0 +1,7 @@ +window.triggerFileDownload = (fileName, url) => { + const anchorElement = document.createElement('a'); + anchorElement.href = url; + anchorElement.download = fileName ?? ''; + anchorElement.click(); + anchorElement.remove(); +} From 788bf15150ba24d4ead87dfc8d9c76bfd7c6bcfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Tue, 2 Jan 2024 13:55:04 +0100 Subject: [PATCH 36/50] feat(BackupComponent): can select file to restore --- .../Client/Components/BackupComponent.razor | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/TeslaSolarCharger/Client/Components/BackupComponent.razor b/TeslaSolarCharger/Client/Components/BackupComponent.razor index d13cb2470..f4610f391 100644 --- a/TeslaSolarCharger/Client/Components/BackupComponent.razor +++ b/TeslaSolarCharger/Client/Components/BackupComponent.razor @@ -1,6 +1,7 @@ @page "/backupAndRestore" @inject IJSRuntime JsRuntime +@inject ISnackbar Snackbar

Backup and Restore

During the backup or restore process all TSC actions will be stopped and started again after the Backup
@@ -21,6 +22,28 @@

Restore

+ +
+ + + + Select Backup File + + + +
+ +@if (_file != default) +{ +
+ @_file.Name @((_file.Size * 0.000001).ToString("0.00")) MB +
+} + @if (_processingRestore) { @@ -38,6 +61,8 @@ private bool _processingBackup; private bool _processingRestore; + private IBrowserFile? _file; + private async Task StartBackup() { @@ -51,6 +76,17 @@ StateHasChanged(); } + private void SelectFile(IBrowserFile file) + { + var maxFileSize = 1024 * 1024 * 1024; // 1024 MB + if (file.Size > maxFileSize) + { + Snackbar.Add($"{file.Name} is greater than {maxFileSize / 1024 / 1024} and won't be uploaded." + , Severity.Error, (options) => { options.CloseAfterNavigation = true; }); + } + _file = file; + } + private Task StartRestore() { _processingRestore = true; From cc6c3c9789bb698b63e1b18b605d09157a6f4b34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Tue, 2 Jan 2024 15:26:59 +0100 Subject: [PATCH 37/50] feat(BackupComponent): Can restore from backup --- .../Client/Components/BackupComponent.razor | 56 ++++++++++++-- .../Contracts/IBaseConfigurationService.cs | 1 + .../BaseConfigurationController.cs | 6 ++ .../Services/BaseConfigurationService.cs | 76 +++++++++++++++++-- TeslaSolarCharger/Server/appsettings.json | 1 + .../Shared/Contracts/IConfigurationWrapper.cs | 2 + .../Shared/Wrappers/ConfigurationWrapper.cs | 11 ++- 7 files changed, 140 insertions(+), 13 deletions(-) diff --git a/TeslaSolarCharger/Client/Components/BackupComponent.razor b/TeslaSolarCharger/Client/Components/BackupComponent.razor index f4610f391..c8ca98f6b 100644 --- a/TeslaSolarCharger/Client/Components/BackupComponent.razor +++ b/TeslaSolarCharger/Client/Components/BackupComponent.razor @@ -1,7 +1,9 @@ @page "/backupAndRestore" +@using System.Net.Http.Headers @inject IJSRuntime JsRuntime @inject ISnackbar Snackbar +@inject HttpClient HttpClient

Backup and Restore

During the backup or restore process all TSC actions will be stopped and started again after the Backup
@@ -44,7 +46,7 @@
} - + @if (_processingRestore) { @@ -60,6 +62,7 @@ @code { private bool _processingBackup; private bool _processingRestore; + private readonly long _maxFileSize = 1024 * 1024 * 1024; // 1024 MB private IBrowserFile? _file; @@ -78,20 +81,59 @@ private void SelectFile(IBrowserFile file) { - var maxFileSize = 1024 * 1024 * 1024; // 1024 MB - if (file.Size > maxFileSize) + if (file.Size > _maxFileSize) { - Snackbar.Add($"{file.Name} is greater than {maxFileSize / 1024 / 1024} and won't be uploaded." - , Severity.Error, (options) => { options.CloseAfterNavigation = true; }); + Snackbar.Add($"{file.Name} is greater than {_maxFileSize / 1024 / 1024} and won't be uploaded." + , Severity.Error); } _file = file; } - private Task StartRestore() + private async Task StartRestore() { _processingRestore = true; + var upload = false; + if (_file == default) + { + Snackbar.Add("No file selected", Severity.Error); + return; + } + using var content = new MultipartFormDataContent(); + try + { + var fileContent = new StreamContent(_file.OpenReadStream(_maxFileSize)); + fileContent.Headers.ContentType = new MediaTypeHeaderValue("multipart/form-data"); + content.Add( + content: fileContent, + name: "\"file\"", + fileName: _file.Name); + upload = true; + } + catch (Exception e) + { + Snackbar.Add($"Error while uploading file: {e.Message}", Severity.Error); + _processingRestore = false; + return; + } + + if (!upload) + { + _processingRestore = false; + return; + } + + // ReSharper disable once UseConfigureAwaitFalse + var response = await HttpClient.PostAsync("api/BaseConfiguration/RestoreBackup", content); + if (response.IsSuccessStatusCode) + { + Snackbar.Add("Restore complete", Severity.Success); + } + else + { + Snackbar.Add($"Error while restoring backup: {response.ReasonPhrase}", Severity.Error); + } + _processingRestore = false; - throw new NotImplementedException(); } } diff --git a/TeslaSolarCharger/Server/Contracts/IBaseConfigurationService.cs b/TeslaSolarCharger/Server/Contracts/IBaseConfigurationService.cs index 4b82c9367..a33096716 100644 --- a/TeslaSolarCharger/Server/Contracts/IBaseConfigurationService.cs +++ b/TeslaSolarCharger/Server/Contracts/IBaseConfigurationService.cs @@ -9,4 +9,5 @@ public interface IBaseConfigurationService Task UpdateMaxCombinedCurrent(int? maxCombinedCurrent); void UpdatePowerBuffer(int powerBuffer); Task DownloadBackup(); + Task RestoreBackup(IFormFile file); } diff --git a/TeslaSolarCharger/Server/Controllers/BaseConfigurationController.cs b/TeslaSolarCharger/Server/Controllers/BaseConfigurationController.cs index 239f5a0e3..138759463 100644 --- a/TeslaSolarCharger/Server/Controllers/BaseConfigurationController.cs +++ b/TeslaSolarCharger/Server/Controllers/BaseConfigurationController.cs @@ -38,5 +38,11 @@ public async Task DownloadBackup() var bytes = await _service.DownloadBackup().ConfigureAwait(false); return File(bytes, "application/zip", "TSCBackup.zip"); } + + [HttpPost] + public async Task RestoreBackup(IFormFile file) + { + await _service.RestoreBackup(file).ConfigureAwait(false); + } } } diff --git a/TeslaSolarCharger/Server/Services/BaseConfigurationService.cs b/TeslaSolarCharger/Server/Services/BaseConfigurationService.cs index 3fe2ec9d4..7d0b9c05d 100644 --- a/TeslaSolarCharger/Server/Services/BaseConfigurationService.cs +++ b/TeslaSolarCharger/Server/Services/BaseConfigurationService.cs @@ -1,4 +1,5 @@ using Microsoft.Data.Sqlite; +using System.IO; using System.IO.Compression; using TeslaSolarCharger.Model.Contracts; using TeslaSolarCharger.Server.Contracts; @@ -82,11 +83,7 @@ public async Task DownloadBackup() await _jobManager.StopJobs().ConfigureAwait(false); var backupCopyDestinationDirectory = _configurationWrapper.BackupCopyDestinationDirectory(); - if (Directory.Exists(backupCopyDestinationDirectory)) - { - Directory.Delete(backupCopyDestinationDirectory, true); - } - Directory.CreateDirectory(backupCopyDestinationDirectory); + CreateEmptyDirectory(backupCopyDestinationDirectory); //Backup Sqlite database using (var source = new SqliteConnection(_dbConnectionStringHelper.GetTeslaSolarChargerDbPath())) @@ -127,4 +124,73 @@ public async Task DownloadBackup() } + + + public async Task RestoreBackup(IFormFile file) + { + _logger.LogTrace("{method}({file})", nameof(RestoreBackup), file.FileName); + try + { + await _jobManager.StopJobs().ConfigureAwait(false); + + var restoreTempDirectory = _configurationWrapper.RestoreTempDirectory(); + CreateEmptyDirectory(restoreTempDirectory); + var restoreFileName = "TSC-Restore.zip"; + var path = Path.Combine(restoreTempDirectory, restoreFileName); + await using FileStream fs = new(path, FileMode.Create); + await file.CopyToAsync(fs).ConfigureAwait(false); + fs.Close(); + var extractedFilesDirectory = Path.Combine(restoreTempDirectory, "RestoredFiles"); + CreateEmptyDirectory(extractedFilesDirectory); + ZipFile.ExtractToDirectory(path, extractedFilesDirectory); + var configFileDirectoryPath = _configurationWrapper.ConfigFileDirectory(); + var directoryInfo = new DirectoryInfo(configFileDirectoryPath); + foreach (var fileInfo in directoryInfo.GetFiles()) + { + fileInfo.Delete(); + } + CopyFiles(extractedFilesDirectory, configFileDirectoryPath); + } + catch (Exception ex) + { + _logger.LogError(ex, "Couldn't restore backup"); + throw; + } + finally + { + await _jobManager.StartJobs().ConfigureAwait(false); + } + } + + private static void CreateEmptyDirectory(string path) + { + if (Directory.Exists(path)) + { + Directory.Delete(path, true); + } + + Directory.CreateDirectory(path); + } + + public void CopyFiles(string sourceDir, string targetDir) + { + // Create the target directory if it doesn't already exist + Directory.CreateDirectory(targetDir); + + // Get the files in the source directory + var files = Directory.GetFiles(sourceDir); + + foreach (var file in files) + { + // Extract the file name + var fileName = Path.GetFileName(file); + + // Combine the target directory with the file name + var targetFilePath = Path.Combine(targetDir, fileName); + + // Copy the file + File.Copy(file, targetFilePath, true); // true to overwrite if the file already exists + } + } + } diff --git a/TeslaSolarCharger/Server/appsettings.json b/TeslaSolarCharger/Server/appsettings.json index c974f9a03..2b5f69417 100644 --- a/TeslaSolarCharger/Server/appsettings.json +++ b/TeslaSolarCharger/Server/appsettings.json @@ -42,6 +42,7 @@ "ConfigFileLocation": "configs", "BackupCopyDestinationDirectory": "backups", "BackupZipDirectory": "backupZips", + "RestoreTempDirectory": "restores", "CarConfigFilename": "carConfig.json", "BaseConfigFileName": "baseConfig.json", "SqliteFileName": "TeslaSolarCharger.db", diff --git a/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs b/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs index 6ab290657..af4a99427 100644 --- a/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs +++ b/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs @@ -93,4 +93,6 @@ public interface IConfigurationWrapper string GetAwattarBaseUrl(); string? GetFleetApiBaseUrl(); bool UseFleetApiProxy(); + string RestoreTempDirectory(); + string ConfigFileDirectory(); } diff --git a/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs b/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs index d4979d930..f7b199962 100644 --- a/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs +++ b/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs @@ -49,6 +49,15 @@ public string BackupZipDirectory() return Path.Combine(configFileDirectory, value); } + public string RestoreTempDirectory() + { + var configFileDirectory = ConfigFileDirectory(); + var environmentVariableName = "RestoreTempDirectory"; + var value = GetNotNullableConfigurationValue(environmentVariableName); + logger.LogTrace("Config value extracted: [{key}]: {value}", environmentVariableName, value); + return Path.Combine(configFileDirectory, value); + } + public string SqliteFileFullName() { var configFileDirectory = ConfigFileDirectory(); @@ -73,7 +82,7 @@ public string BaseConfigFileFullName() return Path.Combine(configFileDirectory, value); } - internal string ConfigFileDirectory() + public string ConfigFileDirectory() { var environmentVariableName = "ConfigFileLocation"; var value = GetNotNullableConfigurationValue(environmentVariableName); From 2b56c5e3422bfc6ae755b1ac0804a9c568cfff97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Tue, 2 Jan 2024 15:45:08 +0100 Subject: [PATCH 38/50] feat(BaseConfigurationService): extract method to crete backup zip file --- .../Contracts/IBaseConfigurationService.cs | 2 +- .../BaseConfigurationController.cs | 2 +- .../Services/BaseConfigurationService.cs | 26 ++++++++++--------- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/TeslaSolarCharger/Server/Contracts/IBaseConfigurationService.cs b/TeslaSolarCharger/Server/Contracts/IBaseConfigurationService.cs index a33096716..15b6e5b0d 100644 --- a/TeslaSolarCharger/Server/Contracts/IBaseConfigurationService.cs +++ b/TeslaSolarCharger/Server/Contracts/IBaseConfigurationService.cs @@ -8,6 +8,6 @@ public interface IBaseConfigurationService Task UpdateBaseConfigurationAsync(DtoBaseConfiguration baseConfiguration); Task UpdateMaxCombinedCurrent(int? maxCombinedCurrent); void UpdatePowerBuffer(int powerBuffer); - Task DownloadBackup(); + Task DownloadBackup(string backupFileNameSuffix, string? backupZipDestinationDirectory); Task RestoreBackup(IFormFile file); } diff --git a/TeslaSolarCharger/Server/Controllers/BaseConfigurationController.cs b/TeslaSolarCharger/Server/Controllers/BaseConfigurationController.cs index 138759463..ae5dcd537 100644 --- a/TeslaSolarCharger/Server/Controllers/BaseConfigurationController.cs +++ b/TeslaSolarCharger/Server/Controllers/BaseConfigurationController.cs @@ -35,7 +35,7 @@ public void UpdatePowerBuffer(int powerBuffer) => [HttpGet] public async Task DownloadBackup() { - var bytes = await _service.DownloadBackup().ConfigureAwait(false); + var bytes = await _service.DownloadBackup(string.Empty, null).ConfigureAwait(false); return File(bytes, "application/zip", "TSCBackup.zip"); } diff --git a/TeslaSolarCharger/Server/Services/BaseConfigurationService.cs b/TeslaSolarCharger/Server/Services/BaseConfigurationService.cs index 7d0b9c05d..be91ed738 100644 --- a/TeslaSolarCharger/Server/Services/BaseConfigurationService.cs +++ b/TeslaSolarCharger/Server/Services/BaseConfigurationService.cs @@ -76,12 +76,18 @@ public void UpdatePowerBuffer(int powerBuffer) _settings.PowerBuffer = powerBuffer; } - public async Task DownloadBackup() + public async Task DownloadBackup(string backupFileNameSuffix, string? backupZipDestinationDirectory) + { + var destinationArchiveFileName = await CreateLocalBackupZipFile(backupFileNameSuffix, backupZipDestinationDirectory).ConfigureAwait(false); + var bytes = await File.ReadAllBytesAsync(destinationArchiveFileName).ConfigureAwait(false); + return bytes; + } + + private async Task CreateLocalBackupZipFile(string backupFileNameSuffix, string? backupZipDestinationDirectory) { try { await _jobManager.StopJobs().ConfigureAwait(false); - var backupCopyDestinationDirectory = _configurationWrapper.BackupCopyDestinationDirectory(); CreateEmptyDirectory(backupCopyDestinationDirectory); @@ -99,32 +105,28 @@ public async Task DownloadBackup() File.Copy(baseConfigFileFullName, Path.Combine(backupCopyDestinationDirectory, Path.GetFileName(baseConfigFileFullName)), true); - var backupFileName = "TSC-Backup.zip"; - var backupZipDirectory = _configurationWrapper.BackupZipDirectory(); - if(Directory.Exists(backupZipDirectory)) + var backupFileName = "TSC-Backup.zip" + backupFileNameSuffix; + var backupZipDirectory = backupZipDestinationDirectory ?? _configurationWrapper.BackupZipDirectory(); + if (Directory.Exists(backupZipDirectory)) { Directory.Delete(backupZipDirectory, true); } Directory.CreateDirectory(backupZipDirectory); var destinationArchiveFileName = Path.Combine(backupZipDirectory, backupFileName); ZipFile.CreateFromDirectory(backupCopyDestinationDirectory, destinationArchiveFileName); - var bytes = await File.ReadAllBytesAsync(destinationArchiveFileName).ConfigureAwait(false); - return bytes; + return destinationArchiveFileName; } catch (Exception ex) { - _logger.LogError(ex, "Couldn't download backup"); + _logger.LogError(ex, "Couldn't create backup zip file"); throw; } finally { await _jobManager.StartJobs().ConfigureAwait(false); } - - - } - + public async Task RestoreBackup(IFormFile file) { From a3d8c0ecc583c2cef0b2f3268847f06a9544a736 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Tue, 2 Jan 2024 15:57:10 +0100 Subject: [PATCH 39/50] feat(CoreService): create autobackups in separate directory --- .../Contracts/IConstants.cs | 1 + .../Values/Constants.cs | 1 + .../Contracts/IBaseConfigurationService.cs | 1 + .../Services/BaseConfigurationService.cs | 10 ++- .../Server/Services/CoreService.cs | 65 ++++++++++++------- TeslaSolarCharger/Server/appsettings.json | 1 + .../Shared/Contracts/IConfigurationWrapper.cs | 1 + .../Shared/Wrappers/ConfigurationWrapper.cs | 9 +++ 8 files changed, 64 insertions(+), 25 deletions(-) diff --git a/TeslaSolarCharger.SharedBackend/Contracts/IConstants.cs b/TeslaSolarCharger.SharedBackend/Contracts/IConstants.cs index 7c669fd2d..6734a9a5d 100644 --- a/TeslaSolarCharger.SharedBackend/Contracts/IConstants.cs +++ b/TeslaSolarCharger.SharedBackend/Contracts/IConstants.cs @@ -15,4 +15,5 @@ public interface IConstants string FleetApiTokenRequested { get; } string TokenRefreshUnauthorized { get; } string TokenMissingScopes { get; } + string BackupZipBaseFileName { get; } } diff --git a/TeslaSolarCharger.SharedBackend/Values/Constants.cs b/TeslaSolarCharger.SharedBackend/Values/Constants.cs index b38907b48..47c001dd3 100644 --- a/TeslaSolarCharger.SharedBackend/Values/Constants.cs +++ b/TeslaSolarCharger.SharedBackend/Values/Constants.cs @@ -9,6 +9,7 @@ public class Constants : IConstants public int MinSocLimit => 50; public int DefaultOverage => -1000000; public int MinimumSocDifference => 2; + public string BackupZipBaseFileName => "TSC-Backup.zip"; public string InstallationIdKey => "InstallationId"; public string FleetApiTokenRequested => "FleetApiTokenRequested"; diff --git a/TeslaSolarCharger/Server/Contracts/IBaseConfigurationService.cs b/TeslaSolarCharger/Server/Contracts/IBaseConfigurationService.cs index 15b6e5b0d..3ad0dadf7 100644 --- a/TeslaSolarCharger/Server/Contracts/IBaseConfigurationService.cs +++ b/TeslaSolarCharger/Server/Contracts/IBaseConfigurationService.cs @@ -10,4 +10,5 @@ public interface IBaseConfigurationService void UpdatePowerBuffer(int powerBuffer); Task DownloadBackup(string backupFileNameSuffix, string? backupZipDestinationDirectory); Task RestoreBackup(IFormFile file); + Task CreateLocalBackupZipFile(string backupFileNameSuffix, string? backupZipDestinationDirectory); } diff --git a/TeslaSolarCharger/Server/Services/BaseConfigurationService.cs b/TeslaSolarCharger/Server/Services/BaseConfigurationService.cs index be91ed738..c000a3173 100644 --- a/TeslaSolarCharger/Server/Services/BaseConfigurationService.cs +++ b/TeslaSolarCharger/Server/Services/BaseConfigurationService.cs @@ -8,6 +8,7 @@ using TeslaSolarCharger.Shared.Dtos.BaseConfiguration; using TeslaSolarCharger.Shared.Dtos.Contracts; using TeslaSolarCharger.Shared.Enums; +using TeslaSolarCharger.SharedBackend.Contracts; namespace TeslaSolarCharger.Server.Services; @@ -21,10 +22,12 @@ public class BaseConfigurationService : IBaseConfigurationService private readonly ISettings _settings; private readonly IPvValueService _pvValueService; private readonly IDbConnectionStringHelper _dbConnectionStringHelper; + private readonly IConstants _constants; public BaseConfigurationService(ILogger logger, IConfigurationWrapper configurationWrapper, JobManager jobManager, ITeslaMateMqttService teslaMateMqttService, ISolarMqttService solarMqttService, - ISettings settings, IPvValueService pvValueService, IDbConnectionStringHelper dbConnectionStringHelper) + ISettings settings, IPvValueService pvValueService, IDbConnectionStringHelper dbConnectionStringHelper, + IConstants constants) { _logger = logger; _configurationWrapper = configurationWrapper; @@ -34,6 +37,7 @@ public BaseConfigurationService(ILogger logger, IConfi _settings = settings; _pvValueService = pvValueService; _dbConnectionStringHelper = dbConnectionStringHelper; + _constants = constants; } public async Task UpdateBaseConfigurationAsync(DtoBaseConfiguration baseConfiguration) @@ -83,7 +87,7 @@ public async Task DownloadBackup(string backupFileNameSuffix, string? ba return bytes; } - private async Task CreateLocalBackupZipFile(string backupFileNameSuffix, string? backupZipDestinationDirectory) + public async Task CreateLocalBackupZipFile(string backupFileNameSuffix, string? backupZipDestinationDirectory) { try { @@ -105,7 +109,7 @@ private async Task CreateLocalBackupZipFile(string backupFileNameSuffix, File.Copy(baseConfigFileFullName, Path.Combine(backupCopyDestinationDirectory, Path.GetFileName(baseConfigFileFullName)), true); - var backupFileName = "TSC-Backup.zip" + backupFileNameSuffix; + var backupFileName = _constants.BackupZipBaseFileName + backupFileNameSuffix; var backupZipDirectory = backupZipDestinationDirectory ?? _configurationWrapper.BackupZipDirectory(); if (Directory.Exists(backupZipDirectory)) { diff --git a/TeslaSolarCharger/Server/Services/CoreService.cs b/TeslaSolarCharger/Server/Services/CoreService.cs index 95c4c50bb..ac360117b 100644 --- a/TeslaSolarCharger/Server/Services/CoreService.cs +++ b/TeslaSolarCharger/Server/Services/CoreService.cs @@ -10,6 +10,7 @@ using TeslaSolarCharger.Shared.Contracts; using TeslaSolarCharger.Shared.Dtos; using TeslaSolarCharger.Shared.Dtos.Contracts; +using TeslaSolarCharger.SharedBackend.Contracts; namespace TeslaSolarCharger.Server.Services; @@ -26,11 +27,14 @@ public class CoreService : ICoreService private readonly ISettings _settings; private readonly IFixedPriceService _fixedPriceService; private readonly ITscConfigurationService _tscConfigurationService; + private readonly IBaseConfigurationService _baseConfigurationService; + private readonly IConstants _constants; public CoreService(ILogger logger, IChargingService chargingService, IConfigurationWrapper configurationWrapper, IDateTimeProvider dateTimeProvider, IConfigJsonService configJsonService, JobManager jobManager, ITeslaMateMqttService teslaMateMqttService, ISolarMqttService solarMqttService, ISettings settings, - IFixedPriceService fixedPriceService, ITscConfigurationService tscConfigurationService) + IFixedPriceService fixedPriceService, ITscConfigurationService tscConfigurationService, IBaseConfigurationService baseConfigurationService, + IConstants constants) { _logger = logger; _chargingService = chargingService; @@ -43,6 +47,8 @@ public CoreService(ILogger logger, IChargingService chargingService _settings = settings; _fixedPriceService = fixedPriceService; _tscConfigurationService = tscConfigurationService; + _baseConfigurationService = baseConfigurationService; + _constants = constants; } public Task GetCurrentVersion() @@ -95,33 +101,48 @@ public async Task BackupDatabaseIfNeeded() return; } - var databaseFileName = _configurationWrapper.SqliteFileFullName(); - if (!File.Exists(databaseFileName)) + var destinationPath = _configurationWrapper.AutoBackupsZipDirectory(); + if (!Directory.Exists(destinationPath)) { - _logger.LogWarning("Database file does not exist. Backup is not created."); - return; + Directory.CreateDirectory(destinationPath); } + var backupFileNameSuffix = $"_{currentVersion}"; - var resultFileName = GenerateResultFileName(databaseFileName, currentVersion); - if (File.Exists(resultFileName)) + var resultingFileName = Path.Combine(destinationPath, $"{_constants.BackupZipBaseFileName + backupFileNameSuffix}"); + if (File.Exists(resultingFileName)) { - _logger.LogInformation("Database before upgrade to current version already backed up."); + _logger.LogInformation("Backup for this version already created. No new backup needed."); return; } - - File.Copy(databaseFileName, resultFileName, true); - - var shmFileName = databaseFileName + "-shm"; - if (File.Exists(shmFileName)) - { - File.Copy(shmFileName, GenerateResultFileName(shmFileName, currentVersion), true); - } - - var walFileName = databaseFileName + "-wal"; - if (File.Exists(walFileName)) - { - File.Copy(walFileName, GenerateResultFileName(walFileName, currentVersion), true); - } + await _baseConfigurationService.CreateLocalBackupZipFile(backupFileNameSuffix, destinationPath).ConfigureAwait(false); + + //var databaseFileName = _configurationWrapper.SqliteFileFullName(); + //if (!File.Exists(databaseFileName)) + //{ + // _logger.LogWarning("Database file does not exist. Backup is not created."); + // return; + //} + + //var resultFileName = GenerateResultFileName(databaseFileName, currentVersion); + //if (File.Exists(resultFileName)) + //{ + // _logger.LogInformation("Database before upgrade to current version already backed up."); + // return; + //} + + //File.Copy(databaseFileName, resultFileName, true); + + //var shmFileName = databaseFileName + "-shm"; + //if (File.Exists(shmFileName)) + //{ + // File.Copy(shmFileName, GenerateResultFileName(shmFileName, currentVersion), true); + //} + + //var walFileName = databaseFileName + "-wal"; + //if (File.Exists(walFileName)) + //{ + // File.Copy(walFileName, GenerateResultFileName(walFileName, currentVersion), true); + //} } private string GenerateResultFileName(string databaseFileName, string currentVersion) diff --git a/TeslaSolarCharger/Server/appsettings.json b/TeslaSolarCharger/Server/appsettings.json index 2b5f69417..d324c5b4e 100644 --- a/TeslaSolarCharger/Server/appsettings.json +++ b/TeslaSolarCharger/Server/appsettings.json @@ -42,6 +42,7 @@ "ConfigFileLocation": "configs", "BackupCopyDestinationDirectory": "backups", "BackupZipDirectory": "backupZips", + "AutoBackupZipDirectory": "autoBackupZips", "RestoreTempDirectory": "restores", "CarConfigFilename": "carConfig.json", "BaseConfigFileName": "baseConfig.json", diff --git a/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs b/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs index af4a99427..6457173e3 100644 --- a/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs +++ b/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs @@ -95,4 +95,5 @@ public interface IConfigurationWrapper bool UseFleetApiProxy(); string RestoreTempDirectory(); string ConfigFileDirectory(); + string AutoBackupsZipDirectory(); } diff --git a/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs b/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs index f7b199962..0385d21b6 100644 --- a/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs +++ b/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs @@ -49,6 +49,15 @@ public string BackupZipDirectory() return Path.Combine(configFileDirectory, value); } + public string AutoBackupsZipDirectory() + { + var configFileDirectory = ConfigFileDirectory(); + var environmentVariableName = "AutoBackupZipDirectory"; + var value = GetNotNullableConfigurationValue(environmentVariableName); + logger.LogTrace("Config value extracted: [{key}]: {value}", environmentVariableName, value); + return Path.Combine(configFileDirectory, value); + } + public string RestoreTempDirectory() { var configFileDirectory = ConfigFileDirectory(); From 0fd5979f1c1685a65e7013fc663bbcbcdbd1ba5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Tue, 2 Jan 2024 17:13:16 +0100 Subject: [PATCH 40/50] fix(JobManager): only stop jobs if not running --- TeslaSolarCharger/Server/Program.cs | 2 -- .../Server/Scheduling/JobManager.cs | 10 +++++-- .../Services/BaseConfigurationService.cs | 25 +++++++++++------ .../Server/Services/CoreService.cs | 28 ------------------- 4 files changed, 25 insertions(+), 40 deletions(-) diff --git a/TeslaSolarCharger/Server/Program.cs b/TeslaSolarCharger/Server/Program.cs index 9a6cec98f..617e642d7 100644 --- a/TeslaSolarCharger/Server/Program.cs +++ b/TeslaSolarCharger/Server/Program.cs @@ -51,8 +51,6 @@ try { - - var baseConfigurationConverter = app.Services.GetRequiredService(); await baseConfigurationConverter.ConvertAllEnvironmentVariables().ConfigureAwait(false); await baseConfigurationConverter.ConvertBaseConfigToV1_0().ConfigureAwait(false); diff --git a/TeslaSolarCharger/Server/Scheduling/JobManager.cs b/TeslaSolarCharger/Server/Scheduling/JobManager.cs index f37f27e41..a1e168d1f 100644 --- a/TeslaSolarCharger/Server/Scheduling/JobManager.cs +++ b/TeslaSolarCharger/Server/Scheduling/JobManager.cs @@ -13,7 +13,7 @@ public class JobManager private readonly IConfigurationWrapper _configurationWrapper; private readonly IDateTimeProvider _dateTimeProvider; - private IScheduler _scheduler; + private IScheduler? _scheduler; #pragma warning disable CS8618 @@ -103,9 +103,15 @@ public async Task StartJobs() await _scheduler.Start().ConfigureAwait(false); } - public async Task StopJobs() + public async Task StopJobs() { _logger.LogTrace("{method}()", nameof(StopJobs)); + if (_scheduler == null) + { + _logger.LogInformation("Jobs were not running, yet, so stop is not needed."); + return false; + } await _scheduler.Shutdown(true).ConfigureAwait(false); + return true; } } diff --git a/TeslaSolarCharger/Server/Services/BaseConfigurationService.cs b/TeslaSolarCharger/Server/Services/BaseConfigurationService.cs index c000a3173..02cc8a365 100644 --- a/TeslaSolarCharger/Server/Services/BaseConfigurationService.cs +++ b/TeslaSolarCharger/Server/Services/BaseConfigurationService.cs @@ -43,7 +43,7 @@ public BaseConfigurationService(ILogger logger, IConfi public async Task UpdateBaseConfigurationAsync(DtoBaseConfiguration baseConfiguration) { _logger.LogTrace("{method}({@baseConfiguration})", nameof(UpdateBaseConfigurationAsync), baseConfiguration); - await _jobManager.StopJobs().ConfigureAwait(false); + var restartNeeded = await _jobManager.StopJobs().ConfigureAwait(false); await _configurationWrapper.UpdateBaseConfigurationAsync(baseConfiguration).ConfigureAwait(false); await _teslaMateMqttService.ConnectMqttClient().ConfigureAwait(false); await _solarMqttService.ConnectMqttClient().ConfigureAwait(false); @@ -65,7 +65,10 @@ public async Task UpdateBaseConfigurationAsync(DtoBaseConfiguration baseConfigur } _settings.PowerBuffer = null; - await _jobManager.StartJobs().ConfigureAwait(false); + if (restartNeeded) + { + await _jobManager.StartJobs().ConfigureAwait(false); + } } public async Task UpdateMaxCombinedCurrent(int? maxCombinedCurrent) @@ -89,9 +92,10 @@ public async Task DownloadBackup(string backupFileNameSuffix, string? ba public async Task CreateLocalBackupZipFile(string backupFileNameSuffix, string? backupZipDestinationDirectory) { + var restartNeeded = false; try { - await _jobManager.StopJobs().ConfigureAwait(false); + restartNeeded = await _jobManager.StopJobs().ConfigureAwait(false); var backupCopyDestinationDirectory = _configurationWrapper.BackupCopyDestinationDirectory(); CreateEmptyDirectory(backupCopyDestinationDirectory); @@ -127,7 +131,10 @@ public async Task CreateLocalBackupZipFile(string backupFileNameSuffix, } finally { - await _jobManager.StartJobs().ConfigureAwait(false); + if (restartNeeded) + { + await _jobManager.StartJobs().ConfigureAwait(false); + } } } @@ -135,10 +142,9 @@ public async Task CreateLocalBackupZipFile(string backupFileNameSuffix, public async Task RestoreBackup(IFormFile file) { _logger.LogTrace("{method}({file})", nameof(RestoreBackup), file.FileName); + var jobsWereRunning = await _jobManager.StopJobs().ConfigureAwait(false); try { - await _jobManager.StopJobs().ConfigureAwait(false); - var restoreTempDirectory = _configurationWrapper.RestoreTempDirectory(); CreateEmptyDirectory(restoreTempDirectory); var restoreFileName = "TSC-Restore.zip"; @@ -164,7 +170,10 @@ public async Task RestoreBackup(IFormFile file) } finally { - await _jobManager.StartJobs().ConfigureAwait(false); + if (jobsWereRunning) + { + await _jobManager.StartJobs().ConfigureAwait(false); + } } } @@ -178,7 +187,7 @@ private static void CreateEmptyDirectory(string path) Directory.CreateDirectory(path); } - public void CopyFiles(string sourceDir, string targetDir) + private void CopyFiles(string sourceDir, string targetDir) { // Create the target directory if it doesn't already exist Directory.CreateDirectory(targetDir); diff --git a/TeslaSolarCharger/Server/Services/CoreService.cs b/TeslaSolarCharger/Server/Services/CoreService.cs index ac360117b..27e5adb09 100644 --- a/TeslaSolarCharger/Server/Services/CoreService.cs +++ b/TeslaSolarCharger/Server/Services/CoreService.cs @@ -115,34 +115,6 @@ public async Task BackupDatabaseIfNeeded() return; } await _baseConfigurationService.CreateLocalBackupZipFile(backupFileNameSuffix, destinationPath).ConfigureAwait(false); - - //var databaseFileName = _configurationWrapper.SqliteFileFullName(); - //if (!File.Exists(databaseFileName)) - //{ - // _logger.LogWarning("Database file does not exist. Backup is not created."); - // return; - //} - - //var resultFileName = GenerateResultFileName(databaseFileName, currentVersion); - //if (File.Exists(resultFileName)) - //{ - // _logger.LogInformation("Database before upgrade to current version already backed up."); - // return; - //} - - //File.Copy(databaseFileName, resultFileName, true); - - //var shmFileName = databaseFileName + "-shm"; - //if (File.Exists(shmFileName)) - //{ - // File.Copy(shmFileName, GenerateResultFileName(shmFileName, currentVersion), true); - //} - - //var walFileName = databaseFileName + "-wal"; - //if (File.Exists(walFileName)) - //{ - // File.Copy(walFileName, GenerateResultFileName(walFileName, currentVersion), true); - //} } private string GenerateResultFileName(string databaseFileName, string currentVersion) From 5b113273ce3722b3b92ccc2a3135fb8b7f65e367 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Tue, 2 Jan 2024 17:35:10 +0100 Subject: [PATCH 41/50] feat(Program): catch startup issues and display in issue list --- .../Client/Components/BackendIssueValidation.razor | 2 +- TeslaSolarCharger/Server/Program.cs | 7 +++++-- .../Server/Resources/PossibleIssues/IssueKeys.cs | 1 + .../Server/Resources/PossibleIssues/PossibleIssues.cs | 8 +++++++- TeslaSolarCharger/Server/Scheduling/JobManager.cs | 9 ++++++++- TeslaSolarCharger/Server/ServiceCollectionExtensions.cs | 2 +- .../Server/Services/IssueValidationService.cs | 8 ++++++++ TeslaSolarCharger/Shared/Dtos/Contracts/ISettings.cs | 2 ++ TeslaSolarCharger/Shared/Dtos/Issue.cs | 2 +- TeslaSolarCharger/Shared/Dtos/Settings/Settings.cs | 3 +++ 10 files changed, 37 insertions(+), 7 deletions(-) diff --git a/TeslaSolarCharger/Client/Components/BackendIssueValidation.razor b/TeslaSolarCharger/Client/Components/BackendIssueValidation.razor index 993771022..d94ca236a 100644 --- a/TeslaSolarCharger/Client/Components/BackendIssueValidation.razor +++ b/TeslaSolarCharger/Client/Components/BackendIssueValidation.razor @@ -18,7 +18,7 @@ _ => "info", })" role="alert">
@issue.IssueMessage
- @if (issue.PossibleSolutions.Length > 0) + @if (issue.PossibleSolutions.Count > 0) {
Possible solutions:
diff --git a/TeslaSolarCharger/Server/Program.cs b/TeslaSolarCharger/Server/Program.cs index 617e642d7..57bae20fd 100644 --- a/TeslaSolarCharger/Server/Program.cs +++ b/TeslaSolarCharger/Server/Program.cs @@ -7,6 +7,7 @@ using TeslaSolarCharger.Server.Scheduling; using TeslaSolarCharger.Server.Services.Contracts; using TeslaSolarCharger.Shared.Contracts; +using TeslaSolarCharger.Shared.Dtos.Contracts; var builder = WebApplication.CreateBuilder(args); @@ -96,8 +97,10 @@ } catch (Exception ex) { - logger.LogCritical(ex, "Crached on startup"); - throw; + logger.LogCritical(ex, "Crashed on startup"); + var settings = app.Services.GetRequiredService(); + settings.CrashedOnStartup = true; + settings.StartupCrashMessage = ex.Message; } // Configure the HTTP request pipeline. diff --git a/TeslaSolarCharger/Server/Resources/PossibleIssues/IssueKeys.cs b/TeslaSolarCharger/Server/Resources/PossibleIssues/IssueKeys.cs index a17e1c3df..cf8f12040 100644 --- a/TeslaSolarCharger/Server/Resources/PossibleIssues/IssueKeys.cs +++ b/TeslaSolarCharger/Server/Resources/PossibleIssues/IssueKeys.cs @@ -26,4 +26,5 @@ public class IssueKeys public string FleetApiTokenRequestExpired => "FleetApiTokenRequestExpired"; public string FleetApiTokenNotReceived => "FleetApiTokenNotReceived"; public string FleetApiTokenExpired => "FleetApiTokenExpired"; + public string CrashedOnStartup => "CrashedOnStartup"; } diff --git a/TeslaSolarCharger/Server/Resources/PossibleIssues/PossibleIssues.cs b/TeslaSolarCharger/Server/Resources/PossibleIssues/PossibleIssues.cs index 96f879897..1cb89fe40 100644 --- a/TeslaSolarCharger/Server/Resources/PossibleIssues/PossibleIssues.cs +++ b/TeslaSolarCharger/Server/Resources/PossibleIssues/PossibleIssues.cs @@ -180,6 +180,12 @@ public PossibleIssues(IssueKeys issueKeys) "Open the Base Configuration and request a new token." ) }, + { + issueKeys.CrashedOnStartup, CreateIssue("The application crashed while starting up.", + IssueType.Error, + "Look into the logfiles for further details." + ) + }, }; } @@ -189,7 +195,7 @@ private Issue CreateIssue(string issueMessage, IssueType issueType, params strin { IssueMessage = issueMessage, IssueType = issueType, - PossibleSolutions = possibleSolutions, + PossibleSolutions = possibleSolutions.ToList(), }; } diff --git a/TeslaSolarCharger/Server/Scheduling/JobManager.cs b/TeslaSolarCharger/Server/Scheduling/JobManager.cs index a1e168d1f..c375480b6 100644 --- a/TeslaSolarCharger/Server/Scheduling/JobManager.cs +++ b/TeslaSolarCharger/Server/Scheduling/JobManager.cs @@ -2,6 +2,7 @@ using Quartz.Spi; using TeslaSolarCharger.Server.Scheduling.Jobs; using TeslaSolarCharger.Shared.Contracts; +using TeslaSolarCharger.Shared.Dtos.Contracts; namespace TeslaSolarCharger.Server.Scheduling; @@ -12,13 +13,14 @@ public class JobManager private readonly ISchedulerFactory _schedulerFactory; private readonly IConfigurationWrapper _configurationWrapper; private readonly IDateTimeProvider _dateTimeProvider; + private readonly ISettings _settings; private IScheduler? _scheduler; #pragma warning disable CS8618 public JobManager(ILogger logger, IJobFactory jobFactory, ISchedulerFactory schedulerFactory, - IConfigurationWrapper configurationWrapper, IDateTimeProvider dateTimeProvider) + IConfigurationWrapper configurationWrapper, IDateTimeProvider dateTimeProvider, ISettings settings) #pragma warning restore CS8618 { _logger = logger; @@ -26,11 +28,16 @@ public JobManager(ILogger logger, IJobFactory jobFactory, IScheduler _schedulerFactory = schedulerFactory; _configurationWrapper = configurationWrapper; _dateTimeProvider = dateTimeProvider; + _settings = settings; } public async Task StartJobs() { _logger.LogTrace("{Method}()", nameof(StartJobs)); + if (_settings.CrashedOnStartup) + { + _logger.LogError("Do not start jobs as application crashed during startup."); + } _scheduler = _schedulerFactory.GetScheduler().GetAwaiter().GetResult(); _scheduler.JobFactory = _jobFactory; diff --git a/TeslaSolarCharger/Server/ServiceCollectionExtensions.cs b/TeslaSolarCharger/Server/ServiceCollectionExtensions.cs index 50fee7932..3355066ef 100644 --- a/TeslaSolarCharger/Server/ServiceCollectionExtensions.cs +++ b/TeslaSolarCharger/Server/ServiceCollectionExtensions.cs @@ -81,7 +81,7 @@ public static IServiceCollection AddMyDependencies(this IServiceCollection servi }, ServiceLifetime.Transient, ServiceLifetime.Transient) .AddTransient() .AddTransient() - .AddSingleton() + .AddTransient() .AddTransient() .AddTransient() .AddTransient() diff --git a/TeslaSolarCharger/Server/Services/IssueValidationService.cs b/TeslaSolarCharger/Server/Services/IssueValidationService.cs index 15565ea0c..0e57f3c29 100644 --- a/TeslaSolarCharger/Server/Services/IssueValidationService.cs +++ b/TeslaSolarCharger/Server/Services/IssueValidationService.cs @@ -1,5 +1,6 @@ using Microsoft.EntityFrameworkCore; using System.Diagnostics; +using System.Net.Sockets; using TeslaSolarCharger.Model.Contracts; using TeslaSolarCharger.Server.Contracts; using TeslaSolarCharger.Server.Resources.PossibleIssues; @@ -47,6 +48,13 @@ public async Task> RefreshIssues(TimeSpan clientTimeZoneId) { _logger.LogTrace("{method}()", nameof(RefreshIssues)); var issueList = new List(); + if (_settings.CrashedOnStartup) + { + var crashedOnStartupIssue = _possibleIssues.GetIssueByKey(_issueKeys.CrashedOnStartup); + crashedOnStartupIssue.PossibleSolutions.Add($"Exeption Message: {_settings.StartupCrashMessage}"); + issueList.Add(crashedOnStartupIssue); + return issueList; + } if (!_configurationWrapper.UseFleetApi()) { issueList.Add(_possibleIssues.GetIssueByKey(_issueKeys.NewTeslaApiNotUsed)); diff --git a/TeslaSolarCharger/Shared/Dtos/Contracts/ISettings.cs b/TeslaSolarCharger/Shared/Dtos/Contracts/ISettings.cs index a9266cfb9..ba3bedbc8 100644 --- a/TeslaSolarCharger/Shared/Dtos/Contracts/ISettings.cs +++ b/TeslaSolarCharger/Shared/Dtos/Contracts/ISettings.cs @@ -17,4 +17,6 @@ public interface ISettings DateTimeOffset LastPvValueUpdate { get; set; } int? AverageHomeGridVoltage { get; set; } int TeslaApiRequestCounter { get; set; } + bool CrashedOnStartup { get; set; } + string? StartupCrashMessage { get; set; } } diff --git a/TeslaSolarCharger/Shared/Dtos/Issue.cs b/TeslaSolarCharger/Shared/Dtos/Issue.cs index 0b58888e3..5eba1dbb6 100644 --- a/TeslaSolarCharger/Shared/Dtos/Issue.cs +++ b/TeslaSolarCharger/Shared/Dtos/Issue.cs @@ -10,6 +10,6 @@ public Issue() } public string IssueMessage { get; set; } - public string[] PossibleSolutions { get; set; } = new string[]{}; + public List PossibleSolutions { get; set; } = new() {}; public IssueType? IssueType { get; set; } } diff --git a/TeslaSolarCharger/Shared/Dtos/Settings/Settings.cs b/TeslaSolarCharger/Shared/Dtos/Settings/Settings.cs index aa90a049d..90a3683ea 100644 --- a/TeslaSolarCharger/Shared/Dtos/Settings/Settings.cs +++ b/TeslaSolarCharger/Shared/Dtos/Settings/Settings.cs @@ -22,5 +22,8 @@ public Settings() public int? AverageHomeGridVoltage { get; set; } public int TeslaApiRequestCounter { get; set; } = 0; + public bool CrashedOnStartup { get; set; } + public string? StartupCrashMessage { get; set; } + public List Cars { get; set; } } From 2d066043c275c15de66c8ee5c6947f23184f1e6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Tue, 2 Jan 2024 17:42:44 +0100 Subject: [PATCH 42/50] feat(BackupoComponent): add warning for private information in backup --- TeslaSolarCharger/Client/Components/BackupComponent.razor | 3 +++ 1 file changed, 3 insertions(+) diff --git a/TeslaSolarCharger/Client/Components/BackupComponent.razor b/TeslaSolarCharger/Client/Components/BackupComponent.razor index c8ca98f6b..900efdc44 100644 --- a/TeslaSolarCharger/Client/Components/BackupComponent.razor +++ b/TeslaSolarCharger/Client/Components/BackupComponent.razor @@ -9,6 +9,9 @@
During the backup or restore process all TSC actions will be stopped and started again after the Backup

Backup

+ @if (_processingBackup) From dfc37c7daf9a04935fa1c272220b243cbc8bd14b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Tue, 2 Jan 2024 20:40:11 +0100 Subject: [PATCH 43/50] feat(RESTPvValueComponent): use MudText instead of default text field --- .../Components/RestPvValueComponent.razor | 19 +++++++++++-------- TeslaSolarCharger/Server/Program.cs | 7 ++++--- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/TeslaSolarCharger/Client/Components/RestPvValueComponent.razor b/TeslaSolarCharger/Client/Components/RestPvValueComponent.razor index 7de14aaff..9f3188746 100644 --- a/TeslaSolarCharger/Client/Components/RestPvValueComponent.razor +++ b/TeslaSolarCharger/Client/Components/RestPvValueComponent.razor @@ -35,18 +35,21 @@ else
@if (Headers.Count > 0) { - - +

Headers

} @for (var i = 0; i < Headers.Count; i++) { var header = Headers.ElementAt(i); -
- - - +
+
+ +
+
+ +
+
+ +
}
diff --git a/TeslaSolarCharger/Server/Program.cs b/TeslaSolarCharger/Server/Program.cs index 57bae20fd..3581f38b4 100644 --- a/TeslaSolarCharger/Server/Program.cs +++ b/TeslaSolarCharger/Server/Program.cs @@ -59,6 +59,10 @@ var coreService = app.Services.GetRequiredService(); coreService.LogVersion(); + //Do nothing before these lines as database is created here. + var teslaSolarChargerContext = app.Services.GetRequiredService(); + await teslaSolarChargerContext.Database.MigrateAsync().ConfigureAwait(false); + var backendApiService = app.Services.GetRequiredService(); await backendApiService.PostInstallationInformation("Startup").ConfigureAwait(false); @@ -70,9 +74,6 @@ coreService.KillAllServices().GetAwaiter().GetResult(); }); - var teslaSolarChargerContext = app.Services.GetRequiredService(); - await teslaSolarChargerContext.Database.MigrateAsync().ConfigureAwait(false); - var tscConfigurationService = app.Services.GetRequiredService(); var installationId = await tscConfigurationService.GetInstallationId().ConfigureAwait(false); logger.LogTrace("Installation Id: {installationId}", installationId); From 655ef6475701deb587bfe808d8ffdfa9c7154c4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Tue, 2 Jan 2024 23:37:09 +0100 Subject: [PATCH 44/50] feat(README): remove wakatime --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 406613aee..5795674c5 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,6 @@ [![](https://img.shields.io/badge/Donate-PayPal-ff69b4.svg)](https://www.paypal.com/donate/?hosted_button_id=S3CK8Q9KV3JUL) [![edgeRelease](https://github.com/pkuehnel/TeslaSolarCharger/actions/workflows/edgeRelease.yml/badge.svg)](https://github.com/pkuehnel/TeslaSolarCharger/actions/workflows/edgeRelease.yml) -Time spent in project since 14th April 2023:
-[![wakatime](https://wakatime.com/badge/github/pkuehnel/TeslaSolarCharger.svg)](https://wakatime.com/badge/github/pkuehnel/TeslaSolarCharger) - TeslaSolarCharger is a service to set one or multiple Teslas' charging current using the datalogger **[TeslaMate](https://github.com/adriankumpf/teslamate)**. From 779e08e080f4ec1c884f5468c37366033bd0d620 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Tue, 2 Jan 2024 23:44:17 +0100 Subject: [PATCH 45/50] feat(Program): send startup exception to backend --- TeslaSolarCharger/Server/Program.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/TeslaSolarCharger/Server/Program.cs b/TeslaSolarCharger/Server/Program.cs index 3581f38b4..bcbf65dcf 100644 --- a/TeslaSolarCharger/Server/Program.cs +++ b/TeslaSolarCharger/Server/Program.cs @@ -102,6 +102,10 @@ var settings = app.Services.GetRequiredService(); settings.CrashedOnStartup = true; settings.StartupCrashMessage = ex.Message; + var backendApiService = app.Services.GetRequiredService(); + await backendApiService.PostErrorInformation(nameof(Program), "Startup", + $"Exception Message: {ex.Message} StackTrace: {ex.StackTrace}") + .ConfigureAwait(false); } // Configure the HTTP request pipeline. From fd1ba8ddd5f8da8926c6651e2ef72f77c20e3293 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Jan 2024 03:29:31 +0000 Subject: [PATCH 46/50] build(deps): Bump MudBlazor from 6.11.2 to 6.12.0 Bumps [MudBlazor](https://github.com/MudBlazor/MudBlazor) from 6.11.2 to 6.12.0. - [Release notes](https://github.com/MudBlazor/MudBlazor/releases) - [Changelog](https://github.com/MudBlazor/MudBlazor/blob/dev/CHANGELOG.md) - [Commits](https://github.com/MudBlazor/MudBlazor/compare/v6.11.2...v6.12.0) --- updated-dependencies: - dependency-name: MudBlazor dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj b/TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj index 5b5c13c1e..29c8ab838 100644 --- a/TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj +++ b/TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj @@ -18,7 +18,7 @@ - + From 36021ca1030403622135dca522a567ff930e9a5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Fri, 5 Jan 2024 23:52:13 +0100 Subject: [PATCH 47/50] fix(possibleIssues): fix typo in Token not yet requested issue --- .../Server/Resources/PossibleIssues/PossibleIssues.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TeslaSolarCharger/Server/Resources/PossibleIssues/PossibleIssues.cs b/TeslaSolarCharger/Server/Resources/PossibleIssues/PossibleIssues.cs index 1cb89fe40..d1161799e 100644 --- a/TeslaSolarCharger/Server/Resources/PossibleIssues/PossibleIssues.cs +++ b/TeslaSolarCharger/Server/Resources/PossibleIssues/PossibleIssues.cs @@ -144,7 +144,7 @@ public PossibleIssues(IssueKeys issueKeys) { issueKeys.FleetApiTokenNotRequested, CreateIssue("You did not request a Tesla Token, yet.", IssueType.Error, - "Open the Base Configuration and request a new token. Important: You need to allow acces to all selectable scopes." + "Open the Base Configuration and request a new token. Important: You need to allow access to all selectable scopes." ) }, { From 9f5bb33193895e2030a74d9d826e10ca2604ec82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Sat, 6 Jan 2024 02:30:09 +0100 Subject: [PATCH 48/50] feat(BackendApiService): post local base url to backend to be able to redirect --- TeslaSolarCharger/Client/Pages/BaseConfiguration.razor | 3 ++- TeslaSolarCharger/Server/Controllers/FleetApiController.cs | 2 +- TeslaSolarCharger/Server/Services/BackendApiService.cs | 4 ++-- .../Server/Services/Contracts/IBackendApiService.cs | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/TeslaSolarCharger/Client/Pages/BaseConfiguration.razor b/TeslaSolarCharger/Client/Pages/BaseConfiguration.razor index a53341f91..fe23b5fb1 100644 --- a/TeslaSolarCharger/Client/Pages/BaseConfiguration.razor +++ b/TeslaSolarCharger/Client/Pages/BaseConfiguration.razor @@ -586,7 +586,8 @@ else { _tokenGenerationButtonDisabled = true; var locale = CultureInfo.CurrentCulture.ToString(); - var url = await HttpClient.GetFromJsonAsync>($"api/FleetApi/GetOauthUrl?locale={locale}").ConfigureAwait(false); + var baseUrl = NavigationManager.BaseUri; + var url = await HttpClient.GetFromJsonAsync>($"api/FleetApi/GetOauthUrl?locale={Uri.EscapeDataString(locale)}&baseUrl={Uri.EscapeDataString(baseUrl)}").ConfigureAwait(false); if (url?.Value != null) { NavigationManager.NavigateTo(url.Value); diff --git a/TeslaSolarCharger/Server/Controllers/FleetApiController.cs b/TeslaSolarCharger/Server/Controllers/FleetApiController.cs index ee4403095..d95af29e5 100644 --- a/TeslaSolarCharger/Server/Controllers/FleetApiController.cs +++ b/TeslaSolarCharger/Server/Controllers/FleetApiController.cs @@ -19,7 +19,7 @@ public class FleetApiController( public Task> FleetApiTokenState() => fleetApiService.GetFleetApiTokenState(); [HttpGet] - public Task> GetOauthUrl(string locale) => backendApiService.StartTeslaOAuth(locale); + public Task> GetOauthUrl(string locale, string baseUrl) => backendApiService.StartTeslaOAuth(locale, baseUrl); [HttpGet] public Task RefreshFleetApiToken() => fleetApiService.RefreshTokenAsync(); diff --git a/TeslaSolarCharger/Server/Services/BackendApiService.cs b/TeslaSolarCharger/Server/Services/BackendApiService.cs index 82f5785d3..defab83a2 100644 --- a/TeslaSolarCharger/Server/Services/BackendApiService.cs +++ b/TeslaSolarCharger/Server/Services/BackendApiService.cs @@ -33,7 +33,7 @@ public BackendApiService(ILogger logger, ITscConfigurationSer _dateTimeProvider = dateTimeProvider; } - public async Task> StartTeslaOAuth(string locale) + public async Task> StartTeslaOAuth(string locale, string baseUrl) { _logger.LogTrace("{method}()", nameof(StartTeslaOAuth)); var currentTokens = await _teslaSolarChargerContext.TeslaTokens.ToListAsync().ConfigureAwait(false); @@ -47,7 +47,7 @@ public async Task> StartTeslaOAuth(string locale) var installationId = await _tscConfigurationService.GetInstallationId().ConfigureAwait(false); var backendApiBaseUrl = _configurationWrapper.BackendApiBaseUrl(); using var httpClient = new HttpClient(); - var requestUri = $"{backendApiBaseUrl}Tsc/StartTeslaOAuth?installationId={installationId}"; + var requestUri = $"{backendApiBaseUrl}Tsc/StartTeslaOAuth?installationId={Uri.EscapeDataString(installationId.ToString())}&baseUrl={Uri.EscapeDataString(baseUrl)}"; var responseString = await httpClient.GetStringAsync(requestUri).ConfigureAwait(false); var oAuthRequestInformation = JsonConvert.DeserializeObject(responseString) ?? throw new InvalidDataException("Could not get oAuth data"); var requestUrl = GenerateAuthUrl(oAuthRequestInformation, locale); diff --git a/TeslaSolarCharger/Server/Services/Contracts/IBackendApiService.cs b/TeslaSolarCharger/Server/Services/Contracts/IBackendApiService.cs index c5449df0f..e8e79b497 100644 --- a/TeslaSolarCharger/Server/Services/Contracts/IBackendApiService.cs +++ b/TeslaSolarCharger/Server/Services/Contracts/IBackendApiService.cs @@ -4,7 +4,7 @@ namespace TeslaSolarCharger.Server.Services.Contracts; public interface IBackendApiService { - Task> StartTeslaOAuth(string locale); + Task> StartTeslaOAuth(string locale, string baseUrl); Task PostInstallationInformation(string reason); Task PostErrorInformation(string source, string methodName, string message, string? stackTrace = null); } From d5a9e263516c8e411da8cc5a0acb411185262b5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Sat, 6 Jan 2024 02:42:38 +0100 Subject: [PATCH 49/50] feat(appsettings.json): set fleet API as default --- TeslaSolarCharger/Server/appsettings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TeslaSolarCharger/Server/appsettings.json b/TeslaSolarCharger/Server/appsettings.json index d324c5b4e..179ab3778 100644 --- a/TeslaSolarCharger/Server/appsettings.json +++ b/TeslaSolarCharger/Server/appsettings.json @@ -61,7 +61,7 @@ "GeoFence": "Home", "DisplayApiRequestCounter": false, "IgnoreSslErrors": false, - "UseFleetApi": false, + "UseFleetApi": true, "FleetApiClientId": "f29f71d6285a-4873-8b6b-80f15854892e", "BackendApiBaseUrl": "https://www.teslasolarcharger.de/api/", "TeslaFleetApiBaseUrl": "https://www.teslasolarcharger.de/teslaproxy/", From 0f35112ee55fdf8b346c2de4a7005b69cb14e76a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20K=C3=BChnel?= Date: Sat, 6 Jan 2024 02:44:19 +0100 Subject: [PATCH 50/50] feat(IssueValidationService): remove use fleet api issue --- .../Server/Resources/PossibleIssues/IssueKeys.cs | 1 - .../Server/Resources/PossibleIssues/PossibleIssues.cs | 7 ------- .../Server/Services/IssueValidationService.cs | 4 ---- 3 files changed, 12 deletions(-) diff --git a/TeslaSolarCharger/Server/Resources/PossibleIssues/IssueKeys.cs b/TeslaSolarCharger/Server/Resources/PossibleIssues/IssueKeys.cs index cf8f12040..4c34de7d2 100644 --- a/TeslaSolarCharger/Server/Resources/PossibleIssues/IssueKeys.cs +++ b/TeslaSolarCharger/Server/Resources/PossibleIssues/IssueKeys.cs @@ -19,7 +19,6 @@ public class IssueKeys public string VersionNotUpToDate => "VersionNotUpToDate"; public string CorrectionFactorZero => "CorrectionFactorZero"; public string ServerTimeZoneDifferentFromClient => "ServerTimeZoneDifferentFromClient"; - public string NewTeslaApiNotUsed => "NewTeslaApiNotUsed"; public string FleetApiTokenNotRequested => "FleetApiTokenNotRequested"; public string FleetApiTokenUnauthorized => "FleetApiTokenUnauthorized"; public string FleetApiTokenMissingScopes => "FleetApiTokenMissingScopes"; diff --git a/TeslaSolarCharger/Server/Resources/PossibleIssues/PossibleIssues.cs b/TeslaSolarCharger/Server/Resources/PossibleIssues/PossibleIssues.cs index d1161799e..edb7eb947 100644 --- a/TeslaSolarCharger/Server/Resources/PossibleIssues/PossibleIssues.cs +++ b/TeslaSolarCharger/Server/Resources/PossibleIssues/PossibleIssues.cs @@ -134,13 +134,6 @@ public PossibleIssues(IssueKeys issueKeys) "Update the TimeZone of the TeslaSolarChargerContainer in your docker-compose.yml." ) }, - { - issueKeys.NewTeslaApiNotUsed, CreateIssue("New cars need a new Tesla API. As this is in a very early beta state I highly recommend not using it if your car supports the old API!", - IssueType.Information, - "To use the new API add UseFleetApi=true as environment variable in your docker-compose.yml", - "Sorry for this information beeing not removable - as switching to the new API in January 2024 as default you won't see this information from then on." - ) - }, { issueKeys.FleetApiTokenNotRequested, CreateIssue("You did not request a Tesla Token, yet.", IssueType.Error, diff --git a/TeslaSolarCharger/Server/Services/IssueValidationService.cs b/TeslaSolarCharger/Server/Services/IssueValidationService.cs index 0e57f3c29..998371c92 100644 --- a/TeslaSolarCharger/Server/Services/IssueValidationService.cs +++ b/TeslaSolarCharger/Server/Services/IssueValidationService.cs @@ -55,10 +55,6 @@ public async Task> RefreshIssues(TimeSpan clientTimeZoneId) issueList.Add(crashedOnStartupIssue); return issueList; } - if (!_configurationWrapper.UseFleetApi()) - { - issueList.Add(_possibleIssues.GetIssueByKey(_issueKeys.NewTeslaApiNotUsed)); - } issueList.AddRange(GetServerConfigurationIssues(clientTimeZoneId)); if (Debugger.IsAttached) {