Here’s how to share data from Particle Gen 3 devices using Bluetooth or NFC


Originally published at: https://blog.particle.io/2019/06/26/get-started-with-ble-and-nfc/

When building IoT solutions, it’s often ideal to have options for connectivity, both in how devices communicate with the outside world, and between each other on a local network. For general, wide-area connectivity, Particle provides devices with support for Wi-Fi (the Argon and Photon), cellular (the Boron, Electron and E Series), and ethernet (any 3rd generation device and Ethernet FeatherWing).

For intra-device communication, Particle Mesh is a new and exciting way to set-up local networks of devices that can work together to solve a problem. As it turns out, Particle Mesh isn’t the only way that 3rd generation devices can share data over short distances.

With the latest release of the Particle Device OS, available today, it’s possible to use Bluetooth Low Energy (BLE) and Near-Field Communication (NFC) to communicate with Particle devices from mobile phones, and even other BLE and NFC-capable devices. In this post, I’ll show you how to use both the Particle BLE and NFC APIs to obtain a battery charge reading from a Particle Xenon.

Start with the BLE basics

Gen 3 devices (Argon, Boron, and Xenon) all support Bluetooth out of the box. In fact, all Gen 3 devices uses Bluetooth during the device configuration process in the Particle mobile app. Starting today, you can use the same Bluetooth Low Energy (BLE) capabilities built into Gen 3 hardware and the Particle Device OS in your own apps.

For those unfamiliar, BLE is a wireless personal area networking technology that was designed to enable communication between devices at moderate distances (~100 meters), while consuming less power than Bluetooth classic devices. This makes BLE perfect for mobile, wearables, and low-power device scenarios.

Using BLE for inter-device communication

BLE is a robust, full-featured, and an admittedly complex technology. There are a number of ways that you can use BLE to communicate between mobile apps and devices, or between multiple devices. The Particle Docs provide an exhaustive overview of BLE capabilities, device roles and APIs. There are also a number of great sample apps that illustrate the various ways you can use BLE in your apps.

For this post, I wanted to build a simple solution with two Particle devices:

These devices are not on the same mesh network, but I can use BLE to enable communication between them. The Xenon reads the current voltage of its connected LiPo through the BATT pin and converts the value to a charge percentage.

battVoltage = analogRead(BATT) * 0.0011224 / 3.7 * 100;

Advertising Battery readings from a Particle Xenon

Once I have this value saved in a global variable, I can configure my Xenon to “advertise” this value. In the context of BLE, advertising means that the device simply broadcasts the value for any in-range BLE device to read. The way you advertise is by creating an array buffer that contains our voltage data, as well as some flags, modes and type settings that are needed to ensure a proper handshake between devices. Each entry in the buffer is a byte that will be used on the receiving end to determine what kind of data the advertising device is sending, and how to read it.

The all caps values in the snippet below are enum values provided by the Device OS API. If you’re using Particle Workbench, you can inspect these values by hovering over them, or by using the “Go to definition” or “Peek definition” commands.

Viewing BLE API flag sources in Particle Workbench
void updateAdvertisingData(bool updateOnly)
{
  uint8_t buf[BLE_MAX_ADV_DATA_LEN];

  size_t offset = 0;
  
  // Company ID (0xffff internal use/testing)
  buf[offset++] = 0xff;
  buf[offset++] = 0xff;

  // Internal packet type. This is arbitrary, but provides an extra
  // check to make sure the data is your data, since we use the 0xffff company
  // code.
  buf[offset++] = 0x55;

  // Copy the battery voltage into the buffer
  memcpy(&buf[offset], &battVoltage, 4);
  offset += 4;

  BleAdvertisingData advData;
  advData.appendCustomData(buf, offset);

  if (updateOnly)
  {
    // Only update data
    BLE.setAdvertisingData(&advData);
  }
  else
  {
    BLE.setAdvertisingInterval(130);
    BLE.advertise(&advData);
  }
}

You’ll notice that I also use two bytes in the buffer to specify a company id, and a single byte to designate the packet type. All of these are arbitrary values I set for testing so I can distinguish the source of the BLE packets on the receiver side. Once I added this metadata into my buffer, I can copy in the voltage reading, and configure BLE to advertise the reading.

The updateOnly boolean allows me to use this function to do the initial setup and advertising by calling setAdvertisingInterval and advertise from my setup function, and only update the data using setAdvertisingData when called from my loop function.

void setup()
{
  battVoltage = analogRead(BATT) * 0.0011224 / 4.7 * 100;

  updateAdvertisingData(false);
}

void loop()
{
  if (millis() - lastUpdate >= UPDATE_INTERVAL_MS)
  
    lastUpdate = millis();
    battVoltage = analogRead(BATT) * 0.0011224 / 4.7 * 100;

    updateAdvertisingData(true);
}

Reading battery status from a Particle Argon

Once I’ve built and flashed the above code to my Xenon, I can add code to my Argon to read it. Since my Argon is plugged into an Adafruit TFT FeatherWing, I can display the reading from the Xenon on the screen, giving my a nice, compact, Particle-powered Battery power meter!

First, I’ll add some imports and configure the TFT screen in my setup function shown below.

#include "Adafruit_GFX.h"
#include "Adafruit_HX8357.h"

#define TFT_DC D5
#define TFT_CS D4
#define STMPE_CS D3
#define SD_CS D2
#define TFT_RST -1

Adafruit_HX8357 tft = Adafruit_HX8357(TFT_CS, TFT_DC, TFT_RST);

const size_t SCAN_RESULT_MAX = 30;
BleScanResult scanResults[SCAN_RESULT_MAX];

float lastVoltage = 0.0;

void setup()
{
  tft.begin();
  tft.fillScreen(HX8357_BLACK);
  tft.setTextSize(3);
  tft.setCursor(30, 20);
  tft.setTextColor(HX8357_WHITE);
  tft.println("Battery Reading");
}

Then, in my loop, I’ll use the BLE APIs to scan for advertising devices and put those results in a BleScanResult object. Then, I’ll loop over each record in the result and get the data sent by the advertiser.

Recall from above that this is the byte array buffer that I stuffed with some metadata and the voltage reading. On the receiver end, I’ll inspect these values to make sure they match with what I set on the receiver (like the test company id values) before copying the voltage value from the buffer into a local value.

Finally, I do a check against the last voltage value I received so that I only update the screen when the value has changed and if it’s a new value, write it to the display.

void loop()
{
  BLE.setScanTimeout(50);
  int count = BLE.scan(scanResults, SCAN_RESULT_MAX);

  for (int i = 0; i < count; i++)
  {
    uint8_t buf[BLE_MAX_ADV_DATA_LEN];
    size_t len;

    len = scanResults[i].advertisingData.get(BleAdvertisingDataType::MANUFACTURER_SPECIFIC_DATA, 
                                             buf, BLE_MAX_ADV_DATA_LEN);
    if (len == 7)
    {
      // This is the dummy company id and data type we set from the advertiser
     if (buf[0] == 0xff && buf[1] == 0xff && buf[2] == 0x55)
     {
        float voltage;
        memcpy(&voltage, &buf[3], 4);

        if (voltage != lastVoltage)
        {
          lastVoltage = voltage;
          tft.fillRect(0, 60, 240, 100, HX8357_BLACK);
          tft.setTextSize(6);
          tft.setCursor(120, 100);
          tft.print((int)voltage);
          tft.println("%");
        }
      }
    }
  }
}

Once I flash the code above, I’ll see battery readings show up on the display!

Xenon Battery Level, delivered through BLE and shown on an Argon-powered TFT

Now, let’s take a look at NFC, and extend the demo to allow a mobile phone to also read the latest battery charge level from my Xenon.

Getting familiar with NFC

Near-Field Communication (NFC) is a collection of communication protocols that enable two devices to communicate with one another over very short distances (~4 cm or less). It’s commonly used in contactless and mobile payment systems, and most modern mobile phones have an NFC reader built-in.

All Particle 3rd Generation devices contain an NFC Tag that can be programed to send links, arbitrary text, or launch apps on Android devices. For this post, I’ll update my Xenon firmware to send the battery charge when read by an NFC reader like a mobile app.

Using NFC to query devices for info

In order to use NFC with Particle 3rd Gen devices, you’ll need an NFC antenna, which is available in the Particle store. Once you have an antenna on hand, you’ll want to connect it to the NFC antenna U.FL connector on the bottom of the device. Note that this is different than the Wi-Fi, Mesh and Cellular U.FL connectors on the top of 3rd Gen devices.

Once the antenna is connected, you can update the Xenon firmware to allow the battery charge to be read via NFC. In the setup function, you’ll turn the NFC tag on, use the setText method to set a string to share with NFC readers, then call the update method to update the tag with our text.

NFC.on();

NFC.setText("Battery voltage: " + String(battVoltage, 2) + "%", "en");
NFC.update();

To ensure that the NFC tag always sends the latest battery value, you’ll also add the last two lines above to our loop function, in the interval check you’re using to update the BLE advertisement:

NFC.setText("Battery voltage: " + String(battVoltage, 2) + "%", "en");
NFC.update();

And that’s all you need to do for NFC. Once you’ve updated your Xenon, open an NFC Reader app on your iPhone or Android device and start a scan. Tap the NFC antenna on your Xenon and you’ll get a response with the latest battery reading!

For the complete code used in this example, go h here for the Xenon code and here, for the Argon code.

When should I use BLE and NFC?

In this post, I shared a simple example of how you can use BLE and NFC to enable Particle devices to share data, both with other devices and mobile phones. BLE and NFC capabilities are a great addition to the existing Wi-Fi, Cellular, and Mesh capabilities of 3rd generation Particle devices, and I encourage you to try them out for yourself. Here are a few examples of use cases where each might provide useful:

  • Use BLE when you want devices to share data with other BLE devices even when those devices aren’t connected to the same network.
  • Use BLE when you want your Particle devices to communicate with other BLE sensors like heart-rate monitors, or environmental sensors.
  • Pick NFC when you want Particle devices to share sensor data nearby mobile apps.
  • Use NFC to launch your Particle-powered mobile app experience on Android phones.
  • Use NFC to share links to docs, guides and other web-based resources related to your Particle-powered apps.

The cases above are just a few examples of the kinds of apps you build with BLE and NFC. What ideas or suggestions do you have for incorporating these technologies into your Particle-powered projects? Let me know if the forum or in the comments below the post.

5 Likes

How about reading NFC tags or Android NFC ?

The nRF52840 can only act as a tag but not as a reader.
For that it would need to provide the “carrier” signal to power the tag and initiate the communication.

an Android/iOS phone provides that signal during a "write " . So there is no NFC_read function at all?

Just as a piece of paper doesn’t need to be capable of reading what you write to it and NFC tag won’t need that either - it just needs to “accept” the written message and store it.

To accept a message you’d hook up an event handler via NFC.on() and therein you process the incoming message (e.g. store it for later read-out)

Gen3 NFC does not read anything as there is no "incoming message" capability. The callback only handles the NFC events. From the docs:

NFC (Near-Field Communication) is typically used to communicate small amounts of data to a mobile app in very close range, within a few inches.

Particle Gen 3 devices only support emulating an NFC tag. They cannot locate or communicate with tags themselves, or support protocols such as for NFC payments.

1 Like

Is reading data from a NFC phone is the works or did I just wait a year and half some something that will never work?

@magchecks, you can read FROM the Gen3 device WITH an NFC-capable phone. You cannot Read FROM an NFC-capable phone WITH a Gen3 device.

Where can i find a comparison between thread and BLE? Why prefer one over another? Specifically when aiming for a low power product?

Very cool post, @bsatrom , thank you!

One thing I am not 100% clear is this formula. Where is the 4.7 coming from?

battVoltage = analogRead(BATT) * 0.0011224 / 4.7 * 100;

I've seen the 0.0011224 factor coming up in other threads, but first time I see the 4.7.
Thanks!
Gustavo.

Thanks @gusgonnet you caught a typo! Should be 3.7 not 4.7. Assuming a 3.7 v Lipo, that * 100 gives me a (rough) % of the batt.

1 Like

super, that explains, thanks!

Nice tutorial @bsatrom !

I’m curious about what would it take to send the battery voltage and SOC broadcast out over BLE UART so you can view the data using the Adafruit Bluefruit app or the Nordic App using their UART reader.

I tried @rickkas7 UART code in the BLE tutorials but the Devices do not show up as UART enabled after I run that code like it does when I run the BLE Logging Tutorial code.

I basically want to be able to broadcast variables over BLE using the BLE UART service.

Thanks @RWB! Are you referring to the “UART Peripheral” sample as the one not working for you? Are you looking to do bi-directional UART between your peripheral and central devices so the central can trigger a voltage read on a peripheral, or just have the peripheral broadcast on an interval?

I’m basically just wanting to see code that boardcast the Battery Voltage & SOC over the BLE UART so I can see that data show up as it’s boradcast over BLE on the Adafruit Bluefruit App.

Then I could take that example code and throw different variables in there to broadcast.

Does that make sense?

Question about NFC - when I read the original blurb about what was supported I understood that it could not power an ariel loop for a passive tag. I didn’t appreciate that the NFC communication is only one way, i.e. from the Gen3 device (acting as a tag) to a NFC reader - a mobile phone app. Could someone explain a bit more about the NFC events?

Turn NFC on, optionally with a callback function.

// PROTOTYPE
int on(nfc_event_callback_t cb=nullptr);

The callback function has this prototype:

void nfcCallback(nfc_event_type_t type, nfc_event_t* event, void* context);
  • type The type of event (described below)
  • event The internal event structure (not currently used)
  • context An optional context pointer set when the callback is registered (not currently used).

The event types are:

  • NFC_EVENT_FIELD_ON NFC tag has detected external NFC field and was selected by an NFC polling device.
  • NFC_EVENT_FIELD_OFF External NFC field has been removed.
  • NFC_EVENT_READ NFC polling device has read all tag data.

Using the example that @bsatrom created I only ever get an event NFC_EVENT_FIELD_ON - which turns on the LED on D7 and NFC_EVENT_READ which enables the next reading of the battery level. Do I have to call NFC.off(); to get the NFC_EVENT_FIELD_OFF event? @ScruffR referred to callback being able to collect data, i.e. for the NFC Reader to write to the tag?

2 Likes

Hey @armor, happy to provide a bit more context. The NFC_EVENT_FIELD_OFF even should fire whenever the reader (phone) moves out of range of the tag. Calling NFC.off() turns of tag emulation on the Particle device, so you wouldn’t need to call it in that case. Are you not seeing the OFF event fire when you move your phone out of range?

Correct - the D7 LED only lights when phone near antenna and is scanned. Moving the phone away does not turn off the LED. I am using an iphone and NFC Tag app - NFC Reader behaves the same way.

Should there be a callback when there is no reader present? Wouldn’t this need some sort of active check?

Also, not being able to get any data from the mobile device (some ID?) when it has scanned the tag is really disappointing. Are you able to point me to the NFC specification?

Hey @armor I’ll pull my NDC Demo device back out and test, but as far as I understand it, its not an active check on the part of the NFC tag (which must be passive, per spec) but rather the removal of the magnetic field generated by the reader (phone) that triggered the data exchange.

As for getting data from the mobile device, to my knowledge, this is a limitation of the NFC-A design. The active device is a polling device that pulls information from a passive tag. By design, there is no capability to send information to the tag when polling. If you’re looking for something more bidirectional, I think BLE is a better fit.

I don’t have a copy of the NFC Spec, but the Nordic dev center has some additional info that may prove useful: https://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.sdk5.v14.0.0%2Fexamples_nfc.html

Thanks - the NFC UART Example is the one I would really like to see working!

The nordic documents definitely say the LED should go off when the polling device is moved away!