Skip to content

Commit

Permalink
feat(ChargingLogs): Log chargingDetails and charging processes
Browse files Browse the repository at this point in the history
  • Loading branch information
pkuehnel committed Mar 10, 2024
1 parent 317c33a commit 99e703c
Show file tree
Hide file tree
Showing 15 changed files with 232 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,7 @@ public interface ITeslaSolarChargerContext
DbSet<RestValueConfiguration> RestValueConfigurations { get; set; }
DbSet<RestValueConfigurationHeader> RestValueConfigurationHeaders { get; set; }
DbSet<RestValueResultConfiguration> RestValueResultConfigurations { get; set; }
DbSet<ChargingProcess> ChargingProcesses { get; set; }
DbSet<ChargingDetail> ChargingDetails { get; set; }
void RejectChanges();
}
2 changes: 2 additions & 0 deletions TeslaSolarCharger.Model/Entities/TeslaSolarCharger/Car.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,6 @@ public class Car
public double? Latitude { get; set; }
public double? Longitude { get; set; }
public CarStateEnum? State { get; set; }

public List<ChargingProcess> ChargingProcesses { get; set; } = new List<ChargingProcess>();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace TeslaSolarCharger.Model.Entities.TeslaSolarCharger;

public class ChargingDetail
{
public int Id { get; set; }
public DateTime TimeStamp { get; set; }
public int SolarPower { get; set; }
public int GridPower { get; set; }

public int ChargingProcessId { get; set; }

public ChargingProcess ChargingProcess { get; set; } = null!;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace TeslaSolarCharger.Model.Entities.TeslaSolarCharger;

public class ChargingProcess
{
public int Id { get; set; }
public DateTime StartDate { get; set; }
public DateTime? EndDate { get; set; }
public decimal? UsedGridEnergy { get; set; }
public decimal? UsedSolarEnergy { get; set; }
public decimal? Cost { get; set; }

public int CarId { get; set; }

public Car Car { get; set; } = null!;

public List<ChargingDetail> ChargingDetails { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ public class TeslaSolarChargerContext : DbContext, ITeslaSolarChargerContext
public DbSet<RestValueConfiguration> RestValueConfigurations { get; set; } = null!;
public DbSet<RestValueConfigurationHeader> RestValueConfigurationHeaders { get; set; } = null!;
public DbSet<RestValueResultConfiguration> RestValueResultConfigurations { get; set; } = null!;
public DbSet<ChargingProcess> ChargingProcesses { get; set; } = null!;
public DbSet<ChargingDetail> ChargingDetails { get; set; } = null!;
// ReSharper disable once UnassignedGetOnlyAutoProperty
public string DbPath { get; }

Expand Down
2 changes: 1 addition & 1 deletion TeslaSolarCharger/Server/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@
}

var jobManager = app.Services.GetRequiredService<JobManager>();
if (!Debugger.IsAttached)
//if (!Debugger.IsAttached)
{
await jobManager.StartJobs().ConfigureAwait(false);
}
Expand Down
8 changes: 4 additions & 4 deletions TeslaSolarCharger/Server/Scheduling/JobManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public async Task StartJobs()
var chargingValueJob = JobBuilder.Create<ChargingValueJob>().Build();
var carStateCachingJob = JobBuilder.Create<CarStateCachingJob>().Build();
var pvValueJob = JobBuilder.Create<PvValueJob>().Build();
var powerDistributionAddJob = JobBuilder.Create<PowerDistributionAddJob>().Build();
var chargingDetailsAddJob = JobBuilder.Create<ChargingDetailsAddJob>().Build();
var handledChargeFinalizingJob = JobBuilder.Create<HandledChargeFinalizingJob>().Build();
var mqttReconnectionJob = JobBuilder.Create<MqttReconnectionJob>().Build();
var newVersionCheckJob = JobBuilder.Create<NewVersionCheckJob>().Build();
Expand Down Expand Up @@ -81,8 +81,8 @@ public async Task StartJobs()
var carStateCachingTrigger = TriggerBuilder.Create()
.WithSchedule(SimpleScheduleBuilder.RepeatMinutelyForever(3)).Build();

var powerDistributionAddTrigger = TriggerBuilder.Create()
.WithSchedule(SimpleScheduleBuilder.RepeatSecondlyForever(16)).Build();
var chargingDetailsAddTrigger = TriggerBuilder.Create()
.WithSchedule(SimpleScheduleBuilder.RepeatSecondlyForever(59)).Build();

var handledChargeFinalizingTrigger = TriggerBuilder.Create()
.WithSchedule(SimpleScheduleBuilder.RepeatMinutelyForever(9)).Build();
Expand All @@ -107,7 +107,7 @@ public async Task StartJobs()
{chargingValueJob, new HashSet<ITrigger> { chargingValueTrigger }},
{carStateCachingJob, new HashSet<ITrigger> {carStateCachingTrigger}},
{pvValueJob, new HashSet<ITrigger> {pvValueTrigger}},
{powerDistributionAddJob, new HashSet<ITrigger> {powerDistributionAddTrigger}},
{chargingDetailsAddJob, new HashSet<ITrigger> {chargingDetailsAddTrigger}},
{handledChargeFinalizingJob, new HashSet<ITrigger> {handledChargeFinalizingTrigger}},
{mqttReconnectionJob, new HashSet<ITrigger> {mqttReconnectionTrigger}},
{newVersionCheckJob, new HashSet<ITrigger> {newVersionCheckTrigger}},
Expand Down
16 changes: 16 additions & 0 deletions TeslaSolarCharger/Server/Scheduling/Jobs/ChargingDetailsAddJob.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Quartz;
using TeslaSolarCharger.Server.Services.ApiServices.Contracts;

namespace TeslaSolarCharger.Server.Scheduling.Jobs;

[DisallowConcurrentExecution]
public class ChargingDetailsAddJob(ILogger<ChargingDetailsAddJob> logger,
ITscOnlyChargingCostService service)
: IJob
{
public async Task Execute(IJobExecutionContext context)
{
logger.LogTrace("{method}({context})", nameof(Execute), context);
await service.AddChargingDetailsForAllCars().ConfigureAwait(false);
}
}

This file was deleted.

3 changes: 2 additions & 1 deletion TeslaSolarCharger/Server/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public static IServiceCollection AddMyDependencies(this IServiceCollection servi
.AddTransient<ChargingValueJob>()
.AddTransient<CarStateCachingJob>()
.AddTransient<PvValueJob>()
.AddTransient<PowerDistributionAddJob>()
.AddTransient<ChargingDetailsAddJob>()
.AddTransient<HandledChargeFinalizingJob>()
.AddTransient<MqttReconnectionJob>()
.AddTransient<NewVersionCheckJob>()
Expand Down Expand Up @@ -97,6 +97,7 @@ public static IServiceCollection AddMyDependencies(this IServiceCollection servi
.AddTransient<ITeslamateApiService, TeslamateApiService>()
.AddTransient<ITscConfigurationService, TscConfigurationService>()
.AddTransient<IBackendApiService, BackendApiService>()
.AddTransient<ITscOnlyChargingCostService, TscOnlyChargingCostService>()
.AddSharedBackendDependencies();
if (useFleetApi)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace TeslaSolarCharger.Server.Services.ApiServices.Contracts;

public interface ITscOnlyChargingCostService
{
Task AddChargingDetailsForAllCars();
}
2 changes: 0 additions & 2 deletions TeslaSolarCharger/Server/Services/ChargingCostService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@
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.Shared.Contracts;
using TeslaSolarCharger.Shared.Dtos.ChargingCost;
using TeslaSolarCharger.Shared.Dtos.ChargingCost.CostConfigurations;
Expand Down
152 changes: 152 additions & 0 deletions TeslaSolarCharger/Server/Services/TscOnlyChargingCostService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
using Microsoft.EntityFrameworkCore;
using TeslaSolarCharger.Model.Contracts;
using TeslaSolarCharger.Model.Entities.TeslaSolarCharger;
using TeslaSolarCharger.Server.Services.ApiServices.Contracts;
using TeslaSolarCharger.Shared.Contracts;
using TeslaSolarCharger.Shared.Dtos.Contracts;

namespace TeslaSolarCharger.Server.Services;

public class TscOnlyChargingCostService(ILogger<TscOnlyChargingCostService> logger,
ITeslaSolarChargerContext context,
ISettings settings,
IDateTimeProvider dateTimeProvider,
IConfigurationWrapper configurationWrapper) : ITscOnlyChargingCostService
{
public async Task FinalizeFinishedChargingProcesses()
{
logger.LogTrace("{method}()", nameof(FinalizeFinishedChargingProcesses));
var openChargingProcesses = await context.ChargingProcesses
.Where(cp => cp.EndDate == null)
.ToListAsync().ConfigureAwait(false);
var timeSpanToHandleChargingProcessAsCompleted = TimeSpan.FromMinutes(10);
foreach (var chargingProcess in openChargingProcesses)
{
var latestChargingDetail = await context.ChargingDetails
.Where(cd => cd.ChargingProcessId == chargingProcess.Id)
.OrderByDescending(cd => cd.Id)
.FirstOrDefaultAsync().ConfigureAwait(false);
if (latestChargingDetail == default)
{
logger.LogWarning("No charging detail found for charging process with ID {chargingProcessId}.", chargingProcess.Id);
continue;
}

if (latestChargingDetail.TimeStamp.Add(timeSpanToHandleChargingProcessAsCompleted) < dateTimeProvider.UtcNow())
{
try
{
await FinalizeChargingProcess(chargingProcess);
}
catch (Exception ex)
{
logger.LogError(ex, "Error while finalizing charging process with ID {chargingProcessId}.", chargingProcess.Id);
}
}
}
}

private async Task FinalizeChargingProcess(ChargingProcess chargingProcess)
{
logger.LogTrace("{method}({chargingProcessId})", nameof(FinalizeChargingProcess), chargingProcess.Id);
var chargingDetails = await context.ChargingDetails
.Where(cd => cd.ChargingProcessId == chargingProcess.Id)
.OrderBy(cd => cd.Id)
.ToListAsync().ConfigureAwait(false);
decimal usedSolarEnergy = 0;
decimal usedGridEnergy = 0;
decimal cost = 0;
for (var index = 1; index < chargingDetails.Count; index++)
{
var chargingDetail = chargingDetails[index];
var timeSpanSinceLastDetail = chargingDetail.TimeStamp - chargingDetails[index - 1].TimeStamp;
usedSolarEnergy += (decimal)(chargingDetail.SolarPower * timeSpanSinceLastDetail.TotalHours);
usedGridEnergy += (decimal)(chargingDetail.GridPower * timeSpanSinceLastDetail.TotalHours);
}
chargingProcess.EndDate = chargingDetails.Last().TimeStamp;
chargingProcess.UsedSolarEnergy = usedSolarEnergy;
chargingProcess.UsedGridEnergy = usedGridEnergy;
chargingProcess.Cost = cost;
await context.SaveChangesAsync().ConfigureAwait(false);
}

public async Task AddChargingDetailsForAllCars()
{
logger.LogTrace("{method}()", nameof(AddChargingDetailsForAllCars));
var availableSolarPower = GetSolarPower();
foreach (var car in settings.CarsToManage.OrderBy(c => c.ChargingPriority))
{
var chargingPowerAtHome = car.ChargingPowerAtHome ?? 0;
if (chargingPowerAtHome < 1)
{
logger.LogTrace("Car with ID {carId} 0 Watt chargingPower at home", car.Id);
continue;
}
var chargingDetail = await GetAttachedChargingDetail(car.Id);
if (chargingPowerAtHome < availableSolarPower)
{
chargingDetail.SolarPower = chargingPowerAtHome;
chargingDetail.GridPower = 0;
}
else
{
chargingDetail.SolarPower = availableSolarPower;
chargingDetail.GridPower = chargingPowerAtHome - availableSolarPower;
}
availableSolarPower -= chargingDetail.SolarPower;
if (availableSolarPower < 0)
{
availableSolarPower = 0;
}
}
await context.SaveChangesAsync().ConfigureAwait(false);
}

private int GetSolarPower()
{
var solarPower = settings.Overage;
if (solarPower == default && settings.InverterPower != default)
{
//no grid meter available, so we have to calculate the power by using the inverter power and the power buffer
var powerBuffer = configurationWrapper.PowerBuffer(true);
solarPower = settings.InverterPower
//if powerBuffer is negative, it will be subtracted as it should be expected home power usage when no grid meter is available
- (powerBuffer > 0 ? powerBuffer : 0);
}

if (solarPower == default)
{
logger.LogInformation("No solar power available, using 0 as default.");
return 0;
}
return (int)solarPower;
}

private async Task<ChargingDetail> GetAttachedChargingDetail(int carId)
{
var latestOpenChargingProcessId = await context.ChargingProcesses
.Where(cp => cp.CarId == carId && cp.EndDate == null)
.OrderByDescending(cp => cp.StartDate)
.Select(cp => cp.Id)
.FirstOrDefaultAsync().ConfigureAwait(false);
var chargingDetail = new ChargingDetail
{
TimeStamp = dateTimeProvider.UtcNow(),
};
if (latestOpenChargingProcessId == default)
{
var chargingProcess = new ChargingProcess
{
StartDate = chargingDetail.TimeStamp,
CarId = carId,
};
context.ChargingProcesses.Add(chargingProcess);
chargingProcess.ChargingDetails.Add(chargingDetail);
}
else
{
chargingDetail.ChargingProcessId = latestOpenChargingProcessId;
}
return chargingDetail;
}
}
1 change: 1 addition & 0 deletions TeslaSolarCharger/Server/TeslaSolarCharger.Server.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
<ItemGroup>
<Folder Include="Enums\" />
<Folder Include="MappingExtensions\" />
<Folder Include="Services\GridPrice\Contracts\" />
</ItemGroup>


Expand Down
15 changes: 14 additions & 1 deletion TeslaSolarCharger/Shared/Dtos/Settings/DtoCar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ namespace TeslaSolarCharger.Shared.Dtos.Settings;

public class DtoCar
{
private int? _chargingPower;
public int Id { get; set; }
public string Vin { get; set; }

Check warning on line 9 in TeslaSolarCharger/Shared/Dtos/Settings/DtoCar.cs

View workflow job for this annotation

GitHub Actions / Building TeslaSolarCharger image

Non-nullable property 'Vin' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
public int? TeslaMateCarId { get; set; }
Expand Down Expand Up @@ -67,7 +68,19 @@ public int? ChargingPowerAtHome
}
}

private int? ChargingPower { get; set; }
private int? ChargingPower
{
get
{
if (_chargingPower == default)
{
return ChargerActualCurrent * ChargerVoltage * ActualPhases;
}
return _chargingPower;
}
set => _chargingPower = value;
}

public CarStateEnum? State { get; set; }
public bool? Healthy { get; set; }
public bool ReducedChargeSpeedWarning { get; set; }
Expand Down

0 comments on commit 99e703c

Please sign in to comment.