DeviceGroupHelperRK
DeviceGroupHelperRK

Retrieve a device's device group from the device using a webhook

This is useful when you have a product and are using the device groups feature. Normally, use this to group related devices and control firmware releases. However, you can use this technique to read the device groups on-device, which would allow you to make decisions in device firmware based on group membership.

  • You can choose when to update groups (manually, at startup, or periodically).
  • You can then either query whether the device is in a specific group using the previously cached group list. This is fast and does not require network access so you can use it in your code liberally.
  • Or, if you prefer, you can register a notification function that will call your function with an indication that the list was updated, and when individual groups are added or removed from the previous retrieval.

Requirements

  • This is only useful for product devices, as developer devices do not have device groups.
  • Because the device needs to subscribe to an event, the device must be claimed to an account. It can be a single account used for all devices, but it must be claimed. Unclaimed devices cannot subscribe to events.
  • A webhook is required, described below. The webhook needs to have a product access token in it.
  • Retrieving the group list will require at least two data operations (request and webhook response).

Getting an access token

Since you probably will want a product access token (that allows access to a specific product only) and also a non-expiring one, you will probably want to use an oAuth client and the command line to generate one.

  • In your product, open the Authentication tab. Make sure you've selected the one in your product, not in your developer account.
Authentication
  • Use the New Client button to create a new oAuth client. Select Two-Legged Auth (Server). The Name is just for identifying it in the console, I entered DeviceGroup.
Authentication
  • Note the Client ID and Client Secret in the next screen. You'll need them in the next step.
  • Use the Particle APU to create a non-expiring product bearer token:
curl https://api.particle.io/oauth/token -u "devicegroup-5835:3138afffffffffffffffffffffffffffffff7528" -d grant_type=client_credentials -d expires_in=0
  • Replace devicegroup-5835 with the Client ID you just created
  • Replace 3138afffffffffffffffffffffffffffffff7528 with the Client Secret

You should get back something like:

{"token_type":"bearer","access_token":"0a795effffffffffffffffffffffffffffff8b5b","expires_in":0}
  • Note the access_token 0a795effffffffffffffffffffffffffffff8b5b, you'll need that in the next step.

Creating the webhook

  • In the Integrations tab for your product, create a new Webhook.
  • Set the Event Name to be the event name you've configured in the library. The default is G52ES20Q_DeviceGroup. You do not need to change it from the default.
  • Set the URL. Be sure to change 7615 to the product ID of your product!
https://api.particle.io/v1/products/7615/devices/{{PARTICLE_DEVICE_ID}}
  • Change Request Type to GET.
  • The Request Format should change the Query Parameters which is correct.
  • The checkbox Only the device that triggers the webhook should receive its response should be checked.
  • Click the disclosure triangle to expand the Advanced Settings.
Create Webhook
  • In the Query Parameters select Custom and add access_token (case-sensitive, and note the underscore) and the value is what you got in the access_token field of the curl response.
  • Leave the *HTTP Basic Auth** and HTTP Headers fields empty.
  • The Response Topic should already be {{PARTICLE_DEVICE_ID}}/hook-response/{{PARTICLE_EVENT_NAME}} and you can leave it set to that.
  • Edit the Response Template to be as follows. Be careful with the square and curly brackets; they must be exactly as shown.
{"groups":[{{{groups}}}]}
Advanced Settings

Device firmware

This is the code in the examples/1-simple directory:

#include "DeviceGroupHelperRK.h"
SerialLogHandler logHandler(LOG_LEVEL_TRACE);
SYSTEM_THREAD(ENABLED);
PRODUCT_ID(7615); // Change this to your product ID!
PRODUCT_VERSION(1);
void setup() {
.setup();
}
void loop() {
if (DeviceGroupHelper::instance().isInGroup("dev")) {
static bool notified = false;
if (!notified) {
Log.info("is in group dev!");
notified = true;
}
}
}

Digging in

You need to include the library, such as by using the Particle: Install Library function in Particle Workbench or search the community libraries in the Web IDE for DeviceGroupHelperRK. Then you can include the header file

#include "DeviceGroupHelperRK.h"

This library is only useful for products, so you must update the PRODUCT_ID macro to match your product.

PRODUCT_ID(7615); // Change this to your product ID!
PRODUCT_VERSION(1);

You must initialize the library in setup()! The following configuration retrieves the device groups at startup once connected to the cloud.

Make sure you also call DeviceGroupHelper::instance().loop() from global loop()! If you don't call the setup and loop methods, the library will not work properly. You should call the loop frequently, preferably on every loop. It returns quickly if it does not have anything to do.

void loop() {
// ...

This code checks to see if the device belongs to a specific group and logs it if it is (once). You'd probably want to do something more useful here. isInGroup() is fast and efficient and you can call it frequently. It uses the previously retrieved value and does not access the network.

if (DeviceGroupHelper::instance().isInGroup("dev")) {
static bool notified = false;
if (!notified) {
Log.info("is in group dev!");
notified = true;
}
}

With callback

Alternatively, you can register a callback to be notified when group membership changes. This checks every 5 minutes for groups. You probably don't want to do it that frequently in real life.

void setup() {
.withNotifyCallback(groupCallback)
.setup();
}

The groupCallbacks function in examples/2-notify looks like this. It only logs messages; you probably want to do something more useful here.

void groupCallback(DeviceGroupHelper::NotificationType notificationType, const char *group) {
switch(notificationType) {
Log.info("updated groups");
break;
Log.info("added %s", group);
break;
Log.info("removed %s", group);
break;
}
}

You can respond to the UPDATED notification if you want to know if the list has been refreshed.

You can also use the ADDED or REMOVED notification to know if the group membership changed. You also get ADDED calls on the first retrieval of the list.

DeviceGroupHelper::loop
void loop()
You must call loop() from global application loop()!
Definition: DeviceGroupHelperRK.cpp:25
DeviceGroupHelper::NotificationType::UPDATED
@ UPDATED
The groups were updated. Use getGroups() to get a set of all group names.
DeviceGroupHelper::setup
void setup()
You must call setup() from global application setup()!
Definition: DeviceGroupHelperRK.cpp:16
DeviceGroupHelper::NotificationType::REMOVED
@ REMOVED
This group was removed.
DeviceGroupHelper::withRetrievalModePeriodic
DeviceGroupHelper & withRetrievalModePeriodic(unsigned long ms)
Sets retrieve groups at start mode and periodically mode. This should be done before setup().
Definition: DeviceGroupHelperRK.h:85
DeviceGroupHelper::NotificationType
NotificationType
Used for the notify callback to specify what is being notified of.
Definition: DeviceGroupHelperRK.h:42
DeviceGroupHelper::withNotifyCallback
DeviceGroupHelper & withNotifyCallback(std::function< void(NotificationType, const char *)> notifyCallback)
Sets a function to be called when the group list is updated.
Definition: DeviceGroupHelperRK.h:149
DeviceGroupHelper::instance
static DeviceGroupHelper & instance()
Get the singleton instance of this class.
Definition: DeviceGroupHelperRK.cpp:8
DeviceGroupHelper::NotificationType::ADDED
@ ADDED
This group was added.
DeviceGroupHelper::withRetrievalModeAtStart
DeviceGroupHelper & withRetrievalModeAtStart()
Sets retrieve groups at start mode. This should be done before setup().
Definition: DeviceGroupHelperRK.h:76