Let's take a quick dip into the code powering the Time Tracking Cube. If you're interested in using an accelerometer to send data to Adafruit IO, this code can serve as a good jumping off point.

Within the loop(), a normalized sensor reading is taken from the accelerometer and we'll call a function to update the timer.

  // Update the timer
  updateTime();

  // Get a normalized sensor reading
  sensors_event_t event;
  lis.getEvent(&event);

Then, the face orientation is detected within a conditional statement. For example, if the cube is tilted to the left, we'll detect it by querying the accelerometer's acceleration along the X axis:

      // Detect cube face orientation
  if (event.acceleration.x > 9 && event.acceleration.x < 10) // left-side up
  {
    //Serial.println("Cube TILTED: Left");
    cubeState = 1;
  }
  

We don't want the cube to register a right-side tilt if we've previously tilted it to the right. To do this, we'll compare the cube's state to its previous state.

  // return if the orientation hasn't changed
  if (cubeState == prvCubeState)
    return;

Then, we'll send the cube's previous task and the time spent on that task to Adafruit IO based off of the cube's orientation we detected earlier (using a switch-case statement). On this new task, the NeoPixel strip is updated, the cube will play a tone, and we'll reset the timer. The idle state (case 3) will play a tone and update the NeoPixels, but it won't send to Adafruit IO.

  // Send to Adafruit IO based off of the orientation of the cube
  switch (cubeState)
  {
  case 1:
    Serial.println("Switching to Task 1");
    // update the neopixel strip
    updatePixels(50, 0, 0);
    // play a sound
    tone(PIEZO_PIN, 650, 300);
    Serial.print("Sending to Adafruit IO -> ");
    Serial.println(taskTwo);
    cubetask->save(taskTwo, minutes);
    // reset the timer
    minutes = 0;
    break;
  case 2:
    Serial.println("Switching to Task 2");
    // update the neopixel strip
    updatePixels(0, 50, 0);
    // play a sound
    tone(PIEZO_PIN, 850, 300);
    Serial.print("Sending to Adafruit IO -> ");
    Serial.println(taskOne);
    cubetask->save(taskOne, minutes);
    // reset the timer
    minutes = 0;
    break;
  case 3:
    updatePixels(0, 0, 50);
    tone(PIEZO_PIN, 950, 300);
    break;
  }

Using the Time Tracking Cube

Upload the code to your Feather Huzzah. Then, open the Arduino Serial Monitor (Tools -> Serial Monitor).

Adafruit IO Time Tracking Cube
LIS3DH found!
Pixels init'd
Connecting to Adafruit IOAdafruitIO::connect()
.
Adafruit IO connected.

Keep the serial monitor open and tilt the cube the left. The cube will glow red and the serial monitor will print out:

Switching to Task 1
Sending to Adafruit IO -> Write Code

 

Tilting the cube to the right will make it switch to the second task and the cube will glow green.

Flipping the cube will change the value in real-time.

 

You can check  that the task has been sent to Adafruit IO by navigating to the feed on the Adafruit IO website. You should see the value set to the previous task. 

Next, we'll want to check that Zapier is working properly.

 

Navigate to the Task History Page on Zapier. If the Zap executed successfully, it'll display success under its status.

 

Clicking the task will show you which data was found in Adafruit IO and what was written to the Google Sheet row.

Don't see anything on this page, but the Zap is turned on?

The time cube zap runs every 15 minutes, grabbing the latest data from the Adafruit IO feed.

If the Zap worked properly, you'll see the spreadsheet updated with new values every 15 minutes.

Taking it Further

Now that we have the time cube sending two tasks to Adafruit IO - the code can be extended to sending more tasks to Adafruit IO. How many tasks are possible? The time cube has six sides - so, six unique tasks are possible. 

We're also using the Prop-Maker FeatherWing for this project, which opens up more possibilities. You can take this project further by adding in a button (possibly along one the back face of the cube) to power-down the cube when it's not in use.

What about if you have more than six tasks? The Prop-Maker FeatherWing has breakouts for an external switch, which you can use as a mode-selector. 

Code

// Adafruit IO Time Tracking Cube
// Tutorial Link: https://learn.adafruit.com/time-tracking-cube
//
// Adafruit invests time and resources providing this open source code.
// Please support Adafruit and open source hardware by purchasing
// products from Adafruit!
//
// Written by Brent Rubell for Adafruit Industries
// Copyright (c) 2019 Adafruit Industries
// Licensed under the MIT license.
//
// All text above must be included in any redistribution.

/************************** Configuration ***********************************/

// edit the config.h tab and enter your Adafruit IO credentials
// and any additional configuration needed for WiFi, cellular,
// or ethernet clients.
#include "config.h"

/************************ Example Starts Here *******************************/
#include <Wire.h>
#include <Adafruit_LIS3DH.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_NeoPixel.h>

// Prop-Maker Wing
#define NEOPIXEL_PIN 2
#define POWER_PIN 15

// Used for Pizeo
#define PIEZO_PIN 0

// # of Pixels Attached
#define NUM_PIXELS 8

// Adafruit_LIS3DH Setup
Adafruit_LIS3DH lis = Adafruit_LIS3DH();

// NeoPixel Setup
Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUM_PIXELS, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800);

// Set up the 'cubeTask' feed
AdafruitIO_Feed *cubetask = io.feed("cubetask");

/* Time Tracking Cube States
 * 1: Cube Tilted Left
 * 2: Cube Tilted Right
 * 3: Cube Neutral, Top
*/
int cubeState = 0;

// Previous cube orientation state
int prvCubeState = 0;

// Tasks (change these to what you're currently working on)
String taskOne = "Write Learn Guide";
String taskTwo = "Write Code";

// Adafruit IO sending delay, in seconds
int sendDelay = 0.5;

// Time-Keeping
unsigned long currentTime;
unsigned long prevTime;
int seconds = 0;
int minutes = 0;

void setup()
{
  // start the serial connection
  Serial.begin(9600);
  // wait for serial monitor to open
  while (!Serial)
    ;
  Serial.println("Adafruit IO Time Tracking Cube");

  // disabling low-power mode on the prop-maker wing
  pinMode(POWER_PIN, OUTPUT);
  digitalWrite(POWER_PIN, HIGH);

  // Initialize LIS3DH
  if (!lis.begin(0x18))
  {
    Serial.println("Couldnt start");
    while (1)
      ;
  }
  Serial.println("LIS3DH found!");
  lis.setRange(LIS3DH_RANGE_4_G);

  // Initialize NeoPixel Strip
  strip.begin();
  Serial.println("Pixels init'd");

  // connect to io.adafruit.com
  Serial.print("Connecting to Adafruit IO");
  io.connect();

  // wait for a connection
  while (io.status() < AIO_CONNECTED)
  {
    Serial.print(".");
    delay(500);
  }

  // we are connected
  Serial.println();
  Serial.println(io.statusText());
}

void updateTime()
{
  // grab the current time from millis()
  currentTime = millis() / 1000;
  seconds = currentTime - prevTime;
  // increase mins.
  if (seconds == 60)
  {
    prevTime = currentTime;
    minutes++;
  }
}

void updatePixels(uint8_t red, uint8_t green, uint8_t blue)
{
  for (int p = 0; p < NUM_PIXELS; p++)
  {
    strip.setPixelColor(p, red, green, blue);
  }
  strip.show();
}

void loop()
{
  // io.run(); is required for all sketches.
  // it should always be present at the top of your loop
  // function. it keeps the client connected to
  // io.adafruit.com, and processes any incoming data.
  io.run();

  // Update the timer
  updateTime();

  // Get a normalized sensor reading
  sensors_event_t event;
  lis.getEvent(&event);

  // Detect cube face orientation
  if (event.acceleration.x > 9 && event.acceleration.x < 10)
  {
    //Serial.println("Cube TILTED: Left");
    cubeState = 1;
  }
  else if (event.acceleration.x < -9)
  {
    //Serial.println("Cube TILTED: Right");
    cubeState = 2;
  }
  else if (event.acceleration.y < 0 && event.acceleration.y > -1)
  {
    cubeState = 3;
  }
  else
  { // orientation not specified
    //Serial.println("Cube Idle...");
  }

  // return if the orientation hasn't changed
  if (cubeState == prvCubeState)
    return;

  // Send to Adafruit IO based off of the orientation of the cube
  switch (cubeState)
  {
  case 1:
    Serial.println("Switching to Task 1");
    // update the neopixel strip
    updatePixels(50, 0, 0);

    // play a sound
    #if defined(ARDUINO_ARCH_ESP32)
        ledcWriteTone(PIEZO_PIN, 650);
    #else
        tone(PIEZO_PIN, 650, 300);
    #endif

    Serial.print("Sending to Adafruit IO -> ");
    Serial.println(taskTwo);
    cubetask->save(taskTwo, minutes);
    // reset the timer
    minutes = 0;
    break;
  case 2:
    Serial.println("Switching to Task 2");
    // update the neopixel strip
    updatePixels(0, 50, 0);

    // play a sound
    #if defined(ARDUINO_ARCH_ESP32)
        ledcWriteTone(PIEZO_PIN, 850);
    #else
        tone(PIEZO_PIN, 850, 300);
    #endif

    Serial.print("Sending to Adafruit IO -> ");
    Serial.println(taskOne);
    cubetask->save(taskOne, minutes);
    // reset the timer
    minutes = 0;
    break;
  case 3:
    updatePixels(0, 0, 50);

    // play a sound
    #if defined(ARDUINO_ARCH_ESP32)
        ledcWriteTone(PIEZO_PIN, 950);
    #else
        tone(PIEZO_PIN, 950, 300);
    #endif

    break;
  }

  // save cube state
  prvCubeState = cubeState;

  // Delay the send to Adafruit IO
  delay(sendDelay * 1000);
}

This guide was first published on Jan 14, 2019. It was last updated on Mar 28, 2024.

This page (Code) was last updated on Mar 28, 2024.

Text editor powered by tinymce.