The IAsyncTask interface from the SDK and implementing real-time non-blocking communication

29 views
Skip to first unread message

Kalin Nakov

unread,
Sep 25, 2014, 4:21:20 AM9/25/14
to
One device per serial port vs. many devices for one serial port.
Implementing the communication to a single modem or a single GPS chip when only one device is connected to a serial port is stright forward, easy to use blocking operation like this:

gsm.send("AT+CIMI");
String id = gsm.readLine();
String response = gsm.readLine();
if ("OK".equals(response))
//...

In the case of the Vehicle Pi though, it's not that simple. The reason is that we have GPS, GSM, accelerometer, thermometer, voltage meter, GPIO, security chip, EEPROM, ECU connectivity and other sensors that can report data at any time. A blocking read operation is definitely not enough, because while waiting for the GSM to respond, the accelerometer can report a dozen of acceleration changes. How to capture all events in real-time and since not miss a single message? Here is a sample output with an obvious explanation how the blocking read may break the response of the GSM modem from the above example:

AT+GSM=AT+CIMI --> request
+TEMP=12.3 --> thermal sensor
+CPU=BATTERY:10.2 --> voltage sensor
+GSM=123454345345543445  --> reply from the AT+CIMI command
+TEMP=12.4 --> thermal sensor
+GSM=OK  --> second part of the reply from the AT+CIMI command

As you see, a blocking read waiting for the OK reply will miss the thermal and voltage sensor readings.

Maintaining state machines.
In the example above, a mix of various sensor messages is difficult to handle in one state machine. Each peripheral device that you communicate with should maintain its own state machine so that the source code is maintainable. How can this be accomplished? The IAsyncTask interface comes in handy here. It has one single method called process() which is called for every line that is printed by the Vehicle Pi. The IAsyncTask implementation must just filter out all lines that are of no interest and keep the state machine triggered only from the messages it understands. For example the GSM state machine will only trigger its state from the +GSM=... messages. You can register as many IAsyncTask implementation in the VehiclePiProtocolParser as you wish. Each async task will wrap around a specific sensor/module and maintain its own state machine (through implementation class members).

Single threaded real-time programming.
Since all messages are broadcasted and handled asynchronously, sending a command after a specific timeout becomes a challenge - how to wait without blocking the current thread? How to avoid creating another thread or use timers? The Vehicle Pi firmware addresses this through its hardware real-time clock. It has a special command to execute another command after a speciffic timeout. So, simply put, if you want to switch off the Raspberry Pi after 5 seconds, you would send the following command to the Vehicle Pi:

AT+CPU=DELAY:5000:AT+CPU=RPI:OFF

For a state machine it's really useful to trigger states based on timeouts. The DELAY command can be used to echo back custom messages to the state machine to trigger its states after certain timeouts:

AT+CPU=DELAY:5000:AT+CPU=ECHO:PING

After 5 seconds, the PING command will be echoed back on the serial port. It can trigger the next state of the state machine. This can be freely called ping-pong programming using the real-time clock of the Vehicle Pi and still maintaining a single thread main loop.

The VehiclePiDemoTCPConnectAsyncTask.java source code shows an example implementation of the IAsyncTask for the GSM.

Broadcasting custom events.
The state machines wrapped in an IAsyncTask implementation should handle only changes in the state machines and the logic for each state is better to be placed outside, as events. For example, if the GSM state machine is able to read the CIMI identifier, it can broadcast it as an event. When broadcasted, the attached IProtocolListener implementations will capture and handle it. This helps to offload the statemachine from complex logic. Let the protocol listeners combine events generated from the different sensor/module state machines and handle the complex logic like interacting with the user interface and executing heavy tasks.

Executing blocking tasks.
Sometimes its needed to execute heavy CPU-intensive tasks or tasks that wait for IO operations. Please do not call such tasks directly in the process() method of your IAsyncTask implementation or inside a broadcasted event. This will freeze the main loop and the messages received will no longer be real time (will be with wrong timestamp). To avoid this, as described in the previous paragraph, broadcast events (ParameterValue). Then when the event is captured, execite the heavy code in a separate worker thread, preferably in a pre-allocated thread pool.
Reply all
Reply to author
Forward
0 new messages