Ruuvi Firmware – Part 17: NFC Writes

Ruuvi Firmware series part 17 intro image

In this part of the tutorial we’ll add Near Field Communication (NFC) writing from smartphone to RuuviTag. Final code of this blog post can be downloaded at Ruuvi GitHub in the ruuviblog-branch, tag 3.17.0-alpha.

Please follow part 1 of the series for details on how to clone the repository and compile the code. Final hex of this tutorial can be downloaded from the Ruuvi Jenkins.

NFC Writes

Previously we implemented NFC reading from tag in our project. Adding write to tag is relatively simple matter of adding event handling and parsing data. However, the structure of NFC messages complicates the work a bit: A single NFC message has multiple records and we have to parse the records out of the message.

Nordic Semiconductor has a NFC parsing library which does the heavy lifting for us. For the sake of simplicity we shall treat every record as a binary record. Additional bit of complexity comes from Nordic NFC driver: The data on tag is in a single buffer, and write to the buffer will modify data which is set up to reader.

Appending the NFC Driver

Our NFC driver has a placeholder function for reading received data, however up until now it has simply returned RUUVI_DRIVER_ERROR_NOT_IMPLEMENTED.

Our implemented receive function first parses the message structure to find the records, and then it parsers the first record data into a given pointer. The function keeps track of the record index and will return next record on subsequent calls until the last record is reached. When the last record is parsed the original data is restored and record counter reset.

/* Read and parse RX buffer into records. Parse records into Ruuvi 
 * communication messages.
 * Restore original data after last record has been parsed
 *
 * parameter msg: Ruuvi Communication message, received record payload is 
 *                copied into message payload field.
 *
 * Return RUUVI_DRIVER_STATUS_MORE_AVAILABLE if payload was parsed into 
 *        msg and more data is available
 * Return RUUVI_SUCCESS if payload was parsed into msg 
 *        and no more data is available
 * Return RUUVI_DRIVER_ERROR_NOT_FOUND if no data was buffered and 
 *        message could not be parsed.
 * Return RUUVI_ERROR_DATA_SIZE if received message could not 
 *        fit into message payload
 */
ruuvi_driver_status_t 
ruuvi_interface_communication_nfc_receive(ruuvi_interface_communication_message_t* msg)
{
  //Input check
  if (NULL == msg) { return RUUVI_DRIVER_ERROR_NULL; }
  // If new data is not received, return not found
  if (!nrf5_sdk15_nfc_state.rx_updated) { return RUUVI_DRIVER_ERROR_NOT_FOUND; }
  ruuvi_driver_status_t err_code = RUUVI_DRIVER_SUCCESS;

  // If we're at index 0, parse message into records
  if (0 == nrf5_sdk15_nfc_state.msg_index)
  {
    uint32_t desc_buf_len = sizeof(nrf5_sdk15_nfc_state.desc_buf);
    uint32_t data_lenu32 = sizeof(nrf5_sdk15_nfc_state.nfc_ndef_msg);
    //Skip NFCT4T length bytes with +2
    err_code = ndef_msg_parser(nrf5_sdk15_nfc_state.desc_buf,
                               &desc_buf_len,
                               nrf5_sdk15_nfc_state.nfc_ndef_msg+2, 
                               &data_lenu32);
    //Debug print records
    //ndef_msg_printout((nfc_ndef_msg_desc_t*) nrf5_sdk15_nfc_state.desc_buf);
  }

  // If there is a new message, parse the payload into Ruuvi Message.
  if (nrf5_sdk15_nfc_state.msg_index < 
     ((nfc_ndef_msg_desc_t*)nrf5_sdk15_nfc_state.desc_buf)->record_count)
  {
    // PLATFORM_LOG_INFO("Parsing message %d", msg_index);
    nfc_ndef_record_desc_t* const p_rec_desc = 
      ((nfc_ndef_msg_desc_t*)
      nrf5_sdk15_nfc_state.desc_buf)->pp_record[nrf5_sdk15_nfc_state.msg_index];
    
    nfc_ndef_bin_payload_desc_t* p_bin_pay_desc = p_rec_desc->p_payload_descriptor;
    // Data length check
    if (p_bin_pay_desc->payload_length > msg->data_length) 
    { err_code = RUUVI_DRIVER_ERROR_DATA_SIZE; }
    else {
      memcpy(msg->data, 
             (uint8_t*)p_bin_pay_desc->p_payload, 
             p_bin_pay_desc->payload_length);
      msg->data_length = p_bin_pay_desc->payload_length;
    }
    nrf5_sdk15_nfc_state.msg_index++;
    if(RUUVI_DRIVER_SUCCESS == err_code) 
    { err_code = RUUVI_DRIVER_STATUS_MORE_AVAILABLE; }
  }

  // If no more records could/can be parsed, reset buffer and message counter
  if (RUUVI_DRIVER_STATUS_MORE_AVAILABLE != err_code || 
      nrf5_sdk15_nfc_state.msg_index == 
      ((nfc_ndef_msg_desc_t*)nrf5_sdk15_nfc_state.desc_buf)->record_count)
  {
    nrf5_sdk15_nfc_state.msg_index = 0;
    nrf5_sdk15_nfc_state.rx_updated = false;
    // If tag is not writeable, restore original data
    if(nrf5_sdk15_nfc_state.configurable)
    {
      ruuvi_interface_communication_nfc_data_set();
    }
  }

  return err_code;
}

View raw

Handling Writes in the Application

Integrating the changes into our application is simple. We handle the callback from driver and schedule the data parsing in task_nfc.c. Once data is parsed, we print it out. Careful reader notices that we also feed the watchdog when data is read from tag over NFC.

void task_acceleration_scheduler_task(void *p_event_data, uint16_t event_size)
{
  // Message + null + <\r>\<n>
  char str[APPLICATION_COMMUNICATION_NFC_TEXT_BUFFER_SIZE + 3] = { 0 };
  ruuvi_interface_communication_message_t message = {0};
  ruuvi_driver_status_t err_code = RUUVI_DRIVER_SUCCESS;
  do{
     message.data_length = sizeof(message.data);
     memset(&(message.data), 0, sizeof(message.data));
     err_code = channel.read(&message);
     snprintf(str, sizeof(str), "%s\r\n", (char *)message.data);
     ruuvi_platform_log(RUUVI_INTERFACE_LOG_INFO, str);
  }while(RUUVI_DRIVER_SUCCESS == err_code || 
         RUUVI_DRIVER_STATUS_MORE_AVAILABLE == err_code);
}

ruuvi_driver_status_t 
task_nfc_on_nfc(ruuvi_interface_communication_evt_t evt, void* p_data, size_t data_len)
{
  ruuvi_driver_status_t err_code = RUUVI_DRIVER_SUCCESS;
  switch(evt)
  {
    case RUUVI_INTERFACE_COMMUNICATION_CONNECTED:
      ruuvi_platform_log(RUUVI_INTERFACE_LOG_INFO, "NFC connected \r\n");
      break;

    case RUUVI_INTERFACE_COMMUNICATION_DISCONNECTED:
      ruuvi_platform_log(RUUVI_INTERFACE_LOG_INFO, "NFC disconnected \r\n");
      break;

    case RUUVI_INTERFACE_COMMUNICATION_SENT:
      ruuvi_platform_log(RUUVI_INTERFACE_LOG_INFO, "NFC data sent\r\n");
      ruuvi_interface_watchdog_feed();
      break;

    case RUUVI_INTERFACE_COMMUNICATION_RECEIVED:
      ruuvi_platform_log(RUUVI_INTERFACE_LOG_INFO, "NFC data received\r\n");
      ruuvi_platform_scheduler_event_put(NULL, 0, task_acceleration_scheduler_task);
      break;

    default:
      break;

  }
  return err_code;
}

View raw

To test the program, we use NFC Tools to write 4 text records with “Hello world” message.

Debug terminal that includes language headers
The text data includes language header “en” and “ä” doesn’t get printed, but bytes are moving

Data is coming through nicely!

Styling the Firmware a Bit

To finish the series with something useable, we restore the dataformat 5 broadcasts and comment out the GATT. We also add watchdog feed on sent advertisements if no errors occurred.

Let’s see how the power consumption looks!

Power profile indicating that we’re at 27 μA
We’re at 27 μA

The consumption of 27 μA compares very well to 2.X branch, which consumes 29 μA with the same features. Such a small difference is within margin of error for the measurement, so we can’t say for certain if there is any true power saving.

In Conclusion

Over the last 17 weeks we have build a complete sensor beacon software from scratch with easily extendable interfaces for different sensors and boards. The work is far from complete however. Our next steps will be to build better documentation and tests as well as make sure that naming scheme and behavior of functions is consistent.

Once we can be fairly sure that the defined interfaces will be stable we can bring the firmware out of alpha and start ironing out any remaining bugs — this might take a while. In meanwhile, we’re always happy to hear about your experiences when building your project on top of Ruuvi.

This is the final weekly part of the blog, but the series will be updated every now and then.

Stay tuned and follow @ojousima and @ruuvicom on Twitter for #FirmwareFriday posts!