ユビキタス入門

リモコンは紛失しやすいので、学習リモコン (BUFFALO PC-OP-RS1) を買ってみた。
http://buffalo.jp/products/catalog/item/p/pc-op-rs1/index.html
これを Debian/etch で動作させたときのメモを残す。

参考にさせていただいたサイト:
http://k-home.no-ip.info/wiki/index.php?PC-OP-RS1
http://blog.gcd.org/archives/50846722.html

以下、見よう見まねと試行錯誤でやっているので、万が一問題がおきても僕は責任とりません。また、よりよい方法を教えていただけるとうれしいです。

1. 上記サイトを参考に、カーネルモジュールに以下のパッチをあてる (参考サイトの情報は少し古く、パッチがあてられなかったので、若干推測気味) 。

diff -ru linux-source-2.6.18.org/drivers/usb/serial/ftdi_sio.c linux-source-2.6.18/drivers/usb/serial/ftdi_sio.c
@@ -306,6 +306,7 @@


 static struct usb_device_id id_table_combined [] = {
+       { USB_DEVICE(BUFFALO_VID, BUFFALO_PCOPRS1_PID) },
        { USB_DEVICE(FTDI_VID, FTDI_AMC232_PID) },
        { USB_DEVICE(FTDI_VID, FTDI_CANUSB_PID) },
        { USB_DEVICE(FTDI_VID, FTDI_ACTZWAVE_PID) },
diff -ru linux-source-2.6.18.org/drivers/usb/serial/ftdi_sio.h linux-source-2.6.18/drivers/usb/serial/ftdi_sio.h
--- linux-source-2.6.18.org/drivers/usb/serial/ftdi_sio.h       2006-09-20 12:42:06.000000000 +0900
+++ linux-source-2.6.18/drivers/usb/serial/ftdi_sio.h   2007-05-21 22:20:33.000000000 +0900
@@ -996,3 +996,8 @@
  *
  */

+/*
+ * BUFFALO RemoteStation PC-OP-RS1
+ */
+#define BUFFALO_VID             0x0411  /* BUFFALO Vendor ID */
+#define BUFFALO_PCOPRS1_PID     0x00b3  /* RemoteStation PC-OP-RS1 */

2. コンパイルする。
3. できあがった linux-source-2.6.18/drivers/usb/serial/ftdi_sio.ko を /lib/modules/2.6.18-4-686/kernel/drivers/usb/serial/ へ上書きコピーする。
4. /lib/modules/2.6.18-4-686/modules.usbmap に以下の行を追加する (もっといい方法ありそう) 。

ftdi_sio             0x0003      0x0411   0x00b3    0x0000       0x0000       0x00         0x00            0x00            0x00            0x00               0x00         0x0

5. /lib/modules/2.6.18-4-686/modules.alias に以下の行を追加する (同上) 。

alias usb:v0411p00B3d*dc*dsc*dp*ic*isc*ip* ftdi_sio

6. /etc/udev/udev.rules に以下の行を追加する。

SUBSYSTEMS=="usb", KERNEL=="ttyUSB*", \
        ATTRS{product}=="BUFFALO RemoteStation PC-OP-RS1", \
                                        SYMLINK+="pc-op-rs1"

7. 再起動。
8. PC-OP-RS1 を接続すると、/dev/pc-op-rs1 ができているはず。これを使って、上記サイトを参考に通信すればいい。libtermios-ruby を使って以下のようなプログラムを書いた。

#!/usr/bin/env ruby

require "fcntl"
require "termios"
require "optparse"

class RemoteStation
	def self.transaction(dev_name, verbose = false)
		i = new(dev_name, verbose)
		yield(i)
		i.close
	end

	def initialize(dev_name, verbose = false)
		@verbose = verbose

		@dev = open(dev_name, File::RDWR | File::NONBLOCK)
		mode = @dev.fcntl(Fcntl::F_GETFL, 0)
		@dev.fcntl(Fcntl::F_SETFL, mode & ~File::NONBLOCK)

		@old_tio = Termios.getattr(@dev)

		new_tio = Termios.new_termios
		new_tio.iflag = Termios::IGNPAR
		new_tio.oflag = 0
		new_tio.cflag = (Termios::CS8 | Termios::CLOCAL | Termios::CREAD)
		new_tio.lflag = 0
		new_tio.cc[Termios::VTIME] = 0
		new_tio.cc[Termios::VMIN] = 1
		new_tio.ispeed = Termios::B115200
		new_tio.ospeed = Termios::B115200

		Termios.flush(@dev, Termios::TCIOFLUSH)
		Termios.setattr(@dev, Termios::TCSANOW, new_tio)
	end

	def close
		Termios.setattr(@dev, Termios::TCSANOW, @old_tio)
		@dev.close
		@dev = nil
	end

	def send_p(cmd)
		@dev.write cmd
		info("-->", cmd)
	end

	def recv_p(len = 1)
		res = @dev.read(len)
		info("<--", res)
		res
	end

	def info(prefix, data)
		return unless @verbose
		str = data.size == 1 ? "0x%02x" % data[0] : "(#{ data.size } bytes)"
		puts(prefix + " " + str)
	end

	# LED を点灯させる
	def led
		# --> 0x69
		send_p("i")

		# <-- 0x4f ※ 0x59 が帰ってくるときは LED は点かない
		[ "O", "Y" ].include? recv_p
	end

	# 受信する
	def recv
		# --> 0x72
		send_p("r")

		# <-- 0x59
		return false unless recv_p == "Y"

		yield if block_given?

		# <-- 0x53
		return false unless recv_p == "S"

		# <-- リモコンデータ 240 Bytes
		packet = recv_p(240)

		# <-- 0x45
		return false unless recv_p == "E"

		packet
	end

	# 送信する
	def send(packet, channel = 1)
		raise "invalid packet" unless packet.size == 240
		raise "invalid channel" unless 1 <= channel && channel <= 4

		# --> 0x74
		send_p("t")

		# <-- 0x59
		return false unless recv_p == "Y"

		# --> 0x31 ※ 1ch=0x31,2ch=0x32,3ch=0x33,4ch=0x34
		send_p((?0 + channel).chr)

		# --> 0x59
		return false unless recv_p == "Y"

		yield if block_given?

		# --> リモコンデータ 240 Bytes
		send_p(packet)

		# <-- 0x45
		return false unless recv_p == "E"

		true
	end
end



dev_name = "/dev/pc-op-rs1"
channel = 1
led = recv = send = quite = verbose = nil

opts = OptionParser.new
opts.on("-d devfile", "set device file") {|dev_name|}
opts.on("-c channel", "set channel (1 - 4)", Integer) {|channel|}
opts.on("-l", "flash led") {|led|}
opts.on("-r packetfile", "receive packet") {|recv|}
opts.on("-t packetfile", "transmit packet") {|send|}
opts.on("-q", "quite mode") {|quite|}
opts.on("-v", "verbose mode") {|verbose|}
opts.parse!(ARGV)

RemoteStation.transaction(dev_name, verbose) do |rs|
	rs.led if led
	if recv
		cmd = rs.recv do
			puts "receiving..." if !quite && $stdout.tty?
		end
		fh = recv == "-" ? $stdout : open(recv, "wb")
		fh.write(cmd)
		fh.close
	end
	if send
		fh = send == "-" ? $stdin : open(send, "rb")
		rs.send(fh.read, channel) do
			puts "sending..." if !quite && $stdout.tty?
		end
	end
end

パケットを受信するときは、./pc-op-rs1.rb -r (ファイル名) を実行した状態で、リモコンを PC-OP-RS1 に向けて発信する。ファイル名にデータが保存 (上書き) される。パケットを送信するときは、PC-OP-RS1 の発信機を所望の機器に向けて ./pc-op-rs1.rb -t (ファイル名) を実行すればよい。

使った感触としては、出力が弱いのか指向性が強すぎるのか、発信機をかなり近づけないと動かない。しょうがないので 2.5 mm ステレオのオーディオ延長ケーブルを買ってきて延長して使っている。