Initial commit
This commit is contained in:
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
/.vs
|
||||||
|
/.vscode
|
||||||
|
|
||||||
|
bin/
|
||||||
|
obj/
|
||||||
|
publish/
|
||||||
|
*.user
|
||||||
29
ConsoleLog/ConsoleLog.csproj
Normal file
29
ConsoleLog/ConsoleLog.csproj
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<RootNamespace>$(SolutionName.Replace(" ", "_")).$(MSBuildProjectName.Replace(" ", "_"))</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Remove="publish\**" />
|
||||||
|
<EmbeddedResource Remove="publish\**" />
|
||||||
|
<None Remove="publish\**" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="appsettings.json">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Configuration" Version="7.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="7.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
|
||||||
|
<PackageReference Include="StackExchange.Redis" Version="2.6.70" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\DataModels\DataModels.csproj" />
|
||||||
|
<ProjectReference Include="..\Utils\Utils.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
167
ConsoleLog/Program.cs
Normal file
167
ConsoleLog/Program.cs
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using MoniteurBaie.DataModels;
|
||||||
|
using MoniteurBaie.Utils;
|
||||||
|
using StackExchange.Redis;
|
||||||
|
|
||||||
|
|
||||||
|
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
|
||||||
|
CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture;
|
||||||
|
|
||||||
|
var config = new ConfigurationBuilder()
|
||||||
|
.AddJsonFile("appsettings.json")
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var redisConfig = config.GetSection("MoniteurBaie:Redis");
|
||||||
|
var commandsChannel = redisConfig.GetValue<string>("Channels:Commands")!;
|
||||||
|
|
||||||
|
using var redis = ConnectionMultiplexer.Connect(redisConfig.GetValue<string>("Endpoint")!, opts =>
|
||||||
|
{
|
||||||
|
opts.ClientName = redisConfig.GetValue<string>("ClientName");
|
||||||
|
});
|
||||||
|
var packetChannel = redis.GetSubscriber().Subscribe(redisConfig.GetValue<string>("Channels:Packets")!);
|
||||||
|
packetChannel.OnMessage(OnPacketMessage);
|
||||||
|
|
||||||
|
redis.GetSubscriber().Subscribe(commandsChannel).OnMessage(OnCommandMessage);
|
||||||
|
|
||||||
|
var inputThread = new Thread(DoInput)
|
||||||
|
{
|
||||||
|
IsBackground = true
|
||||||
|
};
|
||||||
|
inputThread.Start();
|
||||||
|
|
||||||
|
using var cancel = new AutoResetEvent(false);
|
||||||
|
|
||||||
|
void ctrlC(object? sender, ConsoleCancelEventArgs e)
|
||||||
|
{
|
||||||
|
e.Cancel = true;
|
||||||
|
Console.CancelKeyPress -= ctrlC;
|
||||||
|
cancel.Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.CancelKeyPress += ctrlC;
|
||||||
|
|
||||||
|
cancel.WaitOne();
|
||||||
|
|
||||||
|
|
||||||
|
static void OnPacketMessage(ChannelMessage channelMessage)
|
||||||
|
{
|
||||||
|
var message = channelMessage.Message.ToString();
|
||||||
|
|
||||||
|
Console.WriteLine("<< " + message);
|
||||||
|
|
||||||
|
PrintPacket(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void OnCommandMessage(ChannelMessage channelMessage)
|
||||||
|
{
|
||||||
|
Console.WriteLine("<< " + channelMessage.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void PrintPacket(string serializedPacket)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
|
BatteryControllerPacket? batCtrlPacket;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
batCtrlPacket = JsonSerializer.Deserialize(serializedPacket, BatteryControllerPacketContext.Default.BatteryControllerPacket);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine("Invalid packet.");
|
||||||
|
Console.Error.WriteLine(ex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (batCtrlPacket is null)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine("Null packet.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SerialDataPacket packet;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
packet = PacketParser.ParseSerialDataPacket(batCtrlPacket.SerialData);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine("Invalid data packet.");
|
||||||
|
Console.Error.WriteLine(ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.AppendLine($"==== [{batCtrlPacket.Timestamp:yyyy/MM/dd HH:mm:ss)}] ====");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine("-- Tensions --");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine($"{packet.VBat:F2} // {packet.VB1:F2} / {packet.VB2:F2} / {packet.VB3:F2} / {packet.VB4:F2} / {packet.VB5:F2} / {packet.VB6:F2}");
|
||||||
|
sb.AppendLine($"Min : {packet.VBmin:F2}");
|
||||||
|
sb.AppendLine($"Max : {packet.VBmax:F2}");
|
||||||
|
sb.AppendLine($"Diff : {packet.VBmax - packet.VBmin:F2}");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine("-- Puissance --");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine($"Courant : {packet.Curr:F2}");
|
||||||
|
sb.AppendLine($"Puissance : {packet.Power:F2}");
|
||||||
|
sb.AppendLine($"Energie : {packet.Energy:F2}");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine("-- Températures --");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine($"Alimentation : {packet.Temp_alim:F2}");
|
||||||
|
sb.AppendLine($"Chargeur : {packet.Temp_cha:F2}");
|
||||||
|
sb.AppendLine($"Batterie : {packet.Temp_bat:F2}");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine("-- Relais --");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine($"1000W : {packet.S_PowRelay.ToAnsi()}");
|
||||||
|
sb.AppendLine($"120W : {packet.S_ChaRelay.ToAnsi()}");
|
||||||
|
sb.AppendLine($"Batterie-carte : {packet.S_BatRelay1.ToAnsi()}");
|
||||||
|
sb.AppendLine($"Batterie-puissance : {packet.S_BatRelay_State.ToAnsi()}");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine("-- Défauts --");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine($"Défaut général : {packet.DF_GENERAL.ToAnsi()}");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine($"Température alim : {packet.DF_TEMP_ALIM.ToAnsi()} / {packet.MEM_DF_TEMP_ALIM.ToAnsi()}");
|
||||||
|
sb.AppendLine($"Température batterie : {packet.DF_TEMP_BAT.ToAnsi()} / {packet.MEM_DF_TEMP_BAT.ToAnsi()}");
|
||||||
|
sb.AppendLine($"Température chargeur : {packet.DF_TEMP_CHA.ToAnsi()} / {packet.MEM_DF_TEMP_CHA.ToAnsi()}");
|
||||||
|
sb.AppendLine($"Mesure tension incohérente : {packet.DF_V_INCOHERENT.ToAnsi()}");
|
||||||
|
sb.AppendLine($"Mauvaise cellule : {packet.DF_BAD_CELL.ToAnsi()}");
|
||||||
|
sb.AppendLine($"Surintensité : {packet.DF_OVERCURRENT.ToAnsi()}");
|
||||||
|
sb.AppendLine($"Surintensité critique : {packet.DF_OVERCURRENT_STOP.ToAnsi()}");
|
||||||
|
sb.AppendLine($"Stop général : {packet.DF_STOP_GENERAL.ToAnsi()}");
|
||||||
|
sb.AppendLine($"Surtension cellule : {packet.DF_CELL_OVERVOLTAGE.ToAnsi()} / {packet.MEM_DF_CELL_OVERVOLTAGE.ToAnsi()}");
|
||||||
|
sb.AppendLine($"Equilibrage : {packet.DF_UNBALANCE.ToAnsi()} / {packet.MEM_DF_UNBALANCE.ToAnsi()}");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine("-- Informations --");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine($"Décharge : {packet.DECHARGE.ToAnsi()} / {packet.MEM_DECHARGE.ToAnsi()}");
|
||||||
|
sb.AppendLine($"Décharge forcée : {packet.Flag_decharge.ToAnsi()}");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine($"Buzzer stop : {packet.Buzzer_stop.ToAnsi()}");
|
||||||
|
sb.AppendLine($"Compteur demande coupure batterie : {packet.Compteur_demande_coupure_batterie}");
|
||||||
|
sb.AppendLine($"Compteur demande coupure totale : {packet.Compteur_demande_coupure_totale}");
|
||||||
|
|
||||||
|
Console.WriteLine(sb.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
void DoInput()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var line = Console.ReadLine();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(line))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine(">> " + line);
|
||||||
|
|
||||||
|
redis.GetSubscriber().Publish(commandsChannel, line);
|
||||||
|
}
|
||||||
|
}
|
||||||
16
ConsoleLog/Properties/PublishProfiles/RaspiPublish.pubxml
Normal file
16
ConsoleLog/Properties/PublishProfiles/RaspiPublish.pubxml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||||
|
-->
|
||||||
|
<Project>
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
|
<Platform>Any CPU</Platform>
|
||||||
|
<PublishDir>publish\</PublishDir>
|
||||||
|
<PublishProtocol>FileSystem</PublishProtocol>
|
||||||
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
|
<RuntimeIdentifier>linux-arm64</RuntimeIdentifier>
|
||||||
|
<SelfContained>false</SelfContained>
|
||||||
|
<PublishSingleFile>true</PublishSingleFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
12
ConsoleLog/appsettings.json
Normal file
12
ConsoleLog/appsettings.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"MoniteurBaie": {
|
||||||
|
"Redis": {
|
||||||
|
"Endpoint": "mercedes.hbsha.re:6379",
|
||||||
|
"ClientName": "Console",
|
||||||
|
"Channels": {
|
||||||
|
"Packets": "batCtrlPackets",
|
||||||
|
"Commands": "batCtrlCommands"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
DataApi/Common/Repositories/BaseMariaDbRepository.cs
Normal file
19
DataApi/Common/Repositories/BaseMariaDbRepository.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
using MySqlConnector;
|
||||||
|
|
||||||
|
namespace MoniteurBaie.DataApi.Common.Repositories;
|
||||||
|
|
||||||
|
public class BaseMariaDbRepository
|
||||||
|
{
|
||||||
|
private readonly string _connectionString;
|
||||||
|
|
||||||
|
protected BaseMariaDbRepository(IConfiguration config, string connectionStringName) : this(config.GetConnectionString(connectionStringName)!) { }
|
||||||
|
|
||||||
|
protected BaseMariaDbRepository(string connectionString) => _connectionString = connectionString;
|
||||||
|
|
||||||
|
protected async Task<MySqlConnection> NewConnectionAsync()
|
||||||
|
{
|
||||||
|
var connection = new MySqlConnection(_connectionString);
|
||||||
|
await connection.OpenAsync();
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
}
|
||||||
59
DataApi/Controllers/DataPacketsController.cs
Normal file
59
DataApi/Controllers/DataPacketsController.cs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using MoniteurBaie.DataApi.Interfaces.Repositories;
|
||||||
|
using MoniteurBaie.DataModels;
|
||||||
|
|
||||||
|
namespace MoniteurBaie.DataApi.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("packets")]
|
||||||
|
public class DataPacketsController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly ILogger<DataPacketsController> _logger;
|
||||||
|
private readonly IDataRepository _dataRepository;
|
||||||
|
|
||||||
|
public DataPacketsController(ILogger<DataPacketsController> logger, IDataRepository dataRepository)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_dataRepository = dataRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{id}", Name = "GetDataPacket")]
|
||||||
|
public async Task<IActionResult> GetAsync(uint id)
|
||||||
|
{
|
||||||
|
var packet = await _dataRepository.GetAsync(id);
|
||||||
|
return packet is null ? NotFound() : Ok(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost(Name = "CreateDataPacket")]
|
||||||
|
public async Task<IActionResult> CreateAsync(DataPacket packet)
|
||||||
|
{
|
||||||
|
var packetId = await _dataRepository.CreateAsync(packet);
|
||||||
|
return CreatedAtRoute("GetDataPacket", new { id = packetId }, packet with { Id = packetId });
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete("{id}", Name = "DeleteDataPacket")]
|
||||||
|
public async Task<IActionResult> DeleteAsync(uint id)
|
||||||
|
{
|
||||||
|
var success = await _dataRepository.DeleteAsync(id);
|
||||||
|
return success ? NoContent() : NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("last", Name = "GetLastDataPackets")]
|
||||||
|
public IActionResult GetLastAsync(int count)
|
||||||
|
{
|
||||||
|
return Ok(_dataRepository.GetLastAsync(count));
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("range", Name = "GetRangeDataPackets")]
|
||||||
|
public IActionResult GetRangeAsync(DateTime fromInstant, DateTime toInstant)
|
||||||
|
{
|
||||||
|
return Ok(_dataRepository.GetRangeAsync(fromInstant, toInstant));
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("bulk", Name = "BulkCreateDataPackets")]
|
||||||
|
public async Task<IActionResult> BulkCreateAsync(IEnumerable<DataPacket> packets)
|
||||||
|
{
|
||||||
|
await _dataRepository.BulkCreateAsync(packets);
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
28
DataApi/DataApi.csproj
Normal file
28
DataApi/DataApi.csproj
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<RootNamespace>$(SolutionName.Replace(" ", "_")).$(MSBuildProjectName.Replace(" ", "_"))</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Remove="publish\**" />
|
||||||
|
<Content Remove="publish\**" />
|
||||||
|
<EmbeddedResource Remove="publish\**" />
|
||||||
|
<None Remove="publish\**" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Dapper" Version="2.0.123" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="7.0.0" />
|
||||||
|
<PackageReference Include="MySqlConnector" Version="2.2.0" />
|
||||||
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\DataModels\DataModels.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
18
DataApi/Interfaces/Repositories/IDataRepository.cs
Normal file
18
DataApi/Interfaces/Repositories/IDataRepository.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using MoniteurBaie.DataModels;
|
||||||
|
|
||||||
|
namespace MoniteurBaie.DataApi.Interfaces.Repositories;
|
||||||
|
|
||||||
|
public interface IDataRepository
|
||||||
|
{
|
||||||
|
Task<DataPacket?> GetAsync(uint id);
|
||||||
|
|
||||||
|
Task<uint> CreateAsync(DataPacket packet);
|
||||||
|
|
||||||
|
IAsyncEnumerable<DataPacket> GetRangeAsync(DateTime fromInstant, DateTime toInstant);
|
||||||
|
|
||||||
|
IAsyncEnumerable<DataPacket> GetLastAsync(int count);
|
||||||
|
|
||||||
|
Task<bool> DeleteAsync(uint id);
|
||||||
|
|
||||||
|
Task BulkCreateAsync(IEnumerable<DataPacket> packets);
|
||||||
|
}
|
||||||
30
DataApi/Program.cs
Normal file
30
DataApi/Program.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
using MoniteurBaie.DataApi.Interfaces.Repositories;
|
||||||
|
using MoniteurBaie.DataApi.Repositories;
|
||||||
|
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
builder.Host.UseSystemd();
|
||||||
|
|
||||||
|
// Add services to the container.
|
||||||
|
|
||||||
|
builder.Services.AddControllers();
|
||||||
|
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
||||||
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
|
builder.Services.AddSwaggerGen();
|
||||||
|
|
||||||
|
builder.Services.AddSingleton<IDataRepository, DataRepository>();
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
// Configure the HTTP request pipeline.
|
||||||
|
if (app.Environment.IsDevelopment())
|
||||||
|
{
|
||||||
|
app.UseSwagger();
|
||||||
|
app.UseSwaggerUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
app.UseAuthorization();
|
||||||
|
|
||||||
|
app.MapControllers();
|
||||||
|
|
||||||
|
app.Run();
|
||||||
22
DataApi/Properties/PublishProfiles/FolderPublish.pubxml
Normal file
22
DataApi/Properties/PublishProfiles/FolderPublish.pubxml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||||
|
-->
|
||||||
|
<Project>
|
||||||
|
<PropertyGroup>
|
||||||
|
<DeleteExistingFiles>true</DeleteExistingFiles>
|
||||||
|
<ExcludeApp_Data>false</ExcludeApp_Data>
|
||||||
|
<LaunchSiteAfterPublish>true</LaunchSiteAfterPublish>
|
||||||
|
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
|
||||||
|
<LastUsedPlatform>Any CPU</LastUsedPlatform>
|
||||||
|
<PublishProvider>FileSystem</PublishProvider>
|
||||||
|
<PublishUrl>publish\</PublishUrl>
|
||||||
|
<WebPublishMethod>FileSystem</WebPublishMethod>
|
||||||
|
<SiteUrlToLaunchAfterPublish />
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
|
||||||
|
<PublishSingleFile>true</PublishSingleFile>
|
||||||
|
<ProjectGuid>12a93e6e-00cb-4447-b431-64ebfe8ee566</ProjectGuid>
|
||||||
|
<SelfContained>false</SelfContained>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
31
DataApi/Properties/launchSettings.json
Normal file
31
DataApi/Properties/launchSettings.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||||
|
"iisSettings": {
|
||||||
|
"windowsAuthentication": false,
|
||||||
|
"anonymousAuthentication": true,
|
||||||
|
"iisExpress": {
|
||||||
|
"applicationUrl": "http://localhost:56142",
|
||||||
|
"sslPort": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"profiles": {
|
||||||
|
"DataAccess": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "swagger",
|
||||||
|
"applicationUrl": "http://localhost:5256",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"IIS Express": {
|
||||||
|
"commandName": "IISExpress",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "swagger",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
246
DataApi/Repositories/DataRepository.cs
Normal file
246
DataApi/Repositories/DataRepository.cs
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
using MoniteurBaie.DataApi.Common.Repositories;
|
||||||
|
using MoniteurBaie.DataApi.Interfaces.Repositories;
|
||||||
|
using MoniteurBaie.DataModels;
|
||||||
|
using MySqlConnector;
|
||||||
|
|
||||||
|
namespace MoniteurBaie.DataApi.Repositories;
|
||||||
|
|
||||||
|
public class DataRepository : BaseMariaDbRepository, IDataRepository
|
||||||
|
{
|
||||||
|
public DataRepository(IConfiguration config) : base(config, "MariaDb")
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DataPacket Map(MySqlDataReader reader) => new(
|
||||||
|
reader.GetUInt32(0),
|
||||||
|
reader.GetDateTime(1),
|
||||||
|
reader.GetFloat(2),
|
||||||
|
reader.GetFloat(3),
|
||||||
|
reader.GetFloat(4),
|
||||||
|
reader.GetFloat(5),
|
||||||
|
reader.GetFloat(6),
|
||||||
|
reader.GetFloat(7),
|
||||||
|
reader.GetFloat(8),
|
||||||
|
reader.GetFloat(9),
|
||||||
|
reader.GetFloat(10),
|
||||||
|
reader.GetFloat(11),
|
||||||
|
reader.GetFloat(12),
|
||||||
|
reader.GetFloat(13),
|
||||||
|
reader.GetFloat(14),
|
||||||
|
reader.GetFloat(15),
|
||||||
|
reader.GetFloat(16),
|
||||||
|
reader.GetBoolean(17),
|
||||||
|
reader.GetBoolean(18),
|
||||||
|
reader.GetBoolean(19),
|
||||||
|
reader.GetBoolean(20),
|
||||||
|
reader.GetBoolean(21),
|
||||||
|
reader.GetBoolean(22),
|
||||||
|
reader.GetBoolean(23),
|
||||||
|
reader.GetBoolean(24),
|
||||||
|
reader.GetBoolean(25),
|
||||||
|
reader.GetBoolean(26),
|
||||||
|
reader.GetBoolean(27),
|
||||||
|
reader.GetBoolean(28),
|
||||||
|
reader.GetBoolean(29),
|
||||||
|
reader.GetBoolean(30),
|
||||||
|
reader.GetBoolean(31),
|
||||||
|
reader.GetBoolean(32),
|
||||||
|
reader.GetBoolean(33),
|
||||||
|
reader.GetBoolean(34),
|
||||||
|
reader.GetBoolean(35),
|
||||||
|
reader.GetUInt32(36),
|
||||||
|
reader.GetUInt32(37),
|
||||||
|
reader.GetBoolean(38),
|
||||||
|
reader.GetBoolean(39),
|
||||||
|
reader.GetBoolean(40),
|
||||||
|
reader.GetBoolean(41),
|
||||||
|
reader.GetBoolean(42));
|
||||||
|
|
||||||
|
public async Task<DataPacket?> GetAsync(uint id)
|
||||||
|
{
|
||||||
|
await using var connection = await NewConnectionAsync();
|
||||||
|
|
||||||
|
await using var command = connection.CreateCommand();
|
||||||
|
command.CommandText = "SELECT * FROM packets WHERE id = @id";
|
||||||
|
command.Parameters.AddWithValue("@id", id);
|
||||||
|
|
||||||
|
await using var reader = await command.ExecuteReaderAsync();
|
||||||
|
|
||||||
|
return await reader.ReadAsync() ? Map(reader) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async IAsyncEnumerable<DataPacket> GetRangeAsync(DateTime fromInstant, DateTime toInstant)
|
||||||
|
{
|
||||||
|
await using var connection = await NewConnectionAsync();
|
||||||
|
|
||||||
|
await using var command = connection.CreateCommand();
|
||||||
|
command.CommandText = "SELECT * FROM packets WHERE timestamp BETWEEN @from AND @to ORDER BY timestamp DESC";
|
||||||
|
command.Parameters.AddWithValue("@from", fromInstant);
|
||||||
|
command.Parameters.AddWithValue("@to", toInstant);
|
||||||
|
|
||||||
|
await using var reader = await command.ExecuteReaderAsync();
|
||||||
|
|
||||||
|
while (await reader.ReadAsync())
|
||||||
|
{
|
||||||
|
yield return Map(reader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async IAsyncEnumerable<DataPacket> GetLastAsync(int count)
|
||||||
|
{
|
||||||
|
await using var connection = await NewConnectionAsync();
|
||||||
|
|
||||||
|
await using var command = connection.CreateCommand();
|
||||||
|
command.CommandText = "SELECT * FROM packets ORDER BY timestamp DESC FETCH FIRST @count ROWS ONLY";
|
||||||
|
command.Parameters.AddWithValue("@count", count);
|
||||||
|
|
||||||
|
await using var reader = await command.ExecuteReaderAsync();
|
||||||
|
|
||||||
|
while (await reader.ReadAsync())
|
||||||
|
{
|
||||||
|
yield return Map(reader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<uint> CreateAsync(DataPacket packet)
|
||||||
|
{
|
||||||
|
await using var connection = await NewConnectionAsync();
|
||||||
|
|
||||||
|
await using var command = connection.CreateCommand();
|
||||||
|
|
||||||
|
command.CommandText = "INSERT INTO packets (timestamp, vb1, vb2, vb3, vb4, vb5, vb6, vbat, vb_min, vb_max, current, power, energy, temp_alim, temp_cha, temp_bat, df_temp_alim, mem_df_temp_alim, df_temp_bat, mem_df_temp_bat, df_temp_cha, mem_df_temp_cha, df_v_incoherent, df_bad_cell, df_overcurrent, df_overcurrent_stop, df_stop_general, df_general, df_cell_overvoltage, mem_df_cell_overvoltage, df_unbalance, mem_df_unbalance, buzzer_stop, decharge, mem_decharge, compteur_demande_coupure_batterie, compteur_demande_coupure_totale, s_powrelay, s_charelay, s_batrelay1, s_batrelay_state, flag_decharge) VALUES (@timestamp, @vb1, @vb2, @vb3, @vb4, @vb5, @vb6, @vbat, @vb_min, @vb_max, @current, @power, @energy, @temp_alim, @temp_cha, @temp_bat, @df_temp_alim, @mem_df_temp_alim, @df_temp_bat, @mem_df_temp_bat, @df_temp_cha, @mem_df_temp_cha, @df_v_incoherent, @df_bad_cell, @df_overcurrent, @df_overcurrent_stop, @df_stop_general, @df_general, @df_cell_overvoltage, @mem_df_cell_overvoltage, @df_unbalance, @mem_df_unbalance, @buzzer_stop, @decharge, @mem_decharge, @compteur_demande_coupure_batterie, @compteur_demande_coupure_totale, @s_powrelay, @s_charelay, @s_batrelay1, @s_batrelay_state, @flag_decharge)";
|
||||||
|
|
||||||
|
command.Parameters.AddWithValue("@timestamp", packet.Timestamp);
|
||||||
|
command.Parameters.AddWithValue("@vb1", packet.VB1);
|
||||||
|
command.Parameters.AddWithValue("@vb2", packet.VB2);
|
||||||
|
command.Parameters.AddWithValue("@vb3", packet.VB3);
|
||||||
|
command.Parameters.AddWithValue("@vb4", packet.VB4);
|
||||||
|
command.Parameters.AddWithValue("@vb5", packet.VB5);
|
||||||
|
command.Parameters.AddWithValue("@vb6", packet.VB6);
|
||||||
|
command.Parameters.AddWithValue("@vbat", packet.VBat);
|
||||||
|
command.Parameters.AddWithValue("@vb_min", packet.VBmin);
|
||||||
|
command.Parameters.AddWithValue("@vb_max", packet.VBmax);
|
||||||
|
command.Parameters.AddWithValue("@current", packet.Curr);
|
||||||
|
command.Parameters.AddWithValue("@power", packet.Power);
|
||||||
|
command.Parameters.AddWithValue("@energy", packet.Energy);
|
||||||
|
command.Parameters.AddWithValue("@temp_alim", packet.Temp_alim);
|
||||||
|
command.Parameters.AddWithValue("@temp_cha", packet.Temp_cha);
|
||||||
|
command.Parameters.AddWithValue("@temp_bat", packet.Temp_bat);
|
||||||
|
command.Parameters.AddWithValue("@df_temp_alim", packet.DF_TEMP_ALIM);
|
||||||
|
command.Parameters.AddWithValue("@mem_df_temp_alim", packet.MEM_DF_TEMP_ALIM);
|
||||||
|
command.Parameters.AddWithValue("@df_temp_bat", packet.DF_TEMP_BAT);
|
||||||
|
command.Parameters.AddWithValue("@mem_df_temp_bat", packet.MEM_DF_TEMP_BAT);
|
||||||
|
command.Parameters.AddWithValue("@df_temp_cha", packet.DF_TEMP_CHA);
|
||||||
|
command.Parameters.AddWithValue("@mem_df_temp_cha", packet.MEM_DF_TEMP_CHA);
|
||||||
|
command.Parameters.AddWithValue("@df_v_incoherent", packet.DF_V_INCOHERENT);
|
||||||
|
command.Parameters.AddWithValue("@df_bad_cell", packet.DF_BAD_CELL);
|
||||||
|
command.Parameters.AddWithValue("@df_overcurrent", packet.DF_OVERCURRENT);
|
||||||
|
command.Parameters.AddWithValue("@df_overcurrent_stop", packet.DF_OVERCURRENT_STOP);
|
||||||
|
command.Parameters.AddWithValue("@df_stop_general", packet.DF_STOP_GENERAL);
|
||||||
|
command.Parameters.AddWithValue("@df_general", packet.DF_GENERAL);
|
||||||
|
command.Parameters.AddWithValue("@df_cell_overvoltage", packet.DF_CELL_OVERVOLTAGE);
|
||||||
|
command.Parameters.AddWithValue("@mem_df_cell_overvoltage", packet.MEM_DF_CELL_OVERVOLTAGE);
|
||||||
|
command.Parameters.AddWithValue("@df_unbalance", packet.DF_UNBALANCE);
|
||||||
|
command.Parameters.AddWithValue("@mem_df_unbalance", packet.MEM_DF_UNBALANCE);
|
||||||
|
command.Parameters.AddWithValue("@buzzer_stop", packet.Buzzer_stop);
|
||||||
|
command.Parameters.AddWithValue("@decharge", packet.DECHARGE);
|
||||||
|
command.Parameters.AddWithValue("@mem_decharge", packet.MEM_DECHARGE);
|
||||||
|
command.Parameters.AddWithValue("@compteur_demande_coupure_batterie", packet.Compteur_demande_coupure_batterie);
|
||||||
|
command.Parameters.AddWithValue("@compteur_demande_coupure_totale", packet.Compteur_demande_coupure_totale);
|
||||||
|
command.Parameters.AddWithValue("@s_powrelay", packet.S_PowRelay);
|
||||||
|
command.Parameters.AddWithValue("@s_charelay", packet.S_ChaRelay);
|
||||||
|
command.Parameters.AddWithValue("@s_batrelay1", packet.S_BatRelay1);
|
||||||
|
command.Parameters.AddWithValue("@s_batrelay_state", packet.S_BatRelay_State);
|
||||||
|
command.Parameters.AddWithValue("@flag_decharge", packet.Flag_decharge);
|
||||||
|
|
||||||
|
var rowCount = await command.ExecuteNonQueryAsync();
|
||||||
|
|
||||||
|
return rowCount > 0 ? (uint)command.LastInsertedId : 0u;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> DeleteAsync(uint id)
|
||||||
|
{
|
||||||
|
await using var connection = await NewConnectionAsync();
|
||||||
|
|
||||||
|
await using var command = connection.CreateCommand();
|
||||||
|
command.CommandText = "DELETE FROM packets WHERE id = @id";
|
||||||
|
command.Parameters.AddWithValue("@id", id);
|
||||||
|
|
||||||
|
var result = await command.ExecuteNonQueryAsync();
|
||||||
|
|
||||||
|
return result > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task BulkCreateAsync(IEnumerable<DataPacket> packets)
|
||||||
|
{
|
||||||
|
await using var connection = await NewConnectionAsync();
|
||||||
|
|
||||||
|
await using var transaction = await connection.BeginTransactionAsync();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var command = connection.CreateCommand();
|
||||||
|
|
||||||
|
command.Transaction = transaction;
|
||||||
|
command.CommandText = "INSERT INTO packets (timestamp, vb1, vb2, vb3, vb4, vb5, vb6, vbat, vb_min, vb_max, current, power, energy, temp_alim, temp_cha, temp_bat, df_temp_alim, mem_df_temp_alim, df_temp_bat, mem_df_temp_bat, df_temp_cha, mem_df_temp_cha, df_v_incoherent, df_bad_cell, df_overcurrent, df_overcurrent_stop, df_stop_general, df_general, df_cell_overvoltage, mem_df_cell_overvoltage, df_unbalance, mem_df_unbalance, buzzer_stop, decharge, mem_decharge, compteur_demande_coupure_batterie, compteur_demande_coupure_totale, s_powrelay, s_charelay, s_batrelay1, s_batrelay_state, flag_decharge) VALUES (@timestamp, @vb1, @vb2, @vb3, @vb4, @vb5, @vb6, @vbat, @vb_min, @vb_max, @current, @power, @energy, @temp_alim, @temp_cha, @temp_bat, @df_temp_alim, @mem_df_temp_alim, @df_temp_bat, @mem_df_temp_bat, @df_temp_cha, @mem_df_temp_cha, @df_v_incoherent, @df_bad_cell, @df_overcurrent, @df_overcurrent_stop, @df_stop_general, @df_general, @df_cell_overvoltage, @mem_df_cell_overvoltage, @df_unbalance, @mem_df_unbalance, @buzzer_stop, @decharge, @mem_decharge, @compteur_demande_coupure_batterie, @compteur_demande_coupure_totale, @s_powrelay, @s_charelay, @s_batrelay1, @s_batrelay_state, @flag_decharge)";
|
||||||
|
|
||||||
|
foreach (var packet in packets)
|
||||||
|
{
|
||||||
|
command.Parameters.AddWithValue("@timestamp", packet.Timestamp);
|
||||||
|
command.Parameters.AddWithValue("@vb1", packet.VB1);
|
||||||
|
command.Parameters.AddWithValue("@vb2", packet.VB2);
|
||||||
|
command.Parameters.AddWithValue("@vb3", packet.VB3);
|
||||||
|
command.Parameters.AddWithValue("@vb4", packet.VB4);
|
||||||
|
command.Parameters.AddWithValue("@vb5", packet.VB5);
|
||||||
|
command.Parameters.AddWithValue("@vb6", packet.VB6);
|
||||||
|
command.Parameters.AddWithValue("@vbat", packet.VBat);
|
||||||
|
command.Parameters.AddWithValue("@vb_min", packet.VBmin);
|
||||||
|
command.Parameters.AddWithValue("@vb_max", packet.VBmax);
|
||||||
|
command.Parameters.AddWithValue("@current", packet.Curr);
|
||||||
|
command.Parameters.AddWithValue("@power", packet.Power);
|
||||||
|
command.Parameters.AddWithValue("@energy", packet.Energy);
|
||||||
|
command.Parameters.AddWithValue("@temp_alim", packet.Temp_alim);
|
||||||
|
command.Parameters.AddWithValue("@temp_cha", packet.Temp_cha);
|
||||||
|
command.Parameters.AddWithValue("@temp_bat", packet.Temp_bat);
|
||||||
|
command.Parameters.AddWithValue("@df_temp_alim", packet.DF_TEMP_ALIM);
|
||||||
|
command.Parameters.AddWithValue("@mem_df_temp_alim", packet.MEM_DF_TEMP_ALIM);
|
||||||
|
command.Parameters.AddWithValue("@df_temp_bat", packet.DF_TEMP_BAT);
|
||||||
|
command.Parameters.AddWithValue("@mem_df_temp_bat", packet.MEM_DF_TEMP_BAT);
|
||||||
|
command.Parameters.AddWithValue("@df_temp_cha", packet.DF_TEMP_CHA);
|
||||||
|
command.Parameters.AddWithValue("@mem_df_temp_cha", packet.MEM_DF_TEMP_CHA);
|
||||||
|
command.Parameters.AddWithValue("@df_v_incoherent", packet.DF_V_INCOHERENT);
|
||||||
|
command.Parameters.AddWithValue("@df_bad_cell", packet.DF_BAD_CELL);
|
||||||
|
command.Parameters.AddWithValue("@df_overcurrent", packet.DF_OVERCURRENT);
|
||||||
|
command.Parameters.AddWithValue("@df_overcurrent_stop", packet.DF_OVERCURRENT_STOP);
|
||||||
|
command.Parameters.AddWithValue("@df_stop_general", packet.DF_STOP_GENERAL);
|
||||||
|
command.Parameters.AddWithValue("@df_general", packet.DF_GENERAL);
|
||||||
|
command.Parameters.AddWithValue("@df_cell_overvoltage", packet.DF_CELL_OVERVOLTAGE);
|
||||||
|
command.Parameters.AddWithValue("@mem_df_cell_overvoltage", packet.MEM_DF_CELL_OVERVOLTAGE);
|
||||||
|
command.Parameters.AddWithValue("@df_unbalance", packet.DF_UNBALANCE);
|
||||||
|
command.Parameters.AddWithValue("@mem_df_unbalance", packet.MEM_DF_UNBALANCE);
|
||||||
|
command.Parameters.AddWithValue("@buzzer_stop", packet.Buzzer_stop);
|
||||||
|
command.Parameters.AddWithValue("@decharge", packet.DECHARGE);
|
||||||
|
command.Parameters.AddWithValue("@mem_decharge", packet.MEM_DECHARGE);
|
||||||
|
command.Parameters.AddWithValue("@compteur_demande_coupure_batterie", packet.Compteur_demande_coupure_batterie);
|
||||||
|
command.Parameters.AddWithValue("@compteur_demande_coupure_totale", packet.Compteur_demande_coupure_totale);
|
||||||
|
command.Parameters.AddWithValue("@s_powrelay", packet.S_PowRelay);
|
||||||
|
command.Parameters.AddWithValue("@s_charelay", packet.S_ChaRelay);
|
||||||
|
command.Parameters.AddWithValue("@s_batrelay1", packet.S_BatRelay1);
|
||||||
|
command.Parameters.AddWithValue("@s_batrelay_state", packet.S_BatRelay_State);
|
||||||
|
command.Parameters.AddWithValue("@flag_decharge", packet.Flag_decharge);
|
||||||
|
|
||||||
|
await command.ExecuteNonQueryAsync();
|
||||||
|
|
||||||
|
command.Parameters.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
await transaction.RollbackAsync();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
await transaction.CommitAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
8
DataApi/appsettings.Development.json
Normal file
8
DataApi/appsettings.Development.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
DataApi/appsettings.json
Normal file
12
DataApi/appsettings.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*",
|
||||||
|
"ConnectionStrings": {
|
||||||
|
"MariaDb": "Server=mercedes.hbsha.re;Port=3306;User=moniteurbaie;Password=M0ñîT€urB@1E;Database=moniteurbaie;Pooling=True;ApplicationName=MoniteurBaie"
|
||||||
|
}
|
||||||
|
}
|
||||||
24
DataModels/BatteryControllerPacket.cs
Normal file
24
DataModels/BatteryControllerPacket.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace MoniteurBaie.DataModels;
|
||||||
|
|
||||||
|
public class BatteryControllerPacket
|
||||||
|
{
|
||||||
|
public BatteryControllerPacket(string serialData) : this(DateTime.Now, serialData) { }
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
|
public BatteryControllerPacket(DateTime timestamp, string serialData)
|
||||||
|
{
|
||||||
|
Timestamp = timestamp;
|
||||||
|
SerialData = serialData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTime Timestamp { get; private set; }
|
||||||
|
|
||||||
|
public string SerialData { get; private set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonSerializable(typeof(BatteryControllerPacket))]
|
||||||
|
public partial class BatteryControllerPacketContext : JsonSerializerContext
|
||||||
|
{
|
||||||
|
}
|
||||||
14
DataModels/DataModels.csproj
Normal file
14
DataModels/DataModels.csproj
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<RootNamespace>$(SolutionName.Replace(" ", "_")).$(MSBuildProjectName.Replace(" ", "_"))</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Utils\Utils.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
56
DataModels/DataPacket.cs
Normal file
56
DataModels/DataPacket.cs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace MoniteurBaie.DataModels;
|
||||||
|
|
||||||
|
public record DataPacket(
|
||||||
|
uint Id,
|
||||||
|
DateTime Timestamp,
|
||||||
|
float VB1,
|
||||||
|
float VB2,
|
||||||
|
float VB3,
|
||||||
|
float VB4,
|
||||||
|
float VB5,
|
||||||
|
float VB6,
|
||||||
|
float VBat,
|
||||||
|
float VBmin,
|
||||||
|
float VBmax,
|
||||||
|
float Curr,
|
||||||
|
float Power,
|
||||||
|
float Energy,
|
||||||
|
float Temp_alim,
|
||||||
|
float Temp_cha,
|
||||||
|
float Temp_bat,
|
||||||
|
bool DF_TEMP_ALIM,
|
||||||
|
bool MEM_DF_TEMP_ALIM,
|
||||||
|
bool DF_TEMP_BAT,
|
||||||
|
bool MEM_DF_TEMP_BAT,
|
||||||
|
bool DF_TEMP_CHA,
|
||||||
|
bool MEM_DF_TEMP_CHA,
|
||||||
|
bool DF_V_INCOHERENT,
|
||||||
|
bool DF_BAD_CELL,
|
||||||
|
bool DF_OVERCURRENT,
|
||||||
|
bool DF_OVERCURRENT_STOP,
|
||||||
|
bool DF_STOP_GENERAL,
|
||||||
|
bool DF_GENERAL,
|
||||||
|
bool DF_CELL_OVERVOLTAGE,
|
||||||
|
bool MEM_DF_CELL_OVERVOLTAGE,
|
||||||
|
bool DF_UNBALANCE,
|
||||||
|
bool MEM_DF_UNBALANCE,
|
||||||
|
bool Buzzer_stop,
|
||||||
|
bool DECHARGE,
|
||||||
|
bool MEM_DECHARGE,
|
||||||
|
uint Compteur_demande_coupure_batterie,
|
||||||
|
uint Compteur_demande_coupure_totale,
|
||||||
|
bool S_PowRelay,
|
||||||
|
bool S_ChaRelay,
|
||||||
|
bool S_BatRelay1,
|
||||||
|
bool S_BatRelay_State,
|
||||||
|
bool Flag_decharge)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonSerializable(typeof(DataPacket))]
|
||||||
|
public partial class DataPacketContext : JsonSerializerContext
|
||||||
|
{
|
||||||
|
}
|
||||||
6
DataModels/Extensions.cs
Normal file
6
DataModels/Extensions.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace MoniteurBaie.DataModels;
|
||||||
|
|
||||||
|
public static class Extensions
|
||||||
|
{
|
||||||
|
public static DataPacket ToDataPacket(this SerialDataPacket packet, DateTime timestamp) => new(default, timestamp, packet.VB1, packet.VB2, packet.VB3, packet.VB4, packet.VB5, packet.VB6, packet.VBat, packet.VBmin, packet.VBmax, packet.Curr, packet.Power, packet.Energy, packet.Temp_alim, packet.Temp_cha, packet.Temp_bat, packet.DF_TEMP_ALIM, packet.MEM_DF_TEMP_ALIM, packet.DF_TEMP_BAT, packet.MEM_DF_TEMP_BAT, packet.DF_TEMP_CHA, packet.MEM_DF_TEMP_CHA, packet.DF_V_INCOHERENT, packet.DF_BAD_CELL, packet.DF_OVERCURRENT, packet.DF_OVERCURRENT_STOP, packet.DF_STOP_GENERAL, packet.DF_GENERAL, packet.DF_CELL_OVERVOLTAGE, packet.MEM_DF_CELL_OVERVOLTAGE, packet.DF_UNBALANCE, packet.MEM_DF_UNBALANCE, packet.Buzzer_stop, packet.DECHARGE, packet.MEM_DECHARGE, packet.Compteur_demande_coupure_batterie, packet.Compteur_demande_coupure_totale, packet.S_PowRelay, packet.S_ChaRelay, packet.S_BatRelay1, packet.S_BatRelay_State, packet.Flag_decharge);
|
||||||
|
}
|
||||||
63
DataModels/PacketParser.cs
Normal file
63
DataModels/PacketParser.cs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
using MoniteurBaie.Utils;
|
||||||
|
|
||||||
|
namespace MoniteurBaie.DataModels;
|
||||||
|
|
||||||
|
public static class PacketParser
|
||||||
|
{
|
||||||
|
public static SerialDataPacket ParseSerialDataPacket(string s)
|
||||||
|
{
|
||||||
|
// "COM,{VB1},{VB2},{VB3},{VB4},{VB5},{VB6},{VBat},{VBmin},{VBmax},{Curr},{Power},{Energy},{Temp_alim},{Temp_cha},{Temp_bat},{DF_TEMP_ALIM},{MEM_DF_TEMP_ALIM},{DF_TEMP_BAT},{MEM_DF_TEMP_BAT},{DF_TEMP_CHA},{MEM_DF_TEMP_CHA},{DF_V_INCOHERENT},{DF_BAD_CELL},{DF_OVERCURRENT},{DF_OVERCURRENT_STOP},{DF_STOP_GENERAL},{DF_GENERAL},{DF_CELL_OVERVOLTAGE},{MEM_DF_CELL_OVERVOLTAGE},{DF_UNBALANCE},{MEM_DF_UNBALANCE},{Buzzer_stop},{DECHARGE},{MEM_DECHARGE},{Compteur_demande_coupure_batterie},{Compteur_demande_coupure_totale},{!digitalRead(S_PowRelay)},{digitalRead(S_ChaRelay)},{digitalRead(S_BatRelay1)},{S_BatRelay_State},{flag_decharge}";
|
||||||
|
|
||||||
|
var splitter = new Splitter(s);
|
||||||
|
|
||||||
|
var header = splitter.ReadString();
|
||||||
|
if (header is not "COM")
|
||||||
|
{
|
||||||
|
throw new Exception("Invalid packet header.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var VB1 = splitter.ReadFloat();
|
||||||
|
var VB2 = splitter.ReadFloat();
|
||||||
|
var VB3 = splitter.ReadFloat();
|
||||||
|
var VB4 = splitter.ReadFloat();
|
||||||
|
var VB5 = splitter.ReadFloat();
|
||||||
|
var VB6 = splitter.ReadFloat();
|
||||||
|
var VBat = splitter.ReadFloat();
|
||||||
|
var VBmin = splitter.ReadFloat();
|
||||||
|
var VBmax = splitter.ReadFloat();
|
||||||
|
var Curr = splitter.ReadFloat();
|
||||||
|
var Power = splitter.ReadFloat();
|
||||||
|
var Energy = splitter.ReadFloat();
|
||||||
|
var Temp_alim = splitter.ReadFloat();
|
||||||
|
var Temp_cha = splitter.ReadFloat();
|
||||||
|
var Temp_bat = splitter.ReadFloat();
|
||||||
|
var DF_TEMP_ALIM = splitter.ReadBool();
|
||||||
|
var MEM_DF_TEMP_ALIM = splitter.ReadBool();
|
||||||
|
var DF_TEMP_BAT = splitter.ReadBool();
|
||||||
|
var MEM_DF_TEMP_BAT = splitter.ReadBool();
|
||||||
|
var DF_TEMP_CHA = splitter.ReadBool();
|
||||||
|
var MEM_DF_TEMP_CHA = splitter.ReadBool();
|
||||||
|
var DF_V_INCOHERENT = splitter.ReadBool();
|
||||||
|
var DF_BAD_CELL = splitter.ReadBool();
|
||||||
|
var DF_OVERCURRENT = splitter.ReadBool();
|
||||||
|
var DF_OVERCURRENT_STOP = splitter.ReadBool();
|
||||||
|
var DF_STOP_GENERAL = splitter.ReadBool();
|
||||||
|
var DF_GENERAL = splitter.ReadBool();
|
||||||
|
var DF_CELL_OVERVOLTAGE = splitter.ReadBool();
|
||||||
|
var MEM_DF_CELL_OVERVOLTAGE = splitter.ReadBool();
|
||||||
|
var DF_UNBALANCE = splitter.ReadBool();
|
||||||
|
var MEM_DF_UNBALANCE = splitter.ReadBool();
|
||||||
|
var Buzzer_stop = splitter.ReadBool();
|
||||||
|
var DECHARGE = splitter.ReadBool();
|
||||||
|
var MEM_DECHARGE = splitter.ReadBool();
|
||||||
|
var Compteur_demande_coupure_batterie = splitter.ReadUInt();
|
||||||
|
var Compteur_demande_coupure_totale = splitter.ReadUInt();
|
||||||
|
var S_PowRelay = splitter.ReadBool();
|
||||||
|
var S_ChaRelay = splitter.ReadBool();
|
||||||
|
var S_BatRelay1 = splitter.ReadBool();
|
||||||
|
var S_BatRelay_State = splitter.ReadBool();
|
||||||
|
var Flag_decharge = splitter.ReadBool();
|
||||||
|
|
||||||
|
return new(VB1, VB2, VB3, VB4, VB5, VB6, VBat, VBmin, VBmax, Curr, Power, Energy, Temp_alim, Temp_cha, Temp_bat, DF_TEMP_ALIM, MEM_DF_TEMP_ALIM, DF_TEMP_BAT, MEM_DF_TEMP_BAT, DF_TEMP_CHA, MEM_DF_TEMP_CHA, DF_V_INCOHERENT, DF_BAD_CELL, DF_OVERCURRENT, DF_OVERCURRENT_STOP, DF_STOP_GENERAL, DF_GENERAL, DF_CELL_OVERVOLTAGE, MEM_DF_CELL_OVERVOLTAGE, DF_UNBALANCE, MEM_DF_UNBALANCE, Buzzer_stop, DECHARGE, MEM_DECHARGE, Compteur_demande_coupure_batterie, Compteur_demande_coupure_totale, S_PowRelay, S_ChaRelay, S_BatRelay1, S_BatRelay_State, Flag_decharge);
|
||||||
|
}
|
||||||
|
}
|
||||||
47
DataModels/SerialDataPacket.cs
Normal file
47
DataModels/SerialDataPacket.cs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
namespace MoniteurBaie.DataModels;
|
||||||
|
|
||||||
|
public record SerialDataPacket(
|
||||||
|
float VB1,
|
||||||
|
float VB2,
|
||||||
|
float VB3,
|
||||||
|
float VB4,
|
||||||
|
float VB5,
|
||||||
|
float VB6,
|
||||||
|
float VBat,
|
||||||
|
float VBmin,
|
||||||
|
float VBmax,
|
||||||
|
float Curr,
|
||||||
|
float Power,
|
||||||
|
float Energy,
|
||||||
|
float Temp_alim,
|
||||||
|
float Temp_cha,
|
||||||
|
float Temp_bat,
|
||||||
|
bool DF_TEMP_ALIM,
|
||||||
|
bool MEM_DF_TEMP_ALIM,
|
||||||
|
bool DF_TEMP_BAT,
|
||||||
|
bool MEM_DF_TEMP_BAT,
|
||||||
|
bool DF_TEMP_CHA,
|
||||||
|
bool MEM_DF_TEMP_CHA,
|
||||||
|
bool DF_V_INCOHERENT,
|
||||||
|
bool DF_BAD_CELL,
|
||||||
|
bool DF_OVERCURRENT,
|
||||||
|
bool DF_OVERCURRENT_STOP,
|
||||||
|
bool DF_STOP_GENERAL,
|
||||||
|
bool DF_GENERAL,
|
||||||
|
bool DF_CELL_OVERVOLTAGE,
|
||||||
|
bool MEM_DF_CELL_OVERVOLTAGE,
|
||||||
|
bool DF_UNBALANCE,
|
||||||
|
bool MEM_DF_UNBALANCE,
|
||||||
|
bool Buzzer_stop,
|
||||||
|
bool DECHARGE,
|
||||||
|
bool MEM_DECHARGE,
|
||||||
|
uint Compteur_demande_coupure_batterie,
|
||||||
|
uint Compteur_demande_coupure_totale,
|
||||||
|
bool S_PowRelay,
|
||||||
|
bool S_ChaRelay,
|
||||||
|
bool S_BatRelay1,
|
||||||
|
bool S_BatRelay_State,
|
||||||
|
bool Flag_decharge)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
16
DiscordApp/DiscordApp.csproj
Normal file
16
DiscordApp/DiscordApp.csproj
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<RootNamespace>$(SolutionName.Replace(" ", "_")).$(MSBuildProjectName.Replace(" ", "_"))</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Discord.Net" Version="3.8.1" />
|
||||||
|
<PackageReference Include="StackExchange.Redis" Version="2.6.70" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
9
DiscordApp/Program.cs
Normal file
9
DiscordApp/Program.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace MoniteurBaie.DiscordApp;
|
||||||
|
|
||||||
|
public static class Program
|
||||||
|
{
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
61
MoniteurBaie.sln
Normal file
61
MoniteurBaie.sln
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.2.32630.192
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Utils", "Utils\Utils.csproj", "{A18682B4-1D2B-4C0E-BCC7-4E499D853059}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleLog", "ConsoleLog\ConsoleLog.csproj", "{F4A92DE8-E5BF-4048-B3E2-474F0D528195}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DiscordApp", "DiscordApp\DiscordApp.csproj", "{3469C0A6-E375-44C8-BCBA-4E07B9677C49}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DataApi", "DataApi\DataApi.csproj", "{12A93E6E-00CB-4447-B431-64EBFE8EE566}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DataModels", "DataModels\DataModels.csproj", "{8EFF8A80-F571-4EF4-82C3-BFC59DD3ABBD}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SerialCom", "SerialCom\SerialCom.csproj", "{361CFEF0-763E-44B8-A580-0844032C0CFE}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testation", "Testation\Testation.csproj", "{98E084C2-CC2E-4B61-8BEC-0BFE864C40C1}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{A18682B4-1D2B-4C0E-BCC7-4E499D853059}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{A18682B4-1D2B-4C0E-BCC7-4E499D853059}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{A18682B4-1D2B-4C0E-BCC7-4E499D853059}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{A18682B4-1D2B-4C0E-BCC7-4E499D853059}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{F4A92DE8-E5BF-4048-B3E2-474F0D528195}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{F4A92DE8-E5BF-4048-B3E2-474F0D528195}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{F4A92DE8-E5BF-4048-B3E2-474F0D528195}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{F4A92DE8-E5BF-4048-B3E2-474F0D528195}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{3469C0A6-E375-44C8-BCBA-4E07B9677C49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{3469C0A6-E375-44C8-BCBA-4E07B9677C49}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{3469C0A6-E375-44C8-BCBA-4E07B9677C49}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{3469C0A6-E375-44C8-BCBA-4E07B9677C49}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{12A93E6E-00CB-4447-B431-64EBFE8EE566}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{12A93E6E-00CB-4447-B431-64EBFE8EE566}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{12A93E6E-00CB-4447-B431-64EBFE8EE566}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{12A93E6E-00CB-4447-B431-64EBFE8EE566}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{8EFF8A80-F571-4EF4-82C3-BFC59DD3ABBD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{8EFF8A80-F571-4EF4-82C3-BFC59DD3ABBD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{8EFF8A80-F571-4EF4-82C3-BFC59DD3ABBD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{8EFF8A80-F571-4EF4-82C3-BFC59DD3ABBD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{361CFEF0-763E-44B8-A580-0844032C0CFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{361CFEF0-763E-44B8-A580-0844032C0CFE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{361CFEF0-763E-44B8-A580-0844032C0CFE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{361CFEF0-763E-44B8-A580-0844032C0CFE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{98E084C2-CC2E-4B61-8BEC-0BFE864C40C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{98E084C2-CC2E-4B61-8BEC-0BFE864C40C1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{98E084C2-CC2E-4B61-8BEC-0BFE864C40C1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{98E084C2-CC2E-4B61-8BEC-0BFE864C40C1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {81BADD87-1FE2-4F6D-B5A2-5F294EBCE466}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
17
Properties/PublishProfiles/RaspiDeploy.pubxml
Normal file
17
Properties/PublishProfiles/RaspiDeploy.pubxml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||||
|
-->
|
||||||
|
<Project>
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
|
<Platform>Any CPU</Platform>
|
||||||
|
<PublishDir>bin\publish\</PublishDir>
|
||||||
|
<PublishProtocol>FileSystem</PublishProtocol>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<SelfContained>false</SelfContained>
|
||||||
|
<RuntimeIdentifier>linux-arm64</RuntimeIdentifier>
|
||||||
|
<PublishSingleFile>true</PublishSingleFile>
|
||||||
|
<PublishReadyToRun>false</PublishReadyToRun>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
221
SerialCom/BatteryController.cs
Normal file
221
SerialCom/BatteryController.cs
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
using System.IO.Ports;
|
||||||
|
using MoniteurBaie.DataModels;
|
||||||
|
using MoniteurBaie.Utils;
|
||||||
|
|
||||||
|
namespace MoniteurBaie.SerialCom;
|
||||||
|
|
||||||
|
public sealed class BatteryController : IBatteryController
|
||||||
|
{
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
private readonly SerialPort _serialPort;
|
||||||
|
private readonly bool _closePort;
|
||||||
|
|
||||||
|
private readonly List<IObserver<BatteryControllerPacket>> _packetObservers = new();
|
||||||
|
private readonly BlockingListener<string> _commandListener = new();
|
||||||
|
|
||||||
|
public BatteryController(ILogger logger, Action<SerialPort> configure)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_serialPort = new SerialPort();
|
||||||
|
configure(_serialPort);
|
||||||
|
_closePort = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BatteryController(ILogger logger, SerialPort serialPort, bool closePort = false)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_serialPort = serialPort;
|
||||||
|
_closePort = closePort;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task Open(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_serialPort.Open();
|
||||||
|
|
||||||
|
var readThread = new Thread(Test)
|
||||||
|
{
|
||||||
|
IsBackground = true
|
||||||
|
};
|
||||||
|
readThread.Start();
|
||||||
|
|
||||||
|
// var writeThread = new Thread(DoWrite)
|
||||||
|
// {
|
||||||
|
// IsBackground = true
|
||||||
|
// };
|
||||||
|
// writeThread.Start();
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IDisposable AddSerialObserver(IObserver<BatteryControllerPacket> observer)
|
||||||
|
{
|
||||||
|
_packetObservers.Add(observer);
|
||||||
|
return new Disposer(() => _packetObservers.Remove(observer));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task SendCommand(string command, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_commandListener.Push(command);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Test()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (_serialPort.IsOpen)
|
||||||
|
{
|
||||||
|
var b = _serialPort.ReadByte();
|
||||||
|
Console.WriteLine(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "The serial connection has been disposed.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "An exception occurred in the serial read loop.");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DoRead()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (_serialPort.IsOpen)
|
||||||
|
{
|
||||||
|
string line;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
line = _serialPort.ReadLine();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to read line from serial connection.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(line))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var packet = new BatteryControllerPacket(line);
|
||||||
|
|
||||||
|
foreach (var observer in _packetObservers)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
observer.OnNext(packet);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "An exception occurred in a packet handler.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "The serial connection has been disposed.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "An exception occurred in the serial read loop.");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DoWrite()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (_serialPort.IsOpen)
|
||||||
|
{
|
||||||
|
var line = _commandListener.Next();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(line))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_serialPort.Write(line + "\n");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to send command.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "The serial connection has been disposed.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "An exception occurred in the serial write loop.");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region IObservable
|
||||||
|
|
||||||
|
IDisposable IObservable<BatteryControllerPacket>.Subscribe(IObserver<BatteryControllerPacket> observer)
|
||||||
|
{
|
||||||
|
return AddSerialObserver(observer);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region IObserver
|
||||||
|
|
||||||
|
void IObserver<string>.OnCompleted()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void IObserver<string>.OnError(Exception error)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void IObserver<string>.OnNext(string value)
|
||||||
|
{
|
||||||
|
SendCommand(value, default);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region IDisposable
|
||||||
|
|
||||||
|
private bool disposedValue;
|
||||||
|
|
||||||
|
private void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!disposedValue)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
if (_closePort)
|
||||||
|
{
|
||||||
|
_serialPort.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
disposedValue = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(disposing: true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
71
SerialCom/DataApi.cs
Normal file
71
SerialCom/DataApi.cs
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
using System.Net.Http.Json;
|
||||||
|
using MoniteurBaie.DataModels;
|
||||||
|
|
||||||
|
namespace MoniteurBaie.SerialCom;
|
||||||
|
|
||||||
|
sealed class DataApi : IDisposable
|
||||||
|
{
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
private readonly HttpClient _httpClient;
|
||||||
|
|
||||||
|
public DataApi(ILogger logger, IConfiguration config)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_httpClient = new HttpClient
|
||||||
|
{
|
||||||
|
BaseAddress = new Uri(config.GetValue<string>("BaseUrl")!),
|
||||||
|
Timeout = TimeSpan.FromSeconds(1)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Send(BatteryControllerPacket packet) => Task.Run(() => DoSend(packet));
|
||||||
|
|
||||||
|
private async Task DoSend(BatteryControllerPacket packet)
|
||||||
|
{
|
||||||
|
SerialDataPacket serialPacket;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
serialPacket = PacketParser.ParseSerialDataPacket(packet.SerialData);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to parse packet.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _httpClient.PostAsJsonAsync("/packets", serialPacket.ToDataPacket(packet.Timestamp), DataPacketContext.Default.DataPacket);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to send packet.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region IDisposable
|
||||||
|
|
||||||
|
private bool _disposedValue;
|
||||||
|
|
||||||
|
private void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!_disposedValue)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_httpClient.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_disposedValue = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(disposing: true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
12
SerialCom/IBatteryController.cs
Normal file
12
SerialCom/IBatteryController.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using MoniteurBaie.DataModels;
|
||||||
|
|
||||||
|
namespace MoniteurBaie.SerialCom;
|
||||||
|
|
||||||
|
public interface IBatteryController : IObservable<BatteryControllerPacket>, IObserver<string>, IDisposable
|
||||||
|
{
|
||||||
|
Task Open(CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
IDisposable AddSerialObserver(IObserver<BatteryControllerPacket> observer);
|
||||||
|
|
||||||
|
Task SendCommand(string command, CancellationToken cancellationToken);
|
||||||
|
}
|
||||||
129
SerialCom/MockBatteryController.cs
Normal file
129
SerialCom/MockBatteryController.cs
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
using MoniteurBaie.DataModels;
|
||||||
|
using MoniteurBaie.Utils;
|
||||||
|
|
||||||
|
namespace MoniteurBaie.SerialCom;
|
||||||
|
|
||||||
|
public sealed class MockBatteryController : IBatteryController
|
||||||
|
{
|
||||||
|
private readonly List<IObserver<BatteryControllerPacket>> _serialObservers = new();
|
||||||
|
private readonly BlockingListener<string> _commandListener = new();
|
||||||
|
|
||||||
|
public Task Open(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var readThread = new Thread(DoRead)
|
||||||
|
{
|
||||||
|
IsBackground = true
|
||||||
|
};
|
||||||
|
readThread.Start();
|
||||||
|
|
||||||
|
var writeThread = new Thread(DoWrite)
|
||||||
|
{
|
||||||
|
IsBackground = true
|
||||||
|
};
|
||||||
|
writeThread.Start();
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IDisposable AddSerialObserver(IObserver<BatteryControllerPacket> observer)
|
||||||
|
{
|
||||||
|
_serialObservers.Add(observer);
|
||||||
|
return new Disposer(() => _serialObservers.Remove(observer));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task SendCommand(string command, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_commandListener.Push(command);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DoRead()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var packet = CreateFakePacket();
|
||||||
|
|
||||||
|
foreach (var observer in _serialObservers)
|
||||||
|
{
|
||||||
|
observer.OnNext(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BatteryControllerPacket CreateFakePacket()
|
||||||
|
{
|
||||||
|
Thread.Sleep(Random.Shared.Next(1000 - 50, 1000 + 50));
|
||||||
|
|
||||||
|
return new(
|
||||||
|
DateTime.Now,
|
||||||
|
$"COM,{Random.Shared.NextSingle()},{Random.Shared.NextSingle()},{Random.Shared.NextSingle()},{Random.Shared.NextSingle()},{Random.Shared.NextSingle()},{Random.Shared.NextSingle()},{Random.Shared.NextSingle()},{Random.Shared.NextSingle()},{Random.Shared.NextSingle()},{Random.Shared.NextSingle()},{Random.Shared.NextSingle()},{Random.Shared.NextSingle()},{Random.Shared.NextSingle()},{Random.Shared.NextSingle()},{Random.Shared.NextSingle()},{Random.Shared.Next(2)},{Random.Shared.Next(2)},{Random.Shared.Next(2)},{Random.Shared.Next(2)},{Random.Shared.Next(2)},{Random.Shared.Next(2)},{Random.Shared.Next(2)},{Random.Shared.Next(2)},{Random.Shared.Next(2)},{Random.Shared.Next(2)},{Random.Shared.Next(2)},{Random.Shared.Next(2)},{Random.Shared.Next(2)},{Random.Shared.Next(2)},{Random.Shared.Next(2)},{Random.Shared.Next(2)},{Random.Shared.Next(2)},{Random.Shared.Next(2)},{Random.Shared.Next(2)},{Random.Shared.Next(100)},{Random.Shared.Next(100)},{Random.Shared.Next(2)},{Random.Shared.Next(2)},{Random.Shared.Next(2)},{Random.Shared.Next(2)},{Random.Shared.Next(2)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DoWrite()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
_commandListener.Next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region IObservable
|
||||||
|
|
||||||
|
IDisposable IObservable<BatteryControllerPacket>.Subscribe(IObserver<BatteryControllerPacket> observer)
|
||||||
|
{
|
||||||
|
return AddSerialObserver(observer);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region IObserver
|
||||||
|
|
||||||
|
void IObserver<string>.OnCompleted()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void IObserver<string>.OnError(Exception error)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void IObserver<string>.OnNext(string value)
|
||||||
|
{
|
||||||
|
SendCommand(value, default);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region IDisposable
|
||||||
|
|
||||||
|
private bool _disposedValue;
|
||||||
|
|
||||||
|
private void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!_disposedValue)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_commandListener.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_disposedValue = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(disposing: true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
11
SerialCom/Program.cs
Normal file
11
SerialCom/Program.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using MoniteurBaie.SerialCom;
|
||||||
|
|
||||||
|
var host = Host.CreateDefaultBuilder(args)
|
||||||
|
.UseSystemd()
|
||||||
|
.ConfigureServices(services =>
|
||||||
|
{
|
||||||
|
services.AddHostedService<Worker>();
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
await host.RunAsync();
|
||||||
16
SerialCom/Properties/PublishProfiles/RaspiPublish.pubxml
Normal file
16
SerialCom/Properties/PublishProfiles/RaspiPublish.pubxml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||||
|
-->
|
||||||
|
<Project>
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
|
<Platform>Any CPU</Platform>
|
||||||
|
<PublishDir>publish\</PublishDir>
|
||||||
|
<PublishProtocol>FileSystem</PublishProtocol>
|
||||||
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
|
<RuntimeIdentifier>linux-arm64</RuntimeIdentifier>
|
||||||
|
<SelfContained>false</SelfContained>
|
||||||
|
<PublishSingleFile>true</PublishSingleFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
11
SerialCom/Properties/launchSettings.json
Normal file
11
SerialCom/Properties/launchSettings.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"profiles": {
|
||||||
|
"SerialCom": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": true,
|
||||||
|
"environmentVariables": {
|
||||||
|
"DOTNET_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
25
SerialCom/SerialCom.csproj
Normal file
25
SerialCom/SerialCom.csproj
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Worker">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<UserSecretsId>dotnet-SerialCom-7208DE51-06AE-44FC-809D-543080AAAFF0</UserSecretsId>
|
||||||
|
<RootNamespace>$(SolutionName.Replace(" ", "_")).$(MSBuildProjectName.Replace(" ", "_"))</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Remove="publish\**" />
|
||||||
|
<Content Remove="publish\**" />
|
||||||
|
<EmbeddedResource Remove="publish\**" />
|
||||||
|
<None Remove="publish\**" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="7.0.0" />
|
||||||
|
<PackageReference Include="StackExchange.Redis" Version="2.6.104" />
|
||||||
|
<PackageReference Include="System.IO.Ports" Version="7.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\DataModels\DataModels.csproj" />
|
||||||
|
<ProjectReference Include="..\Utils\Utils.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
63
SerialCom/Worker.cs
Normal file
63
SerialCom/Worker.cs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
using System.IO.Ports;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using MoniteurBaie.DataModels;
|
||||||
|
using MoniteurBaie.Utils;
|
||||||
|
using StackExchange.Redis;
|
||||||
|
|
||||||
|
namespace MoniteurBaie.SerialCom;
|
||||||
|
|
||||||
|
public class Worker : BackgroundService
|
||||||
|
{
|
||||||
|
private readonly ILogger<Worker> _logger;
|
||||||
|
private readonly IConfiguration _configuration;
|
||||||
|
|
||||||
|
public Worker(ILogger<Worker> logger, IConfiguration configuration)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
|
{
|
||||||
|
var config = _configuration.GetSection("MoniteurBaie");
|
||||||
|
var serialConfig = config.GetSection("Serial");
|
||||||
|
var redisConfig = config.GetSection("Redis");
|
||||||
|
|
||||||
|
using IBatteryController batteryController = config.GetValue<bool>("Mock")
|
||||||
|
? new MockBatteryController()
|
||||||
|
: new BatteryController(_logger, serialPort =>
|
||||||
|
{
|
||||||
|
serialPort.PortName = serialConfig.GetValue<string>("Path")!;
|
||||||
|
serialPort.BaudRate = serialConfig.GetValue<int>("BaudRate");
|
||||||
|
serialPort.Parity = serialConfig.GetValue<Parity>("Parity");
|
||||||
|
serialPort.DataBits = serialConfig.GetValue<int>("DataBits");
|
||||||
|
serialPort.StopBits = serialConfig.GetValue<StopBits>("StopBits");
|
||||||
|
serialPort.Handshake = serialConfig.GetValue<Handshake>("Handshake");
|
||||||
|
serialPort.Encoding = Encoding.GetEncoding(serialConfig.GetValue<string>("Encoding")!);
|
||||||
|
serialPort.NewLine = serialConfig.GetValue<string>("NewLine")!;
|
||||||
|
});
|
||||||
|
|
||||||
|
await batteryController.Open(stoppingToken);
|
||||||
|
|
||||||
|
using var redis = await ConnectionMultiplexer.ConnectAsync(redisConfig.GetValue<string>("Endpoint")!, opts =>
|
||||||
|
{
|
||||||
|
opts.ClientName = redisConfig.GetValue<string>("ClientName");
|
||||||
|
});
|
||||||
|
|
||||||
|
using var dataApi = new DataApi(_logger, config.GetSection("Api"));
|
||||||
|
|
||||||
|
var redisChannel = redisConfig.GetValue<string>("Channels:Packets")!;
|
||||||
|
batteryController.AddSerialObserver(Listener.Create((BatteryControllerPacket packet) =>
|
||||||
|
{
|
||||||
|
var dataPacket = JsonSerializer.Serialize(packet, BatteryControllerPacketContext.Default.BatteryControllerPacket);
|
||||||
|
redis.GetSubscriber().Publish(redisChannel, dataPacket, CommandFlags.FireAndForget);
|
||||||
|
dataApi.Send(packet);
|
||||||
|
}));
|
||||||
|
|
||||||
|
var mq = await redis.GetSubscriber().SubscribeAsync(redisConfig.GetValue<string>("Channels:Commands")!);
|
||||||
|
mq.OnMessage(channelMessage => batteryController.SendCommand(channelMessage.Message.ToString(), stoppingToken));
|
||||||
|
|
||||||
|
await Task.Delay(Timeout.Infinite, stoppingToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
11
SerialCom/appsettings.Development.json
Normal file
11
SerialCom/appsettings.Development.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.Hosting.Lifetime": "Information"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"MoniteurBaie": {
|
||||||
|
"Mock": false
|
||||||
|
}
|
||||||
|
}
|
||||||
32
SerialCom/appsettings.json
Normal file
32
SerialCom/appsettings.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.Hosting.Lifetime": "Information"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"MoniteurBaie": {
|
||||||
|
"Serial": {
|
||||||
|
"Path": "/dev/ttyAMA0",
|
||||||
|
"BaudRate": 115200,
|
||||||
|
"Parity": "None",
|
||||||
|
"DataBits": 8,
|
||||||
|
"StopBits": "One",
|
||||||
|
"Handshake": "None",
|
||||||
|
"Encoding": "UTF-8",
|
||||||
|
"NewLine": "\r\n"
|
||||||
|
},
|
||||||
|
"Redis": {
|
||||||
|
"Endpoint": "mercedes.hbsha.re:6379",
|
||||||
|
"ClientName": "Serial",
|
||||||
|
"Channels": {
|
||||||
|
"Packets": "batCtrlPackets",
|
||||||
|
"Commands": "batCtrlCommands"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Api": {
|
||||||
|
"BaseUrl": "http://mercedes.hbsha.re:5000"
|
||||||
|
},
|
||||||
|
"Mock": false
|
||||||
|
}
|
||||||
|
}
|
||||||
67
Testation/Program.cs
Normal file
67
Testation/Program.cs
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
using System.Text.Json;
|
||||||
|
using MoniteurBaie.DataModels;
|
||||||
|
using StackExchange.Redis;
|
||||||
|
|
||||||
|
|
||||||
|
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
|
||||||
|
CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture;
|
||||||
|
|
||||||
|
using var redis = ConnectionMultiplexer.Connect("mercedes.hbsha.re:6379", opts =>
|
||||||
|
{
|
||||||
|
opts.ClientName = "Testation";
|
||||||
|
});
|
||||||
|
var packetChannel = redis.GetSubscriber().Subscribe("batCtrlPackets");
|
||||||
|
packetChannel.OnMessage(OnPacketMessage);
|
||||||
|
|
||||||
|
using var cancel = new AutoResetEvent(false);
|
||||||
|
|
||||||
|
void ctrlC(object? sender, ConsoleCancelEventArgs e)
|
||||||
|
{
|
||||||
|
e.Cancel = true;
|
||||||
|
Console.CancelKeyPress -= ctrlC;
|
||||||
|
cancel.Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.CancelKeyPress += ctrlC;
|
||||||
|
|
||||||
|
cancel.WaitOne();
|
||||||
|
|
||||||
|
|
||||||
|
static void OnPacketMessage(ChannelMessage channelMessage)
|
||||||
|
{
|
||||||
|
BatteryControllerPacket? batCtrlPacket;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
batCtrlPacket = JsonSerializer.Deserialize(channelMessage.Message.ToString(), BatteryControllerPacketContext.Default.BatteryControllerPacket);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Invalid packet.");
|
||||||
|
Console.WriteLine(ex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (batCtrlPacket is null)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Null packet.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SerialDataPacket packet;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
packet = PacketParser.ParseSerialDataPacket(batCtrlPacket.SerialData);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Invalid data packet.");
|
||||||
|
Console.WriteLine(ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packet.Temp_alim < 0 || packet.Temp_bat < 0 || packet.Temp_cha < 0)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[{batCtrlPacket.Timestamp:yyyy/MM/dd HH:mm:ss}] {batCtrlPacket.SerialData}");
|
||||||
|
}
|
||||||
|
}
|
||||||
17
Testation/Properties/PublishProfiles/RaspiPublish.pubxml
Normal file
17
Testation/Properties/PublishProfiles/RaspiPublish.pubxml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||||
|
-->
|
||||||
|
<Project>
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
|
<Configuration>Release</Configuration>
|
||||||
|
<Platform>Any CPU</Platform>
|
||||||
|
<PublishDir>publish\</PublishDir>
|
||||||
|
<PublishProtocol>FileSystem</PublishProtocol>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<RuntimeIdentifier>linux-arm64</RuntimeIdentifier>
|
||||||
|
<SelfContained>false</SelfContained>
|
||||||
|
<PublishSingleFile>true</PublishSingleFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
25
Testation/Testation.csproj
Normal file
25
Testation/Testation.csproj
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Remove="publish\**" />
|
||||||
|
<EmbeddedResource Remove="publish\**" />
|
||||||
|
<None Remove="publish\**" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="StackExchange.Redis" Version="2.6.70" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\DataModels\DataModels.csproj" />
|
||||||
|
<ProjectReference Include="..\Utils\Utils.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
58
Utils/BlockingListener.cs
Normal file
58
Utils/BlockingListener.cs
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
namespace MoniteurBaie.Utils;
|
||||||
|
|
||||||
|
public sealed class BlockingListener<T> : IObserver<T>, IDisposable
|
||||||
|
{
|
||||||
|
private readonly AutoResetEvent _waitHandle = new(false);
|
||||||
|
private T? _data;
|
||||||
|
|
||||||
|
public T Next()
|
||||||
|
{
|
||||||
|
_waitHandle.WaitOne();
|
||||||
|
var data = _data!;
|
||||||
|
_data = default;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Push(T value)
|
||||||
|
{
|
||||||
|
_data = value;
|
||||||
|
_waitHandle.Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
void IObserver<T>.OnCompleted()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void IObserver<T>.OnError(Exception error)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void IObserver<T>.OnNext(T value) => Push(value);
|
||||||
|
|
||||||
|
#region IDisposable
|
||||||
|
|
||||||
|
private bool _disposedValue;
|
||||||
|
|
||||||
|
private void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!_disposedValue)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_waitHandle.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_disposedValue = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(disposing: true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
19
Utils/Disposer.cs
Normal file
19
Utils/Disposer.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
namespace MoniteurBaie.Utils;
|
||||||
|
|
||||||
|
public sealed class Disposer : IDisposable
|
||||||
|
{
|
||||||
|
private Action? _callback;
|
||||||
|
|
||||||
|
public Disposer(Action callback) => _callback = callback;
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_callback is not null)
|
||||||
|
{
|
||||||
|
_callback();
|
||||||
|
_callback = default;
|
||||||
|
}
|
||||||
|
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
10
Utils/Extensions.cs
Normal file
10
Utils/Extensions.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace MoniteurBaie.Utils;
|
||||||
|
|
||||||
|
public static class Extensions
|
||||||
|
{
|
||||||
|
public static char ToChar(this bool b) => b ? 'O' : 'N';
|
||||||
|
|
||||||
|
public static string ToEmoji(this bool b) => b ? "✔️" : "❌";
|
||||||
|
|
||||||
|
public static string ToAnsi(this bool b) => $"{(b ? "\u001b[32m" : "\u001b[31m")}{ToChar(b)}\u001b[0m";
|
||||||
|
}
|
||||||
31
Utils/Listener.cs
Normal file
31
Utils/Listener.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
namespace MoniteurBaie.Utils;
|
||||||
|
|
||||||
|
public static class Listener
|
||||||
|
{
|
||||||
|
public static Listener<T> Create<T>(Action<T> callback) => new(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Listener<T> : IObserver<T>
|
||||||
|
{
|
||||||
|
private readonly Action<T> _callback;
|
||||||
|
|
||||||
|
public Listener(Action<T> callback)
|
||||||
|
{
|
||||||
|
_callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnCompleted()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnError(Exception error)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnNext(T value)
|
||||||
|
{
|
||||||
|
_callback(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
38
Utils/Splitter.cs
Normal file
38
Utils/Splitter.cs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace MoniteurBaie.Utils;
|
||||||
|
|
||||||
|
public class Splitter
|
||||||
|
{
|
||||||
|
const char DELIMITER = ',';
|
||||||
|
|
||||||
|
public delegate T ValueReader<T>(ReadOnlySpan<char> s);
|
||||||
|
|
||||||
|
private readonly string _str;
|
||||||
|
private int _pos = 0, _charCount = 0, _nextPos = 0;
|
||||||
|
|
||||||
|
public Splitter(string str) => _str = str;
|
||||||
|
|
||||||
|
public bool MoveNext()
|
||||||
|
{
|
||||||
|
_pos = _nextPos;
|
||||||
|
var i = _str.IndexOf(DELIMITER, _pos);
|
||||||
|
(_charCount, _nextPos) = i < 0 ? (_str.Length - _pos, _str.Length) : (i - _pos, i + 1);
|
||||||
|
|
||||||
|
return HasNext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasNext => _pos < _str.Length;
|
||||||
|
|
||||||
|
public T Read<T>(ValueReader<T> reader) => MoveNext() ? reader(_str.AsSpan(_pos, _charCount)) : throw new InvalidOperationException();
|
||||||
|
|
||||||
|
public string? ReadString() => Read(static s => new string(s));
|
||||||
|
|
||||||
|
public bool ReadBool() => Read(static s => s.Length == 1 ? s[0] switch { '0' => false, '1' => true, _ => throw new InvalidDataException() } : throw new InvalidDataException());
|
||||||
|
|
||||||
|
public int ReadInt() => Read(static s => int.Parse(s, provider: CultureInfo.InvariantCulture));
|
||||||
|
|
||||||
|
public uint ReadUInt() => Read(static s => uint.Parse(s, provider: CultureInfo.InvariantCulture));
|
||||||
|
|
||||||
|
public float ReadFloat() => Read(static s => float.Parse(s, provider: CultureInfo.InvariantCulture));
|
||||||
|
}
|
||||||
10
Utils/Utils.csproj
Normal file
10
Utils/Utils.csproj
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<RootNamespace>$(SolutionName.Replace(" ", "_")).$(MSBuildProjectName.Replace(" ", "_"))</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
Reference in New Issue
Block a user