diff --git a/FCAAddon/FCAClient/FiatClient.cs b/FCAAddon/FCAClient/FiatClient.cs new file mode 100644 index 0000000..00a939f --- /dev/null +++ b/FCAAddon/FCAClient/FiatClient.cs @@ -0,0 +1,358 @@ +using System.Text; +using Amazon; +using Amazon.CognitoIdentity; +using Amazon.Runtime; +using Flurl; +using Flurl.Http; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Serilog; + +namespace FCAUconnect; + +public enum FcaBrand +{ + Fiat, + Jeep, + AlfaRomeo, + Debug +} + +public enum FcaRegion +{ + Europe, + US +} + +public class FiatClient +{ + private readonly string _loginApiKey = ""; + private readonly string _apiKey= ""; + private readonly string _loginUrl= ""; + private readonly string _tokenUrl= ""; + private readonly string _apiUrl= ""; + private readonly string _authApiKey= ""; // for pin + private readonly string _authUrl= ""; // for pin + private readonly string _locale = ""; // for pin + private readonly RegionEndpoint _awsEndpoint = RegionEndpoint.EUWest1; + + private readonly string _user; + private readonly string _password; + private readonly CookieJar _cookieJar = new(); + + private readonly FcaBrand _brand; + private readonly FcaRegion _region; + + private readonly IFlurlClient _defaultHttpClient; + + private (string userUid, ImmutableCredentials awsCredentials)? _loginInfo = null; + + public FiatClient(string user, string password, FcaBrand brand = FcaBrand.Fiat, FcaRegion region = FcaRegion.Europe) + { + _user = user; + _password = password; + _brand = brand; + _region = region; + + if (_region == FcaRegion.Europe) + { + _loginApiKey = "3_mOx_J2dRgjXYCdyhchv3b5lhi54eBcdCTX4BI8MORqmZCoQWhA0mV2PTlptLGUQI"; + + _apiKey = "qLYupk65UU1tw2Ih1cJhs4izijgRDbir2UFHA3Je"; + _tokenUrl = "https://authz.sdpr-01.fcagcv.com/v2/cognito/identity/token"; + _apiUrl = "https://channels.sdpr-01.fcagcv.com"; + + _authApiKey = "JWRYW7IYhW9v0RqDghQSx4UcRYRILNmc8zAuh5ys"; // for pin + _authUrl = "https://mfa.fcl-01.fcagcv.com"; // for pin + + _locale = "de_de"; // for pin + _awsEndpoint = RegionEndpoint.EUWest1; + } + else + { + _loginApiKey = "3_FSxGyaktviayTDRcgp9r9o2KjuFSrHT13wWNN9zPrvAGUCoXPDqoIPOwlBUhck4A"; + + _apiKey = "OgNqp2eAv84oZvMrXPIzP8mR8a6d9bVm1aaH9LqU"; + _tokenUrl = "https://authz.sdpr-02.fcagcv.com/v2/cognito/identity/token"; + _apiUrl = "https://channels.sdpr-02.fcagcv.com"; + + _authApiKey = "fNQO6NjR1N6W0E5A6sTzR3YY4JGbuPv48Nj9aZci"; // UNKNOWN + _authUrl = "https://mfa.fcl-02.fcagcv.com"; // UNKNOWN + + _locale = "en_us"; + _awsEndpoint = RegionEndpoint.USEast1; + } + + if (_brand == FcaBrand.Debug) + { + _loginApiKey = "3_etlYkCXNEhz4_KJVYDqnK1CqxQjvJStJMawBohJU2ch3kp30b0QCJtLCzxJ93N-M"; + _loginUrl = "https://login-us.alfaromeo.com"; + _apiKey = "2wGyL6PHec9o1UeLPYpoYa1SkEWqeBur9bLsi24i"; + _tokenUrl = "https://authz.sdpr-01.fcagcv.com/v2/cognito/identity/token"; + _apiUrl = "https://channels.sdpr-01.fcagcv.com"; + _authApiKey = "JWRYW7IYhW9v0RqDghQSx4UcRYRILNmc8zAuh5ys"; // for pin + _authUrl = "https://mfa.fcl-01.fcagcv.com"; // for pin + _locale = "en_us"; + _awsEndpoint = RegionEndpoint.USEast1; + } + else if (_brand == FcaBrand.Fiat) + { + if (_region == FcaRegion.Europe) + { + _loginUrl = "https://loginmyuconnect.fiat.com"; + } + else + { + _loginUrl = "https://login-us.fiat.com"; + } + } + else if (_brand == FcaBrand.AlfaRomeo) + { + if (_region == FcaRegion.Europe) + { + _loginUrl = "https://login.alfaromeo.com"; + } + else + { + _loginUrl = "https://login-us.alfaromeo.com"; + } + } + else if (_brand == FcaBrand.Jeep) + { + if (_region == FcaRegion.Europe) + { + _loginUrl = "https://login.jeep.com"; + } + else + { + _loginUrl = "https://login-us.jeep.com"; + } + } + + + _defaultHttpClient = new FlurlClient().Configure(settings => + { + settings.HttpClientFactory = new PollyHttpClientFactory(); + }); + } + + public async Task LoginAndKeepSessionAlive() + { + if (_loginInfo is not null) + return; + + await this.Login(); + + _ = Task.Run(async () => + { + var timer = new PeriodicTimer(TimeSpan.FromMinutes(2)); + + while (await timer.WaitForNextTickAsync()) + { + try + { + Log.Information("Refresh Session"); + await this.Login(); + } + catch (Exception e) + { + + Log.Error("ERROR WHILE REFRESH SESSION"); + Log.Debug("login {0}", e); + } + } + }); + } + + private async Task Login() + { + var loginResponse = await _loginUrl + .WithClient(_defaultHttpClient) + .AppendPathSegment("accounts.webSdkBootstrap") + .SetQueryParam("apiKey", _loginApiKey) + .WithCookies(_cookieJar) + .GetJsonAsync(); + + Log.Debug("loginResponse: {0}", loginResponse.Dump()); + + loginResponse.ThrowOnError("Login failed."); + + var authResponse = await _loginUrl + .WithClient(_defaultHttpClient) + .AppendPathSegment("accounts.login") + .WithCookies(_cookieJar) + .PostUrlEncodedAsync( + WithFiatDefaultParameter(new() + { + { "loginID", _user }, + { "password", _password }, + { "sessionExpiration", TimeSpan.FromMinutes(5).TotalSeconds }, + { "include", "profile,data,emails,subscriptions,preferences" }, + })) + .ReceiveJson(); + + Log.Debug("authResponse : {0}", authResponse.Dump()); + + authResponse.ThrowOnError("Authentication failed."); + + var jwtResponse = await _loginUrl + .WithClient(_defaultHttpClient) + .AppendPathSegment("accounts.getJWT") + .SetQueryParams( + WithFiatDefaultParameter(new() + { + { "fields", "profile.firstName,profile.lastName,profile.email,country,locale,data.disclaimerCodeGSDP" }, + { "login_token", authResponse.SessionInfo.LoginToken } + })) + .WithCookies(_cookieJar) + .GetJsonAsync(); + + Log.Debug("jwtResponse : {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("identityResponse : {0}", identityResponse.Dump()); + + identityResponse.ThrowOnError("Identity failed."); + + var client = new AmazonCognitoIdentityClient(new AnonymousAWSCredentials(), _awsEndpoint); + + var res = await client.GetCredentialsForIdentityAsync(identityResponse.IdentityId, + new Dictionary() + { + { "cognito-identity.amazonaws.com", identityResponse.Token } + }); + + _loginInfo = (authResponse.UID, new ImmutableCredentials(res.Credentials.AccessKeyId, + res.Credentials.SecretKey, + res.Credentials.SessionToken)); + } + + private Dictionary WithAwsDefaultParameter(string apiKey, Dictionary? parameters = null) + { + var dict = new Dictionary() + { + { "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" }, + }; + + foreach (var parameter in parameters ?? new()) + dict.Add(parameter.Key, parameter.Value); + + return dict; + } + + 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 }, + }; + + foreach (var parameter in parameters ?? new()) + dict.Add(parameter.Key, parameter.Value); + + return dict; + } + + public async Task SendCommand(string vin, string command, string pin, string action) + { + ArgumentNullException.ThrowIfNull(_loginInfo); + + var (userUid, awsCredentials) = _loginInfo.Value; + + var data = new + { + pin = Convert.ToBase64String(Encoding.UTF8.GetBytes(pin)) + }; + + var pinAuthResponse = await _authUrl + .AppendPathSegments("v1", "accounts", userUid, "ignite", "pin", "authenticate") + .WithHeaders(WithAwsDefaultParameter(_authApiKey)) + .AwsSign(awsCredentials, _awsEndpoint, data) + .PostJsonAsync(data) + .ReceiveJson(); + + Log.Debug("pinAuthResponse: {0}", pinAuthResponse.Dump()); + + var json = new + { + command, + pinAuth = pinAuthResponse.Token + }; + + var commandResponse = await _apiUrl + .AppendPathSegments("v1", "accounts", userUid, "vehicles", vin, action) + .WithHeaders(WithAwsDefaultParameter(_apiKey)) + .AwsSign(awsCredentials, _awsEndpoint, json) + .PostJsonAsync(json) + .ReceiveJson(); + + Log.Debug("commandResponse: {0}", commandResponse.Dump()); + } + + public async Task Fetch() + { + ArgumentNullException.ThrowIfNull(_loginInfo); + + var (userUid, awsCredentials) = _loginInfo.Value; + + var vehicleResponse = await _apiUrl + .WithClient(_defaultHttpClient) + .AppendPathSegments("v4", "accounts", userUid, "vehicles") + .SetQueryParam("stage", "ALL") + .WithHeaders(WithAwsDefaultParameter(_apiKey)) + .AwsSign(awsCredentials, _awsEndpoint) + .GetJsonAsync(); + + Log.Debug("vehicleResponse: {0}", vehicleResponse.Dump()); + + 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("vehicleDetails: {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("vehicleLocation: {0}", vehicleLocation.Dump()); + } + + return vehicleResponse.Vehicles; + } +} + diff --git a/FCAAddon/FCAClient/FiatCommand.cs b/FCAAddon/FCAClient/FiatCommand.cs new file mode 100644 index 0000000..46ca8e9 --- /dev/null +++ b/FCAAddon/FCAClient/FiatCommand.cs @@ -0,0 +1,16 @@ +public class FiatCommand +{ + public static readonly FiatCommand DEEPREFRESH = new() { Action = "ev", Message = "DEEPREFRESH" }; + public static readonly FiatCommand VF = new() { Action = "location", Message = "VF" }; + public static readonly FiatCommand HBLF = new() { Message = "HBLF" }; + public static readonly FiatCommand REON = new() { Message = "REON" }; + public static readonly FiatCommand REOFF = new() { Message = "REOFF" }; + public static readonly FiatCommand TA = new() { Message = "TA" }; + public static readonly FiatCommand ROTRUNKLOCK = new() { Message = "ROTRUNKLOCK" }; + public static readonly FiatCommand ROTRUNKUNLOCK = new() { Message = "ROTRUNKUNLOCK" }; + public static readonly FiatCommand ROPRECOND = new() { Message = "ROPRECOND" }; + public static readonly FiatCommand RDU = new() { Message = "RDU" }; + public static readonly FiatCommand RDL = new() { Message = "RDL" }; + public required string Message { get; init; } + public string Action { get; init; } = "remote"; +} diff --git a/FCAAddon/FCAClient/FiatResponse.cs b/FCAAddon/FCAClient/FiatResponse.cs new file mode 100644 index 0000000..0f3eb1f --- /dev/null +++ b/FCAAddon/FCAClient/FiatResponse.cs @@ -0,0 +1,161 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +public abstract class BaseResponse +{ + public abstract bool CheckForError(); + + public abstract void ThrowOnError(string message); +} + +public class FiatResponse +{ + public string CallId { get; set; } + public long ErrorCode { get; set; } + public string ErrorDetails { get; set; } + public string ErrorMessage { get; set; } + public long ApiVersion { get; set; } + public long StatusCode { get; set; } + public string StatusReason { get; set; } + public DateTimeOffset Time { get; set; } + + public bool CheckForError() + { + return StatusCode != 200; + } + + public void ThrowOnError(string message) + { + if (CheckForError()) + { + throw new Exception(message + $" {this.ErrorCode} {this.StatusReason} {this.ErrorMessage}"); + } + } +} + +public class FiatLoginResponse : FiatResponse +{ +} + +public class FiatAuthResponse : FiatResponse +{ + public string UID { get; set; } + public FiatSessionInfo SessionInfo { get; set; } +} + +public class FiatJwtResponse : FiatResponse +{ + [JsonProperty("id_token")] public string IdToken { get; set; } +} + +public class FcaIdentityResponse : BaseResponse +{ + public string IdentityId { get; set; } + public string Token { get; set; } + + public override bool CheckForError() + { + return string.IsNullOrEmpty(Token) || string.IsNullOrEmpty(IdentityId); + } + + public override void ThrowOnError(string message) + { + if (CheckForError()) + { + throw new Exception(message); + } + } +} + +public class FiatSessionInfo +{ + [JsonProperty("login_token")] public string LoginToken { get; set; } +} + +public partial class AwsCognitoIdentityResponse +{ + public AwsCognitoIdentityCredentials Credentials { get; set; } + public string IdentityId { get; set; } +} + +public class AwsCognitoIdentityCredentials +{ + public string AccessKeyId { get; set; } + public long Expiration { get; set; } + public string SecretKey { get; set; } + public string SessionToken { get; set; } +} + +public class VehicleResponse +{ + public string Userid { get; set; } + public long Version { get; set; } + public Vehicle[] Vehicles { get; set; } +} + +public class Vehicle +{ + public string RegStatus { get; set; } + public string Color { get; set; } + public long Year { get; set; } + public string TsoBodyCode { get; set; } + public bool NavEnabledHu { get; set; } + public string Language { get; set; } + public string CustomerRegStatus { get; set; } + public string Radio { get; set; } + public string ActivationSource { get; set; } + public string? Nickname { get; set; } + public string Vin { get; set; } + public string Company { get; set; } + public string Model { get; set; } + public string ModelDescription { get; set; } + public long TcuType { get; set; } + public string Make { get; set; } + public string BrandCode { get; set; } + public string SoldRegion { get; set; } + [JsonIgnore] public JObject Details { get; set; } + [JsonIgnore] public VehicleLocation Location { get; set; } +} + +public class VehicleLocation +{ + public long TimeStamp { get; set; } + public double Longitude { get; set; } + public double Latitude { get; set; } + public double? Altitude { get; set; } + public object? Bearing { get; set; } + public bool? IsLocationApprox { get; set; } +} + +public class Battery +{ + public long StateOfCharge { get; set; } + public string ChargingLevel { get; set; } + public bool PlugInStatus { get; set; } + public long TimeToFullyChargeL3 { get; set; } + public long TimeToFullyChargeL2 { get; set; } + public string ChargingStatus { get; set; } + public long TotalRange { get; set; } + public DistanceToEmpty DistanceToEmpty { get; set; } +} + +public class DistanceToEmpty +{ + public long Value { get; set; } + public string Unit { get; set; } +} + +public class FcaPinAuthResponse +{ + public long Expiry { get; set; } + public string Token { get; set; } +} + +public class FcaCommandResponse +{ + public string Command { get; set; } + public Guid CorrelationId { get; set; } + public string ResponseStatus { get; set; } + public long StatusTimestamp { get; set; } + public long AsyncRespTimeout { get; set; } +} \ No newline at end of file diff --git a/FCAAddon/FCAClient/FiatUconnect.csproj b/FCAAddon/FCAClient/FiatUconnect.csproj new file mode 100644 index 0000000..4b5cad3 --- /dev/null +++ b/FCAAddon/FCAClient/FiatUconnect.csproj @@ -0,0 +1,24 @@ + + + + Exe + net7.0 + enable + enable + + + + + + + + + + + + + + + + + diff --git a/FCAAddon/FCAClient/HomeAssistant.cs b/FCAAddon/FCAClient/HomeAssistant.cs index 7590edc..81380ee 100644 --- a/FCAAddon/FCAClient/HomeAssistant.cs +++ b/FCAAddon/FCAClient/HomeAssistant.cs @@ -107,9 +107,9 @@ public class HaDeviceTracker : HaEntity public HaDeviceTracker(SimpleMqttClient mqttClient, string name, HaDevice haDevice) : base(mqttClient, name, haDevice) { - _stateTopic = $"homeassistant/sensor/FCA_uconnect/{_id}/state"; - _configTopic = $"homeassistant/sensor/FCA_uconnect/{_id}/config"; - _attributesTopic = $"homeassistant/sensor/FCA_uconnect/{_id}/attributes"; + _stateTopic = $"homeassistant/sensor/fiat_uconnect/{_id}/state"; + _configTopic = $"homeassistant/sensor/fiat_uconnect/{_id}/config"; + _attributesTopic = $"homeassistant/sensor/fiat_uconnect/{_id}/attributes"; } public override async Task PublishState() @@ -166,8 +166,8 @@ public HaSensor(SimpleMqttClient mqttClient, string name, HaDevice haDevice, boo var typeSensor = $"{(_binary ? "binary_sensor" : "sensor")}"; - _stateTopic = $"homeassistant/{typeSensor}/FCA_uconnect/{_id}/state"; - _configTopic = $"homeassistant/{typeSensor}/FCA_uconnect/{_id}/config"; + _stateTopic = $"homeassistant/{typeSensor}/fiat_uconnect/{_id}/state"; + _configTopic = $"homeassistant/{typeSensor}/fiat_uconnect/{_id}/config"; } public override async Task PublishState() @@ -217,8 +217,8 @@ public class HaButton : HaEntity public HaButton(SimpleMqttClient mqttClient, string name, HaDevice haDevice, Func onPressedCommand) : base(mqttClient, name, haDevice) { - _commandTopic = $"homeassistant/button/FCA_uconnect/{_id}/set"; - _configTopic = $"homeassistant/button/FCA_uconnect/{_id}/config"; + _commandTopic = $"homeassistant/button/fiat_uconnect/{_id}/set"; + _configTopic = $"homeassistant/button/fiat_uconnect/{_id}/config"; _ = mqttClient.Sub(_commandTopic, async _ => { @@ -268,9 +268,9 @@ public void SwitchTo(bool onOrOff) public HaSwitch(SimpleMqttClient mqttClient, string name, HaDevice haDevice, Func onSwitchCommand) : base(mqttClient, name, haDevice) { - _commandTopic = $"homeassistant/switch/FCA_uconnect/{_id}/set"; - _stateTopic = $"homeassistant/switch/FCA_uconnect/{_id}/state"; - _configTopic = $"homeassistant/switch/FCA_uconnect/{_id}/config"; + _commandTopic = $"homeassistant/switch/fiat_uconnect/{_id}/set"; + _stateTopic = $"homeassistant/switch/fiat_uconnect/{_id}/state"; + _configTopic = $"homeassistant/switch/fiat_uconnect/{_id}/config"; _ = mqttClient.Sub(_commandTopic, async message => { diff --git a/FCAAddon/FCAClient/Program.cs b/FCAAddon/FCAClient/Program.cs index 9ab00f5..059861b 100644 --- a/FCAAddon/FCAClient/Program.cs +++ b/FCAAddon/FCAClient/Program.cs @@ -2,8 +2,8 @@ using System.Globalization; using Cocona; using CoordinateSharp; -using FCAUconnect; -using FCAUconnect.HA; +using FiatUconnect; +using FiatUconnect.HA; using Flurl.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -13,7 +13,7 @@ var builder = CoconaApp.CreateBuilder(); -builder.Configuration.AddEnvironmentVariables("FCAUconnect_"); +builder.Configuration.AddEnvironmentVariables("FiatUconnect_"); builder.Services.AddOptions() .Bind(builder.Configuration) @@ -46,9 +46,9 @@ await app.RunAsync(async (CoconaAppContext ctx) => Log.Information("{0}", appConfig.ToStringWithoutSecrets()); Log.Debug("{0}", appConfig.Dump()); - FCAClient fcaClient = new FCAClient(appConfig.FCAUser, appConfig.FCAPw, appConfig.Brand, appConfig.Region); + FiatClient fiatClient = new FiatClient(appConfig.FiatUser, appConfig.FiatPw, appConfig.Brand, appConfig.Region); - var mqttClient = new SimpleMqttClient(appConfig.MqttServer, appConfig.MqttPort, appConfig.MqttUser, appConfig.MqttPw, "FCAUconnect"); + var mqttClient = new SimpleMqttClient(appConfig.MqttServer, appConfig.MqttPort, appConfig.MqttUser, appConfig.MqttPw, "FiatUconnect"); await mqttClient.Connect(); @@ -60,7 +60,7 @@ await app.RunAsync(async (CoconaAppContext ctx) => if (!ctx.CancellationToken.IsCancellationRequested && appConfig.AutoDeepRefresh && vinCharging.Any()) { Log.Information("AutoDeepRefresh"); - foreach(string vin in vinCharging) { await TrySendCommand(fcaClient, FCACommand.DEEPREFRESH, vin); } + 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(); @@ -78,9 +78,9 @@ await app.RunAsync(async (CoconaAppContext ctx) => try { - await fcaClient.LoginAndKeepSessionAlive(); + await fiatClient.LoginAndKeepSessionAlive(); - foreach (var vehicle in await fcaClient.Fetch()) + foreach (var vehicle in await fiatClient.Fetch()) { Log.Information($"Found : {vehicle.Nickname} {vehicle.Vin} {vehicle.ModelDescription}"); @@ -101,7 +101,7 @@ await app.RunAsync(async (CoconaAppContext ctx) => await Parallel.ForEachAsync(haEntities, async (sensor, token) => { await sensor.Announce(); }); Log.Information("Pushing new buttons to Home Assistant"); - var haInteractiveEntities = CreateInteractiveEntities(ctx, fcaClient, mqttClient, vehicle, haDevice); + var haInteractiveEntities = CreateInteractiveEntities(ctx, fiatClient, mqttClient, vehicle, haDevice); await Parallel.ForEachAsync(haInteractiveEntities, async (button, token) => { await button.Announce(); }); } @@ -116,7 +116,7 @@ await app.RunAsync(async (CoconaAppContext ctx) => } catch (FlurlHttpException httpException) { - Log.Warning($"Error connecting to the FCA API. \n" + + 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); @@ -139,20 +139,20 @@ await app.RunAsync(async (CoconaAppContext ctx) => } }); -async Task TrySendCommand(FCAClient fcaClient, FCACommand command, string vin) +async Task TrySendCommand(FiatClient fiatClient, FiatCommand command, string vin) { Log.Information("SEND COMMAND {0}: ", command.Message); - if (string.IsNullOrWhiteSpace(appConfig.FCAPin)) + if (string.IsNullOrWhiteSpace(appConfig.FiatPin)) { throw new Exception("PIN NOT SET"); } - var pin = appConfig.FCAPin; + var pin = appConfig.FiatPin; try { - await fcaClient.SendCommand(vin, command.Message, pin, command.Action); + await fiatClient.SendCommand(vin, command.Message, pin, command.Action); await Task.Delay(TimeSpan.FromSeconds(5)); Log.Information("Command: {0} SUCCESSFUL", command.Message); } @@ -168,12 +168,12 @@ async Task TrySendCommand(FCAClient fcaClient, FCACommand command, string -IEnumerable CreateInteractiveEntities(CoconaAppContext ctx, FCAClient fcaClient, SimpleMqttClient mqttClient, Vehicle vehicle, +IEnumerable CreateInteractiveEntities(CoconaAppContext ctx, FiatClient fiatClient, SimpleMqttClient mqttClient, Vehicle vehicle, HaDevice haDevice) { var updateLocationButton = new HaButton(mqttClient, "UpdateLocation", haDevice, async button => { - if (await TrySendCommand(fcaClient, FCACommand.VF, vehicle.Vin)) + if (await TrySendCommand(fiatClient, FiatCommand.VF, vehicle.Vin)) { await Task.Delay(TimeSpan.FromSeconds(6), ctx.CancellationToken); forceLoopResetEvent.Set(); @@ -184,7 +184,7 @@ IEnumerable CreateInteractiveEntities(CoconaAppContext ctx, FCAClient { if (vinPlugged.Contains(vehicle.Vin)) { - if (await TrySendCommand(fcaClient, FCACommand.DEEPREFRESH, vehicle.Vin)) + if (await TrySendCommand(fiatClient, FiatCommand.DEEPREFRESH, vehicle.Vin)) { await Task.Delay(TimeSpan.FromSeconds(6), ctx.CancellationToken); forceLoopResetEvent.Set(); @@ -194,7 +194,7 @@ IEnumerable CreateInteractiveEntities(CoconaAppContext ctx, FCAClient var lightsButton = new HaButton(mqttClient, "Light", haDevice, async button => { - if (await TrySendCommand(fcaClient, FCACommand.HBLF, vehicle.Vin)) + if (await TrySendCommand(fiatClient, FiatCommand.HBLF, vehicle.Vin)) { forceLoopResetEvent.Set(); } @@ -203,7 +203,7 @@ IEnumerable CreateInteractiveEntities(CoconaAppContext ctx, FCAClient var hvacButton = new HaButton(mqttClient, "HVAC", haDevice, async button => { - if (await TrySendCommand(fcaClient, FCACommand.ROPRECOND, vehicle.Vin)) + if (await TrySendCommand(fiatClient, FiatCommand.ROPRECOND, vehicle.Vin)) { forceLoopResetEvent.Set(); } @@ -211,7 +211,7 @@ IEnumerable CreateInteractiveEntities(CoconaAppContext ctx, FCAClient var startengineButton = new HaButton(mqttClient, "StartEngine", haDevice, async button => { - if (await TrySendCommand(fcaClient, FCACommand.REON, vehicle.Vin)) + if (await TrySendCommand(fiatClient, FiatCommand.REON, vehicle.Vin)) { forceLoopResetEvent.Set(); } @@ -219,7 +219,7 @@ IEnumerable CreateInteractiveEntities(CoconaAppContext ctx, FCAClient var stopengineButton = new HaButton(mqttClient, "StopEngine", haDevice, async button => { - if (await TrySendCommand(fcaClient, FCACommand.REOFF, vehicle.Vin)) + if (await TrySendCommand(fiatClient, FiatCommand.REOFF, vehicle.Vin)) { forceLoopResetEvent.Set(); } @@ -228,7 +228,7 @@ IEnumerable CreateInteractiveEntities(CoconaAppContext ctx, FCAClient var lockButton = new HaButton(mqttClient, "DoorLock", haDevice, async button => { - if (await TrySendCommand(fcaClient, FCACommand.RDL, vehicle.Vin)) + if (await TrySendCommand(fiatClient, FiatCommand.RDL, vehicle.Vin)) { forceLoopResetEvent.Set(); } @@ -236,7 +236,7 @@ IEnumerable CreateInteractiveEntities(CoconaAppContext ctx, FCAClient var unLockButton = new HaButton(mqttClient, "DoorUnlock", haDevice, async button => { - if (await TrySendCommand(fcaClient, FCACommand.RDU, vehicle.Vin)) + if (await TrySendCommand(fiatClient, FiatCommand.RDU, vehicle.Vin)) { forceLoopResetEvent.Set(); } @@ -250,7 +250,7 @@ IEnumerable CreateInteractiveEntities(CoconaAppContext ctx, FCAClient var suppressalarmButton = new HaButton(mqttClient, "SuppressAlarm", haDevice, async button => { - if (await TrySendCommand(fcaClient, FCACommand.TA, vehicle.Vin)) + if (await TrySendCommand(fiatClient, FiatCommand.TA, vehicle.Vin)) { forceLoopResetEvent.Set(); } @@ -258,7 +258,7 @@ IEnumerable CreateInteractiveEntities(CoconaAppContext ctx, FCAClient var locktrunkButton = new HaButton(mqttClient, "LockTrunk", haDevice, async button => { - if (await TrySendCommand(fcaClient, FCACommand.ROTRUNKLOCK, vehicle.Vin)) + if (await TrySendCommand(fiatClient, FiatCommand.ROTRUNKLOCK, vehicle.Vin)) { forceLoopResetEvent.Set(); } @@ -266,7 +266,7 @@ IEnumerable CreateInteractiveEntities(CoconaAppContext ctx, FCAClient var unlocktrunkButton = new HaButton(mqttClient, "UnLockTrunk", haDevice, async button => { - if (await TrySendCommand(fcaClient, FCACommand.ROTRUNKUNLOCK, vehicle.Vin)) + if (await TrySendCommand(fiatClient, FiatCommand.ROTRUNKUNLOCK, vehicle.Vin)) { forceLoopResetEvent.Set(); } diff --git a/FCAAddon/translations/en.yaml b/FCAAddon/translations/en.yaml index 537cd42..bb17a3f 100644 --- a/FCAAddon/translations/en.yaml +++ b/FCAAddon/translations/en.yaml @@ -1,48 +1,54 @@ --- configuration: - FCAUser: + FiatUser: name: Login user - description: Your FCA Uconnect username/email. - FCAPw: + description: Your fiat uconnect user. + FiatPw: name: Password - description: Your FCA Uconnect password. - FCAPin: + description: Your fiat uconnect password. + FiatPin: name: Pin - description: Your FCA PIN. Used for sending commands to the car. + description: Your PIN. Used for sending commands to the car. CarUnknownLocation: name: Car unknown location status description: Car location sensor status if car is not in a known zone. StartDelaySeconds: name: Start delay in seconds - description: Delay start of addon. Useful if you want to wait for other addons to start first (like slow MQTT startup). + description: Delay start of addon. Useful if you want to wait for other addons to start first (like slow mqtt startup). + ConvertKmToMiles: + name: Force convert km to miles + description: Not needed if your Home Assistant config is set to imperial system. Force try to convert kilometer values to miles. Brand: name: Brand - description: Jeep and Ram support is new. Dodge and AlfaRomeo is experimental - it may not work. + description: Jeep and Ram support is new. Dodge and AlfraRomeo is experimental it may not work. Region: name: Region description: Some cars need the correct region to work. RefreshInterval: name: Refresh interval - description: Fetch new data from api every X minutes. + description: Fetch new data from api every N minutes. Debug: name: Debug - description: Enable debug logging. It will dump a lot of information to the log including session tokens and sensitive information. + description: Enable debug logging. It will dump many informations to the log including session tokens and sensitive informations. + EnableDangerousCommands: + name: Dangerous commands + description: Enable commands that are potentially dangerous and/or experimental. Like unlocking doors or deactivate air conditioning. AutoRefreshBattery: name: Force refresh battery status - description: Automatic battery status update every refresh interval. This will consume more battery power. + description: Automatic update battery status every refresh interval. This will consume more battery power. AutoRefreshLocation: name: Force refresh location - description: Automatic location update every refresh interval. This will consume more battery power. + description: Automatic update location every refresh interval. This will consume more battery power. OverrideMqttUser: - name: Override MQTT User - description: Override MQTT settings if you are using an external broker. You do not need this if you are using the official Home Assistant MQTT addon. + name: Override mqtt user + description: Override mqtt settings if you are using an external broker. you do not need this if you are using the official home assistant mqtt addon. OverrideMqttPw: - name: Override MQTT PW - description: Override MQTT Password if you are using an external broker. You do not need this if you are using the official Home Assistant MQTT addon. + name: Override mqtt pw + description: Override mqtt settings if you are using an external broker. you do not need this if you are using the official home assistant mqtt addon. OverrideMqttServer: - name: Override MQTT server - description: Override MQTT Server URL if you are using an external broker. You do not need this if you are using the official Home Assistant MQTT addon. + name: Override mqtt server. + description: Override mqtt settings if you are using an external broker. you do not need this if you are using the official home assistant mqtt addon. OverrideMqttPort: - name: Override MQTT port. - description: Override MQTT settings if you are using an external broker. You do not need this if you are using the official Home Assistant MQTT addon. + name: Override mqtt port. + description: Override mqtt settings if you are using an external broker. you do not need this if you are using the official home assistant mqtt addon.