VIERAを操作する方法

# はじめに

パナソニックのテレビ「VIERA」を操作する方法を紹介します。
パナソニックは、「TV Remote」というテレビの操作やスマートフォンなどの動画や写真を表示したり共有するためのスマートフォンアプリを提供しています。
このスマートフォンアプリを使えば、同一LANにあるVIERAを発見、操作することが可能です。
また、このスマートフォンアプリに対応しているVIERAは、内部でHTTPサーバが起動しており、XMLをVIERAに送信することで操作が可能です。

# 機器を発見する

「TV Remote」には、同一LANにあるVIERAを発見する機能があります。
UPnPで規定されたSSDPを利用して機器を発見します。
UPnPは機器をネットワークに接続すると複雑な設定作業を行わなくても他の機器と通信したり利用できるようにするプロトコルです。
またSSDPは、UPnPで用いられるプロトコルの1つで機器の発見や応答を行うためのものです。

サンプルコードをSwift(バージョンは1.2)で記述します。

はじめにヘッダファイルをインポートします。

```hpp:Bridging_Header.h
#import "AsyncUdpSocket.h"
```

次に以下のテキストを`239.255.255.250:1900`に送信します。

> M-SEARCH * HTTP/1.1'
> ST: urn:panasonic-com:device:p00RemoteController:1
> MX: 1
> MAN: \"ssdp:discover\"
> HOST: 239.255.255.250:1900


```swift:sample1.swift
let data = "M-SEARCH * HTTP/1.1'\r\nST: urn:panasonic-com:device:p00RemoteController:1\r\nMX: 1\r\nMAN: \"ssdp:discover\"\r\nHOST: 239.255.255.250:1900\r\n\r\n\r\n" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!

let sock:AsyncUdpSocket = AsyncUdpSocket(delegate: self)
sock.enableBroadcast(true, error: nil)
sock.bindToPort(0, error: nil)
sock.joinMulticastGroup(“239.255.255.250”, error: nil)
sock.sendData(data, toHost: “239.255.255.250”, port: 1900, withTimeout: -1, tag: 1)
sock.receiveWithTimeout(-1, tag: 1)
```

VIERAが見つかると以下の関数が呼ばれます。`host`がVIERAのIPアドレスです。

```swift:sample2.swift
func onUdpSocket(sock: AsyncUdpSocket!, didReceiveData data: NSData!, withTag tag: Int, fromHost host: String!, port: UInt16) -> Bool {

  println(host) // IPアドレス

  return true
}
```

# リモコンキーの送信

発見したVIERAにリモコンキーを送信します。
リモコンキーはリモコンのキー一つ一つに割り当てられたキーのことです。

例えば、次のようなキーがあります。
「NRC_CH_UP-ONOFF」はチャンネルを1つ進めます。
「NRC_VOLUP-ONOFF」はボリュームを1つ高くします。
「NRC_TV-ONOFF」はテレビの電源を切ります。機器によっては内部のHTTPサーバも停止してしまうため再度電源を入れるにはリモコンを使うか、直接電源ボタンを押さなければなりません。
「NRC_RED-ONOFF」はデータ放送の赤ボタンです。

リモコンキーは[tomokas/pyviera](https://github.com/tomokas/pyviera/blob/master/viera.py)を参考にしました。

リモコンキーのXMLは以下のフォーマットです。

```xml:sample.xml
<?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
  <s:Body>
    <u:X_SendKey xmlns:u="urn:panasonic-com:service:p00NetworkControl:1">
      <X_KeyEvent>NRC_TV-ONOFF</X_KeyEvent>
    </u:X_SendKey>
  </s:Body>
</s:Envelope>
```

リモコンキー送信時にヘッダが必要です。

```
SOAPACTION: "urn:panasonic-com:service:p00NetworkControl:1#X_SendKey"
```

これを`http://<IP Address>:55000/nrc/control_0`に送信します。

具体的には以下のようなコードになります。
NSMutableURLRequestを組み立て、NSURLConnectionを使ってリモコンキーを送信します。

```swift:sample4.swif
	/**
	URLリクエストを返します。
	
	:param: host   IPアドレス
	:param: code   リモコンキー
	:returns: URLリクエスト
	*/
	private func commonURLRequest(host: String, code: String) -> NSMutableURLRequest {
		let action = "X_SendKey"
		let option = "<X_KeyEvent>" + code + "</X_KeyEvent>
		let xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?><s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\"><s:Body><u:%@ xmlns:u=\"urn:panasonic-com:service:p00NetworkControl:1\">%@</u:%@></s:Body></s:Envelope>"
		let header = NSString(format: "\"urn:panasonic-com:service:p00NetworkControl:1#%@\"", action)
		let input = NSString(format: xml, action, option, action)
		let url = "http://" + host + ":55000/nrc/control_0"
		let request = NSMutableURLRequest(URL: NSURL(string: url)!)
		request.HTTPMethod = "POST"
		request.HTTPBody = input.dataUsingEncoding(NSUTF8StringEncoding)
		request.addValue(header as String, forHTTPHeaderField: "SOAPACTION")
		
		return request
	}
```

# 最後に

今回の内容をまとめたライブラリを[GitHub](https://github.com/arthur87/M2MFramework)で公開しています。