0

Raspberry PiにrbenvでRuby2.0.0インストールした

aptのrbenvが0.3.0と古かったので

Ubuntu12.04にrbenvインストールして、crontabやdaemontoolsも設定した

と同じ手順で0.4.0をgitから入れた。
けどビルドに2時間以上かかったので、aptで1.9.3-p194を入れればいいと思う。


crontabや、foremanで書きだしたupstartも

/home/pi/.rbenv/shims/bundle exec ruby hogehoge.rb
で起動したら問題なく動いた。

0

Lindaコマンド作った

nekobatoがpythonからlindaを使いたいと言っていたので、JSONを標準入力するとwriteされて、read/take/watchするとJSONを標準出力するツールを作った。
Ruby以外の言語でもLindaが使えるようになった。


インストール

sinatra-rocketio-lindaのgemに付属している。

% gem install sinatra-rocketio-linda
% linda-rocketio --help


使い方(基本)


write
% linda-rocketio write -tuple '["say","hello"]' -base http://linda.shokai.org -space test

改行区切りで複数write
% echo '["say","hello"]\n["say","world"]' | linda-rocketio write -base http://linda.shokai.org -space test


read
% linda-rocketio read -tuple '["sensor","light"]' -base http://linda.shokai.org -space test



使い方(応用)



JSONを標準出力するプログラムを作る。pythonで書くべきかと思ったけど面倒くさいからRubyで書いた。

arduino_sensor.rb
#!/usr/bin/env ruby
require 'rubygems'
require 'arduino_firmata'
require 'json'
$stdout.sync = true

arduino = ArduinoFirmata.connect

loop do
v = arduino.analog_read 0
puts ["arduino", "sensor", v].to_json
sleep 1
end
Rubyだと$stdout.sync = trueを設定しておかないとpipeがうまく動かない。


["arduino","sensor",数値]
が毎秒出力されるので、pipeで接続する

% ruby arduino_sensor.rb | linda-rocketio write -base http://linda.shokai.org -space test -verbose
[“arduino”,”sensor”,数値]がTupleSpaceに書き込まれる。json以外の行は無視されてwriteされない。


データが流れているのが http://linda.shokai.org/test/arduino/sensor で確認できる


JSONを読むプログラムも書いてみる。

pipe-say.rb
!/usr/bin/env ruby
require 'rubygems'
require 'json'
$stdout.sync = true

while line = $stdin.gets do
puts line.strip!
data = JSON.parse line rescue next
v = data[2].to_i
system "say #{v}" ## Macの音声読上げコマンド
end

JSONが出てくるのでpipeで渡して読ませる
% linda-rocketio watch -tuple '["arduino","sensor"]' -base http://linda.shokai.org -space test -verbose | ruby pipe-say.rb


その他の例

arduino_firmataコマンドを使う例。
% echo "[\"sensor\", \"arduino\",`arduino_firmata analog_read 0`]" | linda-rocketio write --base http://linda.shokai.org --space test


twitterのuserstreamをlindaに書き込む
twにはjson出力があるので
% tw --stream --format=json | linda-rocketio write --base http://linda.shokai.org --space test
これだけでuserstreamが共有できたりする。

0

RocketIOのチャットサンプルを全力で改造して画像チャットにした

ここにある
http://chat.shokai.org

herokuでも動いている
http://rocketio-chat.herokuapp.com

ソースコード
shokai/rocketio-tiqav-chat · GitHub


このように1文字入力するごとにTiqav.comで画像検索する。
レス画像検索No.1/画像会話なら ちくわぶ

画像をマウスクリックすれば画像が投稿される。
enterキーを押せばそのままテキストが投稿される。


http://chat.shokai.org/shokai
http://chat.shokai.org/test
のように部屋も無限に作れる。


作った経緯

RubyHirobaでLTして気づいたのだが、もともとchatはRocketIOの最も簡単なサンプルとして作っていた。
サンプルなので、難しい事するとわからなくなるからシンプルに留めていた。

でももっと単純なhello worldサンプルができたので、チャットは手加減する必要なくなった。
shokai/rocketio-hello-world · GitHub
http://hello.shokai.org

むしろ、「それなりに込み入った操作をするUIと通信の連携方法」や「RocketIOのチャンネル機能」の例が必要かと思ったので、
それなりに複雑なアプリを作った。


実装

チャットログはサーバーのオンメモリに保存している。

画像検索は、HTML上のinput要素を監視して変化があれば都度RocketIOでサーバーに文字列を送る。
サーバー(Sinatra)がTiqavに検索をリクエストする。(tiqav gemを使っている)
[ブラウザ]<--(RocketIO)-->[Sinatra]<--(HTTP)-->[Tiqav.com]


同じ文字列を何度も検索するのは無駄なので、ブラウザ上とサーバー上でそれぞれJSのオブジェクトとmemcachedでキャッシュを行なっている。
[ブラウザ/JS Cacheオブジェクト]<--(RocketIO)-->[Sinatra/memcached]<--(HTTP)-->[Tiqav.com]


Tiqav.comにはあまりリクエストが飛ばないようになるので、かなりリアルタイム気味にチャットと画像検索できる。


サーバーで検索する時はEM::defer内でリクエストを飛ばす。
以前HerokuのSinatraにバックグラウンドワーカーを詰め込んで節約で説明したが、Sinatraはシングルスレッドだけど、IO待ちする処理をEM::deferで包むと他のクライアントを待たせずにレスポンスを返せるようになる。


EventEmitterの活用

RocketIOに付属しているEventEmitterを使うと、複雑なUIと通信をうまく連携させられる(ブラウザ用EventEmitterを作った
EventEmitterはあらゆるクラスやインスタンスにイベント機能をmixinできるライブラリです


画像chatのjavascriptは超シンプル。
input要素が変更されたら画像検索して結果をHTMLに表示している。
var io = new RocketIO().connect();
var img_search = new ImageSearch(io);

$(function(){
var chat_input = new InputWatcher("#message");

chat_input.on("change", function(val){
img_search.search(val);
});
});

img_search.on("result", function(res){
$("#img_select").html(""); // clear DOM
for(var i = 0; i < res.imgs.length; i++){
(function(){
var img_url = res.imgs[i];
var img_tag = $("<img>").attr("src", img_url);
$("#img_select").append( $("<li>").html(img_tag) ); // display each Images
})();
}
});


まずInputWatcherというinputタグの中身を監視して”change”イベントを発行するクラスを作る。
中身が変わった時だけchangeイベントが発行される。
// watch text-input and emit event on "change"
var InputWatcher = function(target){
var self = this;
new EventEmitter().apply(this);
this.target = (target instanceof jQuery) ? target : $(target);
var last_val = null;
var watch = function(){
var val = self.target.val();
if(!!last_val && last_val !== val) self.emit("change", val);
last_val = val;
};
setInterval(watch, 100);
this.target.keyup(watch);
};


changeイベントから画像検索する
$(function(){
var chat_input = new InputWatcher("#message");

chat_input.on("change", function(val){ // regist "change" event
img_search.search(val);
});
});


画像検索をRocketIOでリクエストして、結果が返ってきたら”result”イベントを発行するImageSearchクラスを作る。
内部にcacheを持っていて、一度検索したことのある文字列はサーバーに送らずに即”result”イベントを発行する。
コンストラクタにRocketIOインスタンスを渡して、通信にはそれを使うようにする。
// request image-search to server with RocketIO
// emit "result" event on receive image-url-array
var ImageSearch = function(io){
var self = this;
if(!(io instanceof RocketIO)) throw new Error("Argument must be instance of RocketIO");
var cache = {};
new EventEmitter().apply(this);
io.on("img_search", function(data){
if(!data || typeof data.word !== "string" || !(data.imgs instanceof Array)) return;
cache[data.word] = data.imgs;
self.emit("result", data); // "result" event
});
var eid = null;
var last_word = null;
this.search = function(word){
if(!!eid) clearTimeout(eid);
if(typeof word !== "string") return;
if(word.length < 1){
self.emit("result", {imgs: [], word: ""}); // "result" event with Empty images
return;
}
// check Cache
if(cache[word] instanceof Array && cache[word].length > 0){
self.emit("result", {imgs: cache[word], word: ""}); // "result" event with Cached images
return;
}
// if NOT Cached
eid = setTimeout(function(){
eid = null;
cache[word] = [];
io.push("img_search", word); // request to server
}, 300);
};
};


最終形。
さっきのInputWatcherと合わせて使うとこうなる。
var io = new RocketIO().connect();
var img_search = new ImageSearch(io);

$(function(){
var chat_input = new InputWatcher("#message");

chat_input.on("change", function(val){
img_search.search(val);
});
});

img_search.on("result", function(res){
$("#img_select").html(""); // clear DOM
for(var i = 0; i < res.imgs.length; i++){
(function(){
var img_url = res.imgs[i];
var img_tag = $("<img>").attr("src", img_url);
$("#img_select").append( $("<li>").html(img_tag) ); // display each Images
})();
}
});

0

Rapsberry PiでIPアドレスをDMする(pipeとcronで)

羽田先生がやってたやつ
Rapsberry Pi でIPアドレスをDMする | HADA HISAKAZU Personal Information.


twとpipeとcrontabでやってみる


インストール

% sudo gem install tw
% tw --user:add
user:addで @shokai_log としてログインする。


下準備として、 Raspberry Piで使うアカウントにmentionを飛ばす。


さてIPアドレスをRaspberry Piから自分宛にDMする。
これを実行すればいい。自分宛の最新のmentionが @shokai からで、本文に「IP」という文字列が含まれている時に、自分のIPアドレスをDMで送り返す。
tw --user=shokai_log --format="#{user} #{text}" | tail -1 | grep -i "^shokai .*ip.*" && echo "`/sbin/ifconfig -a | grep "inet" | grep -v 127` `date`" | tw --pipe --user=shokai_log --dm:to=shokai


cronで5分ごとぐらいに実行すればいいと思う。
crontab -e
*/5 * * * *  tw --user=shokai_log --format="#{user} #{text}" | tail -1 | grep -i "^shokai .*ip.*" && echo "`/sbin/ifconfig -a | grep "inet" | grep -v 127` `date`" | tw --pipe --user=shokai_log --dm:to=shokai


実行結果


DMがくる


何か別のこと(IPという文字列を含まないtweet)をmentionしない限り、何回もDMを飛ばしてくるので適当に止める。

0

gyazzにアイコングラフ機能を付けた

gyazz.comは増井先生が作っているwikiです。
誰でも http://gyazz.com/Wikiの名前/ページ名 にアクセスすればすぐにwikiが作れるので
http://gyazz.com/自分の名前/ などを作ってみればいいと思います。
編集は行をマウスで長押し、Enterキーで新しい行追加、shift+上下キーで行の入れ替えができる。

———–

発端は8ヶ月前に適当に書いて放置してたissueにhentekoさんがpull requestしてくれた事だった。ありがとうございます。
twitterユーザー名でのリンクをアイコン表示にする · Issue #3 · masui/Gyazz


やってみると面白かったので、twitterアイコンだけでなくページのタイトル画像も敷き詰めれるようにした。
(gyazzはページ最上部の画像がタイトルに設定される)

ついでに小数に対応したり。twitterアイコンはtwiticonで埋め込んでる。


[[(ページ名|画像URL|twitter名).(icon|png)x(数字)]] でアイコンがたくさん表示される。


この部分は

編集状態にするとこうなっている。[[shokai]]というページのタイトル画像を5.6個並べている。


こういう使い方もある


投票などにも使えて、みやすい。
投票数の集計などもできると思う。バカっぽい機能だと思ってたけどやってみると便利だった。