Skip to content

Commit

Permalink
Merge pull request #1649 from pkuehnel/feat/noFleetApiRequestAfterCom…
Browse files Browse the repository at this point in the history
…mandOnFleetTelemetry

feat(TeslaFleetApiService): do not request data after commands if using fleet telemetry
  • Loading branch information
pkuehnel authored Nov 25, 2024
2 parents f41d975 + 614e8ce commit 2fca176
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ public interface IFleetTelemetryWebSocketService
{
Task ReconnectWebSocketsForEnabledCars();
Task DisconnectWebSocketsByVin(string vin);
bool IsClientConnected(string vin);
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ public class FleetTelemetryWebSocketService(

private List<DtoFleetTelemetryWebSocketClients> Clients { get; set; } = new();

public bool IsClientConnected(string vin)
{
logger.LogTrace("{method}({vin})", nameof(IsClientConnected), vin);
return Clients.Any(c => c.Vin == vin && c.WebSocketClient.State == WebSocketState.Open);
}

public async Task ReconnectWebSocketsForEnabledCars()
{
logger.LogTrace("{method}", nameof(ReconnectWebSocketsForEnabledCars));
Expand Down
151 changes: 81 additions & 70 deletions TeslaSolarCharger/Server/Services/TeslaFleetApiService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ public class TeslaFleetApiService(
ISettings settings,
IBleService bleService,
IIssueKeys issueKeys,
ITeslaFleetApiTokenHelper teslaFleetApiTokenHelper)
ITeslaFleetApiTokenHelper teslaFleetApiTokenHelper,
IFleetTelemetryWebSocketService fleetTelemetryWebSocketService)
: ITeslaService, ITeslaFleetApiService
{
private const string IsChargingErrorMessage = "is_charging";
Expand Down Expand Up @@ -494,80 +495,89 @@ private async Task<bool> IsCarDataRefreshNeeded(DtoCar car)
}
logger.LogDebug("Latest car refresh: {latestRefresh}", latestRefresh);
var currentUtcDate = dateTimeProvider.UtcNow();
var homeGeofenceDistance = car.DistanceToHomeGeofence;
var earliestHomeArrival =
// ReSharper disable once PossibleLossOfFraction
latestRefresh.AddSeconds((homeGeofenceDistance ?? 0) / configurationWrapper.MaxTravelSpeedMetersPerSecond());
logger.LogDebug("Earliest Home arrival: {earliestHomeArrival}", earliestHomeArrival);
car.EarliestHomeArrival = earliestHomeArrival;
if (earliestHomeArrival > currentUtcDate)
{
logger.LogDebug("Do not refresh data for car {vin} as ealiest calculated home arrival is {ealiestHomeArrival}", car.Vin, earliestHomeArrival);
return false;
}

var latestCommandTimeStamp = car.WakeUpCalls
.Concat(car.ChargeStartCalls)
.Concat(car.ChargeStopCalls)
.Concat(car.SetChargingAmpsCall)
.Concat(car.OtherCommandCalls)
.OrderByDescending(c => c)
.FirstOrDefault();
if (latestCommandTimeStamp == default)
var earliestDetectedChange = currentUtcDate.AddSeconds(-configurationWrapper.CarRefreshAfterCommandSeconds());
if (fleetTelemetryWebSocketService.IsClientConnected(car.Vin))
{
latestCommandTimeStamp = dateTimeProvider.UtcNow().AddDays(-1);
}
logger.LogTrace("Fleet Telemetry Client connected, check fleet telemetry changes and do not request Fleet API after commands.");
if (await FleetTelemetryValueChanged(car.Id, CarValueType.IsCharging, latestRefresh, earliestDetectedChange).ConfigureAwait(false))
{
logger.LogDebug("Send a request as Fleet Telemetry detected a change in is charging in state.");
return true;
}

logger.LogDebug("Latest command Timestamp: {latestCommandTimeStamp}", latestCommandTimeStamp);
if (await FleetTelemetryValueChanged(car.Id, CarValueType.IsPluggedIn, latestRefresh, earliestDetectedChange).ConfigureAwait(false))
{
logger.LogDebug("Send a request as Fleet Telemetry detected a change in plugged in state.");
return true;
}

//Do not waste a request if the latest command was in the last few seconds. Request the next time instead
if (latestCommandTimeStamp > currentUtcDate.AddSeconds(-configurationWrapper.CarRefreshAfterCommandSeconds()))
{
logger.LogDebug("Do not refresh data as on {latestCommandTimeStamp} there was a command sent to the car.", latestCommandTimeStamp);
return false;
var values = await GetLatestTwoValues(car.Id, CarValueType.ChargeAmps, earliestDetectedChange).ConfigureAwait(false);
if (LatestValueChangeAfterLatestFleetApiRefresh(latestRefresh, values) && values.Any(v => v.DoubleValue == 0))
{
logger.LogDebug("Send a request as Fleet Telemetry detected at least one 0 value in charging amps.");
return true;
}
}

//Note: This needs to be after request waste check
if (latestCommandTimeStamp > latestRefresh)
else
{
logger.LogDebug("Send a request now as more than {carResfreshAfterCommand} s ago there was a command request", configurationWrapper.CarRefreshAfterCommandSeconds());
return true;
}
logger.LogTrace("Fleet Telemetry Client not connected, request Fleet API after commands.");
var homeGeofenceDistance = car.DistanceToHomeGeofence;
var earliestHomeArrival =
// ReSharper disable once PossibleLossOfFraction
latestRefresh.AddSeconds((homeGeofenceDistance ?? 0) / configurationWrapper.MaxTravelSpeedMetersPerSecond());
logger.LogDebug("Earliest Home arrival: {earliestHomeArrival}", earliestHomeArrival);
car.EarliestHomeArrival = earliestHomeArrival;
if (earliestHomeArrival > currentUtcDate)
{
logger.LogDebug("Do not refresh data for car {vin} as ealiest calculated home arrival is {ealiestHomeArrival}", car.Vin, earliestHomeArrival);
return false;
}

if (await FleetTelemetryValueChanged(car.Id, CarValueType.IsCharging, latestRefresh).ConfigureAwait(false))
{
logger.LogDebug("Send a request as Fleet Telemetry detected a change in is charging in state.");
return true;
}
var latestCommandTimeStamp = car.WakeUpCalls
.Concat(car.ChargeStartCalls)
.Concat(car.ChargeStopCalls)
.Concat(car.SetChargingAmpsCall)
.Concat(car.OtherCommandCalls)
.OrderByDescending(c => c)
.FirstOrDefault();
if (latestCommandTimeStamp == default)
{
latestCommandTimeStamp = dateTimeProvider.UtcNow().AddDays(-1);
}

if (await FleetTelemetryValueChanged(car.Id, CarValueType.IsPluggedIn, latestRefresh).ConfigureAwait(false))
{
logger.LogDebug("Send a request as Fleet Telemetry detected a change in plugged in state.");
return true;
}
logger.LogDebug("Latest command Timestamp: {latestCommandTimeStamp}", latestCommandTimeStamp);

var values = await GetLatestTwoValues(car.Id, CarValueType.ChargeAmps).ConfigureAwait(false);
if (LatestValueChangeAfterLatestFleetApiRefresh(latestRefresh, values) && values.Any(v => v.DoubleValue == 0))
{
logger.LogDebug("Send a request as Fleet Telemetry detected at least one 0 value in charging amps.");
return true;
}
//Do not waste a request if the latest command was in the last few seconds. Request the next time instead
if (latestCommandTimeStamp > earliestDetectedChange)
{
logger.LogDebug("Do not refresh data as on {latestCommandTimeStamp} there was a command sent to the car.", latestCommandTimeStamp);
return false;
}

var latestChargeStartOrWakeUp = car.WakeUpCalls.Concat(car.ChargeStartCalls).OrderByDescending(c => c).FirstOrDefault();
if (latestChargeStartOrWakeUp == default)
{
latestChargeStartOrWakeUp = dateTimeProvider.UtcNow().AddDays(-1);
}
logger.LogDebug("Latest wake or charge start Timestamp: {latestChargeStartOrWakeUp}", latestChargeStartOrWakeUp);
//force request after 55 seconds after start or wakeup as car takes much time to reach full charging speed
const int seconds = 55;
var forcedRequestTimeAfterStartOrWakeUp = latestChargeStartOrWakeUp + TimeSpan.FromSeconds(seconds);
if (currentUtcDate > forcedRequestTimeAfterStartOrWakeUp
&& latestRefresh < forcedRequestTimeAfterStartOrWakeUp)
{
logger.LogDebug("Within the last {seconds} seconds a charge start or wake call was sent to the car. Force vehicle data call now", seconds);
return true;
//Note: This needs to be after request waste check
if (latestCommandTimeStamp > latestRefresh)
{
logger.LogDebug("Send a request now as more than {carResfreshAfterCommand} s ago there was a command request", configurationWrapper.CarRefreshAfterCommandSeconds());
return true;
}

var latestChargeStartOrWakeUp = car.WakeUpCalls.Concat(car.ChargeStartCalls).OrderByDescending(c => c).FirstOrDefault();
if (latestChargeStartOrWakeUp == default)
{
latestChargeStartOrWakeUp = dateTimeProvider.UtcNow().AddDays(-1);
}
logger.LogDebug("Latest wake or charge start Timestamp: {latestChargeStartOrWakeUp}", latestChargeStartOrWakeUp);
//force request after 55 seconds after start or wakeup as car takes much time to reach full charging speed
const int seconds = 55;
var forcedRequestTimeAfterStartOrWakeUp = latestChargeStartOrWakeUp + TimeSpan.FromSeconds(seconds);
if (currentUtcDate > forcedRequestTimeAfterStartOrWakeUp
&& latestRefresh < forcedRequestTimeAfterStartOrWakeUp)
{
logger.LogDebug("Within the last {seconds} seconds a charge start or wake call was sent to the car. Force vehicle data call now", seconds);
return true;
}
}


if (latestRefresh.AddSeconds(car.ApiRefreshIntervalSeconds) < currentUtcDate)
{
Expand All @@ -578,9 +588,9 @@ private async Task<bool> IsCarDataRefreshNeeded(DtoCar car)
return false;
}

private async Task<bool> FleetTelemetryValueChanged(int carId, CarValueType carValueType, DateTime latestRefresh)
private async Task<bool> FleetTelemetryValueChanged(int carId, CarValueType carValueType, DateTime latestRefresh, DateTime currentUtcDate)
{
var values = await GetLatestTwoValues(carId, carValueType).ConfigureAwait(false);
var values = await GetLatestTwoValues(carId, carValueType, currentUtcDate.AddSeconds(-configurationWrapper.CarRefreshAfterCommandSeconds())).ConfigureAwait(false);

if (!LatestValueChangeAfterLatestFleetApiRefresh(latestRefresh, values))
{
Expand All @@ -601,7 +611,7 @@ private async Task<bool> FleetTelemetryValueChanged(int carId, CarValueType carV
&& (doubleValueChanged || intValueChanged || stringValueChanged || unknownValueChanged || booleanValueChanged || invalidValueChanged);
}

private static bool LatestValueChangeAfterLatestFleetApiRefresh(DateTime latestRefresh, List<CarValueLogTimeStampAndValues> values)
private bool LatestValueChangeAfterLatestFleetApiRefresh(DateTime latestRefresh, List<CarValueLogTimeStampAndValues> values)
{
//Only one value available
if (values.Count != 2)
Expand All @@ -618,12 +628,13 @@ private static bool LatestValueChangeAfterLatestFleetApiRefresh(DateTime latestR
return true;
}

private async Task<List<CarValueLogTimeStampAndValues>> GetLatestTwoValues(int carId, CarValueType carValueType)
private async Task<List<CarValueLogTimeStampAndValues>> GetLatestTwoValues(int carId, CarValueType carValueType, DateTime startTime)
{
var values = await teslaSolarChargerContext.CarValueLogs
.Where(c => c.Type == carValueType
&& c.Source == CarValueSource.FleetTelemetry
&& c.CarId == carId)
&& c.CarId == carId
&& c.Timestamp > startTime)
.OrderByDescending(c => c.Timestamp)
.Select(c => new CarValueLogTimeStampAndValues
{
Expand Down

0 comments on commit 2fca176

Please sign in to comment.