Skip to content

16bit handles, new GATT interface, async events

Pre-release
Pre-release
Compare
Choose a tag to compare
@trond-snekvik trond-snekvik released this 15 Oct 13:12
· 719 commits to master since this release

The v0.7.0 is the largest update to the bcast-mesh since the initial release almost a year ago.
Bringing several fundamental changes to the core functionality of the mesh, the update should allow for
new usage scenarios and be able to provide better support the existing applications, without forcing
too many big changes. The biggest changes coming with v0.7.0 are:

16bit handles and handle subsets

Based on feedback from mesh-users, we saw that one of the most significant restrictions in the framework
was the number of available handles, and the issues related to scaling the handle space. Up until now,
the bcast mesh has been enforcing a hard limit of 155 handles in a mesh-network, but in practice, we've seen
that both bandwith and memory restrictions have resulted in significant performance problems with
as little as 50 handles.

To combat these problems with scaling, v0.7.0 introduces two major changes to the handle-value system:

  • We've extended the handle range from 155 to 65535 handles - using 16bit handles.
  • Each device now only keeps track of and rebroadcasts a subset of the handles in the mesh

The subset of handles is managed by two caches: the handle cache and the data cache.
The handle cache keeps track of the version number of each handle, and allows the device
to decide whether an incoming handle value packet is new or old. The data cache keeps
track of the retransmissions by storing the current data for each handle, and and timing
related parameters for that particular handle (the Trickle instance).

The handle cache entries are significantly smaller (in memory) than the data cache entries,
and are also more important for correct behavior. Because of this, the handle cache has to be
larger than the data cache (enforced at compile time). The most recently updated handle
cache entries contain a link to a data cache entry, holding the retransmission data for that
handle. As the data cache fills up and overflows, the least recently updated handles
are discarded first, and the "oldest" handles stop retransmitting. Similarly, the least
recently updated handles are the first to be discarded from the handle cache. This is
analogous to classic LRU-caching schemes.

As the cache sizes may be configured by the application (by overriding the RBC_MESH_DATA_CACHE_ENTRIES
and RBC_MESH_HANDLE_CACHE_ENTRIES #defines in rbc_mesh.h in your compiler), the memory and
bandwidth usage can be controlled by the application. For applications utilizing a low number of
handles, the mesh will behave as it always have, as the cache may fit all values in all devices.

While the cache typically follows the LRU-scheme, there is an option to override this behavior.
By setting the "persistent" flag of a handle, that particular handle may never fall out of the
cache (both handle and data cache). It is strongly recommended that a device that intends to
update a value in the future keeps that value as persistent in their cache, as an update to a
value that the device doesn't know the version of is likely to be suppressed by neighbor devices
which keep the original, higher version number. It is also important that the cache is sized to
handle all the persistent values.

If the application attempts to read values that are no longer present in the cache, the call
always returns with NRF_ERROR_NOT_FOUND.

Serial interface update

As a response to the changes in handle count, the mesh serial interface has been updated
to fit the new format. The arduino-implementation of the application controller has also
been updated to fit these changes. See the
serial interface documentation for details.

Spec-conformant packet format

The mesh has been assigned the 16bit Service UUID 0xFEE4 for this release, moving away from the
previous 128bit UUID. With this feature, the mesh is able to use a BLE-core spec compliant message
format, a feature that has been employed for v0.7.0. The mesh packets now use a proper
<AD-len - AD-type - data> structure, as defined by the GAP specification. The AD-type used is
the "Service data" (0x16), with the service UUID being 0xFEE4. While this adds some overhead to
the packets (and reduces payload size), we think it's a valuable addition, as the mesh data may
be read from any Bluetooth 4.0 compliant scanner, and regular advertisers may inject packets
without any changes to link-layer firmware. While we still recommend using the GATT interface
for accessing the mesh from Smartphones or other applications, this opens up possiblities
for any device to be an active part of the mesh. Read more about the packet format in the
"how it works"-document.

New GATT interface

As the number of handles grew, the GATT interface had to change. The Mesh service will no longer
contain a single characteristic per handle-value, but rather just one characteristic for data
access. This new characteristic follows a specific format, and acts as a two-way
transport medium for mesh access. The GATT handling module now has a new name as well,
mesh_gatt. Read more about the syntax of the new mesh characteristic in the
"how it works"-document.

Async event handling

We've seen some performance issues coming from the way events are given to the application.
The main problem is that by sending them inline as a callback to an event handler function,
the mesh-context is blocked for an unknown amount of time, leading to overflowing buffers,
poor bandwidth utilization and unexpected behavior. To change this, we're moving to
asynchonous event passing; events are now queued up in a FIFO-manner from the framework,
and the application has to pop them off the event queue with the rbc_mesh_evt_get()
function. In the examples, this is done in the main while-loop, in combination with the
Softdevice sleep function sd_app_evt_wait(). This methodology is similar to the
way the Softdevice passes events, and we think it improves overall consistency.

Zero-copy for mesh packets

The final major change is the way packet data is handled internally. Instead of creating several
copies of the packet memory for the internal module, and in addition force the application to
do a copy of any data they want to keep, the framework now passes the same data around, and never
duplicates memory. This includes the application, and this improvement has one additional implication to the
way you have to handle events. To let the mesh-framework be able to know when it may safely free
packet memory for other purposes, the application is required to call
rbc_mesh_packet_release(uint8_t* p_data) with the data pointer in the mesh-event as a parameter
after it is finished processing the event. Failure to do so will result in a NRF_ERROR_NO_MEM
event from the framework to the app_error_handler() callback. The release-function will accept
any p_data from the mesh (including NULL), and we recommend calling this for all events,
regardless of event type.

This change includes removing the mesh memory from the GATT server alltogether, and there
is no longer any need for adjusting linker-maps or heap-size if you want extensive amounts
of handles; only the aforementioned #defines for cache sizes.

Misc changes

There are some additional minor changes:

  • The issue #44 hotfix has been pushed into the sdk-8-branch.
  • Fix for issue #45
  • TX events are now posted after the mesh has transmitted the message, and contains a pointer to
    the transmitted data.
  • The rbc_mesh_init function now has a lfclk-field, in which you should supply the same clock-parameter
    as given to the sd_softdevice_enable-function (or SOFTDEVICE_HANDLER_INIT if you're using the softdevice-handler).
    This helps the mesh adjust for clock drift when calculating timeslot lengths.