Table of Contents
Introduction
I recently published a blog showcasing web-NFC for the Project14 RFID or NFC Competion where a question arose in one of the comments about whether QR codes could be used to deliver the same functionality, and I responded with some reasons as to why I thought NFC was still better in this use case.
However, when considering NFC versus QR codes I had missed noting the most obvious difference, namely that with NFC you can read from AND write to an NFC tag.
This type of configuration is typically found in contactless EMV smart card payment systems, which use contactless prepayment cards for applications such as public transport ticketing, vending machines, school canteens etc. These contactless smart cards are first preloaded with a financial value securely stored inside the contactless card’s memory chip (often called an ePurse or electronic purse). When this card is presented to pay for any transaction, the value of goods or services purchased is then subtracted from this value and the new value is securely uploaded onto the card together with some transaction details, depending on memory size. All this data is encrypted and write-protected to prevent unauthorised access via the Secure Access Module (SAM).
Well after a bit contemplation the engineer in me got the better of me and I decided to write up this blog demonstrating yet another (non-transactional) NFC type application using read and write functionality. This use-case also relies on a very handy feature I recently discovered while using the Nordic Semiconductor’s nRF52 NFCT peripheral driver on a nRF52840 development board.
Let me now elaborate.
Keyless electronic locks / access control devices
Keyless electronic locks and access control systems tend to either use a keypad, where the user enters a pin code, or a contactless card reader, where the user presents a compatible RFID or NFC contactless card and the reader then reads the pin code stored in secure non-volatile memory on this contactless card.
These access control systems can essentially be split into its various components, namely:
- input devices, which is typically keypad or rfid/nfc contactless card reader;
- control devices, which is typically a microcontroller; and
- Output devices, which is typically a relay or electronic lock device
My focus is on the input mechanism. No doubt there's strengths and weaknesses for either input mechanism shown. For example, a keypad requires the user to input a secret pin code, but there is no way to identify which user entered that code. While a RFID/NFC card offers convenience as the secret pin code is securely stored on the card’s IC but if there card is lost/stolen there’s always a risk of an unauthorised person using that card, until such time as the card ID is blocked.
Well, I came up with a 3rd input mechanism, which utilises NFC read and write functionality.
Enter an NFC-based access control device (or electronic lock) that uses a virtual keypad… via your mobile phone.
And to deliver this functionality without the use of any additional components such as a NFC controller device, I have based my design on the Nordic Semiconductor nRF52 Series.
I will now explain why.
Nordic Semiconductor nRF52 Series
When Nordic Semiconductor introduced the nRF52 Series Bluetooth Smart SoC back in 2015 it was, and I believe still is, the only ARM Cortex M4 SoC on the market to incorporate NFC Tag type 2 and type 4 functionality (often referred to as NFCT in Nordic Semi’s documentation).
Since nRF5 SDK version 11, NFC-T examples have been included, which demonstrate the ability to create NDEF Text and URL messages, launch apps and pair with Bluetooth.
Then in 2017, Nordic Semiconductor published a blog on the DevZone which introduced read and write and functionality for Type 4 Tags:
This functionality is demonstrated in their Writable NDEF Message Example which can be found in their SDK. In fact it was first included in SDK version 14.
As the info-centre documentation notes, the Writable NDEF Message Example shows how to use the NFC tag to expose an NDEF message, which can be overwritten with any other NDEF message by an NFC device. It also highlights the usage of the NFC Type 4 Tag and the URI message generation modules, and also uses the Experimental: Flash Data Storage module to store the NDEF message in flash memory.
So, if you delve deeper into the NFC code examples, you will notice that when initialising the t2t or t4t NFC code library it requires an NFC callback function for event handling
e.g. nfc_t4t_setup(nfc_callback, NULL);
Within this callback routine you then can define triggers based on certain NFC field detect, read and write events.
- NFC_T4T_EVENT_FIELD_ON: External Reader polling detected.
- NFC_T4T_EVENT_FIELD_OFF: External Reader polling ended.
- NFC_T4T_EVENT_NDEF_READ: External Reader has read static NDEF-Data from Emulation / A Read operation happened on the last byte of NDEF-Data.
- NFC_T4T_EVENT_NDEF_UPDATED: External Reader has written to length information of NDEF-Data from Emulation.
- NFC_T4T_EVENT_DATA_TRANSMITTED: In Raw mode it signals that the data from @ref nfc_t4t_response_pdu_send have been sent out.
- NFC_T4T_EVENT_DATA_IND: In Raw mode delivers the APDU fragments.
Now, what’s far more interesting is how the payload data is stored in memory and how it’s then used by the NFCT peripheral driver.
The nRF52 NFCT peripheral driver uses DMA (direct memory access) to store NFC data when powered then you have to explicitly write this data to flash memory for storage after power down. In other words, nRF52 NFCT memory access behaves very differently to say an NXP NTAG or a ST25DV NFC tag, which uses EEPROM for general data storage or SRAM in pass-through mode.
This can be seen in the callback function within the Writable NDEF Message example. When the NFC_T4T_EVENT_NDEF_UPDATED event is triggered the data written to the nRF52 device via the wireless NFCT antenna is already stored in DMA. If you comment out the line err_code = app_sched_event_put(NULL, 0, scheduler_ndef_file_update);
and then rerun the example you will be able to read the updated data following an NFC write event.
If you then restarted the nRF52 device the original data will then reappear as the previous NFC data write was not stored in flash memory, which would’ve been handled by the scheduler_ndef_file_update
function.
Thus you now have an opportunity to receive data via NFC DMA, store it elsewhere for later analysis and then internally change the NFC DMA data so that the next time an NFC read event occurs the written data is out of sight (i.e. hidden from view).
So essentially that is how I used this logic to create my virtual keypad logic.
You will see in the demo that the data sent cannot be viewed and all you see is the token or an indicator that the relay has been activated.
Anyway, further information about the NFCT functionality is provided here: https://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.nrf52832.ps.v1.1%2Fnfc.html
So in summary, with the nRF52 NFCT peripheral driver you essentially get easy direct access to nRF52 memory blocks as a pseudo pass-through method, which, as I discovered, allows you to use NDEF messaging capabilities to transfer data with a NFC-enabled mobile phone. You certainly cannot readily do this using NTAG’s or ST25DV tags and a common NFC mobile phone app, as these tags use specific memory registers to handle pass through mode and as such you would need to create a low level NFC mobile phone app to enable data transfer to these specified memory registers.
PAN1780 (nRF52840) Firmware
Let’s now dive into the hardware and the firmware.
I had road tested the Panasonic PAN 1780 Bluetooth LE Eval Kit 2 years ago and this evaluation kit included a great NFC antenna.
So I decided to use this evaluation kit to develop my proof of concept design. All I added was a SeeedStudio Grove relay, which required 5V as VIN and one GPIO signal to control the relay trigger.
This evaluation board proved ideal as it also includes a JLink interface so you literally just plug in a USB cable and you are good to go.
For the firmware development, I used Segger Embedded Studio - please see my road test report for further guidance.
As alluded to earlier, the firmware is based on the Writable NDEF Message example. I added in an additional timer component to handle the relay activation trigger and then the timed deactivation.
For purposes of this project demonstration, the encryption part was excluded.
The main code routine is as follows:
/** * Copyright (c) 2017 - 2021, Nordic Semiconductor ASA (original NFC writable example) * This example has been amended to demonstrate a virtual keypad function * Copyright (c) 22 December 2022 - C Gerrish (BigG) * MIT License applies for amendments * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 2. Redistributions in binary form, except as embedded into a Nordic * Semiconductor ASA integrated circuit in a product or a software update for * such product, must reproduce the above copyright notice, this list of * conditions and the following disclaimer in the documentation and/or other * materials provided with the distribution. * * 3. Neither the name of Nordic Semiconductor ASA nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * 4. This software, with or without modification, must only be used with a * Nordic Semiconductor ASA integrated circuit. * * 5. Any software provided in binary form under this license must not be reverse * engineered, decompiled, modified and/or disassembled. * * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ /** @file * * @defgroup nfc_writable_ndef_msg_example_main main.c * @{ * @ingroup nfc_writable_ndef_msg_example * @brief The application main file of NFC writable NDEF message example. * */ #include <stdint.h> #include <stdbool.h> #include "app_error.h" #include "app_scheduler.h" #include "boards.h" #include "nfc_t4t_lib.h" #include "nrf_log_ctrl.h" #include "ndef_file_m.h" #include "nfc_ndef_msg.h" #include "app_timer.h" #include "nrf_drv_clock.h" #include "nrf_log.h" #include "nrf_log_ctrl.h" #include "nrf_log_default_backends.h" #define APP_SCHED_MAX_EVENT_SIZE 0 /**< Maximum size of scheduler events. */ #define APP_SCHED_QUEUE_SIZE 4 /**< Maximum number of events in the scheduler queue. */ #define APP_DEFAULT_BTN BSP_BOARD_BUTTON_0 /**< Button used to set default NDEF message. */ #define APP_RELAY_PIN NRF_GPIO_PIN_MAP(0,28) static uint8_t m_ndef_msg_buf[NDEF_FILE_SIZE]; /**< Buffer for NDEF file. */ static uint8_t m_ndef_msg_len; /**< Length of the NDEF message. */ static uint8_t NFCReadEvent = 0; static const uint8_t secret[] = /**< Secret NDEF code - NOTE for demo purposes this has not been encrypted". */ {'3', '2', '1', '4', '5', '6', '0', '1', '4'}; APP_TIMER_DEF(m_relay_timer_id); /**< Handler for single shot timer used to reset the relay. */ static const uint32_t RELAY_TIMEOUT = 5000; /** * @brief Function for updating NDEF message in the flash file. */ static void scheduler_ndef_file_update(void * p_event_data, uint16_t event_size) { ret_code_t err_code; UNUSED_PARAMETER(p_event_data); UNUSED_PARAMETER(event_size); // Update flash file with new NDEF message. err_code = ndef_file_update(m_ndef_msg_buf, m_ndef_msg_len + NLEN_FIELD_SIZE); APP_ERROR_CHECK(err_code); } /** * @brief Callback function for handling NFC events. */ static void nfc_callback(void * context, nfc_t4t_event_t event, const uint8_t * data, size_t dataLength, uint32_t flags) { (void)context; ret_code_t err_code; switch (event) { case NFC_T4T_EVENT_FIELD_ON: bsp_board_led_on(BSP_BOARD_LED_0); //NRF_LOG_INFO("NFC_T4T_EVENT_FIELD_ON!"); break; case NFC_T4T_EVENT_FIELD_OFF: bsp_board_leds_off(); /* Start sensing NFC field */ if (NFCReadEvent == 1) { err_code = nfc_t4t_emulation_stop(); APP_ERROR_CHECK(err_code); // Change to read write err_code = nfc_t4t_ndef_rwpayload_set(m_ndef_msg_buf, sizeof(m_ndef_msg_buf)); APP_ERROR_CHECK(err_code); NRF_LOG_INFO("Switched to readable/writable NDEF messages."); /* Start sensing NFC field */ err_code = nfc_t4t_emulation_start(); APP_ERROR_CHECK(err_code); NFCReadEvent = 2; } else if (NFCReadEvent == 3) { err_code = nfc_t4t_emulation_stop(); APP_ERROR_CHECK(err_code); // Change to read only functionality err_code = nfc_t4t_ndef_staticpayload_set(m_ndef_msg_buf, sizeof(m_ndef_msg_buf)); APP_ERROR_CHECK(err_code); NRF_LOG_INFO("Back to readable only NDEF messages."); /* Start sensing NFC field */ err_code = nfc_t4t_emulation_start(); APP_ERROR_CHECK(err_code); NFCReadEvent = 0; } break; case NFC_T4T_EVENT_NDEF_READ: bsp_board_led_on(BSP_BOARD_LED_3); // Data has been transferred - now enable write event NRF_LOG_INFO("NFC_T4T_EVENT_NDEF_READ!"); if (NFCReadEvent == 0) { NFCReadEvent = 1; } break; case NFC_T4T_EVENT_NDEF_UPDATED: if (dataLength > 7) { // Ignore the headers bsp_board_led_on(BSP_BOARD_LED_1); NRF_LOG_INFO("NFC_T4T_EVENT_NDEF_UPDATED! %d", dataLength); if (NFCReadEvent == 2) { char checkSecret[12] = {'\0'}; uint8_t cntr = 0; for (uint8_t i = 9; i < dataLength+2; i++) { if (data[i] > 31 && data[i] < 127) { NRF_LOG_INFO("%c",data[i]); if (cntr < 12) { checkSecret[cntr] = data[i]; cntr++; } } else { NRF_LOG_INFO("[%u]",data[i]); } } if (strcmp(checkSecret, secret) == 0 && strlen(checkSecret) == 9) { NRF_LOG_INFO("*** SECRET CODE ENTERED ***"); NFCReadEvent = 3; uint32_t len = sizeof(m_ndef_msg_buf); // Change message to the accept code err_code = accept_msg_encode(m_ndef_msg_buf, &len); APP_ERROR_CHECK(err_code); nrf_gpio_pin_write(APP_RELAY_PIN, 1); // Enable relay // Initiate a one off timer to disable the relay and to revert back the message err_code = app_timer_start(m_relay_timer_id, APP_TIMER_TICKS(RELAY_TIMEOUT), NULL); APP_ERROR_CHECK(err_code); } else { uint32_t len = sizeof(m_ndef_msg_buf); err_code = assettag_msg_encode(m_ndef_msg_buf, &len); APP_ERROR_CHECK(err_code); } } } else { uint32_t len = sizeof(m_ndef_msg_buf); err_code = assettag_msg_encode(m_ndef_msg_buf, &len); APP_ERROR_CHECK(err_code); } break; default: break; } } /** *@brief Function for initializing logging. */ static void log_init(void) { ret_code_t err_code = NRF_LOG_INIT(NULL); APP_ERROR_CHECK(err_code); NRF_LOG_DEFAULT_BACKENDS_INIT(); } /**@brief Function starting the internal LFCLK oscillator. * * @details This is needed by RTC1 which is used by the Application Timer * (When SoftDevice is enabled the LFCLK is always running and this is not needed). */ static void lfclk_request(void) { ret_code_t err_code = nrf_drv_clock_init(); APP_ERROR_CHECK(err_code); nrf_drv_clock_lfclk_request(NULL); } /**@brief Timeout handler for the single shot Relay timer. */ static void relay_timer_handler(void * p_context) { nrf_gpio_pin_write(APP_RELAY_PIN, 0); // Disable relay uint32_t len = sizeof(m_ndef_msg_buf); ret_code_t err_code = assettag_msg_encode(m_ndef_msg_buf, &len); APP_ERROR_CHECK(err_code); } /**@brief Create timers. */ static void create_timers() { ret_code_t err_code; err_code = app_timer_create(&m_relay_timer_id, APP_TIMER_MODE_SINGLE_SHOT, relay_timer_handler); APP_ERROR_CHECK(err_code); } /** * @brief Function for application main entry. */ int main(void) { ret_code_t err_code; log_init(); lfclk_request(); /* Configure LED-pins as outputs */ bsp_board_init(BSP_INIT_LEDS | BSP_INIT_BUTTONS); /* Configure Relay pin as output */ nrf_gpio_cfg_output(APP_RELAY_PIN); nrf_gpio_pin_write(APP_RELAY_PIN, 0); // Disable relay by default /* Initialize App Scheduler. */ APP_SCHED_INIT(APP_SCHED_MAX_EVENT_SIZE, APP_SCHED_QUEUE_SIZE); /* Initialize FDS. */ err_code = ndef_file_setup(); APP_ERROR_CHECK(err_code); /* Encode the asset tag message */ uint32_t len = sizeof(m_ndef_msg_buf); err_code = assettag_msg_encode(m_ndef_msg_buf, &len); //err_code = accept_msg_encode(m_ndef_msg_buf, &len); APP_ERROR_CHECK(err_code); /* Set up NFC */ err_code = nfc_t4t_setup(nfc_callback, NULL); APP_ERROR_CHECK(err_code); /* Run Read-Write mode for Type 4 Tag platform */ err_code = nfc_t4t_ndef_staticpayload_set(m_ndef_msg_buf, sizeof(m_ndef_msg_buf)); APP_ERROR_CHECK(err_code); NRF_LOG_INFO("Switchable READ and WRITE demo, with secret passcode..."); NRF_LOG_INFO("App starts with Readable only NDEF messages."); /* Initialise an app timer */ err_code = app_timer_init(); APP_ERROR_CHECK(err_code); create_timers(); /* Start sensing NFC field */ err_code = nfc_t4t_emulation_start(); APP_ERROR_CHECK(err_code); while (1) { app_sched_execute(); NRF_LOG_FLUSH(); __WFE(); } } /** @} */
It is worth noting the two functions that control NFC read only and NFC read and write functionality.
err_code = nfc_t4t_ndef_staticpayload_set(m_ndef_msg_buf, sizeof(m_ndef_msg_buf));
// this is read only functionalityerr_code = nfc_t4t_ndef_rwpayload_set(m_ndef_msg_buf, sizeof(m_ndef_msg_buf));
// this is read and write functionality
The header file handling the NFC messaging code is as follows:
/** * Copyright (c) 2017 - 2021, Nordic Semiconductor ASA * This example has been amended to demonstrate a virtual keypad function * Copyright (c) 22 December 2022 - C Gerrish (BigG) * MIT License applies for amendments * * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 2. Redistributions in binary form, except as embedded into a Nordic * Semiconductor ASA integrated circuit in a product or a software update for * such product, must reproduce the above copyright notice, this list of * conditions and the following disclaimer in the documentation and/or other * materials provided with the distribution. * * 3. Neither the name of Nordic Semiconductor ASA nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * 4. This software, with or without modification, must only be used with a * Nordic Semiconductor ASA integrated circuit. * * 5. Any software provided in binary form under this license must not be reverse * engineered, decompiled, modified and/or disassembled. * * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ /** @file * * @ingroup nfc_writable_ndef_msg_example_ndef_file_m ndef_file_m.c * @{ * @ingroup nfc_writable_ndef_msg_example * @brief The FDS file handler for the NFC writable NDEF message example. * */ #include "ndef_file_m.h" #include "fds.h" #include "nfc_uri_msg.h" #include "nfc_text_rec.h" #define NRF_LOG_MODULE_NAME ndef_file_m #include "nrf_log.h" NRF_LOG_MODULE_REGISTER(); #define FILE_ID 0x1111 /**< NDEF message file ID. */ #define REC_KEY 0x2222 /**< NDEF message record KEY. */ /* Text message in English with its language code. */ // Note for demo purposes this data is not encrypted static const uint8_t en_payload[] = /**< Default NDEF message: TXT "". */ {'4','1','f','9','0','a','1','e','-','c','4','2','5','-','4','a','6','5','-','a','2','0','b','-','0','c','1','d','5','6','1','a','e','e','4','9'}; static const uint8_t en_accepts[] = /**< Default NDEF message: TXT "". */ {'a','c','c','e','p','t','e','d','-','c','4','2','5','-','4','a','6','5'}; static const uint8_t en_declined[] = /**< Default NDEF message: TXT "". */ {'d','e','c','l','i','n','e','d','-','c','4','2','5','-','4','a','6','5'}; static const uint8_t en_code[] = {'e', 'n'}; static volatile bool m_fds_ready = false; /**< Flag used to indicate that FDS initialization is finished. */ static volatile bool m_pending_write = false; /**< Flag used to preserve write request during Garbage Collector activity. */ static volatile bool m_pending_update = false; /**< Flag used to preserve update request during Garbage Collector activity. */ static uint32_t m_pending_msg_size = 0; /**< Pending write/update request data size. */ static uint8_t const * m_p_pending_msg_buff = NULL; /**< Pending write/update request data pointer. */ static fds_record_desc_t m_record_desc; /**< Record descriptor. */ static fds_record_t m_record; /**< Record description used for writes. */ /** * @brief Prepare record structure for write or update request. * * @details Configures file ID, record KEY, data to be written and message length. * * @param[in] buff Pointer to the NDEF message to be stored in FLASH. * @param[in] size Size of NDEF message. */ static void ndef_file_prepare_record(uint8_t const * p_buff, uint32_t size) { // Set up record. m_record.file_id = FILE_ID; m_record.key = REC_KEY; m_record.data.p_data = p_buff; m_record.data.length_words = BYTES_TO_WORDS(size); // Align data length to 4 bytes. } /** * @brief Function for creating NDEF message in FLASH file. * * @details FDS write operation is performed asynchronously, * operation status is reported through events. * * @param[in] p_buff Pointer to the NDEF message to be stored in FLASH. * @param[in] size Size of NDEF message. * * @return NRF_SUCCESS when update request has been added to queue, * otherwise it returns FDS error code. */ static ret_code_t ndef_file_create(uint8_t const * p_buff, uint32_t size) { ret_code_t err_code; // Prepare record structure. ndef_file_prepare_record(p_buff, size); // Create FLASH file with NDEF message. err_code = fds_record_write(&m_record_desc, &m_record); if (err_code == FDS_ERR_NO_SPACE_IN_FLASH) { // If there is no space, preserve write request and call Garbage Collector. m_pending_write = true; m_pending_msg_size = size; m_p_pending_msg_buff = p_buff; NRF_LOG_INFO("FDS has no free space left, Garbage Collector triggered!"); err_code = fds_gc(); } return err_code; } /** * @brief Flash Data Storage(FDS) event handler. * * @details This function is used to handle various FDS events like end of initialization, * write, update and Garbage Collection activity. It is used to track FDS actions * and perform pending writes after the Garbage Collecion activity. * * @param[in] p_fds_evt Pointer to the FDS event. */ static void fds_evt_handler(fds_evt_t const * const p_fds_evt) { ret_code_t err_code; NRF_LOG_DEBUG("FDS event %u with result %u.", p_fds_evt->id, p_fds_evt->result); switch (p_fds_evt->id) { case FDS_EVT_INIT: APP_ERROR_CHECK(p_fds_evt->result); m_fds_ready = true; break; case FDS_EVT_UPDATE: APP_ERROR_CHECK(p_fds_evt->result); NRF_LOG_INFO("FDS update success."); break; case FDS_EVT_WRITE: APP_ERROR_CHECK(p_fds_evt->result); NRF_LOG_INFO("FDS write success."); break; case FDS_EVT_GC: APP_ERROR_CHECK(p_fds_evt->result); NRF_LOG_INFO("Garbage Collector activity finished."); //Perform pending write/update. if (m_pending_write) { NRF_LOG_DEBUG("Write pending msg.", p_fds_evt->id, p_fds_evt->result); m_pending_write = false; err_code = ndef_file_create(m_p_pending_msg_buff, m_pending_msg_size); APP_ERROR_CHECK(err_code); } else if (m_pending_update) { NRF_LOG_DEBUG("Update pending msg.", p_fds_evt->id, p_fds_evt->result); m_pending_update = false; err_code = ndef_file_update(m_p_pending_msg_buff, m_pending_msg_size); APP_ERROR_CHECK(err_code); } break; default: break; } } ret_code_t ndef_file_setup(void) { ret_code_t err_code; // Register FDS event handler to the FDS module. err_code = fds_register(fds_evt_handler); VERIFY_SUCCESS(err_code); // Initialize FDS. err_code = fds_init(); VERIFY_SUCCESS(err_code); // Wait until FDS is initialized. while (!m_fds_ready); return err_code; } ret_code_t ndef_file_update(uint8_t const * p_buff, uint32_t size) { ret_code_t err_code; ndef_file_prepare_record(p_buff, size); // Update FLASH file with new NDEF message. err_code = fds_record_update(&m_record_desc, &m_record); if (err_code == FDS_ERR_NO_SPACE_IN_FLASH) { // If there is no space, preserve update request and call Garbage Collector. m_pending_update = true; m_pending_msg_size = size; m_p_pending_msg_buff = p_buff; NRF_LOG_INFO("FDS has no space left, Garbage Collector triggered!"); err_code = fds_gc(); } return err_code; } /** * @brief Function for encoding the NDEF text message. */ ret_code_t assettag_msg_encode(uint8_t * p_buffer, uint32_t * p_len) { /** @snippet [NFC text usage_2] */ ret_code_t err_code; /* Create NFC NDEF text record description in English */ NFC_NDEF_TEXT_RECORD_DESC_DEF(nfc_en_text_rec, UTF_8, en_code, sizeof(en_code), en_payload, sizeof(en_payload)); /** @snippet [NFC text usage_2] */ /* Create NFC NDEF message description, capacity - MAX_REC_COUNT records */ /** @snippet [NFC text usage_3] */ NFC_NDEF_MSG_DEF(nfc_text_msg, 1); /** @snippet [NFC text usage_3] */ /* Add text records to NDEF text message */ /** @snippet [NFC text usage_4] */ err_code = nfc_ndef_msg_record_add(&NFC_NDEF_MSG(nfc_text_msg), &NFC_NDEF_TEXT_RECORD_DESC(nfc_en_text_rec)); VERIFY_SUCCESS(err_code); /** @snippet [NFC text usage_5] */ err_code = nfc_ndef_msg_encode(&NFC_NDEF_MSG(nfc_text_msg), p_buffer, p_len); return err_code; /** @snippet [NFC text usage_5] */ } /** * @brief Function for encoding the NDEF text message. */ ret_code_t accept_msg_encode(uint8_t * p_buffer, uint32_t * p_len) { /** @snippet [NFC text usage_2] */ ret_code_t err_code; /* Create NFC NDEF text record description in English */ NFC_NDEF_TEXT_RECORD_DESC_DEF(nfc_en_text_rec, UTF_8, en_code, sizeof(en_code), en_accepts, sizeof(en_accepts)); /** @snippet [NFC text usage_2] */ /* Create NFC NDEF message description, capacity - MAX_REC_COUNT records */ /** @snippet [NFC text usage_3] */ NFC_NDEF_MSG_DEF(nfc_text_msg, 1); /** @snippet [NFC text usage_3] */ /* Add text records to NDEF text message */ /** @snippet [NFC text usage_4] */ err_code = nfc_ndef_msg_record_add(&NFC_NDEF_MSG(nfc_text_msg), &NFC_NDEF_TEXT_RECORD_DESC(nfc_en_text_rec)); VERIFY_SUCCESS(err_code); /** @snippet [NFC text usage_5] */ err_code = nfc_ndef_msg_encode(&NFC_NDEF_MSG(nfc_text_msg), p_buffer, p_len); return err_code; /** @snippet [NFC text usage_5] */ } /** @} */
You will see there are two very similar functions:
ret_code_t assettag_msg_encode(uint8_t * p_buffer, uint32_t * p_len);
andret_code_t accept_msg_encode(uint8_t * p_buffer, uint32_t * p_len)
These functions display different messages depends on whether the secret code was sent to not.
Building the virtual keypad Android app
For my proof of concept I wanted to keep things as simple as possible.
As such, I decided to use MIT App Inventor to develop my Android app as it provides a very simple Near Field communication component that will read and write NDEF text messages.
In fact this limited NFC functionality is ideal for my purposes because if the app detects say an NDEF URL message, which I do not need, it simply ignores it and my mobile phone then triggers an alternative NFC enabled app to read this message.
The only logic required is determining when to switch from NFC read to NFC write and vice versa.
The screen design is very basic in that all I need is a table to mimic a keypad. Here I simply used buttons.
Then it was on to testing. Now NFC you cannot use emulate mode. This applies to all IDE's. including Android Studio. You have to build your firmware and side load it onto the phone using the adb install
command in your computer terminal.
Demonstration
The first demonstration shows how you can overwrite any NFC write event data so that only an ID value is visible on future NFC read events. The video also shows how the firmware checks the written data to see is a secret keyword has been entered.
The second demonstration shows the Android app in action.