ブログ引越しました。
しかじろうが make するよ! http://shikajiro.github.com/
NFC Checkin 端末 プロトタイプを作った。
ADKのサンプルを動かそう
しかだよ。
ADKのバージョンが変わったりしてて正規のサンプルが動かなかったりしますよね。
というわけで、2012/9/30 現時点で動くサンプルをご紹介します。
Android
ソースコード
Y.A.M の 雑記帳: Android Hello ADK つくった!のソースコードをほんの一部だけ修正しています。
MainActivity.java
package com.example.adksample; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import android.app.Activity; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.hardware.usb.UsbAccessory; import android.hardware.usb.UsbManager; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.ParcelFileDescriptor; import android.util.Log; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.TextView; import android.widget.ToggleButton; public class MainActivity extends Activity implements Runnable{ private static final String ACTION_USB_PERMISSION = "com.example.adksample.action.USB_PERMISSION"; private static final String TAG = "ADKSample"; private UsbManager mUsbMng; private PendingIntent mPermissionIntent ; private boolean mPermissionRequestPending; private UsbAccessory mAccessory; /*input output stream */ private ParcelFileDescriptor mFileDescriptor; private FileInputStream mInputStream; private FileOutputStream mOutputStream; /*view*/ private ToggleButton mToggleButton; private TextView mLedStateView; private TextView mStatusView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); /* initialize instance */ mUsbMng = (UsbManager) getSystemService(USB_SERVICE); mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0); /* receiver */ IntentFilter filter = new IntentFilter(); filter.addAction(ACTION_USB_PERMISSION); filter.addAction(UsbManager.ACTION_USB_ACCESSORY_DETACHED); registerReceiver(mUsbReceiver, filter); /* view */ setContentView(R.layout.activity_main); mToggleButton = (ToggleButton) findViewById(R.id.toggleBtn); mLedStateView = (TextView) findViewById(R.id.ledState); mStatusView = (TextView) findViewById(R.id.status); mToggleButton.setOnCheckedChangeListener(new OnCheckedChangeListener() { public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { byte command = 0x1; byte value = (byte) (isChecked ? 0x1 : 0x0); sendCommand(command, value); } }); enableControls(false); } @Override protected void onResume() { super.onResume(); if(mInputStream != null && mOutputStream != null){ return; } UsbAccessory[] accessories = mUsbMng.getAccessoryList(); UsbAccessory accessory = (accessories == null ? null : accessories[0]); if(accessory != null){ if(mUsbMng.hasPermission(accessory)){ openAccessory(accessory); }else{ synchronized (mUsbReceiver) { if(!mPermissionRequestPending){ mUsbMng.requestPermission(accessory, mPermissionIntent); mPermissionRequestPending = true; } } } }else{ Log.d(TAG, "mAccessory is null"); } } @Override protected void onPause() { super.onPause(); closeAccessory(); } @Override protected void onDestroy() { unregisterReceiver(mUsbReceiver); super.onDestroy(); } private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver(){ @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if(ACTION_USB_PERMISSION.equals(action)){ synchronized (this) { UsbAccessory accessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY); if(intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)){ openAccessory(accessory); }else{ Log.d("adksample", "permission denied for accessory "+ accessory); } mPermissionRequestPending = false; } }else if(UsbManager.ACTION_USB_ACCESSORY_DETACHED.equals(action)){ UsbAccessory accessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY); if(accessory!= null && accessory.equals(mAccessory)){ closeAccessory(); } } } }; private void openAccessory(UsbAccessory accessory){ mFileDescriptor = mUsbMng.openAccessory(accessory); if(mFileDescriptor != null){ mAccessory = accessory; FileDescriptor fd = mFileDescriptor.getFileDescriptor(); mInputStream = new FileInputStream(fd); mOutputStream = new FileOutputStream(fd); new Thread(null, this, "DemoKit").start(); Log.d(TAG, "accessory opend"); enableControls(true); }else{ Log.d(TAG, "accessory open fail"); } } private void closeAccessory(){ enableControls(false); try{ if(mFileDescriptor != null){ mFileDescriptor.close(); } }catch(IOException e){ }finally{ mFileDescriptor = null; mAccessory = null; } } private void enableControls(boolean enable){ if(enable){ mStatusView.setText("connected"); }else{ mStatusView.setText("not connected"); } mToggleButton.setEnabled(enable); } private static final int MESSAGE_LED = 1; private class LedMsg{ private byte on; public LedMsg(byte on) { this.on = on; } public boolean isOn(){ if(on == 0x1){ return true; }else{ return false; } } } public void run() { int ret = 0; byte[] buffer = new byte[16384]; int i; while(ret==0){ try{ ret = mInputStream.read(buffer); }catch(IOException e){ break; } i = 0; while(i < ret){ int len = ret - i; switch (buffer[i]) { case 0x1: if(len >= 2){ Message m = Message.obtain(mHandler, MESSAGE_LED); m.obj = new LedMsg(buffer[i+1]); mHandler.sendMessage(m); } i += 2; break; default: Log.d(TAG, "unknown msg: "+buffer[i]); i = len; break; } } } } private Handler mHandler = new Handler(){ public void handleMessage(Message msg) { switch (msg.what) { case MESSAGE_LED: LedMsg o = (LedMsg) msg.obj; handleLedMessage(o); break; default: break; } }; }; private void handleLedMessage(LedMsg l){ if(l.isOn()){ mLedStateView.setText("ON"); }else{ mLedStateView.setText("OFF"); } } public void sendCommand(byte command, byte value){ byte[] buffer = new byte[2]; if(value != 0x1 && value != 0x0) value = 0x0; buffer[0] = command; buffer[1] = value; if(mOutputStream != null){ try{ mOutputStream.write(buffer); }catch(IOException e){ Log.e(TAG, "write failed", e); } } } }
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:gravity="center" android:orientation="vertical" > <TextView android:id="@+id/status" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="10dip" /> <ToggleButton android:id="@+id/toggleBtn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="10dip" /> <TextView android:id="@+id/ledState" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="10dip" /> </LinearLayout>
xml/asccessory_filter.xml
manufacturerが Arduinoの AndroidAccessory の第一引数、modelが第二引数になります。
<?xml version="1.0" encoding="utf-8"?> <resources xmlns:android="http://schemas.android.com/apk/res/android"> <usb-accessory manufacturer="kojika-ya" model="ADKSample" version="1.0" /> </resources>
AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.adksample" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="15" android:targetSdkVersion="15" /> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".MainActivity" android:launchMode="singleInstance" android:label="@string/title_activity_main" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> <intent-filter> <action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" /> </intent-filter> <meta-data android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" android:resource="@xml/asccessory_filter"/> </activity> </application> </manifest>
Arduino
Arduino Labs - Accessory Mode browse
から、ArduinoADK.zipをDLする。
Arduino/libraries/UsbHost を
~/Documents/Arduino/libraries/
にコピーしてUsbHostをArduinoにインストール。
ソースコード
#include <AndroidAccessory.h> # define LED 13 AndroidAccessory acc("kojika-ya", "ADKSample"); void setup() { Serial.begin(115200); Serial.print("\r\nStart"); pinMode(LED, OUTPUT); if (!acc.begin()) { Serial.println("OSCOKIRQ failed to assert"); while (1); //halt } } void loop() { uint8_t msg[64] = {0x00}; byte led; acc.refresh(); if (!acc.isConnected()) { digitalWrite(LED, LOW); return; } uint16_t len = 0; while (acc.available() > 0) { Serial.print("available"); msg[len] = acc.read(); len++; } if (len < 1) { return; } Serial.print("msg[0]" + msg[0]); Serial.print("msg[1]" + msg[1]); if (msg[0] == 0x1) { if (msg[1] == 0x1) { digitalWrite(LED, HIGH); msg[0] = 0x1; msg[1] = 0x1; acc.write(msg, 2); } else { digitalWrite(LED, LOW); msg[0] = 0x1; msg[1] = 0x2; acc.write(msg, 2); } } delay(10); }
node.jsのテンプレートエンジンExpressのroutesの書き方が一瞬わからなかったのでまとめたよ
しかだよ。
Expressのroutes配下の書き方がわからないので調べてみました。
expressのルーティング - hokaccha.hamalog v2
2012-07-14 - ZeBeVogue別館
なんかしっくりこない・・・。
なんとかなるんじゃないかなと思って、色々試してみた。それっぽいまとめ方ができたので報告。
routes配下の作り方
最初のテンプレートはこんなかんじ。
web.js
routes = require("./routes");
routes/index.js
exports.index = function(req, res) { return res.render("index", { title: "Shikajiro dayo" }); };
機能を増やす
index以外にaddとか追加するにはこんな感じでできた。
routes/index.js
exports.index = function(req, res) { return res.render("index", { title: "Shikajiro dayo" }); }; //add処理の追加 exports.add = function(req, res) { };
階層を増やす
index、addだけでは限界がある。exports.user.index, exports.user.addとか、階層を表現できるようにしたい。
routes/index.js
exports.index = function(req, res) { return res.render("index", { title: "Shikajiro dayo" }); }; //【これは動かない】 exports.user.add = function(req, res) { };
だめだった・・・。
userディレクトリを作ってその中にファイルを作る。んで、親階層のindex.jsでそれをrequireする手法をやってみる。
routes/index.js
//ちゃんと動く exports.index = function(req, res) { return res.render("index", { title: "Shikajiro dayo" }); }; //子階層を指定する exports.user = require('./user');
routes/user/index.js
exports.add = function(req, res) { }
呼び出しはこんな感じ。
web.js
var routes = require("./routes"); app.get '/', routes.index app.post '/user/', routes.user.add
routes/index.jsで子階層を指定するのがちょびっと冗長だけど、許せる範囲!
補足
最初間違えた事書いてたので記事消しました。この記事は訂正版になります。
Google SpreadsheetをJSON 取得専用のダミーサーバーとして利用する方法
しかだよ。
webサービスはスピードが正義です。(まさよしじゃないよ。)
1日でも早くサービスをリリースし、1時間でも早くサーバーを構築し、1分でも早くコードを書き、1秒でも速くカチャカチャターンしなくてはいけません。
遅れの原因
そんな中、割りと時間がかかるのが『インターフェース連携』になる部分です。インターフェースの遅れは多岐にわたります。
クライアントとサーバーの疎通もその一つ。
サーバーのapiを待ってたりすると、クライアントの実装が待機状態になり、開発者はニコ動を見始めまてしまいます。
クライアント開発者にはとりあえずJSON返すwebサーバーが必要です。でも、サーバー実装力はないので、可能な限り簡単に、そして素早くテストサーバーが欲しいのです。
シートをテーブルに見立てた
というわけで作りました。Google SpreadSheetに書いたデータをJSONで取得して、あたかもgetを叩いたかのようなテーブルの形に整形するスクリプトです。はてダだとシンタックスハイライトできないのでgistに置いてます。
spreadsheetは一般公開にする必要があるので、ほんとにダミーデータだけにしてね。
gsで書いてる時が僕にもありました。
一生懸命gsで実装してたんですが、クライアント側でやったほうが楽 & 速い事が判明しました。apiの口とかよくわからなかったので・・・。
動いたからよしとします。んじゃ!
ツイッターのタイムラインをゆっくりに喋らせるスクリプト書いた。
ゆっくりだと思った?残念!しかだよ。
ゆっくり実況見ながら寝てるので、ゆっくりボイスを聞かないと眠れない体になってきた30歳♂独身です。ゆっくりかわいいよゆっくり。
SayKanaというライブラリを使うとターミナルからゆっくりボイスを喋らせることができる、と隣の席の「はかせ」から教えてもらったのでやってみた。
SayKana - Mac用音声合成プログラム
SayKanaは、Mac OS X上で動作する日本語音声合成ソフトウェアです。OS Xに付属の say コマンド(英語の音声合成)と同様の機能を実装しています。 AquesTalk音声合成エンジンをMac OS X 上に移植したもので、基本的に『かな』からの音声合成であり、漢字かな交じり文は読み上げられません。 商用でなければ無償でご利用いただけます。
とりあえずヘルプ見る。
$ SayKana -h
NAME
saykana - 日本語用音声合成コマンド(カナからの音声生成)Ver.1.11SYNOPSIS
saykana [-h] [-s speed] [-v f1|m1] [-o out.aiff] [-f file | kana_string]DESCRIPTION
日本語の音声合成を行います。
say コマンドと(ほぼ)同等の機能を実装しています。
そのまま音声出力ほかに、AIFFファイルに出力することもできます。OPTIONS
kana_string
発声するかな文字列で指定します(UTF-8)。漢字は読めません。
アクセントの指定も可能です
詳細は「AquesTalk」の音声記号列仕様を参照してください
スペース等を含む場合は で囲んで指定してください。
-f file
入力をファイルから指定するときに指定します。
kana_stringを指定せず、且つ fileに - を指定したときは、
標準入力から入力します。
-o
AIFFファイルとして出力するときにファイル名を指定します。
指定しないときは音声出力されます。
-v f1 | m1
声種を指定します。 f1:女声(default) m1:男声
-s
発話速度を指定します。 (50-300) default:100
-h
このメッセージを表示します。EXAMPLE
$ saykana こんにちわ
$ saykana -s 150 -v m1 -o out.aiff "ファイルに しゅつ'りょくします。"
$ ls | saykana -f -LICENCE, etc.
ライセンス、その他につきましては、下記サイトを参照してください。
http://www.a-aquest.com/aquestalk/saykana/---- COPYRIGHT 2009 AQUEST Corp. ----
声はゆっくり霊夢とおっさんの声しか出ないのか・・・(´・ω・`)しかたあるまい。
自分のツイッタータイムラインを延々と呟かせる
自分で喋らせても寂しいので、ツイッターをゆっくり呟かせることにしてみた。
shikajiro/yukkuri-voice-tweet
仕組み
- 英語はつぶやけないので無視。アルファベット読みもできるけど長ったらしいので無視!
- 漢字はYahooの漢字ひらがな変換API叩いて漢字->ひらがなに変換してます。
サンプル動画
※動画はちょっとバージョン古いです。
これでいつでもゆっくりとゆっくりできるね。んじゃ。ノシ
追記
ニコニコ動画にうpしました!
Macでの開発効率をアップする、マウントせずにローカルのソースコードをサーバーと同期するスクリプト書いた。
しかだよ。
変更があったローカルのソースコードをサーバーのソースコードと同期しながらwebアプリを開発できるスクリプトを作りました。
shikajiro/eventsync.py
2012/07/10 追記
下で説明するソースコードは古いので、shikajiro/eventsync.pyの方を見てくださいね。
webアプリ開発の問題点
- マウントしたりsshでサーバーに接続してwebアプリを開発してると、回線が遅くて画面が固まることがあり、ストレスフルなコーディングを強いられている。
- ローカルのソースをgitでpushしてサーバーに反映するのは複数人の場合はいいけど、一人用の場合は毎回add commit push がめんどい。
プログラマーが楽しくプログラミングできないのは罪です。直ちに懺悔するか解決しましょう。
開発中のwebアプリを動かす場所を比較検討
実行場所 | 公開範囲 | 反応スピード | PC負荷 | 柔軟性 |
---|---|---|---|---|
ローカルのmac | × | ◯ | △ | × |
vmwareのLinux | × | △ | × | ◯ |
レンタルサーバー | ◯ | × | ◯ | △ |
- ローカルでwebアプリを動かすとレスポンスとか速いんだけど、複数環境などで柔軟性に欠ける。
- vmwareだと柔軟性高いんだけど、メモリとCPU消費が激しくて遅い。
- 上記2つはさらにネットに公開されていないのでテストしにくい。
- レンサバは公開されてて扱い易いけど、直接コーディングするにはレスポンスが遅い。
今この瞬間作っているものをストレス無くサーバーで確認したいのです!
というわけで、「サーバーに常時接続せず」「ローカルで開発できて」「ソースコードに変更があったらサーバーを更新する」方法を探りました。
ソースコードの同期はrsyncで
コードの同期はrsyncでできました。問題は「ソースコードの追加・変更・削除」をどうやって検知するかです。
lsync
Linuxディストリビューションならlsyncを使うとファイルの変更検知ができるみたいですが、Mac OS X はBSDなので、これは使えませんでした。
フォルダアクション
Mac OS X にはフォルダアクションというフォルダ監視の仕組みがあります。フォルダに変更があるとautomatorを実行出来たりします。しかしこれは「フォルダへのファイルの追加」のみ検知可能だったため断念しました。
kevent kqueue
BSDはLinuxとは別の方法でファイルを監視する事が可能でした。それが kevent kqueueです。
*BSD で kqueue・kevent を使ってみよう
pythonスクリプト
んで、色んなサイトのソースコードを参考にしてできました。
shikajiro/eventsync.py
import select import os import sys def kevent(file): ke = select.kevent(file, filter=select.KQ_FILTER_VNODE, flags=select.KQ_EV_ADD | select.KQ_EV_ENABLE | select.KQ_EV_CLEAR, fflags=select.KQ_NOTE_DELETE | select.KQ_NOTE_WRITE ) return ke argvs = sys.argv argc = len(argvs) if(argc != 3): print 'Usage: python {} filename.'.format(argvs[0]) quit() folder = argvs[1] ssh = argvs[2] print '{} to {}'.format(folder, ssh) print 'file update watching...' dirs = os.walk(folder) l = [] for root, dirs, files in dirs: f = os.open(root, os.O_RDONLY) l.append(kevent(f)) for a in files: f = os.open(root + '/' + a, os.O_RDONLY) l.append(kevent(f)) kq = select.kqueue() events = kq.control(l, 0, None) while True: r_events = kq.control(l, 1, None) for event in r_events: print event if event.fflags & select.KQ_NOTE_DELETE or event.fflags & select.KQ_NOTE_WRITE: print "file was updated!" command = 'rsync -av --delete -e ssh {} {}'.format(os.path.join(os.getcwd(), folder), ssh) print command os.system(command)
python eventsync.py watchdir/ shikajiro@hostname:/dir/
第一引数に監視するディレクトリ名、第二引数に同期するsshサーバーとそのフォルダを指定します。
起動するとこんな感じ。
hogehoge/ to root@www.kojika-ya.me:/root/hogehoge/
file update watching...
ファイルに変更などがあると
building file list ... done
deleting urls.pyc
deleting settings.pyc
deleting middleware.pyc
deleting __init__.pyc
deleting api/urls.pyc
deleting api/handlers.pyc
deleting api/__init__.pyc
deleting app/models.pyc
deleting app/admin.pyc
deleting app/__init__.pyc
./
sqlite
test
api/
app/sent 7501 bytes received 1876 bytes 6251.33 bytes/sec
total size is 3252109 speedup is 346.82
.....
こんな感じでどんどん同期されていきます。
これできっとストレスフリーな開発ができる(はず)です。