// 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;
}
}
}