[[FrontPage]] 2008/09/25からのアクセス回数 &counter; #contents SunSPOTを使った用途を考えていたときに、MITの石井さんが提唱されている Tangibleが浮かんだ。認識範囲の短いICTagは、ある地点の道しるべになり、値の読み取り 書き込みができるのでおもしろいことができると思った。 Tagibleの本家の例 http://tangible.media.mit.edu/projects/sensetable/ ** ICTagの選定 [#l3e06cd5] 小型で電子工作の容易なICTagリーダライターとして、大信機器のHF-04SRを使うことにしました。 http://www.daishin-kiki.com/products.html 価格も1万円程度で、3.3VでRS-232C/TTLレベルで通信できるところも魅力です。 最近はもっと小さなリーダも見つけた(http://www.neotechkno.co.jp/nt7c/index.html) HF-04SRの対象とするICTagは、ISO15693とMifare規格に対応しており、このなかでI Code SLIの タグを使うことにしました。 *** ICTagの購入が問題 [#g1b708e0] ICTagの広告でトップにでてくるリンテック株式会社に問い合わせたところ、ICTagの販売は 最低でも100枚単位でないと出荷できないとのことでした。 http://www.lintec.co.jp/e-dept/britem/ictag/products/tsdc_ts.html 営業の方のご厚意でサンプルタグを送ってもらい、HF-04SRでの読み取り確認できました。 ** ICTagリーダライターの動作確認 [#db76db46] いきなりSunSPOTと接続するのは大変なので、シリアル接続でターミナルから操作しました。 *** 秋月のUSB-シリアル変換モジュール [#qb97c876] TTLレベルのRS-232C通信をテストする場合、レベルコンバータとUSB-シリアル変換モジュールが 必要ですが、秋月の[[AE-UM232R>http://akizukidenshi.com/catalog/items2.php?q=AE-UM232]]は、USBから電源(5V, 3.3V)を供給し、買ってすぐにブレッドボード に接続できるすぐれもの(これで950円はやすい!)。 #ref(5.jpg); *** MacOSXでシリアル接続 [#gd689039] 私の使っているMacOSXからシリアル接続できるデバイスは少ないのですが、 - PL-2303 - FT232BM は、MacOSX用のドライバーを公開しているそうです(http://www.zone0.ne.jp/2006/osxserial01.html参照)。 *** ドライバーのダウンロード [#k94e243b] FT232BM用のドライバーを[[Future Technology Devices International Ltd.>http://www.ftdichip.com/Drivers/VCP.htm]] からFTDIUSBSerialDriver_v2_2_10.dmgをダウンロードしました。 Tiger以降でないとだめみたいですから注意してください。 *** ターミナルソフト [#h3ebe6a3] MacOSXで使えるターミナルソフトは、Jerminalを使用することにしました。 先ほどの参照ページのJerminal8095.dmgをダウンロードしました。 *** 接続確認 [#m353b7ee] ターミナルからJerminal(jermコマンド)を以下のオプションで起動します。 #pre{{ $ jerm -b 38400 -p none -d 8 -s 1 /dev/cu.usbserial-A5002yHm }} - ボーレート 38400 - パリティなし - データ長 8bit - ストップビット 1 最後の/dev/cu.cusbserial-xxxxxxは、機種によって変わります。 接続が完了すると以下のような表示でます。 #pre{{ Jerminal v0.8095 Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005 candy Type "Ctrl-M ~ ." to exit. ispeed 38400 ospeed 38400 +IGNBRK -BRKINT -IGNPAR -PARMRK -INPCK -ISTRIP -INLCR -IGNCR -ICRNL -IXON -IXOFF -IXANY -IMAXBEL -OPOST -ONLCR -OXTABS -ONOEOT cs8 -CSTOPB +CREAD -PARENB -PARODD +HUPCL +CLOCAL -CCTS_OFLOW -CRTSCTS -CRTS_IFLOW -MDMBUF -ECHOKE -ECHOE -ECHO -ECHONL -ECHOPRT -ECHOCTL -ISIG -ICANON -ALTWERASE -IEXTEN -EXTPROC -TOSTOP -FLUSHO -NOKERNINFO -PENDIN -NOFLSH }} ICTagリーダライターとの接続を確認するために、ICTagリーダライターのバージョン表示コマンド(V [CR]) を入力すると、 #pre{{ 15693 V03.07 }} めでたく、ICTag 15693に対応した V03.07であることが表示されました(めでたしめでたし)。 ** JavaからICTagを制御する [#c63e1404] ここでのメインテーマは、ハードをjavaで制御することですから、これからが本題です。 *** Javaでシリアル通信をする [#b6c87793] Javaからシリアルポート、パラレルポートを使用するためのAIPがSunの提供する [[Java Communications API>http://java.sun.com/products/javacomm/]] です。 残念ながらSunの提供するAPIは、SPARC Solaris/x86 Solaris/x86 Linux版のみなのでMacOSXや Windowsでは使えません。 そこで、Java Communications APIに準拠したオープンソースのライブラリRXTXを使用します。 - [[RXTX : serial and parallel I/O libraries supporting Sun’s CommAPI >http://www.rxtx.org/]] RXTXのサイトからrxtx-2.1-7r2.zipをダウンロードし、解凍します。 -MACOSX_IDE/ForPackageMaker/install/Library/Extensionsから以下のファイルを取り出します。 -- librxtxSerial.jnlib -- RXTXcomm.jar また、RXTXの例題としてprocessing/Gainerのライブラリを使いました。 - http://gainer.cc/Download/Download からProcessingをダウンロードします。 - gainer_lib_processing_v1-1-0rc4のlibraries/gainer/libraryから -- gainer.jar -- librxtxSerial.so -- rxtxSerial.dll を取り出します。 RXTXシリアル接続については、 -[[ torutkの日記>http://d.hatena.ne.jp/torutk/20071224/p1]] を参考にさせていただきました。 *** 使用するICTag制御コマンド [#oec7ed1e] javaで使用するICTagコマンドは - バージョン情報 : V - スキャンコマンド: 2XX - 連続スキャン開始コマンド: 2XS - 読出コマンド: 2R - 複数ブロック書込コマンド: 2WM としました。 各コマンドの書式では - UID: タグのUID - tt: タグ番号 - bb: ブロック番号 - nn: 数値 - DATA: データ |コマンド|書式|戻り値成功時|戻り値失敗時|制約| |バージョン情報|V[CR]|使用できるタグの種類 バージョン|なし|| |スキャン|2XX[CR]|nn,UID{,UID,UID....}[CR]|00[CR]|複数タグ検知は最大8枚| |連続スキャン|2XS[CR]|01,UID[CR]|中断時None[CR]|1文字を送信すると中断する| |読出|2R,tt,bb,nn[CR]|tt,yy,DATA{,tt,yy,DATA...}[CR]|00[CR]|最大15ブロック| |複数ブロック書込|2WM,tt,bb,nn,DATA[CR]|tt,OK[CR]|tt,NG[CR]|最大15ブロック| *** ICTag制御クラスHF04SL [#zed9ad11] ICTag制御クラスHF04SLに各コマンドの処理を実装します。 最初にICTagのオープンですが、バージョン情報を使って接続先がICTagかどうかをチェックします。今回はISO-15693のICTagを対象としているので、バージョン情報の15693の文字列をキーワードとしました。 #pre{{ public boolean openICTag(String pname){ if(openSerialPort(pname)){ String returnCode=""; while(returnCode.indexOf("15693") < 0){ try{ write("V\r"); returnCode = readWithTimer(1000); }catch(TimeoutException e){ }catch(IOException e){ } } return true; } return false; } }} ICTagの読み込みを逐次チェックするよりも連続スキャンを使ってICTagの検出を 待つようにした方が、処理が簡単になります。 連続スキャンの処理は以下のようになります。 #pre{{ public String contScanICTag() throws IOException { write("2XS\r"); String res = read(3); if (res.equals("01,")) { String id = read(16); read(1); // skip [CR] return (id); } else { read(2); // skip remain None[CR] return (null); } } }} 連続スキャンのキャンセルはきわめて簡単です。 #pre{{ public void cancelContScanICTag() throws IOException { write("a"); } }} 書込もコマンドの仕様通りです。 #pre{{ public boolean writeICTag(int tagNo, int blokNo, byte[] data) throws IOException { int numBlock = (data.length + 3) / 4; StringBuffer buf = new StringBuffer(); buf.append("2WM,"); buf.append(String.format("%02x,", tagNo)); buf.append(String.format("%02x,", blokNo)); buf.append(String.format("%02x,", numBlock)); for (int i = 0; i < data.length; i++) buf.append(String.format("%02x", data[i])); buf.append("\r"); write(buf.toString()); read(3); // skip tt, String result = read(2); read(1); // skip [CR] return (result.equals("OK")); } }} 残りの読込は、ちょっと長くなりましたが、ほとんど仕様どおりです。 #pre{{ public String[] readICTag(int tagNo) throws IOException { StringBuffer buf = new StringBuffer(); buf.append("2R,"); buf.append(String.format("%02d,00,0F\r", tagNo)); write(buf.toString()); String numStr = read(2); if (numStr == "00") { return (null); } else { List<String> list = new ArrayList<String>(); int numTag = Integer.parseInt(numStr); int count = 1; while (available() >= 2) { if (count++ != 1) { numStr = read(2); } read(1); // skip ',' String lenStr = read(2); read(1); // skip ',' int len = Integer.parseInt(lenStr, 16); String data = read(len*2); read(1); // skip ',' or [CR] list.add(data); } return (list.toArray(new String[0])); } } }} 必要な部品がそろったので、テストをします。 #pre{{ public static void main(String[] args) { HF04SL ictag = new HF04SL(); try { ictag.openICTag("/dev/cu.usbserial-A5002yHm"); System.out.println(ictag.contScanICTag()); // Icode SLIは、0ブロックに書き込めない System.out.println(ictag.writeICTag(1, 1, "0123456789ABCDEF".getBytes())); String[] tagData = ictag.readICTag(01); System.out.println(tagData[0]); System.out.println(ictag.hexToString(tagData[0], 4, 16)); } catch (Exception e) { e.printStackTrace(); } finally { ictag.closeSerialPort(); } } }} 実行結果は、 #pre{{ Stable Library ========================================= Native lib Version = RXTX-2.1-7 Java lib Version = RXTX-2.1-7 RXTX Warning: Removing stale lock file. /var/lock/LK.004.011.031 1A8FA410000104E0 true 000000003031323334353637383941424344454630303030000000000000000000000000 740069006F006E0020002000000000000000000000000000 0123456789ABCDEF closing /dev/cu.usbserial-A5002yHm Experimental: JNI_OnLoad called. }} 正常に動作しました。 ** サンプルアプリケーション [#g36e4259] ICTagのサンプルアプリケーションとして、本にICTagを付け、ISBN番号からAmazonの検索を使って - 書籍のタイトル - 著者 - 出版社 - 総ページ数 - 出版日 - 価格 を表示する、ダイアログを作成します。 #ref(BookManager.jpg); + ICTagがリーダにかざされるとダイアログを表示します。 + ISBN番号を入力しISBN Searchボタンを押すと、 アマゾンから書籍の情報を取得し、表示します。 + OKボタンで次のICTagの読込まちになります。 *** Visual Editorを使ってBookDialogを作成する [#gf0ac42e] BookDialogは、EcdlipseのプラグインVisual Editorを使って作成しました。 #ref(VisualEditor.jpg); *** ISBN Searchボタンの処理(AWS検索の処理) [#dba0741e] アマゾンの検索は、AWSを使いました。 ここで、検索結果の読込に失敗して情報をうまく取得できません。ネット調べたところ、 - [[ひびどく>http://yhd.cocolog-nifty.com/hibidoku/2005/11/index.html]] にnamespaceを指定する必要があるとのコメントを見つけ、registerNamespaceメソッドでawsを登録 するとうまく読み取ることができました。 #pre{{ private void searchISBN() { String requestUrl = "http://webservices.amazon.co.jp/onca/xml?" + "Service=AWSECommerceService&SubscriptionId=" + [ここに Access Key IDをセット]" + "&Operation=ItemSearch&ResponseGroup=Medium&SearchIndex=Blended&Keywords=" + getIcbnField().getText(); try { DocumentContainer container = new DocumentContainer(new URL( requestUrl)); container.setNamespaceAware(true); JXPathContext context = JXPathContext.newContext(container); context.registerNamespace("aws", "http://webservices.amazon.com/AWSECommerceService/2005-10-05"); String title = context.getValue("//aws:Title").toString(); String author = context.getValue("//aws:Author").toString(); int numOfAuthor = (int)Double.parseDouble(context.getValue("count(//aws:Author)").toString()); for (int i = 1; i < numOfAuthor; i++) { String nextAuthor = "//aws:Author[" + (i+1) + "]"; author = author + ", " + context.getValue(nextAuthor).toString(); } String publisher = context.getValue("//aws:Manufacturer").toString(); String publicationDate = context.getValue("//aws:PublicationDate").toString(); String noPages = context.getValue("//aws:NumberOfPages").toString(); String listPrice = context.getValue("//aws:Amount").toString(); titleArea.setText(title); authorArea.setText(author); publisherField.setText(publisher); publicationDateField.setText(publicationDate); noPagesField.setText(noPages); listPriceField.setText(listPrice); } catch (Exception e) { e.printStackTrace(); } } }} ** ソースファイル [#sc3a21cd] 今回のソースを以下から参照してください。 - &ref(BookDialog.java); - &ref(BookManager.java); - &ref(HF04SL.java); ** コメント [#bb543b47] この記事は、 #vote(おもしろかった,そうでもない,わかりずらい) 皆様のご意見、ご希望をお待ちしております。 #comment #comment_kcaptcha