Project showcase
Gerbil Fitbit - Now with Pimoroni ePaper Display!

Gerbil Fitbit - Now with Pimoroni ePaper Display!

How far and fast can my gerbils, Snuggles and Gingy, run? IR sensor odometer, with ePaper display of daily stats.

  • 1,271 views
  • 1 comment
  • 5 respects

Components and supplies

Necessary tools and machines

Hy gluegun
Hot glue gun (generic)

About this project

Intro

Our family recently grew with the addition of two sister gerbils, Snuggles and Gingy. A week later an exercise wheel was bought, and the Arduino possibilities started to multiply.

The objective of this little project was to see how far and fast the gerbils could run; hundreds, or thousands of meters? Could they outrun us bipeds?

Update (Version 2.0):

Now the gerbils’ daily stats are posted at midnight on some clever ePaper, using Pimoroni’s Inky pHat and a Raspberry Pi. Total distance and the top speed from the last 24 hours are displayed. A handy histogram shows when Snuggles and Gingy have been at their most active.

Basic Architecture

There are a couple of ways to do this, but this project uses an infrared break beam sensor (because I had one handy). Each time the exercise wheel rotates, a Lego brick glued to its periphery breaks the beam and a rotation is logged. Measuring the time between each rotation means speed as well as distance can be calculated.

The results are posted to both the Arduino IDE serial monitor and an LCD screen. A little extra coding was required to avoid false readings that suggested either Snuggles or her sister had a top speed of 30mph (48kph)!

Update (Version 2.0):

The Arduino is now coded to post data to a Raspberry Pi every hour via serial communication. An Arduino is much better at timing critical tasks than the Pi. Whereas the Pi will enable some pretty cool internet of things possibilities; tweeting stats and virtual Gerbil runs…

Physical Setup

I used some old Technic Lego to construct a mounting for the IR LED and sensor. The LED and sensor housing included some handy holes for screwing them into a couple of two-stud Lego bricks. A third two-stud brick was glue gunned to the outer diameter of the exercise wheel.

Code

The code uses an interrupt routine to capture each instance of the IR beam being broken. This calls a function, BeamBreak(), that calculates the distance for each rotation based on the circumference of the wheel and the speed using the time since the last full rotation. The speed is compared to the previous maximum to determine Snuggles’ top speed. The results are posted to the serial monitor, printing distance, speed and maximum speed.

The columns of data printed to the serial monitor are labelled periodically by the Header() function. Distance and maximum speed are also posted to a 2 x 16 LCD by employing an easy to use Adafruit library.

The IR sensor I have used pulls the output low (0 volts) when the beam is broken. So to read this input, the sensing pin should be pulled high (pinMode(IR_SENSOR, INPUT_PULLUP)). The interrupt pin is then configured to trigger on a falling signal.

The first iteration of the code resulted in some incredulously high speeds. This was cause by some bounce in the signal. The sensor was being activated twice in a fraction of a second. Probably due to vibration of the gerbil cage as Snuggles accelerated to her maximum velocity! Cutting and pasting the data from the serial monitor to Excel and plotting the data helped with finding a solution.

The errant readings created large spikes in the speed recorded, that were easily discernible from the more believable data. The sensible looking chunks of data showed that the gerbils’ maximum readings was 3mph. By adding an ‘if’ statement to filter out jumps in speed greater than 3mph, the false readings were eliminated.

Raspberry Pi Interaction

The Arduino is coded to respondto data request from a Raspberry Pi over a serial connection. Each hour, the Pirequests the distance and max speed recorded by the Arduino, which poles forserial commands. At the start of each day, the Pi commands the Ardunio to resetthe distance and maximum speed variables. The distance run each hour is storedin an array so that it can be plotted on histogram.

Displaying Daily Data on Pimoroni Inky pHat (ePaper)

The day’s stats are displayed on a Pimoroni Inky pHat tri colour ePaper. The gerbil graphic is part of aback ground png file on top of which distance and maximum speed are printed.Python Image Library is used to create the histogram with a series of draw.line commands. The Python code is included in a dollared-out section at the end of Arduino code below.

The End

So how fit are my gerbils? Well, between them, they are currently running over 1km a day and reaching a top speed just shy of 7mph. Which one runs the furthest and fastest remains a mystery. Has anyone written some gerbil face recognition code?

Code

Gerbil Exercise Wheel MonitorArduino
Measuring the distance and speed of a Gerbil in an exercise wheel.
// Gerbil exercise wheel monitor
// by Stuart Bryant - 25-Feb-19

// Include the library code:
#include "Wire.h"
#include "Adafruit_LiquidCrystal.h"

// Connect via i2c, default address #0 (A0-A2 not jumpered):
Adafruit_LiquidCrystal lcd(0);

// Define LED pin:
#define LED_PIN 13

// Define IR sensor pin:
#define IR_SENSOR 3

// Define wheel diameter constant [mm]:
#define Diameter 130

// Variable for recording distance:
float Distance = 0;

// Define integer variables:
int Counts = 0; // counts the number of times that the IR beam has been broken
int n = 0; // counter for Header function
int DistInt = 0; // rounded distance

// Define timing variables:
float LastTrigger = 0.0;
float Period = 0.0; 
float Speed = 0.0;
float MaxSpeed = 0.0;
float LastSpeed = 0.0;


void setup() {

  // Open serial monitor:
  Serial.begin(9600);
  Serial.println("Gerbil monitor!");
  Serial.println("Distance [m] | Speed [mph] | Max Speed [mph]");

  // set up the LCD's number of rows and columns: 
  lcd.begin(16, 2);
  // Print a text to LCD:
  lcd.clear();
  lcd.print("Meters");
  lcd.setCursor(0, 1);
  lcd.print("Max mph");
  
  // Setup the LED:
  pinMode(LED_PIN,OUTPUT);
    
  // Setup the IR sensor input with a pull-up resistor:
  pinMode(IR_SENSOR,INPUT_PULLUP);

  // Attach interrupt tp IR sensor pin:
  attachInterrupt(digitalPinToInterrupt(IR_SENSOR),BeamBreak, FALLING);
}

  
void BeamBreak() {
  
  Counts++;
  
  // Calculated distance in meters:
  Distance = Diameter * 3.14 * Counts / 1000;
  DistInt = Distance;
  Period = millis() - LastTrigger;
  Speed = Diameter / Period * 7.028; // pi x 1000ms x 2.23694 mph/ m/s

  if (Speed - LastSpeed < 3) { // filters out false readings
    
    LastSpeed = Speed; // updates max speed
    if (Speed > MaxSpeed) {
      MaxSpeed = Speed;
    }      
    
    // Print data to serial monitor:
    Serial.print(Distance);
    Serial.print("            ");
    Serial.print(Speed);
    Serial.print("            ");
    Serial.println(MaxSpeed);

    Header();  
    LastTrigger = millis(); 
  }
}

// Function for formatting the serial monitor output with headers:
void Header() { 
  if (n > 4) {
    Serial.println("Distance [m] | Speed [mph] | Max Speed [mph]");
    n = 0;
  }
  n++;
}

void loop() {

  // Illuminate board LED each time IR beam is broken:
  if (digitalRead(IR_SENSOR) == LOW) {
    digitalWrite(LED_PIN, HIGH); 
  }
  else
  {
    digitalWrite(LED_PIN, LOW);
  }
  
  // Print data to LCD:
  lcd.setCursor(9, 0);
  lcd.print(DistInt);
  lcd.setCursor(9, 1);
  lcd.print(MaxSpeed);
}
Gerbil Exercise Wheel Monitor v2 (serial communication with Pi)Arduino
// Gerbil exercise wheel monitor 
// by Stuart Bryant - 15-Apr-19
// v2 - adds serial communication with Raspberry Pi 

// Include the library code:
#include "Wire.h"
#include "Adafruit_LiquidCrystal.h"

// Connect via i2c, default address #0 (A0-A2 not jumpered):
Adafruit_LiquidCrystal lcd(0);

// Define LED pin:
#define LED_PIN 13

// Define IR sensor pin:
#define IR_SENSOR 3

// Define wheel diameter constant [mm]:
#define Diameter 130

// Variable for recording distance:
float Distance = 0;

// Define integer variables:
int Counts = 0; // counts the number of times that the IR beam has been broken
int n = 0; // counter for Header function
int DistInt = 0; // rounded distance
int pi = 0; // serial message from Pi

// Define timing variables:
float LastTrigger = 0.0;
float Period = 0.0; 
float Speed = 0.0;
float MaxSpeed = 0.0;
float LastSpeed = 0.0;


void setup() {

  // Open serial monitor:
  Serial.begin(9600);
  //Serial.println("Gerbil monitor!");
  //Serial.println("Distance [m] | Speed [mph] | Max Speed [mph]");

  // set up the LCD's number of rows and columns: 
  lcd.begin(16, 2);

  displayReset();
  
  // Setup the LED:
  pinMode(LED_PIN,OUTPUT);
    
  // Setup the IR sensor input with a pull-up resistor:
  pinMode(IR_SENSOR,INPUT_PULLUP);

  // Attach interrupt tp IR sensor pin:
  attachInterrupt(digitalPinToInterrupt(IR_SENSOR),BeamBreak, FALLING);
}



void displayReset() {
  // Print a reading description text to LCD:
  lcd.clear();
  lcd.print("Meters");
  lcd.setCursor(0, 1);
  lcd.print("Max mph");
}
  
void BeamBreak() {
  
  Counts++;
  
  // Calculated distance in meters:
  Distance = Diameter * 3.14 * Counts / 1000;
  DistInt = Distance;
  Period = millis() - LastTrigger;
  Speed = Diameter / Period * 7.028; // pi x 1000ms x 2.23694 mph/ m/s

  if (Speed - LastSpeed < 3) { // filters out false readings
    
    LastSpeed = Speed; // updates max speed
    if (Speed > MaxSpeed) {
      MaxSpeed = Speed;
      //Serial.println("M"); // disabled in v2
      //delay(5);
      //Serial.println(MaxSpeed);
    }      
    
    // Print data to serial monitor:
    //Serial.print(Distance); // disabled in v2
    //Serial.print("            ");
    //Serial.print(Speed);
    //Serial.print("            ");
    //Serial.println(MaxSpeed);
    //Header();  

    LastTrigger = millis(); 
  }
}

// Function for formatting the serial monitor output with headers:
void Header() { 
  if (n > 4) {
    Serial.println("Distance [m] | Speed [mph] | Max Speed [mph]");
    n = 0;
  }
  n++;
}

void loop() {

  // Illuminate board LED each time IR beam is broken:
  if (digitalRead(IR_SENSOR) == LOW) {
    digitalWrite(LED_PIN, HIGH); 
  }
  else
  {
    digitalWrite(LED_PIN, LOW);
  }

  if(Serial.available()>0){
    pi = Serial.read();
    if(pi == 115){ // if 's' recieved
      Serial.println(MaxSpeed);
    }
    else if(pi == 100) { // if 'd' recieved
      Serial.println(Distance);
    }
    else if(pi == 114) { // if 'r' recieved
      Counts = 0;
      DistInt = 0;
      Distance = 0;
      MaxSpeed = 0;
      displayReset();
    }
    else{
      Serial.println(pi);
    }
    
  }
  
  // Print data to LCD:
  lcd.setCursor(9, 0);
  lcd.print(DistInt);
  lcd.setCursor(9, 1);
  lcd.print(MaxSpeed);
}
Inky pHAT Display of Gerbil DataPython
Code for Raspberry Pi
# Inky pHAT display of gerbil data
# by Stuart Bryant 15-Apr-19

# Inky pHAT set-up:

from inky import InkyPHAT
import numpy as np
import math
import csv


inky_display = InkyPHAT("yellow")
inky_display.set_border(inky_display.YELLOW)

# Time imports and set-up:
import calendar as c
import time as t
import datetime as dt

weekdays = ['Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday']

#Set-up serial connection to Arduino:
import serial
ser = serial.Serial('/dev/ttyACM0', 9600) # open serial port to Arduino
ser.flushInput() # remove any data from the serial buffer

# Import Python Image Library:
from PIL import Image, ImageFont, ImageDraw

# Create a new canvas to draw on:
img = Image.open("/home/pi/GerbilProject/G6.png")
draw = ImageDraw.Draw(img)

# Font definitions:
from font_fredoka_one import FredokaOne
font = ImageFont.truetype(FredokaOne, 22)
font2 = ImageFont.truetype("Piboto-Bold.ttf", 14)
font3 = ImageFont.truetype("Piboto-Bold.ttf", 7)

# Create array for storing hourly distance:
dist_array = np.full(24,1)

# Create array for storing daily distance:
week_array = np.full(7,0)

# Import previous 24 hours of data:
with open('24hrData.csv', 'rt') as f:
    csv_reader = csv.reader(f, quoting=csv.QUOTE_NONNUMERIC)
    for line in csv_reader:
        for n in range (0, 24):
            dist_array[n] = line[n]
f.close()

# Import previous daily distances:
with open('WeekData.csv', 'rt') as f:
    csv_reader = csv.reader(f, quoting=csv.QUOTE_NONNUMERIC)
    for line in csv_reader:
        for n in range (0, 7):
            week_array[n] = line[n]    
f.close()

m = np.amax(dist_array) # maxiumum hourly distance
maxSpeed = 0 # maximum speed variable


def screenUpdate():
    # Create a new canvas to draw on
    img = Image.open("/home/pi/GerbilProject/G6.png")
    draw = ImageDraw.Draw(img)
    
    # Title text:
    title = "Snuggles & Gingy"
    w, h = font.getsize(title)
    x = (inky_display.WIDTH / 2) - (w / 2)
    y = 7
    draw.text((x,y), title, inky_display.YELLOW, font)

    # Data lines:
    textDay = yesterday() + "'s data:"
    draw.text((8,40), textDay, inky_display.BLACK, font2)

    textDistance ="Distance: " + str(np.sum(dist_array)) + "m"
    draw.text((8,60), textDistance, inky_display.BLACK, font2)

    textSpeed ="Max Speed: " + str(maxSpeed) + "mph"
    draw.text((8,80), textSpeed, inky_display.BLACK, font2)

    m = np.amax(dist_array)
    # Draw histogram data:
    for n in range (0, 24):
        draw.line((129+n*2,60, 129+n*2,60-dist_array[n]/m*20), fill=1)

    # Histogram axes:
    draw.line((127,40, 127,62), fill=2)
    draw.line((125,60, 175,60), fill=2)

    # x-axis labels:
    draw.text((121,62), "0", inky_display.BLACK, font3)
    draw.text((146,62), "12", inky_display.BLACK, font3)
    draw.text((170,62), "24", inky_display.BLACK, font3)

    # Set image and display:
    inky_display.set_image(img)
    inky_display.show()
    
def yesterday():
    yesterday = weekdays[c.weekday(t.gmtime().tm_year,t.gmtime().tm_mon,t.gmtime().tm_mday)-1]
    return yesterday

def ArduinoSerial(input):
    ser.write(bytes(input.encode('ascii')))
    t.sleep(0.1) # wait for Arduino to respond
    if(ser.inWaiting() >0):
        output = ""
        line = ser.readline()
        l = len(line)
        l = l - 2
        for char in line:
            output = output + chr(char)  
            outputFloat = float(output[:l])
    return outputFloat


screenUpdate()
 

step = 3600

s = t.time()
s = math.ceil(s/step)*step
interval = step # one hour interval
lastDistance = 0

while 1:
    if (t.time() > s):
        distance = ArduinoSerial('d')
        maxSpeed = ArduinoSerial('s')

        deltaDistance = distance - lastDistance
        lastDistance = distance
        dist_array[t.gmtime().tm_hour-1] = deltaDistance

        s = s + interval
        
        print("###################################")
        print("Time: "+str(t.gmtime().tm_hour)+":"+str(t.gmtime().tm_min)+":"+str(t.gmtime().tm_sec))
        print("Interval distance =",deltaDistance, "Total distance:", distance)
        print("Max speed today: "+str(maxSpeed))
        print("Distance array (last 24 hours):")
        print(dist_array)

        # Save last 24 hours of data:
        with open('24hrData.csv', 'wt') as f:
            csv_writer = csv.writer(f, delimiter=',', quoting=csv.QUOTE_NONNUMERIC)
            csv_writer.writerow(dist_array)
        f.close()
        

        # Midnight posting of th days stats:
        if (t.gmtime().tm_hour == 0):
    
            week_array[c.weekday(t.gmtime().tm_year,t.gmtime().tm_mon,t.gmtime().tm_mday)-1] = np.sum(dist_array)

            # Save daily data:
            with open('WeekData.csv', 'wt') as f:
                csv_writer = csv.writer(f, delimiter=',', quoting=csv.QUOTE_NONNUMERIC)
                csv_writer.writerow(week_array)
            f.close()

            screenUpdate()
            ser.write(b'r') # to reset Arduino counter
            t.sleep(0.1)
            ser.flushInput()
            lastDistance = 0

Comments

Similar projects you might like

Aquarium Automation

Project showcase by Team GUT

  • 14,632 views
  • 8 comments
  • 26 respects

Streaming Data to Power BI from a Spin Bike

Project showcase by santoshkanthety and danshrader

  • 1,106 views
  • 2 comments
  • 4 respects

Multi-Dashboard Display with Arduino Controller

Project showcase by Colin O'Dell

  • 25,679 views
  • 6 comments
  • 70 respects

Temperature and Humidity Logger (Using Arduino)

Project showcase by lmsousa

  • 8,524 views
  • 7 comments
  • 36 respects

The Drawing Machine

Project showcase by Kramick Saha

  • 9,857 views
  • 6 comments
  • 20 respects

Automated Snake Enclosure with Camera

Project showcase by hagakure

  • 6,304 views
  • 9 comments
  • 17 respects
Add projectSign up / Login