Bluetooth Low Energy (BLE) allows a device to make a connection to another device. In BLE terminology, a device with the central role connects to one with a peripheral role. The connection provides reliable, bidirectional data exchange between two devices.
The BLE advertising mechanism which as its name suggests is used by a device to advertise its presence can also be used to transmit a small amount of data. Bluetooth's use of (omnidirectional) radio as a medium makes it inherently a broadcast based technology. BLE advertising uses the term broadcasting in the sense that all nearby devices receive and process the packet if they are in scanning mode. Advertising uses three disparate channels (radio frequencies) to transmit data to increase the the reliability of transmission.
The original BLE advertising format was limited to 31 bytes of
AdvData data. This is now sometimes referred to as the legacy format as Bluetooth 5 introduced an extended format for longer packets, shown below.
Broadcasting data is attractive as an efficient mechanism for sensors or BLE beacons to send data to multiple nearby devices - the BLE roles here are broadcaster and observer. This can also be used for other forms of communication like simple multi-player games. However, if there is a requirement for reliable communication then the application or a library needs to add features to provide that.
Protocols like TCP check for lost packets and re-transmit these even on LANs which when lightly-loaded can be almost loss-free. The unlicensed Bluetooth spectrum is shared with other systems like Wi-Fi and is often very busy in areas crowded with people and their devices. This creates a high degree of packet loss. For advertising, a lower layer in BLE discards corrupted packets detected by a 3 byte (24 bit) CRC but it needs additional features for a sender to detect end-to-end loss and re-transmit.
Custom Advertisement Packets using ManufacturerData
AdafruitColor is an example of an Advertisement class in CircuitPython. The source file mentions how classes can be created for other applications using the
ManufacturerDataField field and Adafruit's Bluetooth company identifier (
Adafruit manufacturing data is key encoded like advertisement data and the Apple manufacturing data. However, the keys are 16-bits to enable many different uses. Keys above 0xf000 can be used by Adafruit customers for their own data.
This game uses values in that range for each field in the
Advertisement sub-classes. There is currently no registry for allocation of these values so they were chosen at random for this game.
This version of the game is a two-player game which makes the exchange of players' choices relatively straightforward. A simple approach in the face of packet loss is to send the packets lots of times with the expectation that one will get through. This is a crude form of low-performance forward error correction (FEC).
The diagram below shows how two players exchange data. Time runs downwards in the diagram. Player 1 presses their button first to transmit their choice followed by Player 2.
This diagram is not a true sequence diagram as it does not show the complete algorithm with all the timeouts, rather it's showing a specific, abbreviated example of unsynchronised players and some packet loss.
The start_scan() method returns an iterable object returning any received advertising packets in real-time until the (optional) specified
timeout value. The returned packets which are parsed into an
Advertisement class or sub-class are represented on the diagram with the dotted lines.
The advertising interval is set to the minimum value of 20 milliseconds with the hardware adding a random 0-10ms to the interval. This jitter is added to reduce the chance of a prolonged clash with another device advertising at the same interval and starting at the same time.
The algorithm is transmitting packets using start_advertising() for a specified maximum amount of time, this is shortened to a fixed duration when a packet is received from the other player. A single application-specific type of packet is used by both players which holds a text representation of the player's choice. These duration values need to be chosen to:
- allow for the time difference between players sending their choice by pressing their right button;
- provide enough time for the volume of packets sent to provide a very high probability of receiving at least one;
- keep the overall time low enough for players not to get bored or frustrated.
The final implementation uses 20 seconds for the maximum advertising duration reduced to 6 seconds when the other player's choice is received. The minimum permissible advertising interval of 20ms is used. Apple recommends this very short advertising interval in the context of a peripheral advertising for a connection in Accessory Design Guidelines for Apple Devices:
The accessory should first use the recommended advertising interval of 20 ms for at least 30 seconds.
If it is not discovered within the initial 30 seconds, Apple recommends using one of the following longer
intervals to increase chances of discovery by the device: 152.5ms, 211.25ms, 318.75ms, 417.5ms, 546.25ms, 760ms, 852.5ms, 1022.5ms, 1285ms.
This short advertising interval should only be used for brief periods particularly when multiple devices are advertising simultaneously in a coordinated fashion.
This version of the game allows multiple players to play and has a more sophisticated protocol featuring four types of message represented by different
Making the List of Players
The Advanced game first has to determine who is playing. This could be part of the configuration data but that's not a very flexible approach.
The game advertises itself once at start-up using
JoinGameAdvertisement packets. Up to eight players are shown on the display as they are discovered together with the first received signal strength indicator (RSSI) value. The names and RSSI come from the default scan response.
The RSSI isn't strictly needed but it's interesting to see the value. It provides an approximate indication of proximity and reliability. Typical values will be between
-30 for very close and
-90 for distant (~6m/20ft) and unreliable.
Exchanging Data between Players
The diagram below shows how two players exchange data. The sequence and algorithm is the same for more players. The essence of this per-round exchange is:
- Each program encrypts the player's choice and sends it in an
RpsEncDataAdvertisementmessage. When the same message is received from all of the opponents then an acknowledgement (ack) field is added or updated in the sent message.
- Each program then sends their encryption key in an
RpsKeyDataAdvertisementmessage when they have received all the encrypted values from the opponents.
- The final
RpsRoundEndAdvertisementmessage is sent. Its main purpose is to provide a reasonable assurance that the ack has been received by all for the previous message.
All of these messages also contain the round number of the game to ensure the players agree on the current round number.
The diagram below could show more players but it would get even more crowded! Time runs downward with Player 1 pressing their button, then Player 2.
Again this is not a true sequence diagram as a lot of the logic in the algorithm is not shown and it shows a particular sequence of transmissions reacting to packet loss indicated by the lightning symbols.
The arrows between the CLUE 1 and CLUE 2 are showing the BLE advertising broadcasts with a representation of the data inside each packet. The first one,
RpsEncDataAd(cipherText, round), has a value of
"xxxxxxxx:2:4:3" which is representing:
xxxxxxxx- 8 bytes of cipherText,
- a round number of
- a message sequence number of
- an ack of message
3from the previous round.
When Player 1 first transmits, Player 2 is not yet scanning for the messages. Player 1 re-transmits the message at the advertising interval. A similar thing happens when there is packet loss. The diagram shows how the ack values are only incremented when the packet is actually received.
If there were more than two players this can take a lot longer as the message needs to be received by every opponent before the next message is sent in this simple protocol.
The advertising interval is increased when there are more than four players to reduce the use and congestion on the BLE advertising channels.
Practical and efficient reliable multicast protocols are complex and difficult to design particularly in the face of high or uneven packet loss. There are lots of improvements that could be made in this protocol but this is a functioning starting point and one that can be refined in further work. Bluetooth Mesh offers some features for basic reliable multicast but this is not currently supported by CircuitPython.