An eBay Addiction, a Logic Analyzer, and a VR Game: How I Made Some Christmas Decorations Dance

Adam Leventhal
8 min readDec 30, 2019

This was kind of a silly project. If I had known it would end with a bunch of wholesome Christmas decorations dancing along to Lizzo I probably would have done it a long time ago. Here’s the story of how I took some beloved kitsch, a logic analyzer, sed, awk, Google Sheets, Python, an FT232H, some wires, data from a VR game, and a few evenings to make it happen. (Just want the punchline? Skip to the end.)

Happy Tapper eBay addiction

My family has a fair bit of music-producing stuff from Hallmark: books, stuffed animals, and, of course, holiday decorations. Chalk it up to an enthusiastic grandparent. The Happy Tappers are a favorite. We started with two — a reindeer and a snowman — and I was tasked with filling out the full set of four. We now have 13, so mission accomplished and then some.

Some debate in my family about whose toys these are…

Each Tapper plays two songs and dances; you can plug in other Tappers via ports on either side and they become backup dancers. Deeply alluring, clearly. I got a good deal on Santa, then a better deal on a different Santa. Then there was a great deal on a set of 4, so had to get it. Then — after months of waiting — I pounced on a set of 5, including the very rare “painter” elf. So, in short: I have a problem. This solstice holiday season my plan was to figure out how they work and get them to do some out-of-spec dancing.

Gotta catch ’em all

Vivisection

My first step was to pull one apart. There’s a microcontroller (covered with scratched up epoxy) that controls the speaker and electromagnet actuators while sending and receiving signals over the bus formed by connecting multiple Tappers (henceforth, the Dance Bus).

Santa’s (X5053) brain

Examination with a multimeter showed that pin 3 is ground, pin 2 is for dance signals, and pin 1 is high when one of the Tappers on the Dance Bus starts playing. I tried sending some pulses down pin 2, but that didn’t produce any tapping (happy or otherwise). So I hooked one up to my brand new logic analyzer.

Reverse Choreography

You can spend a lot for a great logic analyzer which is what I didn’t do. I had never used a logic analyzer of any kind until a few weeks ago when I took a hardware hacking class. Basically as a party favor for the class, we were each given a BitMagic logic analyzer ($30) (worse, by the way, than the fancy Saleae we used during the class). I used PulseView, the open source front-end, to gather and examine signal data. The docs were great; it was an testament to the power of open.

I hooked up the leads to the target Tapper, pushed the button, and… nothing. Its button didn’t work. Whether an artifact of the cheap logic analyzer or due to some pilot error, the logic analyzer was actually sending a signal — 3V measured between pins 1 and 3 — which means on the Dance Bus that a Tapper is active and others should expect to receive signals. I worked around this by pushing the Tapper’s button and then quickly hooking up the leads.

Here’s 15 seconds worth of Dance Bus activity in PulseView:

Each of those little blips corresponds to a message on the Dance Bus. Zooming in we can see a couple of the messages:

The cursor tool lets you measure the time between features:

I measured a few by hand, but it was going to be tedious to look at each signal that way. I needed the data in a form I could examine programmatically. PulseView has a bunch of output formats; the one I landed on (by trying them all) is “Value Change Data” whose output looks like this:

$date Thu Dec 19 23:17:09 2019 $end
$version libsigrok 0.6.0-git-ef62ab6 $end
$comment
Acquisition with 1/8 channels at 1 MHz
$end
$timescale 1 us $end
$scope module libsigrok $end
$var wire 1 ! D0 $end
$upscope $end
$enddefinitions $end
#0 1!
#1291266 0!
#1309128 1!
#1311126 0!
#1313143 1!
#1315111 0!
#1319103 1!
#1321095 0!
#1323096 1!
#1325115 0!
...

Microsecond timestamps (noted in the header; perhaps due to the 1 MHz sampling frequency I used) and values at the time of changes (Value Change Data — in retrospect: duh). I stripped out the header and line prefixes with sed, then computed deltas between timestamps with awk:

$ cat xx.vcd | sed -n 's/^#\(.*\)/\1/p' | awk 'NR>1{ print $1-last } { last=$1 }'
1291266
17862
1998
2017
1968
3992
1992
2001
2019
2002
289808
17531
1989
2010
1962
3978
1984
1995
2003
3958

The longest signals I care about appear to be no more than 18ms so I used some more awk to convert >20ms durations to newlines and everything else to CSV:

$ cat xx.vcd | sed -n 's/^#\(.*\)/\1/p' | awk 'NR>1{ print $1-last } {last=$1 }' | awk '{ if ($1 < 20000) printf "%s,", $1; else print ""; }'
17862,1998,2017,1968,3992,1992,2001,2019,2002,
17531,1989,2010,1962,3978,1984,1995,2003,3958,
17811,1995,1999,1997,4020,1964,1999,1993,2005,
17631,1996,2020,1967,3995,1991,2003,2012,3973,
17676,1986,1995,2004,3953,1983,1990,1987,2013,
17765,2008,1993,1956,3978,1989,2011,1960,3979,
...
17788,1995,2001,2016,3970,1993,1999,1997,2022,
17959,1966,1998,1992,4018,1968,1999,1994,4003,
17525,1989,2009,1961,3977,1985,1994,2005,1997,
17612,1965,1997,1991,3998,2012,2001,1963,3993,
17879,1963,1990,1983,3981,2005,1994,1956,1992,
17782,1993,1998,1996,4015,1963,2000,1990,4018,
17968,1970,1997,1992,3998,2013,2001,1964,2002,81,2,

Scanning vertically, the numbers looked close enough that felt like I might be onto something, so I threw them into a spreadsheet to verify (I ❤️ spreadsheets):

The signals looked pretty consistent until the last segment, 9 pulses of the following durations: 17.7ms, 2ms, 2ms, 2ms, 4ms, 2ms, 2ms, 2ms, ???. I split the last pulse into subsets above and below the average to get a pretty clear bimodal distribution, one right at 2ms and the other at 4ms:

Two signals for the two feet of each Tapper on the Dance Bus. I suppose I could have guessed that, but based on the preprogrammed dances I expected a single signal that just cycled from foot to foot. Now that I knew how the Tappers communicated to each other, I had to generate those signals myself.

Tapping Out Signals

Every kid wants an FT232H in their stocking

Back when I was a kid (in the days before our serial buses were universal) PCs had a parallel port. You could hook up printers and other peripherals (like Zip Drives and slow-ass network devices), but with the tiniest bit of ingenuity you could write code to turn certain pins on and off. We used them to do really cool stuff like control LEDs… and control other LEDs. The possibilities were endless.

Another party favor from that hardware hacking class was an Adafruit FT232H Breakout ($15) which Limor Fried (founder of Adafruit) called the “modern parallel port”. It’s a USB bridge that you can use to speak useful, well-known protocols like SPI and I2C, or — in our case — less-useful, dance protocols via general purpose input/output (GPIO) pins. This thing is super cool: extremely approachable, easy to use, no IDE. It’s a great starting point for anyone who wants to write software that can control simple electronics.

The Tappers are ~4.5V (3 x AAA batteries) and the FT232H outputs 3V signals, but it’s what I had so I went with it. Adafruit operates mostly in Python so I went with that too. Because I was paranoid about the resolution of time.sleep() I decided to spin for the allotted time which resulted in this code:

And I tacked on a little interactive test program:

Full of anticipation, I plugged in a Tapper expecting to j-k-j-k a little two-step and… nothing. So I hooked up the logic analyzer again, this time to my FT232H to see if it was producing the signals I expected. Here’s what I got:

That first signal should be 17.7ms but is only 2ms

The first low pulse that was supposed to be 17.7ms was only around 2ms. I thought a bit about the many sources of latency that could cause this, but then opted for urgency over curiosity, and added another 15.7 to that first value. I measured again and things looked good, so I hooked up the Tapper. Lo and behold, he started tapping:

One small tap for man…

I could move their feet now I needed to make them dance.

Tap Tap Revolution

I knew I couldn’t outtap the rhythmic geniuses from Hallmark, but I could tap into one of the great breakthroughs of our era: rhythm games. The greatest of these — in my completely unqualified opinions — is indisputably Beat Saber. It’s the killer app for VR. I’m not going to explain; just check it out:

VR is rad; mixed reality is incredible

Beyond just the songs arranged by the developer, there’s an awesome community of content creators that make new songs available for download on BeatSaver (and other places I infer). The file format is pretty simple. You can check out the code if you want the details, but eventually you’ll poke deep enough in a JSON file to find an array of objects that look like this:

{
"_time": 51.875,
"_lineIndex": 1,
"_lineLayer": 0,
"_type": 0,
"_cutDirection": 7
}

Time is in units of beats (we get BPM elsewhere); index and layer indicate the location of the block to hit; cut direction is pretty self-explanatory; type indicates which hand you need to hit the block with. I ignored location and direction, and just used time and type. With some simple Python, I unpacked the file format, played the music, and fed the tap data into my Tappers. Here’s the result:

So last decade…

You can plug in more Tappers to the Dance Bus so without further ado, Truth Hurts like you’ve never seen it before:

Took a DNA test, turns out I’m 100% that grinch…

There were a bunch of moving parts; I was stoked (and surprised) to see it all come together. It turns out not to be that hard, and the parts weren’t that expensive. Give it a shot; just don’t outbid me on eBay.

Parts List:

Optional:

--

--

Adam Leventhal

Building computers at Oxide; past: DTrace, ZFS, Delphix CTO, Transposit founder, CEO