Skip to content

Commit

Permalink
Merge pull request #1281 from pkuehnel/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
pkuehnel authored Jun 2, 2024
2 parents 78334e6 + 57ccf30 commit 341e231
Show file tree
Hide file tree
Showing 17 changed files with 131 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ public interface IModbusValueExecutionService
Task<byte[]> GetResult(DtoModbusConfiguration modbusConfig, DtoModbusValueResultConfiguration resultConfiguration);
Task<decimal> GetValue(byte[] byteArray, DtoModbusValueResultConfiguration resultConfig);
Task<List<DtoValueConfigurationOverview>> GetModbusValueOverviews();
string GetBinaryString(byte[] byteArray);
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public async Task<decimal> GetValue(byte[] byteArray, DtoModbusValueResultConfig
throw new ArgumentException("BitStartIndex must be set for ValueType Bool", nameof(ModbusResultConfiguration.BitStartIndex));
var binaryString = GetBinaryString(byteArray);
var bitChar = binaryString[resultConfig.BitStartIndex.Value];
logger.LogDebug("Bit value: {bitChar}", bitChar);
rawValue = bitChar == '1' ? 1 : 0;
return rawValue;
default:
Expand All @@ -73,6 +74,7 @@ public async Task<decimal> GetValue(byte[] byteArray, DtoModbusValueResultConfig

private async Task<decimal> InvertValueOnExistingInversionRegister(decimal rawValue, int? resultConfigInvertedByModbusResultConfigurationId)
{
logger.LogTrace("{method}({rawValue}, {resultConfigInvertedByModbusResultConfigurationId})", nameof(InvertValueOnExistingInversionRegister), rawValue, resultConfigInvertedByModbusResultConfigurationId);;
if (resultConfigInvertedByModbusResultConfigurationId == default)
{
return rawValue;
Expand All @@ -86,11 +88,13 @@ await modbusValueConfigurationService.GetModbusResultConfigurationsByPredicate(r
c.ModbusResultConfigurations.Any(r => r.Id == resultConfigInvertedByModbusResultConfigurationId.Value));
var valueConfiguration = valueConfigurations.Single();
var byteArray = await GetResult(valueConfiguration, resultConfiguration);
logger.LogDebug("Inversion bits: {byteArray}", GetBinaryString(byteArray));
var inversionValue = await GetValue(byteArray, resultConfiguration);
logger.LogDebug("Inversion value: {inversionValue}", inversionValue);
return inversionValue == 0 ? rawValue : -rawValue;
}

private string GetBinaryString(byte[] byteArray)
public string GetBinaryString(byte[] byteArray)
{
var stringbuilder = new StringBuilder();
foreach (var byteValue in byteArray)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,14 +98,21 @@ public async Task<int> SaveRestValueConfiguration(DtoFullRestValueConfiguration
foreach (var dtoHeader in dtoData.Headers)
{
var dbHeader = headerMapper.Map<RestValueConfigurationHeader>(dtoHeader);
dbHeader.RestValueConfigurationId = dbData.Id;
if (dbHeader.Id == default)
if (dbData.Id == default)
{
context.RestValueConfigurationHeaders.Add(dbHeader);
dbData.Headers.Add(dbHeader);
}
else
{
context.RestValueConfigurationHeaders.Update(dbHeader);
dbHeader.RestValueConfigurationId = dbData.Id;
if (dbHeader.Id == default)
{
context.RestValueConfigurationHeaders.Add(dbHeader);
}
else
{
context.RestValueConfigurationHeaders.Update(dbHeader);
}
}
}
await context.SaveChangesAsync().ConfigureAwait(false);
Expand Down
4 changes: 2 additions & 2 deletions TeslaSolarCharger.Tests/Services/Server/SpotPriceService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public void Generates_Awattar_Url_With_DateTimeOffset()
var endFutureMilliseconds = currentDate.AddHours(48).ToUnixTimeMilliseconds();

var spotPriceService = Mock.Create<TeslaSolarCharger.Server.Services.SpotPriceService>();
var url = spotPriceService.GenerateAwattarUrl(dateTimeOffset);
var url = spotPriceService.GenerateAwattarUrl(dateTimeOffset, null);
Assert.Equal($"https://api.awattar.de/v1/marketdata?start=1674511200000&end={endFutureMilliseconds}", url);
}

Expand All @@ -57,7 +57,7 @@ public void Generates_Awattar_Url_WithOut_DateTimeOffset()
Mock.Mock<IConfigurationWrapper>()
.Setup(c => c.GetAwattarBaseUrl())
.Returns("https://api.awattar.de/v1/marketdata");
var url = spotPriceService.GenerateAwattarUrl(null);
var url = spotPriceService.GenerateAwattarUrl(null, null);
Assert.Equal("https://api.awattar.de/v1/marketdata", url);
}
}
18 changes: 15 additions & 3 deletions TeslaSolarCharger/Client/Pages/ChargeCostDetail.razor
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
@using TeslaSolarCharger.Shared.Dtos.ChargingCost.CostConfigurations
@using TeslaSolarCharger.Shared.Enums
@using Newtonsoft.Json
@using TeslaSolarCharger.Shared.Dtos
@inject HttpClient HttpClient
@inject NavigationManager NavigationManager
@inject IDateTimeProvider DateTimeProvider
Expand Down Expand Up @@ -127,7 +128,13 @@ else
}

<br/>
<button type="submit" class="btn btn-primary">
@if(ChargePricesUpdateText != null)
{
<div class="alert alert-warning" role="alert">
@ChargePricesUpdateText
</div>
}
<button type="submit" class="btn btn-primary" disabled="@(ChargePricesUpdateText != null)">
@if (SubmitIsLoading)
{
<span>
Expand All @@ -140,6 +147,9 @@ else
<span>Save</span>
}
</button>
<div>
<small class="form-text text-muted">Note: updating charge prices can take a significant ammount of time as the price of all previous charges is updated</small>
</div>
</EditForm>
}

Expand All @@ -154,8 +164,11 @@ else

private List<FixedPrice>? FixedPrices { get; set; }

public string? ChargePricesUpdateText { get; set; }

protected override async Task OnInitializedAsync()
{
ChargePricesUpdateText = (await HttpClient.GetFromJsonAsync<DtoValue<string?>>($"api/ChargingCost/GetChargePricesUpdateText").ConfigureAwait(false))?.Value;
if (ChargeCostId != null)
{
ChargePrice = await HttpClient.GetFromJsonAsync<DtoChargePrice>($"api/ChargingCost/GetChargePriceById?id={ChargeCostId}").ConfigureAwait(false);
Expand Down Expand Up @@ -197,8 +210,7 @@ else
{
ChargePrice.EnergyProviderConfiguration = null;
}
await HttpClient.PostAsJsonAsync("api/ChargingCost/UpdateChargePrice", ChargePrice).ConfigureAwait(false);
SubmitIsLoading = false;
HttpClient.PostAsJsonAsync("api/ChargingCost/UpdateChargePrice", ChargePrice).ConfigureAwait(false);
NavigateToList();
}

Expand Down
2 changes: 1 addition & 1 deletion TeslaSolarCharger/Client/TeslaSolarCharger.Client.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<ItemGroup>
<PackageReference Include="CodeBeam.MudBlazor.Extensions" Version="6.9.2" />
<PackageReference Include="Microsoft.AspNetCore.Components.Forms" Version="8.0.6" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.6" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.6" PrivateAssets="all" />
<PackageReference Include="MudBlazor" Version="6.19.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
Expand Down
11 changes: 10 additions & 1 deletion TeslaSolarCharger/Server/Controllers/ChargingCostController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@
using TeslaSolarCharger.Model.Entities.TeslaSolarCharger;
using TeslaSolarCharger.Server.Contracts;
using TeslaSolarCharger.Server.Services.ApiServices.Contracts;
using TeslaSolarCharger.Shared.Dtos;
using TeslaSolarCharger.Shared.Dtos.ChargingCost;
using TeslaSolarCharger.Shared.Dtos.Contracts;
using TeslaSolarCharger.SharedBackend.Abstracts;

namespace TeslaSolarCharger.Server.Controllers
{
public class ChargingCostController(
IChargingCostService chargingCostService,
ITscOnlyChargingCostService tscOnlyChargingCostService)
ITscOnlyChargingCostService tscOnlyChargingCostService,
ISettings settings)
: ApiBaseController
{
[HttpGet]
Expand Down Expand Up @@ -59,5 +62,11 @@ public Task UpdateChargePrice([FromBody] DtoChargePrice chargePrice)
{
return chargingCostService.UpdateChargePrice(chargePrice);
}

[HttpGet]
public DtoValue<string?> GetChargePricesUpdateText()
{
return new DtoValue<string?>(settings.ChargePricesUpdateText);
}
}
}
9 changes: 7 additions & 2 deletions TeslaSolarCharger/Server/Program.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Serilog;
using Serilog.Context;
using System.Diagnostics;
using TeslaSolarCharger.Model.Contracts;
using TeslaSolarCharger.Server;
using TeslaSolarCharger.Server.Contracts;
using TeslaSolarCharger.Server.Scheduling;
using TeslaSolarCharger.Server.Services.ApiServices.Contracts;
using TeslaSolarCharger.Server.Services.Contracts;
using TeslaSolarCharger.Server.Services.GridPrice.Contracts;
using TeslaSolarCharger.Services;
using TeslaSolarCharger.Services.Services.Contracts;
using TeslaSolarCharger.Shared;
Expand Down Expand Up @@ -62,7 +65,6 @@
var baseConfigurationConverter = app.Services.GetRequiredService<IBaseConfigurationConverter>();
await baseConfigurationConverter.ConvertAllEnvironmentVariables().ConfigureAwait(false);
await baseConfigurationConverter.ConvertBaseConfigToV1_0().ConfigureAwait(false);

DoStartupStuff(app, logger, configurationWrapper);

// Configure the HTTP request pipeline.
Expand Down Expand Up @@ -174,7 +176,10 @@ async Task DoStartupStuff(WebApplication webApplication, ILogger<Program> logger

var pvValueService = webApplication.Services.GetRequiredService<IPvValueService>();
await pvValueService.ConvertToNewConfiguration().ConfigureAwait(false);


var spotPriceService = webApplication.Services.GetRequiredService<ISpotPriceService>();
await spotPriceService.GetSpotPricesSinceFirstChargeDetail().ConfigureAwait(false);

var jobManager = webApplication.Services.GetRequiredService<JobManager>();
//if (!Debugger.IsAttached)
{
Expand Down
9 changes: 8 additions & 1 deletion TeslaSolarCharger/Server/Services/ChargingCostService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using TeslaSolarCharger.Server.Contracts;
using TeslaSolarCharger.Server.Services.ApiServices.Contracts;
using TeslaSolarCharger.Shared.Dtos.ChargingCost;
using TeslaSolarCharger.Shared.Dtos.Contracts;
using TeslaSolarCharger.Shared.Enums;
using TeslaSolarCharger.Shared.Resources.Contracts;
using TeslaSolarCharger.SharedBackend.MappingExtensions;
Expand All @@ -18,7 +19,8 @@ public class ChargingCostService(
IMapperConfigurationFactory mapperConfigurationFactory,
IServiceProvider serviceProvider,
IConstants constants,
ITscOnlyChargingCostService tscOnlyChargingCostService)
ITscOnlyChargingCostService tscOnlyChargingCostService,
ISettings settings)
: IChargingCostService
{
public async Task ConvertToNewChargingProcessStructure()
Expand Down Expand Up @@ -116,6 +118,11 @@ public async Task UpdateChargePrice(DtoChargePrice dtoChargePrice)
{
logger.LogTrace("{method}({@dtoChargePrice})",
nameof(UpdateChargePrice), dtoChargePrice);
if (!string.IsNullOrEmpty(settings.ChargePricesUpdateText))
{
logger.LogWarning("Can not update charge price as currently updating due to previous change");
return;
}
ChargePrice chargePrice;
if (dtoChargePrice.Id == null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ public interface ISpotPriceService
{
Task UpdateSpotPrices();
Task<DateTimeOffset> LatestKnownSpotPriceTime();
Task GetSpotPricesSinceFirstChargeDetail();
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,11 @@ public async Task<IEnumerable<Price>> GetPriceData(DateTimeOffset from, DateTime
},
};
}

var fromDateTime = from.UtcDateTime;
var toDateTime = to.UtcDateTime;
var spotPrices = await teslaSolarChargerContext.SpotPrices
.Where(p => p.EndDate >= from && p.StartDate <= to)
.Where(p => p.EndDate >= fromDateTime && p.StartDate <= toDateTime)
.OrderBy(p => p.StartDate)
.ToListAsync();
var result = new List<Price>();
Expand Down
16 changes: 16 additions & 0 deletions TeslaSolarCharger/Server/Services/PvValueService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using TeslaSolarCharger.Services.Services.Mqtt.Contracts;
using TeslaSolarCharger.Services.Services.Rest.Contracts;
using TeslaSolarCharger.Shared.Contracts;
using TeslaSolarCharger.Shared.Dtos.BaseConfiguration;
using TeslaSolarCharger.Shared.Dtos.Contracts;
using TeslaSolarCharger.Shared.Dtos.ModbusConfiguration;
using TeslaSolarCharger.Shared.Dtos.MqttConfiguration;
Expand Down Expand Up @@ -162,6 +163,15 @@ public async Task ConvertToNewConfiguration()

if (!await _context.MqttConfigurations.AnyAsync())
{
var frontendConfiguration = _configurationWrapper.FrontendConfiguration();
if (frontendConfiguration == default ||
(frontendConfiguration.GridValueSource != SolarValueSource.Mqtt
&& frontendConfiguration.HomeBatteryValuesSource != SolarValueSource.Mqtt
&& frontendConfiguration.InverterValueSource != SolarValueSource.Mqtt))
{
_logger.LogDebug("Do not convert MQTT as no value source is on MQTT.");
return;
}
var solarMqttServer = _configurationWrapper.SolarMqttServer();
var solarMqttUser = _configurationWrapper.SolarMqttUsername();
var solarMqttPassword = _configurationWrapper.SolarMqttPassword();
Expand Down Expand Up @@ -752,12 +762,18 @@ public async Task UpdatePvValues()
var modbusConfigurations = await _modbusValueConfigurationService.GetModbusConfigurationByPredicate(c => c.ModbusResultConfigurations.Any(r => valueUsages.Contains(r.UsedFor))).ConfigureAwait(false);
foreach (var modbusConfiguration in modbusConfigurations)
{
_logger.LogDebug("Get Modbus results for modbus Configuration {host}:{port}", modbusConfiguration.Host,
modbusConfiguration.Port);
var modbusResultConfigurations =
await _modbusValueConfigurationService.GetModbusResultConfigurationsByPredicate(r =>
r.ModbusConfigurationId == modbusConfiguration.Id);
foreach (var resultConfiguration in modbusResultConfigurations)
{
_logger.LogDebug("Get Modbus result for modbus Configuration {host}:{port}: Register: {register}", modbusConfiguration.Host,
modbusConfiguration.Port, resultConfiguration.Address);
var byteArry = await _modbusValueExecutionService.GetResult(modbusConfiguration, resultConfiguration);
_logger.LogDebug("Got Modbus result for modbus Configuration {host}:{port}: Register: {register}, Result: {bitResult}", modbusConfiguration.Host,
modbusConfiguration.Port, resultConfiguration.Address, _modbusValueExecutionService.GetBinaryString(byteArry));
var value = await _modbusValueExecutionService.GetValue(byteArry, resultConfiguration);
var valueUsage = resultConfiguration.UsedFor;
if (!resultSums.ContainsKey(valueUsage))
Expand Down
48 changes: 42 additions & 6 deletions TeslaSolarCharger/Server/Services/SpotPriceService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,40 @@ public async Task UpdateSpotPrices()
getPricesFrom = latestKnownSpotPriceTime;
}

var awattarPrices = await GetAwattarPrices(getPricesFrom).ConfigureAwait(false);
var awattarPrices = await GetAwattarPrices(getPricesFrom, null).ConfigureAwait(false);
await AddAwattarPricesToDatabase(awattarPrices);
}

public async Task GetSpotPricesSinceFirstChargeDetail()
{
_logger.LogTrace("{method}()", nameof(GetSpotPricesSinceFirstChargeDetail));

var firstChargeDetail = await _teslaSolarChargerContext.ChargingDetails
.OrderBy(cd => cd.TimeStamp)
.Select(cd => cd.TimeStamp)
.FirstOrDefaultAsync().ConfigureAwait(false);
if (firstChargeDetail == default)
{
_logger.LogInformation("No chargingdetails found so gettings spotprices is not needed");
return;
}
var firstSpotPrice = await _teslaSolarChargerContext.SpotPrices
.OrderBy(sp => sp.StartDate)
.Select(sp => sp.StartDate)
.FirstOrDefaultAsync().ConfigureAwait(false);
if (firstSpotPrice != default && firstSpotPrice < firstChargeDetail)
{
_logger.LogInformation("Spotprices already exist for all chargingdetails");
return;
}
var getPricesFrom = new DateTimeOffset(firstChargeDetail.AddDays(-1), TimeSpan.Zero);
var getPricesTo = new DateTimeOffset(firstSpotPrice == default ? _dateTimeProvider.UtcNow() : firstSpotPrice, TimeSpan.Zero);
var awattarPrices = await GetAwattarPrices(getPricesFrom, getPricesTo).ConfigureAwait(false);
await AddAwattarPricesToDatabase(awattarPrices);
}

private async Task AddAwattarPricesToDatabase(DtoAwattarPrices? awattarPrices)
{
if (awattarPrices == null)
{
_logger.LogWarning("Clould not get awattar prices");
Expand Down Expand Up @@ -71,21 +104,24 @@ internal SpotPrice GenerateSpotPriceFromAwattarPrice(Datum value)
return spotPrice;
}

internal string GenerateAwattarUrl(DateTimeOffset? fromDate)
internal string GenerateAwattarUrl(DateTimeOffset? fromDate, DateTimeOffset? toDate)
{
var url = _configurationWrapper.GetAwattarBaseUrl();
//var url = "https://api.awattar.de/v1/marketdata";
if (fromDate != null)
{
var toDate = _dateTimeProvider.DateTimeOffSetNow().AddHours(48);
url += $"?start={fromDate.Value.ToUnixTimeMilliseconds()}&end={toDate.ToUnixTimeMilliseconds()}";
if (toDate == null)
{
toDate = _dateTimeProvider.DateTimeOffSetNow().AddHours(48);
}
url += $"?start={fromDate.Value.ToUnixTimeMilliseconds()}&end={toDate.Value.ToUnixTimeMilliseconds()}";
}
return url;
}

private async Task<DtoAwattarPrices?> GetAwattarPrices(DateTimeOffset? fromDate)
private async Task<DtoAwattarPrices?> GetAwattarPrices(DateTimeOffset? fromDate, DateTimeOffset? toDate)
{
var url = GenerateAwattarUrl(fromDate);
var url = GenerateAwattarUrl(fromDate, toDate);
using var httpClient = new HttpClient();
var awattarPrices = await httpClient.GetFromJsonAsync<DtoAwattarPrices>(url)
.ConfigureAwait(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,17 +63,22 @@ public async Task UpdateChargePricesOfAllChargingProcesses()
var openChargingProcesses = await context.ChargingProcesses
.Where(cp => cp.EndDate != null)
.ToListAsync().ConfigureAwait(false);
var failedCounter = 0;
foreach (var chargingProcess in openChargingProcesses)
{
settings.ChargePricesUpdateText =
$"Updating price of charging process {openChargingProcesses.IndexOf(chargingProcess)}/{openChargingProcesses.Count} ({failedCounter} failed)";
try
{
await FinalizeChargingProcess(chargingProcess);
}
catch (Exception ex)
{
logger.LogError(ex, "Error while updating charge prices of charging process with ID {chargingProcessId}.", chargingProcess.Id);
failedCounter++;
}
}
settings.ChargePricesUpdateText = null;
}

public async Task<Dictionary<int, DtoChargeSummary>> GetChargeSummaries()
Expand Down
Loading

0 comments on commit 341e231

Please sign in to comment.