MIT App Inventor (AI2) is a web-based online graphical mobile application development environment for Android devices, where you can create an application by simply dragging and connecting a series of function blocks.
When researching the task, I found a lot of disparate information about how to write the Bluetooth management code for AI2. This information (some good, some wrong, and a lot repetitive) was synthesized it into a set of functions, described here, that provide a reliable communications interface to my project.
The AI2 web site has a lot of good information on how to use the product, so here I will simply focus on the Bluetooth part of the developed App.
In an ideal world, robust communications means that sender and receiver continue to communicate error free indefinitely, despite the occurrence of disruptions in the communications link. This is achieved using a combination of hardware and software elements.
As the focus is on Bluetooth (BT) comms, the radio link, control hardware and software drivers are a given. At the Arduino end this is implemented in firmware for the BT device (eg, HC-05, HC-06 or similar). The other end may be implemented in Windows, Android, iOS or another control-type device.
BT supports error correction and packet re-transmission. A faulty wireless connection will lead to an increase in the bit error rate, so error correcting packets transmit redundant information which can be used by the receiver to correct faulty packets. This significantly reduces the bit error rate and eliminates the need for repeated packets. Additionally an automatic repeat request packet re-transmission scheme can be applied where each packet is checked for errors. Re-transmission is done if packets are lost or not acknowledged, allowing for safe data transmission at the hardware and low level driver level.
In this article I am more concerned with the application level, where several techniques can be used to enhance robustness:
- Structure your data transfer. Creating structure, such as an formatted data packet, ensures that the type of data in the packet can be correctly identified. Data packets may be standardized or, more flexibly, have an indicator to tell the receiver what data it contains. Packet usually have defined start and end characters to define the packet boundary. They may also add some form of checksum ensure packet level integrity.
- Allow the devices to synchronize. The two devices need to be tolerant of getting out of synch and the must be able to re-synchronize if this happens.
Data is generally transmitted in one of two ways – a continuous stream or with a send/acknowledge protocol.
• Send/acknowledge protocols tend to self-regulate because not receiving the acknowledgement allows the sender to detect the receiver is unavailable.
• With continuous data streams, the sender does not care if the data is received. A data packet start byte is usually used to allow the receiver to know where the packet starts. To synchronize, the receiver can ignore all the data received until the start byte is detected.
- Don’t assume data will arrive together, especially with serial data streams. Data will arrive one byte after the other and what the application sees when it checks is, obviously, what has arrived up to that check point. Any application code must be able to process a packet received in multiple pieces. Practically, this means some form of buffering or a Finite State Machine implementation.
- Implement receive timeouts. A receive timeout allows the receiver to recover if the sender goes offline part way during sending. The receiver can the re-synchronize to the start of the next data packet once transmission resumes.
- Make it easy to debug. This is a practical consideration. More complexity usually means more bugs, so keep things simple. ASCII protocols are easier to debug (using a serial terminal) than binary ones (using specialized equipment or software), and small data packets are easier to analyze than big, complex ones.
The application involves an Arduino receiving control commands through a serial BT interface. The Arduino is configured to only respond to commands and data requests from the Android BT application using a simple protocol described below.
Communications between BT requester and responder are implemented in structured packets with the following request/response pattern
- <Start_Char> is the single character ‘*’ to synchronize the start of the data packet
- <Command> is an single character identifier for the action requested
- <Data> is an optional single character for data supporting <Command>
- <End_Char> marks the end of a data packet (‘~’)
A request is always followed by a response in the format
- <Start_Char> and <End_Char> are as defined above
- <Cmd> is always ‘Z’ to signify a response
- <Error_Code> is an ASCII digit (‘0’..’9′) with the result of the action
Packets of data time-out if they are not completely received within 1 second and the requester should expect a response with the same timeout period.
The AI2 Bluetooth Interface
In the spirit of robust communications, I wanted the BT application to gracefully handle errors and be as robust as AI2 would allow me to make it. Specifically, I wanted it to be able to operate if the BT functionality was turned off and when no paired devices were found, both likely issues when the application was run the first time. The comms protocol also need to have the attributes outlined in the section above.
The Android AI2 BT interface code (shown at right) is split into a number of functional blocks triggered from application events.
The first time application code is run is when the screen is initialized. This makes this the ideal spot to determine whether BT is enabled on the Android device. If it is not enabled, a call is made to an “Activity Starter” object that will prompt the user for permission to start BT.
The Activity Starter is an object that can start other Android applications, including system functions. Documentation on how this works is on the AI2 web site, although the Android API manuals need to be consulted to get details for more obscure functions like this one. This page seems to have a comprehensive list of what can be done and complements the AI2 documentation, but does not contain the necessary BT information. The BT enabling action data was found at this code snippet site.
The BT enabling Activity Starter is configured with the command “android.bluetooth.adapter.action.REQUEST_ENABLE” in the Action field.
Connecting to and Disconnecting from a paired device
Most implementations suggest using a Picker object to select the device, but this did not provide a satisfactory user experience when there are no paired devices.
A better alternative is to use a Pushbutton and to first check if there are any paired devices. If at least one is found, the Picker object is opened under code control. If not, another Activity Starter is invoked to allow the user to pair a BT device, using the Android setup application. Another bit of web sourced black magic happens when the Activity Starter object is configured with
- “android.intent.action.MAIN” as the Action,
- “com.android.settings.Settings$BluetoothSettingsActivity” as the ActivityClass, and
- “com.android.settings” as the ActivityPackage.
Once the paired device is selected and the BT object can connect, the isConnected user procedure is invoked to hide/unhide elements of the user interface (see the main code for this code block).
Disconnecting is more straightforward, as it is a simple BT disconnect followed by a rearrangement of the user interface.
Sending Request Messages
Sending messages using the simple protocol described above is easy to implement. Function requests are generally tied to screen Pushbuttons, which call the user procedure SendCommand1 or SendCommand2 (for packets containing no data or data respectively) with the appropriate message parameters.
The procedure transmits the command via the BT interface and then starts a timer object tho poll the BT interface for the response message. The Timer is a global object configured to be disabled by default and is controlled exclusively by the communications code.
Handling Response Messages
The code block to handle response messages is the most complex part of the application. In AI2, a timer event is used to repeatedly check if there are any characters to process. The timer handler implements a Finite State Machine (FSM) with 2 functions:
- Monitor if a time out has occurred in receiving the response
- Match incoming characters to the protocol definition
A few global variables are used to track the state of the FSM between invocations of the timer:
- A packet counter which is used for information purposes only
- The current state of the FSM
- The elapsed time to calculate if a timeout has occurred
- An variable to store the response error/status value
The special packet boundary characters (start/stop) are defined as variables to allow them to be easily changed globally if needed.
The first part of the event handler checks for the timeout. The handler uses 1100 milliseconds to provide some leeway with timing. If a timeout is detected the error is reported (err2Status user procedure) and the timer is stopped from executing further. This will be turned on again when the next request is sent.
The next section is a FSM parser for the expected response “*Zn~”, where n is a status/error code. Each element of the simple string is recognized in one of the FSM states and the while loop ensures all the characters received are processed every time it is invoked.
In each FSM state we either have a correct character, and move on to the next state, or we trigger an error and reset the FSM to the starting state, ready to re-synchronize to the start of the next packet.
Update 11 Nov 2017: A project that implements these concepts, with Arduino and AI2 code, can be found here.