壊れたメガネ

ホッチキスの達人の意識の高いブログ。

あるぺちぱーの最期

昨日、某PHP拡張の作者に、5.5対応してくれってんでパッチ送ったんですわ。ええ、恥ずかしながら人生初パッチですわ。
それがね、一時間もしないうちに「その変更は既に行われていて、すぐに公式にリリースする」って、ご丁寧にgithubのURLまで添えて返信返ってきたんですわ。
顔真っ赤にしながら、change log

 
憤死した情弱の手記はここで途切れていた。

 
 
 
 

iOS プログラミングメモ; HTTP を扱う

概要

HTTP リクエストを発行しレスポンスを受信するまでのプログラムの流れはだいたい次の通り。

  1. リクエストヘッダ(NSURLRequest)を作る
  2. NSURLConnection のクラスメソッドを用いリクエストを発行する
  3. レスポンスヘッダ(NSURLResponse)とレスポンスボディを取得する

Request オブジェクトを作る

NSMutableURLRequest を用いる。
NSMutableURLRequest は親クラスである NSURLRequest に比べリクエストボディ(POSTデータ)や HTTP ヘッダの設定が容易に行える。

NSData *query = [[NSString stringWithFormat:@"foo=bar&baz=%@", @"foobar"]
                       dataUsingEncoding: NSUTF8StringEncoding];
NSMutableURLRequest *request = [NSMutableURLRequest
                      requestWithURL:[NSURL URLWithString:@"http://localhost/test/"]
                      cachePolicy:NSURLRequestUseProtocolCachePolicy
                      timeoutInterval:60.0];
[request setHTTPMethod:@"POST"];
[request setValue:@"application/x-www-form-urlencoded" 
                      forHTTPHeaderField:@"Content-Type"];
[request setValue:[NSString stringWithFormat:@"%d", [query length]] 
                      forHTTPHeaderField:@"Content-Length"];
[request setHTTPBody:query];

同期方式

NSURLConnection.sendSynchronousRequest:returningResponse:error: を用いる。
同期方式は sendSynchronousRequest を呼び出したスレッドにおいて、リクエスト・レスポンス送受信が逐次処理される。 仮に UI スレッドでこのメソッドを呼び出すと、呼び出し元に制御を返すまでブロックされ、画面はフリーズする。

NSHTTPURLResponse *httpResponse;

/* HTTP リクエスト送信 */
NSData *contents = [NSURLConnection sendSynchronousRequest:request 
                      returningResponse:&httpResponse error:nil];
NSString *contentsString = [[NSString alloc] initWithData:contents encoding:NSUTF8StringEncoding];
NSLog(@"contents:\n%@", contentsString);

/* HTTP レスポンスヘッダ取得 */
NSDictionary *headers = httpResponse.allHeaderFields;
for (id key in headers) {
    NSLog(@"%@: %@", key, [headers objectForKey:key]);
}

非同期方式

NSURLConnectionDelegate プロトコルを実装する。 NSURLConnection.initWithRequest で接続されるが、同期方式と違い別スレッドでリクエスト・レスポンス送受信が処理されるためブロックされない。

@interface HTTPDownloadDelegate : NSObject<NSURLConnectionDelegate> {
    NSMutableData *contents;
}

@end

@implementation HTTPDownloadDelegate

/* レスポンスヘッダを受け取る */
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    // (NSHTTPURLResponse *)にキャストする
    NSHTTPURLResponse *httpResopnse = (NSHTTPURLResponse *)response;
    // レスポンスヘッダを列挙
    NSDictionary *headers = httpResopnse.allHeaderFields;
    for (id key in headers) {
        NSLog(@"%@: %@", key, [headers objectForKey:key]);
    }
}

/* データを受け取る度に呼び出される */
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    [contents appendData:data];
}

/* データを全て受け取ると呼び出される */
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    NSString *csvContents = [[NSString alloc] initWithData:contents
                      encoding:NSShiftJISStringEncoding];
    NSLog(@"%@", csvContents);
}

@end

int main(int argc, char *argv[]) {
    /* リクエストを作る */
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://localhost/"]];

    /* 上で定義した NSURLConnectionDelegate のインスタンスを作る */
    HTTPDownloadDelegate *delegate = [[HTTPDownloadDelegate alloc] init];

    /* リクエストを送信する。 
     * この main スレッドから分岐し別のスレッドでリクエスト・レスポンスの送受信が行われる。
     * そのためアプリはここでブロックされない。 */
    NSURLConnection *connection = [[NSURLConnection alloc]
                      initWithRequest:request delegate:delegate];
    if (!connection) NSLog(@"failed to create connection");

    [[NSRunLoop currentRunLoop] run];
}

参考

PHP で USB デバイスを制御したい

PHP の libusb-1.0 バインディングを書いています。
libusb: Modules にある API をそのまんま移植する形で書いています。( Asynchronous device I/O と Polling and timing は手をつけていません。)

USB の勉強がしたくて libusb を用いて C でプログラムを書いていたのですが、思ったより面倒くさかったので PHP で書けたらいいな〜と思ってこの PHP 拡張を書き始めました。
現状は isochronous transfer 以外の転送方法 (control, bulk, interrupt) を実装しています。
いろいろな USB Class で遊んであとにオブジェクト指向API を用意する予定です。

この拡張を用いて USB プリンタで遊んで見ました。

USB プリンタ (Canon iP2700)

f:id:oasynnoum:20130322005600j:plain

試した PHP スクリプト
print2ip2700.php

結果

f:id:oasynnoum:20130321015200j:plain

無残な結果に。。
この PHP スクリプトの印刷データを用意するあたりを見ていただければ分かりますが、僕はプリンタに送るデータをどのように用意すればいいのか分からなかったので、Wireshark でキャプチャしたそれらしいデータを bulk transfer でプリンタに送りつけたらこうなりました。
この辺は USB Printer Class というより、プリンタドライバを読んで勉強するあたりなんだろうと思います。
というわけでこの php-usb はそれなりに動いているようです。

CD-ROM トレイを取り出せる PHP 拡張、「php-eject」作った。

PHP 使ってると良く CD-ROM を取り出したくなりますよね。
ならないとしたら、今すぐこの記事を読むのをやめて病院に行って下さい。
oasynnoum/php-eject GitHub

インストールすると eject_toggle_tray() eject_close_tray() というコマンドが追加されるので、

<?php
eject_toggle_tray('/dev/sr0'); // あるいは eject_toggle_tray('cdrom') など
// Windows 上で E ドライブを Eject するなら、
// eject_toggle_tray('\\\\.\\E:');

と書くだけで CD-ROM トレイがゲロっと排出されます。
便利ですね!
皆さんもぜひ、使ってみて下さい。

Gorilla - Windows 版の開発(メモ)

Gorilla Windows 版の開発やってます。 Windows でのプログラミングは初めてなのでいろいろ手間取ってます。
このエントリは Windows の開発環境を整える際にはまったところや、参考にしたウェブサイトなどを現在進行形でまとめている自身用のメモです。

PHP 本体と拡張の Windows ビルド

神エントリです。ありがたや。
PHP のバージョンはこのエントリで紹介されているものと同じ 5.3.8 を用いていました。その環境下で大体以下のようなことをやると Windows の CRT (C Runtime) Warning が起きていました。

char *device, mode;
int serial_port_fd, flags;
php_stream *stream;

_sopen_s(&serial_port_fd, device, flags, _SH_DENYNO, _S_IREAD|_S_IWRITE);
stream = php_stream_fopen_from_fd(serial_port_fd, mode, NULL);

php_stream_fopen_from_fd 内部で _get_osfhandle(serial_port_fd) がコールされるのですがこの時に CRT Warning が起きていました。 _sopen_s() のオプションがまずいのかとか、 CreateFile() から _open_osfhandle() しなきゃいけないのかとか、いろいろ試してみましたが結局 Warning は取れず、原因もわかりませんでした。なさけない。。
ググってみると、 PHP :: Bug #54563 :: invalid crt parameters on stream_select という似たようなエラーが出るというバグレポートが真っ先に出てきて、

I have the same issue with Windows Server 2008 R2, PHP 5.3.8.

とあったので「これは!」と思い、 old stable の 5.3.18 を用いてみると CRT Warning が消えました。という感じで 5.3.8 固有のバグと決めつけてめでたしめでたし。

いや、めでたくありません。デバッグ力低すぎて情けない。これはもう少し時間とれた時にリベンジしたい。というか、このエラーがでないバージョンとの diff 見るという手もあるんだけど、それは次リベンジした時の答え合わせなのです。

ちなみに configure オプションは PHP 拡張のビルドが目的なので以下の様に必要最小限にしています。

configure --disable-all --enable-cli --enable-json

(json は動作確認する際に必要なのでつけています)

このエントリでは、PHP 拡張のバイナリ dll に埋め込まれる build id の一部、 'compiler id' を変更する方法に言及しています。この build id は PHP 本体のバイナリにも埋め込まれていて、両方の build id が一致していないと PHP 拡張をロードできません。

--disable-zts フラグをつけてコンパイルした NTSPHP 環境下で拡張をビルドしても何故か TS になってしまうという事が起きています(現在進行形です)。この問題は今はほったらかしています。 Windows のバイナリを配布する段になってこの方法を試してみます。(ひょっとしてこれも 5.3.8 だったのがいけないとか?そうだったらうれしいな。)

Windows シリアルポートプログラミング サンプルプログラム

ほんとに助かります。正しく垂涎(^p^)

前者は DCB 構造体の詳細な説明で、後者は DCB 構造体を用いたフロー制御の方法を解説しています。

Windows scripting

プログラミングは Ubuntu で、ビルドは Ubuntu 、 Windows で、とやってます。そのためにスクリプト書きましたが bat 力低すぎて・・・ WinSCP のスクリプトはちょっと便利な気がします。しかし bat で scripting は辛い。 JScript のリファレンス本欲しいなぁ。

termios.c_cc の VMIN VTIME について(メモ)

VMIN VTIME についてメモとしてここに自分なりにまとめてみます。
Gorilla という、シリアル通信のための PHP ライブラリを使うと色々捗ると思うので、ぜひ使ってください。

2012-11-18 追記

再び、ここに記してある4通りの方法試し、いくつか加筆修正しました。
誤っていた記述はあえて消さずに打ち消し線を用いています。

VTIME と VMIN

termios.c_cc[VMIN] と termios.c_cc[VTIME] (以下それぞれ VMIN VTIME) はデータ(character)読み込み時に、タイムアウトを規定するのに用いられる。ただしこのタイムアウトの設定はカノニカルモード、もしくは open(2) の際に O_NDELAY フラグを立てた場合に無視される。

VMIN は最低限読み込むデータサイズ(バイト数)を指す。この値に 0 が指定された場合に、 VTIME の指定する値が1バイトごとのタイムアウトまでの待ち時間となる。VTIME はデータを待ち受ける時間を 1/10 秒単位で指定する。

VTIME と VMIN の組み合わせと読み込みの挙動

VTIME を計るタイマーの挙動とブロックの振る舞いが VTIME と VMIN の組み合わせによって異なる。 以下に組み合わせとそれぞれの振る舞いを示す。

  • VMIN = 0 かつ VTIME = 0
    読み込み可能なデータがあればそれを読み込み、なければ何も読み込まずに呼び出し元に戻る。どちらの場合もブロックはされない。
  • VMIN > 0 かつ VTIME = 0
    データを最低 VMIN だけ受け取るまでブロックし続ける。 VMIN 以上のデータを受け取らないと呼び出し元に制御は戻らない。
  • VMIN > 0 かつ VTIME > 0
    データを VMIN だけ受け取るか、次のデータ読み込みまでに VTIME 以上費やした場合に呼び出し元に戻る。
    VTIME を計るタイマーは1バイト目のデータが来て初めて初期化される。いいかえるなら、1バイト目のデータがいつまでたっても来ないなら、無期限にブロックされ続ける。
    VMIN 未満のデータを受信した場合、1バイト目を受け取った時点でタイマーが初期化され、更にデータが送信されてくるのを待つ。この時 VTIME 内に受信データの合計サイズが VMIN かそれ以上になった時点で read(2) は受信データを返し、制御は呼び出し元に戻る。一方 VTIME 内に受信データの合計サイズが VMIN を満たさなかった場合、 read(2) は少なくともその時点までに受け取ったデータを返し、制御は呼び出し元に戻る。
  • VMIN = 0 かつ VTIME > 0
    1バイトでも読み込み可能なデータがあればそれを読み込み、呼び出し元に戻る。ただし VTIME で指定した時間内は、 read(2) で指定したサイズを読み込もうとし、この間ブロックする。
    読み込み可能なデータがない場合、データが送られてくのを VTIME だけ待ち、その間ブロックする。 VTIME を過ぎてデータがない場合、何も読み込まずに呼び出し元に戻る。
    VTIME 内に1バイト目を受け取り、続くデータがバッファに残っていても read(2) が残りすべてを読み込むとは限らない。

参考

Serial Programming Guide for POSIX Operating Systems
Understanding UNIX termios VMIN and VTIME

Gorilla - シリアル通信のための PHP 拡張ライブラリ

2013-12-26 追記

PHPMake という「 PHP で電子工作とかしようぜ!」というユーザーグループ作ってみました。Gorilla の使い方や、 PHP での工作についての情報共有が目的です。気軽にご参加ください。
PHPMake ホームページ
http://phpmake.info/

イイタイコト

Gorilla というシリアル通信のための PHP 拡張ライブラリを作っています。

経緯&動機

Ruby のお勉強に、と、 ruby-arduino という gem をつかって LED 光らせたりしていました。これがかなり楽しく、結構はまっていました。 ruby-arduinoruby-serialport というシリアル通信のためのモジュールを使っていて、 Windows でも POSIX システムでも動きます。

Ruby であるのであれば、ということでシリアル通信のための PHP ライブラリってあったりするのかと軽く調べました。
PECLDirect IO というパッケージが登録されていて、 dio_tcsetattr() という関数を使えばシリアルポートを制御できます。しかし、この関数は Windows では未実装で、さらに Direct IO 自体がシステムコールAPI をそのまま包んだ様なインタフェースなので、なんかなーという感じがします。
PHPでシリアル通信 - 脱サラ大学生のプログラム日記
この4年前の記事では色々とライブラリが紹介されているのですが、 Windows でしか動かなかったり、あるいは Linux でしか動かなかったり、ソースは非公開だったりします。Google の検索結果 https://www.google.com/search?q=PHP+serial+library を見ると、そのあたりの状況は4年前と大して変わってないんじゃないかと思います。

という風にどのライブラリも惜しいものばかり。
個人的に欲しいのは、

  • シリアル通信プログラミングのための PHP インタフェース
  • Windows でも POSIX システムどちらでも動く
  • ソースコードが公開されている

というものです。
そういうものが見当たらないので作ることにしました。

現状

現在の状況は POSIX システム、というか、 Linux のための実装を一通り終えています。
手元の arduino はちゃんと操作できてるっぽいです。次のスクリプトは一応フツーに動いています。テストはまだ書けていません。。

これから使い方とAPIドキュメント書いて、簡単なサイト作ろうというところです。
それと並行して Windows のための実装を進めます。

arduino やシリアルポートをスクリプティングしたいって人はぜひ Gorilla を使ってみてください!

蛇足

というか「 Ruby でスクリプティングできるんだからおとなしく ruby-serialport と ruby-arduino 使っとけよ」と思わないわけではありません。
しかし、PHPerとしては「何かできないか」と思うわけです。これでPHPerの選択肢が少しでも増えればいいなと思って作っています。このへんは妄想です。すみません。

2012-11-10 追記

どうにか Windows 版の開発できています。
Gorilla - Windows 版の開発(メモ)

2012-11-04 追記

とりあえず Gorilla のサイト作ってみました。説明とかデモとか随時追加してます。
http://sandbox.n-3.so/Gorilla/