All checks were successful
Build, Push and Run Container / build (push) Successful in 27s
168 lines
7.0 KiB
C#
168 lines
7.0 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 == "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);
|
|
}
|
|
|
|
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)
|
|
{
|
|
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");
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|