Saving Sensor Data as Comma Separated Values (csv) on the SD Card

Data is often persisted in text files as comma separated values. This kind of data can be viewed and analyzed by Microsoft Excel or other spreadsheet software.  Applications based on the .NET Micro Framework and .NET Gadgeteer can easily save data in this format using a module such as the GHI Electronics SD Card module. This example records data from the posture sensor/alert device described in two earlier posts: Posture Regulator using .NET Gadgeteer Accelerometer from Seeed Studio and Posture Regulator with LED Alert – Silent.

Of the two options, the first seems most effective to me. The buzzer cannot be ignored as easily as a LED that turns red when I slouch.  This example adds logic that records posture alerts and the interval between them as comma separated values in files on an SD card.  It provides a record, after some hours of use, that can be analyzed to possibly explain a pain in the neck or tension in the shoulders.  I’m prone to hunching over the keyboard, and using this device is proving more effective and less expensive than visits to a physical therapist.

I had the device running while I wrote the previous paragraphs and got the following results. The colums labeled X axis, Y axis, and Z axis show the readings of the Seeed Accelerometer, which is calibrated to the desireable upright posture.  When the Z axis drops below 1.0, an alert is generated and recorded.  The first column shows the time of alert in seconds from calibration time.  The column labeled Delta shows the intervals between alerts: the Δ value  in seconds.  The object of the game, of course, is to make the delta as large as possible between posture alerts.

Posture Alert Intervals and Data

Posture Alert Intervals and Data

To set up the file on the SD Card to record this data, you have to mount the SD card and get the Gadgeteer.Storage object it contains.

    sdCard.MountSDCard();
    if (sdCard.IsCardMounted)
    {
        storage = sdCard.GetStorageDevice();
        oledDisplay.SimpleGraphics.Clear();
        oledDisplay.SimpleGraphics.DisplayText("Calibrated",
            Resources.GetFont(Resources.FontResources.small), GT.Color.Red, 10, 50);
    }

Then you need a Stream object and TextWriter to write lines of data to .csv files on the card. The following code also starts the accelerometer measurements (complete code is included at the end of this article.) The first call to writer.WriteLine initializes the headings for the columns.  Subsequent calls write alert data.

if (sdCard.IsCardMounted && accelerometer.LoadCalibration() && storage != null)
    {
        stream = storage.Open("\\Posture\\Alerts_" + seconds
            + ".csv", FileMode.Create, FileAccess.Write);
        writer = new StreamWriter(stream);
        writer.WriteLine("Timestamp, Delta,  X axis,  Y axis,  Z axis");

        button.TurnLEDOn();
        accelerometer.StartContinuousMeasurements();
    }

The rest of the application calibrates the Seeed accelerometer to the correct posture and sends alerts when the user lapses from that position.  Data about user’s position, the time of alerts, and deltas between them are recorded by the following code.  The results are also displayed on the Seeed OLED Display module.

    void accelerometer_MeasurementComplete(Accelerometer sender, Accelerometer.Acceleration acceleration)
    {
        time2 = seconds;
        oledDisplay.SimpleGraphics.Clear();

        if (acceleration.Z < 1)
        {
            writer.WriteLine(seconds + "," + (time2 - time1) + "," + acceleration.X.ToString() + "," +
                acceleration.Y.ToString() + "," +acceleration.Z.ToString());

            oledDisplay.SimpleGraphics.DisplayText("Posture alert",
                Resources.GetFont(Resources.FontResources.small), GT.Color.Red, 20, 20);

            relays.Relay1 = true;
            time1 = seconds;
        }
        else
        {
            relays.Relay1 = false;
        }

        oledDisplay.SimpleGraphics.DisplayText("X: " + acceleration.X.ToString(),
            Resources.GetFont(Resources.FontResources.small), GT.Color.Green, 20, 40);
        oledDisplay.SimpleGraphics.DisplayText("Y: " + acceleration.Y.ToString(),
            Resources.GetFont(Resources.FontResources.small), GT.Color.Green, 20, 60);
        oledDisplay.SimpleGraphics.DisplayText("Z: " + acceleration.Z.ToString(),
            Resources.GetFont(Resources.FontResources.small), GT.Color.Green, 20, 80);
    }

Program flow in the complete application uses a calibration button to initialize the accelerometer to posture the user wants to maintain.  Another button starts continuous measurements using the asynchronous method: Accelerometer.StartContinuousMeasurements.  The data on the user’s position is returned in the Accelerometer.MeasurementComplete event.  When the Z axis of the accelerometer shows undesirable leaning or slouching, conditional code closes the circuit on a relay module, which turns on a buzzer to remind the user to sit up straight.  Alert data is recorded on the SD card.

The modules used in this device are included in the project by the Visual Studio Designer for .NET Gadgeteer as shown in the following illustration (click to enlarge).

Modules used in Posture Alert Device

Modules used in Posture Alert Device

The complete Program.cs file is included in the following code block.

using System;
using Microsoft.SPOT;

using GT = Gadgeteer;
using Gadgeteer.Modules.Seeed;
using Gadgeteer.Modules.GHIElectronics;

using System.IO;

namespace PostureSensorAccelerometer
{
    public partial class Program
    {
        Stream stream;
        TextWriter writer;
        GT.StorageDevice storage;
        GT.Timer timer;
        Int32 seconds;
        int time1;
        int time2;

        void ProgramStarted()
        {
            button.ButtonPressed += new Button.ButtonEventHandler(button_ButtonPressed);
            button1.ButtonPressed += new Button.ButtonEventHandler(button1_ButtonPressed);
            accelerometer.ContinuousMeasurementInterval = new TimeSpan(0, 0, 0, 0, 700);
            accelerometer.MeasurementComplete +=
                new Accelerometer.MeasurementCompleteEventHandler(accelerometer_MeasurementComplete);
            timer = new GT.Timer(1000);
            timer.Tick += new GT.Timer.TickEventHandler(timer_Tick);

            Debug.Print("Program Started");
            oledDisplay.SimpleGraphics.DisplayText("Ready to calibrate",
                        Resources.GetFont(Resources.FontResources.small), GT.Color.Red, 10, 25);
        }

        void timer_Tick(GT.Timer timer)
        {
            seconds++;
        }

        void button1_ButtonPressed(Button sender, Button.ButtonState state)
        {
            accelerometer.Calibrate();
            accelerometer.SaveCalibration();
            time1 = seconds;

            sdCard.MountSDCard();
            if (sdCard.IsCardMounted)
            {
                storage = sdCard.GetStorageDevice();
                oledDisplay.SimpleGraphics.Clear();
                oledDisplay.SimpleGraphics.DisplayText("Calibrated",
                    Resources.GetFont(Resources.FontResources.small), GT.Color.Red, 10, 50);
                    timer.Start();
            }
            else
            {
                oledDisplay.SimpleGraphics.Clear();
                oledDisplay.SimpleGraphics.DisplayText("Insert SD Card.",
                        Resources.GetFont(Resources.FontResources.small), GT.Color.Red, 10, 25);
                oledDisplay.SimpleGraphics.DisplayText("Recalibrate.",
                    Resources.GetFont(Resources.FontResources.small), GT.Color.Red, 10, 50);
            }
        }

        void button_ButtonPressed(Button sender, Button.ButtonState state)
        {
            if (button.IsLedOn)
            {
                accelerometer.StopContinuousMeasurements();
                writer.Close();
                stream.Close();
                storage = null;
                timer.Stop();

                button.TurnLEDOff();
            }
            else
            {
                if (sdCard.IsCardMounted && accelerometer.LoadCalibration() && storage != null)
                {
                    stream = storage.Open("\\Posture\\Alerts_" + seconds
                        + ".csv", FileMode.Create, FileAccess.Write);
                    writer = new StreamWriter(stream);
                    writer.WriteLine("Timestamp, Delta,  X axis,  Y axis,  Z axis");

                    button.TurnLEDOn();
                    accelerometer.StartContinuousMeasurements();
                }
                else
                {
                    oledDisplay.SimpleGraphics.Clear();
                    oledDisplay.SimpleGraphics.DisplayText("Insert SD Card.",
                        Resources.GetFont(Resources.FontResources.small), GT.Color.Red, 10, 25);
                    oledDisplay.SimpleGraphics.DisplayText("Push calibation button.",
                        Resources.GetFont(Resources.FontResources.small), GT.Color.Red, 10, 50);
                }
            }
        }

        void accelerometer_MeasurementComplete(Accelerometer sender, Accelerometer.Acceleration acceleration)
        {
            time2 = seconds;
            oledDisplay.SimpleGraphics.Clear();

            if (acceleration.Z < 1)
            {
                writer.WriteLine(seconds + "," + (time2 - time1) + "," + acceleration.X.ToString() + "," +
                    acceleration.Y.ToString() + "," +acceleration.Z.ToString());

                oledDisplay.SimpleGraphics.DisplayText("Posture alert",
                    Resources.GetFont(Resources.FontResources.small), GT.Color.Red, 20, 20);

                relays.Relay1 = true;
                time1 = seconds;
            }
            else
            {
                relays.Relay1 = false;
            }

            oledDisplay.SimpleGraphics.DisplayText("X: " + acceleration.X.ToString(),
                Resources.GetFont(Resources.FontResources.small), GT.Color.Green, 20, 40);
            oledDisplay.SimpleGraphics.DisplayText("Y: " + acceleration.Y.ToString(),
                Resources.GetFont(Resources.FontResources.small), GT.Color.Green, 20, 60);
            oledDisplay.SimpleGraphics.DisplayText("Z: " + acceleration.Z.ToString(),
                Resources.GetFont(Resources.FontResources.small), GT.Color.Green, 20, 80);
        }
    }
}
Advertisement

, , ,

  1. Leave a comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: