Windows ストアアプリでUPnPデバイスを発見する
UPnP(Universal Plug and Play)対応のデバイスをWindows ストアプリで発見する方法を紹介します。UPnPアプリを発見するプロトコルであるSSDPを使ってデバイスの定義を取得する方法です。DLNA対応機器や、Philips Hueなど、この解説を使ってアドレスを取得することができます。
SSDPは、UDPのMulticast Group通信を使っており、ストアアプリでは、DatagramSocketを使用します。
DatagramSocket ssdpSocket = new DatagramSocket();
と、データグラムソケットを一つ作成します。そして、データグラムソケットに、UPnPデバイスからの応答を受け取るためのハンドラ―を登録します。
ssdpSocket.MessageReceived += ssdpSocket_MessageReceived;
SSDPは、239.255.255.250、1900というグループアドレス、グループポートを使います。この情報を使ってマルチキャストグループにジョインします。
var ssdpGroup = new HostName("239.255.255.250");
string ssdpGroupPort = "1900";
await ssdpSocket.BindEndpointAsync(null, "");
ssdpSocket.JoinMulticastGroup();
そして、SSDPの規約に従った、デバイス発見の為の送信メッセージを組立て送信します。
string discoverPacket = "M-SEARCH * HTTP/1.1\r\n";
discoverPacket += "HOST: " + ssdpGroup .DisplayName +":" + ssdpGroupPort + "\r\n";
discoverPacket += "ST: upnp:rootdevice\r\n";
discoverPacket += "MAN: \"ssdp:discover\"\r\n";
discoverPacket += "MX: 3:\r\n\r\n";
var stream = await ssdpSocket.GetOutputStreamAsync(ssdpGroup, ssdpGroupPort);
var writer = new DataWriter(stream) { UnicodeEncoding = UnicodeEncoding.Utf8 };
writer.WriteString(discoverPacket);
await writer.StoreAsync();
はい、これで、準備OK。このメッセージを送信後、同一ネットワークにある、SSDP対応デバイスから応答メッセージが先ほど登録したハンドラ―に送られてきます。
次に、メッセージ受信用のハンドラ―です。
async void hueSocket_MessageReceived(DatagramSocket sender, DatagramSocketMessageReceivedEventArgs args)
{
var reader = args.GetDataReader();
uint length = reader.UnconsumedBufferLength;
try
{
// 受信メッセージの取り出し
var receivedMessage = reader.ReadString(length);
// 受信メッセージ解析
var response = new Dictionary<string, string>();
string resultCode = "";
string resultStatus = "";
foreach (var msgLine in receivedMessage.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None))
{
if (msgLine.Contains(":"))
{
int colonIndex = msgLine.IndexOf(":");
if (colonIndex + 1 == msgLine.Length)
{
response.Add(msgLine.Substring(0, colonIndex), "");
}
else
{
response.Add(msgLine.Substring(0, colonIndex), msgLine.Substring(colonIndex + 2));
}
}
else if (msgLine.StartsWith("HTTP"))
{
var strings = msgLine.Split(' ');
resultCode = strings[1];
resultStatus = strings[2];
}
}
if (resultCode == "200" && resultStatus == "OK")
{
// いちおう、メッセージが正しいかも確認しておいて…
// メッセージの中にあるLOCATIONの値を取り出す
var upnpDeviceIp = "";
if (response.ContainsKey("LOCATION"))
{
var strings = response["LOCATION"].Split(':');
if (strings.Length == 3)
{
// LOCATIONの値が、UPnPデバイスの定義に関するURLである。そのURLを使って、UPnPの定義を取得
upnpDeviceIp = strings[0] + ":" + strings[1] + ":" + strings[2];
if (upnpDeviceIp.EndsWith(".xml"))
{
var httpClient = new System.Net.Http.HttpClient();
var deviceDescripXml = await XmlDocument.LoadFromUriAsync(new Uri(upnpDeviceIp));
// 中身を解析して必要な情報を確認
最後のXMLコンテンツの中に、色々な定義がはいっています。Philips Hueの場合は、LOCATIONの値が、https://<device's ip address:80/description.xml になっていて、description.xmlより前をREST APIのアドレスとして使えば、ストアアプリからリモートコントロールが可能です。
SSDPに応答するデバイスが沢山ネット上で参照できる場合、それらのデバイスが一斉に応答を返してくるので、メッセージ、及び、LOCATIONで示されたコンテンツの内容を確認して、制御したいデバイスを見つけなければなりません。
一旦見つかったらssdpSocketは必要ないのでDisposeしておきましょう。