Files
Automatic-Parking/Source/ProofOfConcept/Services/MessageProcessor.cs
Szakáts Alpár Zsolt df999abf6c
All checks were successful
Build, Push and Run Container / build (push) Successful in 28s
Improves parking state detection and logging
Enhances parking state logic by setting the initial gear to "P" and adding more detailed logging for state changes and parking events.

This provides better insight into vehicle states and parking behaviors.
2025-08-21 09:41:51 +02:00

173 lines
7.1 KiB
C#

using System.Text.Json;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using ProofOfConcept.Models;
using Pushover;
using SzakatsA.Result;
namespace ProofOfConcept.Services;
public interface IMessageProcessor
{
Task ProcessMessage(string vin, string field, string value);
}
public class MessageProcessor : IMessageProcessor
{
private readonly ILogger<MessageProcessor> logger;
private MessageProcessorConfiguration configuration;
private readonly IMemoryCache memoryCache;
private readonly ZoneDeterminatorService zoneDeterminatorService;
private readonly TeslaState teslaState;
private readonly ParkingState parkingState;
private readonly PushoverClient pushApi;
public MessageProcessor(ILogger<MessageProcessor> logger, IOptions<MessageProcessorConfiguration> options, IMemoryCache memoryCache, ZoneDeterminatorService zoneDeterminatorService)
{
this.logger = logger;
this.configuration = options.Value;
this.memoryCache = memoryCache;
this.zoneDeterminatorService = zoneDeterminatorService;
this.teslaState = new TeslaState();
this.parkingState = new ParkingState();
this.pushApi = new PushoverClient(this.configuration.PushoverAPIKey);
}
public async Task ProcessMessage(string vin, string field, string value)
{
this.logger.LogTrace("Processing {Field} = {Value} for {VIN}...", field, value, vin);
string[] validGears = [ "P", "R", "N", "D", "SNA" ];
if (field == "gear" && validGears.Contains(value))
this.teslaState.Gear = value;
else if (field == "locked" && bool.TryParse(value, out bool locked))
this.teslaState.Locked = locked;
else if (field == "driverseatoccupied" && bool.TryParse(value, out bool driverSeatOccupied))
this.teslaState.DriverSeatOccupied = driverSeatOccupied;
else if (field == "location")
{
try
{
using var doc = JsonDocument.Parse(value);
var root = doc.RootElement;
this.teslaState.Latitude = root.GetProperty("latitude").GetDouble();
this.teslaState.Longitude = root.GetProperty("longitude").GetDouble();
}
catch (Exception e)
{
this.logger.LogError("Invalid location data: {LocationValue}", value);
}
}
this.logger.LogTrace("State updated for {VIN}. Current state is Gear: {Gear}, Locked: {locked}, driver seat occupied: {DriverSeatOccupied}, Location: {Latitude},{Longitude}", vin, this.teslaState.Gear, this.teslaState.Locked, this.teslaState.DriverSeatOccupied, this.teslaState.Latitude, this.teslaState.Longitude);
if (this.teslaState is { Gear: "P", Locked: true, DriverSeatOccupied: false })
{
this.parkingState.SetCarParked();
this.logger.LogInformation("{vin} is in parked state", vin);
}
else
{
this.parkingState.SetCarMoved();
this.logger.LogInformation("{vin} moved (not parking anymore)", vin);
}
if (this.parkingState is { ParkingInProgress: false, CarParked: true })
await StartParkingAsync(vin);
else if (this.parkingState.ParkingInProgress && (this.teslaState.Gear != "P" || this.teslaState.DriverSeatOccupied || !this.teslaState.Locked))
await StopParkingAsync(vin);
}
private async Task StartParkingAsync(string vin)
{
this.logger.LogTrace("Start parking for {vin}...", vin);
//Get parking zone
Result<string> zoneLookupResult = await this.zoneDeterminatorService.DetermineZoneCodeAsync(this.teslaState.Latitude, this.teslaState.Longitude);
bool sendNotification = this.configuration.VinNotifications.TryGetValue(vin, out string? pushoverToken);
if (zoneLookupResult.IsSuccessful)
{
if (String.IsNullOrWhiteSpace(zoneLookupResult.Value))
{
// Push not a parking zone
if (sendNotification)
this.pushApi.Send(pushoverToken, new PushoverMessage
{
Title = "Nem parkolózóna",
Message = $"Megálltál nem parkoló zónában, a GPS szerint: {this.teslaState.Latitude},{this.teslaState.Longitude}",
Priority = Priority.Normal,
Timestamp = DateTimeOffset.Now.ToLocalTime().ToString(),
});
this.logger.LogInformation("Parking started in non-parking zone for {VIN}", vin);
}
// Push parking started in zone
this.parkingState.SetParkingStarted();
if (sendNotification)
this.pushApi.Send(pushoverToken, new PushoverMessage
{
Title = $"Parkolás elindult: {zoneLookupResult.Value}",
Message = $"Megálltál egy parkolási zónában, a GPS szerint: {this.teslaState.Latitude},{this.teslaState.Longitude}" + Environment.NewLine +
$"A zónatérkép szerint ez a {zoneLookupResult.Value} jelű zóna",
Priority = Priority.Normal,
Timestamp = DateTimeOffset.Now.ToLocalTime().ToString(),
});
this.logger.LogInformation("Parking started for {VIN}", vin);
}
else
this.logger.LogError(zoneLookupResult.Exception, "Can't start parking: error while determining parking zone");
}
private async Task StopParkingAsync(string vin)
{
this.logger.LogTrace("Stopping parking for {vin}...", vin);
// Push parking stopped
this.parkingState.SetParkingStopped();
if (this.configuration.VinNotifications.TryGetValue(vin, out string? pushoverToken))
this.pushApi.Send(pushoverToken, new PushoverMessage
{
Title = $"Parkolás leállt ({DateTimeOffset.Now.Subtract(this.parkingState.ParkingStartedAt!.Value).ToElapsed()})",
Message = $"A {this.parkingState.ParkingStartedAt?.ToString("yyyy-MM-dd HH:mm")} -kor indult parkolásod leállt",
Priority = Priority.Normal,
Timestamp = DateTimeOffset.Now.ToLocalTime().ToString(),
});
this.logger.LogInformation("Parking stopped for {VIN}", vin);
}
}
public class MessageProcessorConfiguration
{
public string PushoverAPIKey { get; set; } = "acr9fqxafqeqjpr4apryh17m4ak24b";
public Dictionary<string, string> VinNotifications { get; set; } = new Dictionary<string, string>() { { "5YJ3E7EB7KF291652", "u2ouaqqu5gd9f1bq3rmrtwriumaffu"} /*Zoli*/ };
}
file static class DateTimeOffsetExtensions
{
public static string ToElapsed(this TimeSpan ts)
{
var parts = new List<string>();
if (ts.Days > 0)
parts.Add($"{ts.Days} nap");
if (ts.Hours > 0)
parts.Add($"{ts.Hours} óra");
if (ts.Minutes > 0)
parts.Add($"{ts.Minutes} perc");
return string.Join(", ", parts);
}
}