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されたら成功!





2012-11-26

Android向けのシンプルな壁紙を作るためのGIMP用スクリプト

いくや先生がこんなことをおっしゃっていたので、作ってみました。祝・初Script-Fu.


ダウンロードはこちら: https://gist.github.com/4143779 (android-wallpaper.scm)

といっても、端末の画面サイズの画像をセンタリングしてAndroid壁紙サイズにリサイズするだけだけども。
しょっちゅう壁紙作って替えたい人向けですね。

使い方

Scriptのダウンロードと配置

ダウンロードしたandroid-wallpaper.scmを以下の場所に配置後、GIMPを起動する。
  • C:\Users\[username]\.gimp-2.8\scripts (Windows (Vista以降))
  • ~/.gimp-2.8/scripts (Linux他)

端末の画面サイズの壁紙候補の画像をロードする

壁紙にしたい画像を読み込み、端末の画面サイズに加工する。変なAndroid壁紙サイズではなく、スペック表に書かれている画面サイズ。
この場合の例は、Xperia acroなので480x854。

Script起動

メニュー項目が追加されているので、「画像>Resize for Android Wallpaper」から起動する。

調整と保存

「Resize for Android Wallpaper」を実行すると、壁紙候補の画像がセンタリングされた状態で画像が大きくなる。「Wallpaper Layer」というレイヤーが追加されるので、ここに適当なパターンなりグラデーションなりを貼ればシンプルな壁紙の完成。

裏話

作る前、Androidの壁紙はホーム画面の枚数分横並びだと誤解してたから、ImageMagickでちゃちゃっとくっつければ終わりだろうと思ったんだけど、画面サイズ2倍の横幅をトータルで合うようにちょいちょいずらすだけっていうやっかいな仕様であった。
(詳しく:Androidの壁紙に最適な画像サイズがわからない...助けてライフハッカー!

横に並べることができない以上、サイドパネルの部分を何らかの部分で埋めないといけないのでinteractiveなGIMPで実装することにしたという流れがあったのであった。

おかげで、10年以上昔に書いてみようと思って書いたことがなかったScript-Fuを書けるようになった。Schemeを実用で使ったのは初めてかも。

Script-Fuの書き方

Script-Fuの書き方は、以下を読めばわかった。
おれおれ Script-Fu 入門

ポイントだけ書くと、以下の感じ。
  • スクリプトはfoobar.scmとして$HOME/.gimp-2.8/scriptsに配置する。
  • スクリプトを配置したら、「フィルター>Script-Fu>スクリプトを再読み込み」で読み直す。
  • どんな関数があるかは、「フィルター>Script-Fu>Script-Fu コンソール」でScript-Fuコンソールを起動して「参照」ボタンを押せばインクリメンタルサーチで調べられる。
  • スクリプト用の関数を適当にdefineして、script-fu-register関数を呼んで登録する。メニューにも載せたい場合はscript-fu-menu-registerを呼ぶ。
  • スクリプト用の関数の引数はscript-fu-registerの引数で決まる。
  • どんな感じに書けばいいかはGIMP内蔵のスクリプトを読めばわかる。/usr(C:\Program Files\GIMP 2)/share/gimp/2.0/scripts にたくさんある。

2012-11-18

Gollumのblockdiag対応

どうも都合のよいことにGollumのpull requestにGraphviz対応があり、変更すべきところがまるわかりだったので、真似してblockdiagに対応した。

こんな感じでblockdiagが書ける。
 <blockdiag>
  { A -> B; }
 </blockdiag>

MediaWiki extensionと異なり、seqdiag/actdiag/nwdiagを使うときは専用のものを使う。
 <seqdiag>
  { A -> B; }
 </seqdiag>

出力サンプルはこんな感じ。


Extensionという仕組みがないようなので、パッチという形になってしまうが、対応したものがこちら

Gollumで日本語ファイル名を使う

前回紹介したWiki、Gollum。すでにレポジトリに入っているものに対しては問題なく日本語ファイル名も使えるのだが、Createするときに問題がある。それは...

このように日本語ページへのリンクを作り、
リンクをたどってCreateさせると...
"nituite"はいいにしても"ri ben yu"は日本語じゃありませんから!

これはあまりにもひどいので調べたところ、"to_url"というメソッドがこの変換をやっている模様。あまりはっきりとは調べてないのだが、どうやらstringexというモジュールに"Unidecode"という機能があり、これをしているようだ。

仕方ないので、この機能を外した。to_urlはどうもこのページ名の調整に使っているっぽいので、すべてのto_urlを機能しなくした。それに伴いURLエンコードが必要になる部分があるので、対応した。
 その変更がこちら

GitバックエンドなWiki「Gollum」をインストールする

Gitit使ってたんじゃなかったのかよ、という話なのだが、Gititは単体でディレクトリ以下にホストできない((Virtual)Host必須)とか、いろいろ遭遇したので、別のものを使ってみた。

使ってみたのは、Githubの(?)Wiki engine「Gollum」
http://github.com/github/gollum


Ruby, Sinatraのお馴染みの構成。今のところRuby 1.8でも動く優しい仕様。
画面はこんな感じ。



インストール

gemで入れる方法もあるが、細かい事情によりちょいちょいいじる必要があるため、git cloneしてbundlerで拾ってくる方法を取る(bundlerは例えばUbuntu 12.04ならapt-get install ruby-bundlerで入る。)

bundleコマンドが使える状態になったら、bundleコマンドを叩く前に、libxsltの開発パッケージをインストールする(自分のところでこれが足りなかっただけで、他にも必要かも)
# apt-get install libxslt-dev

あとは、cloneしてbundle installすればOK。
$ cd ~/opt
$ git clone https://github.com/github/gollum.git
$ cd gollum
$ bundle install --path vendor/bundler

設定

保存するためのgitレポジトリを作る。
$ mkdir ~/wikidata
$ cd wikidata
$ git init
--bareで作ったものは2012/11/17時点のmasterでは怒られる(手元のは対応させちゃった)

実行方法

bundlerで入れたので、bundle execを使う。
$ bundle exec bin/gollum --port 5080 --base-path wiki ~/wikidata
--base-path wikiを指定すると、/wiki/がトップになる(=ProxyPassするときに便利)

あとはhttp://server:5080/wiki/にアクセスすればOK.

デーモン化

例によってrunitを使った。以下のような感じ。
$ mkdir -p ~/var/log/gollum
$ mkdir -p ~/service/.gollum/log

$ vi ~/service/.gollum/run
~~~
#!/bin/sh
echo "Start."
cd ~/opt/gollum
exec bundle exec bin/gollum --host 127.0.0.1 --port 5080 --base-path wiki ~/wikidata 2>&1
~~~

$ chmod +x ~/service/.gollum/run
$ vi ~/service/.gollum/log/run
~~~
#!/bin/sh
exec svlogd -tt ~/var/log/gollum
~~~

chmod +x ~/service/.gollum/log/run

$ mv ~/service/.gollum ~/service/gollum

--host 127.0.0.1をつけたいんだけど、--helpに書いてあるのにまさかの無視される罠があるのでそのままでは使えない。(これも対応させた。)
マージされたので--host使えます。

認証

Apacheでかける。

<Location /wiki>
          AuthUserFile /dokka/www/htpasswd-wiki
          AuthGroupFile /dev/null
          AuthName "Input password"
          AuthType basic

          require valid-user
        </Location>

        ProxyPass /wiki/ http://127.0.0.1:5080/wiki/
        ProxyPassReverse /wiki/ http://127.0.0.1:5080/wiki/

これで概ね使うことができる。


2012-11-17

blockdiag-mediawiki-extensionをseqdiag,actdiag,nwdiagに対応させた

blockdiagMediaWiki対応が素晴らしかったので、他のseqdiag, actdiag, nwdiagにも対応させた。PHPなんてひさしぶりで、arrayの書き方覚えてなかったよ。pull requestしたので、うまくいけば取り込んでもらえるかも。

対応してて気付いたのだけど、nwdiagにはrackdiag, packetdiagなんてのもあるのね。ラック図はまぁそうだよねって感じだけど、パケットのアレを描くという発想はなかった... バイナリアン達には便利かも。

(追記)
早速取り込んでいただけました。

2012-11-10

MSBuild Launcher 0.0.2リリース

MSBuild Launcher 0.0.2をリリースした。
 https://github.com/sunnyone/MsbuildLauncher/downloads

キャンセル機能がついた。といっても、ビルド処理を別プロセスで立ち上げてキャンセルしたいときはKillするというアグレッシブな仕様だけど。

bkimgprocに傾き補正機能を追加

bkimgprocに傾き補正機能を追加した。
(といっても、-deskew 40%を引数に追加しただけだけど)

ちなみに、"t"を指定された場合のみ機能するようになっている。テキストでないと誤爆しそうだったので。これでようやくmとtを分けた意味がでてきた。




2012-10-27

MSBuild Launcher 0.0.1をリリース

ここでちょっと触れたMSBuildの良いGUIが見つからなかったので、さくっと作った。

ダウンロードはこちらから。
https://github.com/sunnyone/MsbuildLauncher


MSBuildファイルを開くと、左側にターゲットのボタンができるので、クリックすれば実行される。
コマンドライン引数にも対応しているので、.msbuildを関連付けることも可能。

2012-10-25

C#の"ref"引数のMSIL的取り扱い(.NETと参照渡し)

C#でrefを使ったメソッドのMSIL的作り方/呼び出し方(参照渡しの方法)についてメモしておく。

サマリ

  • メソッドに渡す際は、アドレスを渡す。Cのポインタで言う&valのイメージ。
    • 引数のpushには ldloca命令を使う。
  • メソッド内では、アドレスの解決を行う。Cのポインタで言う*aのイメージ。
    • 取得にはldind命令シリーズ(ldobj)、設定にはstind命令シリーズ(stobj)を使う。
  • 型は、実際の型に&がついた「ref型」を利用する(例: System.Int32&)
    • Type.MakeByRefTypeでref型化、Type.GetElementTypeで非ref型化できる。
    • ref型かどうかは、Type.IsByRefで判断できる。

具体例として、参照を使うコードにありがちなSwapメソッドを作ってみる。

C#コード

void Swap(ref int x, ref int y) {
    int tmp;
    tmp = x;
    x = y;
    y = tmp;
}

void SampleFunc()
{
    int x = 10; int y = 20;
    Swap(ref x, ref y);
    System.Console.WriteLine("x, y: {0},{1}", x, y);
}

これをコンパイルしたものを逆アセンブルしたものが以下。
.method private hidebysig instance void  Swap(int32& x,
                                              int32& y) cil managed
{
  .maxstack  2
  .locals init ([0] int32 tmp)
  IL_0000:  nop
  IL_0001:  ldarg.1
  IL_0002:  ldind.i4
  IL_0003:  stloc.0
  IL_0004:  ldarg.1
  IL_0005:  ldarg.2
  IL_0006:  ldind.i4
  IL_0007:  stind.i4
  IL_0008:  ldarg.2
  IL_0009:  ldloc.0
  IL_000a:  stind.i4
  IL_000b:  ret
}

.method private hidebysig instance void  SampleFunc() cil managed
{
  .maxstack  3
  .locals init ([0] int32 x,
           [1] int32 y)
  IL_0000:  nop
  IL_0001:  ldc.i4.s   10
  IL_0003:  stloc.0
  IL_0004:  ldc.i4.s   20
  IL_0006:  stloc.1
  IL_0007:  ldarg.0
  IL_0008:  ldloca.s   x
  IL_000a:  ldloca.s   y
  IL_000c:  call       instance void ConsoleApplication1.Program::Swap(int32&,
                                                                       int32&)
  IL_0011:  nop
  IL_0012:  ldstr      "x, y: {0},{1}"
  IL_0017:  ldloc.0
  IL_0018:  box        [mscorlib]System.Int32
  IL_001d:  ldloc.1
  IL_001e:  box        [mscorlib]System.Int32
  IL_0023:  call       void [mscorlib]System.Console::WriteLine(string,
                                                                object,
                                                                object)
  IL_0028:  nop
  IL_0029:  ret
} // end of method Program::SampleFunc

呼ばれるSwapメソッド

適当に端折りながら、上から見ていく。
(評価スタックのイメージを忘れてしまった場合は、この絵を思い出してほしい。)

まずメソッドの定義。
.method private hidebysig instance void  Swap(int32& x,
                                              int32& y) cil managed
int32ではなく、int32&型が使われている。これは、
Type t = typeof(Int32).MakeByRefType();
てな感じで得られる。

そしてtmp = x;の部分。
IL_0001:  ldarg.1
  IL_0002:  ldind.i4
  IL_0003:  stloc.0
これは、
  1. 1番目の引数(ref x)を持ってくる
  2. 持ってきたアドレスからint32の値を持ってくる
  3. 0番目のローカル変数に格納する
というイメージである。

x = y;はいったん飛ばして、y = tmp;部分を見るとこう。
IL_0008:  ldarg.2
  IL_0009:  ldloc.0
  IL_000a:  stind.i4
これは、
  1. 2番目の引数(ref y)を持ってくる
  2. 0番目のローカル変数からint32の値を持ってくる
  3. 持ってきたアドレスに持ってきた値を入れる
というイメージである。

x = yは上記の組み合わせ。
IL_0004:  ldarg.1
  IL_0005:  ldarg.2
  IL_0006:  ldind.i4
  IL_0007:  stind.i4

ここでは、int32なので、ldind.i4/stind.i4だが、型によって使うべき命令は違う。ただし、
すべての ldind 命令は、対応している組み込み値クラスを指定する Ldobj 命令のショートカットです。
(OpCodes.Ldind_I4 フィールド)なので、ldobj [type]でもよい。

ldobj/stobjの例としては、ref int?を使うと、以下の感じで生成される。
IL_0008: stobj valuetype [mscorlib]System.Nullable`1

Swap()を呼ぶメソッド

こちらは実際に呼び出しているところだけ。
IL_0001:  ldc.i4.s   10
  IL_0003:  stloc.0
  IL_0004:  ldc.i4.s   20
  IL_0006:  stloc.1
  IL_0007:  ldarg.0
  IL_0008:  ldloca.s   x
  IL_000a:  ldloca.s   y
  IL_000c:  call       instance void ConsoleApplication1.Program::Swap(int32&,
                                                                       int32&)
10、20をそれぞれx、yに入れたあと、this、xのアドレス、yのアドレスの順に持ってきて、関数をコールしている。

リフレクションでの扱い

すでにあるメソッドのMethodInfoをGetMethod()で持ってきて、ParameterTypeを見ると、
&のついたref型で入っている。この元の型を得たいときは、こんな感じになる。
if (parameterType.IsByRef) {
  Type valueType = parameterType.GetElementType();
}
(Type.MakeByRefType メソッド)

2012-10-21

テキトーに使うWCF

WCFのちょっとした実験をしたいとか、ちょっとしたプロセス間通信をしたいといったときに、app.configだとかServiceReferenceだとかはわずらわしい。そんなときに使うコードサンプル。




2012-10-10

自炊向け本画像処理スクリプト群bkimgproc公開

前回のエントリで予告した、ImageMagickのwrapper script群を公開しました。
https://github.com/sunnyone/bkimgproc
git cloneするか、"ZIP"のボタンを押してzipを落として展開してください。

本1冊ごとにディレクトリを作って連番の素材を入れていき、最後にスクリプトを実行するとまとめてくれるツールです。詳しい使い方は、READMEに載せました。

Ubuntu/DebianであればREADMEの方法で使えますが、普通のshell scriptなので、もしかしたらCygwinでも動くかもしれません。

特徴としては、以下の通りです。
  • pdf, cbz出力(PDFは見開き方向指示に対応)
  • 横向きスキャンデータの回転
  • 白レベル自動判定による白レベル調整
  • kobo向けリサイズ(kobo plugin)
  • 余白の自動カット(kobo plugin)
  • テキストのボールド化(kobo plugin)
使い方にも書いてありますが、白レベル判定は、配置したPDFを単位に行うことで、確実性を高めています。なお、PDFの中でも、偶数と奇数ページで白さが違うことがあったので、別々に判定して対応しています。

PDFの見開き方向指示は、こちらの方法を利用させていただいています。

ちなみにうちでは、convall.shの起動は、Jenkinsを使っています。

2012-09-27

ImageMagickで紙の白レベルを判定する

今日は、一部で「美白化」と呼ばれる、もともと白かった紙の色褪せた部分を白くする処理について。白くするには、どれだけ白っぽくするか、すなわち白とする基準を指定する必要があるが、ImageMagickを利用して、どのレベル以降を白と見做すか判断する方法について、この記事で述べる。

結果だけ使いたい方は、判定ロジックのシェルスクリプトを最後に載せているのでどうぞ。
実行すると、このようになる(以下は95%だった例)
$ ./whitelevel.sh test.jpg
95

この方法(+α)で運用しているが、かなりうまく動いている。

背景

ImageMagickを使って美白化をする例はいくつかWeb上にもあり、-levelを使ってパーセンテージ指定で白レベルを指定するか、-linear-stretchを使ってハイライト側のピクセル数から判断する方法があった。しかし、前者は紙によって異なるし、後者は白い部分が少ない画像で悲しい思いをする可能性があるので、実際の画像から白のレベルを決めたい、というのが背景である。

方針

直感的に言えば、白っぽい部分はヒストグラム上は山になっているはずなので、その直感の方法に合わせて、ヒストグラムを作成し、グラフの傾きから計算すればできそうである。しかし、「ImageMagickでヒストグラムを出力し、数値を計算可能な状態にparseする」「数値をもとに山を求める」というののどちらも面倒くさい。しかも、実際に画像を見てみると、白部分がなだらかだったりして、山になっていないこともある。

ちょっと困っていたが、画像を見ていてふと
「はじっこ(枠)って白くね?」
「ImageMagickって枠切りとれるわ」

という2点に気付き、枠を切り出し、その色を白とすることを思い立った。

もう少し具体的な方針を書くと、以下の通りである。
  1. convertコマンドの-trimを利用して、画像全体のサイズと枠の切り出し座標を得る。
  2. 枠のサイズが、白レベルを判定するのに十分であるかを確認する。
  3. 枠を切り出し、色の平均レベル(μ)と、標準偏差(σ)を算出する。
  4. μ-3σ(正規分布における99.73%の点)を得て、白レベルとする。
  5. (得られたレベルと-levelを利用して、美白化する。)

なお、紙の横方向(綴じてあるほうと、その反対側)は、均一でないことがあるので、余白は上下を用いる。

手順

1. convertコマンドの-trimを利用して、画像全体のサイズと切り出し座標を得る

まず、convertコマンドでは、-trimを使うと、余白を切り取ることができる。
-fuzzオプションを併せて利用すると、その範囲を誤差と見做してくれる。
例としては、以下の通り。
$ convert -trim -fuzz 40% hello.jpg hello-trim.jpg

こんな画像が:

こうなる:


さらに、convertコマンドでは、info:を出力先にすると、画像を出力する代わりに、画像の情報を吐き出してくれる。
$ convert -trim -fuzz 40% hello.jpg info:
hello.jpg JPEG 1338x719 1792x1076+184+136 8-bit DirectClass 0.020u 0:00.010

実は、必要な情報は含まれているので、このままでもいいのだが、-formatというオプションを利用すると、出力フォーマットを調整できる。

まずは、以下の情報を使う。
%H   page (canvas) height
%Y   page (canvas) y offset (including sign)
%h   current image height in pixels
それぞれ、「元画像の高さ」「切り出し縦オフセット」「切り出された画像の高さ」である。

$ convert -trim -fuzz 40%  hello.jpg  -format "%H %Y %h" info:
1076 +136 719

これで、切り出しサイズが得られた。

2. 枠のサイズが、白レベルを判定するのに十分であるかを確認する

端から黒かったりすると、白レベル判定に不十分なので、1.で得たサイズが妥当かどうか、確認する。
上記のサイズから計算するだけなので詳細は省くが、今は、高さが全体の1%以下あるいは25%以上の場合は、白レベル判定不能にしている。

3. 枠を切り出し、色の平均レベル(μ)と、標準偏差(σ)を算出する

サイズ情報を得ているので、-cropを使って画像を切り出し、また-formatとinfo:を使って白部分の平均と標準偏差を得る。今回は、以下の指定を使う。

%[mean]                 CALCULATED: average value statistic of image
%[standard-deviation]   CALCULATED: standard-deviation statistic of image

コマンドとしては、ボールド化のときに使った彩度をゼロにする-modulate 100,0を併せて、以下の形(わかりやすさのため改行を入れている)。
$ convert hello.jpg \
  -modulate 100,0 \
  '(' -clone 0 -crop x$TOP_BLANK+0+0 ')' \
  '(' -clone 0 -crop x+0+$BOTTOM_OFFSET ')' \
  -delete 0 \
  -append -format "%[mean] %[standard-deviation]" info:
64209.3 671.804

これは、上下の双方が白レベル判定として有効だったときで、上下をそれぞれ切り出し、もとの画像を削除したあと、くっつけて情報を得ている。$TOP_BLANKは%Yの値で、$BOTTOM_OFFSETは%Y + %hの値である。

4. μ-3σ(正規分布における99.73%の点)を得て、白レベルとする

この得られた白い部分は、ちゃんと白だけの画像であれば、正規分布だろうと想定して、正規分布では99.73%の値をカバーできる、μ-3σの点を白と判断する。
正規分布のイメージはこちら等で: http://www.cap.or.jp/~toukei/kandokoro/html/14/14_2migi.htm

上記の例では、64209.3 - 3 * 671.804 = 62193.888である。
Maxが65535なので、パーセンテージにすると、655.35で割って、62193.888 / 655.35 = 94.901...である。

これで、95%というレベルが得られた。

(5. 得られたレベルと-levelを利用して、美白化する)

端から色がついていたりして、取得できない画像があるので、もう少し工夫が必要なのだが、一応得られたものを適用するには、-levelを利用して、以下のように処理できる。
$ convert -modulate 100,0 -level ,94% hello.jpg hello-white.jpg

実際にこれを使って白くしたものがこちら。

実装

上記を実装したスクリプトは、以下の通り。


よりうまく使うには

よりうまく使うには、単体の画像ではうまくいかず、画像群に対して使うのが妥当なのだけど、それをするとスクリプトを使う前提の説明がしんどいのでここでは省く。

実は何度か紹介してきたような方法を組合せて、決まった置き方をすると自動で本をまとめるスクリプト群ができていて、うまく動いているのだけど、PDF対応中なので、近日公開予定。


2012-09-22

画像分割ツールKiritoriMage公開

画像分割ツールKiritoriMage version 0.0.1をリリースした。GUIアプリケーションのフリーソフトウェアの新規リリースは、もう10年ぶりくらいになる。

http://github.com/sunnyone/kiritorimage/
ダウンロード: https://github.com/downloads/sunnyone/kiritorimage/KiritoriMage-0.0.1.zip

どんなソフトウェアかというと、区切り線を指定して、その区切られた領域をクリックしていくことで、画像を分割していくツール。以下の画像を見てもらえれば、どんなアプリケーションかわかると思う。


たいていの画像処理ソフトは、1回の保存オペレーションでひとつの範囲しか保存できないのに対し、このツールは複数の範囲が保存できるのが特徴。ちなみに、mageと呼ぶほどすごいことはできない。

自分用なので、保存形式がJPEG固定だったり、保存フォルダは画像と同じフォルダのみ(実は裏技があるのだけど)だったりと、制限は多いのだけど、ひとまずは使えるようになったので公開。

MITライセンスにしているので、改変や再配布も自由。もし拡張とかあればpull requestしてもらえれば。

2012-09-09

Taoを使ってC#/WPFでゲームパッドのボタンを読み取る

ちょっとしたツールを作るために、ゲームパッドをC#で書いたWPFアプリケーションから読もうと思ったが、意外とすっきりいかなかった。結論としては、Tao Frameworkというフレームワークに含まれるWin32 APIのwrapperを使うことで、気軽に読める。

---

ゲームパッド (Joystick? Joypad?)を読むにはDirectInputが定番らしくて、たしかに簡単そうに見えたのでチャレンジしてみたのだけど、managed DirectXを.NET 4で使うにはapp.configを書き替えないといけないとか、SetCooperativeLevelの第一引数がWindows.Windows.Forms.Controlだったりする罠(HWNDも取れるので実は後述の方法でいけるのだけど)にあったりして、挫折した。

ゲームパッドを読むためのAPIがWin32のwinmmというのにあるらしいとわかったが、P/Invokeの定義を書くのが面倒なので探してみたら、Tao Frameworkというゲーム用のフレームワークに定義されているらしい。調べてみると、MITライセンスなので使うことにした。

簡単な使い方

Joystick系のAPIはTao.Platform.Windowsに入っている。Tao Frameworkにはインストーラもあるのだけど、このdllを参照追加すればいいだけなので、zipを落としてdllだけ横に置くのが簡単でいいと思う。

参照追加したら、JOYINFO構造体をnewして、joyGetPos()というメソッドにjoypad IDをセットで渡せばおわり。IDはただの0からの連番。なお、joyGetNumDevs()は、つながっているデバイスの数を返すわけじゃないから注意だ。

for (int i = 0; i < Tao.Platform.Windows.Winmm.joyGetNumDevs(); i++)
{
    Tao.Platform.Windows.Winmm.JOYINFO joyinfo = new Tao.Platform.Windows.Winmm.JOYINFO();
    if (Tao.Platform.Windows.Winmm.joyGetPos(i, ref joyinfo) == Tao.Platform.Windows.Winmm.JOYERR_NOERROR)
    {
        this.textBlock1.Text += String.Format("Joypad[{0}] detected. Button: {1}\n", i, joyinfo.wButtons);
    }
}
パッドによってはもっと詳しい情報が取れるjoyGetPosEx()もあるらしい。自分の用途にはその必要がなかったので使わなかったけど、このへんのAPIの使い方についてはこのへんに書いてある。

イベント的に読み取る1: タイマーで読み取る

もうちょっと実用的に使うために、押されたことを検知したい。

API的には、joySetCapture()というメソッドがあり、これを使うと、ウィンドウメッセージを飛ばすことができる。しかし、引数からしてポーリングっぽい感じなので、WPFアプリケーションであれば、HWNDとか意識しなくていいので、タイマーで見たほうが素直だと思うのでそうした。実装はこのような感じ。


イベント的読み取る2: joySetCaptureで読み取る

それでもjoySetCaptureが使いたいならば、こんなかんじ。 一応動いた。

x86/x86_64関数呼び出しチートシートを書いた

アセンブラはいまのところたまにしか触れないので、espとebpってどっちがどっちだっけとか、ebpからプラスとマイナスはどっちがどっちだっけとか、触れるたびに思ってしまう。そんなとき思い出すために、x86/x86_64関数呼び出しチートシートを書いた。呼び出し規約(calling convention)のcheet sheetみたいになってるけど、一番必要だったのは左上のスタックの絵。
(リンクでPDF。書く都合でA3にしたけど、A4で印刷しても読めると思う。)


本当はそれぞれに対して絵をつけたかったんだけど、それどころではなかった。
勉強のために書いたくらいで全然詳しくないので、間違っているところがあればぜひ教えてくださいまし。

以下余談。

興味深いのは、64bit版のcl.exe(on Windows)とgcc(on Linux)の挙動の違い。関数の引数をスタックにpushするときに、cl.exeではraxに入れてからメモリに置くんだけど、gccはDWORDで区切ってそのままメモリに置く。最適化なし(/Odと-O0)なので、-O2とかにすればまた違うんだろうけどね。

cl.exe版(cl /Od /Zi):
  0000000140001030: 48 83 EC 58        sub         rsp,58h
  0000000140001034: 48 B8 08 00 FF FF  mov         rax,0AAAAFFFF0008h
                    AA AA 00 00
  000000014000103E: 48 89 44 24 38     mov         qword ptr [rsp+38h],rax
  0000000140001043: 48 B8 07 00 FF FF  mov         rax,0AAAAFFFF0007h
                    AA AA 00 00
  000000014000104D: 48 89 44 24 30     mov         qword ptr [rsp+30h],rax
  0000000140001052: 48 B8 06 00 FF FF  mov         rax,0AAAAFFFF0006h
                    AA AA 00 00
  000000014000105C: 48 89 44 24 28     mov         qword ptr [rsp+28h],rax
  0000000140001061: 48 B8 05 00 FF FF  mov         rax,0AAAAFFFF0005h
                    AA AA 00 00
  000000014000106B: 48 89 44 24 20     mov         qword ptr [rsp+20h],rax
  0000000140001070: 49 B9 04 00 FF FF  mov         r9,0AAAAFFFF0004h
                    AA AA 00 00
  000000014000107A: 49 B8 03 00 FF FF  mov         r8,0AAAAFFFF0003h
                    AA AA 00 00
  0000000140001084: 48 BA 02 00 FF FF  mov         rdx,0AAAAFFFF0002h
                    AA AA 00 00
  000000014000108E: 48 B9 01 00 FF FF  mov         rcx,0AAAAFFFF0001h
                    AA AA 00 00
  0000000140001098: E8 6D FF FF FF     call        @ILT+5(callee)

gcc版(gcc -O0):
  400498:       48 83 ec 20             sub    rsp,0x20
  40049c:       c7 44 24 08 08 00 ff    mov    DWORD PTR [rsp+0x8],0xffff0008
  4004a3:       ff
  4004a4:       c7 44 24 0c aa aa 00    mov    DWORD PTR [rsp+0xc],0xaaaa
  4004ab:       00
  4004ac:       c7 04 24 07 00 ff ff    mov    DWORD PTR [rsp],0xffff0007
  4004b3:       c7 44 24 04 aa aa 00    mov    DWORD PTR [rsp+0x4],0xaaaa
  4004ba:       00
  4004bb:       49 b9 06 00 ff ff aa    mov    r9,0xaaaaffff0006
  4004c2:       aa 00 00
  4004c5:       49 b8 05 00 ff ff aa    mov    r8,0xaaaaffff0005
  4004cc:       aa 00 00
  4004cf:       48 b9 04 00 ff ff aa    mov    rcx,0xaaaaffff0004
  4004d6:       aa 00 00
  4004d9:       48 ba 03 00 ff ff aa    mov    rdx,0xaaaaffff0003
  4004e0:       aa 00 00
  4004e3:       48 be 02 00 ff ff aa    mov    rsi,0xaaaaffff0002
  4004ea:       aa 00 00
  4004ed:       48 bf 01 00 ff ff aa    mov    rdi,0xaaaaffff0001
  4004f4:       aa 00 00
  4004f7:       e8 06 00 00 00          call   400502 

たぶん、movは即値ではQWORDに詰められないんだろうね。

おまけとして、別のアーキテクチャでやったらどうか?ということで、x86版のCコードをARMで試してみた。
00008398 <caller>:
    8398:       e92d4800        push    {fp, lr}
    839c:       e28db004        add     fp, sp, #4
    83a0:       e24dd008        sub     sp, sp, #8
    83a4:       e59f0020        ldr     r0, [pc, #32]   ; 83cc 
    83a8:       e59f1020        ldr     r1, [pc, #32]   ; 83d0 
    83ac:       e59f2020        ldr     r2, [pc, #32]   ; 83d4 
    83b0:       e59f3020        ldr     r3, [pc, #32]   ; 83d8 
    83b4:       eb000008        bl      83dc 
    83b8:       e1a03000        mov     r3, r0
    83bc:       e50b3008        str     r3, [fp, #-8]
    83c0:       e24bd004        sub     sp, fp, #4
    83c4:       e8bd4800        pop     {fp, lr}
    83c8:       e12fff1e        bx      lr
    83cc:       ffff0001        .word   0xffff0001
    83d0:       ffff0002        .word   0xffff0002
    83d4:       ffff0003        .word   0xffff0003
    83d8:       ffff0004        .word   0xffff0004

000083dc <callee>:
    83dc:       e52db004        push    {fp}            ; (str fp, [sp, #-4]!)
    83e0:       e28db000        add     fp, sp, #0
    83e4:       e24dd01c        sub     sp, sp, #28
    83e8:       e50b0010        str     r0, [fp, #-16]
    83ec:       e50b1014        str     r1, [fp, #-20]
    83f0:       e50b2018        str     r2, [fp, #-24]
    83f4:       e50b301c        str     r3, [fp, #-28]
    83f8:       e51b2010        ldr     r2, [fp, #-16]
    83fc:       e51b3014        ldr     r3, [fp, #-20]
    8400:       e0823003        add     r3, r2, r3
    8404:       e50b300c        str     r3, [fp, #-12]
    8408:       e51b200c        ldr     r2, [fp, #-12]
    840c:       e51b3018        ldr     r3, [fp, #-24]
    8410:       e0633002        rsb     r3, r3, r2
    8414:       e50b300c        str     r3, [fp, #-12]
    8418:       e51b200c        ldr     r2, [fp, #-12]
    841c:       e51b101c        ldr     r1, [fp, #-28]
    8420:       e0030291        mul     r3, r1, r2
    8424:       e50b3008        str     r3, [fp, #-8]
    8428:       e51b3008        ldr     r3, [fp, #-8]
    842c:       e1a00003        mov     r0, r3
    8430:       e28bd000        add     sp, fp, #0
    8434:       e8bd0800        pop     {fp}
    8438:       e12fff1e        bx      lr

さすがに違うね。

2012-09-04

Ubuntu 12.04のGNOMEのメディア自動マウントを無効化する

自動マウントは便利といえば便利なのだが、ファイルシステム復旧のようなセンシティブな作業をしているときにやられると困る場合がある。その際に、無効化するには、以下のコマンドを実行する。
$ gsettings get org.gnome.desktop.media-handling automount
true    ←有効の状態
$ gsettings set org.gnome.desktop.media-handling automount false
$ gsettings get org.gnome.desktop.media-handling automount
false   ←無効になった

うーん、SDカードまた壊れたっぽい。早いなぁ…
[   68.000883] mmcblk0: retrying using single block read
[   68.015714] mmcblk0: error -84 sending status comand
[   68.020530] mmcblk0: error -110 sending read/write command, response 0x900, card status 0x1001201
[   68.029647] end_request: I/O error, dev mmcblk0, sector 1082526
[   73.025586] mmc0: Timeout waiting for hardware interrupt.
[   73.031015] mmc0: hw_state=0x22f8, intr_status=0x0811 intr_en=0x4002
[   73.037895] mmcblk0: error -84 sending status comand
[   73.042710] mmcblk0: error -110 sending read/write command, response 0x900, card status 0x80000980
[   73.051924] mmcblk0: error -110 transferring data, sector 1082527, nr 31, card status 0x80000980
[   73.060763] end_request: I/O error, dev mmcblk0, sector 1082527

参考:http://www.liberiangeek.net/2012/05/windows-7-vs-ubuntu-12-04-how-to-disable-auto-mount-on-media-insertion/

2012-08-22

裁断機180AT-P

前々から、CARL社のディスクカッターDC-210Nで本を切っていたんだけど、最近この機器に必要な40枚(実際は漫画ぐらいの紙の分厚さになると50p=25枚が妥当な印象)ごとに分割する処理が大変になってきて、裁断機がほしくなってきていた。

安全性は気にしている製品がいいし、定番どころでPK-513Lか、その後継のPK-513LNかなぁと思っていたんだけど、ハンドルが畳めないらしく、それはちょっと邪魔だなぁ、いいのないかなぁと思ってやめていた。

ところが、高崎精器というところが出している180AT-Pだと、LEDライトも含めPK-513Lとほぼ同じ性能でハンドルが畳めるというじゃないですか。

ということで180AT-Pを買ってみた。

裁断能力という点では、DC-210Nと比較して40枚が180枚になったというのはやはり大きくて、分厚くない文庫本とか漫画の類なら一発でいけるのは非常に楽。大きい本だとさすがに分けなくちゃいけないけど、手間の量が段違い。作業って、時間も重要な要素だけど、手間っていうのも重要なんだなぁと認識した。

配置しておく場所を作ったので、実はこのブログにあるような立ててしまっておくということはしていないんだけど、ハンドルを折っておけるメリットとして、安心感というのがあるということに気づいた。
というのも、ハンドルが上がった状態だと、ロックはかかっていて実際のところは問題ないのだけど、下方向に力をかけられてしまう。でも、ハンドルが畳んである状態だと、平たいのでそもそも力をかける状況になりにくい。(あそびはあって、押すとちょっとだけ下がるけど)
この安心感は違うなぁと思った。

あとは、LEDが思ったより役に立つということ。裁断機って、こんなに切る場所を定めにくいとは思わなかった。慣れるまでかもしれないけど、期待してなかったけどあってよかった装備。とはいえ、表紙が大きすぎてスキャナに入らない、といった1枚っぺらのようなものだと、これからもディスクカッターを使うけどね。

というわけで、良製品なのに、全然メジャーな感じがしない180AT-Pについて、書いておいてみました。

2012-08-19

ダ・ヴィンチ32UとフリスクケースとUbuntuと

ダ・ヴィンチ32U (Da Vinci 32U) という、ストロベリー・リナックスというところが出しているマイコンボードがある。このボード、「Arduino Leonardo」っぽく動く機械なのだが、1つ1260円という安価で購入できる。その上コンパクトなので、ちょっとしたUSBデバイスを作るのには最適な機械なのだ。
(念のために書いておくと、「Arduino」 とは、ハード的にはAtmel社のマイコン、AVRが乗ったマイコンボードである。しかし、このボードには、専用の簡単なIDE(ほぼエディタ)と、豊富なライブラリ集があり、USBの口から簡単にファームウェアを転送できるので、素でAVRを使うよりずっと簡単に機械を作れる。一つ一つを取ると決してすごくないのだが、全体を合わせたときのお手軽さは、 よくできている。)

で、このダ・ヴィンチ32Uがどのくらいコンパクトかというと、フリスクケースに収まるので、入れてみた。


この赤い基板がダ・ヴィンチ32U。つけたのは、赤外線LEDと抵抗、赤外線リモコン用センサーだけ。

閉じたときの絵:
 

食べるときみたいにちょっとだけ開けると、読み取りセンサーが使えるようになる。
見てわかる通り、フリスクケースに入れてもなお隙間ができるくらい小さいので、物は入れやすい。
フリスクケースは、比較的薄いので、ちょっと力を入れればカッターでも切れる。危ないから、ピンバイスで穴を開けてから広げたほうがいいとは思うけど。

問題は、本来はピンヘッダをつけて、何かに刺して固定する製品のようなので、固定がちょっと難しい。うまい方法が思いつかなかったので、ケースに千枚通しで穴を開けて、右端のGNDピンと、ISPピンに糸を通して、それぞれ縫ってしまった。玉止めなにそれうれしいのという感じになるので、おすすめしない。たぶん、ホットボンドなんかで止めるのがいいんじゃないかなと思う。

なお、ボードに書かれているピン名称は、Leonardoのものと異なるので、読み替えが必要。いくつかのサイトに載っているので、参考にできる。たとえばここ:http://dev.tetrastyle.net/2012/02/leonardo.html

Ubuntuで使うには

Ubuntu 12.04では、arduinoのIDEは1.0が入っている。そのため、Leonardは使えない。 boards.txtを書き換えれば使えるかなーと思ったんだけど、以下のようなエラーが出て書き込みができなかった。
Binary sketch size: 4450 bytes (of a 28672 byte maximum)
avrdude: stk500_recv(): programmer is not responding

なので、http://arduino.cc/en/Main/Software からLinux/32bit版を落としてきて、tar zxvfで/optに展開したら、Leonardを選択することで「Linuxでは」使えるようになった。
(Ubuntu本体に入っているarduino packageには依存関係があるので、apt-get install arduinoで1.0を普通にインストールしてからやったほうがいいと思う。)

ただし、この方法で書き込んでしまったダ・ヴィンチ32Uは、Leonardのbootloaderが書き込まれてしまうので、strawberry-linux公式の言うとおり、Windowsでは使えなくなってしまう(Leonardとして認識されるが、arduino.ccのドライバもstrawberry-linuxのドライバも受け付けない)。

そのため、Ubuntuであっても、最初からstrawberry-linuxのWindows版ドライバのReadmeに書いてある通り、boards.txtを上書きし、ダ・ヴィンチ32Uの定義で使うのがよい。
# cp da-vinci-32u/boards.txt /opt/arduino-1.0.1/hardware/arduino/boards.txt

うちのようにLeonardのbootloaderをうっかり上書きしてしまったらどうするか。Linuxではまだ書き込みができるので、ドライバに含まれている「da-vinci-32u-master.hex」を、上記のboards.txtに書いてある「davinci.hex」というファイル名で、しかるべき場所にコピーしてあげれば、書き込み(Upload)時に、ファームを更新してくれて、Windowsでも使えるようになる。
# cp da-vinci-32u/da-vinci-32u-master.hex /opt/arduino-1.0.1/hardware/arduino/bootloaders/caterina/davinci.hex
こうしてうっかり書いてしまったダ・ヴィンチ32Uを復活させる手段としてUbuntuを使うのもいいかもね。

Arduino Unoとの違い

気をつけなくてはいけないかもしれない点として、Arduino UnoとArduino Leonardが若干違うところがあるようだ。スイッチサイエンスのページにまとめられている:http://trac.switch-science.com/wiki/Guide/ArduinoLeonardo

そもそも簡単にUSBデバイスとして振舞えるようになったというのは大きく違うので、せっかくだからそのうち遊んでみたい。
http://trac.switch-science.com/wiki/tinker-with-Leonardo


C#でHelloWorldプログラムを作成する

C#を使い始めたので、ひとつ入門記事でも。Hello, Worldするプログラムを作るよ。

まずはソースから。


いつもの数行のコードだと思った?Hello, Worldプログラム自体を生成するプログラムでした。

プログラムの使い方はこう。第一引数にexeのファイル名を指定すると、Hello, World!を出力するexeファイルを作成する。
c:\temp> HelloWorld1.exe Hello.exe
c:\temp> Hello.exe
Hello, World!

スタックのイメージ

説明の前に、この絵がとても大事なので覚えてほしい。このイメージがあれば世のMSIL説明記事と戦える。

.NETの世界では、ローカル変数、スタック、引数という3つの枠があり、それぞれ必要に応じて移動して使う。引数の枠から、値をスタックに移動してきて、なんらかの操作を実行、必要に応じてローカル変数に退避する、という感じかな。

ソースの説明

さて、実際にHello, World生成クラスを作ってみる。static void Main()でWriteLine()するだけで本当は十分なのだが、説明のためにインスタンスを生成してSay()というメソッドを呼び出すようになっている。

なお、ソースの頭のところは、お決まりの引数処理だったり.NETの仕組みみたいな話なので省略して進める。

1. クラスの作成

TypeBuilder helloClassBuilder = moduleBuilder.DefineType("Hello", TypeAttributes.Class);
ConstructorBuilder ctorBuilder = helloClassBuilder.DefineDefaultConstructor(MethodAttributes.Public);
ModuleBuilderのDefineType()というメソッドを使い、クラスを作る。
あとでnewしたいので、デフォルトコンストラクタのConstructorBuilderをとっておく。

2. インスタンスメソッドSay()の作成

文字列をコンソールに出力するSay()を作成する。生成後をC#で書くとこんな感じ。
public void Say(string value)
{
 System.Console.WriteLine(value);
}

生成するコードは以下。
MethodBuilder sayMethodBuilder = helloClassBuilder.DefineMethod("Say", 
    MethodAttributes.Public, 
    typeof(void), 
    new Type[] { typeof(string) });
TypeBuilderのDefineMethod()を使って、メソッドを作る。"Say"という名前で、public、戻り値はvoid、ひとつの引数stringを取るという感じ。

ILGenerator ilSay = sayMethodBuilder.GetILGenerator();
MethodBuilderのGetILGenerator()というメソッドを呼び出すと、ILを生成していくためのILGeneratorオブジェクトを得られる。アセンブラコードを書いていくようなイメージ。このあと、Emit()やEmitなんとか()を利用して書き込んでいく。

// System.Console.WriteLine()にSayの第一引数を渡してコール
ilSay.Emit(OpCodes.Ldarg_1);
ilSay.EmitCall(OpCodes.Call,
    typeof(System.Console).GetMethod("WriteLine", new Type[] { typeof(String) }),
    null);

// Sayを抜ける
ilSay.Emit(OpCodes.Ret);

まずldarg_1を使って、関数の引数の1番目を評価スタックにロードする。なお、0はインスタンス。C#はthisはメソッド定義に書いてないけど、最初の引数がselfだったりthisだったりする言語あるよね。そんな感じ。

そして、callを使って、System.Console.WriteLineを呼び出す。
OpCodes.Callには、MethodInfoを渡してあげる必要があるが、これはリフレクションで取ってきてしまう。
上記の例では、stringという引数を持つWriteLineというメソッドを、System.Consoleのタイプから拾ってくる形である。

なぜCallのときはEmitではなく、EmitCallを使っているかというと、可変長引数を持つメソッド用の型をEmit()が受け取ることができないから。ここでは使ってないので、実はEmitでよい。

最後にreturnたるretを書いて、Sayの実装は終わり。

3. Mainの作成

エントリポイントとなるMain()を実装する。生成後のものをC#で書くとこんな感じ。
private static void Main(string[] array)
{
 string text = "Hello, World";
 Hello hello = new Hello();
 hello.Say(text);
}

生成するコードは以下。
MethodInfo thisMainMethod = System.Reflection.Assembly.GetExecutingAssembly().EntryPoint;
MethodBuilder mainMethodBuilder = helloClassBuilder.DefineMethod(
    thisMainMethod.Name,
    thisMainMethod.Attributes,
    thisMainMethod.ReturnType,
    (from p in thisMainMethod.GetParameters() select p.ParameterType).ToArray<Type>());
ILGenerator ilMain = mainMethodBuilder.GetILGenerator();
Mainメソッドは、この生成プログラム自体の定義からもらってきた。実際にILGeneratorを使ってILを作っていくような場面では、生成側のオブジェクトの定義をリフレクションでもらってくるケースが多いと思う。

3.1. ローカル変数の作成

// ローカル変数を宣言してメッセージを格納
LocalBuilder msgLocal = ilMain.DeclareLocal(typeof(string));
ilMain.Emit(OpCodes.Ldstr, "Hello, World");
ilMain.Emit(OpCodes.Stloc, msgLocal);
試しにローカル変数を作ってみた例。DeclareLocal()を呼ぶと、ローカル変数が宣言できるので、評価スタックに値をロードしてから、stlocを使って、そこに保存する。

3.2. インスタンスの生成

// ローカル変数を宣言してHelloオブジェクトを生成・格納
LocalBuilder helloLocal = ilMain.DeclareLocal(helloClassBuilder);
ilMain.Emit(OpCodes.Newobj, ctorBuilder);
ilMain.Emit(OpCodes.Stloc, helloLocal);
インスタンスを作成するときは、newobjにコンストラクタを渡す。ここに限った話じゃないけど、Emit達は、~Builderを受けてくれる。すごくよくできていると思う。

3.3. Sayメソッドを実行

// 作成したオブジェクトに対して、Sayメソッドを実行
ilMain.Emit(OpCodes.Ldloc, helloLocal);
ilMain.Emit(OpCodes.Ldloc, msgLocal);
ilMain.EmitCall(OpCodes.Call, sayMethodBuilder, null);
インスタンス、引数の順にldlocを使ってスタックに詰めて、callでSayメソッドを呼び出す。

なお、お気づきかもしれないが、「3.2. インスタンスの生成」で、オブジェクトはスタックに生成されているので、いちいちローカル変数に入れなくてもいい。文字列も固定なので同様。なので、3.1.~は、以下のように書くこともできる。
ilMain.Emit(OpCodes.Newobj, ctorBuilder);
ilMain.Emit(OpCodes.Ldstr, "Hello, World");
ilMain.EmitCall(OpCodes.Call, sayMethodBuilder, null);

ilMain.Emit(OpCodes.Ret);
retを呼んで終了。


あとは、エントリポイントを指定して、アセンブリをファイルにSaveするだけ。

ここまで読んだあなたは、C#を使って、プログラムを生成することができるようになっているはず。
フィールドとか、Genericとか、よく使いそうなものを全然解説していないけど、そこは「詳しくはWebで」という感じで。

Javaでも同じようなことがきっとできると思う。Cはどうだろう?LLVMとかの力を借りればいいのかな。

IL生成の罠

間違えると実行時のエラーがわかりにくいので、大変である。
以下は例。

ret忘れちゃった

// Sayを抜ける
// ilSay.Emit(OpCodes.Ret);

ハンドルされていない例外: System.InvalidProgramException: 共通言語ランタイムが無効なプログラムを検出しました。
場所 Hello.Say(String )
場所 Hello.Main(String[] )

スタックから取るの忘れた


// ローカル変数を宣言してメッセージを格納
LocalBuilder msgLocal = ilMain.DeclareLocal(typeof(string));
ilMain.Emit(OpCodes.Ldstr, "Hello, World");
// ilMain.Emit(OpCodes.Stloc, msgLocal);

ハンドルされていない例外: System.InvalidProgramException: JIT コンパイラで内部的な制限が発生しました。
場所 Hello.Main(String[] )

気をつけるべし。

参考

動的処理 « C#たんっ! http://csharptan.wordpress.com/2011/12/16/%E5%8B%95%E7%9A%84%E5%87%A6%E7%90%86/
KEN's .NET IL入門 http://www5b.biglobe.ne.jp/~yone-ken/VBNET/IL/il01_FirstStep.html
ラッパークラスの生成 - 匣の向こう側 - あまりに.NETな http://d.hatena.ne.jp/akiramei/20040810/1345306640

余談

実際のところ、「dynamic」もあるし、System.Reflection.EmitでILを書くことになるケースはほとんどないと思う。ダイナミックに関数を生成したいだけなら、Expression Treeという便利なものもあるし。ただ、type-safeなものをなんとかして作りたいなんてレアなときに有用かもしれない。

2012-08-15

ImageMagickで本文ボールド化(改良版)

ImageMagickで「本文ボールド化」っぽいことをするで一応ボールド化できるようになったのだけど、threshold で、重ねる画像(レイヤー)を2値化してしまっているせいで、白黒でスキャンしたときのようなでこぼこが目立つようになってしまった。そこで改良してみた。

改良版はこちら。


改良点は、-thresholdによる2値化をやめ、画像の重ね方を「Multiply」(いわゆるレイヤーの「乗算」)にした。あと、アルファチャネルの作成をやめた。

以前のコマンドは、-thresholdを使わずに+compositeで重ねると、色が濃くなるどころか、かえって薄くなるケースがあったため(重ねた部分が優先されるため)に、黒いところを黒くするために使っていた。しかし、乗算にすれば、どちらかが黒ければ黒くなるので、そうすることにした。(ざっくりたとえば0.2 × 0.2 = 0.04なので、黒くなるという感じ。)
参考:ImageMagick v6 Examples -- Compositing Images


アルファチャンネルの作成も、重ね合わせの処理前にレベル補正をきちんとやっておけば問題なかったのでなくした。逆に、レベル補正しない状態では、アルファチャンネル使うverだと、効果が若干弱くなるものの、ハイライト部分が黒っぽくなることが避けられる感じなので、レベル補正がちゃんとできないようなケースでは、前回と同じく、アルファチャネルにレベルをコピーして乗算したほうがいいかもしれない。

細かいところではほかにも-geometryを-rollにしているが、これは-rollが絵を動かすためのオプションだったので、名前を変えただけ(別に-geometryでも動く)。


この本文ボールド化の効果は、コマンドではわかりづらいので、絵も乗せておく。
(下の絵は、コマンドで処理したのち、画像エディタで切り出し、400%に拡大したもの。)

リサイズ/グレースケール化のみ

convert input.jpg -modulate 100,0 -geometry 650x700 output.jpg


薄い感じ。

+レベル補正/ガンマ補正

convert input.jpg -modulate 100,0 -level 10%,90% -gamma 0.5 -geometry 650x700 output.jpg

結構よくなる。これでもいいかも。ガンマ補正がかなり有効。


+本文ボールド化

convert input.jpg 
  -modulate 100,0   # 色を飛ばす
  -level 10%,90%    # レベル補正
    '(' +clone      # 画像をコピー
        -roll +0+1  # 縦に1pixelずらす
      '(' +clone    # 画像をコピー
        -roll +1-1  # 縦に1pixel戻して、横に1pixelずらす
      ')' +composite -compose Multiply # 縦にずらしたものと、横にずらしたものを乗算で重ねる
    ')' +composite -compose Multiply   # 元の画像と、ずらしたものを乗算で重ねる
  -gamma 0.5        # ガンマ補正
  -geometry 600x750 # サイズ変更
  output.jpg


さらに文字を太く。

2012-08-14

横倒しスキャンはどれだけ速いのか?

本をスキャンする際、横に倒して読んで後で回転させたほうが速くスキャンできると聞いて、どのくらい速くなるか試してみた。単純に考えると、紙を送る量が少ないので、速そうである。

結論としては、試した範囲では、縦でのスキャンに比べて、1.3倍程度の高速化であった。

計測方法

ScanSnap S1500のスーパーファイン/カラーのモードで、以下の3種類の紙を、25枚ずつ、縦(そのままの形)と横(倒した形)で1回(*)ずつ、スキャンボタンを押してから紙送りが終わるまでの時間を、ストップウォッチで計る。

(*)実はいくつか2回読んだ。でも、ほとんど変わらなかったので、1回としている。まぁ、1回でも2回でも精度はよくないと思うので、真面目な結果を知りたい方はぜひもっといろいろなパターン/回数でチャレンジを。

種類高さ幅・高さ比
新書10.8cm17.3cm1.60
コミック(普通)12.3cm18.2cm1.47
コミック(大)14.3cm21.0cm1.46

サイズは、裁断後のサイズ(実測)である。

計測結果

スキャン所要時間の結果は、以下となった。
種類縦(1枚あたり)横(1枚あたり)縦時間・横時間比
新書48.5s34.9s1.94s/枚1.40s/枚1.39
コミック(普通)50.8s38.0s2.03s/枚1.52s/枚1.34
コミック(大)56.4s41.8s2.26s/枚1.67s/枚1.34

感想

200ページ(100枚)のコミックだとすると、縦の場合と横の場合で以下のようになる。
縦:2.03 秒 × 100 枚 = 203 秒 = 3分23秒
横:1.52 秒 × 100 枚 = 152 秒 = 2分32秒
後処理の問題や、紙挿入の取り違えなんかもあるので、この時間差をどう見るかは、人次第だと思う、という感じの結果であった。

また、最初は縦横比そのものになるのかなと思っていたが、縦横比とは若干ずれた比になった。動きを見ていると、紙を送り終わった後に若干の待ちがあるので、縦横比に関わらず1枚あたりに固定的にかかる時間があるのだと思う。

実際、「コミック」の場合で、縦横比に関わらない時間があると想定して計算してみると...
 a: 1cmあたりのスキャン時間
 b: 1枚あたりで固定にかかる時間

 2.03 = 18.2a + b
 1.52 = 12.3a + b
を解いて
 0.51 = 5.9a よって a = 0.086
 2.03 = 18.2 × 0.086 + b より b = 0.46
1cmあたり0.086s、1枚あたり0.46s。

残りの「新書」「コミック(大)」で試しに計算してみると、
新書(横):10.8×0.086 + 0.46 = 1.43s/枚 (実測: 1.40s/枚)
新書(縦):17.3×0.086 + 0.46 = 1.94s/枚 (実測: 1.94s/枚)
コミック大(横):14.3×0.086 + 0.46 = 1.69s/枚 (実測: 1.67s/枚)
コミック大(縦):21.0×0.086 + 0.46 = 2.27s/枚 (実測: 2.26s/枚)
とまぁ実測と近い感じなので、きっとそういう感じなのでしょう。
(もっといい考察があれば教えてくださいまし。)

なお、横版は、紙送りが終わった後に、ファイル(PDF)に出すのに時間がかかっていたということも付け加えておく。

2012-07-29

ImageMagickで「本文ボールド化」っぽいことをする

スキャンした本達を加工しているのだが、バッチ的に加工したいので、ImageMagickで加工する方法を模索している。

概ね自分に必要なことはImageMagickでできそうなのだが、ChainLPについている「本文ボールド化」というのに相当するのが見当たらない。ImageMagickなら適当に操作すればできるだろうということでやってみた。

結論のコマンドは、以下のとおり。


どうしてこうなったのかがわからないと思うので、詳しいことは以下に書く。

方針
本文ボールド化の設定画面を見ると、XとかYがあって、それぞれ値を増やすと画像の黒い部分がそれぞれの方向に伸びる。 ということは、黒い部分を検出して、XとYに伸ばしているに違いないと読んだ。

これは、以下の方法でできそうなので、それをコマンドでやることにする。
  1. 画像をコピーする
  2. 黒検出する
  3. 1pixel移動する
  4. 元の画像に重ねる
実装
上述の方法を、ImageMagickのコマンドで実現するとこうなる。
convert input.jpg # input.jpgをロード
   '('                   # スタックの次の画像として処理開始
      +clone                   # 最後の画像をコピー
      -threshold 50%           # 50%を境に、白黒にする
      -alpha Copy              # 白黒の値(Value大=白なので、白っぽさ)をアルファチャンネルにコピーする
      -channel alpha -negate   # アルファチャンネルの値を、逆転させる(黒っぽさにする)
      -geometry +0+1           # 縦に1pixelずらす
        '('                    # スタックの次の画像として処理開始
            +clone             # 前の画像をコピーして
            -geometry +1-1     # 縦の1pixelを戻し、横に1pixelずらす
        ')'                    # スタックの現在の画像の処理完了
      +composite               # ひとつ前の画像を、その前の画像に結合
    ')'                        # スタックの現在の画像の処理完了
   +composite                  # ひとつ前の画像を、その前に画像に結合 
   output.jpg                  # output.jpgに出力

-threshold 50%は、結構悩んだ。最適解には思えないので、-levelとか、-black-thresholdとかをうまくあてこむともっとよくなると思う。

参考
さらっと書いているが、ここまでたどりつくのにだいぶ苦労した。いろいろなところを読んだのだが、一番近くなったのはここ。
ImageMagick 透過PNGを非透過な画像に変換する

まぁ、なんといっても公式が大事。-fxまで使えれば、特殊なアルゴリズムを必要としない、バッチ的な画像加工は概ねできるんじゃないかと思っているが、まだ使いこなせていない。
ImageMagick: Command-line Options

2012-07-12

片付けの基本方針とコンピュータのデータ管理

片付けについてTipsはよくあれど、原則というか、もっと基本方針的なものがあるに違いない、ということで、ちょっと片付けについて勉強してみた。

Tips集ではなく、片付け方針が書いてありそうな本ということで、この本をチョイス。

この本[もう片づけで疲れない収納法]には、概ねこんなことが書いてある(もっと書いてあるけど、自分に都合のいいところだけピックアップ)
  •  片付けをするのは、使うため(使いやすくするため)。
  •  身の丈にあった片付け方をする(人によって、家の広さも、片付けにかけられる時間も、収納にかけられるお金も異なる)
  •  収納指数(歩数+アクション数)を最小に(よく使うものほど近く)
このあたりから、これって、こんぴゅーたにおけるデータの管理の仕方の話じゃね?ということに思い当たる。
  • 保存しなければいけないのは、後で使うデータ。それ以外、さっさと解放したほうがいい。
  • indexをつけると検索(取り出し)は早くなるが、書き込み(収納)は遅くなる(=手間がかかる)。
  •  アクセスコストの低い装置(L1, L2, メインメモリ…)には、よく使うものを配置したほうが効率が良い。

とりあえずの基本方針として、以下を選定。
  •  不要なものは、きちんと捨てる(Garbage Collectionする)。
  •  人/場所/物に合わせた、検索効率と書込効率のバランスのとれた収納手段を選ぶ。
  •  アクセスコストの低い場所には、利用頻度が高いものを収納する。

何をもって必要と判断するか、というとても大きな問題が残っているけど、とりあえずは。継続性を考えるのが最優先課題である。

昔は「メモリは、机の広さみたいなもので、広い方がいい」なんていう話をよく聞いたけど、今は逆の発想で、「机の上(memory)は限度があるから、使っていない(recentにuseしていない)ものはしまう(page outする)」とか、そういった方向性で考えたい。

2012-07-05

vimでテキストを横に並べて同期スクロールする

二つのテキストファイルがあり、WinMergeのような比較ツールが役に立たない場合に、並べて比較したい場合がある。

そんなときは、gvimを使って、垂直ウィンドウ分割をした後、それぞれにファイルをロードし、それぞれで以下のコマンドを打つと、左右のウィンドウでスクロールが同期されて便利。
:set scb

中身が全然違っていて、ずれちゃったような場合は、テキストを入力してずらしてもいいし、以下のコマンドを打てば、スクロール同期が解除される。
:set noscb

本当はもっと汎用的な方法(エディタに依存しないとか)がありそう、というかそうしたいのだけど、とりあえず。

参考:http://edo.tumblr.com/post/10388828485/vim

2012-06-20

Gitで失敗コミット+修正コミットをひとつのコミットにする(git rebase -i)

gitを使っていて、間違ってコミットしたファイルを修正したコミットを、後からくっつける(正しく作ってコミットしたときと同じにする)方法。

概要としては、git rebase -iを使い、特定のコミットに戻した後、interactiveにコミットを操作する。

以下、手順。
1) 状態確認
git logで、状態を確認する。
$ git log --oneline
68af727 Write CCC                →でも他のコミットをしてしまった
719560b Write BBB (Retry)        →間違いに気づいてまたコミットした
92746af Write BBB                →ここでコミットしちゃったんだけど
585c5d6 Write AAA
84041cf Created initial files.

「Write BBB」で誤って、「Write BBB (Retry)」で修正をして、コミットしたのを、「Write BBB」に統合する。(Retry)の時点でうっかりamendを忘れた感じ。

2) rebase -iで「Write AAA」の時点に戻す
git rebase -iをすると、指定したコミットから後の処理をeditorで指定してリプレイできる。

$ git rebase -i 585c5d6

エディタが開くので、(Retry)の「pick」の部分を「f」に変更する。

pick 585c5d6 Write AAA
pick 92746af Write BBB
pickf 719560b Write BBB (Retry)  ←修正
pick 68af727 Write CCC

# Rebase 84041cf..68af727 onto 84041cf
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message ←fはfixup
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#
「f」でなく、「s」にすると、コミットログを修正できる。

保存してエディタを終了すると、修正される。
[detached HEAD a7fb63f] Write BBB
 1 files changed, 1 insertions(+), 1 deletions(-)
Successfully rebased and updated detached HEAD.

3) 確認
くっついている。
$ git log --oneline
d519a6c Write CCC
a7fb63f Write BBB
585c5d6 Write AAA
84041cf Created initial files.

git本当に難しい...






ScanWebにスマートフォンUIとプレビュー機能を追加


前に「SheevaPlugを使ってScanSnap fi-5110EOXをネットワーク対応スキャナにする」という話で、簡単なWebUI (scanweb)を作ったが、以下の点が不便だったので、改良を加えた。思想はそのまま、作りはごっそり変更という形。

  •  スマートフォンからボタンが操作しにくい
    →jQuery Mobileを利用したスマートフォンインタフェースを実装
  • 結果がわからないので、裏表が間違っていたりしてもわからない
    →プレビュー機能を実装
launchpadのjunkに置いてあったが、launchpadのprojectを作るほどでもないので、githubに置いた。必要であれば、zipで落とすなりgit cloneするなりお願いします。
https://github.com/sunnyone/scanweb

操作イメージは、以下のような形。
スキャンを押してしばらくすると...↓
 プレビューとログが出力される。

生のWEBrickはしんどくなってきたので、Sinatraを使った。プレビューはどうしているかというと、pdfを作るときに、サムネイルの入ったzipファイルを作るようにして、Webから呼び出されたら中身のファイルを抽出して表示するようにしている。

jQuery Mobileは使いやすくて、↑の画面には装飾されたボタンなんかがあるけど、CSSやJSは全然書いてない。マークアップのルールにしたがっただけ。こういう適当なアプリでは、見た目は何も考えなくてよくなってすばらしい。

問題は、SheevaPlugが遅くて、スキャン後の後処理にかなりの時間がかかること。これは想定外だった...

2012-06-12

PowerShellをMSBuildから呼び出す

MSBuildは、MSの世界のAntのようなもの。そのMSBuildから、PowerShellのスクリプトブロックを呼び出す方法。

きれいな方法を目指すのであれば、PowerShell MSBuild Taskというものがあり、そのTaskを呼び出してPowerShellを実行すればよいのであるが、入っているdllが32bitであった。

32bit版MSBuildから呼べばいい話なのだと思うが、他のツールとの関連が面倒なので、本エントリでは安直にpowershellコマンドを呼ぶ方法を紹介する。

単純に書けば簡単で、Execタスクを利用して、powershell -Command "& { }" を呼び出せばよい(この表記については、PowerShell.exe コンソールのヘルプを参照。)

ただし、生で記述するとごちゃごちゃするので、MSBuildのPropertyGroupの中に、コマンドの始まりと終わりの文字列をPSBegin, PSEndなどとして定義し、ExecのCommand属性で$(PSBegin) $(PSEnd)などのように使うことで、ちょっときれいに書ける。
 
 例は以下のとおり。


上述のように、Invoke-Commandを使ってのリモートの呼び出しを塊にしておけば、リモート呼び出しもさっくり書ける。(リモートからPowerShellを呼ぶ環境を作るのが難しいのはまた別の話。)

参考:http://blog.brianhartsock.com/2009/10/20/using-powershell-scripts-from-msbuild-scheduled-tasks-etc/


まったく余談だが、勝手な要素をどんどん作っていくのがキモチワルイ...


2012-06-11

Javaっぽいスクリプト言語BeanShell クイックスタート

前のエントリとは打って変わって、今度はJava世界のお話。

BeanShellは、JavaVM上で動くスクリプト言語。Javaっぽい文法で、Javaのライブラリを利用することができる。簡単な説明はこちら:http://news.mynavi.jp/column/jsr/031/index.html

どうしてこれを使いたかったかというと、JMeterからBeanShellのスクリプトを実行することができるため、前処理等で好きな処理を実行することができる(らしい)ためである。
(ctxやvarsなど、JMeterからBeanShell側に渡ってくる変数から、JMeterのデータを操作することができる。らしい。これはまだ試してない。)

基本

マニュアルのQuick Startにある通り、bshのjarにクラスパスを通し、"bsh.Console"か"bsh.Interpreter"のクラスを動作させることで、実行することができる。
http://www.beanshell.org/manual/quickstart.html#Quick_Start

C:\temp> java -cp c:\opt\bsh\bsh-2.0b2.jar bsh.Interpreter
BeanShell 2.0b2 - by Pat Niemeyer (pat@pat.net)
bsh % System.out.println("Hello, World");
Hello, World
bsh %

bsh.Interpreterは、ふつうのインタプリタで、/bin/bashみたいなものだと思えばOK.
bsh.Consoleは、GUIつきのコンソールを出してくれる。そのため、Windowsでは、以下のようなバッチファイルを準備しておくと便利。



ファイルを実行

ファイルに書いたスクリプトを実行するには、bsh.Interpreterにファイル名を渡せばよい。ファイル名のあとにさらに引数を書くと、プログラム側のbsh.argsから取得できる。

c:\temp\sample.bsh




C:\temp> java -cp c:\opt\bsh\bsh-2.0b2.jar bsh.Interpreter c:\temp\sample.bsh FIRST-ARGUMENT
args[0]: FIRST-ARGUMENT

編集環境を作る

Eclipse上で動くEclipseShellを試してみたが、なにやらいまいちだったので、安直にサクラエディタを使うことにした。サクラエディタの設定 > タイプ別設定一覧からJavaを選択し、設定変更で「ファイル拡張子」にbshを加えれば、色づけはそれっぽく使える。

ツール>外部コマンド実行(Ctrl+F5)で実行できると便利なので、以下のようなバッチファイルを作成して実行できるようにする(~%4とかは適当。$@的に書く方法もあると思うが調べていない。)



あとは「外部コマンド実行」ダイアログから以下のように指定すればOK
C:\opt\bsh\bsh-interpreter.bat $F FIRST-ARGUMENT

ここまでわかれば、あとはJVMの世界なので、適当に使えると思う。

PowerShellとWPFでシンプルGUIプログラミング

ずっとWindowsでお手軽にGUIを作る方法を探していたが、ついに見つけてしまったかもしれない。

欲しかったのは、こんな感じのことができるもの:
  • なるべく追加ソフトウェアをインストールせずに使えるもの
  • 複雑なレイアウトは別にできなくてもよい
  • UIはなるべくシンプルに書ける
  • プロジェクトなどを必要とせず、1ファイルのスクリプトで表現できる
どのようにするかというと、PowerShellスクリプト上にXAMLをインラインで記述し、WPFのUIを作ればよい。

以下、ボタンを押すとWebサービスを呼び出して、テキストボックスに東京の天気を表示する例である。


PowerShellの仕組み上、.NETでできることはできるだろうとわかっていたので、Windows Formsを使えばいけそうな気はしていたが、コードがごちゃごちゃしそうでやめていた。
しかしWPFという発想はなかった。

なお、「なるべく追加ソフトウェアをインストールせずに使えるもの」という条件を外せば、ShowUIというモジュールがあり、よりPowerShell的に記述できる(らしい)。しかし使う人みんな入れないといけないので、ちょっと微妙だと思っている。

また、蛇足であるが、PowerGUIというエディタが便利。一度走らせると、ダイナミックな言語にも関わらず、ちゃんとVisual Studio的な補完が可能である。

参考:
http://stackoverflow.com/questions/5829787/powershell-wpf-from-xml-findname-problem
http://thepowershellguy.com/blogs/posh/archive/2009/05/15/powershell-v2-get-weather-function-using-a-web-service.aspx
http://www.dougfinke.com/blog/index.php/2011/07/24/extending-powershell-to-the-gui-with-showui/

2012-05-24

gitレポジトリのテキストファイルがWikiになるGititのインストール


プレーンテキストなファイルで保存されるWikiでモダンなものがないかなーと思ったら
Gitをバックエンドに使ったプログラマ向きWiki - Gitit を見つけたので、Gititを Debian squeezeにインストールしてみた。

最初は記事の通り入れようと思ったんだけど、cabal installでエラーになり失敗。
検索してみると、
もしかしたらコンパイラが古いのではとGHCについて検索してみると、yumでインストールしたものは6.12.3で、http://www.haskell.org/ghc/ にある最新版は既に7.4.1になっていました。
さくらVPS(CentOS6)にGititをインストール

とのことなので、ビルドの依存パッケージを入れようとしたが、apt-cache searchしてたらgitit自体のパッケージがあることが判明。
# apt-get install git
# apt-get install gitit
でインストール完了。

 あとは、
$ mkdir gitit
$ cd gitit
$ gitit --print-default-config > gitit.conf
$ gitit -f gitit.conf
 とすれば、とりあえず5001ポートで立ち上がるので、http://localhost:5001/で試すことができる。

 デフォルトでは、gitit/wikidataがレポジトリとなり、ページを作成すると、ページ名.pageというテキストファイルができる。ページの名前に/を入れてページを作れば、ディレクトリができる。日本語のページ名にすれば、日本語のファイル名ができる。UTF-8だったので、普通にlsで見れる。すごく幸せな感じ。

Webから変更するときに、変更内容を入力するが、これはコミットメッセージになる。

なお、フォーマットはデフォルトではMarkdown. reSTなどいくつかの形式に変更できる。

デーモン化

 gititは(デフォルトでは?)フォアグラウンドで立ち上がるので、runitでデーモン化した。Debian的には、start-stop-daemonを使えばいいと思う。

 いつもの通り、適切な場所に"run"ファイルを作ってあげればおしまい。

(runsvdir対象ディレクトリは~/service, ログディレクトリは~/var/log/gititとする)


 これでサービス化完了。

Apacheで受け付ける

Wiki自体に認証をかけたかったので、5001の前にApacheを立てる。


リンクに含まれるパスを調整する方法がわからなく、mod_proxy_htmlを使うのもなんだったので、VirtualHostで作った。適当なVirtualHostの設定を/etc/apache2/sites-available以下に置いて、a2ensiteで有効化した。

BASIC認証のユーザを使う

デフォルトではGititは自前のフォームで認証するため、BASIC認証とかぶってしまう。そのため、gigit.confを変更して、Apache側で認証したユーザ名を使えるようにする。
~~~
authentication-method: http  #formから変更
~~~

ついでにトップページのファイル名もFront PageからFrontPageに変えてしまった。
~~~
front-page: FrontPage

no-delete: FrontPage, Help
~~~

これで完了。






2012-05-07

掛け時計を修理する(ムーブメントの交換)

全くITとは関係ないのだが、GWに時計の修理をしてノウハウっぽいものが若干たまったのでブログに載せる。

家にある壁掛け時計が壊れてしまった。前々から兆候はあり、たまに止まる現象が発生していた。入れていた電池がひどく液漏れしていたので、同じ現象が発生し、電池に負荷を与え続けながら最期を迎えたのだと思う。

新しい時計を買ってきて壊れた時計は捨ててしまえば良い話なのだが、おそらく10年、もしかすると20年以上使い続けている時計なので、修理を試みることにした。
(修理といっても、時計についているムーブメントを交換してしまうので、別物といってもいいのだけど...)

ムーブメントは通販で買えるようだったが、実物を比較しながら買いたかったので、東急ハンズに買いに行った。買ってきたムーブメントが以下である。


ちなみに、このムーブメントは、掛け時計の場所には置いていない。ジャンル分けを聞いたわけではないので確実ではないが、ハンズに売っているのは、自作の時計を作るためのもののようなので、クラフト用品という扱いで、そのフロアにあるのだと思う。2012/5/2時点では、新宿のハンズでは7Fにあった。

<ムーブメント購入時の注意点>

このムーブメントだが、買う際にチェックすべきポイントがいくつかある。
  1. ステップ式(カチカチと動くタイプ)か、スウィープ式(なめらかに動くタイプ)か
  2. ネジ部分(下図青線部分)が必要かどうか(組み込み式かどうか)
  3. 突起部分を通す穴の幅(下図黄色線部分)
  4. 突起部分の高さ(下図赤線部分)
  5. 針が適合するかどうか




1. ステップ式(カチカチと動くタイプ)か、スウィープ式(なめらかに動くタイプ)か

これは好みの問題なので、好きなほうを買えばいいのだが、2種類あることは知っておく必要がある。

2. ネジ部分が必要かどうか(組込み式かどうか)

今回購入したムーブメントは、突起部分の一番下についているネジと、時計板の穴にセットした金具をネジ止めすることにより固定するタイプである。

しかし、今回修理対象となった時計がそうなのだが、時計板裏面に切欠があり、ムーブメントをひっかけて止める時計がある。

Webによると前者が一般的なようである。後者は「組込み式」と呼ぶらしい。
http://item.rakuten.co.jp/watchparts-okayama/10000725/

ムーブメント側がネジ止めするタイプで、時計側がひっかけて止めるタイプであっても、ムーブメントにひっかける部分があれば止めることができる。そのため、今回は特に問題にならなかった。しかし、ひっかけて止めるタイプの場合、ネジ止めする金具が不要のため、3.で記載する穴が狭く設計されていて、幅が足りない可能性があるので、注意が必要である。

3. 突起部分を通す穴の幅

時計板の真ん中に空いた穴よりも、突起部分の幅のほうが大きいと、ムーブメントを取り付けられない。そのため、穴の幅は測ってからムーブメントを買いに行くべきである。
(どうしてもつけたい場合は、時計板の穴を大きくするという方法もあるが。)

4. 突起部分の高さ

突起部分の高さはとても重要である。低すぎると時計板を通らないし、高すぎると時計のカバーに当たってしまう。

5. 針が適合するかどうか

ムーブメントの突起に針の根元の穴を押し込んで止める形になるので、ムーブメントと針が適合していないと、針を差し込むことができない。

今回はまってしまったのだが、もともとの時計についていた秒針が今回購入したムーブメントとあわず、針の穴の奥まで差し込むことができずに、秒針が高く(図の緑線)なってしまい、時計のカバーをかけることができなくなってしまった。

そのため秒針は、再度ハンズに行き、このメーカーの、元の時計と似たようなデザインのものを購入し、交換した。

<交換作業の注意点>

適合するムーブメントを購入することができれば、あとは交換するだけなのだが、1点だけ注意点がある。

それは、針をセットするときは、すべての針を12時に揃えてセットすることである。

これをしないと、長針が0分なのに短針は4時と5時の真ん中を示している、といった読み取れない時計が出来上がってしまう。
(上記を一度やってしまい、プログラマが一度ははまる、テトリミノが壁を突破するテトリスを作ってしまった気分になった。)

<まとめ>

秒針が刺さらないというアクシデントはあったが、無事に交換することができた。
出費は、ムーブメントが1,500円くらい、針が400円くらいである。
Web情報だが、作業を時計屋さんにお願いすると、4桁円台後半のようであった。調査やら作業の手間、なにより部品適合のリスクを考慮すると、妥当な金額と思える。
安い時計であれば買い換えてしまえば良いし、高い時計であれば修理を依頼したほうが良いと思うので、ムーブメント交換は趣味の範囲かもしれない。

なお、100円ショップで買ってきた時計のムーブメントと交換するという技をよく聞くが、上述のようなハードルがあるので、対応する時計を選択することは難しいかもしれない。100円なのでダメだったら捨てればよいという考えもあるかもしれないが、注意が必要である。


<蛇足>

時計の修理とは違う方向になるが、電波時計のムーブメントや、逆方向に動くムーブメント、24時間で1周のムーブメントといったものもあった。自作時計という意味では面白いかもしれない。