nfc-tools 開發 nfc 相關的 tool, 最有名的應該是libnfc
底下是 nfc-tools 的網頁
https://code.google.com/p/nfc-tools/
但是libnfc還是太底層, 為了實作 NDEF 或 NPP, 可能還要裝 libfreefare 或 libllcp
libfreefare 是專門用在 Mifare
https://code.google.com/p/libfreefare/
libllcp 則是一般性的NDEF communication
https://code.google.com/p/libllcp/
關於怎麼install:
http://nfc-tools.org/index.php?title=Libllcp
LLCP (Logical Link Control Protocol), 有點像是 IP (Internet Protocol)
它可以經由建立連線之後, 由兩端傳data
而 LLCP 又實作了兩種 protocol
一個是 NPP (NDEF Push Protocol), 就像是 UDP 一樣, 丟封包出去就不用等
另個是 SNEP (Simple NDEF Exchange Protocol), 就像是 TCP, 丟封包出去要等ACK, (但沒有三方交握)
2. NPP sample code
在 libllcp/examples/npp-server/ 有個npp server的範例它的用途是建立連線之後, 接收從tag來的資料
但是裡面似乎有些bug, 所以我將它重寫並測試過, sample code如下:
(錯的部份是, header 檔像 llcp.h 應該在 nfc/llcp.h底下, 然後stdout的部份會強制導到file)
#include <err.h> #include <signal.h> #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <nfc/llcp.h> #include <nfc/llc_service.h> #include <nfc/llc_link.h> #include <nfc/llc_connection.h> #include <nfc/mac.h> #define errx(code, ...) do { \ fprintf (stderr, __VA_ARGS__); \ fprintf (stderr, "\n"); \ exit (code); \ } while (0) struct mac_link *mac_link; nfc_device *device; static void stop_mac_link(int sig) { if (mac_link && mac_link->device) { nfc_abort_command(mac_link->device); } } static void bye(VOID) { if (device) { nfc_close(device); } } static void print_hex(const uint8_t *pbtData, const size_t szBytes) { size_t szPos; for (szPos = 0; szPos < szBytes; szPos++) { printf("%02x ", pbtData[szPos]); } printf("\n"); } static void* com_android_npp_thread(void *arg) { struct llc_connection *connection = (struct llc_connection *)arg; uint8_t buffer[1024]; int len; size_t n = 0; len = llc_connection_recv(connection, buffer, sizeof(buffer), NULL); if (len < 0) return NULL; if (len < 10) return NULL; // NPP's header (5 bytes) + NDEF entry header (5 bytes) // byte 0 : protocol version printf("NDEF Push Protocol version: %02x\n", buffer[n]); if (buffer[n] != 0x01) return NULL; // version not supported n++; // byte 1~4 : number of NDEF entries uint32_t ndef_entries_count = be32toh(*((uint32_t *)(buffer+n))); printf("NDEF entries count: %u\n", ndef_entries_count); if (ndef_entries_count != 1) return NULL; // Version 0x01 has value 0x00 0x00 0x00 0x01 n += 4; // NDEF Entry printf("Action code: %02x\n", buffer[n]); if (buffer[n] != 0x01) return NULL; // Action code not supported n++; uint32_t ndef_length = be32toh(*((uint32_t *)(buffer+n))); n += 4; if ((len-n) < ndef_length) return NULL; // less received bytes than expected? printf("NDEF entry received (%u bytes): ", ndef_length); print_hex(buffer + n, ndef_length); llc_connection_stop(connection); return NULL; } int main(int argc, char *argv[]) { int ret; if (llcp_init() < 0) errx(EXIT_FAILURE, "llcp_init()"); signal(SIGINT, stop_mac_link); atexit(bye); nfc_context *context; nfc_init(&context); if (context == NULL) errx(EXIT_FAILURE, "nfc_init()"); device = nfc_open(context, NULL); if (device == NULL) errx(EXIT_FAILURE, "Cannot connect to NFC device"); struct llc_link *llc_link = llc_link_new(); if (llc_link == NULL) errx(EXIT_FAILURE, "Cannot allocate LLC link data structures"); struct llc_service *com_android_npp = llc_service_new_with_uri(NULL, com_android_npp_thread, "com.android.npp", NULL); if (com_android_npp == NULL) errx(EXIT_FAILURE, "cannot create com.android.npp service"); llc_service_set_miu(com_android_npp, 512); llc_service_set_rw(com_android_npp, 2); ret = llc_link_service_bind(llc_link, com_android_npp, -1); if (ret < 0) errx(EXIT_FAILURE, "Cannot bind service"); mac_link = mac_link_new(device, llc_link); if (mac_link == NULL) errx(EXIT_FAILURE, "Cannot create MAC link"); ret = mac_link_activate_as_target(mac_link); if (ret < 0) errx(EXIT_FAILURE, "Cannot create MAC link"); void *err; mac_link_wait(mac_link, &err); mac_link_free(mac_link); llc_link_free(llc_link); nfc_close(device); device = NULL; llcp_fini(); nfc_exit(context); exit(EXIT_SUCCESS); }
然後將它compile, 其中 llcp 的部份官網寫 -lnfc-llcp是錯的
$ gcc -o npp_server_example npp_server_example.c -lnfc -lllcp
有些手機仍舊可以接受NPP
我手上有台 HTC ONE, 將它的瀏覽器打開, 把手機放在 reader 上面
然後執行剛剛的程式, 手機會感應到, 手機螢幕顯示點選以傳送資料, 點它, 程式就會收到data, 最後將手機移開reader, 程式就結束
$ ./npp_server_example NDEF Push Protocol version: 01 NDEF entries count: 1 Action code: 01 NDEF entry received (23 bytes): d1 01 13 55 04 74 77 2e 6d 6f 62 69 2e 79 61 68 6f 6f 2e 63 6f 6d 2f
其中
74 77 2e 6d 6f 62 69 2e 79 61 68 6f 6f 2e 63 6f 6d 2f = tw.mobi.yahoo.com/
(TODO: 前面header應該可以parse)
3. npp server code analysis
llcp_init() 是使用llcp要用的初始化, 裡面設定signal handler以及log function,
llcp_fini() 則是結束時釋放資源
int llcp_init(void) int llcp_fini(void)
nfc context 以及 nfc device 的用法和 libnfc 相同
llc_link 用於紀錄 llc link 相關資訊
struct llc_link { uint8_t role; enum { LL_ACTIVATED, LL_DEACTIVATED, } status; struct llcp_version version; uint16_t local_miu; uint16_t remote_miu; uint16_t remote_wks; struct timeval local_lto; struct timeval remote_lto; uint8_t local_lsc; uint8_t remote_lsc; uint8_t opt; pthread_t thread; char *mq_up_name; char *mq_down_name; mqd_t llc_up; mqd_t llc_down; struct llc_service *available_services[MAX_LLC_LINK_SERVICE + 1]; struct llc_connection *datagram_handlers[MAX_LOGICAL_DATA_LINK]; struct llc_connection *transmission_handlers[MAX_LLC_LINK_SERVICE + 1]; /* Unit tests metadata */ void *cut_test_context; struct mac_link *mac_link; };
用 llc_link_new() 來設定 llc link 的 default 值
struct llc_link* llc_link_new(void)
llc_service 用來建 LLC(logical link control) 會用到的相關設定
struct llc_service { char *uri; void *(*accept_routine)(void *); void *(*thread_routine)(void *); int8_t sap; uint8_t rw; uint16_t miu; void *user_data; };
llc_service_new_with_uri() 則是設定 llc_service 成default
struct llc_service* llc_service_new_with_uri(void * (*accept_routine)(void *), void * (*thread_routine)(void *), const char *uri, void *user_data)accept_routine 是 thread function, 在建立連線時(收到PDU_CONNECT) 呼叫
thread_routine 也是 thread function, 在收到 PDU_UI 時呼叫
uri 應該只是 llc service identify
user_data 可以送 data, 這個例子裡沒用到
有了相關設定之後, 可以bind service
int llc_link_service_bind(struct llc_link *link, struct llc_service *service, int8_t sap)
接著是 mac layer 相關設定
struct mac_link { enum { MAC_LINK_UNSET, MAC_LINK_INITIATOR, MAC_LINK_TARGET } mode; nfc_device *device; struct llc_link *llc_link; uint8_t nfcid[10]; uint8_t buffer[BUFSIZ]; size_t buffer_size; pthread_t *__restrict__ exchange_pdus_thread; };
一樣有設定default值的function
struct mac_link* mac_link_new(nfc_device *device, struct llc_link *llc_link)
然後enable mac layer setting
int mac_link_activate_as_target(struct mac_link *mac_link)裡面做的事是用 libnfc 選 target tag
其中 mac_link_run() 開了一條thread 用於傳送接收apdu
預設是
void* mac_link_exchange_pdus(void *arg)
最後用這個 function 等 mac_link_exchange_pdus 這條 thread 結束
int mac_link_wait(struct mac_link *link, void **value_ptr)
4. snep sample code
我一樣改寫了 libllcp 的 libllcp/examples/snep-server/#include <err.h> #include <signal.h> #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <nfc/llcp.h> #include <nfc/llc_service.h> #include <nfc/llc_link.h> #include <nfc/llc_connection.h> #include <nfc/mac.h> #define LOG_DGB 1 #define errx(code, ...) do { \ fprintf (stderr, __VA_ARGS__); \ fprintf (stderr, "\n"); \ exit (code); \ } while (0) struct mac_link *mac_link; nfc_device *device; static void stop_mac_link(int sig) { if (mac_link && mac_link->device) { nfc_abort_command(mac_link->device); } } static void bye(VOID) { if (device) { nfc_close(device); } } static void print_hex(const uint8_t *pbtData, const size_t szBytes) { size_t szPos; for (szPos = 0; szPos < szBytes; szPos++) { printf("%02x ", pbtData[szPos]); } printf("\n"); } static void* com_android_snep_thread(void *arg) { struct llc_connection *connection = (struct llc_connection *) arg; uint8_t buffer[1024]; uint8_t frame_snep_response_success[] = { 0x10, 0x81, 0x00, 0x00, 0x00, 0x00 }; int len; len = llc_connection_recv(connection, buffer, sizeof(buffer), NULL); if (len < 0 || len < 7) {
// SNEP's header (2 bytes) + NDEF entry header (5 bytes)printf("ERR: invalid length\n"); return NULL; } size_t n = 0; printf("SNEP version: %d.%d\n", buffer[n]>>4, buffer[n] & 0x0f ); if (buffer[n] != 0x10) { printf("WRN: snep version other than version 1.0 may not support.\n"); } n++; uint8_t action_code = buffer[n]; n++; switch(action_code) { case 0x00: // Continue break; case 0x01: // GET break; case 0x02: // PUT { uint32_t ndef_length = be32toh(*((uint32_t *)(buffer + n))); if ((len - 6) < ndef_length) { printf("ERR: less received bytes than expected?\n"); return NULL; } n+=4; /** return snep success response package */ llc_connection_send(connection, frame_snep_response_success, sizeof(frame_snep_response_success)); print_hex(buffer + n, ndef_length); break; } default: printf("unknown action code\n"); break; } llc_connection_stop(connection); return NULL; } int main(int argc, char *argv[]) { int ret; if (llcp_init() < 0) errx(EXIT_FAILURE, "llcp_init()"); signal(SIGINT, stop_mac_link); atexit(bye); nfc_context *context; nfc_init(&context); if (context == NULL) errx(EXIT_FAILURE, "nfc_init()"); device = nfc_open(context, NULL); if (device == NULL) errx(EXIT_FAILURE, "Cannot connect to NFC device"); struct llc_link *llc_link = llc_link_new(); if (llc_link == NULL) errx(EXIT_FAILURE, "Cannot allocate LLC link data structures"); struct llc_service *com_android_snep = llc_service_new_with_uri(NULL, com_android_snep_thread, "urn:nfc:sn:snep", NULL); if (com_android_snep == NULL) errx(EXIT_FAILURE, "cannot create com.android.npp service"); llc_service_set_miu(com_android_snep, 512); llc_service_set_rw(com_android_snep, 2); ret = llc_link_service_bind(llc_link, com_android_snep, LLCP_SNEP_SAP); if (ret < 0) errx(EXIT_FAILURE, "Cannot bind service"); mac_link = mac_link_new(device, llc_link); if (mac_link == NULL) errx(EXIT_FAILURE, "Cannot create MAC link"); ret = mac_link_activate_as_target(mac_link); if (ret < 0) errx(EXIT_FAILURE, "Cannot create MAC link"); void *err; mac_link_wait(mac_link, &err); mac_link_free(mac_link); llc_link_free(llc_link); nfc_close(device); device = NULL; llcp_fini(); nfc_exit(context); exit(EXIT_SUCCESS); }
compile 之後, 實驗方式跟 npp server 一樣
5. code analysis
整份 code 跟 npp server 很像, 但有些小差異
在 llc_link_service_bind(), 後面帶的 sap 是 LLCP_SNEP_SAP
另個差異是 header 不一樣
然後在收到data之後, 要回傳OK packet
其它的就沒有差異了
請問一下可以用snep-server或snep-client把data (比如文字或網址甚至圖片) 從PN532傳送到android嗎?
回覆刪除如果可以請問要怎樣做到?
我沒有實際試過, 但我猜 PN532 應該要扮演 snep-client 的角色, 也就是NFC裡的TAG, 讓Android 扮演 NFC的reader, 然後把要傳的東西包成NDEF, NDEF的格式在網路上可以找到, 文字或圖片的格式都不太一樣
回覆刪除我試過用snep-client把http://google.com傳到手機, 我把com_android_snep_service下的uint8_t frame[] 改成了
回覆刪除{ 0xD1, 0x01, 0x0B, 0x55, 0x01,
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d } // 這串是google.com
可是手機只震了一下, 沒有把http://google.com傳過去
請問要像snep-server一樣改他的source code還是有其他問題?
如果要像snep-server一樣改source code的話 請問應該怎樣改? 一直google也找不到解決方法..
刪除很抱歉, 最近有點忙, 沒辦法把玩PN532這張卡, 所以在code上面沒辦法給有用的建議。
刪除底下是一些debug的方向供你參考:
我之前用過不同手機測試, 有的手機會正確收到data, 但有的只會震, 可能要看手機的log才會知道它實際上把它認成什麼。
Android 偵測到卡的時候會嘗試解讀卡的內容 (同時會震或者會嗶一聲), 卡分成三種, ACTION_TAG_DISCOVERED, ACTION_TECH_DISCOVERED, ACTION_NDEF_DISCOVERED, 只有ACTION_NDEF_DISCOVERED 會真的把NDEF的內容讀出來並做出對應的事情 (Ex. 開網頁).
你可以抓一些讀TAG的APP, (Ex. NXP的NFC Reader), 用這些APP讀看看實際上它把卡認成什麼內容。
您好,
回覆刪除拙學有一個NFC的疑惑請前輩指引,NFC手機和NFC晶片卡之間,
從晶片卡讀取資料,處理加入其他資料(如時間),再寫入晶片卡,
這一連串動作,是否可以在一次感應下就完成?
因為目前拙學作出來是感應時,只有讀取,不會寫入,再感應才會寫入,
也就是讀取和寫入要分兩次感應,能不能一次感應就完成讀取,處理,寫入的動作呢?
查過許多參考書和網路資料,有的說可以,但都沒有實例,全部都是讀寫分開,
到底可不可以一次感應就完成呢?
拙學 Stephen tzushow@gmail.com
您好,
回覆刪除拙學有一個NFC的疑惑請前輩指引,NFC手機和NFC晶片卡之間,
從晶片卡讀取資料,處理加入其他資料(如時間),再寫入晶片卡,
這一連串動作,是否可以在一次感應下就完成?
因為目前拙學作出來是感應時,只有讀取,不會寫入,再感應才會寫入,
也就是讀取和寫入要分兩次感應,能不能一次感應就完成讀取,處理,寫入的動作呢?
查過許多參考書和網路資料,有的說可以,但都沒有實例,全部都是讀寫分開,
到底可不可以一次感應就完成呢?
拙學 Stephen tzushow@gmail.com
Dear Andrew,
回覆刪除我在 Android手機 + NXP 203F Tag 上實作過, 當Android detect 到 NFC TAG 的時候, 只要藉由Tag物件就可以寫入Tag