Created
January 23, 2023 16:53
-
-
Save ramonsmits/a3573660aeff18861aba6fa86e9cb822 to your computer and use it in GitHub Desktop.
ChargeOrchestrator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System.Globalization; | |
| using Microsoft.Extensions.Logging; | |
| using MQTTnet; | |
| class ChargeOrchestrator | |
| { | |
| const double PackSizeRemainingAtFull = 85000; // kWh | |
| readonly ILogger<ChargeOrchestrator> logger; | |
| readonly IMessagePublisher messagePublisher; | |
| double chargeSpeedPercentagePerHour = 17D; // 17%/h roughly 17kWh/h bu | |
| List<Price> PriceData = new(); | |
| int BatteryLevel; | |
| int ChargeLimitSoc; | |
| public ChargeOrchestrator(ILogger<ChargeOrchestrator> logger, IMessagePublisher messagePublisher) | |
| { | |
| this.logger = logger; | |
| this.messagePublisher = messagePublisher; | |
| FetchDataLoop(); | |
| } | |
| async void FetchDataLoop() | |
| { | |
| while (true) | |
| { | |
| using (var ctx = new EnergyContext()) | |
| { | |
| PriceData = ctx.Prices.Where(x => x.At > DateTime.UtcNow).OrderBy(x => x.At).ToList(); | |
| logger.LogInformation("Price count: {Count}", PriceData.Count); | |
| } | |
| await Update(CancellationToken.None); | |
| await Task.Delay(TimeSpan.FromHours(1));// Elke 6 uur... garandeerd een update tussen 18 en 24 | |
| } | |
| } | |
| [Subscribe("teslamate/cars/+/charge_current_request")] | |
| public async Task HandleChargeCurrentRequest(MqttApplicationMessage msg, CancellationToken cancellationToken) | |
| { | |
| var amps = int.Parse(msg.ConvertPayloadToString()); | |
| var chargerPower = 230 * amps * 3; | |
| var kWhPerPercent = PackSizeRemainingAtFull / 100; // 85000 /100 = 850 Wh | |
| chargeSpeedPercentagePerHour = chargerPower / kWhPerPercent;// 17.000 / 850 = | |
| await messagePublisher.Enqueue("chargeoptimizer/chargeSpeed", chargeSpeedPercentagePerHour.ToString(CultureInfo.InvariantCulture), retain: true, cancellationToken: cancellationToken); | |
| logger.LogInformation("charge_current_request = {Amps} => {ChargeSpeedPercentagePerHour}", amps, chargeSpeedPercentagePerHour); | |
| await Update(cancellationToken); | |
| } | |
| [Subscribe("teslamate/cars/+/battery_level")] | |
| public async Task HandleBatteryLevel(MqttApplicationMessage msg, CancellationToken cancellationToken) | |
| { | |
| BatteryLevel = int.Parse(msg.ConvertPayloadToString()); | |
| await Update(cancellationToken); | |
| } | |
| [Subscribe("teslamate/cars/+/charge_limit_soc")] | |
| public async Task HandleChargeLimitSoc(MqttApplicationMessage msg, CancellationToken cancellationToken) | |
| { | |
| ChargeLimitSoc = int.Parse(msg.ConvertPayloadToString()); | |
| await Update(cancellationToken); | |
| } | |
| async Task Update(CancellationToken cancellationToken) | |
| { | |
| if (ChargeLimitSoc == 0) return; | |
| if (BatteryLevel == 0) return; | |
| if (PriceData == null) return; | |
| var diff = Math.Max(ChargeLimitSoc - BatteryLevel, 0); | |
| var chargeDuration = TimeSpan.FromHours(diff / chargeSpeedPercentagePerHour); | |
| var requiredHours = (int)Math.Ceiling(chargeDuration.TotalHours); | |
| await messagePublisher.Enqueue("chargeoptimizer/chargeDuration", chargeDuration.ToString(), retain: true, cancellationToken: cancellationToken); | |
| var cheapest = (index: 0, sum: double.MaxValue); | |
| var now = DateTime.UtcNow; | |
| var hourlyPrices = PriceData | |
| .Where(x => x.At > now) | |
| .ToArray(); | |
| for (int i = 0; i < (hourlyPrices.Length + 1 - requiredHours); i++) | |
| { | |
| var sum = hourlyPrices.Skip(i).Take(requiredHours).Sum(x => x.prijsANWB); | |
| logger.LogTrace("Hourly prices sum from {firstHour:s} till {lastHour:s} = {sum}", hourlyPrices[i].At, hourlyPrices[i + requiredHours - 1].At, sum); | |
| if (sum < cheapest.sum) cheapest = (i, sum); | |
| } | |
| var at = hourlyPrices[cheapest.index].At; | |
| var startHourPrice = hourlyPrices[cheapest.index].prijs; | |
| var endHourPrice = hourlyPrices[cheapest.index + requiredHours - 1].prijs; | |
| var lastHourIsCheaperThenFirst = endHourPrice < startHourPrice; | |
| if (lastHourIsCheaperThenFirst) | |
| { | |
| logger.LogTrace("Defer start to maximize last hour"); | |
| at = at.AddHours(-(chargeDuration.TotalHours - requiredHours)); | |
| } | |
| logger.LogInformation("Start charging at: {at}", at); | |
| await messagePublisher.Enqueue("chargeoptimizer/chargestart", at.ToString("s"), retain: true, cancellationToken: cancellationToken); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment