diff --git a/FCAAddon/FCAClient/Program.cs b/FCAAddon/FCAClient/Program.cs index 0225d39..751da62 100644 --- a/FCAAddon/FCAClient/Program.cs +++ b/FCAAddon/FCAClient/Program.cs @@ -1,442 +1,609 @@ -using System.Collections.Concurrent; -using System.Globalization; -using Cocona; -using CoordinateSharp; -using FCAUconnect; -using FCAUconnect.HA; +using System.Text; +using Amazon; +using Amazon.CognitoIdentity; +using Amazon.Runtime; +using Flurl; using Flurl.Http; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Serilog; -using Serilog.Events; -var builder = CoconaApp.CreateBuilder(); +namespace FCAUconnect; -builder.Configuration.AddEnvironmentVariables("FCAUconnect_"); - -builder.Services.AddOptions() - .Bind(builder.Configuration) - .ValidateDataAnnotations() - .ValidateOnStart(); - -var app = builder.Build(); - -var persistentHaEntities = new ConcurrentDictionary(); -var vinCharging = new HashSet(); -var vinPlugged = new HashSet(); - - -var appConfig = builder.Configuration.Get(); -var forceLoopResetEvent = new AutoResetEvent(false); -var forceLoopDeepEvent = new AutoResetEvent(false); -var haClient = new HaRestApi(appConfig.HomeAssistantUrl, appConfig.SupervisorToken); - -Log.Logger = new LoggerConfiguration() - .MinimumLevel.Is(appConfig.Debug ? LogEventLevel.Debug : LogEventLevel.Information) - .WriteTo.Console() - .CreateLogger(); - -Log.Information("Delay start for seconds: {0}", appConfig.StartDelaySeconds); -await Task.Delay(TimeSpan.FromSeconds(appConfig.StartDelaySeconds)); - - -await app.RunAsync(async (CoconaAppContext ctx) => +public interface IFiatClient { - Log.Information("{0}", appConfig.ToStringWithoutSecrets()); - Log.Debug("{0}", appConfig.Dump()); - - FiatClient fiatClient = new FiatClient(appConfig.FCAUser, appConfig.FCAPw, appConfig.Brand, appConfig.Region); - - var mqttClient = new SimpleMqttClient(appConfig.MqttServer, appConfig.MqttPort, appConfig.MqttUser, appConfig.MqttPw, "FCAUconnect"); - - await mqttClient.Connect(); - + Task LoginAndKeepSessionAlive(); + Task SendCommand(string vin, string command, string pin, string action); + Task Fetch(); +} -_ = Task.Run(async () => +public class FiatClientFake : IFiatClient +{ + public Task LoginAndKeepSessionAlive() + { + return Task.CompletedTask; + } + + public Task SendCommand(string vin, string command, string pin, string action) + { + return Task.CompletedTask; + } + + public Task Fetch() + { + var vehicle = JsonConvert.DeserializeObject(""" { - while (!ctx.CancellationToken.IsCancellationRequested) - { - if (!ctx.CancellationToken.IsCancellationRequested && appConfig.AutoDeepRefresh && vinCharging.Any()) - { - Log.Information("AutoDeepRefresh"); - foreach(string vin in vinCharging) { await TrySendCommand(fiatClient, FiatCommand.DEEPREFRESH, vin); } - await Task.Delay(TimeSpan.FromSeconds(6), ctx.CancellationToken); - Log.Information("AutoDeepRefresh COMPLETED. Next update in {0} minutes.", appConfig.AutoDeepInterval); - forceLoopResetEvent.Set(); - } - WaitHandle.WaitAny(new[] { ctx.CancellationToken.WaitHandle ,forceLoopDeepEvent }, TimeSpan.FromMinutes(appConfig.AutoDeepInterval)); - } - }); - - - while (!ctx.CancellationToken.IsCancellationRequested) + "RegStatus": "COMPLETED_STAGE_3", + "Color": "BLUE", + "Year": 2022, + "TsoBodyCode": "", + "NavEnabledHu": false, + "Language": "", + "CustomerRegStatus": "Y", + "Radio": "", + "ActivationSource": "DEALER", + "Nickname": "KEKW", + "Vin": "LDM1SN7DHD7DHSHJ6753D", + "Company": "FCA", + "Model": 332, + "ModelDescription": "Neuer 500 3+1", + "TcuType": 2, + "Make": "FIAT", + "BrandCode": "12", + "SoldRegion": "EMEA" + } + """); + + vehicle.Details = JObject.Parse(""" { - Log.Information("Now fetching new data..."); - - GC.Collect(); - - try - { - await fiatClient.LoginAndKeepSessionAlive(); - - foreach (var vehicle in await fiatClient.Fetch()) + "vehicleInfo": { + "totalRangeADA": null, + "odometer": { + "odometer": { + "value": "1234", + "unit": "km" + } + }, + "daysToService": "null", + "fuel": { + "fuelAmountLevel": null, + "isFuelLevelLow": false, + "distanceToEmpty": { + "value": "150", + "unit": "km" + }, + "fuelAmount": { + "value": "null", + "unit": "null" + } + }, + "oilLevel": { + "oilLevel": null + }, + "tyrePressure": [ + { + "warning": false, + "pressure": { + "value": "null", + "unit": "kPa" + }, + "type": "FL", + "status": "NORMAL" + }, + { + "warning": false, + "pressure": { + "value": "null", + "unit": "kPa" + }, + "type": "FR", + "status": "NORMAL" + }, + { + "warning": false, + "pressure": { + "value": "null", + "unit": "kPa" + }, + "type": "RL", + "status": "NORMAL" + }, + { + "warning": false, + "pressure": { + "value": "null", + "unit": "kPa" + }, + "type": "RR", + "status": "NORMAL" + } + ], + "batteryInfo": { + "batteryStatus": "0", + "batteryVoltage": { + "value": "14.55", + "unit": "volts" + } + }, + "tripsInfo": { + "trips": [ + { + "totalElectricDistance": { + "value": "null", + "unit": "km" + }, + "name": "TripA", + "totalDistance": { + "value": "1013", + "unit": "km" + }, + "energyUsed": { + "value": "null", + "unit": "kmpl" + }, + "averageEnergyUsed": { + "value": "null", + "unit": "kmpl" + }, + "totalHybridDistance": { + "value": "null", + "unit": "km" + } + }, { - Log.Information($"Found : {vehicle.Nickname} {vehicle.Vin} {vehicle.ModelDescription}"); - - var haDevice = new HaDevice() - { - Name = string.IsNullOrEmpty(vehicle.Nickname) ? vehicle.Vin : vehicle.Nickname, - Identifier = vehicle.Vin, - Manufacturer = vehicle.Make, - Model = vehicle.ModelDescription, - Version = "1.0" - }; - - IEnumerable haEntities = await GetHaEntities(haClient, mqttClient, vehicle, haDevice); - - if (persistentHaEntities.TryAdd(vehicle.Vin,DateTime.Now)) - { - Log.Information("Pushing new sensors to Home Assistant"); - await Parallel.ForEachAsync(haEntities, async (sensor, token) => { await sensor.Announce(); }); - - Log.Information("Pushing new buttons to Home Assistant"); - var haInteractiveEntities = CreateInteractiveEntities(ctx, fiatClient, mqttClient, vehicle, haDevice); - await Parallel.ForEachAsync(haInteractiveEntities, async (button, token) => { await button.Announce(); }); - } - - Log.Information("Pushing sensors values to Home Assistant"); - await Parallel.ForEachAsync(haEntities, async (sensor, token) => { await sensor.PublishState(); }); - - var lastUpdate = new HaSensor(mqttClient, "LastUpdate", haDevice, false) { Value = DateTime.Now.ToString("dd/MM HH:mm:ss") }; - - await lastUpdate.Announce(); - await lastUpdate.PublishState(); + "totalElectricDistance": { + "value": "null", + "unit": "km" + }, + "name": "TripB", + "totalDistance": { + "value": "14", + "unit": "km" + }, + "energyUsed": { + "value": "null", + "unit": "kmpl" + }, + "averageEnergyUsed": { + "value": "null", + "unit": "kmpl" + }, + "totalHybridDistance": { + "value": "null", + "unit": "km" + } } - } - catch (FlurlHttpException httpException) - { - Log.Warning($"Error connecting to the FIAT API. \n" + - $"This can happen from time to time. Retrying in {appConfig.RefreshInterval} minutes."); - - Log.Debug("ERROR: {0}", httpException.Message); - Log.Debug("STATUS: {0}", httpException.StatusCode); + ] + }, + "batPwrUsageDisp": null, + "distanceToService": { + "distanceToService": { + "value": "5127.0", + "unit": "km" + } + }, + "wheelCount": 4, + "hvacPwrUsageDisp": null, + "mtrPwrUsageDisp": null, + "tpmsvehicle": false, + "hVBatSOH": null, + "isTPMSVehicle": false, + "timestamp": 1665779022952 + }, + "evInfo": { + "chargeSchedules": [], + "battery": { + "stateOfCharge": 72, + "chargingLevel": "LEVEL_2", + "plugInStatus": true, + "timeToFullyChargeL2": 205, + "chargingStatus": "CHARGING", + "totalRange": 172, + "distanceToEmpty": { + "value": 172, + "unit": "km" + } + }, + "timestamp": 1665822611085, + "schedules": [ + { + "chargeToFull": false, + "scheduleType": "NONE", + "enableScheduleType": false, + "scheduledDays": { + "sunday": false, + "saturday": false, + "tuesday": false, + "wednesday": false, + "thursday": false, + "friday": false, + "monday": false + }, + "startTime": "00:00", + "endTime": "00:00", + "cabinPriority": false, + "repeatSchedule": true + }, + { + "chargeToFull": false, + "scheduleType": "NONE", + "enableScheduleType": false, + "scheduledDays": { + "sunday": false, + "saturday": false, + "tuesday": false, + "wednesday": false, + "thursday": false, + "friday": false, + "monday": false + }, + "startTime": "00:00", + "endTime": "00:00", + "cabinPriority": false, + "repeatSchedule": true + }, + { + "chargeToFull": false, + "scheduleType": "NONE", + "enableScheduleType": false, + "scheduledDays": { + "sunday": false, + "saturday": false, + "tuesday": false, + "wednesday": false, + "thursday": false, + "friday": false, + "monday": false + }, + "startTime": "00:00", + "endTime": "00:00", + "cabinPriority": false, + "repeatSchedule": true + } + ] + }, + "timestamp": 1665822611085 + } + """); + + vehicle.Location = JsonConvert.DeserializeObject(""" + { + "TimeStamp": 1665779022952, + "Longitude": 4.1234365, + "Latitude": 69.4765989, + "Altitude": 40.346462111, + "Bearing": 0, + "IsLocationApprox": true + } + """); - var task = httpException.Call?.Response?.GetStringAsync(); + return Task.FromResult(new[] { vehicle }); + } +} - if (task != null) { Log.Debug("RESPONSE: {0}", await task); } - } - catch (Exception e) - { - Log.Error("{0}", e); - } +public enum FcaBrand +{ + Fiat, + Ram, + Jeep, + Dodge, + AlfaRomeo +} - Log.Information("Fetching COMPLETED. Next update in {0} minutes.", appConfig.RefreshInterval); +public enum FcaRegion +{ + Europe, + America +} - WaitHandle.WaitAny(new[] { ctx.CancellationToken.WaitHandle, forceLoopResetEvent }, TimeSpan.FromMinutes(appConfig.RefreshInterval)); +public class FiatClient : IFiatClient +{ + private readonly string _loginApiKey = "3_mOx_J2dRgjXYCdyhchv3b5lhi54eBcdCTX4BI8MORqmZCoQWhA0mV2PTlptLGUQI"; + private readonly string _apiKey = "2wGyL6PHec9o1UeLPYpoYa1SkEWqeBur9bLsi24i"; + private readonly string _loginUrl = "https://loginmyuconnect.fiat.com"; + private readonly string _tokenUrl = "https://authz.sdpr-01.fcagcv.com/v2/cognito/identity/token"; + private readonly string _apiUrl = "https://channels.sdpr-01.fcagcv.com"; + private readonly string _authApiKey = "JWRYW7IYhW9v0RqDghQSx4UcRYRILNmc8zAuh5ys"; // for pin + private readonly string _authUrl = "https://mfa.fcl-01.fcagcv.com"; // for pin + private readonly string _locale = "de_de"; // for pin + private readonly RegionEndpoint _awsEndpoint = RegionEndpoint.EUWest1; + private readonly string _user; + private readonly string _password; + private readonly FcaBrand _brand; + private readonly FcaRegion _region; + private readonly CookieJar _cookieJar = new(); - } -}); + private readonly IFlurlClient _defaultHttpClient; -async Task TrySendCommand(FiatClient fiatClient, FiatCommand command, string vin) -{ - Log.Information("SEND COMMAND {0}: ", command.Message); + private (string userUid, ImmutableCredentials awsCredentials)? _loginInfo = null; - if (string.IsNullOrWhiteSpace(appConfig.FCAPin)) + public FiatClient(string user, string password, FcaBrand brand = FcaBrand.Fiat, FcaRegion region = FcaRegion.Europe) + { + _user = user; + _password = password; + _brand = brand; + _region = region; + + if (_brand == FcaBrand.Ram) { - throw new Exception("PIN NOT SET"); + _loginApiKey = "3_7YjzjoSb7dYtCP5-D6FhPsCciggJFvM14hNPvXN9OsIiV1ujDqa4fNltDJYnHawO"; + _apiKey = "OgNqp2eAv84oZvMrXPIzP8mR8a6d9bVm1aaH9LqU"; + _loginUrl = "https://login-us.ramtrucks.com"; + _tokenUrl = "https://authz.sdpr-02.fcagcv.com/v2/cognito/identity/token"; + _apiUrl = "https://channels.sdpr-02.fcagcv.com"; + _authApiKey = "JWRYW7IYhW9v0RqDghQSx4UcRYRILNmc8zAuh5ys"; // UNKNOWN + _authUrl = "https://mfa.fcl-01.fcagcv.com"; // UNKNOWN + _awsEndpoint = RegionEndpoint.USEast1; + _locale = "en_us"; } - - var pin = appConfig.FCAPin; - - try + else if(_brand == FcaBrand.Dodge) { - await fiatClient.SendCommand(vin, command.Message, pin, command.Action); - await Task.Delay(TimeSpan.FromSeconds(5)); - Log.Information("Command: {0} SUCCESSFUL", command.Message); + _loginApiKey = "3_etlYkCXNEhz4_KJVYDqnK1CqxQjvJStJMawBohJU2ch3kp30b0QCJtLCzxJ93N-M"; + _apiKey = "OgNqp2eAv84oZvMrXPIzP8mR8a6d9bVm1aaH9LqU"; + _loginUrl = "https://login-us.dodge.com"; + _tokenUrl = "https://authz.sdpr-02.fcagcv.com/v2/cognito/identity/token"; + _apiUrl = "https://channels.sdpr-02.fcagcv.com"; + _authApiKey = "JWRYW7IYhW9v0RqDghQSx4UcRYRILNmc8zAuh5ys"; // UNKNOWN + _authUrl = "https://mfa.fcl-01.fcagcv.com"; // UNKNOWN + _awsEndpoint = RegionEndpoint.USEast1; + _locale = "en_us"; } - catch (Exception e) + else if (_brand == FcaBrand.Fiat && _region == FcaRegion.America) { - Log.Error("Command: {0} ERROR : {1}", command.Message,e.Message); - Log.Debug("{0}", e); - return false; + _loginApiKey = "3_etlYkCXNEhz4_KJVYDqnK1CqxQjvJStJMawBohJU2ch3kp30b0QCJtLCzxJ93N-M"; + _apiKey = "OgNqp2eAv84oZvMrXPIzP8mR8a6d9bVm1aaH9LqU"; + _loginUrl = "https://login-us.fiat.com"; + _tokenUrl = "https://authz.sdpr-02.fcagcv.com/v2/cognito/identity/token"; + _apiUrl = "https://channels.sdpr-02.fcagcv.com"; + _authApiKey = "JWRYW7IYhW9v0RqDghQSx4UcRYRILNmc8zAuh5ys"; // UNKNOWN + _authUrl = "https://mfa.fcl-01.fcagcv.com"; // UNKNOWN + _awsEndpoint = RegionEndpoint.USEast1; + _locale = "en_us"; } - - return true; -} - - - -IEnumerable CreateInteractiveEntities(CoconaAppContext ctx, FiatClient fiatClient, SimpleMqttClient mqttClient, Vehicle vehicle, - HaDevice haDevice) -{ - var updateLocationButton = new HaButton(mqttClient, "UpdateLocation", haDevice, async button => + else if (_brand == FcaBrand.Jeep) { - if (await TrySendCommand(fiatClient, FiatCommand.VF, vehicle.Vin)) - { - await Task.Delay(TimeSpan.FromSeconds(6), ctx.CancellationToken); - forceLoopResetEvent.Set(); - } - }); + if (_region == FcaRegion.Europe) + { + _loginApiKey = "3_ZvJpoiZQ4jT5ACwouBG5D1seGEntHGhlL0JYlZNtj95yERzqpH4fFyIewVMmmK7j"; + _loginUrl = "https://login.jeep.com"; + } + else + { + _loginApiKey = "3_5qxvrevRPG7--nEXe6huWdVvF5kV7bmmJcyLdaTJ8A45XUYpaR398QNeHkd7EB1X"; + _apiKey = "OgNqp2eAv84oZvMrXPIzP8mR8a6d9bVm1aaH9LqU"; + _loginUrl = "https://login-us.jeep.com"; + _tokenUrl = "https://authz.sdpr-02.fcagcv.com/v2/cognito/identity/token"; + _apiUrl = "https://channels.sdpr-02.fcagcv.com"; + _authApiKey = "fNQO6NjR1N6W0E5A6sTzR3YY4JGbuPv48Nj9aZci"; + _authUrl = "https://mfa.fcl-02.fcagcv.com"; + _awsEndpoint = RegionEndpoint.USEast1; + _locale = "en_us"; + } + } - var deepRefreshButton = new HaButton(mqttClient, "DeepRefresh", haDevice, async button => + _defaultHttpClient = new FlurlClient().Configure(settings => { - if (vinPlugged.Contains(vehicle.Vin)) - { - if (await TrySendCommand(fiatClient, FiatCommand.DEEPREFRESH, vehicle.Vin)) - { - await Task.Delay(TimeSpan.FromSeconds(6), ctx.CancellationToken); - forceLoopResetEvent.Set(); - } - } + settings.HttpClientFactory = new PollyHttpClientFactory(); }); + } - var lightsButton = new HaButton(mqttClient, "Light", haDevice, async button => + public async Task LoginAndKeepSessionAlive() + { + if (_loginInfo is not null) + return; + + await this.Login(); + + _ = Task.Run(async () => { - if (await TrySendCommand(fiatClient, FiatCommand.HBLF, vehicle.Vin)) + var timer = new PeriodicTimer(TimeSpan.FromMinutes(2)); + + while (await timer.WaitForNextTickAsync()) + { + try { - forceLoopResetEvent.Set(); + Log.Information("REFRESH SESSION"); + await this.Login(); } - }); - - - var hvacButton = new HaButton(mqttClient, "HVAC", haDevice, async button => - { - if (await TrySendCommand(fiatClient, FiatCommand.ROPRECOND, vehicle.Vin)) + catch (Exception e) { - forceLoopResetEvent.Set(); + + Log.Error("ERROR WHILE REFRESH SESSION"); + Log.Debug("{0}", e); } + } }); - - var startengineButton = new HaButton(mqttClient, "StartEngine", haDevice, async button => - { - if (await TrySendCommand(fiatClient, FiatCommand.REON, vehicle.Vin)) + } + + private async Task Login() + { + var loginResponse = await _loginUrl + .WithClient(_defaultHttpClient) + .AppendPathSegment("accounts.webSdkBootstrap") + .SetQueryParam("apiKey", _loginApiKey) + .WithCookies(_cookieJar) + .GetJsonAsync(); + + Log.Debug("{0}", loginResponse.Dump()); + + loginResponse.ThrowOnError("Login failed."); + + var authResponse = await _loginUrl + .WithClient(_defaultHttpClient) + .AppendPathSegment("accounts.login") + .WithCookies(_cookieJar) + .PostUrlEncodedAsync( + WithFiatDefaultParameter(new() { - forceLoopResetEvent.Set(); - } - }); - - var stopengineButton = new HaButton(mqttClient, "StopEngine", haDevice, async button => - { - if (await TrySendCommand(fiatClient, FiatCommand.REOFF, vehicle.Vin)) + { "loginID", _user }, + { "password", _password }, + { "sessionExpiration", TimeSpan.FromMinutes(5).TotalSeconds }, + { "include", "profile,data,emails,subscriptions,preferences" }, + })) + .ReceiveJson(); + + Log.Debug("{0}", authResponse.Dump()); + + authResponse.ThrowOnError("Authentication failed."); + + var jwtResponse = await _loginUrl + .WithClient(_defaultHttpClient) + .AppendPathSegment("accounts.getJWT") + .SetQueryParams( + WithFiatDefaultParameter(new() { - forceLoopResetEvent.Set(); - } - }); - + { "fields", "profile.firstName,profile.lastName,profile.email,country,locale,data.disclaimerCodeGSDP" }, + { "login_token", authResponse.SessionInfo.LoginToken } + })) + .WithCookies(_cookieJar) + .GetJsonAsync(); + + Log.Debug("{0}", jwtResponse.Dump()); + + jwtResponse.ThrowOnError("Authentication failed."); + + var identityResponse = await _tokenUrl + .WithClient(_defaultHttpClient) + .WithHeader("content-type", "application/json") + .WithHeaders(WithAwsDefaultParameter(_apiKey)) + .PostJsonAsync(new + { + gigya_token = jwtResponse.IdToken, + }) + .ReceiveJson(); + + Log.Debug("{0}", identityResponse.Dump()); + + identityResponse.ThrowOnError("Identity failed."); - var lockButton = new HaButton(mqttClient, "DoorLock", haDevice, async button => - { - if (await TrySendCommand(fiatClient, FiatCommand.RDL, vehicle.Vin)) - { - forceLoopResetEvent.Set(); - } - }); + var client = new AmazonCognitoIdentityClient(new AnonymousAWSCredentials(), _awsEndpoint); - var unLockButton = new HaButton(mqttClient, "DoorUnlock", haDevice, async button => - { - if (await TrySendCommand(fiatClient, FiatCommand.RDU, vehicle.Vin)) - { - forceLoopResetEvent.Set(); - } - }); + var res = await client.GetCredentialsForIdentityAsync(identityResponse.IdentityId, + new Dictionary() + { + { "cognito-identity.amazonaws.com", identityResponse.Token } + }); - var fetchNowButton = new HaButton(mqttClient, "FetchNow", haDevice, async button => - { - Log.Information($"Force Fetch Now"); - await Task.Run(() => forceLoopResetEvent.Set()); - }); - - var suppressalarmButton = new HaButton(mqttClient, "SuppressAlarm", haDevice, async button => - { - if (await TrySendCommand(fiatClient, FiatCommand.TA, vehicle.Vin)) - { - forceLoopResetEvent.Set(); - } - }); - - var locktrunkButton = new HaButton(mqttClient, "LockTrunk", haDevice, async button => - { - if (await TrySendCommand(fiatClient, FiatCommand.ROTRUNKLOCK, vehicle.Vin)) - { - forceLoopResetEvent.Set(); - } - }); + _loginInfo = (authResponse.UID, new ImmutableCredentials(res.Credentials.AccessKeyId, + res.Credentials.SecretKey, + res.Credentials.SessionToken)); + } - var unlocktrunkButton = new HaButton(mqttClient, "UnLockTrunk", haDevice, async button => + private Dictionary WithAwsDefaultParameter(string apiKey, Dictionary? parameters = null) + { + var dict = new Dictionary() { - if (await TrySendCommand(fiatClient, FiatCommand.ROTRUNKUNLOCK, vehicle.Vin)) - { - forceLoopResetEvent.Set(); - } - }); + { "x-clientapp-name", "CWP" }, + { "x-clientapp-version", "1.0" }, + { "clientrequestid", Guid.NewGuid().ToString("N")[..16] }, + { "x-api-key", apiKey }, + { "locale", _locale }, + { "x-originator-type", "web" }, + }; - var haEntities = new HaEntity[] { hvacButton, startengineButton, stopengineButton, deepRefreshButton, lightsButton, updateLocationButton, lockButton, unLockButton, fetchNowButton, suppressalarmButton, locktrunkButton, unlocktrunkButton }; + foreach (var parameter in parameters ?? new()) + dict.Add(parameter.Key, parameter.Value); - Log.Debug("Announce haEntities : {0}", haEntities.Dump()); + return dict; + } - return haEntities; -} + private Dictionary WithFiatDefaultParameter(Dictionary? parameters = null) + { + var dict = new Dictionary() + { + { "targetEnv", "jssdk" }, + { "loginMode", "standard" }, + { "sdk", "js_latest" }, + { "authMode", "cookie" }, + { "sdkBuild", "12234" }, + { "format", "json" }, + { "APIKey", _loginApiKey }, + }; -async Task> GetHaEntities(HaRestApi haClient, SimpleMqttClient mqttClient, Vehicle vehicle, HaDevice haDevice) -{ - var compactDetails = vehicle.Details.Compact(""); + foreach (var parameter in parameters ?? new()) + dict.Add(parameter.Key, parameter.Value); - bool charging = false; - string charginglevel = "battery_timetofullychargel2"; - string batteryPluginstatus = "battery_pluginstatus"; + return dict; + } + + public async Task SendCommand(string vin, string command, string pin, string action) + { + ArgumentNullException.ThrowIfNull(_loginInfo); - DateTime refChargeEndTime = DateTime.Now; - - List haEntities = compactDetails.Select(detail => - { - - bool binary = false; - string deviceClass = ""; - string unit = ""; - string value = detail.Value; - - if (detail.Key.Contains("scheduleddays", StringComparison.InvariantCultureIgnoreCase) - || detail.Key.Contains("pluginstatus", StringComparison.InvariantCultureIgnoreCase) - || detail.Key.Contains("cabinpriority", StringComparison.InvariantCultureIgnoreCase) - || detail.Key.Contains("chargetofull", StringComparison.InvariantCultureIgnoreCase) - || detail.Key.Contains("enablescheduletype", StringComparison.InvariantCultureIgnoreCase) - || detail.Key.Contains("repeatschedule", StringComparison.InvariantCultureIgnoreCase) - ) - { - binary = true; - } - - if (detail.Key.Contains("battery_timetofullycharge", StringComparison.InvariantCultureIgnoreCase)) - { - deviceClass = "duration"; - unit = "min"; - } - - if (detail.Key.EndsWith("chargingstatus", StringComparison.InvariantCultureIgnoreCase)) - { - binary = true; - deviceClass = "battery_charging"; - if (detail.Value == "CHARGING") - { - value = "True"; - charging = true; - } - else - { - value = "False"; - charging = false; - } - - } - - if (detail.Key.EndsWith("evinfo_battery_charginglevel", StringComparison.InvariantCultureIgnoreCase)) - { - charginglevel = $"battery_timetofullychargel{detail.Value.Last()}"; - } - - if (detail.Key.EndsWith("battery_stateofcharge", StringComparison.InvariantCultureIgnoreCase)) - { - deviceClass = "battery"; - unit = "%"; - } - - if (detail.Key.EndsWith("evinfo_timestamp", StringComparison.InvariantCultureIgnoreCase)) - { - refChargeEndTime = GetLocalTime(Convert.ToInt64(detail.Value)); - } - - if (detail.Key.EndsWith("_timestamp", StringComparison.InvariantCultureIgnoreCase)) - { - value = GetLocalTime(Convert.ToInt64(detail.Value)).ToString("dd/MM HH:mm:ss"); - //deviceClass = "duration"; - } - - var sensor = new HaSensor(mqttClient, detail.Key, haDevice, binary) - { - DeviceClass = deviceClass, - Unit = unit, - Value = value, - }; - - return sensor as HaEntity; - }).ToList(); - - - var plugged = haEntities.OfType().Any(s => s.Name.EndsWith(batteryPluginstatus, StringComparison.InvariantCultureIgnoreCase) && s.Value.Equals("True", StringComparison.InvariantCultureIgnoreCase)); - if (plugged) - { - if (!vinPlugged.Contains(vehicle.Vin)) - { - vinPlugged.Add(vehicle.Vin); - } - } - else - { - vinPlugged.Remove(vehicle.Vin); - } + var (userUid, awsCredentials) = _loginInfo.Value; - var textChargeDuration = "0"; - var textChargeEndTime = "00:00"; - if (charging) + var data = new { - if (!vinCharging.Contains(vehicle.Vin)) - { - vinCharging.Add(vehicle.Vin); - forceLoopDeepEvent.Set(); - } + pin = Convert.ToBase64String(Encoding.UTF8.GetBytes(pin)) + }; - var chargeDuration = Convert.ToInt32(haEntities.OfType().Single(s => s.Name.EndsWith(charginglevel, StringComparison.InvariantCultureIgnoreCase)).Value); - textChargeDuration = $"{chargeDuration / 60}:{$"{chargeDuration % 60}".PadLeft(2, '0')}"; - textChargeEndTime = refChargeEndTime.AddMinutes(chargeDuration).ToString("H:mm"); - } - else - { - vinCharging.Remove(vehicle.Vin); - } + var pinAuthResponse = await _authUrl + .AppendPathSegments("v1", "accounts", userUid, "ignite", "pin", "authenticate") + .WithHeaders(WithAwsDefaultParameter(_authApiKey)) + .AwsSign(awsCredentials, _awsEndpoint, data) + .PostJsonAsync(data) + .ReceiveJson(); - haEntities.Add(new HaSensor(mqttClient, "Charge_Duration", haDevice, false) - { - // DeviceClass = "duration", - Value = textChargeDuration, - }); + Log.Debug("{0}", pinAuthResponse.Dump()); - haEntities.Add(new HaSensor(mqttClient, "Charge_Endtime", haDevice, false) + var json = new { - // DeviceClass = "duration", - Value = textChargeEndTime, - }); + command, + pinAuth = pinAuthResponse.Token + }; - var currentCarLocation = new Coordinate(vehicle.Location.Latitude, vehicle.Location.Longitude); + var commandResponse = await _apiUrl + .AppendPathSegments("v1", "accounts", userUid, "vehicles", vin, action) + .WithHeaders(WithAwsDefaultParameter(_apiKey)) + .AwsSign(awsCredentials, _awsEndpoint, json) + .PostJsonAsync(json) + .ReceiveJson(); - var zones = await haClient.GetZonesAscending(currentCarLocation); + Log.Debug("{0}", commandResponse.Dump()); + } - Log.Debug("Zones: {0}", zones.Dump()); + public async Task Fetch() + { + ArgumentNullException.ThrowIfNull(_loginInfo); - var tracker = new HaDeviceTracker(mqttClient, "Location", haDevice) - { - Lat = currentCarLocation.Latitude.ToDouble(), - Lon = currentCarLocation.Longitude.ToDouble(), - StateValue = zones.FirstOrDefault()?.FriendlyName ?? "Away" - }; + var (userUid, awsCredentials) = _loginInfo.Value; - haEntities.Add(tracker); + var vehicleResponse = await _apiUrl + .WithClient(_defaultHttpClient) + .AppendPathSegments("v4", "accounts", userUid, "vehicles") + .SetQueryParam("stage", "ALL") + .WithHeaders(WithAwsDefaultParameter(_apiKey)) + .AwsSign(awsCredentials, _awsEndpoint) + .GetJsonAsync(); - var trackerTimeStamp = new HaSensor(mqttClient, "Location_TimeStamp", haDevice, false) - { - Value = GetLocalTime(vehicle.Location.TimeStamp).ToString("dd/MM HH:mm:ss"), - //DeviceClass = "duration" - }; - - haEntities.Add(trackerTimeStamp); - - Log.Debug("Announce haEntities : {0}", haEntities.Dump()); + Log.Debug("{0}", vehicleResponse.Dump()); - return haEntities; -} + foreach (var vehicle in vehicleResponse.Vehicles) + { + var vehicleDetails = await _apiUrl + .WithClient(_defaultHttpClient) + .AppendPathSegments("v2", "accounts", userUid, "vehicles", vehicle.Vin, "status") + .WithHeaders(WithAwsDefaultParameter(_apiKey)) + .AwsSign(awsCredentials, _awsEndpoint) + .GetJsonAsync(); + + Log.Debug("{0}", vehicleDetails.Dump()); + + vehicle.Details = vehicleDetails; + + var vehicleLocation = await _apiUrl + .WithClient(_defaultHttpClient) + .AppendPathSegments("v1", "accounts", userUid, "vehicles", vehicle.Vin, "location", "lastknown") + .WithHeaders(WithAwsDefaultParameter(_apiKey)) + .AwsSign(awsCredentials, _awsEndpoint) + .GetJsonAsync(); + + vehicle.Location = vehicleLocation; + + Log.Debug("{0}", vehicleLocation.Dump()); + } -DateTime GetLocalTime(long timeStamp) -{ - return DateTimeOffset.FromUnixTimeMilliseconds(timeStamp).UtcDateTime.ToLocalTime(); + return vehicleResponse.Vehicles; + } }