いま巷で話題の NFC で書き換えできる名札サイズの電子ペーパーのプロトコルを調べました。 @alt-coreさんの追加の調査も参考にしています。
- Swift EPaperNFCSwift (@niw)
- Python nfc-eink (@alt-core)
- ISO 14443-A (Type A)
- Application Identifier (AID):
D2760000850101 - ISO7816 APDU を使う
| サイズ | 色数 (ビット数) | 解像度 | 色インデックス | 画像データの方向 |
|---|---|---|---|---|
| 2.9-inch | 2 (1-bit) | 296 x 128 | 0x00: Black0x01: White |
90度回転 |
| 2.9-inch | 4 (2-bit) | 296 x 128 | 0x00: Black0x01: White0x02: Yellow0x03: Red |
90度回転 |
| 4.2-inch | 2 (1-bit) | 400 x 300 | 0x00: Black0x01: White |
左右反転 |
| 4.2-inch | 4 (2-bit) | 400 x 300 | 0x00: Black0x01: White0x02: Yellow0x03: Red |
そのまま |
送信
0x00, 0x20, 0x00, 0x01, 0x04, 0x20, 0x09, 0x12, 0x10
受信ステータス
- 成功:
0x90, 0x00 - 失敗: その他
送信
0x00, 0xD1, 0x00, 0x00, 0x00
受信ステータス
- 成功:
0x90, 0x00 - 失敗: その他
受信データ
TLV形式 (タグ 1byte, データ長 1byte, データ…, の繰り返し)
Tag: 0xA0
Length: 7
Data: {
unknown: UInt8,
numberOfColors: UInt8,
unknown: UInt8,
heightInBits: UInt16 (Big-Endian)
width: UInt16 (Big-Endian)
}
// numberOfColors
0x01, 0x47: 2-Color, 1bpp (bits per pixel)
0x07: 4-Color, 2bpp
// size
(width, heightInBits / bpp)
Tag: 0xA1
Length: 7
Data: {
imageOrientation: UInt8
unknown: UInt8[6]
}
// imageOrientation (未確定)
0x00: 90度回転
0x01: そのまま
Tag: 0xC0
Length: 10
Data: {
name: Char[10]
}
Tag: 0xC1
Length: 4
Data: {
uid: UInt32 (Big-Endian)
}
Warning
imageOrientation フィールドについては推定で不確定です。特に 2.9-inch 4色と 4.2-inch 2色については不明です。
送信
0xF0, 0xD3, 0x00, P2, Lc, Data...
詳細は後述
受信ステータス
- 成功:
0x90, 0x00 - 失敗: その他
送信
0xF0, 0xD4, 0x85, P2, 0x00
// P2
0x00: 画面更新を待って受信 (ブロック), 後述の画面更新の確認は不要
0x80: 画面更新を待たずに受信
ドキュメントには記載がないが、iOS の Core NFC はタグのコネクションが connect(to:) を呼んでから20秒に制限されている。ブロックせずに画面更新をポーリングする場合、20秒を越えることがあり、画面更新が完了せずに終了することがある。ブロックする場合は結果を受信するまで20秒を超えてコネクションが維持される。
受信ステータス
- 成功:
0x90, 0x00 - 失敗: その他
送信
0xF0, 0xDE, 0x00, 0x00, 0x01
- 例えば 500ms 間隔でポーリングする
受信ステータス
- 成功:
0x90, 0x00 - 失敗: その他
受信データ
- 0x00: 更新完了
- 0x01: 更新中
- 認証コマンド
0x00, 0x20, ...→ 成功0x90, 0x00 - 画像データ送信
0xF0, 0xD3, ...→ 成功0x90, 0x00を複数回 - 画面更新
0xF0, 0xD4, 0x85, 0x00, 0x00→ 成功0x90, 0x00
- 認証コマンド
0x00, 0x20, ...→ 成功0x90, 0x00 - 画像データ送信
0xF0, 0xD3, ...→ 成功0x90, 0x00を複数回 - 画面更新
0xF0, 0xD4, 0x85, 0x80, 0x00→ 成功0x90, 0x00 - 画面更新の確認
0xF0, 0xDE, ...→ 成功0x90, 0x00, データが完了0x01になるま繰り返す
- デバイス情報取得コマンドの結果を使うか、手動で選択して色数とサイズを決定。
- 色インデックスを使ったビットマップデータ
UInt8[width * height]を用意。 - 2.9-inchのデバイスは画像データを90度回転する
- 4.2-inch 4-Colorsのデバイスは画像データを左右反転する
画像データをそれぞれの行の右から左に 1-byte に 8-pixel (2-color) か 4-pixel (4-color) をパックする
// 画像それぞれの行の右から左に以下の色インデックスで並んでいる場合
p0, p1, p2, p3, p4, p5, p6, p7 ...
// 2色なら
byte = p0 | (p1 << 1) | (p2 << 2) | (p3 << 3) | (p4 << 4) | ...
// 4色なら
byte = p0 | (p1 << 2) | (p2 << 4) | (p3 << 6)
- パックしたデータを 2,000-byte ごとに分割してブロックにする。
- 2.9-inch の場合、最後のブロックは 2,000-byte 未満になる。
- ブロックごとに
LZO1X-1(lzo1x_1_compress) で圧縮する
- 圧縮したブロックを 250-byte ごとに分割してフラグメントにする。
- 最後のフラグメントは 250-byte 以下になる。
画像データ送信コマンド 0xF0, 0xD3, ... を複数回使いフラグメントを送信する。
0xF0, 0xD3, 0x00, P2, Lc, Data...
// P2
- 0x00: ブロック途中のフラグメント
- 0x01: ブロック最後のフラグメント
// Lc
フラグメントの長さ + 2
// Data
{
blockIndex: UInt8
fragmentIndex: UInt8
fragment: UInt8[]
}