Interfacing IoT with embedded using Node.js, part 3: Accelerometer
September 22, 2017
In "Interfacing IoT with embedded using Node.js part 2" we looked at GPIO control in our IoT interfacing demo package, the
ts7680-demo-server. Now let’s look at the package for reading the accelerometer,
service-mma8451. We'll be implementing support for
The TS-7680 board used in this project carries an MMA8451 triple-axis accelerometer chip, which is supported in Linux is via a polling driver. Unfortunately, this polling architecture severely limits the sample rate. In practice it yields 20-30 samples per second, even at the highest polling rate, compared to the raw rate of 800 Hz that the chip is capable of. A higher sample rate is possible with a custom driver like those engineered by Technologic Systems. For our purposes though, the low sample rate will be sufficient.
The default kernel shipping on the TS-7680 currently does not have MMA8451 chip support compiled in. While it will be present in future releases, this means the kernel must first be compiled and installed on the board per instructions in the TS-7680 manual.
Setting up the accelerometer
The accelerometer can be accessed via the /
dev/input/event0 special file. Each read of this file will return an integral number of events, so setting up some kind of state machine to handle partial data returns isn't necessary. However, each accelerometer sample is typically comprised of at least 4 events: one for each axis of the accelerometer, and one indicating that the data is complete.
mma8451.js implementation there are just three functions. The first to look at is assigned to the
module.exports variable, which is called when the package is first initialized. The function will starts by checking for options. If there is a string option, that will be used as the endpoint instead of the default endpoint,
Next, we send a message to the log indicating that the service was entered. While the logging framework will already log the IP address of the caller and the URL, this is an additional piece of information to indicate the request was routed correct.
Afterwards the accelerometer needs to be initialized. The accelerometer can be enabled by writing a value of
1 to the appropriate file in the sys filesystem:
The polling period should be set to 10 ms. This is the smallest value we can use for this kernel.
The scaling factor should be set to +/-2 gs, or up to two gs (19.62 m/s^2), by writing a value of
0. It is possible to set it to a higher value such as +/-4 gs (by writing a
1) or +/-8 gs (by writing a
2) if a higher acceleration range is needed at the expense of lower resolution.
Finally, call the express app to respond to GET requests from the endpoint by calling the
Server function. Append a parameter to the base supplied endpoint – express will convert the name provided into a key in the “param” object in the request object passed to our service function:
Server function will now be called to handle the HTTP requests for accelerometer data at any URL that starts with “
/accelerometer/”, or whatever endpoint is passed in the options. Any additional characters in the URL after this string will be accepted, allowing parameters to be passed. The function prototype of the handler looks like other express handlers, taking a request object (
req) and response object (
Handling HTTP requests for accelerometer data
The first thing to do in the
Server function is interpret the parameter as a number – this is the number of milliseconds the accelerometer will be sampled. Sanity check this value, allowing any positive amount of time up to one hour. In practice, such a long sample time is probably not useful, but we have to draw the line somewhere:
At this point we are committed to sending accelerometer values to the client, so go ahead and write out an HTTP header.
We will be sending plain text, one sample per line with the comma separate values of time (as number of milliseconds since the Epoch), and x, y, and z-axis values as raw accelerometer-reported integers. To begin sampling the
listen function needs to be called, which takes a callback function to which it will pass the time, x, y, and z values whenever a sample is received. Write these values directly out to the response data:
This stored the return value from
listen in the
acc variable. The return value is an object containing a function named
stop, which when called will terminate the sampling process. Call setTimeout to receive a callback in the number of milliseconds specified by the client, and when it is received call the stop function and then end the response to the client:
Now on to the
listen function, where the actual work of streaming data off of
/dev/input/event0 and turning it into samples takes place.
Streaming and sampling accelerometer data
First, declare some local variables:
fd variable will hold the file descriptor for the event file. The
t variables will hold the components of each sample until they are ready to be sent to the caller. The
state variable holds the current state of the input stream (each sample is sent as multiple events, and this variable will keep track of whenever the whole sample is received). Finally, the
ret variable will hold the return value, which will be an object containing a function to stop the sampling process.
Start by opening the event device file. Once this is done the accelerometer will begin taking samples.
Whenever a sample is taken, multiple events will be received. Node.js will take care of automatically reading data whenever it is available, so set up a callback to accept this data and process it:
chunk parameter will be a buffer containing the data read from the file. The first thing to do is read events from the chunk. Each event is 16 bytes long. In C, the structure of an event looks like this:
Data from the event device will always be available in multiples of 16 bytes, so no special logic to handle partial events is required. Here is the code to process chunks of event data:
The while loop keeps us reading as long as there is at least one chunk of data left. The index variable,
i, is used to keep track of the offset in the chunk that is being processed next. The Buffer functions
readUInt16LE are used to read 16- and 32-bit integers from the specified offset in the buffer in little endian format.
Once the event has been read, advance the index. Then check if the event is of
type 3, indicating that a sample element is present; if it is, look at the code to determine whether it is the x, y, or z-axis part of the sample.
Note that the time is included redundantly in each event, and should be the same for all parts of the same sample, so the value directly from the event can be used. When an event of
code 0 is received, this indicates that the sample is complete. However, you can use the
state variable to keep track of which parts of the sample have been recorded and only send the sample if all of them have been received. Parts of a sample are normally only missed if you start reading events in the middle of a sample (which actually does occur).
Finally, having set up the function to process samples streamed from the kernel, set up the return value object with the
stop function, which closes the file descriptor to stop the streaming.
In our next installment, we'll look at implementing support for the analog to digital convertors (ADCs).