# Circuit Playground Express TV Zapper

## Overview

![](https://cdn-learn.adafruit.com/assets/assets/000/052/134/medium800/adafruit_gemma_pytv.png?1521401319)

Pew Pew! Televisions are _toast_ when you build this project, a universal TV zapper made with just your Circuit Playground Express. This guide will not only show you how to make a DIY universal remote, we'll also show how to grab data using a logic analyzer, parse it with Jupyter notebooks, compress it to fit into our little CircuitPython boards, and take advantage of the Python `eval` function to dynamically load data into memory.

### BUT FIRST...a story...

When I first made the Circuit Playground (a.k.a the 'Classic' AVR based one) I showed it off to my inspirational friend Mitch Altman. Mitch is a wonderful maker who travels to events and maker spaces to teach people soldering and electronics. We met over a decade ago when our mutual friend pt suggested we work together on the TV-B-Gone kit. See, [Mitch had been making and selling the TV-B-Gone](https://www.tvbgone.com/), which looked like this:

![](https://cdn-learn.adafruit.com/assets/assets/000/051/753/medium800/adafruit_gemma_tvbg_model_BLK-1.gif?1520616586)

And that one LED could reach pretty darn far, maybe 50 feet. But we wanted to see if we could design one that would go 300 feet! So we worked on a kit version and made this:

### TV-B-Gone Kit

[TV-B-Gone Kit](https://www.adafruit.com/product/73)
When&nbsp;we were putting together the TV-B-Gone kit,&nbsp;we started imagining a dystopian future, filled with televisions, where this kit would be super useful and we'd be a hero for having one. &nbsp;And while maybe that's dramatic, the TV-B-Gone is perfect&nbsp;for playing pranks...

Out of Stock
[Buy Now](https://www.adafruit.com/product/73)
[Related Guides to the Product](https://learn.adafruit.com/products/73/guides)
![Angled assembled TV-B-Gone kit in dramatic lighting.](https://cdn-shop.adafruit.com/640x480/73-07.jpg)

With a total of _four_ high powered LED blasters. It worked great, and Mitch, as we said, goes to events and does workshops, so this was a popular workshop kit.

But back to the tale at hand, I showed him the Circuit Playground and he said that it was really neat but if it had an IR LED it could act like a TV-B-Gone! And, frankly, I agreed. I couldn't fit an IR LED onto the original, but when I designed the _Express_ version, I made some space for an IR LED and receiver. So, here we are!

In this guide, we'll build a TV zapper using just your Circuit Playground Express, a battery pack, and CircuitPython. We also have a bonus page for making a miniature zapper using a Gemma M0

Info: 

# Circuit Playground Express TV Zapper

## Grabbing Data

Before we begin, we need to get some data. In particular, we'll need the 'power' codes for each TV we want to disable. That's the infrared transmissions, how often to turn the LED on and off. Back when we designed the TV-B-Gone kit Mitch provided me some data that I used to create the ATtiny85 lookup tables. But its been a long time and there's been a few 'generations' of updates to the code database. But we didn't have access to the code itself, it basically provided in binary-format only, on the raw chip that Mitch uses. So we'd have to extract those codes ourselves!

The simplest way to do that is to hook up an IR _receiver_ and point it at the IR _transmitter_ (LED) on the TV-B-Gone like so:

![](https://cdn-learn.adafruit.com/assets/assets/000/051/836/medium800/adafruit_gemma_IMG_2040.jpg?1520730201)

### IR (Infrared) Receiver Sensor

[IR (Infrared) Receiver Sensor](https://www.adafruit.com/product/157)
IR sensor tuned to 38KHz, perfect for receiving commands from a TV remote control. Runs at 3V to 5V so it's great for any microcontroller.  
  
To use, connect pin 3 (all the way to the right) to 5V power, pin 2 (middle) to ground and listen on pin 1. It doesn't do any decoding...

In Stock
[Buy Now](https://www.adafruit.com/product/157)
[Related Guides to the Product](https://learn.adafruit.com/products/157/guides)
![IR (Infrared) Receiver Sensor plugged into breadboard](https://cdn-shop.adafruit.com/640x480/157-00.jpg)

Then on the Arduino or CircuitPython board, run a program that will capture the Infrared signals and print them out. [We have some code on how to do that in this guide here](../../../../ir-sensor). But then I realized right after I wired the whole thing up that it actually wouldn't work. Why? Because the IR receiver is tuned to 38KHz but the transmission bursts can be modulated at a bunch of different frequencies, sometimes as high as 56KHz. While the receiver will still likely receive the data, it could get garbled, and in any case, it's demodulated so we can't know the original encoding frequency, and the TV's we're blasting may be more sensitive.

# It's Logic Analyzer Time!

OK no worries, we _have the technology to solve this!_ Instead of using an IR receiver to demodulate the signal, we'll tap directly into the GPIO pin on the TV-B-Gone and listen to the signals. In this case we're going to use an old [Saleae logic analyzer](https://www.adafruit.com/?q=saleae), but [sigrok](https://sigrok.org) can also do the job and may be more affordable. The data rates we're talking about here, no faster than 60KHz, are fairly slow. It's just that the data is very&nbsp;_long_, the TV-B-Gone transmits for over a minute!

![](https://cdn-learn.adafruit.com/assets/assets/000/051/837/medium800/adafruit_gemma_IMG_2044.jpg?1520730528)

Once we've performed the capture, we'll get a waveform like this:

![](https://cdn-learn.adafruit.com/assets/assets/000/051/754/medium800/adafruit_gemma_logicgrab.png?1520617891)

Each of those 'pillars' is a pulse of infrared light at a certain frequency, the submodulation means its easier for the TV receiver to tell the signal is for it. As mentioned before, that submodulation can vary, from 34KHz to 50KHz or more. If we zoom into the data we can see the details of each 'pillar' above:

![](https://cdn-learn.adafruit.com/assets/assets/000/051/755/medium800/adafruit_gemma_logicgrabdetail.png?1520617894)

Wonderful! now we just have to extract the frequencies, the time 'on' and the time 'off'. Its _parsing time!_

# Circuit Playground Express TV Zapper

## Parsing Data

Any logic analyzer with a software component will let you extract the data. Here's what the 'data dump' looks like from the Saleae software:

[raw_tvbgone.csv](https://cdn-learn.adafruit.com/assets/assets/000/051/838/original/raw_tvbgone.csv?1520730902)
You can open up this data in any spreadsheet program, you'll see a long list with **Sample** column (sample _time_) and a **Channel 0** (data) column. There's only one data column and we extracted only the transitions, so you'll see alternating numbers only.

![](https://cdn-learn.adafruit.com/assets/assets/000/051/839/medium800/adafruit_gemma_csv.png?1520731034)

The first sample is the pre-trigger (-1200000 us before trigger) you can just ignore that.

Afterwards, you see alternating 1's and 0's about 105 'somethings' apart. You might at first think its maybe ms or microseconds, but its not, its actually the sample # based on the sample _rate_. You need to know that the rate we sampled at here is **12MHz** so each sample point is **0.083 us**. doing the math, the period between a 0, 1 and back to zero transition is ~210 samples. The period is `210 * 0.083us = 17.5us`, which is the same as ~57KHz. So the first burst is 57KHz modulated.

We could go thru the entire 600,000 point CSV file but of course that would be tedious! Let's use python instead.

# Jupyter to the Rescue

Our new favorite way to manage data with python is to use **Jupyter** (also referred to sometimes as a Python notebook) Jupyter is free, and lets you do data analysis with ease, I personally like that data is managed in chunks, so you can read in all the data in one chunk, then do math in other chunks, rather than re-running the whooooole thing over and over.

Here's our notebook, you can load it with any Jupyter install you've got

[TV_B_Gone_parser.ipynb](https://cdn-learn.adafruit.com/assets/assets/000/098/884/original/TV_B_Gone_parser.ipynb?1611603747)
## Block #1

Let's start with the first block, where we read in the dataset:

![](https://cdn-learn.adafruit.com/assets/assets/000/051/841/medium800/adafruit_gemma_ipy1.png?1520732179)

Here, we open the `'raw tvbgone.csv'` file as `'r'`eadable text, then read the first two lines and toss them, then read each line, split the CSV into an list, then append the list to one big-ass list called `dataset`. At the end, we check, did we read the right number?

![](https://cdn-learn.adafruit.com/assets/assets/000/051/842/medium800/adafruit_gemma_endcs.png?1520732310)

Yep, last line is 603966 and we tossed the first line (text header) and first datapoint (the -1200000 pre-trigger marker) so 603964 is correct

## Block #2
OK this block is where we do all the work. so we'll chunk it up into pieces

In this code, we define our sample rate (12 MHz is a common rate), then loop thru the dataset, iterating through the length by 2's. the `hi_p` is 'high pulse', the amount of time we are at logic 1. `lo_p` is 'low pulse', the amount of time we are at logic 0. We also make a `hi2_p` which is the next pulse that goes high _after_ the low pulse. We will fake this if we're at the end of the dataset, otherwise, we just take the next point.

```
SAMPLERATE = 12000000       # 12 Mhz default

unusual_codes = []          # These are manchester coded or otherwise non-standard!

frequency_pairs = []
pulse_points = []

# This function eats up two points at a time (but peeks at the third) 
# to calculate the high and low pulse lengths. As a pair, it determines
# the frequency (usually 38KHz - 57KHz) and stores the freq in pulse_points
# until it gets to a long low pulse (e.g. between bits or signals). It then
# checks that the pulses so far are all the same frequency, and compresses 
# them into a triplet of the frequency, the amount of time that freq is emitted
# and the amount of time the signal is 0 into frequency_pairs
for p in range(0, len(dataset), 2):  # take points two at a time
    hi_p = dataset[p]
    lo_p = dataset[p+1]
    if (p+2) == len(dataset):
        # we make a fake final pulse
        hi2_p = [lo_p[0] + 100000, 1]
    else:
        hi2_p = dataset[p+2]
```

Now we do a quick assertion, that the high pulses should be value '1' and the low pulse should be value '0' and bail if somehow that happened.

Then, we take the actual _length of time in samples_ of the high and low pulses, by taking the differences (deltas) between the pulse's timecode and the next one. Once we have the deltas, add them to make one cycle, and divide by the samplerate to convert to seconds, then invert to get the frequency of those two pulses. This is basically the stuff we did by hand at the top of this page, but now its done in code.

```
        if (hi_p[1] != 1) or (lo_p[1] != 0) or (hi2_p[1] != 1):
        print("Error in matching pulse polarity")
        exit(0)
    delta_high = lo_p[0] - hi_p[0]  # length of high pulse
    delta_low = hi2_p[0] - lo_p[0]  # length of low pulse
    pulse_period = (delta_high + delta_low) / SAMPLERATE
    pulse_freq = 1 / pulse_period
    #print("%d, %d -&gt; %0.2f" % (delta_high, delta_low, pulse_freq))
  
```

Now we've got a pulse of on/off light. Check at the bottom of this block and we have this section after our special-case checks;

` # otherwise, add this pulse point`  
` pulse_points.append(pulse_freq)`

That is, assuming nothing special, we'll append the frequency reading we just made to a list for later handling. We'll do this 99% of the time, calculating the frequency of a pair of pulses, then appending until....

Now we come back to the special cases at the top of the&nbsp;`if` statement. If the low pulse is over 30 times longer than the high pulse, we're probably at the end of a pillar of modulated signal. (we picked 30 arbitrarily) Lets check if we have anything stored in `pulse_points`, if not it means we had a single blip of light, which is super weird (but did happen to us) So we store it in `unusual_codes`.

Otherwise, lets figure out what happened in this 'pillar' of pulses. We calculate `avg_freq` which is just the plain 'mean' average. Then we check that all the pulses are within 10% (over 0.9x and under 1.1x the average). If there is any such variation, we store for later and keep going. This did happen a few times, we just dropped these points.

Finally, if all the frequency-pulses in a pillar are within our exactly standards, we simplify them all down to a 3-part list. The list contains the average-frequency, the length that the pulses were active, and then that long-`delta_low` pulse converted to seconds.

We then loop around and keep going to the next 'pillar'

```
if 30*delta_high &lt; delta_low: # e.g. the last pulse
        if not pulse_points:
            print("#%d: %d, %d -&gt; %0.2f" % (p, delta_high, delta_low, pulse_freq))
            print("Found an unusual pulse, storing for later")
            unusual_codes.append([p, delta_high, delta_low])
            continue
        # Lets get the avg frequency of all the pulse_points (they do have some slight variation)
        avg_freq = sum(pulse_points) / len(pulse_points)
        if not all([ 0.9*avg_freq&lt;i&lt;1.1*avg_freq for i in pulse_points ]):
            print("#%d: %d, %d -&gt; %0.2f" % (p, delta_high, delta_low, pulse_freq))
            print("Found an unusual code, storing for later")
            unusual_codes.append(pulse_points)
            pulse_points = []
        # we'll just store the frequency, and the length of time on, then the length of time off
        # We add one pulse for the 'final' pair we're on now
        frequency_pairs.append([avg_freq, 1/avg_freq * (len(pulse_points)+1), delta_low / SAMPLERATE])
        pulse_points = []
        continue # go to next pair of pulses
# otherwise, add this pulse point
pulse_points.append(pulse_freq)
```

# Block #3
OK so far we've taken all the sub-modulated 1/0's and converted them to frequencies with on/off durations. In theory that's all we need to fully duplicate the TV-B-Gone, but it would be a huge amount of data and hard to manage. What we'll do now is group all the pulses within a chain, usually 10-30 are in a row, for an emitted code, and look like this (its common to have one big burst in the beginning to 'get the attention' of the TV)

![](https://cdn-learn.adafruit.com/assets/assets/000/051/881/medium800/adafruit_gemma_onecode.png?1520796057)

In order to know when a code is done, we'll look back at the logic analyzer data. Just from scanning the data it seems like a lot of codes are 'repeated' about 65ms apart

![](https://cdn-learn.adafruit.com/assets/assets/000/051/882/medium800/adafruit_gemma_codebreak.png?1520796353)

And then there is a 0.25 second delay between code-types:

![](https://cdn-learn.adafruit.com/assets/assets/000/051/883/medium800/adafruit_gemma_intracode.png?1520796387)

We want to keep the 'duplicated' codes together (we'll deal with 'compressing' them later) So as a 'Intra Code Delay" we'll pick 0.2 seconds.

In block #3, we kinda do the same thing we did in block #2, but instead of individual light pulses, we'll group together modulated light chunks:

```
# given the high frequency pairs, group them together by frequency and before a long (10ms?) pulse
all_codes = []
code = []
INTRA_CODE_DELAY = 0.2   # in seconds
for f in frequency_pairs:
    freq, high, low = f
    #print("%0.2f %0.2f @ %0.1f" % (high * 1000, low * 1000, freq))
    code.append(f)
    if low &gt; INTRA_CODE_DELAY:
        code_freqs = [p[0] for p in code]
        avg_freq = sum(code_freqs) / len(code_freqs)
        if not all([ 0.9*avg_freq&lt;i&lt;1.1*avg_freq for i in code_freqs ]):
            print("Got an abberant frequency, bailing!")
            code = []
            continue
        only_pulses = [[p[1], p[2]] for p in code]
        all_codes.append({'freq':avg_freq, 'pulses':only_pulses})
        code = []
        continue

print("Decoded: ", len(all_codes))
```

For each on/off pair, we add it to our list called code. We keep going until the 'off' half of a pair is longer than that 0.2 seconds in which case we'll assume all the pairs till now are grouped together. We take the average modulation frequency of all the pairs and verify all are within 10%. Once we know they're all the same frequency, we don't have to save that part anymore, so `only_pulse` contains only the on/off timings. We then put those pulses in a dictionary that has the overall modulation frequency and the pulses, save it to `all_codes` and continue until we've finished processing all the on/off pairs.

According to our script, we've got 207 codes, which means about 207 different brands/models of TVs.

If we ask Python to print out the first code with `print(all_codes[0])` we'll get this:

```
{'freq': 56697.911251837904, 'pulses': [[0.003968534673961258, 0.003993666666666667], [0.0004937998948659543, 0.0020033333333333335], [0.0004937998948659543, 0.00200325], [0.0004938864353312305, 0.00200325], [0.0004938864353312304, 0.0020033333333333335], [0.0004937998948659543, 0.0010096666666666667], [0.0004938864353312304, 0.00100975], [0.0004937998948659543, 0.00200325], [0.0004938864353312304, 0.00100975], [0.0004937998948659543, 0.0020033333333333335], [0.0004937998948659543, 0.0010096666666666667], [0.0004938864353312304, 0.0020033333333333335], [0.0004937998948659543, 0.0010096666666666667], [0.0004938864353312304, 0.00100975], [0.0004937998948659543, 0.0010096666666666667], [0.0004938864353312304, 0.00100975], [0.0004937998948659543, 0.0010096666666666667], [0.0004938864353312304, 0.0020033333333333335], [0.0004937998948659543, 0.00200325], [0.0004938864353312305, 0.00100975], [0.0004937998948659543, 0.00200325], [0.0004937998948659543, 0.0010096666666666667], [0.0004938864353312304, 0.0020033333333333335], [0.0004937998948659543, 0.00100975], [0.0004938864353312305, 0.00200325], [0.0004938864353312304, 0.007964666666666667], [0.003968450847028007, 0.003993666666666667], [0.0004937998948659543, 0.00200325], [0.0004937998948659543, 0.00200325], [0.0004938864353312304, 0.00200325], [0.0004938864353312304, 0.0020033333333333335], [0.0004937998948659543, 0.0010096666666666667], [0.0004938864353312305, 0.00100975], [0.0004937998948659543, 0.00200325], [0.0004938864353312305, 0.00100975], [0.0004937998948659543, 0.00200325], [0.0004938864353312305, 0.0010096666666666667], [0.0004938864353312304, 0.0020033333333333335], [0.0004937998948659543, 0.0010096666666666667], [0.0004938864353312304, 0.00100975], [0.0004937998948659543, 0.0010096666666666667], [0.0004938864353312304, 0.00100975], [0.0004938864353312305, 0.0010096666666666667], [0.0004938864353312304, 0.0020033333333333335], [0.0004937998948659543, 0.00200325], [0.0004938864353312305, 0.0010096666666666667], [0.0004938864353312304, 0.0020033333333333335], [0.0004937998948659543, 0.0010096666666666667], [0.0004938864353312304, 0.0020033333333333335], [0.0004937998948659543, 0.0010096666666666667], [0.0004938864353312304, 0.0020033333333333335], [0.0004937998948659543, 0.21241525]]}
```
Thanks to the precision of floating point numbers this is very wordy. Starting at the beginning, the dictionary item has&nbsp;`{'freq': 56697.911251837904` which implies that the average frequency of this code is about 56.7KHz. If we look at the logic analyzer, we see that this is correct (each pulse has slight variation)

![](https://cdn-learn.adafruit.com/assets/assets/000/051/884/medium800/adafruit_gemma_576khz.png?1520796983)

Zooming out, the first pillar starts at 0ms and ends at about 4ms, then is off for about 4 ms. Then the next pillar of pulses starts at about 7.9ms and ends at 8.3ms (so about 0.4ms long).

![](https://cdn-learn.adafruit.com/assets/assets/000/051/885/medium800/adafruit_gemma_firstpulse.png?1520797097)

That corresponds to the first few entries in our pulses list:

`'pulses': [[0.003968534673961258, 0.003993666666666667], [0.0004937998948659543, 0.0020033333333333335]`...

Note that all the times in the list are in seconds: Python has double-precision and we're not worried about running out of memory so double's are fine for storage. Anyhow, its always good to check what your parser puts out, compared to the raw data in the logic analyzer!

Let's continue!

# Block #4

Now we've got all our codes in a nice dictionary format, with the frequency and on/off pulses stored away. We're going to _keep_ making improvements to the formatting. Why? Well, for one, we want to compress the data a little so we can fit it on a Gemma. As is, the output of `all_codes` is 384KB

[block3codes.txt](https://cdn-learn.adafruit.com/assets/assets/000/051/903/original/block3codes.txt?1520806674)
Which will work on a Circuit Playground Express or other Express boards. But we wanted to make it fit in a Gemma M0 for a super-compact project, and that would require the whole source code to take less than about 40KB. So, time for compression!

First up, those double-precision floats take up a lot more space ascii-wise than if we just converted to micro-seconds which will keep each entry at about 2-4 digits rather than the 6+ we have now:

```
int_codes = []
for code in all_codes:
    # convert to integers and make a dictionary
    int_code = {'freq':int(code['freq'])}
    pulses = []
    for p in range(len(code['pulses'])):
        pulses.append(int(code['pulses'][p][0] * 1000000))   # convert to us
        pulses.append(int(code['pulses'][p][1] * 1000000))   # convert to us
    if len(pulses) % 2 == 0:
        x = pulses.pop()
        int_code['delay'] = x / 1000000  # convert to s
    int_code['pulses'] = pulses
```

We also make the dictionary object for the codes a little more comprehensive. To start, the frequency is converted to an integer (we really dont need to have more than 3 digits of precision for the frequency, so even this is overkill!) Then we go thru each pulse and multiply by 10<sup>6</sup>. The very last entry, which is the final 'off' pulse, is removed, and renamed 'delay' and re-converted to seconds.

Next, remember we mentioned a lot of codes are repeated? That gives you a better chance of hitting the TV. So, the remainder of this block is dividing the pulses in half, then comparing each on/off timing entry to verify its 'similar'

```
    # lets see if we can cut it in half and compare both halves
    half = len(int_code['pulses']) // 2
    left_half = int_code['pulses'][0:half]
    repeat_delay = int_code['pulses'][half]
    right_half = int_code['pulses'][half+1:]
    #print(left_half)
    #print(repeat_delay)
    #print(right_half)
    equiv = True
    for i in range(len(left_half)):
        if not similar(left_half[i], right_half[i]):
            equiv = False
            break
    if equiv:
        # many/most codes repeat twice!
        int_code['repeat'] = 2
        int_code['repeat_delay'] = repeat_delay / 1000000 # convert to seconds
        int_code['pulses'] = left_half
    else:
        #print("NOT REPEAT!")
        pass
    int_codes.append(int_code)
```

The middle 'off' pulse is the repeat delay, usually about 100ms (0.1 seconds). We have a helper function that checks if two values are within 5%, since the timings are slightly variant, we will accept that much variation to consider both 'halves' equivalent

```
def similar(a, b, percent=0.05):
  return (abs(1.0 - a / b) &lt; percent)
```

In theory we could check if the codes repeat 3 or 4 times instead of 2, but from scanning thru the data we could tell it was pretty much either once or twice per code.

Outputting all the `int_codes`, we see they now look like this (the first code)

```
{'freq': 56697, 'delay': 0.212415, 'pulses': [3968, 3993, 493, 2003, 493, 2003, 493, 2003, 493, 2003, 493, 1009, 493, 1009, 493, 2003, 493, 1009, 493, 2003, 493, 1009, 493, 2003, 493, 1009, 493, 1009, 493, 1009, 493, 1009, 493, 1009, 493, 2003, 493, 2003, 493, 1009, 493, 2003, 493, 1009, 493, 2003, 493, 1009, 493, 2003, 493], 'repeat': 2, 'repeat_delay': 0.007964}
```

Which is way more compact than the previous floating point and non-repeat-optimized version. Our entire text file of codes is now **82 KB** compared to the previous **382KB** - a very nice compression that 'cost' us nothing.

[block4codes.txt](https://cdn-learn.adafruit.com/assets/assets/000/051/922/original/block4codes.txt?1520811549)
# Block #5

Buuuut....82KB is still too big, we need it to be less than half that. Let's look at more ways to compress the data. Looking at the first code:

```
{'freq': 56697, 'delay': 0.212415, 'pulses': [3968, 3993, 493, 2003, 493, 2003, 493, 2003, 493, 2003, 493, 1009, 493, 1009, 493, 2003, 493, 1009, 493, 2003, 493, 1009, 493, 2003, 493, 1009, 493, 1009, 493, 1009, 493, 1009, 493, 1009, 493, 2003, 493, 2003, 493, 1009, 493, 2003, 493, 1009, 493, 2003, 493, 1009, 493, 2003, 493], 'repeat': 2, 'repeat_delay': 0.007964}
```

We see some patterns. The numbers **493** , **1009** , and **2003** show up a lot. In fact, its nearly all of the timing points! That's not too surprising, nearly all Infrared remotes send data that is encoded as 0's and 1's, and they do so with different length pulse pairs. In this code, there's 3 distinct pulse pairs:

1. `3968, 3993 `- This is the initial 'attention' pulse, about 4000us on and 4000us off
2. `493, 2003` - about 500us on, 2000us off, will be decoded as a zero or one
3. `493, 1009` - about 500us on, 1000us off, will be decoded as the opposite as the above pulse

Instead of just repeating those full values over and over, lets 'compress' the pairs by just having a single digit number for each pair. That's what we'll do in the next block:

```
paired_codes = []

for c in int_codes:
    print('-'*40)
    print(c)
    pair_table = []
    pair_lookup = []
    for p in range(0, len(c['pulses']), 2):
        pair = (c['pulses'][p:p+2])
        if len(pair) == 1:               # for the last entry, which is solitary
            for pairs in pair_table:     # match it up with the first pair we find
                if pair[0] == pairs[0]:  # where the first pulse matches
                    pair.append(pairs[1])# (put in a false 'off' pulse)
                    break
        if not pair in pair_table:
            pair_table.append(pair)
                
        pair_lookup.append(pair_table.index(pair))
    p_code = {'freq': c['freq'], 'delay': c['delay']}
    try:
        p_code['repeat'] = c['repeat']
        p_code['repeat_delay'] = c['repeat_delay']
    except KeyError:
        pass
    p_code['table'] = pair_table
    p_code['index'] = pair_lookup
    print(p_code)
    paired_codes.append(p_code)
```

After complete, you'll see comparisons of the pre-tableified and post codes like so:

```
{'freq': 56697, 'delay': 0.212415, 'pulses': [3968, 3993, 493, 2003, 493, 2003, 493, 2003, 493, 2003, 493, 1009, 493, 1009, 493, 2003, 493, 1009, 493, 2003, 493, 1009, 493, 2003, 493, 1009, 493, 1009, 493, 1009, 493, 1009, 493, 1009, 493, 2003, 493, 2003, 493, 1009, 493, 2003, 493, 1009, 493, 2003, 493, 1009, 493, 2003, 493], 'repeat': 2, 'repeat_delay': 0.007964}
{'freq': 56697, 'delay': 0.212415, 'repeat': 2, 'repeat_delay': 0.007964, 'table': [[3968, 3993], [493, 2003], [493, 1009]], 'index': [0, 1, 1, 1, 1, 2, 2, 1, 2, 1, 2, 1, 2, 2, 2, 2, 2, 1, 1, 2, 1, 2, 1, 2, 1, 1]}
```

As you can see, there's a new dictionary entry called '`table`' with 3 entries: `[[3968, 3993], [493, 2003], [493, 1009]]` and then an index list, starting with a `0`, then lots of `1`'s and `2`'s, those are the _indicies_ into the pulse pair table.

# Block #6

Now we're down to about&nbsp; **45KB** - which is pretty good. We could try to convert all the codes into pure binary format instead of having indices, but considering the wide range of encoding schemes, and that we've essentially reached our target codesize, we can stop.

We can squeeze just a tiny bit more space out by removing spaces and reducing the floating point precision. That's what the final block does, it rounds out the floating points and takes out all the whitespace, then writes the codes out to a text file that we can load into our CircuitPython Board

```
# Compactify and print!

with open("codes.txt", "w") as f:
    for code in paired_codes:
        code['delay'] = round(code['delay'],2)  # keep only 2 digits of precision for the long delay
        try:
            code['repeat_delay'] = round(code['repeat_delay'],3) # only 1ms precision for shot delay
        except KeyError:
            pass
        s = str(code).replace(' ', '')  # remove whitespace!
        print(s)
        f.write(s+"\n")
```

And here's the final output

[codes.txt](https://cdn-learn.adafruit.com/assets/assets/000/052/051/original/codes.txt?1521230499)
# Circuit Playground Express TV Zapper

## The Magic of eval()

OK we've done the hard part, we've extracted all the data from a TV-B-Gone, captured, parsed and compressed it. Now we've got a 45KB file of TV power codes in the form of python 'dictionaries'.

This is where things get a little interesting, and we get to take advantage of CircuitPython being an interpreted language.

If you've written this kind of code before in C or C++, you'd think "OK lets compile the codes in as raw binary data or as structures, and then refer to them in the compiled code".

With CircuitPython, you might thus think "OK lets just paste the codes.txt file into `main.py` and then iterate over the list" But if you tried that you'd quickly realize that with codes.txt being 45KB and the ATSAMD21 only has 32KB of RAM, so its impossible to load all this data into RAM.

# Disk Storage

The next possible thought you might have is "ok well if I cant fit it all into RAM, lets put it on disk and parse the codes, we just have to write a parser" After all, that's how you'd do it with an Arduino C/C++

**But, in python, we already&nbsp;_have_ that parser!** It's called `eval()` and its the engine of how Python and CircuitPython works.

Let's look at eval for a moment, so you can see how handy it is!

# Evaluating eval()

Open up your REPL to CircuitPython (we're using Mu) and type in the following:

```
1+1
```

You'll get this as a reply:

![](https://cdn-learn.adafruit.com/assets/assets/000/052/131/medium800/adafruit_gemma_112.png?1521397733)

2, is the _evaluated_ reply to 1+1

If you think about it, the text you wrote "1+1" is basically a command - saying "Hey CircuitPython parser, please add two numbers, as I've given them to you". The "1+1" command is not compiled into code, there's no lookup table where "1+1" is an entry and "2" is the output. Instead, the CircuitPython interpreter must **read** that text, parse the text, realize that its valid Python, and then **evaluate** it, before printing the answer.

We can be somewhat flexible in how we give it the text. For example, its valid Python to have a bunch of space between the + sign:

![](https://cdn-learn.adafruit.com/assets/assets/000/052/130/medium800/adafruit_gemma_spaces.png?1521397728)

But we cannot have spaces before the first digit:

![](https://cdn-learn.adafruit.com/assets/assets/000/052/129/medium800/adafruit_gemma_indent.png?1521397683)

Given that Python is interpreted, there is a function that takes the text you've typed in either here at the prompt, or in main.py and runs each line. That function is called `eval`, and you can run it by hand like so:

```
eval("1+1")
```

![](https://cdn-learn.adafruit.com/assets/assets/000/052/132/medium800/adafruit_gemma_eval2.png?1521397818)

Same as before, we get 2 back.

Since you're just passing in a character string, you can get quite creative. For example, you can create dynamic code like:

```
import random

maths = ["+", "-", "*", "/"]
eval("1" + maths[random.randint(0,3)] + "2")
```

This code creates a list with the 4 standard math operations, then evaluates a 'random' math operation between the integers 1 and 2 each time its run - creating a different string to evaluate each time:

![](https://cdn-learn.adafruit.com/assets/assets/000/052/133/medium800/adafruit_gemma_randeval.png?1521398196)

Which, again, if you have only ever written compiled code, is very unusual.

But, since its built into Python, we can take advantage of it to do parsing for us - we simply have CircuitPython read each line of code.txt which contains the printed out version of the dictionary item, and parse it directly as an evaluated string. Taa-dah, we've converted data on disk to code to data in memory with one function call!

# Circuit Playground Express TV Zapper

## CircuitPython Code

OK whew, finally we are ready to make our Circuit Playground Express zapper. There's no hardware changes or add-ons, you can use the CPX exactly as is thanks to the built-in IR emitter. You'll just need to save this code as `main.py` on your CPX:

https://github.com/adafruit/Adafruit_Learning_System_Guides/blob/main/CircuitPython_TVBGone/cpx_main/code.py

And save this file as codes.txt on the CPX mini disk drive as well (it contains the 200+ IR codes)

[codes.txt](https://raw.githubusercontent.com/adafruit/Adafruit_Learning_System_Guides/master/CircuitPython_TVBGone/codes.txt)
Let's go through what this code actually does!

# Stealth Mode

When you're in the field, you may want to avoid having something blinking in your hand. On the other hand, you want to know when its done transmitting so you can move on to the next target. So we use the built in slide switch for 'Stealth Mode':

```
############## Switch to select 'stealth-mode'
switch = DigitalInOut(board.SLIDE_SWITCH)
switch.direction = Direction.INPUT
switch.pull = Pull.UP
# Button to see output debug
led = DigitalInOut(board.D13)
led.direction = Direction.OUTPUT

############## Speaker as haptic feedback
spkr_en = DigitalInOut(board.SPEAKER_ENABLE)
spkr_en.direction = Direction.OUTPUT
spkr_en.value = True
spkr = DigitalInOut(board.SPEAKER)
spkr.direction = Direction.OUTPUT
```

If the switch is one way, the red LED will be used to blink when emitting a code. Flip the switch to have the speaker make a 'tic', a very small sound that you can hear if you're nearby but won't give you away!

# Buttons and IR output

We'll use the two buttons to tell when its time to zap! Since its hard to remember which is which while out in the field, we set up both buttons:

```
############## Allow any button to trigger activity!
button_a = DigitalInOut(board.BUTTON_A)
button_a.direction = Direction.INPUT
button_a.pull = Pull.DOWN
button_b = DigitalInOut(board.BUTTON_B)
button_b.direction = Direction.INPUT
button_b.pull = Pull.DOWN

```

The built in `REMOTEOUT` pin is connected to the IR LED, we just create a PWM output on that pin. Even though we set up the frequency as 38000, we'll change the frequency for each code (thus the `variable_frequency=True`)

```
pwm = pulseio.PWMOut(board.REMOTEOUT, frequency=38000, duty_cycle=2 ** 15, variable_frequency=True)
pulse = pulseio.PulseOut(pwm)
```

# Main Loop

Now we're ready. In the main loop, we'll wait for any button press. If we get a press, we pause a moment (to get lined up) and then open that `codes.txt` file to read in IR codes!

```
while True:
    # Wait for button press!
    while not (button_a.value or button_b.value):
        pass
    time.sleep(0.5)  # Give a half second before starting
    
    # gooooo!
    f = open("/codes.txt", "r")
```

Each line contains that dictionary entry. We use the magical eval() function to convert the text to a Python object. Depending on the switch position we either turn on the LED or give the speaker a pulse high.

```
for line in f:
        code = eval(line)
        print(code)
        if switch.value:
            led.value = True
        else:
            spkr.value = True
```

Then we can check the dictionary. Repeating codes have a 'repeat' and 'repeat\_delay' entry. If not, we'll assume we're transmitting only once.

```
        # If this is a repeating code, extract details
        try:
            repeat = code['repeat']
            delay = code['repeat_delay']
        except KeyError:   # by default, repeat once only!
            repeat = 1
            delay = 0
```

Then, we can take that code-pair table out, and 'de-index' the table to recreate the original on/off list (we need that list for the `pulse` output `send` function)

```
        # The table holds the on/off pairs
        table = code['table']
        pulses = []  # store the pulses here
        # Read through each indexed element
        for i in code['index']:
            pulses += table[i]  # and add to the list of pulses
        pulses.pop()  # remove one final 'low' pulse
```

Finally, set the PWM output frequency to whatever the TV is listening for, and send the pulse on/off codes, repeating if desired.

Once done, turn off the LED and speaker, and have one long inter-code delay.

```
        pwm.frequency = code['freq']
        for i in range(repeat):
            pulse.send(array.array('H', pulses))
            time.sleep(delay)
        
        led.value = False
        spkr.value = False
        time.sleep(code['delay'])
```

And when done, close the file so we can start over, waiting for another button press

```
    f.close()
```

That's it! You may want to run the code on the REPL the first time, to make sure you've got everything set up.

Note that the IR LED draws a couple hundred milli-Amps when sending IR data, so a good battery pack will help&nbsp; you get that range. You can expect up to 30 feet, depending on your targeting skills!


## Featured Products

### Circuit Playground Express

[Circuit Playground Express](https://www.adafruit.com/product/3333)
 **Circuit Playground Express** is the next step towards a perfect introduction to electronics and programming. We've taken the original Circuit Playground Classic and made it even better! Not only did we pack even more sensors in, we also made it even easier to...

In Stock
[Buy Now](https://www.adafruit.com/product/3333)
[Related Guides to the Product](https://learn.adafruit.com/products/3333/guides)
### Circuit Playground Express - Base Kit

[Circuit Playground Express - Base Kit](https://www.adafruit.com/product/3517)
It's the **Circuit Playground Express Base Kit!** &nbsp;It provides&nbsp;the few things you'll need to get started with the new [Circuit Playground Express](https://www.adafruit.com/product/3333).&nbsp;This version of Circuit Playground is super powered, and will...

In Stock
[Buy Now](https://www.adafruit.com/product/3517)
[Related Guides to the Product](https://learn.adafruit.com/products/3517/guides)
### Super-bright 5mm IR LED (25 pack)

[Super-bright 5mm IR LED (25 pack)](https://www.adafruit.com/product/388)
Infrared LEDs are used for remote controls (they're the little LED in the part you point at your TV) and 'night-vision' cameras, and these little blue guys are high powered ones! They are 940nm wavelength, which is what nearly all devices listen to. They're 20 degree beamwidth,...

In Stock
[Buy Now](https://www.adafruit.com/product/388)
[Related Guides to the Product](https://learn.adafruit.com/products/388/guides)
### Super-bright 5mm IR LED

[Super-bright 5mm IR LED](https://www.adafruit.com/product/387)
Infrared LEDs are used for remote controls (they're the little LED in the part you point at your TV) and 'night-vision' cameras, and these little blue guys are high powered ones! They are 940nm wavelength, which is what nearly all devices listen to. They're 20 degree beamwidth,...

Out of Stock
[Buy Now](https://www.adafruit.com/product/387)
[Related Guides to the Product](https://learn.adafruit.com/products/387/guides)
### TV-B-Gone, Skill badge, iron-on patch

[TV-B-Gone, Skill badge, iron-on patch](https://www.adafruit.com/product/470)
You turned off a TV with a kit you made! Adafruit offers a fun and exciting "badges" of achievement for electronics, science and engineering. We believe everyone should be able to be rewarded for learning a useful skill, a badge is just one of the many ways to show and share.<br...></br...>

No Longer Stocked
[Buy Now](https://www.adafruit.com/product/470)
[Related Guides to the Product](https://learn.adafruit.com/products/470/guides)
### TV-B-Gone - Sticker!

[TV-B-Gone - Sticker!](https://www.adafruit.com/product/647)
Celebrate turning off TVs or making a [TV-B-Gone](http://www.adafruit.com/category/20)! Adafruit offers a fun and exciting stickers to show your interest and achievements for electronics, science and engineering. We believe everyone should be able to be rewarded for learning a...

In Stock
[Buy Now](https://www.adafruit.com/product/647)
[Related Guides to the Product](https://learn.adafruit.com/products/647/guides)

## Related Guides

- [Adafruit Circuit Playground Express](https://learn.adafruit.com/adafruit-circuit-playground-express.md)
- [Light Up Paper Dragon Wall Sconce](https://learn.adafruit.com/light-up-paper-dragon-wall-sconce.md)
- [BOSEbuild Reactive Sound ](https://learn.adafruit.com/bosebuild-reactive-sound.md)
- [The Scream: Interactive Screaming Painting](https://learn.adafruit.com/the-scream-munch-screaming-interactive-scream-painting.md)
- [CircuitPython Essentials](https://learn.adafruit.com/circuitpython-essentials.md)
- [Microsoft Teams Mute Button](https://learn.adafruit.com/teams-mute-button.md)
- [Circuit Playground Express Sugar Glider](https://learn.adafruit.com/cpx-sugar-glider.md)
- [Circuit Playground Powerpuff Girls Valentine](https://learn.adafruit.com/circuit-playground-valentine.md)
- [TFT Gizmo Animated Eye](https://learn.adafruit.com/tft-gizmo-animated-eye.md)
- [Scratch 3 Walkthrough and Demo](https://learn.adafruit.com/guide-to-scratch-3.md)
- [Paper Airplane Launcher](https://learn.adafruit.com/paper-airplane-launcher-with-crickit.md)
- [Chinese Dragon Puppet with Motion-Reactive Flame Effect](https://learn.adafruit.com/chinese-dragon-puppet-with-motion-reactive-flame-effect.md)
- [Case for Circuit Playground](https://learn.adafruit.com/case-for-circuit-playground.md)
- [CircuitPython Snow Globe](https://learn.adafruit.com/circuitpython-snow-globe.md)
- [Using EduBlocks with Circuit Playground Express](https://learn.adafruit.com/using-edublocks-with-circuit-playground-express.md)
