En lugar de seguir buscando codigo y ya que todo, o casi, en Azure tiene API REST empecé a leer la documentación de la API y a codear.
Si quieren probar el codigo de este post, a parte un account de Azure, necesitan crear un IoTHub y tener a mano tres parametros:
1) el host del IoTHub que creaste
2) El nombre de la policy. En este caso, aunque ya tienen policies definida por default, le conviene crear una policy nueva.
3) la key de la policy. Como key pueden usar la primary o la secondary; en muchos servicios en Azure siempre tienen dos keys para cambiar/regenerar la key sin sufrir downtime.
Ya tenemos los ingredientes arriba la mesada y podemos empezar a prepararlos para la cocción.
Ya hace un tiempito que en Azure muchos servicios gozan de la fantastica SAS (Shared Access Signature) que, basicamente, nos permite compartir recursos y/o servicios sin por eso tener que hacer “viajar” las keys o utilizar procesos de auth que pueden ser más complejos como OAuth2. La SAS viaja plácidamente en el query string o en los headers de un request REST. En este caso usaremos la SAS para evitar el obscuro MitM (man in the middle) creando una SAS, usable por un tiempo relativamente corto (TTL time to live), para enviar mensajes al Azure IoT Hub. Una SAS para, condimentar un request al IoT Hub, se prepara de esta forma:
private static readonly DateTime epochTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
public static string SharedAccessSignature(string hostUrl, string policyName, string policyAccessKey, TimeSpan timeToLive)
{
if (string.IsNullOrWhiteSpace(hostUrl))
{
throw new ArgumentNullException(nameof(hostUrl));
}
var expires = Convert.ToInt64(DateTime.UtcNow.Add(timeToLive).Subtract(epochTime).TotalSeconds).ToString(CultureInfo.InvariantCulture);
var resourceUri = WebUtility.UrlEncode(hostUrl.ToLowerInvariant());
var toSign = string.Concat(resourceUri, "\n", expires);
var signed = Sign(toSign, policyAccessKey);
var sb = new StringBuilder();
sb.Append("sr=").Append(resourceUri)
.Append("&sig=").Append(WebUtility.UrlEncode(signed))
.Append("&se=").Append(expires);
if (!string.IsNullOrEmpty(policyName))
{
sb.Append("&skn=").Append(WebUtility.UrlEncode(policyName));
}
return sb.ToString();
}
private static string Sign(string requestString, string key)
{
using (var hmacshA256 = new HMACSHA256(Convert.FromBase64String(key)))
{
var hash = hmacshA256.ComputeHash(Encoding.UTF8.GetBytes(requestString));
return Convert.ToBase64String(hash);
}
}
public static async Task CreateIfNotExists(HttpClient httpClient, string deviceId)
{
if (await Exists(httpClient, deviceId))
{
return;
}
var jsonMessage =
$"{{\"deviceId\": \"{deviceId}\", \"status\": \"enabled\", \"statusReason\": \"Listo para enviar info\"}}";
var request = new HttpRequestMessage(HttpMethod.Put, $"devices/{deviceId}?api-version=2016-02-03")
{
Content = new StringContent(jsonMessage, Encoding.ASCII, "application/json")
};
var response = await httpClient.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
throw new IOException("No fue posible registrar la estación de medición.");
}
}
public static async Task<bool> Exists(HttpClient httpClient, string deviceId)
{
var request = new HttpRequestMessage(HttpMethod.Get, $"devices/{deviceId}?api-version=2016-02-03");
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await httpClient.SendAsync(request);
return response.IsSuccessStatusCode;
}
{
if (await Exists(httpClient, deviceId))
{
return;
}
var jsonMessage =
$"{{\"deviceId\": \"{deviceId}\", \"status\": \"enabled\", \"statusReason\": \"Listo para enviar info\"}}";
var request = new HttpRequestMessage(HttpMethod.Put, $"devices/{deviceId}?api-version=2016-02-03")
{
Content = new StringContent(jsonMessage, Encoding.ASCII, "application/json")
};
var response = await httpClient.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
throw new IOException("No fue posible registrar la estación de medición.");
}
}
public static async Task<bool> Exists(HttpClient httpClient, string deviceId)
{
var request = new HttpRequestMessage(HttpMethod.Get, $"devices/{deviceId}?api-version=2016-02-03");
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var response = await httpClient.SendAsync(request);
return response.IsSuccessStatusCode;
}
public class MeditionInfoSender
{
private readonly string stationId;
private HttpClient currentHttpClient;
private readonly string iotHubHost;
private readonly string iotHubPolicyName;
private readonly string iotHubPolicyKey;
public MeditionInfoSender(string iotHubHost, string iotHubPolicyName, string iotHubPolicyKey, string stationId)
{
if (string.IsNullOrWhiteSpace(iotHubHost))
{
throw new ArgumentNullException(nameof(iotHubHost));
}
if (string.IsNullOrWhiteSpace(iotHubPolicyName))
{
throw new ArgumentNullException(nameof(iotHubPolicyName));
}
if (string.IsNullOrWhiteSpace(iotHubPolicyKey))
{
throw new ArgumentNullException(nameof(iotHubPolicyKey));
}
if (string.IsNullOrWhiteSpace(stationId))
{
throw new ArgumentNullException(nameof(stationId));
}
this.stationId = stationId;
this.iotHubHost = iotHubHost;
this.iotHubPolicyName = iotHubPolicyName;
this.iotHubPolicyKey = iotHubPolicyKey;
}
public void InitializeStation()
{
using (var httpClient = new HttpClient())
{
httpClient.BaseAddress = new UriBuilder {Scheme = "https", Host = iotHubHost}.Uri;
var hubSharedAccessSignature = DeviceRest.SharedAccessSignature(iotHubHost
, iotHubPolicyName
, iotHubPolicyKey
, TimeSpan.FromMinutes(1));
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("SharedAccessSignature", hubSharedAccessSignature);
DeviceRest.CreateIfNotExists(httpClient, stationId).Wait();
}
currentHttpClient = CreateHttpClient();
}
public async Task<bool> Send(string jsonMessage)
{
if (string.IsNullOrWhiteSpace(jsonMessage))
{
return false;
}
var request = new HttpRequestMessage(HttpMethod.Post, $"devices/{stationId}/messages/events?api-version=2016-02-03")
{
Content = new StringContent(jsonMessage, Encoding.ASCII, "application/json")
};
try
{
var httpClient = GetHttpClient();
var response = await httpClient.SendAsync(request);
return response.IsSuccessStatusCode;
}
catch (Exception)
{
return false;
}
}
private HttpClient GetHttpClient()=> currentHttpClient ?? (currentHttpClient= CreateHttpClient());
private HttpClient CreateHttpClient()
{
var httpClient = new HttpClient(new SharedAccessSignatureAuthHandler(iotHubHost, iotHubPolicyName, iotHubPolicyKey))
{
BaseAddress = new UriBuilder {Scheme = "https", Host = iotHubHost}.Uri,
};
return httpClient;
}
private class SharedAccessSignatureAuthHandler : HttpClientHandler
{
private AuthenticationHeaderValue currentSas;
private readonly TimeSpan maxSasTtl = TimeSpan.FromMinutes(23);
private readonly Stopwatch timer = new Stopwatch();
private readonly string iotHubHost;
private readonly string iotHubPolicyName;
private readonly string iotHubPolicyKey;
public SharedAccessSignatureAuthHandler(string iotHubHost, string iotHubPolicyName, string iotHubPolicyKey)
{
if (string.IsNullOrWhiteSpace(iotHubHost))
{
throw new ArgumentNullException(nameof(iotHubHost));
}
if (string.IsNullOrWhiteSpace(iotHubPolicyName))
{
throw new ArgumentNullException(nameof(iotHubPolicyName));
}
if (string.IsNullOrWhiteSpace(iotHubPolicyKey))
{
throw new ArgumentNullException(nameof(iotHubPolicyKey));
}
this.iotHubHost = iotHubHost;
this.iotHubPolicyName = iotHubPolicyName;
this.iotHubPolicyKey = iotHubPolicyKey;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (timer.Elapsed >= maxSasTtl || currentSas == null)
{
SetCurrentAuth();
}
request.Headers.Authorization = currentSas;
return base.SendAsync(request, cancellationToken);
}
private void SetCurrentAuth()
{
timer.Reset();
var hubSharedAccessSignature = DeviceRest.SharedAccessSignature(iotHubHost
, iotHubPolicyName
, iotHubPolicyKey
, maxSasTtl.Add(TimeSpan.FromMinutes(3)));
currentSas = new AuthenticationHeaderValue("SharedAccessSignature", hubSharedAccessSignature);
timer.Start();
}
}
}
{
private readonly string stationId;
private HttpClient currentHttpClient;
private readonly string iotHubHost;
private readonly string iotHubPolicyName;
private readonly string iotHubPolicyKey;
public MeditionInfoSender(string iotHubHost, string iotHubPolicyName, string iotHubPolicyKey, string stationId)
{
if (string.IsNullOrWhiteSpace(iotHubHost))
{
throw new ArgumentNullException(nameof(iotHubHost));
}
if (string.IsNullOrWhiteSpace(iotHubPolicyName))
{
throw new ArgumentNullException(nameof(iotHubPolicyName));
}
if (string.IsNullOrWhiteSpace(iotHubPolicyKey))
{
throw new ArgumentNullException(nameof(iotHubPolicyKey));
}
if (string.IsNullOrWhiteSpace(stationId))
{
throw new ArgumentNullException(nameof(stationId));
}
this.stationId = stationId;
this.iotHubHost = iotHubHost;
this.iotHubPolicyName = iotHubPolicyName;
this.iotHubPolicyKey = iotHubPolicyKey;
}
public void InitializeStation()
{
using (var httpClient = new HttpClient())
{
httpClient.BaseAddress = new UriBuilder {Scheme = "https", Host = iotHubHost}.Uri;
var hubSharedAccessSignature = DeviceRest.SharedAccessSignature(iotHubHost
, iotHubPolicyName
, iotHubPolicyKey
, TimeSpan.FromMinutes(1));
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("SharedAccessSignature", hubSharedAccessSignature);
DeviceRest.CreateIfNotExists(httpClient, stationId).Wait();
}
currentHttpClient = CreateHttpClient();
}
public async Task<bool> Send(string jsonMessage)
{
if (string.IsNullOrWhiteSpace(jsonMessage))
{
return false;
}
var request = new HttpRequestMessage(HttpMethod.Post, $"devices/{stationId}/messages/events?api-version=2016-02-03")
{
Content = new StringContent(jsonMessage, Encoding.ASCII, "application/json")
};
try
{
var httpClient = GetHttpClient();
var response = await httpClient.SendAsync(request);
return response.IsSuccessStatusCode;
}
catch (Exception)
{
return false;
}
}
private HttpClient GetHttpClient()=> currentHttpClient ?? (currentHttpClient= CreateHttpClient());
private HttpClient CreateHttpClient()
{
var httpClient = new HttpClient(new SharedAccessSignatureAuthHandler(iotHubHost, iotHubPolicyName, iotHubPolicyKey))
{
BaseAddress = new UriBuilder {Scheme = "https", Host = iotHubHost}.Uri,
};
return httpClient;
}
private class SharedAccessSignatureAuthHandler : HttpClientHandler
{
private AuthenticationHeaderValue currentSas;
private readonly TimeSpan maxSasTtl = TimeSpan.FromMinutes(23);
private readonly Stopwatch timer = new Stopwatch();
private readonly string iotHubHost;
private readonly string iotHubPolicyName;
private readonly string iotHubPolicyKey;
public SharedAccessSignatureAuthHandler(string iotHubHost, string iotHubPolicyName, string iotHubPolicyKey)
{
if (string.IsNullOrWhiteSpace(iotHubHost))
{
throw new ArgumentNullException(nameof(iotHubHost));
}
if (string.IsNullOrWhiteSpace(iotHubPolicyName))
{
throw new ArgumentNullException(nameof(iotHubPolicyName));
}
if (string.IsNullOrWhiteSpace(iotHubPolicyKey))
{
throw new ArgumentNullException(nameof(iotHubPolicyKey));
}
this.iotHubHost = iotHubHost;
this.iotHubPolicyName = iotHubPolicyName;
this.iotHubPolicyKey = iotHubPolicyKey;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (timer.Elapsed >= maxSasTtl || currentSas == null)
{
SetCurrentAuth();
}
request.Headers.Authorization = currentSas;
return base.SendAsync(request, cancellationToken);
}
private void SetCurrentAuth()
{
timer.Reset();
var hubSharedAccessSignature = DeviceRest.SharedAccessSignature(iotHubHost
, iotHubPolicyName
, iotHubPolicyKey
, maxSasTtl.Add(TimeSpan.FromMinutes(3)));
currentSas = new AuthenticationHeaderValue("SharedAccessSignature", hubSharedAccessSignature);
timer.Start();
}
}
}
Ya está el plato cocinado falta solo ver como se come…
Considerando que ustedes sabrán como obtener el stationId (que para el IoT Hub es el Device-ID), el primer bocón sería mas o menos así:
sender = new MeditionInfoSender(iotHubHost, iotHubPolicyName, iotHubPolicyKey, stationId);
Console.WriteLine($"Inicializando estación de medición '{stationId}'...");
sender.InitializeStation();
Console.WriteLine($"Estación '{stationId}' inicializada.");
Console.WriteLine($"Inicializando estación de medición '{stationId}'...");
sender.InitializeStation();
Console.WriteLine($"Estación '{stationId}' inicializada.");
var sent = await sender.Send(iotJsonMessage);
Esta receta es para la versión ligth/zero que pueden consumir quienes están a dieta o le resulta pesado el SDK o simplemente se divierte cocinando comida casera (pueden usarlo para traducirlo en el lenguaje de su device). Los que pueden ir al fast-food sería mejor que usen el SDK que le corresponda; le paso la que ya es solo la landing page del SDK (en el texto de la pagina encontrarán en link al repo de cada lenguaje): https://github.com/Azure/azure-iot-sdks
Como postre: el codigo de este post, as is, se usó en un par de proyectos .NET Core para los siguientes runtimes:
"runtimes": {
"win10-x64": {},
"ubuntu.14.04-x64": {},
"debian.8-x64": {}
}
"win10-x64": {},
"ubuntu.14.04-x64": {},
"debian.8-x64": {}
}
No comments:
Post a Comment