Development Notes
by Henry M. Walker
This page documents understandings, experiments, and sample programs in the
development of Bluetooth communication in C with the Scribbler 2 robots.
Chapter 1: Communication through low-level Bluetooth procedures and
BlueZ
The textbook, Bluetooth Essentials for Programmers by Albert
S. Huang and Larry Rudolph, Cambridge University Press, 2007, provides
detailed commentary and examples for using C to communicate over
Bluetooth.
-
All C communication in the book utilizes the freely-available and
widely-distributed BlueZ package.
-
The textbook's program, simplescan.c,
scans a local area to determine what Bluetooth devices currently are
active.
-
The textbook's program, rfcomm-client.c, sends the text, "Hello!",
to the hard-coded Bluetooth MAC address "01:23:45:67:89:AB". For use in
the context of a Scribbler 2 robot, the MAC address needed to be changed to
the specific Scribbler 2, and text needed to be adjusted to yield a
specific Scribbler 2 command.
-
The Scribbler 2 Bluetooth MAC address can be found by turning on the robot
and then using either simplescan.c
(from above) or the standard Linux utility
hcitool scan
-
Several sources from IPRE and elsewhere indicate that all Scribbler 2
commands are 9 bytes in length. For example, Keith O'Hara (then at Georgia
Tech) has
documented Fluke/Scribbler
Byte Codes.
-
Commanding the robot to beep at one or two frequencies seemed an
appropriate Scribbler 2 activity. Documentation from IPRE (e.g., with the
above link) indicates code 113 for SET_SPEAKER is the byte code
for the tone generator.
-
Parameters for the beep command (the remaining bytes in the 9-byte command)
could be determined from C++ code by John Hoare (then at the University of
Tennessee at Knoxville). In the Scribbler.cpp class,
the beep method indicates parameters include an int
frequency and an int duration (in milliseconds), based on
a _set_speaker method. The _set_speaker method indicates
byte 0 is the 113 command code, bytes 1 and 2 are the 16-bit duration,
bytes 3 and 4 are the 16-bit frequency, and byte 5 is -1.
-
Making these adjustments to the textbook program
yields rfcomm-client.c. This
program asks the robot to beep at one frequency for 2 seconds and then at a
frequency an octave lower for three seconds.
Chapter 2: Serial Communication with fopen
Email correspondence with Douglas Blank (Bryn Mawr College) and Keith
O'Hara (now at Bard College) noted that use of low-level Bluetooth
procedures with BlueZ or other packages can work fine, but would require
much detailed coding for synchronization, error handling, etc. Thus, they
suggested using a higher-level serial communication over Bluetooth.
The first attempt involved treating the Scribbler 2 as a file, designated
on MathLAN as /dev/rfcomm0. A natural mechanism for file handling
in C involves fopen with various procedures to read and write.
-
Opening /dev/rfcomm0 with fopen to write turns out to be
straightforward, yielding a file pointer FILE * .
-
Writing a 9-byte message requires some care, as formatted print commands
are not oriented toward byte-addressed data. One solution involves placing
the 9 bytes in a character array (as in the earlier experiment using BlueZ)
and then using a loop to write each byte as a single character.
-
The resulting program is
rfcomm-serial-fopen.c.
Chapter 3: Issuing Commands with open
Since file pointers to an internal struct include moderate
infrastructure and do not easily support direct byte-oriented data
communication, the next logical step seemed to use open, which
returns a port number (int) rather than a file pointer (FILE
*).
-
Using a port, byte-oriented communications are handled with write
and read. Both procedures specify a port number, a base address
for a byte-specified array, and a number of bytes to transfer. In addition
to parameters, each function returns the number of bytes actually
transferred.
-
Program
rfcomm-serial-open.c represents
the translation of the earlier tone-beeping programs to port-based
communications using open
Communication with Keith O'Hara clarified that
-
All Scribbler 2 commands (i.e., commands actually executed on the Scribbler
2 robot) are indeed 9 bytes in length, with the first byte giving the
command code.
-
Commands executed on the Fluke may have variable length, although the first
byte always gives the command code.
To illustrate, the following code (adapted from Keith O'Hara) using a
1-byte command to turn an LED on and off on the Fluke.
-
Program rfcomm-led.c illustrates how to
turn on and off an LED on the Fluke.
-
Since write requires a base address for bytes, one cannot simply
give the robot command (e.g., 116 or 't') as the data parameter. Rather,
one can use a variable (e.g., ch), load the variable with the
correct commend (e.g., 't'), and pass write the address of the
variable (e.g., &ch); or one can use a string (e.g., "t") which is
designated by its base address.
Chapter 4: Two-way commands
Many Scribbler 2 commands instruct the robot to return data. This activity
is complicated by several details:
-
Whenever the Fluke/Scribbler 2 receives a command, it echos the same
command back as its first response.
-
When data are transmitted back to the workstation, time delays are
possible.
-
Synchronization is needed to ensure that data echoed or sent from the robot
will be interpreted correctly by the C program running at a workstation.
Some initial experiments turned out to be unreliable.
-
Some documentation suggested synchronization could be attained by adding
the line
lseek(robot, 0, SEEK_CUR);
where robot is the name of open communication port. The idea,
from the literature, indicated that this would force communications to
begin at the current location with no further offset. Unfortunately,
several attempts with this approach failed; data received may or may not
match with what was expected from the robot. Altogether, in this
context, lseek did not seem to provide the desired
synchronization.
-
Since the Fluke/Scribbler 2 echos all commands, another approach involved
pairing a write with a read to obtain the echo of the
command sent. A simple sequence might be
char message [9] = ...
write (robot, message, 9);
read (robot, message, 9);
The 9 messages sent to the robot with write would be retrieved
by read. In practice, however, the read might not
receive all 9 bytes; read is not blocking, so read
returns whatever transmission might have been received at the time of
execution.
To obtain reliable communication, it seems necessary to count the number of
bytes received, until an anticipated data set is obtained.
-
Since a C program knows the length of each command sent and the length of
the data to be returned, responses can be assured through
on-going read statements that attempt to read 1 byte at a time
from the Bluetooth port. When a byte is actually received, a counter can
be incremented or decremented, and processing can move to the next byte.
-
The
program rfcomm-serial-getname.c
retrieves the name of the robot through requests of two 8-byte character
sequences. The program counts bytes received over Bluetooth, both for each
command sent and for the data expected from the robot.
-
Program rfcomm-serial-setname.c
sets the robot's name to "abcdefghABCDEFGH".
-
This is done in two parts: Step 1 sets the first 8 bytes of the 16-byte
name, and Step 2 sets the remaining 8 bytes.
-
The original version of this program sent commands to the robot, but did
not read the echo. This orginal program worked fine in setting the robot's
name. However, if this setname program were run, immediately
followed by getname, processing in getname sometimes read
echoed commands from the earlier setname.
-
To clear echoed commands from the Bluetooth transmissions, this
refined setname both sends the relevant commands to set a robot's
name but also reads each byte of the echoed command from the Fluke.
This document may be found at
http://www.cs.grinnell.edu/~walker/bluetooth-with-c/development-notes.shtml