All checks were successful
Build, Push and Run Container / build (push) Successful in 27s
Adds query parameters for latitude and longitude to the `/TestStartParking` endpoint, allowing to simulate different locations. Adds logic to set the gear to "P" if the received value is null to cover edge cases. Ensures asynchronous message processing by awaiting the result of ProcessMessage to prevent potential race conditions.
175 lines
7.2 KiB
C#
175 lines
7.2 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 class MessageProcessor
|
|
{
|
|
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 == "gear" && value == "null")
|
|
this.teslaState.Gear = "P";
|
|
|
|
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} is NOT in a parked state", 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);
|
|
}
|
|
|
|
public 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)
|
|
{
|
|
//Set parking started
|
|
this.parkingState.SetParkingStarted();
|
|
|
|
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);
|
|
}
|
|
|
|
else
|
|
{
|
|
// Push parking started in zone
|
|
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} at {ZoneCode}", vin, zoneLookupResult.Value);
|
|
}
|
|
}
|
|
else
|
|
this.logger.LogError(zoneLookupResult.Exception, "Can't start parking: error while determining parking zone");
|
|
}
|
|
|
|
public 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);
|
|
}
|
|
}
|