chore: added smappee api
This commit is contained in:
36
smappee-api/ChargerOptions.cs
Normal file
36
smappee-api/ChargerOptions.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace options;
|
||||
|
||||
internal class ChargerOptions
|
||||
{
|
||||
public const string Name = "CHARGER";
|
||||
|
||||
[Required]
|
||||
[ConfigurationKeyName("URL")]
|
||||
public required Uri Url { get; init; }
|
||||
|
||||
[Required]
|
||||
[ConfigurationKeyName("CLIENT_ID")]
|
||||
public required string ClientId { get; init; }
|
||||
|
||||
[Required]
|
||||
[ConfigurationKeyName("CLIENT_SECRET")]
|
||||
public required string ClientSecret { get; init; }
|
||||
|
||||
[Required]
|
||||
[ConfigurationKeyName("USERNAME")]
|
||||
public required string UserName { get; init; }
|
||||
|
||||
[Required]
|
||||
[ConfigurationKeyName("PASSWORD")]
|
||||
public required string Password { get; init; }
|
||||
|
||||
[Required]
|
||||
[ConfigurationKeyName("STATION")]
|
||||
public required string ChargingStation { get; init; }
|
||||
|
||||
[Required]
|
||||
[ConfigurationKeyName("CONNECTOR")]
|
||||
public required int Connector { get; init; }
|
||||
}
|
||||
43
smappee-api/Program.cs
Normal file
43
smappee-api/Program.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using enums;
|
||||
using interfaces;
|
||||
using options;
|
||||
using services;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.Services.AddHttpClient();
|
||||
builder.Services.AddRedaction();
|
||||
builder.Services.AddExtendedHttpClientLogging(
|
||||
builder.Configuration.GetSection("HttpClientLogging"));
|
||||
|
||||
builder
|
||||
.Services.AddOptions<ChargerOptions>()
|
||||
.Bind(builder.Configuration.GetSection(ChargerOptions.Name))
|
||||
.ValidateDataAnnotations();
|
||||
builder.Services.AddSingleton<IBearerTokenService, SmappeeTokenService>();
|
||||
builder.Services.AddScoped<IChargerService, SmappeeChargerService>();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
app.MapPost(
|
||||
"/enable",
|
||||
async (IChargerService chargerService) =>
|
||||
{
|
||||
await chargerService.ExecuteRequest(ChargerRequests.Enable);
|
||||
return;
|
||||
}
|
||||
)
|
||||
.WithName("Enable");
|
||||
|
||||
app.MapPost(
|
||||
"/disable",
|
||||
() =>
|
||||
async (IChargerService chargerService) =>
|
||||
{
|
||||
await chargerService.ExecuteRequest(ChargerRequests.Disable);
|
||||
return;
|
||||
}
|
||||
)
|
||||
.WithName("Disable");
|
||||
|
||||
app.Run();
|
||||
23
smappee-api/Properties/launchSettings.json
Normal file
23
smappee-api/Properties/launchSettings.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": false,
|
||||
"applicationUrl": "http://localhost:5057",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": false,
|
||||
"applicationUrl": "https://localhost:7028;http://localhost:5057",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
32
smappee-api/appsettings.Development.json
Normal file
32
smappee-api/appsettings.Development.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Information"
|
||||
}
|
||||
},
|
||||
"CHARGER": {
|
||||
"URL": "https://app1pub.smappee.net",
|
||||
"CLIENT_ID": "5700002178",
|
||||
"CLIENT_SECRET": "cPcRKc26ul",
|
||||
"USERNAME": "serruysw@gmail.com",
|
||||
"PASSWORD": "uAfUhnBXkZeqs$@7ca7P3M9QE6z0Dg1N0cbz&",
|
||||
"STATION": "6230003897",
|
||||
"CONNECTOR": 1
|
||||
},
|
||||
"HttpClientLogging": {
|
||||
"LogRequestStart": false,
|
||||
"LogBody": true,
|
||||
"BodySizeLimit": 32768,
|
||||
"BodyReadTimeout": "00:00:01",
|
||||
"RequestHeadersDataClasses": {
|
||||
"User-Agent": "None",
|
||||
"Content-Type": "None"
|
||||
},
|
||||
"ResponseHeadersDataClasses": {
|
||||
"Content-Type": "None"
|
||||
},
|
||||
"RequestPathLoggingMode": "Formatted",
|
||||
"RequestPathParameterRedactionMode": "Strict"
|
||||
}
|
||||
}
|
||||
9
smappee-api/appsettings.json
Normal file
9
smappee-api/appsettings.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
7
smappee-api/enums/ChargerRequests.cs
Normal file
7
smappee-api/enums/ChargerRequests.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace enums;
|
||||
|
||||
internal enum ChargerRequests
|
||||
{
|
||||
Enable,
|
||||
Disable
|
||||
}
|
||||
5
smappee-api/interfaces/IBearerTokenService.cs
Normal file
5
smappee-api/interfaces/IBearerTokenService.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
namespace interfaces;
|
||||
|
||||
internal interface IBearerTokenService {
|
||||
ValueTask<string> GetToken(bool forceNewToken);
|
||||
}
|
||||
8
smappee-api/interfaces/IChargerService.cs
Normal file
8
smappee-api/interfaces/IChargerService.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using enums;
|
||||
|
||||
namespace interfaces;
|
||||
|
||||
internal interface IChargerService
|
||||
{
|
||||
Task ExecuteRequest(ChargerRequests request);
|
||||
}
|
||||
51
smappee-api/services/SmappeeChargerService.cs
Normal file
51
smappee-api/services/SmappeeChargerService.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using enums;
|
||||
using interfaces;
|
||||
using Microsoft.Extensions.Options;
|
||||
using options;
|
||||
|
||||
namespace services;
|
||||
|
||||
internal class SmappeeChargerService : IChargerService
|
||||
{
|
||||
private readonly ChargerOptions options;
|
||||
private readonly IBearerTokenService tokenService;
|
||||
private readonly IHttpClientFactory httpClientFactory;
|
||||
|
||||
public SmappeeChargerService(
|
||||
IOptions<ChargerOptions> options,
|
||||
IBearerTokenService tokenService,
|
||||
IHttpClientFactory httpClientFactory
|
||||
)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(tokenService);
|
||||
this.options = options?.Value ?? throw new ArgumentNullException(nameof(options));
|
||||
this.tokenService = tokenService;
|
||||
this.httpClientFactory =
|
||||
httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));
|
||||
}
|
||||
|
||||
public async Task ExecuteRequest(ChargerRequests request)
|
||||
{
|
||||
var token = await tokenService.GetToken(false);
|
||||
var httpClient = httpClientFactory.CreateClient("smappee");
|
||||
httpClient.BaseAddress = options.Url;
|
||||
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");
|
||||
var response = request switch
|
||||
{
|
||||
ChargerRequests.Enable => await httpClient.PutAsJsonAsync<SmappeeRequestBody>(
|
||||
$"/dev/v3/chargingstations/${options.ChargingStation}/connectors/{options.Connector}/mode",
|
||||
new SmappeeRequestBody("NORMAL", new Limit("AMPERE", 6))
|
||||
),
|
||||
ChargerRequests.Disable => await httpClient.PutAsJsonAsync(
|
||||
"/dev/v3/chargingstations",
|
||||
new SmappeeRequestBody("PAUSED", null)
|
||||
),
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
|
||||
record SmappeeRequestBody(string mode, Limit? limit);
|
||||
|
||||
record Limit(string unit, int value);
|
||||
}
|
||||
73
smappee-api/services/SmappeeTokenService.cs
Normal file
73
smappee-api/services/SmappeeTokenService.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using interfaces;
|
||||
using Microsoft.Extensions.Options;
|
||||
using options;
|
||||
|
||||
namespace services;
|
||||
|
||||
internal class SmappeeTokenService : IBearerTokenService
|
||||
{
|
||||
private readonly ChargerOptions options;
|
||||
private readonly IHttpClientFactory httpClientFactory;
|
||||
|
||||
private TokenData? tokenData = null;
|
||||
|
||||
public SmappeeTokenService(
|
||||
IOptions<ChargerOptions> options,
|
||||
IHttpClientFactory httpClientFactory
|
||||
)
|
||||
{
|
||||
this.options = options?.Value ?? throw new ArgumentNullException(nameof(options));
|
||||
this.httpClientFactory =
|
||||
httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));
|
||||
}
|
||||
|
||||
public async ValueTask<string> GetToken(bool forceNewToken)
|
||||
{
|
||||
if (
|
||||
tokenData == null
|
||||
|| tokenData.Expiration < DateTime.UtcNow.AddSeconds(-10)
|
||||
|| forceNewToken
|
||||
)
|
||||
{
|
||||
tokenData = await RequestNewToken();
|
||||
}
|
||||
return tokenData.Token;
|
||||
}
|
||||
|
||||
private async Task<TokenData> RequestNewToken()
|
||||
{
|
||||
var httpClient = httpClientFactory.CreateClient("smappeeTokenService");
|
||||
httpClient.BaseAddress = options.Url;
|
||||
|
||||
var result = await httpClient.PostAsJsonAsync<TokenRequestBody>(
|
||||
"/dev/v3/oauth2/token",
|
||||
new TokenRequestBody(
|
||||
"password",
|
||||
options.ClientId,
|
||||
options.ClientSecret,
|
||||
options.UserName,
|
||||
options.Password
|
||||
)
|
||||
);
|
||||
result.EnsureSuccessStatusCode();
|
||||
var tokenResponse =
|
||||
await result.Content.ReadFromJsonAsync<TokenResponse>()
|
||||
?? throw new InvalidOperationException("Token could not be fetched");
|
||||
return new TokenData(
|
||||
tokenResponse.access_token,
|
||||
DateTime.UtcNow.AddSeconds(tokenResponse.expires_in)
|
||||
);
|
||||
}
|
||||
|
||||
record TokenRequestBody(
|
||||
string grant_type,
|
||||
string client_id,
|
||||
string client_secret,
|
||||
string username,
|
||||
string password
|
||||
);
|
||||
|
||||
record TokenResponse(string access_token, int expires_in);
|
||||
|
||||
record TokenData(string Token, DateTime Expiration);
|
||||
}
|
||||
16
smappee-api/smappee-api.csproj
Normal file
16
smappee-api/smappee-api.csproj
Normal file
@@ -0,0 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<RootNamespace>smappee_api</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.10" />
|
||||
<PackageReference Include="Microsoft.Extensions.Compliance.Redaction" Version="10.9.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http.Diagnostics" Version="10.9.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
6
smappee-api/smappee-api.http
Normal file
6
smappee-api/smappee-api.http
Normal file
@@ -0,0 +1,6 @@
|
||||
@smappee_api_HostAddress = http://localhost:5057
|
||||
|
||||
GET {{smappee_api_HostAddress}}/weatherforecast/
|
||||
Accept: application/json
|
||||
|
||||
###
|
||||
Reference in New Issue
Block a user