Skip to content

Commit

Permalink
Merge pull request #50 from pkuehnel/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
pkuehnel authored Mar 19, 2022
2 parents b543ef0 + efb02ae commit e965223
Show file tree
Hide file tree
Showing 21 changed files with 380 additions and 180 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/alphaRelease.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
file: ./SmartTeslaAmpSetter/Server/Dockerfile
platforms: linux/amd64,linux/arm64,linux/arm/v7
push: true
tags: pkuehnel/smartteslaampsetter:alpha
tags: pkuehnel/smartteslaampsetter:jsonExtraction

SmaEnergymeterplugin:
name: Building SMAPlugin Image
Expand Down Expand Up @@ -61,4 +61,4 @@ jobs:
file: ./Plugins.SmaEnergymeter/Dockerfile
platforms: linux/amd64,linux/arm64,linux/arm/v7
push: true
tags: pkuehnel/smartteslaampsettersmaplugin:alpha
tags: pkuehnel/smartteslaampsettersmaplugin:jsonExtraction
8 changes: 3 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,7 @@ services:
- TeslaMateApiBaseUrl=http://teslamateapi:8080
- UpdateIntervalSeconds=30
- CarPriorities=1|2
- GeoFence=Zu Hause
- MaxAmpPerCar=16
- MinAmpPerCar=1
- GeoFence=Home
- MinutesUntilSwitchOn=5
- MinutesUntilSwitchOff=5
- PowerBuffer=0
Expand Down Expand Up @@ -105,11 +103,11 @@ Note: TeslaMateApi has to be configured to allow any command without authenticat
| **UpdateIntervalSeconds** | int | Intervall how often the charging amps should be set (Note: TeslaMateApi takes some time to get new current values, so do not set a value lower than 30) | 30 |
| **CarPriorities** | string | TeslaMate Car Ids separated by \| in the priority order. | 1\|2 |
| **GeoFence** | string | TeslaMate Geofence Name where amps should be set | Home |
| **MaxAmpPerCar** | int | Maximum current that can be set to a single car | 16 |
| **MinAmpPerCar** | int | Minimum current that can be set to a single car | 1 |
| **MinutesUntilSwitchOn** | int | Minutes with more power to grid than minimum settable until charging starts | 5 |
| **MinutesUntilSwitchOff** | int | Minutes with power from grid until charging stops | 5 |
| **PowerBuffer** | int | Power Buffer in Watt | 0 |
| **CurrentPowerToGridJsonPattern** | string | If Power to grid is json formated use this to extract the correct value | $.data.overage |
| **CurrentPowerToGridInvertValue** | boolean | Set this to `true` if Power from grid has positive values and power to grid has negative values | true |

### Car Priorities
If you set `CarPriorities` environment variable like the example above, the car with ID 2 will only start charing, if car 1 is charging at full speed and there is still power left, or if car 1 is not charging due to reached battery limit or not within specified geofence. Note: You always have to add the car Ids to this list separated by `|`. Even if you only have one car you need to ad the car's Id but then without `|`.
Expand Down
69 changes: 69 additions & 0 deletions SmartTeslaAmpSetter/Client/Pages/CarSettings.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
@page "/CarSettings"
@using SmartTeslaAmpSetter.Shared.Dtos
@inject HttpClient _httpClient
@inject IToastService _toastService


<h3>CarSettings</h3>

@if (_carBasicConfigurations == null)
{
<p><em>Loading...</em></p>
}
else
{
@foreach (var carBasicConfiguration in _carBasicConfigurations)
{
<div class="shadow p-3 mb-5 bg-white rounded">
<b>@carBasicConfiguration.CarName</b>
<p>
<label class="col-sm-4 col-md-3 col-lg-2" for="minAmpere">Min Ampere:</label>
<input class="col-sm-6 col-md-3 col-lg-2" value="@carBasicConfiguration.MinimumAmpere" type="number" id="minAmpere" name="minAmpere" min="1" max="32"
@onchange="@(e => carBasicConfiguration.MinimumAmpere = Int32.Parse(e.Value?.ToString() ?? "1"))">
</p>
<p>
<label class="col-sm-4 col-md-3 col-lg-2" for="maxAmpere">Max Ampere:</label>
<input class="col-sm-6 col-md-3 col-lg-2" value="@carBasicConfiguration.MaximumAmpere" type="number" id="maxAmpere" name="maxAmpere" min="1" max="32"
@onchange="@(e => carBasicConfiguration.MaximumAmpere = Int32.Parse(e.Value?.ToString() ?? "1"))">
</p>
<p>
<label class="col-sm-4 col-md-3 col-lg-2" for="usableEnergy">Usable kWh:</label>
<input class="col-sm-6 col-md-3 col-lg-2" value="@carBasicConfiguration.UsableEnergy" type="number" id="usableEnergy" name="usableEnergy" min="1" max="120"
@onchange="@(e => carBasicConfiguration.UsableEnergy = Int32.Parse(e.Value?.ToString() ?? "1"))">
</p>
<p><button class="btn btn-success col-sm-10 col-md-6 col-lg-4" @onclick="() => UpdateCarConfiguration(carBasicConfiguration.CarId, carBasicConfiguration)">@_saveButtonTexts[carBasicConfiguration.CarId]</button></p>
</div>
}
}

@code {
private List<CarBasicConfiguration>? _carBasicConfigurations;
private Dictionary<int, string> _saveButtonTexts = new Dictionary<int, string>();
private readonly string _saveButtonDefaultText = "Save";
private readonly string _buttonLoadingText = "...";

protected override async Task OnInitializedAsync()
{
_carBasicConfigurations = await _httpClient.GetFromJsonAsync<List<CarBasicConfiguration>>("/api/Config/GetCarBasicConfigurations");

foreach (var carBasicConfiguration in _carBasicConfigurations!)
{
_saveButtonTexts.Add(carBasicConfiguration.CarId, _saveButtonDefaultText);
}
}

private async Task UpdateCarConfiguration(int carId, CarBasicConfiguration carBasicConfiguration)
{
_saveButtonTexts[carId] = _buttonLoadingText;
var result = await _httpClient.PutAsJsonAsync($"api/Config/UpdateCarBasicConfiguration?carId={carId}", carBasicConfiguration);
if (result.IsSuccessStatusCode)
{
_toastService.ShowSuccess("Car Configuration updated");
}
else
{
_toastService.ShowError("Error updating car configuration");
}
_saveButtonTexts[carId] = _saveButtonDefaultText;
}
}
29 changes: 10 additions & 19 deletions SmartTeslaAmpSetter/Client/Pages/Index.razor
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
@page "/"
@using System.Globalization
@using System.Runtime.CompilerServices
@using System.Timers
@using SmartTeslaAmpSetter.Shared
@using SmartTeslaAmpSetter.Shared.Dtos
@using SmartTeslaAmpSetter.Shared.Dtos.Settings
@using SmartTeslaAmpSetter.Shared.Enums
@inject HttpClient _http
@inject HttpClient _httpClient
@inject IToastService _toastService
@inject IConfiguration _configuration

Expand All @@ -23,7 +22,7 @@ else
<div>Inverter: @_settings.InverterPower W</div>
<div>House: @(_settings.InverterPower - _settings.Overage - _settings.Cars.Sum(c => c.CarState.ChargingPowerAtHome)) W</div>
}
<div>Grid: @(-_settings.Overage) W</div>
<div class="@(_settings.Overage > 0 ? "text-success" : "text-danger")">Grid: @(Math.Abs(_settings.Overage)) W</div>
<div>Cars: @_settings.Cars.Sum(c => c.CarState.ChargingPowerAtHome) W</div>
<button class="btn btn-primary col-sm-5 col-md-3" @onclick="() => RefreshSettings()">@_refreshButtonText</button>
</div>
Expand Down Expand Up @@ -106,14 +105,6 @@ else
@(car.CarConfiguration.LatestTimeToReachSoC.ToString("HH:mm dd.MM"))
</td>
</tr>
<tr>
<td>
Voll geladen bei maximaler Geschwindigkeit
</td>
<td>
@(car.CarState.FullChargeAtMaxAcSpeed.ToString("HH:mm dd.MM"))
</td>
</tr>
<tr>
<td>
Voll geladen in
Expand Down Expand Up @@ -200,7 +191,7 @@ else

protected override async Task OnInitializedAsync()
{
var handler = _http.GetFromJsonAsync<Settings>("api/Config/GetSettings");
var handler = _httpClient.GetFromJsonAsync<Settings>("api/Config/GetSettings");
_settings = await handler;
foreach (var car in _settings!.Cars)
{
Expand All @@ -210,14 +201,14 @@ else

_timer = new Timer();
_timer.Interval = (_configuration.GetValue<int>("FrontendUpdateintervallSeconds") < 5 ? 5 : _configuration.GetValue<int>("FrontendUpdateintervallSeconds")) * 1000;
_timer.Elapsed += new ElapsedEventHandler(RefreshStates);
_timer.Elapsed += RefreshStates;
_timer.Start();
}

private async Task ChangeChargeMode(int carId)
{
_chargemodeChangeButtonTexts[carId] = _buttonLoadingText;
var updateBackend = _http.PostAsync($"api/Config/ChangeChargeMode?carId={carId}", new StringContent(string.Empty));
var updateBackend = _httpClient.PostAsync($"api/Config/ChangeChargeMode?carId={carId}", new StringContent(string.Empty));
var car = _settings?.Cars.First(c => c.Id == carId);
var result = await updateBackend;
if (result.IsSuccessStatusCode)
Expand All @@ -235,7 +226,7 @@ else
private async Task UpdateCarConfiguration(int carId, CarConfiguration carConfiguration)
{
_saveButtonTexts[carId] = _buttonLoadingText;
var result = await _http.PutAsJsonAsync($"api/Config/UpdateCarConfiguration?carId={carId}", carConfiguration);
var result = await _httpClient.PutAsJsonAsync($"api/Config/UpdateCarConfiguration?carId={carId}", carConfiguration);
if (result.IsSuccessStatusCode)
{
_toastService.ShowSuccess("Car Configuration updated");
Expand All @@ -249,7 +240,7 @@ else

private async void RefreshStates(object? sender, ElapsedEventArgs elapsedEventArgs)
{
var tmpSettings = await _http.GetFromJsonAsync<Settings>("api/Config/GetSettings");
var tmpSettings = await _httpClient.GetFromJsonAsync<Settings>("api/Config/GetSettings");
_settings!.InverterPower = tmpSettings!.InverterPower;
_settings.Overage = tmpSettings.Overage;
foreach (var tmpCar in tmpSettings.Cars)
Expand All @@ -260,10 +251,10 @@ else
this.StateHasChanged();
}

private async void RefreshSettings(bool autorefresh = false)
private async void RefreshSettings()
{
_refreshButtonText = _buttonLoadingText;
_settings = await _http.GetFromJsonAsync<Settings>("api/Config/GetSettings");
_settings = await _httpClient.GetFromJsonAsync<Settings>("api/Config/GetSettings");
this.StateHasChanged();
_toastService.ShowSuccess("Refreshed");
_refreshButtonText = _refreshButtonDefaultText;
Expand Down
5 changes: 5 additions & 0 deletions SmartTeslaAmpSetter/Client/Shared/NavMenu.razor
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="CarSettings">
<span class="oi oi-wrench" aria-hidden="true"></span> Car Settings
</NavLink>
</div>
</nav>
</div>

Expand Down
19 changes: 18 additions & 1 deletion SmartTeslaAmpSetter/Server/Controllers/ConfigController.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Mvc;
using SmartTeslaAmpSetter.Server.Services;
using SmartTeslaAmpSetter.Shared.Dtos;
using SmartTeslaAmpSetter.Shared.Dtos.Settings;
using SmartTeslaAmpSetter.Shared.Enums;

namespace SmartTeslaAmpSetter.Server.Controllers
Expand Down Expand Up @@ -36,6 +37,22 @@ public ConfigController(ConfigService service)
/// <param name="carId">Car Id of car to update</param>
/// <param name="carConfiguration">Car Configuration which should be set to car</param>
[HttpPut]
public void UpdateCarConfiguration(int carId, [FromBody] CarConfiguration carConfiguration) => _service.UpdateCarConfiguration(carId, carConfiguration);
public void UpdateCarConfiguration(int carId, [FromBody] CarConfiguration carConfiguration) =>
_service.UpdateCarConfiguration(carId, carConfiguration);

/// <summary>
/// Get basic Configuration of cars, which are not often changed
/// </summary>
[HttpGet]
public List<CarBasicConfiguration> GetCarBasicConfigurations() => _service.GetCarBasicConfigurations();

/// <summary>
/// Update Car's configuration
/// </summary>
/// <param name="carId">Car Id of car to update</param>
/// <param name="carBasicConfiguration">Car Configuration which should be set to car</param>
[HttpPut]
public void UpdateCarBasicConfiguration(int carId, [FromBody] CarBasicConfiguration carBasicConfiguration) =>
_service.UpdateCarBasicConfiguration(carId, carBasicConfiguration);
}
}
19 changes: 18 additions & 1 deletion SmartTeslaAmpSetter/Server/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using Serilog;
using SmartTeslaAmpSetter.Server.Scheduling;
using SmartTeslaAmpSetter.Server.Services;
using SmartTeslaAmpSetter.Shared.Dtos;
using SmartTeslaAmpSetter.Shared.Dtos.Settings;

var builder = WebApplication.CreateBuilder(args);

Expand Down Expand Up @@ -81,4 +81,21 @@ async Task AddCarIdsToSettings(Settings settings1)
{
var configJsonService = app.Services.GetRequiredService<ConfigJsonService>();
settings1.Cars = await configJsonService.GetCarsFromConfiguration();
foreach (var car in settings1.Cars)
{
if (car.CarConfiguration.UsableEnergy < 1)
{
car.CarConfiguration.UsableEnergy = 75;
}

if (car.CarConfiguration.MaximumAmpere < 1)
{
car.CarConfiguration.MaximumAmpere = 16;
}

if (car.CarConfiguration.MinimumAmpere < 16)
{
car.CarConfiguration.MinimumAmpere = 1;
}
}
}
35 changes: 24 additions & 11 deletions SmartTeslaAmpSetter/Server/Services/ChargingService.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using System.Text;
using Newtonsoft.Json;
using SmartTeslaAmpSetter.Server.Dtos;
using SmartTeslaAmpSetter.Shared.Dtos;
using SmartTeslaAmpSetter.Shared.Dtos.Settings;
using SmartTeslaAmpSetter.Shared.Enums;
using Car = SmartTeslaAmpSetter.Shared.Dtos.Car;
using Car = SmartTeslaAmpSetter.Shared.Dtos.Settings.Car;

namespace SmartTeslaAmpSetter.Server.Services;

Expand Down Expand Up @@ -146,13 +146,15 @@ private async Task<int> ChangeCarAmp(TeslaMateState teslaMateState, int ampToReg
_logger.LogTrace("{method}({param1}, {param2})", nameof(ChangeCarAmp), teslaMateState.data.car.car_name, ampToRegulate);
var finalAmpsToSet = teslaMateState.data.status.charging_details.charger_actual_current + ampToRegulate;
_logger.LogDebug("Amps to set: {amps}", finalAmpsToSet);
var car = _settings.Cars.First(c => c.Id == teslaMateState.data.car.car_id);
var ampChange = 0;
var maxAmpPerCar = _configuration.GetValue<int>("MaxAmpPerCar");
var minAmpPerCar = _configuration.GetValue<int>("MinAmpPerCar");
_logger.LogDebug("Max amp per car: {amp}", maxAmpPerCar);
var minAmpPerCar = car.CarConfiguration.MinimumAmpere;
var maxAmpPerCar = car.CarConfiguration.MaximumAmpere;
_logger.LogDebug("Min amp for car: {amp}", minAmpPerCar);
_logger.LogDebug("Max amp for car: {amp}", maxAmpPerCar);

var car = _settings.Cars.First(c => c.Id == teslaMateState.data.car.car_id);
var reachedMinimumSocAtFullSpeedChargeDateTime = ReachedMinimumSocAtFullSpeedChargeDateTime(car);
var activePhases = teslaMateState.data.status.charging_details.charger_phases > 1 ? 3 : 1;
var reachedMinimumSocAtFullSpeedChargeDateTime = ReachedMinimumSocAtFullSpeedChargeDateTime(car, activePhases);

//FullSpeed Aktivieren, wenn Minimum Soc nicht mehr erreicht werden kann
if (reachedMinimumSocAtFullSpeedChargeDateTime > car.CarConfiguration.LatestTimeToReachSoC
Expand All @@ -161,8 +163,9 @@ private async Task<int> ChangeCarAmp(TeslaMateState teslaMateState, int ampToReg
{
car.CarState.AutoFullSpeedCharge = true;
}
//FullSpeed deaktivieren, wenn Minimum Soc erreicht wurde
if (car.CarState.AutoFullSpeedCharge && car.CarState.SoC >= car.CarConfiguration.MinimumSoC)
//FullSpeed deaktivieren, wenn Minimum Soc erreicht wurde, oder Ziel SoC mehr als eine halbe Stunde zu früh erreicht
if (car.CarState.AutoFullSpeedCharge &&
(car.CarState.SoC >= car.CarConfiguration.MinimumSoC || reachedMinimumSocAtFullSpeedChargeDateTime < car.CarConfiguration.LatestTimeToReachSoC.AddMinutes(-30)))
{
car.CarState.AutoFullSpeedCharge = false;
}
Expand Down Expand Up @@ -265,14 +268,19 @@ private async Task<int> ChangeCarAmp(TeslaMateState teslaMateState, int ampToReg
return ampChange;
}

private static DateTime ReachedMinimumSocAtFullSpeedChargeDateTime(Car car)
private static DateTime ReachedMinimumSocAtFullSpeedChargeDateTime(Car car, int numberOfPhases)
{
var socToCharge = (double) car.CarConfiguration.MinimumSoC - car.CarState.SoC;
if (socToCharge < 1)
{
return DateTime.Now + TimeSpan.Zero;
}
return DateTime.Now + TimeSpan.FromHours(socToCharge / 15);
var energyToCharge = car.CarConfiguration.UsableEnergy * 1000 * (decimal) (socToCharge / 100.0);
var maxChargingPower =
car.CarConfiguration.MaximumAmpere * numberOfPhases
//Use 230 instead of actual voltage because of 0 Volt if charging is stopped
* 230;
return DateTime.Now + TimeSpan.FromHours((double) (energyToCharge/maxChargingPower));
}

private void UpdateEarliestTimesAfterSwitch(int carId)
Expand Down Expand Up @@ -419,6 +427,11 @@ private async Task<List<TeslaMateState>> GetTeslaMateStates(List<int> carIds)
{
result.EnsureSuccessStatusCode();
var state = await result.Content.ReadFromJsonAsync<TeslaMateState>().ConfigureAwait(false);
if (state != null && state.data.status.charging_details.charge_limit_soc < 50)
{
_logger.LogWarning("Charge Limit of car number {carId} is below 50.", carId);
state.data.status.charging_details.charge_limit_soc = 90;
}
teslaMateStates.Add(state ?? throw new InvalidOperationException());
}
catch (Exception e)
Expand Down
9 changes: 6 additions & 3 deletions SmartTeslaAmpSetter/Server/Services/ConfigJsonService.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System.Reflection;
using Newtonsoft.Json;
using SmartTeslaAmpSetter.Shared;
using SmartTeslaAmpSetter.Shared.Dtos;
using SmartTeslaAmpSetter.Shared.Dtos.Settings;
using SmartTeslaAmpSetter.Shared.Enums;

namespace SmartTeslaAmpSetter.Server.Services;
Expand All @@ -19,13 +19,13 @@ public ConfigJsonService(ILogger<ConfigJsonService> logger, IConfiguration confi
_settings = settings;
}

public bool CarConfigurationFileExists()
private bool CarConfigurationFileExists()
{
var path = GetConfigurationFileFullPath();
return File.Exists(path);
}

public string GetConfigurationFileFullPath()
private string GetConfigurationFileFullPath()
{
var configFileLocation = _configuration.GetValue<string>("ConfigFileLocation");
var path = new FileInfo(Assembly.GetExecutingAssembly().Location).Directory?.FullName;
Expand Down Expand Up @@ -78,6 +78,9 @@ private static void AddNewCars(List<string> newCarIds, List<Car> cars)
{
ChargeMode = ChargeMode.MaxPower,
UpdatedSincLastWrite = true,
MaximumAmpere = 16,
MinimumAmpere = 2,
UsableEnergy = 75,
},
CarState =
{
Expand Down
Loading

0 comments on commit e965223

Please sign in to comment.