Time Management Cube

So, this academic year is already well down the road, and I haven’t blogged in a while. To make up for this, I thought I’d show you a time management cube, which notes which side is up, and changes the text accordingly. The device also writes the update into the Web, making this yet another inane IoT device.

As you can see, it has an accelerometer in the center of the cube, and when it is rotated, the side that is up is taken to be whatever you are currently doing. When you have finished doing that, simply rotate the cube to start another activity. Let’s have a look at how this is done.

Parts list

ESP32 is a widely available system-on-a-chip device, which you can find anywhere. I usually buy mine off eBay where they seem to cost around 6,50 euros apiece.

The accelerometer is a GY-521, which I got from my Freenove Arduino Starter kits. It’s also very generic and can be bought on a variety of suppliers.

The ESP32

I’ll highlight some parts in the code. It has the usual definitions at the top, then the setup, where the display is started and the device connects to the Web. During this it will write the word “Booted” into the Web, which assumes you start the device when you come into the office in the morning. The definitions and setup are largely from the sample files used in this mashup project.

Perhaps the interesting bit is the accelerometer:

//gyroscope settings
double x;
double y;
double z;
const int MPU_addr = 0x68;
int16_t AcX, AcY, AcZ, Tmp, GyX, GyY, GyZ;
int minVal = 265;
int maxVal = 402;
int shortx, shorty, shortz, anglesum;
String strShortX, strShortY, strShortZ, strActivity;

The shortx, y, and z are my variables used in the calculation of the position of the cube, and the corresponding strShortX etc. are used for display and Web update purposes.

The next part shows you what happens in the loop, when the accelerometer stance is read.

Wire.requestFrom(MPU_addr, 14, true);
AcX = Wire.read() << 8 | Wire.read();
AcY = Wire.read() << 8 | Wire.read();
AcZ = Wire.read() << 8 | Wire.read();
int xAng = map(AcX, minVal, maxVal, -90, 90);
int yAng = map(AcY, minVal, maxVal, -90, 90);
int zAng = map(AcZ, minVal, maxVal, -90, 90);
x = RAD_TO_DEG * (atan2(-yAng, -zAng) + PI);
y = RAD_TO_DEG * (atan2(-xAng, -zAng) + PI);
z = RAD_TO_DEG * (atan2(-yAng, -xAng) + PI);
Serial.print("AngleX= ");
shortx = int(x / 60) + 1;
String xangle;
xangle = int(x);
Serial.print("AngleY= ");
shorty = int(y / 60) + 1;
String yangle;
yangle = int(y);
Serial.print("AngleZ= ");
shortz = int(z / 60) + 1;
String zangle;
zangle = int(z);
anglesum = shortx + shorty + shortz;

The first block finds out the angles in radians, then they are simplified into x, y, and z degrees. The third block then converts the angle that can be between 0 to 360 into 0 to 6. The anglesum variable then contains the sum of the simplified angles. The Serial.println statements are used to show you values in the Serial Mointor, which is useful for debugging purposes.

Then there is a case structure, which uses the sum to define which side of the cube is up. When I had the system installed, I rotated the cube and noted down the sums, and then used them as the trigger here.

switch (anglesum) {
case 5:
strActivity = "Email";
case 10:
strActivity = "Coffee";
case 12:
strActivity = "Documents";
case 13:
strActivity = "Meeting";
case 14:
strActivity = "Teaching";
case 16:
strActivity = "3D Work";

The loop then continues to check whether the activity is the same as a second before, or whether there was a change:

So, if strActivityOld is “Booting…” which happens in the setup, it will display that. When it comes to the main loop, the cube stance is read, and the strActivity is updated. If the activity is different from strActivityOld, it will display “Change!” and then go into the UpdateActivity function to write the new activity into the Web:

display.setTextSize(2); // Normal 1:1 pixel scale
display.setTextColor(SSD1306_WHITE); // Draw white text
display.setCursor(0, 0); // Start at top-left corner
display.setCursor(0, 16); // Start at top-left corner
if (strActivity != strActivityOld) { //THIS IS where the activity is checked.
display.setCursor(0, 0); // Start at top-left corner
if (strActivityOld == "Booting…") {
else {
UpdateActivity(strActivity); //This is the update function for the Web.
strActivityOld = strActivity;

The UpdateActivity function is invoked whenever there is a change in the stance of the cube, with the strActivity passed as a parameter to it.

void UpdateActivity(String myActivity) {
  //sending data to the server:

  Serial.println("add data to web");
  Serial.printf("Connecting to %s ", ssid);
  if (WiFi.status() == WL_CONNECTED) {
    WiFiClient client;
    HTTPClient http;

    // Your Domain name with URL path or IP address with path

    // If you need an HTTP request with a content type: text/plain
    http.addHeader("Content-Type", "text/plain");
    String httpRequestData = "Activity: " + myActivity;
    // Send HTTP POST request
    int httpResponseCode = http.POST(httpRequestData);

    Serial.println("HTTP Response code: ");

    // Free resources
  else {
    Serial.println("WiFi Disconnected");

That explains the various interesting bits in the code, the rest is merely definitions and run-of-the-mill code.

Wiring scheme

Both the accelerometer and the display are I2C devices. Therefore they both have just four wires, VCC for voltage, GND for ground, SDA for data, and SCL for clock. You need to share the pins 21 and 22, since they are in the same bus, and separated by their device IDs. It is likely you don’t have to change the IDs, they should work out of the box. The easiest way is to make a jumper cable with one female connector at one end and two females at the other

Connect the display as follows:

  • VCC to ESP32 5V
  • GND to ESP32 ground
  • SDA to pin 21
  • SCL to pin 22

Connect the accelerometer as follows:

  • VCC to ESP32 3.3V
  • GND to ESP32 ground
  • SDA to pin 21
  • SCL to pin 22

3D Printed cube

The 3D printable STL file is also on GitHub. My cube is 8 x 8 cm, and I printed with with the Formlabs Form 3 resin printer. It comes out rather light, and even if I used the most flexible wires I could find in the lab, it was slightly tilted when the wires coming out of one of the cube’s corners were bent. Therefore I added a bolt with a bunch of nuts to the center bar of the cube, and now the cube sits flat regardless of the side facing down.

You can print it with a filament printer as well, but you will need to use supports, and you will spend some time ripping them off (the resin printer also used supports but they are easy to remove.)

The Web end of it

Every time the cube is turned, it sends the new position to a file on the Web:

Time list

The PHP that manages it is very simple. One file receives the input:

    $_POST = file_get_contents('php://input');
    $myYear = date("d.m.Y");
    $myHour = date("H:i");
    $myFile = "gravicube.txt";
    $fh = fopen($myFile, 'a') or die("can't open file");
    fwrite($fh, $myYear . " " .  $myHour);
    fwrite($fh, "\t");
    fwrite($fh, $_POST);
    fwrite($fh, "\n");    

This is called gravicube.php and it records the data in a file. There is also a file in the same folder called index.php:

<!doctype html>
<meta charset="utf-8">
<title>3D Lab Activity Monitor</title>
<link rel="stylesheet" type="text/css" href="gravicube.css">


$myRealTime = date("H:i");
echo ("<p>Current time is "), $myRealTime;
echo "";

    $linecount = 0;
    $handle = fopen($file, "r");
    echo ("<p>The latest reported activity:<p>");
    $data = array_slice(file('gravicube.txt'), -10);
        foreach ($data as $line) {
            $pos = strpos($line, "A:");
            $showLine = substr($line, 0, $pos-1);  
            if ($mylinecount == 10) {
                echo("<p class='mybold'>");
                echo $showLine;
            else {
                echo("<p>"), $showLine, ("</p>");

This way you don’t need to type anything behind the folder name.

Final demo video

Gravicube in action


Leave a Reply

Your email address will not be published. Required fields are marked *


This site uses Akismet to reduce spam. Learn how your comment data is processed.