僕らの96bit戦争

はじめに

こんにちは、イメージ・マジックの安藤です。

先日の台風24号で私の住んでいるアパートは何度も大いに揺らされ、今にも倒壊するのではないかという勢いでした。
本当に大変だったのは翌日の電車でしたね。私の使用している沿線は早朝の段階では止まっていました。

そんなわけで、今回の話題は文字表現とビット演算の話です。

情報を管理する媒体について

当社で日々製造されている製品は、実際にお客様のお手元に届けられるまでに実に様々な工程を経ています。
しかも、それらは大量生産されているようなものばかりではなくお客様ごとのオンデマンド製品も多分に扱っています。
これらの製品を間違いなくお客様にお届けするためには、注文や製品ごとに情報をきっちりと管理していく必要があります。
そしてこれらの製造を支える在庫の管理・調整も効率的に向上を回していくうえで欠かせないものです。

そこで重要になってくるのが情報を管理する媒体です。最も簡単に実現できるものとしては、バーコードを印刷したラベルシールがあります。

ところで、皆様はユニクロのセルフレジを利用したことがありますでしょうか?
商品を置くだけで何をどのくらい買うのかを瞬時に判断してくれるので初めて使われた方は驚かれるかと思います。

実は、このレジにはRFID(非接触型ICタグ)という仕組みが使われており、一度に複数の商品を読み取ることができます。

当社においても、RFIDを実用していくために現在様々な検討をしています。

RFIDに書き込む、RFIDを読み込む

RFIDにはEPC Class1 Generation2という規格があり、4つのメモリバンクで構成されています。

  • RESERVED
  • EPC
  • TID
  • USER

データを読み書きする部分は主にEPCメモリで、通常は96bitを使います。USERメモリも自由に読み書きできますが、使用するチップによっては実装されていないこともあり容量も特に決まっていません。
TIDメモリはRead Onlyで32bit以上のチップの識別情報が書き込まれています。

一言でまとめますと、96bitに書きたい情報を詰めろということです。

96bitの使い道を考える

96bitでできることは何か考えてみましょう。

まず、これをビットボートとして扱った場合は96の状態を表現できますが、RFIDの使い道としては微妙すぎます。

次に、考えることとしては数字として使うことでしょう。32bitの整数であれば3つ分表現できますが、やはり不便です。

そこで行き着くものとしては、やはりASCII文字になるかと思います。この場合、1文字が8bitで最大12文字まで使用できます。

ところで、ここで一つ問題があります。
日本で使われているバーコードの規格であるJANは13桁で規定されているのです。もっとも、13桁目はパリティビットとなっているため導出が不可能というわけではないですが様々な場面で余計な手間がかかってしまいます。

様々な検討の結果、最終的にASCIIの余計な文字を削って16字まで表現できるようにするという方式を取ることにしました。

96bitで16文字を表現する

96bitで表現できる文字数を増やすには、1文字に使用できる選択肢を減らせばいいです。
1文字を6bit(=64種類)とすると96/6=16となり16文字分使えることとなります。
この際、64文字は好きなように選ぶことができます。

今回は0-9A-Zの半角英数大文字と括弧など一部の半角記号を除いた55文字を採用することとしました。

エンコード/デコードを実装する

諸般の事情により、上記の実装はnode.jsで行いRFIDリーダライタとのやり取りはネイティブモジュールを挟むことで実現しています。

読み取られたデータは16Bのデータとなっており、最初の4BはTIDです。
デコード時は12Bを6bitごとに分解する必要があり、エンコード時は最大16文字を6bitで表現し12Bに詰めなおす必要があります。

const codeMap = []; // 省略 最大64種類の文字に置き換えられる

const bitMap = [1, 2, 4, 8, 16, 32, 64, 128];

const calc = arr => arr.map((n , i) => n ? bitMap[i] : 0)
    .reduce((sum,  n) => sum + n,  0);

const toBit = (b, arr = [], index = 0) => index === 6 ? arr :
    toBit(parseInt(b / 2, 10), arr.concat(b % 2), index + 1);

const encode = src => {
    const chunk = src.split('') // 1文字ごとに分ける
        .map(s => toBit(codeMap.indexOf(s)))
        .reduce((a , b) => a.concat(b), []);
    return Array(12).fill(0)
        .map((_, i) => calc(chunk.slice(i * 8, (i + 1) * 8)));
};

const decode = src => {
    const chunk = src.slice(4) // TIDをスキップ
        .map(b => Array(8).fill(0).map((_, i) => b >> i & 1))
        .reduce((a , b) => a.concat(b), []);
    return Array(16).fill(0)
        .map((_, i) => codeMap[calc(chunk.slice(i * 6, (i + 1) * 6))])
        .join('');
};

おわりに

エンコード/デコードの実装が意外と面倒で2時間ほどかけてしまいましたが、これで16文字の範囲で自由にRFIDを書き換えることが可能になりました。
ちなみにネイティブライブラリは当社別メンバーの力作です。

参考

20160129-UHF帯RFID標準コード体系ガイドライン初版