.Net Micro Framework適合使用在資源受限制的設備上,例如各種嵌入式設備;讓開發人員使用熟悉的Visual Studio以及C#來撰寫這些設備上的程式。在物聯網的情境下,若要與Azure IOT Hub連接發送感測資料,或是接收由IOT Hub發送下來的資料時,可以透過適用於.Net MF上的MQTT或是AMQP函式庫,直接操作底層的通訊協議;或是使用Azure IOT SDK的Micro Framework Libaray連接。
在這裡,我們使用AMQP .Net Lite這個Library來實現.Net Micro Framework與IOT Hub連接。
程式碼(文末)相當簡單,基本上只要照著範例做就可以了;比較需要注意的是連接時的username與password
- username:其格式為<DEVICE ID>@sas.<IOT HUB NAME>;如果我的設備ID為test001,IOT Hub名稱為myIOTHub,則username為test001@sas.myIOTHub
- password:可以透過Device Exploere產生,其格式為:SharedAccessSignature sr=<IOT HUB URL>%2Fdevices%2F<DEVICE ID>&sig=<SIGNATURE>%3D&se=<EXPIRY>
另外,如果在.NET MF Emulator上跑這個程式,會發現程式在建立Connection完就會hang住了;這是由於在.NET MF Emulator上的一個Issue,導致在Emulator上使用SSL時如果沒有資料回傳便會卡住。詳細說明以及解決方式如下,目前解決方案尚未正式釋出,但可以先透過修改Emulator Source code解決。
using Amqp;
using Amqp.Framing;
using System;
using System.Diagnostics;
using System.Text;
using System.Threading;
#if NETMF
using Microsoft.SPOT;
#endif
namespace IoTHubAmqp
{
class Program
{
/*
string HOST = "";
int PORT = ;
string DEVICE_ID = "<DEVICE ID>";
string DEVICE_KEY = "<DEVICE KEY>";
string password = "<SHARE ACEESS TOKEN>";
string userName = "<IOTHUB-HOST URL>/<DEVICE ID>";
string publishPath = "devices/<DEIVCE ID>/messages/events";
* */
private const string HOST = "<IOT HUB URL>";
private const int PORT = 5671;
private const string DEVICE_ID = "<DEVICe ID>";
private const string DEVICE_KEY“ = “<DEVICE KEY>”;
private const string SAS_TOKEN = "<SHARE ACCESS TOKEN>";
private static Address address;
private static Connection connection;
private static string username = “<DEVICE ID>@sas.<IOT HUB NAME>”
private static Session session;
static void Main(string[] args)
{
Amqp.Trace.TraceLevel = Amqp.TraceLevel.Frame | Amqp.TraceLevel.Verbose;
#if NETMF
Amqp.Trace.TraceListener = (f, a) => Debug.Print(DateTime.Now.ToString("[hh:ss.fff]") + " " + Fx.Format(f, a));
#else
Amqp.Trace.TraceListener = (f, a) => System.Diagnostics.Trace.WriteLine(DateTime.Now.ToString("[hh:ss.fff]") + " " + Fx.Format(f, a));
#endif
string audience = Fx.Format("{0}/devices/{1}", HOST, DEVICE_ID);
string resourceUri = Fx.Format("{0}/devices/{1}", HOST, DEVICE_ID);
string sasToken = SAS_TOKEN;
#if false
address = new Address(HOST, PORT, username,
SAS_TOKEN,
"AMQPS");
connection = new Connection(address);
bool cbs = true;
#else
address = new Address(HOST, PORT, null, null);
connection = new Connection(address);
bool cbs = PutCbsToken(connection, HOST, sasToken, audience);
#endif
if (cbs)
{
session = new Session(connection);
SendEvent();
receiverThread = new Thread(ReceiveCommands);
receiverThread.Start();
}
// just as example ...
// the application ends only after received a command or timeout on receiving
receiverThread.Join();
session.Close();
connection.Close();
}
static private void SendEvent()
{
string entity = Fx.Format("/devices/{0}/messages/events", DEVICE_ID);
SenderLink senderLink = new SenderLink(session, "sender-link", entity);
var messageValue = Encoding.UTF8.GetBytes(DateTime.UtcNow.ToString());
Message message = new Message()
{
BodySection = new Data() { Binary = messageValue }
};
senderLink.Send(message);
senderLink.Close();
}
static private void ReceiveCommands()
{
string entity = Fx.Format("/devices/{0}/messages/deviceBound", DEVICE_ID);
ReceiverLink receiveLink = new ReceiverLink(session, "receive-link", entity);
while (true)
{
Thread.Sleep(0);
Message received = receiveLink.Receive();
//Debug.Print((string)received.Body);
if (received != null)
receiveLink.Accept(received);
}
receiveLink.Close();
}
static private bool PutCbsToken(Connection connection, string host, string shareAccessSignature, string audience)
{
bool result = true;
Session session = new Session(connection);
string cbsReplyToAddress = "cbs-reply-to";
var cbsSender = new SenderLink(session, "cbs-sender", "$cbs");
var cbsReceiver = new ReceiverLink(session, cbsReplyToAddress, "$cbs");
// construct the put-token message
var request = new Message(shareAccessSignature);
request.Properties = new Properties();
request.Properties.MessageId = Guid.NewGuid().ToString();
request.Properties.ReplyTo = cbsReplyToAddress;
request.ApplicationProperties = new ApplicationProperties();
request.ApplicationProperties["operation"] = "put-token";
request.ApplicationProperties["type"] = "azure-devices.net:sastoken";
request.ApplicationProperties["name"] = audience;
cbsSender.Send(request);
// receive the response
var response = cbsReceiver.Receive();
if (response == null || response.Properties == null || response.ApplicationProperties == null)
{
result = false;
}
else
{
int statusCode = (int)response.ApplicationProperties["status-code"];
string statusCodeDescription = (string)response.ApplicationProperties["status-description"];
if (statusCode != (int)202 && statusCode != (int)200) // !Accepted && !OK
{
result = false;
}
}
// the sender/receiver may be kept open for refreshing tokens
cbsSender.Close();
cbsReceiver.Close();
session.Close();
return result;
}
private static readonly long UtcReference = (new DateTime(1970, 1, 1, 0, 0, 0, 0)).Ticks;
static string GetSharedAccessSignature(string keyName, string sharedAccessKey, string resource, TimeSpan tokenTimeToLive)
{
// http://msdn.microsoft.com/en-us/library/azure/dn170477.aspx
// the canonical Uri scheme is http because the token is not amqp specific
// signature is computed from joined encoded request Uri string and expiry string
#if NETMF
// needed in .Net Micro Framework to use standard RFC4648 Base64 encoding alphabet
System.Convert.UseRFC4648Encoding = true;
#endif
string expiry = ((long)(DateTime.UtcNow - new DateTime(UtcReference, DateTimeKind.Utc) + tokenTimeToLive).TotalSeconds()).ToString();
string encodedUri = HttpUtility.UrlEncode(resource);
byte[] hmac = SHA.computeHMAC_SHA256(Convert.FromBase64String(sharedAccessKey), Encoding.UTF8.GetBytes(encodedUri + "\n" + expiry));
string sig = Convert.ToBase64String(hmac);
if (keyName != null)
{
return Fx.Format(
"SharedAccessSignature sr={0}&sig={1}&se={2}&skn={3}",
encodedUri,
HttpUtility.UrlEncode(sig),
HttpUtility.UrlEncode(expiry),
HttpUtility.UrlEncode(keyName));
}
else
{
return Fx.Format(
"SharedAccessSignature sr={0}&sig={1}&se={2}",
encodedUri,
HttpUtility.UrlEncode(sig),
HttpUtility.UrlEncode(expiry));
}
}
}
}
沒有留言:
張貼留言