d.sunnyone.org
sunnyone.org

ページ

2012-12-23

Linux boxにUSBストレージを接続したとき勝手に同期するようにする

同期とはなんだか難しそうだと思ったあなた!正しいと思います。

そんな「同期」ですが、今日の話は難しくありません。
簡単に書くと、udevデーモンにデバイス固有の設定を入れて、つないだときにrsyncを走らせましょうという話です。

必要なもの

  • 適当なLinux box(今回はDebian squeeze)
  • 適当なUSB storage
今回はSheevaPlugとKobo gloを利用。
udevは若干変わっているので、最近のUbuntuでも大丈夫だと思うが、RHEL(特に5)だとちょっとわからない。
ただし、GNOMEが動いていたりすると、そっちで勝手にマウントされそうなので、無効にしておいたほうがいいかも。

デバイスを特定する

つないだら、まずは普通にdmesgコマンドでログを見るか、fdisk -lでデバイスを調べる。
今回のkoboはsdaとして認識された。
(このSheevaPlugはルートがSDカードだからmmcblk0なのだけど、sdaがシステムになっているデバイスが多いと思うので、この下のコマンド達をパスまでコピペしないよう注意。)

/dev/sdaがデバイスだとわかったので、devadmコマンドのinfo -q path -n /dev/XXXを使って、sysfsでのパスを特定する。
$ sudo udevadm info -q path -n /dev/sda
/devices/platform/orion-ehci.0/usb1/1-1/1-1:1.0/host11/target11:0:0/11:0:0:0/block/sda

そうしたら、udevadmコマンドのinfo -p [パス] -aを使って、デバイスに関する情報を表示する。
(もちろん、この2つのコマンドは$()とかを使って1行で実行してもよい)
$ sudo udevadm info -p /devices/platform/orion-ehci.0/usb1/1-1/1-1:1.0/host11/target11:0:0/11:0:0:0/block/sda -a

Udevadm info starts with the device specified by the devpath and then
walks up the chain of parent devices. It prints for every device
found, all possible attributes in the udev rules key format.
A rule to match, can be composed by the attributes of the device
and the attributes from one single parent device.

  looking at device '/devices/platform/orion-ehci.0/usb1/1-1/1-1:1.0/host11/target11:0:0/11:0:0:0/block/sda':
    KERNEL=="sda"
    SUBSYSTEM=="block"
(略)
  looking at parent device '/devices/platform/orion-ehci.0/usb1/1-1':
    KERNELS=="1-1"
    SUBSYSTEMS=="usb"
    DRIVERS=="usb"
    ATTRS{configuration}=="Self-powered"
    ATTRS{bNumInterfaces}==" 1"
    ATTRS{bConfigurationValue}=="1"
    ATTRS{bmAttributes}=="c0"
    ATTRS{bMaxPower}=="  2mA"
    ATTRS{urbnum}=="229"
    ATTRS{idVendor}=="2237"
    ATTRS{idProduct}=="4173"
    ATTRS{bcdDevice}=="0110"
    ATTRS{bDeviceClass}=="00"
    ATTRS{bDeviceSubClass}=="00"
    ATTRS{bDeviceProtocol}=="00"
    ATTRS{bNumConfigurations}=="1"
    ATTRS{bMaxPacketSize0}=="64"
    ATTRS{speed}=="480"
    ATTRS{busnum}=="1"
    ATTRS{devnum}=="13"
    ATTRS{version}==" 2.00"
    ATTRS{maxchild}=="0"
    ATTRS{quirks}=="0x0"
    ATTRS{authorized}=="1"
    ATTRS{manufacturer}=="Kobo"
    ATTRS{product}=="eReader"
    ATTRS{serial}=="XXXXXXXXXXXXX"
(略)

  looking at parent device '/devices/platform/orion-ehci.0':
    KERNELS=="orion-ehci.0"
    SUBSYSTEMS=="platform"
    DRIVERS=="orion-ehci"
    ATTRS{modalias}=="platform:orion-ehci"

  looking at parent device '/devices/platform':
    KERNELS=="platform"
    SUBSYSTEMS==""
    DRIVERS==""
重要なのは「ATTRS{serial}」というところ。これをキーにデバイスを決定する。
ただし、万が一でも誤爆しないよう念の為「ATTRS{manufacturer}」と「ATTRS{product}=="eReader"」も使うことにした。なので、今回使う情報は以下。
  • ATTRS{manufacturer}=="Kobo"
  • ATTRS{product}=="eReader"
  • ATTRS{serial}=="XXXXXXXXXXXXX"

同じ種類のデバイスをひとつしか持っていないときは、USBデバイスの種類を特定するかぎである「ATTRS{idVendor}」「ATTRS{idProduct}」
を使ってもよいと思う。

udevの設定をする(1) - デバイスに名前をつける

まずは、調べた情報を使って、/dev以下に勝手なシンボリックリンクを置く。今回はkoboなので/dev/kobo。

/etc/udev/rules.dに以下のように設定ファイルを配置すればOK。

/etc/udev/rules.d/92-kobo.rules
KERNEL=="sd*", ACTION=="add", \
  ATTRS{manufacturer}=="Kobo", ATTRS{product}=="eReader", \
  ATTRS{serial}=="XXXXXXXXXXXXX" \
  NAME="%k", SYMLINK="kobo"

あとは、デバイスをつなぐと、/dev/koboが作られる。
$ ls -l /dev/kobo /dev/sda
lrwxrwxrwx 1 root root      3 Dec 23 18:19 /dev/kobo -> sda
brw-rw---- 1 root floppy 8, 0 Dec 23 18:19 /dev/sda

udevadmのman pageを見ると「The udev daemon detects changes automatically」って書いてあるけど、もし認識しなかったら以下のコマンドを叩いてみるといいかも。
$ sudo udevadm control --reload-rules

fstabの設定

/dev/koboがつながったときに、マウントできるようにする。

まずマウントするためのディレクトリを作る。
$ sudo mkdir /media/kobo

koboの内部ストレージは、パーティションなしでデバイスまるまるマウントすればいいので、以下のような行を
/etc/fstabに追加する。

/dev/kobo        /media/kobo     vfat   rw,codepage=932,iocharset=utf8 0 0

マウントできることを確かめる。
$ sudo mount /media/kobo
$ df -h

ディスク容量が表示されたら、umountしてデバイスを外す(本当は切り離しもやったほうがいいんだろうけど…)
$ sudo umount /media/kobo

自動同期するスクリプトを用意する

マウントしてrsyncし、アンマウントしてメールを送るだけの簡単なスクリプトを用意して、
/usr/local/bin/synckobo.shに配置する。


デバイスをつないで、マウントされてない状態で、スクリプトを叩いて同期できたらOK.
見慣れない"--modify-window=1"については、「rsync FAQ」を参照。vfatならつけておくのが幸せだと思う。

udevの設定をする(2) - 接続時にスクリプトを実行する

さきほど作った設定ファイルに「RUN」を追加する。

KERNEL=="sd*", ACTION=="add", \
  ATTRS{manufacturer}=="Kobo", ATTRS{product}=="eReader", \
  ATTRS{serial}=="XXXXXXXXXXXXX" \
  NAME="%k", SYMLINK="kobo", \
  RUN="/usr/local/bin/synckobo.sh"

接続して同期されれば成功!

2012-12-15

PowerShellでApacheのログを集計する

この記事はPowerShell Advent Calendar 2012向けです。PowerShellはまだまだマスターには遠いですが、ありがたみがわかってきたので、書くことにしました。昨日の牟田口さんの記事を見るとAdd-TypeもPSObjectも使わずにPowerShellでクラスを作ってみろと言われているような気がしますが、誘惑を振り切りもともと想定していたネタです。

今日は、PowerShellを使ってApacheのログを集計してみます。Windowsを使っている人だったら、「bashとsedともげもげで十分じゃん」「Perl使うよ」と言わず、いいところがあるので見てください。特にログ集計でshもLLもわずらわしくなって、SQLiteやPostgreSQLにワンタイムのDBを作ってSQLでログ集計したことがある人におすすめです。

メリット

そもそもなにがうれしいの?という部分ですが、PowerShellには「パイプラインをオブジェクトが流れる」という特徴があり、これを利用するとshやPerlやRubyでの処理と比較して以下のようなメリットがあります。
  • (shで処理する場合と比較して)パーサがパイプにテキストではなく、オブジェクト群を流せるので、再利用が容易。きちんとパイプに流れてきたオブジェクトを処理するコマンド(コマンドレット)が用意されている。空白区切りテキストを見て、「うーん、User-Agent何番目かな…1, 2, 3...」と数えなくてよい。
  • (Perl等で処理する場合と比較して)sedやawk, sort等々をつなげて作っていくような考え方で、パイプに流して絞り込んでいく形で処理を記述できるので、試行錯誤で試していくのがラク。
要は、いいとこ取りというわけです。具体的にはこれから見ていきます。

対象ログ

今回対象とするのは、"combined"として定義されているタイプのログです。こんな感じです。


ひとつ正規表現を変えるだけなので、ちょっと違ってても大きな問題はないです。

パーサ(モジュール)の準備

まずは、ログのパーサを準備します。とはいっても、テキストにマッチしてオブジェクトをばんばん返す(流す)関数を書くだけです。
以下のスクリプトを(Win7/Vistaの場合)「C:\Users\(ユーザ名)\Documents\WindowsPowerShell\Modules\ApacheLogParser\ApacheLogParser.psm1」に配置します。



ここはあまりPowerShellっぽくないので、ポイントだけ書きます。
  • 関数名は動詞-名詞にする。使える動詞は「Approved Verbs for Windows PowerShell Commands」を参照。
  • 正規表現のマッチ結果である$matches ハッシュテーブルを活用する。
  • PowerShellの文字列の特殊文字のエスケープは`。 例: `"。
  • 正規表現では、名前つきキャプチャを活用する。 例: (?<Host>.*?)。こうすると、$matchesに指定した名前で入ってくる。
  • パイプラインには、ハッシュをPSObjectにして流す。その際は、「New-Object」の-Propertyを使うと便利。

returnを書いているので、値を返すにはそうしないといけなそうに見えますが、このケースは別に書かなくても平気です。どういうことかというと、値を返すコマンドレットを使ったら、|Out-Nullとか、>$null とかして捨てないと、どんどん「流れ」ていってしまうのです。shでコマンドを実行したらstdout/stderrに出力が出るようなものです。不要なものは捨てるようにします。

Pathを受けとるのではなく、ログ本文を受け取るようにして、この関数自体をパイプで使うのが「ぽい」ような気もしますが、物理ファイルを指定するケースが多そうなのでとりあえずこの形にしました。需要に応じて変更するのがいいと思います。Perlの<>や、RubyのARGFみたいにstdin or 引数のファイルからのInputというのが簡単にできるといいのですが。

作業開始前の準備

使う段になったら、powershellを起動後、「Import-Module」で先程のモジュールをロードします。
PS C:\temp> Import-Module ApacheLogParser

試しに、「Read-ApacheLog」にログを食べさせてみましょう。そのまま出すと出すぎるので、「Select-Object(エイリアス:select)」の-Lastオプションで数を減らしたものが以下です。


各行の「 : 」の左側に書かれたプロパティ名を使って、さまざまな操作をしていくことになります。


実践

先程のパーサを使って、具体的に集計をしてみます。

トータルのアクセス数

もっともシンプルなものとして、全てのアクセス数を数えてみましょう。ここでは、いろいろ数えてくれる「Measure-Object(エイリアス: measure)」を使います。

PS C:\temp> Read-ApacheLog .\access.log | measure


Count    : 281
Average  :
Sum      :
Maximum  :
Minimum  :
Property :
281アクセスあることがわかりました(テスト用に作ったので少なくてごめんなさい)

特定のパスのみのアクセス数

先の「Measure-Object」に加えて、与えた条件を満たすもののみを流す「Where-Object(エイリアス:where、?)」を使います。

PS C:\temp> Read-ApacheLog .\access.log | where { $_.Path -eq "/favicon.ico" } | measure


Count    : 2
Average  :
Sum      :
Maximum  :
Minimum  :
Property :
"/favicon.ico"へのアクセスは2回あるようです。

ステータス別アクセス数

ステータスコードで分類してみましょう。今度は指定したプロパティが同じ値を持つオブジェクトをグループ化する「Group-Object(エイリアス:group)」と、表示順を整えるために「Sort-Object(エイリアス:sort)」を使います。

PS C:\temp> Read-ApacheLog .\access.log | group Status | sort Name

Count Name                      Group
----- ----                      -----
  259 200                       {@{Time=2012/12/14 0:09:47; Host=127.0.0.1; Request=OPTIONS * HTTP/1.0; TimeString=1...
    1 301                       {@{Time=2012/12/14 0:17:35; Host=10.0.2.2; Request=GET /mediawiki/index.php HTTP/1.1...
    1 302                       {@{Time=2012/12/14 0:17:44; Host=10.0.2.2; Request=POST /mediawiki/index.php?title=M...
   11 304                       {@{Time=2012/12/14 0:17:41; Host=10.0.2.2; Request=GET /mediawiki/skins/common/share...
    9 404                       {@{Time=2012/12/14 0:14:26; Host=10.0.2.2; Request=GET /favicon.ico HTTP/1.1; TimeSt...
200が多いですが、3xxや4xxもちらほらあることがわかります。

トップディレクトリごとのアクセス数

今度はトップディレクトリごとのアクセス数を集計してみましょう。「Group-Object」に置換を行うブロックを渡します(パスのファイル名部分と、サブディレクトリ以下をカットします)。

PS C:\temp> Read-ApacheLog .\access.log | group { $_.Path -replace '[^/]*$','' -replace '^(/.+?)/.*','$1' } | sort -Descending Count

Count Name                      Group
----- ----                      -----
  207 /pukiwiki                 {@{Time=2012/12/14 0:23:12; Host=127.0.0.1; Request=GET /pukiwiki/ HTTP/1.1; TimeStr...
   58 /mediawiki                {@{Time=2012/12/14 0:14:30; Host=10.0.2.2; Request=GET /mediawiki/ HTTP/1.1; TimeStr...
   11                           {@{Time=2012/12/14 0:09:47; Host=127.0.0.1; Request=OPTIONS * HTTP/1.0; TimeString=1...
    4 /                         {@{Time=2012/12/14 0:14:26; Host=10.0.2.2; Request=GET / HTTP/1.1; TimeString=14/Dec...
    1 /trac                     {@{Time=2012/12/14 0:17:51; Host=10.0.2.2; Request=GET /trac/ HTTP/1.1; TimeString=1...

"/pukiwiki"にたくさんアクセスされているようです。

時間帯別アクセス数

パーサスクリプトの中で、時刻はSystem.DateTime型に変換しておいたので「Hour」プロパティが使えます。今までと同じように「Group-Object」を使えばOKです。

PS C:\temp> Read-ApacheLog .\access.log | group { $_.Time.Hour }

Count Name                      Group
----- ----                      -----
  227 0                         {@{Time=2012/12/14 0:09:47; Host=127.0.0.1; Request=OPTIONS * HTTP/1.0; TimeString=1...
   54 18                        {@{Time=2012/12/14 18:30:05; Host=127.0.0.1; Request=GET /pukiwiki/index.php?InterWi...

例がとてもよくないのですが、0時台にたくさんアクセスがあり、18時台に少しアクセスがあるようです。他の時間帯には使われていません。

日付/時間帯別アクセス数をCSV出力する

CSV出力することも可能です。「Export-Csv」コマンドレットを使います。
(日付+時間を作るには、若干トリッキーですがDateTime型の「Date」プロパティで日付部をもらい、「AddHours」メソッドを呼び出して時間を付加しています。)

PS C:\temp> Read-ApacheLog .\access.log | group { $_.Time.Date.AddHours($_.Time.Hour) } | select Name,Count | Export-Csv output.csv -Encoding default


平均レスポンスバイト数を調べる

combinedフォーマットには「%b:レスポンスのバイト数」があるので、レスポンスのサイズを集計することも可能です。「Where-Object」「Measure-Object」を使って、/pukiwiki以下の返答の平均バイト数を出してみましょう。

PS C:\temp> Read-ApacheLog .\access.log | where { $_.Path -match "/pukiwiki" } | measure -Property BytesSent -Average


Count    : 207
Average  : 3019.21256038647
Sum      :
Maximum  :
Minimum  :
Property : BytesSent

このようにひとつのログ形式に対して、パーサを1回作ってあげれば、あとは直感的に集計処理が可能になります。量的にどこまで耐えられるかは検証していないのですが、GUIな表計算ソフトでうんぬんするよりはさっくりできると思います。もちろんApacheのログでなくて、テキストのログは同じように処理することが可能ですので、しょっちゅうログを処理するような方は、試してみてはいかがでしょうか。

2012-12-03

ILGeneratorの「向こう側」でTypeやMethodInfoを得るために"ldtoken"命令と~FromHandleメソッドを使う

最初のころは黒魔術だったのにだんだん慣れてきちゃった。とはいえ、書いておかないと忘れかねないので書いておく。今回のテーマはタイトルの通りなので、タイトルを読んでわかる人には不要の話。

概要

ILGeneratorを使ってコードを書いているような状況では、ILGeneratorの「向こう側」(生成したILコードが動作している文脈のこと。ILを使ってコードを生成していると、文脈を区別したくなるので、勝手にこう呼んでいる)でTypeやMethodInfoなどのリフレクション用のオブジェクトが欲しくなるときがある。もちろん、Nameを使ってGetMethod等々を「向こう側」で呼ぶということも可能だが、「こっち側」(ILコードを生成している側の文脈)でType等々が識別できている状況では、もう少しいい方法がある。それは以下の通り。

  1. "ldtoken"命令と「メタデータ トークン」を使って、評価スタックにRuntimeHandleをpushする。
  2. 各種~FromHandleメソッドを使って、TypeやMethodInfoなどを得る。

実践 - Typeの場合

Typeの場合こんな感じ。
var typeObj = il.DeclareLocal(typeof(Type));   // ローカル変数の準備
il.Emit(OpCodes.Ldtoken, typeof(SampleClass)); // -- (1)
il.EmitCall(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle"), null); // -- (2)
il.Emit(OpCodes.Stloc, typeObj); // ローカル変数へのストア

(1) メタデータトークンからRuntimeTypeHandleを得る

まず、「メタデータ トークン」を使ってldtoken命令を実行し、RuntimeHandleを得る。
書式 / アセンブリ形式 / 説明
D0 < T > / ldtoken token / メタデータ トークンをそのランタイム表現に変換します。
(from OpCodes.Ldtoken フィールド)

しかし、メタデータ トークンとは何か?
メタデータは、ランタイム型 (クラス、値型、およびインターフェイス)、グローバル関数、グローバル変数などの抽象化の宣言情報です。
(from メタデータ トークン)
ということで、詳細は上記ページに書いてあるが、型やメソッドなんかのプリミティブな表現くらいに思っておけば大丈夫だと思う。

幸いなことに、ILGenerator.Emit(OpCodes.Ldtoken, xxx)なら、TypeやMethodInfoを渡せば裏でうまいことやってくれるので、あまり意識しなくていい。

このメタデータトークンを使って"ldtoken"を使うと、「RuntimeHandle」が得られる。またややこしいものがでてきたが、
内部メタデータ トークンを使用する型を表します。
(RuntimeTypeHandle 構造体)
ということで、メタデータトークンの構造体表現くらいだと思っておけばいいと思う。

(2) RuntimeTypeHandleからTypeオブジェクトを得る

RuntimeTypeHandleからTypeオブジェクトを得るには、Type.GetTypeFromHandle(RuntimeTypeHandle)を使う。

これでTypeが得られたので、あとは好きに使えばOK.
たとえば、Nameを表示したければこんな感じ。
// 試しに名前を出してみる
il.Emit(OpCodes.Ldloc, typeObj);
il.EmitCall(OpCodes.Callvirt, typeof(Type).GetProperty("Name").GetGetMethod(), null);
il.EmitCall(OpCodes.Call,
  typeof(System.Console).GetMethod("WriteLine", new Type[] { typeof(String) }),
 null);

このコードで生成したコードがこんな感じ。
ldtoken [HelloMetadata]HelloMetadata.SampleClass
call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
stloc.0
 
ldloc.0
callvirt instance string [mscorlib]System.Reflection.MemberInfo::get_Name()
call void [mscorlib]System.Console::WriteLine(string)

実践 - MethodInfoの場合

MethodInfoの場合はこんな感じ。
var methodInfoObj = il.DeclareLocal(typeof(MethodInfo));
il.Emit(OpCodes.Ldtoken, typeof(SampleClass).GetMethod("SampleMethod"));
il.EmitCall(OpCodes.Call, typeof(MethodBase).GetMethod("GetMethodFromHandle", new Type[] { typeof(RuntimeMethodHandle) } ), null);
il.Emit(OpCodes.Stloc, methodInfoObj);

概ねTypeの場合と同じだが、Methodの場合は二つ注意点がある。
  • GetMethodFromHandle()は、MethodInfo/ConstructorInfoの親であるMethodBaseに存在する。
  • GetMethodFromHandle()は、Typeが指定できるオーバーロードがあるので、メソッド名だけではGetMethod()できない。

名前を表示したければ、同じようにこんなかんじ。
// 試しに名前を出してみる
il.Emit(OpCodes.Ldloc, methodInfoObj);
il.EmitCall(OpCodes.Callvirt, typeof(MethodInfo).GetProperty("Name").GetGetMethod(), null);
il.EmitCall(OpCodes.Call,
   typeof(System.Console).GetMethod("WriteLine", new Type[] { typeof(String) }),
   null);

これで生成されたコードがこんな感じ。
ldtoken method void [HelloMetadata]HelloMetadata.SampleClass::SampleMethod()
call class [mscorlib]System.Reflection.MethodBase [mscorlib]System.Reflection.MethodBase::GetMethodFromHandle(valuetype [mscorlib]System.RuntimeMethodHandle)
stloc.1

ldloc.1
callvirt instance string [mscorlib]System.Reflection.MemberInfo::get_Name()
call void [mscorlib]System.Console::WriteLine(string)

その他

FieldInfoでは試してないけど、OpCodes.Ldlocの説明を見る限り、概ね同じ方法でいけると思う。
また、RuntimeMethodHandleについては、IL生成をしていない場面でもメソッドを識別するのに便利な場面があるかもしれない。

2012-12-02

自室の照明をスマートフォンからOn/Offする

寝たいんだけど、電気消しに行くのめんどくさくて寝れない。そんなことありませんか?今回はそんな人向けのソリューション。でも、うまく動かなくて勝手についたり、チカチカしちゃったりしても自己責任でお願いします。

ちなみに、おおかたの期待を裏切り、今回はハードウェア工作はナシ。既製品だけ。

仕組み

「電気を消したいだけなのに複雑だ」と言われたので絵にしてみた。


複雑だろうか?ちなみに、結構昔からある方法。

使った物

  1. 天井照明器具専用 リモコンスイッチOCR-04 (\1,500くらい)
  2. パソコン用学習リモコン PC-OP-RS1 (\4,500くらい)
  3. Debian GNU/Linuxの入った箱:今回は玄箱PRO (\不明:今ならGuruPlug (\20,000くらい)や、Raspberry Pi (\4,000くらい)がいいかも)
OCR-04以外は家にあったものなのでかかったのは1,500くらいだけ。

作り方

Step1. OCR-04を設置する

OCR-04はこんな形をしたもの。箱に書いてある通り、照明器具と天井の引っ掛けシーリングの間に受信部をセットするだけ。元々のスイッチはOnにしっぱなしにしないと使いものにならないので、ついてきたリモコンは、スイッチがあったところの隣に置いておく。

Step2. Linux boxとPC-OP-RS1をつなぐ

今回はNASとして稼動していた玄箱PROを使ってしまったが、USBがついたLinuxの箱ならなんでもいいはず。LinuxからPC-OP-RS1は、USBシリアルデバイスとして認識できるが、VendorID/ProductIDが登録されていないので、指示してあげる必要がある。コマンドは以下の通り。
# modprobe ftdi_sio vendor=0x0411 product=0x00b3
これで/dev/ttyUSBXができたらひとまずOK。再起動しても認識される&あとで使うための権限が調整できるよう、以下の設定を行う。

まずvid/pidの設定。

/etc/modprobe.d/remosta.conf作成
alias remosta ftdi_sio
options ftdi_sio vendor=0x0411 product=0x00b3

起動時にロードされるようにする。

/etc/modules追記
remosta

このデバイスが使える人のグループを作る。
# groupadd remosta

このデバイスを/dev/remostaXにし、グループを変更する。

/etc/udev/rules.d/92-remosta.rules作成(92なのは91でこのデバイスのグループ変更がされていたから)
KERNEL=="ttyUSB*", ACTION=="add", \
  ATTRS{idVendor}=="0411", ATTRS{idProduct}=="00b3", \
  MODE="0660", GROUP="remosta", NAME="%k", SYMLINK="remosta%n"
ここの設定は間違えるとOSが起動しなくなるから注意!(一回やってHDD抜き出した…)

再起動して、以下の状態になっていればOK。
$ ls -al /dev/remosta* /dev/ttyUSB0
lrwxrwxrwx 1 root root         7 Nov 23 20:58 /dev/remosta0 -> ttyUSB0
crw-rw---- 1 root remosta 188, 0 Dec  1 22:59 /dev/ttyUSB0

Step3. コマンドラインで送受信してみる

Rubyでスクリプトを書いたので、それを使う。

Ruby/serialportが必要なので、インストールしておく。
# apt-get install libserialport-ruby

あとはこのスクリプトをダウンロードして、以下の通りで動く。

受信
$ ruby remosta.rb recv
Receiving. →ここでPC-OP-RS1に対して、リモコンで送ってみる
fffff070000e03fc07f80f.... →受信した信号
Received.

送信
$ ruby remosta.rb send fffff070000e03fc07f80f....(信号)
Sending.
Sent.

これで「A」と書いてあるほうの黄色いシールがついてるLEDから信号が発信される。

Step3. Web UIをつける

凝るほどでもないので、レガシーにRuby/CGIでWebを作った。
https://github.com/sunnyone/lightremo

スクリプトを/var/www/lightremoに配置したら、以下の感じで設定する。
<Directory /var/www/lightremo>
            AddHandler cgi-script .cgi
            AllowOverride None
            Options +ExecCGI
        </Directory>
今回は自宅の無線LANでしか接続できないところに置いたから、CGIとしてはとくに認証もしていないけど、必要ならかける。

www-dataを"remosta"グループに所属させるのを忘れない。

Apacheの設定をリロードして、http://[SERVER]/lightremo/lightremo.cgiにアクセスすれば以下の画面がでてくるはず。


onやoffを押して実際に照明がOn/Offされたら成功!