.NET Micro FrameworkからSignalRでデータをWebサイトに送る
…ようやくつながった。
※と思いきや、この投稿の内容では、SignalR2.0では動かないようなので、ご注意ください。SignalR2.0版は、後日投稿の予定です。
って訳で、.NET Micro Frameworkから、SignalRでデータをWebサイトに送信する方法を紹介します。想定シナリオとしては、.NET Micro Framework上でセンサー計測情報を送り続けるというものです。ネット越しにSignalRで.NET Micro Frameworkがデータを受け取りモータなどを制御するというシナリオも考えられますが、ネット越しにリアルタイム的な制御をするのは結構難しいので、ここでは、.NET Micro FrameworkからWebへのデータ送信のみを取り扱います。
https://msdn.microsoft.com/ja-jp/windowsazure/dn146079.aspx
こちらご覧ください。簡単にいうと、ネットワークを介したリアルタイム通信機構です。
Webサイト側は、
を参考に、Webアプリを作ってください。このWebアプリを動かすためのサイトは、https://www.windowsazure.com/ja-jp/ でサブスクリプション契約し、Webサイトを使ってくださいね。一日当たりの使用に制限つきですが無料枠あります。Webサイトの作成は、https://msdn.microsoft.com/ja-jp/windowsazure/jj983748.aspx を参考にやってください。
Internet ExplorerのF12や、Fiddlerなどを使って解析すると、SignalRを使った通信は、最初にWebサイトに対してネゴシエーションを行い、ConnectionTokenを取得し、データを送り続けるという形式をとります。これを、.NET Micro Frameworkで実装すると、
ネゴシエーションフェーズ:
string entryPoint = serverUri + "negotiate?_=1369908593886";
var request = HttpWebRequest.Create(entryPoint) as HttpWebRequest;
request.Method = "GET";
var response = request.GetResponse() as HttpWebResponse;
if (response.StatusCode == HttpStatusCode.OK)
{
using (var resStream = response.GetResponseStream())
{
var reader = new StreamReader(resStream);
var jsonContent = reader.ReadToEnd();
int ctStartIndex = jsonContent.IndexOf(keyConnectionType);
jsonContent = jsonContent.Substring(ctStartIndex + keyConnectionType.Length + 3);
connectionToken = jsonContent.Substring(0, jsonContent.IndexOf("\""));
int ciStartIndex = jsonContent.IndexOf(keyConnectionId);
jsonContent = jsonContent.Substring(ciStartIndex + keyConnectionId.Length + 3);
connectionId = jsonContent.Substring(0, jsonContent.IndexOf("\""));
isConnected = true;
order = 0;
UpdatePosition(serverUri, topPos, leftPos, order);
order++;
}
}
※このコードでは、ConnectionTokenに'+'が含まれると正しくサーバーに送信できません。上のコードに、'+'が入っている場合には、"%2b"に置き換える処理を加えてください。
serverUriは、SignalRをホストするサイトのURLの下のsignalrです。例えば、ローカルのエミュレータを使っている場合は、"https://localhost:53925/signalr/" といった様に、WebサイトのURLに/signalr/を加えた文字列です。.NET MFの通常のHttp WebアクセスのコードでWebサイトから応答を取得します。JSON形式で送られてくるので、そこから、ConnectionTokenを取り出します。コードの中のkeyConnectionTypeは、”ConnectionToken"という値をセットしたstringです。orderは、int型の変数。データ送信フェーズで使います。
※keyConnectionIdは、”ConnectionId”
データ送信フェーズ:
実装は、以下の様になります。
string sendUri = serverUri + "send?transport=foreverFrame&connectionToken=" + connectionToken;
using (var sendRequest = HttpWebRequest.Create(sendUri) as HttpWebRequest)
{
sendRequest.Method = "POST";
sendRequest.ContentType = "application/x-www-form-urlencoded; charset=UTF-8";
sendRequest.KeepAlive = true;
sendRequest.Timeout = 3000000;
string updateContent = "{\"H\":\"moveshapehub\",\"M\":\"UpdateModel\",\"A\":[{\"top\":" + topPos + ",\"left\":" + leftPos + "}],\"I\":" + order + "}";
string encoded = "data=" + HttpUtility.UrlEncode(updateContent);
byte[] content = System.Text.UTF8Encoding.UTF8.GetBytes(encoded);
sendRequest.ContentLength = content.Length;
using (var reqStream = sendRequest.GetRequestStream())
{
reqStream.Write(content, 0, content.Length);
var updateResponse = sendRequest.GetResponse() as HttpWebResponse;
if (updateResponse.StatusCode == HttpStatusCode.OK)
{
Debug.Print("Succeeded");
}
using (var sendResStream = updateResponse.GetResponseStream())
{
byte[] resContentBytes = new byte[updateResponse.ContentLength];
sendResStream.Read(resContentBytes, 0, (int)updateResponse.ContentLength);
char[] resContentChars = System.Text.UTF8Encoding.UTF8.GetChars(resContentBytes);
string resContent = new string(resContentChars);
Debug.Print(resContent);
order++;
}
updateResponse.Dispose();
}
}
serviceUriは、ネゴシエーションの時と同じです。POSTメソッドで、通知するデータをJSON形式で送付します。MoveShapeのデモでは、
data={"H":"moveshapehub","M":"UpdateModel","A":[{"top":27,""left":105}],"I":3}
というデータを送付しています。大した量ではないので上のコードではべた書きです。上のコードでは、最後のIの値にorderというint変数を指定しています。送信順を指定しているようなので、クラスのメンバー変数などで用意しておいて、順次増していきます。
topPos、leftPosは、int型の変数で、ブラウザの左上からの座標値を指定します。.NET MFのエミュレータの場合、真ん中のボタンでネゴシエーション、UP、DOWN、LEFT、RIGHTボタンで上下左右に座標を動かす、といった感じでコーディングしてください。
int topPos = 200;
int leftPos = 200;
int order = 0;
private void OnButtonUp(object sender, RoutedEventArgs evt)
{
string serverUri = "https://localhost:53925/signalr/";
ButtonEventArgs e = (ButtonEventArgs)evt;
if (e.Button==Button.VK_SELECT)
{
try
{
// ネゴシエーションコード
isConnected = true;
}
catch (Exception ex)
{
Debug.Print("Negotiation - " + ex.Message);
}
}
else
{
if (isConnected)
{
int delta = 10;
switch (e.Button)
{
case Button.VK_LEFT:
leftPos -= delta;
if (leftPos < 0)
{
leftPos = 0;
}
break;
case Button.VK_UP:
topPos -= delta;
if (topPos < 0)
{
topPos = 0;
}
break;
case Button.VK_RIGHT:
leftPos += delta;
break;
case Button.VK_DOWN:
topPos += delta;
break;
}
// 更新通知コード
order++;
}
}
}
こんな感じ。
HttpUtilityというクラスがコードの中に出てきますが、UrlEncode()メソッドで、URLパラメータ用の変換を施しています。.NET Micro Frameworkでは残念ながらこの機能がないので、
public static string UrlEncode(string s)
{
char[] encodedChar = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
int num0='0';
int num9='9';
int chara='a';
int charz='z';
int charA='A';
int charZ='Z';
string encoded = "";
foreach (var c in s.ToCharArray())
{
int cv = c;
if ((num0 <= cv && cv <= num9) || (chara <= cv && cv <= charz) || (charA <= cv && cv <= charZ))
{
encoded += c;
}
else
{
encoded += "%";
int ch = c >> 4;
int cl = c & 0x0f;
if (ch > 0x8)
{
throw new ArgumentOutOfRangeException("should be ascii code");
}
encoded += encodedChar[ch];
encoded += encodedChar[cl];
}
}
return encoded;
}
こんなコードで代用しています。(バグありかも)
以上、.NET Micro Frameworkの方は、Emulatorでも試せるので、是非やってみてくださいね。