テストとかでやりたくなると思うけど、こんなくだらないことに gem とか使いたくないので、簡単に書ける方法をさがしてる。

class Foo
	def hello
		puts "Hello"
	end
end

みたいなクラスがあったとして、hello メソッドを一時的に上書きしたい (そして戻したい) とき

あるオブジェクトのメソッドだけを上書き

これは singleton class を使ったらすぐできるのでかんたん

foo = Foo.new

orig = foo.method(:hello) # 元のメソッドを呼びたいときだけ必要
foo.define_singleton_method(:hello) do
	puts "before hello"
	orig.call
	puts "after hello"
end

foo.hello #=>
# before hello
# Hello
# after hello

# 元に戻す
foo.singleton_class.send(:remove_method, :hello)

foo.hello #=>
# Hello

この方法は define_singleton_method だけでほぼ終わるし、元に戻すのも元のメソッドを保存しておく必要がないので (継承ツリーで上書きするので)、簡単

class << foo; alias ....; end

みたいなやりかたもあるけど alias でかぶったら嫌だし、alias の引数の順番覚えられないし好みではない

グローバルにあるクラスに属するメソッドを上書き

これはちょっと面倒な感じがする

# klass に定義された method メソッドをブロックで上書きする。
# ブロックには第1引数にオリジナルのメソッドが渡される
# 返り値は元に戻すための Proc インスタンス
def localize_method(klass, method, &block)
	unbound = klass.instance_method(method)
	klass.send(:define_method, method) do |*args|
		block.call(unbound.bind(self), *args)
	end
	lambda {
		klass.send(:define_method, method, unbound)
	}
end

みたいなのを定義して

foo = Foo.new
bar = Foo.new

puts "===="
foo.hello
puts "----"
bar.hello
# ====
# Hello
# ----
# Hello

release = localize_method(Foo, :hello) do |orig|
	puts "before hello"
	orig.call
	puts "after hello"
end

puts "===="
foo.hello
puts "----"
bar.hello
# ====
# before hello
# Hello
# after hello
# ----
# before hello
# Hello
# after hello

# 元に戻す
release.call

puts "===="
foo.hello
puts "----"
bar.hello
# ====
# Hello
# ----
# Hello

みたいに使えばいいのかな。もっと簡単にできたらいいけどできなそう…

  1. トップ
  2. tech
  3. Ruby でメソッドの一時的な上書き

見るたびにいちいちコードを読まないと安心して使えないライブラリなんてライブラリの価値がない。ライブラリ自体はブラックボックスになっていても、インターフェイスからその挙動を想像できるべきだ。

ライブラリを使っている側のソースを読む場合ではなく、あるライブラリを使おうとするときは、一度はライブラリの読んだほうがいいとは思うが、その目的は、そのライブラリが信頼できるかを計るためで、使いかたや、細かい挙動を確認するためではない。そして「どの程度信頼できるか」ということ、すなわちイケてるかイケてないかだけを覚えていればよい。

あるライブラリを使ったコードを読むとき、そのライブラリのソースまで毎回を読んで挙動を調べたりしなくてはいけないライブラリはインターフェイスがイケてない。ライブラリのインターフェイスが説明的になっていない。適切なインターフェイス設計がされていない。

適切なインターフェイス設計がされていなくても便利な「ライブラリ」は存在するけど、それはようするに便利なコピペ関数の寄せ集めである。あくまで書くとき便利なだけであって、読むときは害になる。

書くときに便利になるだけのものにはあまり意味がない。簡潔に書けることの目的はタイプ数を減らすことではなく、読むときに理解しやすくするためだ。

  1. トップ
  2. tech

ちょっとググってもいい感じのドキュメントが見つからず難儀したので、Perl でやるならこれで!! って感じで、コピペして使えるようなサンプルコードを書きました

社内でちょっと喋る必要があったので資料もあります (公開しても良さそうなように資料をつくってあります)

  1. トップ
  2. tech
  3. Perl で Thrift を扱うサンプルプログラム

なんかうまく AVR だと動かなかったりしたので、カっとなってテストを充実させた。(動くようになった)

とはいえ、コード上でテストだけ書いてもよくわからないので、可視化させながらやった。つまり時系列にバスの動きをシミュレーションできるようなコードを書いた。Ruby で cairo が結構かんたんに使えたので良かった。Time.now によってグラフを書いているので、多少ゆらぎがある。しかしこうすることで、意図せず状態を変えてしまっているのが一目でわかるようになったので大変役にたった。

例えば、i2cset のテストの場合

i2cget のテストの場合

見ての通り get のほうが複雑、というのも get するためにどのアドレスを get するかを書かないといけないから……

オシロスコープでの実際の波形

テストと同じデータの送受信 (対 AVR)。波形を綺麗にするために速度を 1kHz 程度に制限して (周辺処理に時間がかかるので最終的に400Hz程度だけど) sleep をちゃんと入れるようにしている。速度は下がるけど波形は綺麗になる。時間の単位さえ気にしなければ I2C 動いてる感がある。

i2cset

i2cget

AVR での repeated start condition in slave

AVR 向けだと repeated start condition を発行したとき AVR がハングする問題があって、解決方法がわからないので stop condition を常に挟むように実装を変えた。MPL115A2 だと普通に動くんだけど……

  1. トップ
  2. tech
  3. 続・GPIO (sysfs) を使ったソフトウェア I2C

普通の GPIO 経由で I2C できたらなんとなく嬉しいかなと思って作ってみた。つまりソフトウェアでピン状態をいじって I2C 通信するというもので、いわゆる bit banging というやつです。

[tech] Ruby の I2C ライブラリ | Sun, Feb 16. 2014 - 氾濫原 で I2C ライブラリを書いたけど、これは i2c-dev という Linux 組込みのドライバを使ったもので、ハードウェアが持ってる I2C の機能を使うので、もしハードウェアに I2C がない場合 (普通あるけど) で、GPIO はあるという場合に使えることがあるかもしれません。

使いかた

最終的には以下のような感じで使えるようになった。もちろん Raspberry Pi には I2C ついてるのでこんなことする必要は全くない。

require "i2c/device/mpl115a2"
require "i2c/driver/gpio"

mpl = MPL115A2.new(address: 0x60, driver: I2CDevice::Driver::GPIO.new(
    sda: 23, # pin 16 in raspberry pi
    scl: 24, # pin 18 in raspberry pi
))

p mpl.calculate_hPa

実装

コードは以下の通りだけど、けっこう時間がかかった割にはコードサイズはそうでもない。

普通に仕様通りに実装するだけだけど、いくつか問題があった

  • sysfs 経由のピン状態書きかえが非常に遅い
    • C + sysfs だと5KHz 程度 (Raspberry Pi 上)
    • Ruby + sysfs だと 1.2kHz 程度 (Raspberry Pi 上)
  • GPIO export 直後に EACCESS (パーミッションエラー) がでる
  • GPIO direction 直後での初期状態
遅い

I2C は Standard-mode でも 100kbit/s を規定してるので、全く届いていない。ただ、I2C はマスターが常に生成するクロックラインを持っていて、クロックが High のときだけデータが有効という感じで、大抵のデバイスはデータシート上では対応クロックを 0 〜 100kHz とか 0 〜 400kHz とかで書いてあるので、動くものは動くはず…… MPL115A2 というデバイスでは動いてくれた。

もちろんマスターとしてしか動かない。また、調停とかも実装してない。

ループ展開とかいろいろやったけど、多少改善はするものの、上記の通りのスピードは超えることができないので、諦めて可読性優先で富豪的に書いてある。

CPU 特化のネイティブなライブラリを使えば当然早くはなるんだけど、今回 sysfs 経由で抽象化された gpio でなければやる意味がないなと思ってやってない。

EACCESS

GPIO export 直後の EACCESS はよくわからなくて、pi を gpio グループに入れてやってるせいかもしれない。ただ、時間が経てばアクセスできるようになるので (たぶん sysfs がバグってる感じがするって人のせい的に書いといたら、カっとなって誰か調べてくれそう)、EACCESS を rescue して retry かけている。

direction 直後の状態

direction を単純に "out" に設定すると、たぶん active_low に従って初期状態が設定されると思うんだけど、うっかり SCL ラインを High にしてしまうとその後困ったことになるので、アトミックに direction 切り替え + ピンの High 又は Low 設定を行う必要がある。

しばらくわからなかったんだけど、普通に direction に対して "high" や "low" を書きこむと、初期状態が指定した状態の "out" になるみたいだった。

  1. トップ
  2. tech
  3. GPIO (sysfs) を使ったソフトウェア I2C

Locale::Maketext::Extract::Plugin 以下にはいろいろ対応してるフォーマットがあったりする。まぁ大抵一緒なので頑張って使う必要もないけど、これらを使って .po ファイルを作らないまでも、msgid の抽出だけ行いたいという場合があったりします。そんなときは直接 LME インスタンスを作って extract_file をかけて compile すれば、とれるようになるみたいです。

use Locale::Maketext::Extract;
use Locale::Maketext::Extract::Plugin::Xslate;
use File::Zglob;

my $lme = Locale::Maketext::Extract->new(
    plugins => {
        perl => [ 'pm' ],
        xslate  => {
            syntax     => 'TTerse',
            extensions => [qw/ tt /],
        },
        generic => [ 'js' ]
    },
    warnings => 1,
    verbose => 0,
);
 
 
for (zglob('lib/**/*.pm'), zglob('template/**/*.tt'), zglob('static/**/*.js')) {
    $lme->extract_file($_);
}

$lme->compile(1); # これをしないと msgids がとれない

for my $msgid ($lme->msgids) {
    say $msgid;
    $lme->msg_positions($msgid); # 見つかった場所がとれる
}
  1. トップ
  2. tech
  3. Locale::Maketext::Extract でスキャンだけする。
  1. トップ
  2. perl
  3. Locale::Maketext::Extract でスキャンだけする。