Goals
- Track how far, how fast, and how long Ruby runs
- Visualize and export data
- Learn more about a Phodopus campbelli hamster behavior through real data
How It Works
The first prototype was made with an Arduino UNO:

We measure speed using the basic formula:
Speed = Distance / Time
We know the wheel's radius (6 cm), so we can compute its circumference using:
C = 2 * π * r
Each time the magnet passes the reed sensor, we count a full rotation. By measuring how many rotations happen every 2 seconds, we can estimate Ruby’s speed.
To count the wheel’s rotations, we use a reed switch sensor, which detects magnetic fields. By carefully placing the sensor and attaching a small magnet to the wheel, we can count each time the magnet passes in front of the sensor.
The magnet’s position must be chosen so that it passes close to the sensor once per full rotation.
Pay some attention because the magnet can pass near the sensor slowly, which may result in multiple readings. To avoid this, we used a comparison with the previous value in the code to detect only the falling edge of the magnet's passage.

Over time, we calculate:
- Maximum speed
- Total distance run
- Time spent running
- Average speed (only while active)
The Code
The code changed a lot during times, the first version was not connected to internet and run on an Arduino UNO:
After the first prototype with Arduino I've created a version for Wemos microcontroller with a small SSD1306 display (128x64 pixel).
The simplified sketch (available in the file section and also in my repository on Github) handles all core functions of the smart hamster wheel: reading the sensor, calculating speed and distance, tracking activity time, and displaying the data on an OLED screen.
This is the final configuration schema:

Display and Sensor Initialization
-
The SSD1306 OLED display is connected via I2C on pins
D2
andD1
. -
The reed switch sensor is connected to pin
D7
to detect each full wheel rotation. -
Wheel radius is set to
6 cm
, and the circumference is computed in meters.
Main Loop Behavior
The loop handles several tasks:
1. Speed Calculation (every 2 seconds)
-
Counts how many times the magnet passed the sensor in the last 2 seconds.
-
Multiplies that by the wheel’s circumference to get the distance.
-
Calculates instantaneous speed in km/h.
-
Stores the highest speed recorded as
vmax
.
2. Activity Time
-
If speed is non-zero, it increments the running time (
totS
) every 0.5 seconds. -
This gives a measure of how long the hamster has actually been running.
3. Reed Sensor Debounce
Detects transitions from HIGH to LOW on the sensor.
Ignores noise or repeated triggers if less than 200ms have passed since the last trigger.
Increments two counters:
c
: total number of wheel spins (for distance)q
: spins in current 2-second window (for speed)
4. Average Speed Calculation
-
Calculated only if total running time is greater than 0.
-
Only considers active periods (not idle time).
Display Functions notes
I've used several functions to handle the display of the data in the screen, this is necessary to have a nice dashboard on the tiny screen. There is also a timer that every 5 seconds changes the data printed on the display to show maximum speed, instant speed, avarage speed, total distance... So I have different functions:
-
printLabels()
: Draws static text labels depending on screen mode. -
printSpin()
: Shows the total spin count on the screen. -
printInstantSpeed()
: Displays current speed in large or small font depending on context (running or resting).
Design notes
-
Sampling time (
deltat
) is fixed to 2 seconds. -
All key data —speed, distance, time— are updated in near real time.
-
The system is designed to run continuously and display data clearly on a small screen.
After a few period of test, I've improved the system adding wifi connectivity.
IoT Connectivity
The ESP8266 is connected to Wi-Fi. Every 30 minutes, if Ruby is not running, the system uploads data to Google Sheets using HTTPS and a Google Apps Script backend.
This required:
-
Setting up a Google Script endpoint
-
Managing a local data queue to avoid losing data during network delays: when the Wemos send data it can't count spins, so I've created a queue where I store data and only when Ruby is not running I send data.
-
Splitting data into two sheets: one for daily totals, one for 30-minute intervals
To properly configure this we use the instructions provided by Github user StorageB here. It manages to configure an endpoint on Google Drive which receive data from our Wemos through an https call.
At this endpoint there is a Google script (AppScript) which receives the post call and can insert data in a already existing Google Sheet.
So: Wemos get data, send to your home wifi, then through Internet we reach Google server, store data on a Google Sheet and now we can see data on our smartphone! Now the hamster wheel is connected.
This is Internet Of Things.

Interface and UX Features
Last improvements to the code concerned:
-
A Wi-Fi setup page (via captive portal) allows changing SSID/password without reprogramming (this is nice if we had to move the hamster in another house)
-
OTA firmware updates let us upload new code without physically reconnecting the Wemos to a computer
-
A basic graphic interface on the OLED shows live stats and a simple linear chart for the distance
Lego case
....and this was the final device, hosted inside a beautiful Lego case placed above the cage (note the wire colors replicated with lego pieces for correct order plugging!!):

Results and Insights
Over 516 days, Ruby ran surprising distances. Her best performance: over 15 km in one night!
Averages from the full dataset:
-
Max speed: 6.73 km/h
-
Average speed (active time only): 1.38 km/h
-
Average daily distance: 2.77 km
-
Average running time per day: 6955 s (about 2 hours)
Data was analyzed using Google Sheet and also ChatGPT and it's available for everybody in this public Google Sheet.
We found that Ruby was active discontinuously (some day few meters, some days many kilometers!) and almost exclusively between 11 PM and 7 AM, confirming hamsters are nocturnal.

She was a night creature, it's documented by data:

A Tribute to Ruby
With all our affection for Ruby — who was with us for two years — we’ve decided not to keep small animals in cages anymore. Even the most enriched environment cannot match the complexity and freedom of nature.
This project taught us about electronics, animal behavior, and above all, respect for living beings. It’s not about giving up love for animals, it’s about honoring that love with empathy and awareness.