
Bluetooth Mesh with four EBYTE E73-TBB Development Boards based on Nordic nRF52832 Microcontroller
Bluetooth Mesh with nRF52 and Apache Mynewt
Suppose we have four light bulbs in our home, each with its own on/off switch…

Let’s make them smart… With Wireless Remote Control!

Flipping the on/off switch of the first bulb should switch the other bulbs on and off. So the first on/off switch acts like a Master On/Off Switch for our entire home.
How shall we implement this?
We could do this with WiFi… Assuming that we have good WiFi coverage at every corner of our home. There’s a simpler solution… Bluetooth Mesh!
With Bluetooth Mesh there’s no need for a shared network like WiFi. Each node in the mesh may relay messages to other nodes in the mesh. So our bulbs could relay on/off messages to one another all around the house… Maybe outside the house too! (Think Christmas lights)
That’s exactly what I have created here… A Master On/Off Switch for four nRF52 wireless nodes, connected via Bluetooth Mesh…
Master On/Off Switch for four nRF52 wireless nodes, connected via Bluetooth Mesh. The nRF52 boards are powered by a USB charger connected to the Micro USB ports.
Now Bluetooth Mesh is incredibly complicated and difficult to grasp… So I’ll explain it in small pieces (while I’m learning it myself!)
In this tutorial we’ll set up the above nRF52 mesh network, step by step, without any coding! During the setup we’ll encounter mesh concepts (like Models, Elements, Groups, …) We’ll learn them only when we meet them. (The advanced concepts shall be deferred to the next tutorial.)
The complete source code may be
                  found in the mesh branch of this repository…

EBYTE E73-TBB Development Board (based on nRF52) provisioned into a Bluetooth Mesh by running meshctl on Raspberry Pi 4
Hardware Needed
Get ready the following hardware to build our Bluetooth Mesh…
- One or more nRF52 Development Boards (I used the $8 EBYTE E73-TBB Development Board)
 - ST-Link v2 USB Programmer (under $2, check my article)
 - Raspberry Pi 3 or 4 (Older models may work too. I tested on Pi 4)
 
This is the same setup that I have explained in my previous article “Coding nRF52 with Rust and Apache Mynewt on Visual Studio Code”
Install Raspberry Pi
To set up the Bluetooth Mesh network, we need to use a Raspberry Pi to talk to the nRF52 boards wirelessly and add them one by one to the mesh… This is called Mesh Provisioning.
We’ll be using a tool called
                  meshctl to perform the provisioning on our Pi. (Why Pi? Because
                  meshctl doesn’t run on Windows and macOS.) meshctl is part of the open-source BlueZ suite of Bluetooth utilities.
Follow
                    the instructions here to install meshctl on your Raspberry
                  Pi. (Yes BlueZ is preinstalled on Pi but it lacks the meshctl
                  utility.)
Unfortunately we need to rebuild
                  the Pi kernel (as explained in the instructions)… Because meshctl needs AEAD-AES_CCM encryption, which runs as a secure
                  service in the kernel. This may take an hour or more to complete.
Install nRF52 Boards
Follow
                    the instructions here to flash the bootloader and sample application (NimBLE blemesh_models_example_2)
                  to your nRF52 boards. If the boards have been previously provisioned, click Terminal → Run Task → Erase Flash to erase the previous provisioning
                  info (stored in Flash ROM), then re-flash the bootloader and application.
Connect the first nRF52 board to
                  your computer via ST-Link. Start a debug session, click Continue
                  for all breakpoints. Observe the Console Log.
Keep your nRF52 Board close to your Raspberry Pi. Remember we’ll be doing the Mesh Provisioning wirelessly, over the crowded 2.4 GHz airwaves, so the provisioning works better when the devices are close together.
Provision First nRF52 Board
On our Raspberry Pi, enter these commands…
cd ~
cp bluez-5.50/mesh/local_node.json bluez-5.50/mesh/prov_db.json .
                The cp command is only needed for provisioning the
                    first nRF52 board. What’s prov_db.json?
As we provision the mesh, meshctl will remember the details of our mesh… security keys, node
                  addresses, services in each nodes. These details will be saved into prov_db.json when each nRF52 node has been provisioned.
When we provision the second,
                  third, fourth, … nRF52 nodes, meshctl will read the mesh details
                  from prov_db.json so that it can add the nodes to be existing
                  mesh.
Now enter…
meshctl
                Press Enter to see the [meshctl]#
                  prompt.
Our first nRF52 board is powered
                  on and ready to be provisioned… But meshctl has no clue how to
                  contact the board. Let’s give meshctl some guidance. Enter…
discover-unprovisioned on
                This enables meshctl to discover any nodes that have not been provisioned…
                  Because unprovisioned nodes will broadcast their unprovisioned status to the airwaves (like a beacon).
                
In a while we’ll see this…
SetDiscoveryFilter success
Discovery started
Adapter property changed
[CHG] Controller DC:A6:32:2C:70:F1 Discovering: yes
  Mesh Provisioning Service (00001827-0000-1000-8000-00805f9b34fb)
   Device UUID: dddd0000000000000000000000000000
   OOB: 0000
[NEW] Device 09:10:C7:7C:DC:8F nimble-mesh-node
                Our nRF52 board has been found! (If you don’t see this, move the nRF52 board closer to your Raspberry Pi)
Take note of the discovered
                  Device UUID dddd0000000000000000000000000000
Now we tell meshctl to provision this new node into our mesh. Enter…
provision dddd0000000000000000000000000000
                If the provisioning goes well we should see this prompt…
Request ASCII key (max characters 6)
[mesh] Enter key (ascii string):
                (Move the devices closer if you don’t see this)
This is a security check, to be sure that we are authorised to provision the node into the mesh. How do we find the secret key?
Switch over to the Visual Studio Code Debugger. Check the Console Log of our nRF52 board. We should see this message…
OOB String: RWLDWY
                Go back to meshctl and enter exactly what you see next to OOB String (assuming
                  that you see RWLDWY)…
RWLDWY
                Our provisioning is almost done!
Unicast Address
In seconds we should see the provisioning completed for our first mesh node…
Provision success. Assigned Primary Unicast 0100
                On the Console Log of our nRF52 board we should see a matching message…
Local node provisioned, primary address 0x0100
                What is this
                    Primary Unicast Address 0100?
Each node in our mesh will be
                  assigned a permanent address (in hexadecimal): 0100, 0102, 0104, 0106, … Since this is the first node, it’s assigned address 0100.
(What happened to 0101, 0103, …? I’ll explain in
                  the next tutorial)
This is called a Unicast Address, the direct address for the node… “Got something to say to this node? Call this direct number!”

Unicast Addresses for our Mesh Nodes
(Later we’ll see a different kind of address, a Group Address, that we may use to contact multiple nodes.)
Unicast Addresses are stored in
                  prov_db.json of our Raspberry Pi, also in the Flash ROM of the
                  nRF52 board (so the board won’t forget its assigned address).
If we ever need to re-provision
                  this node, we will have to run the Terminal → Run Task → Erase Flash command in Visual Studio Code to
                  erase the Unicast Address stored in the Flash ROM.
Configure the nRF52 Node
Now that we have provisioned the
                  node, let’s configure it. Enter into meshctl…
menu config
                meshctl shall now dial up the new node at 0100 to give it a makeover. Enter…
target 0100
                Let’s talk secrets. We can’t let
                  anyone manipulate our nodes, so we need to lock our nodes with a common secret Application Key or AppKey. Enter into meshctl…
appkey-add 1
                Where 1 refers to the AppKey with index 1 from prov_db.json…
"appKeys":[
    {
      "index":0,
      "boundNetKey":0,
      "key":"4f68ad85d9f48ac8589df665b6b49b8a"
    },
    {
      "index":1,
      "boundNetKey":0,
      "key":"2aa2a6ded5a0798ceab5787ca3ae39fc"
    }
  ],
              Generic On/Off Model, Server and Client
Now that we have loaded the AppKey, we are ready to lock our new node, part by part. Recall that we have two parts in our nRF52 node, the Switch and the Light…

In Bluetooth Mesh lingo this setup is called a Generic On/Off Model… We only remember the on/off state of the Switch and the Light.
The Switch controls the Light. So we call the Switch the Server, and we call the Light the Client.
Let’s lock the Switch, a.k.a.
                  On/Off Server. Enter the into meshctl…
bind 0 1 1000
                This locks Element #0 using AppKey #1 (remember
                  our key?) for Model #1000.
Today we won’t be going combing
                  the entire Periodic Table… We’ll limit ourselves to a single Element: Element #0. (More about Elements next time)
Model #1000… Wow have we got thousands of Models squeezed into our tiny
                  workspace? (Like Project Runway?)
No, each Model Server/Client is
                  designated a fixed number by the creators of Bluetooth Mesh… Model #1000 is the official designation for Generic
                    On/Off Server a.k.a. BT_MESH_MODEL_ID_GEN_ONOFF_SRV
                  a.k.a. The Switch.
So this command locks the Switch with our AppKey. Once it’s locked, others may access the Switch securely, as long as they got the right AppKey.
In case you’re curious about the
                  other Model numbers: https://github.com/apache/mynewt-nimble/blob/master/nimble/host/mesh/include/mesh/access.h
                
For completeness, let’s lock and
                  expose our Light as well. Enter into meshctl…
bind 0 1 1001
                This locks (and exposes) Element
                  #0 (only one element today) with AppKey #1 (yes the same) for Model #1001.
If we ask the creators of
                  Bluetooth Mesh, they’ll say that Model #1001 is the Generic On/Off Client alias BT_MESH_MODEL_ID_GEN_ONOFF_CLI alias The Light. So our Light has
                  been locked for secure access.
Generic Level Model, Server and Client
Our Switch and Light are ready to
                  be used! But let’s strut some more fabulous Models down our tiny runway. Enter into meshctl…
bind 0 1 1002
bind 0 1 1003
                Same thing as before, but Model
                  #1002 is a Generic Level Server
                  (BT_MESH_MODEL_ID_GEN_LEVEL_SRV) and Model #1003 is a Generic Level Client (BT_MESH_MODEL_ID_GEN_LEVEL_CLI).
What’s a Generic Level? Why not just use Generic On/Off?
Sure… if we’re happy with binary states like On and Off, Black and White. But if we’re into Fifty Shades Of Grey (think dimmable lights), then we need a Generic Level to express levels of brightness (“50% brightness for this light”)
So many Models… Will this runway ever end?
Yes… eventually. Our sample
                  application defines a number of Bluetooth Mesh Models. But today we’ll learn only the simplest On/Off
                  Model. The Models are defined here: https://github.com/lupyuen/stm32bluepill-mynewt-sensor/blob/mesh/apps/my_sensor_app/src/device_composition.c#L2691-L2767
                
Publish and Subscribe
Recall from the video that we had a Central Switch controlling the other Lights wirelessly. How did the on/off state of our Central Switch propagate through the airwaves to the other Lights?
With a little Bluetooth Mesh magic called Publish and Subscribe!
Our Central Switch publishes its on/off state to the mesh. Then each Light subscribes to this on/off state to receive updates.

Let’s understand this better by
                  doing a simple Publish and Subscribe. Enter into meshctl…
sub-add 0100 c000 1000
                This creates a new Subscription
                  that will monitor updates to Unicast Address 0100 (our first
                  node). The Subscription will listen to Group Address c000 for
                  updates to Model #1000 (our Switch, the Generic On/Off Server).
                
What is this Group
                    Address c000?
In a while we’ll see that c000 is the Group Address that our Switch will use to publish on/off
                  updates.
Yes we have only one node, but it’s perfectly OK for the node to subscribe to itself. This enables the Light inside our node to subscribe to updates from the Switch that belongs to the same node. Flip the Switch, and the Light changes!
Let’s create another Subscription
                  to reinforce the Publish/Subscribe concept. Enter into meshctl…
                
sub-add 0100 c000 1002
                This creates a new Subscription
                  that will monitor updates to Unicast Address 0100 (same node).
                  The Subscription will listen to Group Address c000 (same
                  address).
But this time we’re listening for
                  updates to Model #1002 (our Generic Level Server). Why would we
                  do this? So that the first node can propagate its Brightness Level to other nodes and achieve
                  all-round Fifty Shades of Grey (actually 65,536 shades).

Publish On/Off Status and Brightness Level
To complete the node
                  configuration let’s publish the On/Off Status. Enter into meshctl…
pub-set 0100 c000 1 0 5 1001
                This creates a new Publication
                  that will notify its subscribers of updates to Unicast Address 0100 (the first node).
The updates shall be published to
                  Group Address c000 (remember this?). We may pick any Group
                  Address from c000 to ffff.
                
For security, we shall lock the
                  Publication with AppKey #1. The numbers 0 and 5 refer to the Publish
                  Period and Publish Retransmit Count (which we won’t cover today, but retransmits are really helpful).
                
Updates to the On/Off Status
                  shall be channelled to the Light, which is Model #1001 (our
                  Generic On/Off Client).
Lastly we publish the Brightness Level by entering…
pub-set 0100 c000 1 0 5 1003
                Everything is the same, except
                  for Model #1003, the Generic Level Client that can receive
                  updates for 65,536 levels of brightness. (No more shady business today I promise!)
                
Test Publish and Subscribe
Enough provisioning and
                  configuring for our first node… Let’s test it! In meshctl enter…
                
back
menu onoff
target 0100
                Remember that Generic On/Off is a
                  standard Model in Bluetooth Mesh. So meshctl will let us send
                  On/Off commands easily (via the onoff menu) to test our node.
                
As for the target… Which node shall we be testing? 0100 of course. Enter into meshctl…
onoff 0
                This sets the On/Off Client Status to Off. The nRF52 application has been programmed to show the On/Off Client Status on the LED, so this flips the LED Light off. (Check the LED!)
Verify the On/Off Client Status by entering…
get
                You should see value 0…
On Off Model Message received (1) opcode 8204
00
                The nRF52 Console Log should show the LED Light flipping off…
power-> 0, color-> 0
                Now watch what happens when you enter this…
onoff 1
get
                Yes we are now flipping our nRF52 LED Light on and off wirelessly via our Raspberry Pi!
Try the same thing you see in the video at the top of this article… Press the buttons on the nRF52 board one at a time. Pressing the first button should flip the Light on, pressing the second button should flip the Light off.
The nRF52 application has been programmed such that pressing the buttons will set the on/off state of the On/Off Server (the Master Switch).
Remember that the LED Light is an On/Off Client that subscribes to the server… Hence the LED Light will also flip on and off.
This is Publish and Subscribe in action!
When we have finished testing,
                  exit meshctl by entering…
exit
                Always exit meshctl before powering down any mesh node. Otherwise meshctl may hang while attempting to exit. (And you would need to
                  reboot your Raspberry Pi to release the locked Bluetooth driver)
To connect to the mesh network in
                  future, just launch meshctl and enter connect. All the provisioning details are stored in prov_db.json,
                  so meshctl knows exactly how to connect to the mesh. You may
                  power up the nRF52 board by disconnecting it from ST-Link and connecting a USB Charger to the Micro
                  USB port. Here’s a video demo…
Connecting to Bluetooth Mesh with meshctl. The nRF52 board is powered by a USB charger connected to the Micro USB port.
Provision, Configure and Test Node #1
Here’s a recap of the steps to provision, configure and test our first mesh node…
Provision, configure and test Node #1. From https://github.com/lupyuen/stm32bluepill-mynewt-sensor/blob/mesh/logs/provision-mesh.log
Here are my meshctl Log and Console Log for provisioning
                  the first node.
Let’s complete the setup for the remaining three nodes in our mesh…

Our Bluetooth Mesh with four nodes
Provision, Configure and Test Nodes #2, 3 and 4
The mesh demo application works with two or more nodes. Here are the steps for setting up the second, third and fourth nodes.
Follow the instructions in this article (“Flash The Firmware”) to flash our application to the second, third and fourth nRF52 boards.
Before setting up the second mesh node, stop the Visual Studio Code Debugger and disconnect the first node from ST-Link.
Connect the second node to
                  ST-Link and start the debugger. Press Continue at all
                  breakpoints. Don’t worry, the mesh will run fine without the first node, because meshctl has all the mesh details in prov_db.json
                
Enter these commands to provision, configure and test Node #2…
Provision, configure and test Node #2. From https://github.com/lupyuen/stm32bluepill-mynewt-sensor/blob/mesh/logs/provision-mesh2.log
Here are my meshctl Log and Console Log for provisioning
                  the second node.
Now this looks odd… Can you spot
                  the problem? (Remember that 0102 is the Unicast Address for Node
                  #2)
sub-add 0102 c000 1000 — Subscribe to On/Off Updates at Group
                  Address c000 — Publish On/Off
                  Updates at Group Address 
pub-set 0102 c000 1 0 5 1001c000
Within Node #2 it makes sense to publish and subscribe On/Off updates locally, so that pressing the buttons on Node #2 will flip the Node #2 LED Light on and off.
But Group Address
                  c000 is already used by Node #1 to publish
                    On/Off updates for the buttons on Node #1!
Hence when we press the buttons on Node #2, the updates will be propagated to Node #1… The buttons on Node #2 also work as the Master Switch!
This fix for this is… left as an exercise for the reader. For now we’re using a simple setup that has the unintended effect of making all nodes the Master Switch. Now for Node #3…
Stop the debugger, disconnect
                  Node #2 from ST-Link, connect Node #3 to ST-Link and start the debugger. Press Continue at all breakpoints.
Enter these commands to provision, configure and test Node #3…
Provision, configure and test Node #3. From https://github.com/lupyuen/stm32bluepill-mynewt-sensor/blob/mesh/logs/provision-mesh3.log
Here are my meshctl Log and Console Log for provisioning
                  the third node. Finally for Node #4…
Stop the debugger, disconnect
                  Node #3 from ST-Link, connect Node #4 to ST-Link and start the debugger. Press Continue at all breakpoints.
Enter these commands to provision, configure and test Node #4…
Provision, configure and test Node #4. From https://github.com/lupyuen/stm32bluepill-mynewt-sensor/blob/mesh/logs/provision-mesh4.log
Here are my meshctl Log and Console Log for provisioning
                  the fourth node.
Here’s my prov_db.json
                  after provisioning all four nodes. I strongly recommend that you backup your copy of prov_db.json
                  located at the pi home directory of your Raspberry Pi… Because
                  the microSD Card can get corrupted so easily. (Happened a few times while I was writing this)
If we need to re-provision any of
                  the nodes, remember to erase the Flash ROM with the Terminal → Run Task → Erase Flash command in Visual Studio Code.
And we’re done! Disconnect the fourth node from ST-Link. Power up all four nodes by connecting a USB Charger to the Micro USB port. Press the buttons on Node #1 like in this video… And the LED Light on other nodes will magically flip on and off!
Master Light Switch accomplished with Bluetooth Mesh!

What’s Next?
This is the second article in my nRF52 series of articles. The first article is here: “Coding nRF52 with Rust and Apache Mynewt on Visual Studio Code”
Bluetooth Mesh looks really useful for the upcoming PineTime Smart Watch (also based on nRF52). So you can discover which of your friends and family members are in close range (assuming they are wearing the watch too).
Can’t wait to install Bluetooth Mesh on PineTime! Here’s my review…
You may have noticed that the nRF52 application includes the Embedded Rust runtime. I’ll be using Rust Macros to eliminate the repetitive C code used for building Bluetooth Mesh.
Bluetooth Mesh programming doesn’t need to be so complicated… Stay tuned for the simpler, safer Rust version!
References
Our sample application comes from the open-source Apache NimBLE project, which is described here…
Here’s an excellent tutorial on Bluetooth Mesh based on Zephyr embedded OS…
Another tutorial based on nRF52 and Nordic SoftDevice…
Bluetooth SIG has a series of high-level articles about Bluetooth Mesh…
The official Bluetooth Mesh specifications are here…