How to Choose a Counter Source for Hubble's EID Rotation on the Silicon Labs EFR32xG26
Every Hubble packet has a 32-bit ephemeral identifier (EID), which Hubble Cloud uses to determine whether it came from a registered device. If Hubble recognizes your packet’s EID, your data will reach the Hubble Network. If it doesn’t, your packet will be ignored, even if the rest of the packet is perfect.
Each Hubble device generates its EID on the fly from its device key and time counter. The time counter comes from the device’s counter source, which you can choose only once for the device’s lifetime. In this guide, you’ll learn how to choose the right counter source for your Silicon Labs EFR32xG26 Dev Kit so your board never silently drops out of the Hubble Network.
Counter Source Basics
The Relationship Between Time Counter and Counter Source
The Hubble SDK produces the time counter in hubble_counter_get(). Every time your device builds an advertisement payload, hubble_ble_advertise_get() calls that function, and the single number it returns feeds three things: the EID that goes on air, the AES key your payload is encrypted with, and the nonce that keeps the encryption safe. All three rotate together, once per EID rotation period, which Kconfig pins at 86,400 seconds and refuses to let you change.
Your counter source decides what hubble_counter_get() does. You can either choose Unix time or Device uptime via the following config flags in prj.conf, which are set at compile time:
CONFIG_HUBBLE_COUNTER_SOURCE_UNIX_TIME=y # the default
CONFIG_HUBBLE_COUNTER_SOURCE_DEVICE_UPTIME=yUnix time divides wall-clock time by the rotation period:
counter = unix_time_ms / 86,400,000The result is the number of days since the Unix epoch. It only counts up, and it never repeats.
Device uptime counts rotation periods since boot and wraps:
counter = (initial_counter + days_of_uptime) % 128It has no wall clock. The % 128 is HUBBLE_EID_POOL_SIZE, fixed in the SDK and matched by a preset pool of 128 EIDs in the Hubble Cloud.
The two counter sources primarily differ over whether your device needs to know what time it is, and how long before its identities start repeating.
Note: The terms time counter, counter source, and time source all sound very similar, but they each have different meanings. The time counter is the number itself, and it changes once a day. The counter source is the rule that produces that number, set by a compile-time choice. A time source tells the device what time it actually is (e.g. an RTC, GNSS, NTP, a phone). While choosing Unix time as your counter source requires a time source, choosing device uptime does not, which is why Hubble calls it a timeless protocol.
Set Once at Device Registration
The Hubble Cloud cannot validate your device’s EIDs unless it knows how you derive them, so you must declare the counter source when you register your device. Therefore, encryption configurations are fixed for a device upon registration and cannot be modified afterward.
This is worth pausing on if you followed our earlier guide on building and provisioning a Hubble beacon on the EFR32xG26. In the device registration step, that guide said to enter a device name and leave the remaining fields at their defaults. One of those default fields is the counter source. If you used the default counter source and changed your firmware’s counter source to device uptime, your board will silently drop out of the Hubble Network. This is because your board will generate EIDs from a 128-slot pool while Hubble Cloud still expects day numbers. The only fix is to register a new device with device uptime as your counter source.
To make sure Hubble Cloud correctly sees your beacon, follow these steps in order:
- Choose the counter source
- Register your device to match on your Hubble dashboard
- Set the config flag to match in
prj.conf - Build and flash
EFR32xG26 Caveats
The EFR32xG26 board is very good at keeping time, but it can’t learn it.
Zephyr’s board definition enables the 32.768 kHz LFXO with precision = <50>, so the low-frequency domain is specified to 50 ppm (i.e., 4.3 seconds of drift per day, roughly 26 minutes over a year). Against a rotation boundary that moves once every 24 hours, that time drift is negligible. The Hubble SDK asks for ±500 ppm or better from hubble_uptime_get(), so the xG26 has an order of magnitude in hand.
What this board lacks is a time source. It has a CR2032 holder and a two-pin external battery connector so that the board can run untethered, but nothing on it knows what day it is at power-up. hubble_uptime_get() in the sample Zephyr application is just a thin wrapper around k_uptime_get(), which starts at zero on every boot.
Thus, you need to carefully consider time acquisition and reboots when turning an EFR32xG26 board into a Hubble device.
Unix Time vs. Device Uptime
Unix Time Downsides
If you choose Unix time as your device’s time source, something has to tell this board what time it is at every boot, forever. That can go wrong in several ways:
The build timestamp is not the flash timestamp. The embed_key_time.py provisioning script writes your PC’s time into the firmware before build, not during flash. So if you build your firmware on Monday, debug for four days, and flash on Friday, your beacon will boot up thinking it is Monday. Its counter would be four days behind, leading Hubble Cloud to ignore all of your beacon’s packets.
Every reboot rewinds the clock. The starting timestamp is a compiled-in constant, so a watchdog reset, a battery swap, or a colleague pressing the reset button will all send the device back to the time at build. Unfortunately, the SDK’s nonce-reuse check misses this error because when the time counter changes, the check accepts any sequence number and treats it as the new daily reference.
A device that never sees a phone never sees a clock. The sample’s built-in time source is Current Time Service (CTS):
CONFIG_HUBBLE_BEACON_SAMPLE_USE_CTS=yWith this flag enabled, the beacon won’t transmit until another device gives it the time over BLE. This won’t be a problem if your product has a companion app nearby at all times to provide the time. It will be a problem if you plan to deploy your device to a remote location without nearby support.
Device Uptime Downsides
Where device uptime is better than Unix time in some ways, it’s also worse in others:
128 identifiers, then they repeat. A device that stays running for months will eventually cycle through its whole EID pool in 128 days and start over. This makes device uptime a worse option for maintaining privacy. Whereas a Unix time device always has a distinct time counter, a device uptime device reuses its time counters, allowing a bad actor to catalog your EIDs and find your device again months later.
Reboots reset the counter ring. At reboot, device uptime automatically gets initialized to 0. You can avoid this problem by persisting the time counter in NVS and reloading it after reboot.
The first argument changes meaning silently. This is the biggest trap for device uptime. At boot, hubble_init(unix_time, master_key) automatically initializes the uptime counter to the build-time unix_time value. This means if your build has a timestamp of 1788877800000, your board’s initial counter will be set to 1788877800000 % 128, or 64, and your device would start two-thirds of the way through its EID pool for no reason. You can work around this problem by setting time to 0, or another value you saved yourself, at every boot.
Note: Unix time has its own guard against this problem.
hubble_time_set()computes its base asunix_time - hubble_uptime_get()and returns-EINVALif that underflows, so it rejects any timestamp smaller than the device’s uptime outright.
Comparison Table
| Unix time | Device uptime | |
|---|---|---|
| Needs a time source | Yes | No |
| Time counter | Days since epoch, never repeats | 0–127, repeats every 128 days |
hubble_init() first argument | Unix time in milliseconds; 0 is an error | Initial counter; 0 is valid |
| After a reboot | Returns to whatever time it is told | Returns to the initial counter unless you persist it |
| Privacy | Best available | Linkable across a ~4-month cycle |
| Registration | Register as unix time | Register as device uptime |
Which counter source is best for you? Ask yourself: will anything in my device’s life ever tell it what time it is?
If yes (because you know your device will have a companion app, a gateway, a GNSS fix, or a provisioning fixture at the end of the line), choose Unix time and gain an unbounded time counter and better privacy.
If not, choose device uptime. Make sure to persist the time counter so a field reset doesn’t throw the device back to slot zero, or reset device uptime to 0 at boot if you plan to reflash multiple times a day.
Verify Your Counter Source
Now that you understand the two types of counter sources you can use for your Hubble device, follow the steps below to verify the counter source on your Silicon Labs EFR32xG26 board.
Assuming you’ve already turned your board into a Hubble beacon, add the following lines to your main.c file:
int main(void)
{
// Initialize to a known value. If hubble_counter_get() fails, the log
// below prints 0 instead of whatever happened to be on the stack.
uint32_t counter = 0;
// Writes the device's current time counter through the pointer.
// Returns 0 on success, or -EINVAL under Unix time when the clock has
// not been set yet -- so call this only after hubble_init() succeeds.
hubble_counter_get(&counter);
// The string is the counter source you COMPILED. IS_ENABLED() is
// Zephyr's safe test for a Kconfig bool: it evaluates to 0 when the
// symbol is undefined, so no #ifdef is needed here.
//
// The number is the time counter the device is PRODUCING -- a
// five-digit day count under Unix time, or 0 through 127 under device
// uptime. If they ever disagree, trust the number.
LOG_INF("counter source: %s, counter=%u",
IS_ENABLED(CONFIG_HUBBLE_COUNTER_SOURCE_DEVICE_UPTIME) ?
"device uptime" : "unix time",
counter);
}IS_ENABLED() is Zephyr’s safe test for a Kconfig boolean, as it returns 0 when the symbol is undefined.
Build and flash your updated firmware, and you should see something like the following in your serial terminal:

In the second output line, counter source: is the counter source you compiled, whereas counter= is the time counter your device is producing. Note that the latter value can tell you your device’s counter source on its own: Unix time yields a five-digit day count, while device uptime can only ever return a value between 0 and 127.
Next Steps
Once you choose your device’s counter source, all that remains is customizing the advertising interval and the payload rotation rate. Tune those factors to tailor your Hubble device to your exact use case. To get started, register a production device at dash.hubble.com today.
Ready to connect your devices anywhere on Earth? Get started with Hubble Network for free →