CircuitPython Remote Lamp

We've designed the geometric Sjopenna's code to allow control from afar using a mini infrared remote control. Any remote will work if you properly decode the signals. This project is specifically coded for the Adafruit Mini Remote Control. We'll use the CircuitPython IR remote library to read and decode the IR signals.

This project uses many colors associated with different buttons on the IR remote. To keep the code as efficient as possible, we learned how to use a dictionary to eliminate the need for a large block of if and elif statements. Now we're going to incorporate that into this code.

Regarding Rainbows

The way that the CircuitPython library for the infrared remote code is written, it is not possible to have a rainbow cycle setting. The library code is blocking which does not allow for the cycle generator that we used for Spoka to function properly. It continues only when the board receives an infrared signal, because the read function will wait indefinitely until it reads a signal. Due to the significant IR noise present in most environments, at first glance, the rainbow cycle appears to be functioning. As well, if you were to set a rainbow to a particular button, and then hold the button down constantly, it would spam the signal and cycle the rainbow. However, if you cover the IR sensor to block any signals, you'll find that the cycle does not progress. In the event that intermittent noise is received, the progression is jumpy and inconsistent. Given that we cannot predict the amount of IR noise present in any given environment, or expect you to hold down a button forever, we've chosen not to include a rainbow cycle setting in this code.

The Code!

Due to memory constraints we will not be using the same Express class that we used for Spoka - therefore we will import and initialise each library separately. However, using that class would only eliminate the need to import neopixel and board. The Adafruit IR Remote library, adafruit_irremote, and pulseio are not included in Express class and would have been required regardless.

Let's take a look at the code!

import adafruit_irremote
import board
import neopixel
import pulseio

pixels = neopixel.NeoPixel(board.NEOPIXEL, 10)

pulsein = pulseio.PulseIn(board.REMOTEIN, maxlen=120, idle_state=True)
decoder = adafruit_irremote.GenericDecode()

last_command = None

brightness_up = 95  # Up arrow
brightness_down = 79  # Down arrow

command_to_color = {  # button = color
    247: (255, 0, 0),  # 1 = red
    119: (255, 40, 0),  # 2 = orange
    183: (255, 150, 0),  # 3 = yellow
    215: (0, 255, 0),  # 4 = green
    87: (0, 255, 120),  # 5 = teal
    151: (0, 255, 255),  # 6 = cyan
    231: (0, 0, 255),  # 7 = blue
    103: (180, 0, 255),  # 8 = purple
    167: (255, 0, 20),  # 9 = magenta
    207: (255, 255, 255),  # 0 = white
    127: (0, 0, 0),  # Play/Pause = off
}

while True:
    pulses = decoder.read_pulses(pulsein, max_pulse=5000)
    command = None
    try:
        code = decoder.decode_bits(pulses)
        if len(code) > 3:
            command = code[2]
        print("Decoded:", command)
        print("-------------")
    except adafruit_irremote.IRNECRepeatException:  # Catches the repeat signal
        command = last_command
    except adafruit_irremote.IRDecodeException:  # Failed to decode
        pass

    if not command:
        continue
    last_command = command

    if command == brightness_up:
        pixels.brightness += 0.1
    elif command == brightness_down:
        pixels.brightness -= 0.1
    elif command in command_to_color:
        pixels.fill(command_to_color[command])

First we import the four libraries we'll be using in our code. Then we setup use of those libraries. Now we'll take a look at the next section.

Variables

First we assign last_command for use later. Then, we assign brightness_up to the IR command code associated with the up arrow on the IR remote, and brightness_down to the code for the down arrow.

Dictionary

Here is where we use the dictionary we learned about!

command_to_color = {       # button = color
    247: (255, 0, 0),      # 1 = red
    119: (255, 40, 0),     # 2 = orange
    183: (255, 150, 0),    # 3 = yellow
    215: (0, 255, 0),      # 4 = green
    87: (0, 255, 120),     # 5 = teal
    151: (0, 255, 255),    # 6 = cyan
    231: (0, 0, 255),      # 7 = blue
    103: (180, 0, 255),    # 8 = purple
    167: (255, 0, 20),     # 9 = magenta
    207: (255, 255, 255),  # 0 = white
    127: (0, 0, 0),        # Play/Pause = off
}

The keys are the IR codes for the eleven buttons we're using and the values are their associated (r, g, b) tuples. NeoPixel colors are represented using red, green and blue in values of 0 - 255 to determine the amount of a given color. For example, red is (255, 0, 0) as it does not contain any green or blue. When red, green and blue are all off, the values are (0, 0, 0). We'll call this off, and use it to turn off the LEDs. We've used comments on each line to identify which button on the remote and assigned color the dictionary is referring to.

There are 21 buttons total on the Mini IR remote, and in this code, 13 of them are used. If you wanted to add more colors to this project, you can do so by extending the dictionary. You simply need to choose a button, add that key to the dictionary, and assign the desired (r, g, b) value to use them later in the code. Add a comment to the line to make it easier to remember what button and color you chose!

The Loop

The first two sections of code inside the loop are designed to read the incoming IR signals, decode them, and prepare them for practical use.

while True:
    pulses = decoder.read_pulses(pulsein, max_pulse=5000)
    command = None
    try:
        code = decoder.decode_bits(pulses)
        if len(code) > 3:
            command = code[2]
        print("Decoded:", command)
        print("-------------")
    except adafruit_irremote.IRNECRepeatException:  # Catches the repeat signal
        command = last_command
    except adafruit_irremote.IRDecodeException:  # Failed to decode
        pass

    if not command:
        continue
    last_command = command

We must deal with the significant amount of IR noise, which shows up as single-value signals. The line if len(code) > 3: says the signal must be longer longer than three values before we bother to do anything with it. The decoded signal from each button on this remote is four numbers in a list format: [0, 0, 0, 0]. However, for use, you need only the third number from that list. So, when we get a code of the correct length, we assign command to be the third value from the list by assigning command = code[2]. (Remember, in CircuitPython, counting starts with 0, so the third value is 2!)

We've left in the two print statements so you can identify the command code for the unused buttons on the remote in the event you'd like to expand the project to use them.

        print("Decoded:", command)
        print("-------------")

Simply connect to the REPL, press a button, and use the resulting number in your code. If you choose to add to the color dictionary, this is how you can find the IR code to include as the key!

The last section is where we're telling the code what to do when a particular button is pressed.

When the lamp first lights up, the brightness is set to the maximum. Brightness is a percentage of 0 to 100 represented by a value of 0.0 - 1.0, and is set using pixels.brightness().

    if command == brightness_up:
        pixels.brightness += 0.1
    elif command == brightness_down:
        pixels.brightness -= 0.1

When we press the down arrow, assigned to brightness_down, it decreases the brightness by 0.1 each time. When we press the up arrow, assigned to brightness_up, it increases the brightness by 0.1. This will not increase or decrease it beyond the minimum or maximum.

And finally, we get to call our dictionary!

    elif command in command_to_color:
        pixels.fill(command_to_color[command])

This last elif statement is checking to make sure that the key we're using is found in our dictionary. Without this check, your code will throw an error if you pressed a button not in use, as it cannot use a key or value that isn't found. The last line uses pixels.fill() and the values associated with the keys in the dictionary to turn our NeoPixels the chosen colors. This works because pixels.fill() expects the (r, g, b) values, we've associated the (r, g, b) values the keys in the dictionary. Like we learned earlier, this is how we take what would have been a huge block of elif statements and slimmed it down to one!

Now you can control a lamp from across the room with a little IR remote!

Last updated on Feb 19, 2018 Published on Feb 20, 2018