using System.Net.Http.Json; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Configuration; var host = Host.CreateDefaultBuilder(args) .ConfigureServices((context, services) => { services.AddHttpClient("Backend", client => { client.BaseAddress = new Uri("http://localhost:5000"); // Default backend URL }); services.AddHostedService(); }) .Build(); await host.RunAsync(); public class VehicleWorker : BackgroundService { private readonly IHttpClientFactory _httpClientFactory; private readonly ILogger _logger; private readonly IConfiguration _configuration; private string _vin; private string _currentVersion = "1.0.0"; private string _status = "Online"; public VehicleWorker(IHttpClientFactory httpClientFactory, ILogger logger, IConfiguration configuration) { _httpClientFactory = httpClientFactory; _logger = logger; _configuration = configuration; // Read VIN from args or config, or generate random _vin = _configuration["VIN"] ?? $"VIN-{Guid.NewGuid().ToString().Substring(0, 8).ToUpper()}"; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _logger.LogInformation($"Vehicle {_vin} starting up. Version: {_currentVersion}"); var client = _httpClientFactory.CreateClient("Backend"); // Initial Registration await RegisterAsync(client, stoppingToken); while (!stoppingToken.IsCancellationRequested) { try { await SendHeartbeatAsync(client, stoppingToken); await CheckForUpdatesAsync(client, stoppingToken); } catch (Exception ex) { _logger.LogError(ex, "Error in vehicle loop"); } await Task.Delay(5000, stoppingToken); // 5s Interval } } private async Task RegisterAsync(HttpClient client, CancellationToken ct) { try { var response = await client.PostAsJsonAsync("/api/vehicles/register", new { Vin = _vin, CurrentVersion = _currentVersion }, ct); if (response.IsSuccessStatusCode) { _logger.LogInformation("Successfully registered with backend."); } else { _logger.LogWarning($"Registration failed: {response.StatusCode}"); } } catch (Exception ex) { _logger.LogError($"Could not register: {ex.Message}"); } } private async Task SendHeartbeatAsync(HttpClient client, CancellationToken ct) { var response = await client.PostAsJsonAsync("/api/vehicles/heartbeat", new { Vin = _vin, Status = _status }, ct); if (!response.IsSuccessStatusCode) { _logger.LogWarning($"Heartbeat failed: {response.StatusCode}"); } } private async Task CheckForUpdatesAsync(HttpClient client, CancellationToken ct) { if (_status == "Updating") return; try { var response = await client.GetAsync($"/api/vehicles/updates?vin={_vin}", ct); if (response.StatusCode == System.Net.HttpStatusCode.OK) { var update = await response.Content.ReadFromJsonAsync(cancellationToken: ct); if (update != null) { _logger.LogInformation($"New update found: {update.Version} (ID: {update.UpdateId})"); await PerformUpdateAsync(client, update, ct); } } } catch (Exception ex) { _logger.LogError($"Error checking updates: {ex.Message}"); } } private async Task PerformUpdateAsync(HttpClient client, UpdateInfo update, CancellationToken ct) { _status = "Updating"; await SendHeartbeatAsync(client, ct); // Update status in backend _logger.LogInformation("Downloading update..."); // Simulate download await Task.Delay(3000, ct); // Ideally we would download the file here: // var bytes = await client.GetByteArrayAsync(update.DownloadUrl, ct); // _logger.LogInformation($"Downloaded {bytes.Length} bytes."); _logger.LogInformation("Installing update..."); await Task.Delay(5000, ct); _currentVersion = update.Version; _status = "Online"; _logger.LogInformation($"Update complete. Rebooting system... New Version: {_currentVersion}"); // Re-register with new version await RegisterAsync(client, ct); } } public class UpdateInfo { public int UpdateId { get; set; } public string Version { get; set; } = string.Empty; public string? Description { get; set; } public string DownloadUrl { get; set; } = string.Empty; }