Skip to content

Commit

Permalink
Merge pull request #31 from pkuehnel/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
pkuehnel authored Mar 13, 2022
2 parents 3945eb9 + 5aebd41 commit 39a1fb3
Show file tree
Hide file tree
Showing 11 changed files with 171 additions and 43 deletions.
2 changes: 1 addition & 1 deletion Plugins.SmaEnergymeter/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@
"AllowedHosts": "*",
"EnergyMeterPort": 9522,
"EnergyMeterMulticastAddress": "239.12.255.254",
"MaxValuesInLastValuesList": 60
"MaxValuesInLastValuesList": 30
}
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ Note: TeslaMateApi has to be configured to allow any command without authenticat
| Variable | Type | Explanation | Example |
|---|---|---|---|
| **CurrentPowerToGridUrl** | string | URL to REST Endpoint of smart meter | http://192.168.1.50/api/CurrentPower |
| **CurrentInverterPowerUrl** | string | URL to REST Endpoint of inverter (optional) | http://192.168.1.50/api/CurrentInverterPower |
| **TeslaMateApiBaseUrl** | string | Base URL to TeslaMateApi instance | http://teslamateapi:8080 |
| **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 |
Expand Down
126 changes: 94 additions & 32 deletions SmartTeslaAmpSetter/Client/Pages/Index.razor
Original file line number Diff line number Diff line change
@@ -1,48 +1,63 @@
@page "/"
@page "/"
@using System.Globalization
@using System.Runtime.CompilerServices
@using System.Timers
@using SmartTeslaAmpSetter.Shared
@using SmartTeslaAmpSetter.Shared.Dtos
@using SmartTeslaAmpSetter.Shared.Enums
@inject HttpClient _http
@inject IToastService _toastService
@inject IConfiguration _configuration

<PageTitle>Index</PageTitle>

@if (_settings?.Cars == null)
@if (_settings == null)
{
<p><em>Loading...</em></p>
}
else
{
@foreach (var car in _settings.Cars)
{
<p><b>@(car.CarState.Name)</b>: @(car.CarState.SoC)% (@(car.CarState.SocLimit)%)</p>
<p>@car.CarConfiguration.ChargeMode.ToFriendlyString() <button class="btn btn-primary" @onclick="() => ChangeChargeMode(car.Id)">Change</button></p>
@if (car.CarConfiguration.ChargeMode == ChargeMode.PvAndMinSoc || car.CarConfiguration.ChargeMode == ChargeMode.PvOnly)
{
<p>
<label for="minSoc">Minimum SOC:</label>
<input value="@car.CarConfiguration.MinimumSoC" type="number" id="minSoc" name="minSoc" min="0" max="100"
@onchange="@(e => car.CarConfiguration.MinimumSoC = Int32.Parse(e.Value?.ToString() ?? "0"))">%
</p>
}
@if (car.CarConfiguration.ChargeMode == ChargeMode.PvOnly)
{
<p>
<label for="date">Datum:</label>
<input value="@car.CarConfiguration.LatestTimeToReachSoC.Date.ToString("yyyy-MM-dd")" type="date" id="date" name="date"
@onchange="@(e => car.CarConfiguration.LatestTimeToReachSoC = DateTime.Parse(e.Value?.ToString() ?? DateTime.MaxValue.ToString(CultureInfo.CurrentCulture)).Date.AddHours(car.CarConfiguration.LatestTimeToReachSoC.Hour).AddMinutes(car.CarConfiguration.LatestTimeToReachSoC.Minute))">
</p>
<p>
<label for="time">Uhrzeit:</label>
<input value="@car.CarConfiguration.LatestTimeToReachSoC.ToString("HH:mm")" type="time" id="time" name="time"
@onchange="@(e => car.CarConfiguration.LatestTimeToReachSoC = car.CarConfiguration.LatestTimeToReachSoC.Date.AddHours(TimeSpan.Parse(e.Value?.ToString() ?? "00:00").Hours).AddMinutes(TimeSpan.Parse(e.Value?.ToString() ?? "00:00").Minutes))">
</p>
}
@if (car.CarConfiguration.ChargeMode == ChargeMode.PvAndMinSoc || car.CarConfiguration.ChargeMode == ChargeMode.PvOnly)
<div class="shadow p-3 mb-5 bg-white rounded">
@if (_settings.InverterPower != null)
{
<p><button class="btn btn-primary" @onclick="() => UpdateCarConfiguration(car.Id, car.CarConfiguration)">Save</button></p>
<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>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>
@foreach (var car in _settings.Cars)
{
<div class="shadow p-3 mb-5 bg-white rounded">
<div><b>@(car.CarState.Name)</b>: @(car.CarState.SoC)% (@(car.CarState.SocLimit)%) @car.CarState.ChargingPowerAtHome W</div>
<div class="col-sm-4 col-md-3">@car.CarConfiguration.ChargeMode.ToFriendlyString()</div> <button class="btn btn-primary col-sm-5 col-md-3" @onclick="() => ChangeChargeMode(car.Id)">@_chargemodeChangeButtonTexts[car.Id]</button>
@if (car.CarConfiguration.ChargeMode == ChargeMode.PvAndMinSoc || car.CarConfiguration.ChargeMode == ChargeMode.PvOnly)
{
<p>
<label class="col-sm-4 col-md-3 col-lg-2" for="minSoc">Min SOC:</label>
<input class="col-sm-6 col-md-3 col-lg-2" value="@car.CarConfiguration.MinimumSoC" type="number" id="minSoc" name="minSoc" min="0" max="100"
@onchange="@(e => car.CarConfiguration.MinimumSoC = Int32.Parse(e.Value?.ToString() ?? "0"))">%
</p>
}
@if (car.CarConfiguration.ChargeMode == ChargeMode.PvOnly)
{
<p>
<label class="col-sm-4 col-md-3 col-lg-2" for="date">Datum:</label>
<input class="col-sm-6 col-md-3 col-lg-2" value="@car.CarConfiguration.LatestTimeToReachSoC.Date.ToString("yyyy-MM-dd")" type="date" id="date" name="date"
@onchange="@(e => car.CarConfiguration.LatestTimeToReachSoC = DateTime.Parse(e.Value?.ToString() ?? DateTime.MaxValue.ToString(CultureInfo.CurrentCulture)).Date.AddHours(car.CarConfiguration.LatestTimeToReachSoC.Hour).AddMinutes(car.CarConfiguration.LatestTimeToReachSoC.Minute))">
</p>
<p>
<label class="col-sm-4 col-md-3 col-lg-2" for="time">Uhrzeit:</label>
<input class="col-sm-6 col-md-3 col-lg-2" value="@car.CarConfiguration.LatestTimeToReachSoC.ToString("HH:mm")" type="time" id="time" name="time"
@onchange="@(e => car.CarConfiguration.LatestTimeToReachSoC = car.CarConfiguration.LatestTimeToReachSoC.Date.AddHours(TimeSpan.Parse(e.Value?.ToString() ?? "00:00").Hours).AddMinutes(TimeSpan.Parse(e.Value?.ToString() ?? "00:00").Minutes))">
</p>
}
@if (car.CarConfiguration.ChargeMode == ChargeMode.PvAndMinSoc || car.CarConfiguration.ChargeMode == ChargeMode.PvOnly)
{
<p><button class="btn btn-success col-sm-10 col-md-6 col-lg-4" @onclick="() => UpdateCarConfiguration(car.Id, car.CarConfiguration)">@_saveButtonTexts[car.Id]</button></p>
}
</div>
}

<h2>Details: </h2>
Expand Down Expand Up @@ -172,31 +187,54 @@ else

@code {
private Settings? _settings;
private Dictionary<int, string> _chargemodeChangeButtonTexts = new Dictionary<int, string>();
private Dictionary<int, string> _saveButtonTexts = new Dictionary<int, string>();

private Timer? _timer;

private readonly string _chargeModeChangeButtonDefaultText = "Change";
private readonly string _saveButtonDefaultText = "Save";
private string _refreshButtonText = "Refresh";
private string _refreshButtonDefaultText = "Refresh";
private readonly string _buttonLoadingText = "...";

protected override async Task OnInitializedAsync()
{
_settings = await _http.GetFromJsonAsync<Settings>("api/Config/GetSettings");
var handler = _http.GetFromJsonAsync<Settings>("api/Config/GetSettings");
_settings = await handler;
foreach (var car in _settings!.Cars)
{
_chargemodeChangeButtonTexts.Add(car.Id, _chargeModeChangeButtonDefaultText);
_saveButtonTexts.Add(car.Id, _saveButtonDefaultText);
}

_timer = new Timer();
_timer.Interval = (_configuration.GetValue<int>("FrontendUpdateintervallSeconds") < 5 ? 5 : _configuration.GetValue<int>("FrontendUpdateintervallSeconds")) * 1000;
_timer.Elapsed += new ElapsedEventHandler(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 car = _settings?.Cars.First(c => c.Id == carId);
var result = await updateBackend;
if (result.IsSuccessStatusCode)
{
_toastService.ShowSuccess("ChargeMode Changed");
car!.CarConfiguration.ChargeMode = car.CarConfiguration.ChargeMode.Next();
}
else
{
_toastService.ShowError("Error changing ChargeMode");
return;
}
car!.CarConfiguration.ChargeMode = car.CarConfiguration.ChargeMode.Next();
_chargemodeChangeButtonTexts[carId] = _chargeModeChangeButtonDefaultText;
}

private async Task UpdateCarConfiguration(int carId, CarConfiguration carConfiguration)
{
_saveButtonTexts[carId] = _buttonLoadingText;
var result = await _http.PutAsJsonAsync($"api/Config/UpdateCarConfiguration?carId={carId}", carConfiguration);
if (result.IsSuccessStatusCode)
{
Expand All @@ -206,5 +244,29 @@ else
{
_toastService.ShowError("Error updating car configuration");
}
_saveButtonTexts[carId] = _saveButtonDefaultText;
}

private async void RefreshStates(object? sender, ElapsedEventArgs elapsedEventArgs)
{
var tmpSettings = await _http.GetFromJsonAsync<Settings>("api/Config/GetSettings");
_settings!.InverterPower = tmpSettings!.InverterPower;
_settings.Overage = tmpSettings.Overage;
foreach (var tmpCar in tmpSettings.Cars)
{
var car = _settings.Cars.First(c => c.Id == tmpCar.Id);
car.CarState = tmpCar.CarState;
}
this.StateHasChanged();
}

private async void RefreshSettings(bool autorefresh = false)
{
_refreshButtonText = _buttonLoadingText;
_settings = await _http.GetFromJsonAsync<Settings>("api/Config/GetSettings");
this.StateHasChanged();
_toastService.ShowSuccess("Refreshed");
_refreshButtonText = _refreshButtonDefaultText;
this.StateHasChanged();
}
}
2 changes: 1 addition & 1 deletion SmartTeslaAmpSetter/Server/Controllers/ConfigController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public ConfigController(ConfigService service)
/// Get all settings and status of all cars
/// </summary>
[HttpGet]
public Settings GetSettings() => _service.GetSettings();
public Task<Settings> GetSettings() => _service.GetSettings();

/// <summary>
/// Change Chargemode of car
Expand Down
8 changes: 8 additions & 0 deletions SmartTeslaAmpSetter/Server/Scheduling/JobManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ public async void StartJobs(TimeSpan jobIntervall)
_scheduler = _schedulerFactory.GetScheduler().GetAwaiter().GetResult();
_scheduler.JobFactory = _jobFactory;

var minimumJobIntervall = TimeSpan.FromSeconds(30);

if (jobIntervall < minimumJobIntervall)
{
_logger.LogWarning("Jobintervall below {minimumJobIntervall}. Due to delays in TeslaMate and TeslaMate API use {minimum} as job interval", minimumJobIntervall, minimumJobIntervall);
jobIntervall = minimumJobIntervall;
}

var chargeLogJob = JobBuilder.Create<Job>().Build();
var configJsonUpdateJob = JobBuilder.Create<ConfigJsonUpdateJob>().Build();

Expand Down
34 changes: 29 additions & 5 deletions SmartTeslaAmpSetter/Server/Services/ChargingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,20 @@ public ChargingService(ILogger<ChargingService> logger, GridService gridService,
_teslaMateBaseUrl = _configuration.GetValue<string>("TeslaMateApiBaseUrl");
}

public async Task SetNewChargingValues()
public async Task SetNewChargingValues(bool onlyUpdateValues = false)
{
_logger.LogTrace($"{nameof(SetNewChargingValues)}()");

var overage = await _gridService.GetCurrentOverage().ConfigureAwait(false);

_settings.Overage = overage;

_logger.LogDebug($"Current overage is {overage} Watt.");

var inverterPower = await _gridService.GetCurrentInverterPower().ConfigureAwait(false);

_settings.InverterPower = inverterPower;

_logger.LogDebug($"Current overage is {overage} Watt.");

var buffer = _configuration.GetValue<int>("PowerBuffer");
Expand All @@ -40,12 +48,17 @@ public async Task SetNewChargingValues()
var carIds = _settings.Cars.Select(c => c.Id).ToList();

var teslaMateStates = await GetTeslaMateStates(carIds).ConfigureAwait(false);

UpdateCarStates(teslaMateStates);


var geofence = _configuration.GetValue<string>("GeoFence");
_logger.LogDebug("Relevant Geofence: {geofence}", geofence);

UpdateCarStates(teslaMateStates, geofence);

if (onlyUpdateValues)
{
return;
}

var relevantTeslaMateStates = GetRelevantTeslaMateStates(teslaMateStates, geofence);
_logger.LogDebug("Number of relevant Cars: {count}", relevantTeslaMateStates.Count);

Expand Down Expand Up @@ -103,7 +116,7 @@ private static List<TeslaMateState> GetRelevantTeslaMateStates(List<TeslaMateSta



private void UpdateCarStates(List<TeslaMateState> teslaMateStates)
private void UpdateCarStates(List<TeslaMateState> teslaMateStates, string geofence)
{
foreach (var teslaMateState in teslaMateStates)
{
Expand All @@ -114,6 +127,17 @@ private void UpdateCarStates(List<TeslaMateState> teslaMateStates)
car.CarState.SocLimit = teslaMateState.data.status.charging_details.charge_limit_soc;
car.CarState.TimeUntilFullCharge =
TimeSpan.FromHours(teslaMateState.data.status.charging_details.time_to_full_charge);

// ReSharper disable once ConvertIfStatementToConditionalTernaryExpression
if (teslaMateState.data.status.car_geodata.geofence.Equals(geofence,
StringComparison.InvariantCultureIgnoreCase))
{
car.CarState.ChargingPowerAtHome = teslaMateState.data.status.charging_details.ChargingPower;
}
else
{
car.CarState.ChargingPowerAtHome = 0;
}
}
}

Expand Down
7 changes: 5 additions & 2 deletions SmartTeslaAmpSetter/Server/Services/ConfigService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,19 @@ public class ConfigService
{
private readonly ILogger<ConfigService> _logger;
private readonly Settings _settings;
private readonly ChargingService _chargingService;

public ConfigService(ILogger<ConfigService> logger, Settings settings)
public ConfigService(ILogger<ConfigService> logger, Settings settings, ChargingService chargingService)
{
_logger = logger;
_settings = settings;
_chargingService = chargingService;
}

public Settings GetSettings()
public async Task<Settings> GetSettings()
{
_logger.LogTrace("{method}()", nameof(GetSettings));
await _chargingService.SetNewChargingValues(true);
return _settings;
}

Expand Down
23 changes: 23 additions & 0 deletions SmartTeslaAmpSetter/Server/Services/GridService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,27 @@ public async Task<int> GetCurrentOverage()

throw new InvalidCastException($"Could not parse result {result} from uri {requestUri} to integer");
}

public async Task<int?> GetCurrentInverterPower()
{
_logger.LogTrace("{method}()", nameof(GetCurrentInverterPower));
using var httpClient = new HttpClient();
var requestUri = _configuration.GetValue<string>("CurrentInverterPowerUrl");
if (requestUri == null)
{
return null;
}
var response = await httpClient.GetAsync(
requestUri)
.ConfigureAwait(false);
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

if (int.TryParse(result, out var overage))
{
return overage;
}

throw new InvalidCastException($"Could not parse result {result} from uri {requestUri} to integer");
}
}
1 change: 1 addition & 0 deletions SmartTeslaAmpSetter/Server/appsettings.Development.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
},
"AllowedHosts": "*",
"CurrentPowerToGridUrl": "http://192.168.1.50:5007/api/ChargingLog/GetAverageGridPowerOfLastXseconds",
"CurrentInverterPowerUrl": "http://192.168.1.50:5007/api/ChargingLog/GetAverageInverterPowerOfLastXseconds",
"TeslaMateApiBaseUrl": "http://192.168.1.50:8097",
"UpdateIntervalSeconds": 30,
"CarPriorities": "1|2",
Expand Down
5 changes: 3 additions & 2 deletions SmartTeslaAmpSetter/Server/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@
]
},
"AllowedHosts": "*",
"ConfigFileLocation": "configs/carConfig.json"
"ConfigFileLocation": "configs/carConfig.json",
"UpdateIntervalSeconds": 30,
"FrontendUpdateintervallSeconds": 6
//"CurrentPowerToGridUrl": "http://192.168.1.50:5007/api/ChargingLog/GetAverageGridPowerOfLastXseconds",
//"TeslaMateApiBaseUrl": "http://192.168.1.50:8097",
//"UpdateIntervalSeconds": 30,
//"CarPriorities": "1|2",
//"GeoFence": "Zu Hause",
//"MaxAmpPerCar": 16,
Expand Down
5 changes: 5 additions & 0 deletions SmartTeslaAmpSetter/Shared/Dtos/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ public Settings()
Cars = new List<Car>();
}

public int? InverterPower { get; set; }
public int Overage { get; set; }

public List<Car> Cars
{
get => _cars;
Expand Down Expand Up @@ -96,6 +99,8 @@ public DateTime FullChargeAtMaxAcSpeed
}
public int LastSetAmp { get; set; }

public int ChargingPowerAtHome { get; set; }

}

public class Car
Expand Down

0 comments on commit 39a1fb3

Please sign in to comment.