Monitoring a Cat Exercise Wheel

My cat Cygnus really likes to run on his wheel:

It's a wheel from One Fast Cat that I got from a friend who couldn't train her cats to use it. Cygnus is young, active, and easily swayed by treats, so getting him to use it was easy.

Clearly, I wanted to figure out how fast he gets up to (spoiler: it's about 14 mph), how often he runs, and whether he prefers clockwise or counterclockwise.

  1. Hardware
  2. Software
  3. Results
  4. Conclusion

The Hardware

The system is pretty simple. First, I glued some small magnets to some plastic pieces on the outer circumference:

There are 16 plastic clippy things around the wheel; I am lazy and just used 8 of them. The silvery disc is the magnet, held in place with hot glue. To sense the magnet passing, I used a pair of 3144E Hall-effect sensors:

Technically, I only needed a single sensor to measure the speed. But I wanted to measure the direction as well, and the sensors are cheap. The idea is simple: the two sensors are close together, while the magnets are (relatively) far apart. If the timing looks like A-B------A-B------A-B, then it is going in one direction. If the timing looks like B-A------B-A------B-A, then it is in the other direction.

Here's the full assembled system:

The chair-like piece fits over one of the supports for the wheel. The ESP8266 I used for the controller sits on that, and there's a little angle piece that the magnets attach to. The whole thing is adjustable to get a reasonable distance to the magnets (I found that about 5-10 mm distance is fine). STL files for your 3D printer: NodeMCU base, wheel sensor bracket, and wheel sensor platform.

I use the digital out (D0) pin on each sensor as inputs to the ESP chip; aside from that, they need only power and ground, and use so little power that the ESP can power them directly.

Here it is all fitted in place. The magnets pass by the sensors on the right side from this angle.

The Software

GitHub link to the Arduino project

The basic process is pretty simple: fire an interrupt each time one of the sensor pins goes high and run an update function. The update function looks at the last three distinct readings: that is, when the sensor pattern is A-B-A or B-A-B. And when the middle reading is in the upper 75th percentile of the time between the first and last interrupt firing, it is a pretty good bet that the last two represent the same magnet passing by the sensors in one direction or the other. The gap between sensors is, after all, much smaller than that between magnets, and therefore it would take some wild changes in speed for it to behave any differently.

Once I have that A------B-A or B------A-B pattern, I can simply look at the last reading to determine the direction (clockwise or counterclockwise), and I can determine the speed by looking at the time difference between the first and last readings (A to A or B to B). The wheel is about 3.4 m around, or 425 mm per "segment" (recall that I used 8 magnets around the circumference), so I just have to divide by the time to get the speed. If that was, say, 250 ms, then I can say that Cygnus ran at 425 mm/250 ms = 1.7 m/s, or 3.8 mph.

I did a bit of processing on the data along the way, keeping a peak speed, total duration, and a simple histogram of the results.

From there, I wanted to store the data somewhere easily accessible. I used Google Apps Script for this purpose, to add each run to a new row on a spreadsheet. I have to admit: I used ChatGPT to guide me along here! I know Javascript well enough, but not Google's APIs. It did pretty well and helped set up the script boilerplate. I encode to a JSON string and POST it to a "web app" URL. The hardest part was getting SSL support working on the ESP chip; eventually I found that BearSSL did the job. The Apps Script is just this:

function doPost(e) { var param = JSON.parse(e.postData.contents); var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("log"); var date = new Date(); if (param.message == 'startup') { var misc = "host=" + + " buildDate=" + + " buildTime=" +; sheet.appendRow([date, param.message, '', '', '', '', '', '', '', '', '', misc]); } else if (param.message == 'run') { sheet.appendRow([ date, param.message, param.speed.peak_mph, param.speed.ave_mph, param.dir, param.dur, param.histo['80-100'], param.histo['60-80'], param.histo['40-60'], param.histo['20-40'], param.histo['0-20'], misc]); } else if (param.message == 'test') { sheet.appendRow([date, param.message, '', '', '', '', '', '', '', '', '', param.misc]); } else { sheet.appendRow([date, 'unknown', '', '', '', '', '', '', '', '', '', e.postData.contents]); } return ContentService.createTextOutput(JSON.stringify({ status: 'success' })).setMimeType(ContentService.MimeType.JSON); }

Oh, and there's a little telnet server on the ESP with some simple commands. Trivial to implement using Arduino libraries.


This cat runs a lot! Here's a typical little burst of activity:

[time][message][peak_mph][ave_mph][dir][dur][histo 80-100][histo 60-80][histo 40-60][histo 20-40][histo 0-20]
2/14/2023 11:48:35run6.8402.132CW9.3006.3354.3203.1282.2220.959
2/14/2023 11:48:52run6.5552.182CW9.0865.8004.4953.1032.1781.016
2/14/2023 11:49:20run10.0422.775CW13.2669.5636.5164.2002.7881.093
2/14/2023 11:49:38run5.2741.788CW5.2815.2304.2912.7281.7830.692
2/14/2023 11:50:04run4.6272.124CW4.4454.1683.6662.2911.7811.185

As you can see, the runs are not very long--usually around 5-10 seconds at a time. But so far he is averaging about 50 runs per day. He prefers clockwise, but not universally; it's about at 2/3 at the moment.

The following data is real-time, so the following conclusions may be out of date.

Cygnus seems to take many slow walks between 2-3 mph, but there are plenty of good sprints in there, up to >13 mph. I've seen >15 mph in previous data.

I keep a fairly late schedule (sleep between 3am-11am), and you can see that Cygnus mostly runs just after I go to sleep and just after I wake up (mostly the latter). I didn't realize until I looked at the data that he's fairly active between 4am-6am!

Cygnus generally does fairly short runs, though there are some reasonably long walks in there was well. Cats aren't built for marathons, in any case.

Likewise, most of the runs are short--between 5 and 15 seconds. But some go for more than a minute!

When I wrote this, Cygnus preferred clockwise by about 60% to 40%. I'll check in later to see if it's changed.

Finally, here are the number of runs per day (30-day limit). At the moment, he's averaging about 50, though with a few outliers in either direction. His current record is 118, which he set the day I enabled the automatic treat dispenser. He's settled down a bit since then.


Overall, I find that I have a strangely energetic cat. He's not a Bengal or anything unusual, just an extremely black housecat.

Next on the project list is an automatic treat dispenser. Although Cygnus clearly likes to run on his own, I try to keep him motivated by treats as well. With this system, I can trigger a device to reward Cygnus for a good run automatically.

Copyright 2023 Scott Cutler. All code may be used freely and is without warranty. Acknowledgement is preferred but not required. Feel free to contact me at scott at scottcutler dot net.