runtime; build; native; contentfiles; analyzers; buildtransitive
all
diff --git a/TeslaSolarCharger.sln b/TeslaSolarCharger.sln
index 5ea150bad..e345e9c12 100644
--- a/TeslaSolarCharger.sln
+++ b/TeslaSolarCharger.sln
@@ -29,6 +29,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
README.md = README.md
EndProjectSection
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TeslaSolarCharger.GridPriceProvider", "TeslaSolarCharger.GridPriceProvider\TeslaSolarCharger.GridPriceProvider.csproj", "{1BE60FFB-0C76-4D8A-8FD5-04C886885AB9}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -75,6 +77,10 @@ Global
{9958124C-3B96-4186-AFFA-1477C6582D84}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9958124C-3B96-4186-AFFA-1477C6582D84}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9958124C-3B96-4186-AFFA-1477C6582D84}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1BE60FFB-0C76-4D8A-8FD5-04C886885AB9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1BE60FFB-0C76-4D8A-8FD5-04C886885AB9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1BE60FFB-0C76-4D8A-8FD5-04C886885AB9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1BE60FFB-0C76-4D8A-8FD5-04C886885AB9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/TeslaSolarCharger/Client/Components/FixedPriceComponent.razor b/TeslaSolarCharger/Client/Components/FixedPriceComponent.razor
new file mode 100644
index 000000000..b1ddc07fa
--- /dev/null
+++ b/TeslaSolarCharger/Client/Components/FixedPriceComponent.razor
@@ -0,0 +1,91 @@
+@using TeslaSolarCharger.Shared.Dtos.ChargingCost.CostConfigurations
+
+@if (FixedPrice == null)
+{
+
+}
+else
+{
+
+
+
+
+
+
+
+
+
+
+ :
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ :
+
+
+
+
+
+
+
+
+
+
+}
+
+@code {
+ [Parameter]
+ public FixedPrice? FixedPrice { get; set; }
+
+ private void CheckboxChanged(DayOfWeek day)
+ {
+ if(FixedPrice?.ValidOnDays == null)
+ {
+ return;
+ }
+
+ if (FixedPrice.ValidOnDays.Contains(day))
+ {
+ FixedPrice.ValidOnDays.Remove(day);
+ }
+ else
+ {
+ FixedPrice.ValidOnDays.Add(day);
+ }
+ }
+}
diff --git a/TeslaSolarCharger/Client/Pages/ChargeCostDetail.razor b/TeslaSolarCharger/Client/Pages/ChargeCostDetail.razor
index d380cf9de..3b7c3ace2 100644
--- a/TeslaSolarCharger/Client/Pages/ChargeCostDetail.razor
+++ b/TeslaSolarCharger/Client/Pages/ChargeCostDetail.razor
@@ -2,6 +2,8 @@
@page "/ChargePrice/new"
@using TeslaSolarCharger.Shared.Dtos.ChargingCost
@using TeslaSolarCharger.Shared.Contracts
+@using TeslaSolarCharger.Shared.Dtos.ChargingCost.CostConfigurations
+@using TeslaSolarCharger.Shared.Enums
@inject HttpClient HttpClient
@inject NavigationManager NavigationManager
@inject IDateTimeProvider DateTimeProvider
@@ -44,6 +46,18 @@ else
+
+
+
+
+ Fixed Price or Spot price
+ Time based prices
+
+
+
-
-
-
- Use Spot Prices
-
-
- Enable this if you are using dynamic prices based on EPEX Spot DE (e.g. Tibber or aWATTar)
-
-
- @if (ChargePrice.AddSpotPriceToGridPrice)
+ @if (ChargePrice.EnergyProvider == EnergyProvider.FixedPrice && ChargePrice.FixedPrices != null)
{
-
-
-
-
-
+ You can specify times with special prices here. If there are times left, you didn't specify a price for, the default grid price, specified above, is used.
+ @foreach (var fixedPrice in ChargePrice.FixedPrices)
+ {
+
+
+
+
+
+
+
+
+
+
+ DeleteFixedPrice(fixedPrice)">
+
+
+
+
+ }
+ Add time based price
}
+
+ @if (ChargePrice.EnergyProvider == EnergyProvider.OldTeslaSolarChargerConfig)
+ {
+
+
+
+ Use Spot Prices
+
+
+ Enable this if you are using dynamic prices based on EPEX Spot DE (e.g. Tibber or aWATTar)
+
+
+ @if (ChargePrice.AddSpotPriceToGridPrice)
+ {
+
+
+
+
+
+ }
+ }
+
@if (SubmitIsLoading)
@@ -113,6 +163,7 @@ else
ValidSince = DateTimeProvider.Now(),
GridPrice = new decimal(0.285),
SolarPrice = new decimal(0.12),
+ EnergyProvider = EnergyProvider.OldTeslaSolarChargerConfig,
};
}
}
@@ -129,4 +180,36 @@ else
SubmitIsLoading = false;
NavigateToList();
}
+
+ private void EnergyProviderChanged(EnergyProvider energyProvider)
+ {
+ if(ChargePrice == null)
+ {
+ return;
+ }
+ ChargePrice.EnergyProvider = energyProvider;
+ ChargePrice.FixedPrices = energyProvider == EnergyProvider.FixedPrice ? new List() : null;
+ }
+
+ private void AddFixedPrice()
+ {
+ if(ChargePrice?.FixedPrices == null)
+ {
+ return;
+ }
+ ChargePrice.FixedPrices.Add(new FixedPrice()
+ {
+ ValidOnDays = new List(),
+ });
+ }
+
+ private void DeleteFixedPrice(FixedPrice fixedPrice)
+ {
+ if(ChargePrice?.FixedPrices == null)
+ {
+ return;
+ }
+ ChargePrice.FixedPrices.Remove(fixedPrice);
+ }
+
}
diff --git a/TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj b/TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj
index caa433013..bc514b61c 100644
--- a/TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj
+++ b/TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj
@@ -18,8 +18,8 @@
-
-
+
+
diff --git a/TeslaSolarCharger/Server/Contracts/ICoreService.cs b/TeslaSolarCharger/Server/Contracts/ICoreService.cs
index bc13e24c3..706836fb9 100644
--- a/TeslaSolarCharger/Server/Contracts/ICoreService.cs
+++ b/TeslaSolarCharger/Server/Contracts/ICoreService.cs
@@ -1,4 +1,5 @@
-using TeslaSolarCharger.Shared.Dtos;
+using TeslaSolarCharger.GridPriceProvider.Data;
+using TeslaSolarCharger.Shared.Dtos;
namespace TeslaSolarCharger.Server.Contracts;
@@ -17,4 +18,5 @@ public interface ICoreService
Task DisconnectMqttServices();
DtoValue TeslaApiRequestsSinceStartup();
DtoValue ShouldDisplayApiRequestCounter();
+ Task> GetPriceData(DateTimeOffset from, DateTimeOffset to);
}
diff --git a/TeslaSolarCharger/Server/Controllers/HelloController.cs b/TeslaSolarCharger/Server/Controllers/HelloController.cs
index 5f12597c9..f1fe03f4c 100644
--- a/TeslaSolarCharger/Server/Controllers/HelloController.cs
+++ b/TeslaSolarCharger/Server/Controllers/HelloController.cs
@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc;
+using TeslaSolarCharger.GridPriceProvider.Data;
using TeslaSolarCharger.Server.Contracts;
using TeslaSolarCharger.Shared.Dtos;
using TeslaSolarCharger.SharedBackend.Abstracts;
@@ -49,5 +50,8 @@ public HelloController(ICoreService coreService)
[HttpGet]
public Task> ShouldDisplayApiRequestCounter() => Task.FromResult(_coreService.ShouldDisplayApiRequestCounter());
+
+ [HttpGet]
+ public Task> GetPriceData(DateTimeOffset from, DateTimeOffset to) => _coreService.GetPriceData(from, to);
}
}
diff --git a/TeslaSolarCharger/Server/Program.cs b/TeslaSolarCharger/Server/Program.cs
index 7f1cb975d..e70e02fc1 100644
--- a/TeslaSolarCharger/Server/Program.cs
+++ b/TeslaSolarCharger/Server/Program.cs
@@ -1,5 +1,6 @@
using Microsoft.EntityFrameworkCore;
using Serilog;
+using TeslaSolarCharger.GridPriceProvider;
using TeslaSolarCharger.Model.Contracts;
using TeslaSolarCharger.Server;
using TeslaSolarCharger.Server.Contracts;
@@ -8,6 +9,8 @@
var builder = WebApplication.CreateBuilder(args);
+//To get valus from configuration before dependency injection is set up
+var configurationManager = builder.Configuration;
// Add services to the container.
builder.Services.AddControllersWithViews();
@@ -19,7 +22,7 @@
builder.Services.AddSwaggerGen();
builder.Services.AddMyDependencies();
-
+builder.Services.AddGridPriceProvider();
builder.Host.UseSerilog((context, configuration) => configuration
.ReadFrom.Configuration(context.Configuration));
diff --git a/TeslaSolarCharger/Server/Services/ChargingCostService.cs b/TeslaSolarCharger/Server/Services/ChargingCostService.cs
index 8ae82b615..c90cd01e8 100644
--- a/TeslaSolarCharger/Server/Services/ChargingCostService.cs
+++ b/TeslaSolarCharger/Server/Services/ChargingCostService.cs
@@ -1,12 +1,16 @@
using AutoMapper.QueryableExtensions;
using Microsoft.EntityFrameworkCore;
+using TeslaSolarCharger.GridPriceProvider.Data;
+using TeslaSolarCharger.GridPriceProvider.Services.Interfaces;
using TeslaSolarCharger.Model.Contracts;
using TeslaSolarCharger.Model.Entities.TeslaMate;
using TeslaSolarCharger.Model.Entities.TeslaSolarCharger;
using TeslaSolarCharger.Server.Contracts;
+using TeslaSolarCharger.Server.Dtos;
using TeslaSolarCharger.Server.MappingExtensions;
using TeslaSolarCharger.Shared.Contracts;
using TeslaSolarCharger.Shared.Dtos.ChargingCost;
+using TeslaSolarCharger.Shared.Dtos.ChargingCost.CostConfigurations;
using TeslaSolarCharger.Shared.Dtos.Contracts;
using TeslaSolarCharger.Shared.Enums;
@@ -21,11 +25,13 @@ public class ChargingCostService : IChargingCostService
private readonly ISettings _settings;
private readonly IMapperConfigurationFactory _mapperConfigurationFactory;
private readonly IConfigurationWrapper _configurationWrapper;
+ private readonly IFixedPriceService _fixedPriceService;
public ChargingCostService(ILogger logger,
ITeslaSolarChargerContext teslaSolarChargerContext, ITeslamateContext teslamateContext,
IDateTimeProvider dateTimeProvider, ISettings settings,
- IMapperConfigurationFactory mapperConfigurationFactory, IConfigurationWrapper configurationWrapper)
+ IMapperConfigurationFactory mapperConfigurationFactory, IConfigurationWrapper configurationWrapper,
+ IFixedPriceService fixedPriceService)
{
_logger = logger;
_teslaSolarChargerContext = teslaSolarChargerContext;
@@ -34,6 +40,7 @@ public ChargingCostService(ILogger logger,
_settings = settings;
_mapperConfigurationFactory = mapperConfigurationFactory;
_configurationWrapper = configurationWrapper;
+ _fixedPriceService = fixedPriceService;
}
public async Task UpdateChargePrice(DtoChargePrice dtoChargePrice)
@@ -55,8 +62,29 @@ public async Task UpdateChargePrice(DtoChargePrice dtoChargePrice)
chargePrice.GridPrice = (decimal)dtoChargePrice.GridPrice!;
chargePrice.SolarPrice = (decimal)dtoChargePrice.SolarPrice!;
chargePrice.ValidSince = dtoChargePrice.ValidSince;
+ chargePrice.EnergyProvider = dtoChargePrice.EnergyProvider;
chargePrice.AddSpotPriceToGridPrice = dtoChargePrice.AddSpotPriceToGridPrice;
chargePrice.SpotPriceCorrectionFactor = (dtoChargePrice.SpotPriceSurcharge ?? 0) / 100;
+ switch (dtoChargePrice.EnergyProvider)
+ {
+ case EnergyProvider.Octopus:
+ break;
+ case EnergyProvider.Tibber:
+ break;
+ case EnergyProvider.FixedPrice:
+ chargePrice.EnergyProviderConfiguration = _fixedPriceService.GenerateConfigString(dtoChargePrice.FixedPrices ?? throw new InvalidOperationException());
+ break;
+ case EnergyProvider.Awattar:
+ break;
+ case EnergyProvider.Energinet:
+ break;
+ case EnergyProvider.HomeAssistant:
+ break;
+ case EnergyProvider.OldTeslaSolarChargerConfig:
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
await _teslaSolarChargerContext.SaveChangesAsync().ConfigureAwait(false);
await UpdateHandledChargesPriceCalculation().ConfigureAwait(false);
@@ -82,7 +110,7 @@ private async Task UpdateHandledChargesPriceCalculation()
continue;
}
- UpdateChargingProcessCosts(handledCharge, chargePrice, chargingProcess);
+ await UpdateChargingProcessCosts(handledCharge, chargePrice, chargingProcess).ConfigureAwait(false);
}
await _teslaSolarChargerContext.SaveChangesAsync().ConfigureAwait(false);
await _teslamateContext.SaveChangesAsync().ConfigureAwait(false);
@@ -383,6 +411,7 @@ private async Task FinalizeHandledCharges(List handledCharges)
var relevantPowerDistributions = await _teslaSolarChargerContext.PowerDistributions
.Where(p => p.HandledCharge == openHandledCharge)
.OrderBy(p => p.TimeStamp)
+ .AsNoTracking()
.ToListAsync().ConfigureAwait(false);
var price = chargePrices
.FirstOrDefault(p => p.ValidSince < chargingProcess.StartDate);
@@ -394,25 +423,123 @@ private async Task FinalizeHandledCharges(List handledCharges)
if (price != default)
{
- UpdateChargingProcessCosts(openHandledCharge, price, chargingProcess);
+ await UpdateChargingProcessCosts(openHandledCharge, price, chargingProcess).ConfigureAwait(false);
}
}
await _teslaSolarChargerContext.SaveChangesAsync().ConfigureAwait(false);
await _teslamateContext.SaveChangesAsync().ConfigureAwait(false);
}
- private void UpdateChargingProcessCosts(HandledCharge openHandledCharge, ChargePrice price,
+ private async Task UpdateChargingProcessCosts(HandledCharge openHandledCharge, ChargePrice price,
ChargingProcess chargingProcess)
{
- openHandledCharge.CalculatedPrice = price.GridPrice * openHandledCharge.UsedGridEnergy +
- price.SolarPrice * openHandledCharge.UsedSolarEnergy;
- if (price.AddSpotPriceToGridPrice)
+ if (chargingProcess.EndDate == null)
{
- openHandledCharge.CalculatedPrice += openHandledCharge.AverageSpotPrice * openHandledCharge.UsedGridEnergy;
+ _logger.LogWarning("Charging process {id} has no end date. Can not calculate costs.", chargingProcess.Id);
+ return;
}
+
+ var relevantPowerDistributions = await _teslaSolarChargerContext.PowerDistributions
+ .Where(p => p.HandledCharge == openHandledCharge)
+ .OrderBy(p => p.TimeStamp)
+ .AsNoTracking()
+ .ToListAsync().ConfigureAwait(false);
+
+ List prices;
+ decimal? gridCost = null;
+
+ switch (price.EnergyProvider)
+ {
+ case EnergyProvider.Octopus:
+ throw new NotImplementedException();
+ break;
+ case EnergyProvider.Tibber:
+ break;
+ case EnergyProvider.FixedPrice:
+ prices = (await _fixedPriceService.GetPriceData(chargingProcess.StartDate, chargingProcess.EndDate.Value, price.EnergyProviderConfiguration).ConfigureAwait(false)).ToList();
+ gridCost = GetGridChargeCosts(relevantPowerDistributions, prices, price.GridPrice);
+ break;
+ case EnergyProvider.Awattar:
+ throw new NotImplementedException();
+ break;
+ case EnergyProvider.Energinet:
+ throw new NotImplementedException();
+ break;
+ case EnergyProvider.HomeAssistant:
+ throw new NotImplementedException();
+ break;
+ case EnergyProvider.OldTeslaSolarChargerConfig:
+ gridCost = price.GridPrice * openHandledCharge.UsedGridEnergy;
+ if (price.AddSpotPriceToGridPrice)
+ {
+ gridCost += openHandledCharge.AverageSpotPrice * openHandledCharge.UsedGridEnergy;
+ }
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ openHandledCharge.CalculatedPrice = gridCost + price.SolarPrice * openHandledCharge.UsedSolarEnergy;
chargingProcess.Cost = openHandledCharge.CalculatedPrice;
}
+ internal decimal? GetGridChargeCosts(List relevantPowerDistributions, List prices, decimal priceGridPrice)
+ {
+ try
+ {
+ var priceGroups = GroupDistributionsByPrice(prices, relevantPowerDistributions, priceGridPrice);
+ decimal totalCost = 0;
+ foreach (var priceGroup in priceGroups)
+ {
+ var usedEnergyWhilePriceGroupsWasActive = priceGroup.Value
+ .Select(p => p.UsedWattHours * p.GridProportion ?? 0)
+ .Sum();
+ var usedkWh = usedEnergyWhilePriceGroupsWasActive / 1000;
+ totalCost += (decimal)(usedkWh * (float)priceGroup.Key.Value);
+ }
+
+ return totalCost;
+ }
+ catch (Exception e)
+ {
+ _logger.LogError(e, "Error while calculating chargeCosts for HandledCharge {handledChargeId}", relevantPowerDistributions.FirstOrDefault()?.HandledChargeId);
+ return null;
+ }
+
+ }
+
+ private Dictionary> GroupDistributionsByPrice(List prices, List distributions,
+ decimal priceGridPrice)
+ {
+ var groupedByPrice = new Dictionary>();
+
+ foreach (var price in prices)
+ {
+ var relevantDistributions = distributions
+ .Where(d => d.TimeStamp >= price.ValidFrom.UtcDateTime &&
+ d.TimeStamp <= price.ValidTo.UtcDateTime)
+ .ToList();
+
+ groupedByPrice.Add(price, relevantDistributions);
+ distributions.RemoveAll(relevantDistributions.Contains);
+ }
+
+ if (distributions.Any())
+ {
+ var oldestDistribution = distributions.OrderBy(d => d.TimeStamp).First().TimeStamp;
+ var newestDistribution = distributions.OrderByDescending(d => d.TimeStamp).First().TimeStamp;
+ groupedByPrice.Add(
+ new Price()
+ {
+ ValidFrom = new DateTimeOffset(oldestDistribution, TimeSpan.Zero),
+ ValidTo = new DateTimeOffset(newestDistribution, TimeSpan.Zero),
+ Value = priceGridPrice,
+ },
+ distributions.ToList());
+ }
+
+ return groupedByPrice;
+ }
+
internal async Task CalculateAverageSpotPrice(List relevantPowerDistributions, ChargePrice? chargePrice)
{
var startTime = relevantPowerDistributions.First().TimeStamp;
@@ -531,14 +658,36 @@ public async Task GetChargePriceById(int id)
var mapper = _mapperConfigurationFactory.Create(cfg =>
{
cfg.CreateMap()
- .ForMember(d => d.Id, opt => opt.MapFrom(c => c.Id))
.ForMember(d => d.SpotPriceSurcharge, opt => opt.MapFrom(c => c.SpotPriceCorrectionFactor * 100))
+ .ForMember(d => d.FixedPrices, opt => opt.Ignore())
;
});
var chargePrices = await _teslaSolarChargerContext.ChargePrices
.Where(c => c.Id == id)
.ProjectTo(mapper)
.FirstAsync().ConfigureAwait(false);
+ switch (chargePrices.EnergyProvider)
+ {
+ case EnergyProvider.Octopus:
+ break;
+ case EnergyProvider.Tibber:
+ break;
+ case EnergyProvider.FixedPrice:
+ chargePrices.FixedPrices = chargePrices.EnergyProviderConfiguration != null ? _fixedPriceService.ParseConfigString(chargePrices.EnergyProviderConfiguration) : new List();
+ break;
+ case EnergyProvider.Awattar:
+ break;
+ case EnergyProvider.Energinet:
+ break;
+ case EnergyProvider.HomeAssistant:
+ break;
+ case EnergyProvider.OldTeslaSolarChargerConfig:
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+
+
return chargePrices;
}
diff --git a/TeslaSolarCharger/Server/Services/CoreService.cs b/TeslaSolarCharger/Server/Services/CoreService.cs
index 3ceb88a9f..d43c9f8f8 100644
--- a/TeslaSolarCharger/Server/Services/CoreService.cs
+++ b/TeslaSolarCharger/Server/Services/CoreService.cs
@@ -1,5 +1,7 @@
using System.Diagnostics;
using System.Reflection;
+using TeslaSolarCharger.GridPriceProvider.Data;
+using TeslaSolarCharger.GridPriceProvider.Services.Interfaces;
using TeslaSolarCharger.Server.Contracts;
using TeslaSolarCharger.Server.Scheduling;
using TeslaSolarCharger.Shared.Contracts;
@@ -19,10 +21,12 @@ public class CoreService : ICoreService
private readonly ITeslaMateMqttService _teslaMateMqttService;
private readonly ISolarMqttService _solarMqttService;
private readonly ISettings _settings;
+ private readonly IFixedPriceService _fixedPriceService;
public CoreService(ILogger logger, IChargingService chargingService, IConfigurationWrapper configurationWrapper,
IDateTimeProvider dateTimeProvider, IConfigJsonService configJsonService, JobManager jobManager,
- ITeslaMateMqttService teslaMateMqttService, ISolarMqttService solarMqttService, ISettings settings)
+ ITeslaMateMqttService teslaMateMqttService, ISolarMqttService solarMqttService, ISettings settings,
+ IFixedPriceService fixedPriceService)
{
_logger = logger;
_chargingService = chargingService;
@@ -33,6 +37,7 @@ public CoreService(ILogger logger, IChargingService chargingService
_teslaMateMqttService = teslaMateMqttService;
_solarMqttService = solarMqttService;
_settings = settings;
+ _fixedPriceService = fixedPriceService;
}
public Task GetCurrentVersion()
@@ -158,4 +163,10 @@ public DtoValue ShouldDisplayApiRequestCounter()
_logger.LogTrace("{method}()", nameof(TeslaApiRequestsSinceStartup));
return new DtoValue(_configurationWrapper.ShouldDisplayApiRequestCounter());
}
+
+ public Task> GetPriceData(DateTimeOffset from, DateTimeOffset to)
+ {
+ _logger.LogTrace("{method}({from}, {to})", nameof(GetPriceData), from, to);
+ return _fixedPriceService.GetPriceData(from, to, null);
+ }
}
diff --git a/TeslaSolarCharger/Server/Services/PvValueService.cs b/TeslaSolarCharger/Server/Services/PvValueService.cs
index 0e52d40e9..06f64d63c 100644
--- a/TeslaSolarCharger/Server/Services/PvValueService.cs
+++ b/TeslaSolarCharger/Server/Services/PvValueService.cs
@@ -1,6 +1,8 @@
using Newtonsoft.Json.Linq;
using System.Diagnostics;
using System.Globalization;
+using System.Net.Security;
+using System.Security.Cryptography.X509Certificates;
using System.Xml;
using TeslaSolarCharger.Server.Contracts;
using TeslaSolarCharger.Shared.Contracts;
@@ -224,11 +226,24 @@ await _telegramService.SendMessage(
private async Task GetHttpResponse(HttpRequestMessage request)
{
_logger.LogTrace("{method}({request}) [called by {callingMethod}]", nameof(GetHttpResponse), request, new StackTrace().GetFrame(1)?.GetMethod()?.Name);
- using var httpClient = new HttpClient();
+ var httpClientHandler = new HttpClientHandler();
+
+ if (_configurationWrapper.ShouldIgnoreSslErrors())
+ {
+ _logger.LogWarning("PV Value SSL errors are ignored.");
+ httpClientHandler.ServerCertificateCustomValidationCallback = MyRemoteCertificateValidationCallback;
+ }
+
+ using var httpClient = new HttpClient(httpClientHandler);
var response = await httpClient.SendAsync(request).ConfigureAwait(false);
return response;
}
+ private bool MyRemoteCertificateValidationCallback(HttpRequestMessage requestMessage, X509Certificate2? certificate, X509Chain? chain, SslPolicyErrors sslErrors)
+ {
+ return true; // Ignoriere alle Zertifikatfehler
+ }
+
private static HttpRequestMessage GenerateHttpRequestMessage(string? gridRequestUrl, Dictionary requestHeaders)
{
if (string.IsNullOrEmpty(gridRequestUrl))
@@ -379,10 +394,11 @@ internal double GetValueFromResult(string? pattern, string result, NodePatternTy
{
switch (patternType)
{
+ //allow JSON values to be null, as this is needed by SMA inverters: https://tff-forum.de/t/teslasolarcharger-laden-nach-pv-ueberschuss-mit-beliebiger-wallbox/170369/2728?u=mane123
case NodePatternType.Json:
_logger.LogTrace("Extract overage value from json {result} with {pattern}", result, pattern);
result = (JObject.Parse(result).SelectToken(pattern ?? throw new ArgumentNullException(nameof(pattern))) ??
- throw new InvalidOperationException("Could not find token by pattern")).Value() ?? throw new InvalidOperationException("Extracted Json Value is null");
+ throw new InvalidOperationException("Could not find token by pattern")).Value() ?? "0";
break;
case NodePatternType.Xml:
_logger.LogTrace("Extract overage value from xml {result} with {pattern}", result, pattern);
diff --git a/TeslaSolarCharger/Server/Services/SolarMqttService.cs b/TeslaSolarCharger/Server/Services/SolarMqttService.cs
index fff062cdc..4aeb50d35 100644
--- a/TeslaSolarCharger/Server/Services/SolarMqttService.cs
+++ b/TeslaSolarCharger/Server/Services/SolarMqttService.cs
@@ -80,7 +80,7 @@ public async Task ConnectMqttClient()
_pvValueService.AddOverageValueToInMemoryList((int)_setting.Overage);
}
}
- else if (topic == _configurationWrapper.CurrentInverterPowerMqttTopic() && frontendConfiguration.InverterValueSource == SolarValueSource.Mqtt)
+ if (topic == _configurationWrapper.CurrentInverterPowerMqttTopic() && frontendConfiguration.InverterValueSource == SolarValueSource.Mqtt)
{
var patternType = frontendConfiguration.InverterPowerNodePatternType ?? NodePatternType.Direct;
var jsonPattern = _configurationWrapper.CurrentInverterPowerJsonPattern();
@@ -92,7 +92,7 @@ public async Task ConnectMqttClient()
_setting.InverterPower = _pvValueService.GetIntegerValueByString(value, jsonPattern, xmlPattern, correctionFactor, patternType,
xmlAttributeHeaderName, xmlAttributeHeaderValue, xmlAttributeValueName);
}
- else if (topic == _configurationWrapper.HomeBatterySocMqttTopic() && frontendConfiguration.HomeBatteryValuesSource == SolarValueSource.Mqtt)
+ if (topic == _configurationWrapper.HomeBatterySocMqttTopic() && frontendConfiguration.HomeBatteryValuesSource == SolarValueSource.Mqtt)
{
var patternType = frontendConfiguration.HomeBatterySocNodePatternType ?? NodePatternType.Direct;
var jsonPattern = _configurationWrapper.HomeBatterySocJsonPattern();
@@ -104,7 +104,7 @@ public async Task ConnectMqttClient()
_setting.HomeBatterySoc = _pvValueService.GetIntegerValueByString(value, jsonPattern, xmlPattern, correctionFactor, patternType,
xmlAttributeHeaderName, xmlAttributeHeaderValue, xmlAttributeValueName);
}
- else if (topic == _configurationWrapper.HomeBatteryPowerMqttTopic() && frontendConfiguration.HomeBatteryValuesSource == SolarValueSource.Mqtt)
+ if (topic == _configurationWrapper.HomeBatteryPowerMqttTopic() && frontendConfiguration.HomeBatteryValuesSource == SolarValueSource.Mqtt)
{
var patternType = frontendConfiguration.HomeBatteryPowerNodePatternType ?? NodePatternType.Direct;
var jsonPattern = _configurationWrapper.HomeBatteryPowerJsonPattern();
@@ -116,10 +116,6 @@ public async Task ConnectMqttClient()
_setting.HomeBatteryPower = _pvValueService.GetIntegerValueByString(value, jsonPattern, xmlPattern, correctionFactor, patternType,
xmlAttributeHeaderName, xmlAttributeHeaderValue, xmlAttributeValueName);
}
- else
- {
- _logger.LogWarning("Received value does not match a topic");
- }
return Task.CompletedTask;
};
diff --git a/TeslaSolarCharger/Server/Services/TeslaMateMqttService.cs b/TeslaSolarCharger/Server/Services/TeslaMateMqttService.cs
index 0bf7a25c5..3690c6f26 100644
--- a/TeslaSolarCharger/Server/Services/TeslaMateMqttService.cs
+++ b/TeslaSolarCharger/Server/Services/TeslaMateMqttService.cs
@@ -241,7 +241,9 @@ internal void UpdateCar(TeslaMateValue value)
}
else
{
- car.CarState.ChargerPhases = null;
+ //This is needed as TeslaMate sometime sends empty values during charger being connected.
+ _logger.LogDebug($"{nameof(TopicChargerPhases)} is {value.Value}. Do not overwrite charger phases.");
+ //car.CarState.ChargerPhases = null;
}
break;
case TopicChargerVoltage:
diff --git a/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj b/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj
index 70930adf5..9185b9f5b 100644
--- a/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj
+++ b/TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj
@@ -34,8 +34,8 @@
-
-
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
@@ -52,12 +52,13 @@
-
+
+
diff --git a/TeslaSolarCharger/Server/appsettings.Development.json b/TeslaSolarCharger/Server/appsettings.Development.json
index ab20f8160..f208db1bb 100644
--- a/TeslaSolarCharger/Server/appsettings.Development.json
+++ b/TeslaSolarCharger/Server/appsettings.Development.json
@@ -55,5 +55,24 @@
"TeslaMateDbUser": "teslamate",
"TeslaMateDbPassword": "secret",
"AllowCORS": true,
- "DisplayApiRequestCounter": true
+ "DisplayApiRequestCounter": true,
+ "GridPriceProvider": {
+ "EnergyProvider": "Tibber",
+ "Octopus": {
+ "BaseUrl": "https://api.octopus.energy/v1",
+ "ProductCode": "AGILE-18-02-21",
+ "TariffCode": "E-1R-AGILE-18-02-21"
+ },
+ "Tibber": {
+ "BaseUrl": "https://api.tibber.com/v1-beta/gql",
+ "AccessToken": "5K4MVS-OjfWhK_4yrjOlFe1F6kJXPVf7eQYggo8ebAE"
+ },
+ "Awattar": {
+ "BaseUrl": "https://api.awattar.de/v1",
+ "VATMultiplier": 1.19
+ },
+ "Energinet": {
+ "BaseUrl": "https://api.energidataservice.dk/dataset/"
+ }
+ }
}
\ No newline at end of file
diff --git a/TeslaSolarCharger/Server/appsettings.json b/TeslaSolarCharger/Server/appsettings.json
index 706b7241a..58de41eb7 100644
--- a/TeslaSolarCharger/Server/appsettings.json
+++ b/TeslaSolarCharger/Server/appsettings.json
@@ -55,21 +55,24 @@
"TeslaMateDbPassword": "secret",
"TeslaMateApiBaseUrl": "http://teslamateapi:8080",
"GeoFence": "Home",
- "DisplayApiRequestCounter": false
- //"CurrentPowerToGridUrl": "http://192.168.1.50:5007/api/ChargingLog/GetAverageGridPowerOfLastXseconds",
- //"CurrentInverterPowerUrl": "http://192.168.1.50:5007/api/ChargingLog/GetAverageInverterPowerOfLastXseconds",
- //"CurrentPowerToGridJsonPattern": "pattern",
- //"CurrentPowerToGridXmlPattern": "pattern",
- //"CurrentPowerToGridXmlAttributeHeaderName": "Type",
- //"CurrentPowerToGridXmlAttributeHeaderValue": "GridPower",
- //"CurrentPowerToGridXmlAttributeValueName": "Value",
- //"CurrentInverterPowerJsonPattern": "pattern",
- //"CurrentInverterPowerXmlPattern": "pattern",
- //"CurrentInverterPowerAttributeHeaderName": "Type",
- //"CurrentInverterPowerAttributeHeaderValue": "GridPower",
- //"CurrentInverterPowerAttributeValueName": "Value",
- //"CurrentPowerToGridInvertValue": false,
- //"MinutesUntilSwitchOn": 5,
- //"MinutesUntilSwitchOff": 5,
- //"PowerBuffer": 0
+ "DisplayApiRequestCounter": false,
+ "IgnoreSslErrors": false,
+ "GridPriceProvider": {
+ "EnergyProvider": "FixedPrice",
+ "Octopus": {
+ "BaseUrl": "https://api.octopus.energy/v1",
+ "ProductCode": "AGILE-18-02-21",
+ "TariffCode": "E-1R-AGILE-18-02-21"
+ },
+ "Tibber": {
+ "BaseUrl": "https://api.tibber.com/v1-beta/gql"
+ },
+ "Awattar": {
+ "BaseUrl": "https://api.awattar.de/v1",
+ "VATMultiplier": 1.19
+ },
+ "Energinet": {
+ "BaseUrl": "https://api.energidataservice.dk/dataset/"
+ }
+ }
}
\ No newline at end of file
diff --git a/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs b/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs
index 084dd8afa..4e56e570f 100644
--- a/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs
+++ b/TeslaSolarCharger/Shared/Contracts/IConfigurationWrapper.cs
@@ -82,4 +82,5 @@ public interface IConfigurationWrapper
FrontendConfiguration? FrontendConfiguration();
bool AllowCors();
bool ShouldDisplayApiRequestCounter();
+ bool ShouldIgnoreSslErrors();
}
diff --git a/TeslaSolarCharger/Shared/Dtos/ChargingCost/CostConfigurations/FixedPrice.cs b/TeslaSolarCharger/Shared/Dtos/ChargingCost/CostConfigurations/FixedPrice.cs
new file mode 100644
index 000000000..23d026c5e
--- /dev/null
+++ b/TeslaSolarCharger/Shared/Dtos/ChargingCost/CostConfigurations/FixedPrice.cs
@@ -0,0 +1,11 @@
+namespace TeslaSolarCharger.Shared.Dtos.ChargingCost.CostConfigurations;
+
+public class FixedPrice
+{
+ public int FromHour { get; set; }
+ public int FromMinute { get; set; }
+ public int ToHour { get; set; }
+ public int ToMinute { get; set; }
+ public decimal Value { get; set; }
+ public List? ValidOnDays { get; set; }
+}
diff --git a/TeslaSolarCharger/Shared/Dtos/ChargingCost/DtoChargePrice.cs b/TeslaSolarCharger/Shared/Dtos/ChargingCost/DtoChargePrice.cs
index 1a7b950c6..a02d6cbf8 100644
--- a/TeslaSolarCharger/Shared/Dtos/ChargingCost/DtoChargePrice.cs
+++ b/TeslaSolarCharger/Shared/Dtos/ChargingCost/DtoChargePrice.cs
@@ -1,4 +1,6 @@
using System.ComponentModel.DataAnnotations;
+using TeslaSolarCharger.Shared.Dtos.ChargingCost.CostConfigurations;
+using TeslaSolarCharger.Shared.Enums;
namespace TeslaSolarCharger.Shared.Dtos.ChargingCost;
@@ -6,6 +8,9 @@ public class DtoChargePrice
{
public int? Id { get; set; }
public DateTime ValidSince { get; set; }
+ public EnergyProvider EnergyProvider { get; set; } = EnergyProvider.FixedPrice;
+ public string? EnergyProviderConfiguration { get; set; }
+ public List? FixedPrices { get; set; }
[Required]
public decimal? SolarPrice { get; set; }
[Required]
diff --git a/TeslaSolarCharger/Shared/Enums/EnergyProvider.cs b/TeslaSolarCharger/Shared/Enums/EnergyProvider.cs
new file mode 100644
index 000000000..0a24a27ff
--- /dev/null
+++ b/TeslaSolarCharger/Shared/Enums/EnergyProvider.cs
@@ -0,0 +1,12 @@
+namespace TeslaSolarCharger.Shared.Enums;
+
+public enum EnergyProvider
+{
+ Octopus,
+ Tibber,
+ FixedPrice,
+ Awattar,
+ Energinet,
+ HomeAssistant,
+ OldTeslaSolarChargerConfig,
+}
diff --git a/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs b/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs
index 3f5cd334d..a27544105 100644
--- a/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs
+++ b/TeslaSolarCharger/Shared/Wrappers/ConfigurationWrapper.cs
@@ -583,6 +583,14 @@ private void SetHomeBatteryDefaultConfiguration(DtoBaseConfiguration dtoBaseConf
dtoBaseConfiguration.HomeBatterySocXmlPattern);
}
+ public bool ShouldIgnoreSslErrors()
+ {
+ _logger.LogTrace("{method}()", nameof(ShouldIgnoreSslErrors));
+ var environmentVariableName = "IgnoreSslErrors";
+ var value = _configuration.GetValue(environmentVariableName);
+ return value;
+ }
+
public async Task TryAutoFillUrls()
{
var dtoBaseConfiguration = await GetBaseConfigurationAsync().ConfigureAwait(false);