Skip to content

Commit

Permalink
Merge pull request #67 from pkuehnel/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
pkuehnel authored Apr 17, 2022
2 parents 475d426 + 343b630 commit 3671f1a
Show file tree
Hide file tree
Showing 44 changed files with 1,043 additions and 158 deletions.
8 changes: 8 additions & 0 deletions .github/workflows/alphaRelease.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ jobs:
- name: Checkout
uses: actions/checkout@v2

- name: Setup dotnet
uses: actions/setup-dotnet@v2
with:
dotnet-version: '6.0.x'

- name: Run unit tests
run: dotnet test SmartTeslaAmpSetter.Tests

- name: Set up QEMU
uses: docker/setup-qemu-action@v1

Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/edgeRelease.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ jobs:
- name: Set up QEMU
uses: docker/setup-qemu-action@v1

- name: Setup dotnet
uses: actions/setup-dotnet@v2
with:
dotnet-version: '6.0.x'

- name: Run unit tests
run: dotnet test SmartTeslaAmpSetter.Tests

- name: Buildx
id: buildx
uses: docker/setup-buildx-action@v1
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/versionRelease.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v2

- name: Setup dotnet
uses: actions/setup-dotnet@v2
with:
dotnet-version: '6.0.x'

- name: Run unit tests
run: dotnet test SmartTeslaAmpSetter.Tests

- name: Set up QEMU
uses: docker/setup-qemu-action@v1
Expand Down
10 changes: 5 additions & 5 deletions Plugins.SmaEnergymeter/Plugins.SmaEnergymeter.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
Expand All @@ -9,10 +9,10 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.14.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
<PackageReference Include="Quartz" Version="3.3.3" />
<PackageReference Include="Serilog.AspNetCore" Version="4.1.0" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.15.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.3.0" />
<PackageReference Include="Quartz" Version="3.4.0" />
<PackageReference Include="Serilog.AspNetCore" Version="5.0.0" />
<PackageReference Include="Serilog.Settings.Configuration" Version="3.3.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
</ItemGroup>
Expand Down
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Needs:
- [Power Buffer](#power-buffer)
- [UI](#UI)
- [Charge Modes](#charge-modes)
- [Telegram Notifications](#telegram-notifications)
- [Plugins](#plugins)
- [SMA-EnergyMeter Plugin](#sma-energymeter-plugin)

Expand Down Expand Up @@ -47,7 +48,7 @@ services:
- CurrentPowerToGridUrl=http://192.168.1.50/api/CurrentPower
- TeslaMateApiBaseUrl=http://teslamateapi:8080
- UpdateIntervalSeconds=30
- CarPriorities=1|2
- CarPriorities=1
- GeoFence=Home
- MinutesUntilSwitchOn=5
- MinutesUntilSwitchOff=5
Expand Down Expand Up @@ -108,6 +109,8 @@ Note: TeslaMateApi has to be configured to allow any command without authenticat
| **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 |
| **TelegramBotKey** | string | Telegram Bot API key | 1234567890:ASDFuiauhwerlfvasedr |
| **TelegramChannelId** | string | ChannelId Telegram bot should send messages to | -156480125 |

### 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 All @@ -124,6 +127,9 @@ Currently there are three different charge modes available:
1. **Maximum Power**: Car charges with maximum available power
1. **Min SoC + PV**: If plugged in the car starts charging with maximum power until set Min SoC is reached. After that only PV Power is used to charge the car.

### Telegram Notifications
If you set the environment variables `TelegramBotKey`and `TelegramChannelId`, you get messages, if a car can not be woken up, or any command could not be sent to a Tesla. Note: If your car takes longer than 30 seconds to wake up probably you will get an error notification, but as soon as the car is online charging starts.

### Plugins
If your SmartMeter does not have a REST Endpoint as needed you can use plugins:

Expand Down
111 changes: 111 additions & 0 deletions SmartTeslaAmpSetter.Tests/Services/ChargeTimeUpdateService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
using System;
using SmartTeslaAmpSetter.Shared.Dtos.Settings;
using SmartTeslaAmpSetter.Shared.TimeProviding;
using Xunit;
using Xunit.Abstractions;

namespace SmartTeslaAmpSetter.Tests.Services;

public class ChargeTimeUpdateService : TestBase
{
public ChargeTimeUpdateService(ITestOutputHelper outputHelper)
: base(outputHelper)
{
}

[Theory]
[InlineData(3)]
[InlineData(1)]
public void Calculates_Correct_Charge_MaxSpeed_Charge_Time(int numberOfPhases)
{
var car = new Car()
{
Id = 1,
CarState = new CarState()
{
PluggedIn = true,
SoC = 30,
ChargerPhases = numberOfPhases
},
CarConfiguration = new CarConfiguration()
{
MinimumSoC = 45,
UsableEnergy = 74,
MaximumAmpere = 16,
}
};


var dateTime = new DateTime(2022, 4, 1, 14, 0, 0);
Mock.Mock<IDateTimeProvider>().Setup(d => d.Now()).Returns(dateTime);
var chargingService = Mock.Create<Server.Services.ChargeTimeUpdateService>();

chargingService.UpdateChargeTime(car);

var lowerMinutes = 60 * (3 / numberOfPhases);

#pragma warning disable CS8629
Assert.InRange((DateTime)car.CarState.ReachingMinSocAtFullSpeedCharge, dateTime.AddMinutes(lowerMinutes), dateTime.AddMinutes(lowerMinutes + 1));
#pragma warning restore CS8629
}

[Fact]
public void Handles_Plugged_Out_Car()
{
var car = new Car()
{
Id = 1,
CarState = new CarState()
{
PluggedIn = false,
SoC = 30,
ChargerPhases = 1
},
CarConfiguration = new CarConfiguration()
{
MinimumSoC = 45,
UsableEnergy = 74,
MaximumAmpere = 16,
}
};


var dateTime = new DateTime(2022, 4, 1, 14, 0, 0);
Mock.Mock<IDateTimeProvider>().Setup(d => d.Now()).Returns(dateTime);
var chargingService = Mock.Create<Server.Services.ChargeTimeUpdateService>();

chargingService.UpdateChargeTime(car);

Assert.Null(car.CarState.ReachingMinSocAtFullSpeedCharge);
}

[Fact]
public void Handles_Reaced_Minimum_Soc()
{
var car = new Car()
{
Id = 1,
CarState = new CarState()
{
PluggedIn = true,
SoC = 30,
ChargerPhases = 1
},
CarConfiguration = new CarConfiguration()
{
MinimumSoC = 30,
UsableEnergy = 74,
MaximumAmpere = 16,
}
};


var dateTime = new DateTime(2022, 4, 1, 14, 0, 0);
Mock.Mock<IDateTimeProvider>().Setup(d => d.Now()).Returns(dateTime);
var chargingService = Mock.Create<Server.Services.ChargeTimeUpdateService>();

chargingService.UpdateChargeTime(car);

Assert.Equal(dateTime, car.CarState.ReachingMinSocAtFullSpeedCharge);
}
}
183 changes: 183 additions & 0 deletions SmartTeslaAmpSetter.Tests/Services/ChargingService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
using System;
using System.Collections.Generic;
using Autofac;
using SmartTeslaAmpSetter.Shared.Dtos.Settings;
using SmartTeslaAmpSetter.Shared.Enums;
using SmartTeslaAmpSetter.Shared.TimeProviding;
using Xunit;
using Xunit.Abstractions;

namespace SmartTeslaAmpSetter.Tests.Services;

public class ChargingService : TestBase
{
public ChargingService(ITestOutputHelper outputHelper)
: base(outputHelper)
{
}

[Theory]
[InlineData(ChargeMode.PvAndMinSoc, -32, 10, false)]
[InlineData(ChargeMode.PvAndMinSoc, 2, -10, false)]
[InlineData(ChargeMode.PvOnly, -32, 10, false)]
[InlineData(ChargeMode.PvOnly, 2, -10, false)]
[InlineData(ChargeMode.PvAndMinSoc, -32, 10, true)]
[InlineData(ChargeMode.PvAndMinSoc, 2, -10, true)]
[InlineData(ChargeMode.PvOnly, -32, 10, true)]
[InlineData(ChargeMode.PvOnly, 2, -10, true)]
public void Does_autoenable_fullspeed_charge_if_needed(ChargeMode chargeMode, int fullSpeedChargeMinutesAfterLatestTime, int moreSocThanMinSoc, bool autofullSpeedCharge)
{
var chargingService = Mock.Create<Server.Services.ChargingService>();
var currentTimeProvider = Mock.Create<FakeDateTimeProvider>(
new NamedParameter("dateTime", new DateTime(2022, 4, 1, 14, 0, 0)));
var currentTime = currentTimeProvider.Now();

var timeSpanToLatestTimeToReachMinSoc = TimeSpan.FromMinutes(60);
var timeSpanToReachMinSoCAtFullSpeedCharge = timeSpanToLatestTimeToReachMinSoc.Add(TimeSpan.FromMinutes(fullSpeedChargeMinutesAfterLatestTime));

var minSoc = 50;

var car = CreateDemoCar(chargeMode, currentTime + timeSpanToLatestTimeToReachMinSoc, minSoc + moreSocThanMinSoc, minSoc, autofullSpeedCharge);


car.CarState.ReachingMinSocAtFullSpeedCharge = currentTime + timeSpanToReachMinSoCAtFullSpeedCharge;

chargingService.EnableFullSpeedChargeIfMinimumSocNotReachable(car);
chargingService.DisableFullSpeedChargeIfMinimumSocReachedOrMinimumSocReachable(car);


if (fullSpeedChargeMinutesAfterLatestTime > 0)
{
Assert.True(car.CarState.AutoFullSpeedCharge);
return;
}

if (moreSocThanMinSoc >= 0)
{
Assert.False(car.CarState.AutoFullSpeedCharge);
return;
}

switch (chargeMode)
{
case ChargeMode.PvAndMinSoc:
Assert.True(car.CarState.AutoFullSpeedCharge);
break;

case ChargeMode.PvOnly:
Assert.False(car.CarState.AutoFullSpeedCharge);
break;

default:
throw new NotImplementedException("This test does not handle this charge mode");
}

}

[Theory]
[InlineData(false)]
[InlineData(true)]
public void Enable_Full_Speed_Charge_Can_Handle_Null_Values(bool autoFullSpeedCharge)
{
var chargingService = Mock.Create<Server.Services.ChargingService>();
var currentTimeProvider = Mock.Create<FakeDateTimeProvider>(
new NamedParameter("dateTime", new DateTime(2022, 4, 1, 14, 0, 0)));
var currentTime = currentTimeProvider.Now();

var timeSpanToLatestTimeToReachMinSoc = TimeSpan.FromMinutes(60);

var minSoc = 50;

var car = CreateDemoCar(ChargeMode.PvAndMinSoc, currentTime + timeSpanToLatestTimeToReachMinSoc, minSoc + 10, minSoc, autoFullSpeedCharge);

car.CarState.ReachingMinSocAtFullSpeedCharge = null;

chargingService.EnableFullSpeedChargeIfMinimumSocNotReachable(car);

Assert.Equal(autoFullSpeedCharge, car.CarState.AutoFullSpeedCharge);
}

[Theory]
[InlineData(false)]
[InlineData(true)]
public void Disable_Full_Speed_Charge_Can_Handle_Null_Values(bool autoFullSpeedCharge)
{
var chargingService = Mock.Create<Server.Services.ChargingService>();
var currentTimeProvider = Mock.Create<FakeDateTimeProvider>(
new NamedParameter("dateTime", new DateTime(2022, 4, 1, 14, 0, 0)));
var currentTime = currentTimeProvider.Now();

var timeSpanToLatestTimeToReachMinSoc = TimeSpan.FromMinutes(60);

var minSoc = 55;

var car = CreateDemoCar(ChargeMode.PvOnly, currentTime + timeSpanToLatestTimeToReachMinSoc, minSoc - 10, minSoc, autoFullSpeedCharge);

car.CarState.ReachingMinSocAtFullSpeedCharge = null;

chargingService.DisableFullSpeedChargeIfMinimumSocReachedOrMinimumSocReachable(car);

Assert.False(car.CarState.AutoFullSpeedCharge);
}

[Fact]
public void Gets_relevant_car_IDs()
{
var geofence = "Home";
var cars = new List<Car>()
{
new Car()
{
Id = 1,
CarState = new CarState()
{
Geofence = geofence,
PluggedIn = true,
ClimateOn = false,
ChargerActualCurrent = 3,
SoC = 30,
SocLimit = 60,
},
},
new Car()
{
Id = 2,
CarState = new CarState()
{
Geofence = null,
PluggedIn = true,
ClimateOn = false,
ChargerActualCurrent = 3,
SoC = 30,
SocLimit = 60,
},
},
};
Mock.Mock<ISettings>().Setup(s => s.Cars).Returns(cars);
var chargingService = Mock.Create<Server.Services.ChargingService>();

var relevantIds = chargingService.GetRelevantCarIds(geofence);

Assert.Contains(1, relevantIds);
Assert.Single(relevantIds);
}

private Car CreateDemoCar(ChargeMode chargeMode, DateTime latestTimeToReachSoC, int soC, int minimumSoC, bool autoFullSpeedCharge)
{
var car = new Car()
{
CarState = new CarState()
{
AutoFullSpeedCharge = autoFullSpeedCharge,
SoC = soC,
},
CarConfiguration = new CarConfiguration()
{
LatestTimeToReachSoC = latestTimeToReachSoC,
MinimumSoC = minimumSoC,
ChargeMode = chargeMode,
},
};
return car;
}
}
Loading

0 comments on commit 3671f1a

Please sign in to comment.