// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using Azure.Iot.Operations.Connector; using Azure.Iot.Operations.Connector.Files; using Azure.Iot.Operations.Services.AssetAndDeviceRegistry.Models; using System.Net.Http.Headers; using System.Text; using System.Text.Json; namespace RestThermostatConnector { internal class ThermostatStatusDatasetSampler : IDatasetSampler, IAsyncDisposable { private readonly HttpClient _httpClient; private readonly string _assetName; private readonly EndpointCredentials? _credentials; private readonly static JsonSerializerOptions _jsonSerializerOptions = new() { AllowTrailingCommas = true, }; public ThermostatStatusDatasetSampler(HttpClient httpClient, string assetName, EndpointCredentials? credentials) { _httpClient = httpClient; _assetName = assetName; _credentials = credentials; } /// /// Sample the datapoints from the HTTP thermostat and return the full serialized dataset. /// /// The dataset of an asset to sample. /// Cancellation token. /// The serialized payload containing the sampled dataset. public async Task SampleDatasetAsync(AssetDataset dataset, CancellationToken cancellationToken = default) { int retryCount = 0; while (true) { try { string httpServerTemperatureRequestPath = dataset.DataSource!; if (_credentials != null && _credentials.Username != null && _credentials.Password != null) { // Note that this sample uses username + password for authenticating the connection to the asset. In general, // x509 authentication should be used instead (if available) as it is more secure. string httpServerUsername = _credentials.Username; string httpServerPassword = _credentials.Password; var byteArray = Encoding.ASCII.GetBytes($"{httpServerUsername}:{httpServerPassword}"); _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray)); } var currentTemperatureStatusHttpResponse = await _httpClient.GetAsync(httpServerTemperatureRequestPath, cancellationToken); if (currentTemperatureStatusHttpResponse.StatusCode == System.Net.HttpStatusCode.Unauthorized) { throw new Exception("Failed to authorize request to HTTP server. Check credentials configured in rest-server-device-definition.yaml."); } currentTemperatureStatusHttpResponse.EnsureSuccessStatusCode(); var payloadAsBytes = await currentTemperatureStatusHttpResponse.Content.ReadAsByteArrayAsync(cancellationToken); RestThermostatStatus currentStatus = JsonSerializer.Deserialize(payloadAsBytes, _jsonSerializerOptions)!; // The HTTP response payload matches the expected message schema, so return it as-is return payloadAsBytes; } catch (Exception ex) { if (++retryCount >= 3) { throw new InvalidOperationException($"Failed to sample dataset with name {dataset.Name} in asset with name {_assetName}. Error: {ex.Message}", ex); } await Task.Delay(1000, cancellationToken); } } } public Task GetSamplingIntervalAsync(AssetDataset dataset, CancellationToken cancellationToken = default) { return Task.FromResult(TimeSpan.FromSeconds(3)); } public ValueTask DisposeAsync() { _httpClient.Dispose(); return ValueTask.CompletedTask; } } }