The Spoka lamp appears designed for Circuit Playground Express. The board fits perfectly into a groove in the bottom which holds it in place and the lamp is easy to hold in your hand. These things make it perfect for using motion to control it.
We're going to use three different inputs: double-tap, shake, and rotation. All three of these inputs are motion based and use the accelerometer. These inputs will control different modes, speeds, brightness and turning the lamp off.
We'll use:
- double-tap to change color modes
- rotate left to change brightness
- rotate right to change speeds
- and shake to turn the lamp off
The nine different modes that double-tap will cycle through are:
- a smooth rainbow cycle
- 7 different static solid colors
- and a cycle through the 7 solid colors (party mode!)
The speed changes we will code affect the speed of the cycle modes, and do not affect the solid colors.
As all three of these inputs are motion based and use the same sensor, under certain circumstances, one input can be mistaken for another. If this happens consistently, try performing one of the motions differently. For example, perhaps you are double-tapping the lamp while it is sitting on the table, but it is moving around enough that the shake input is triggering. In that case, try holding it in your hand and double-tapping it. The same goes for any input that is being triggered inadvertently. Identify which one it is and modify your motion to only trigger the input that you're actively trying to use.
What Worked and What Didn't
We planned ahead of time to use IR to control Sjopenna, and this proved to work perfectly. Our little creature friend Spoka, however, didn't have any specific plans to begin with, because we wanted to experiment with all the options to see what worked. So, the first thing we did was test different inputs.
The Circuit Playground Express fits snugly into the bottom of Spoka and mostly covers the capacitive touch pads. We tried adding a strip of copper tape to the side that would make contact with one of the pads, but the tape didn't stick to the surface. The lamp itself is not at all conductive so sensing touch through the lamp itself was out. We tried using the sound sensor to have it respond to loud noises, however, the CPX is sealed enough into the lamp that sound didn't reach it effectively. We tried using the light sensor as an input, but the amount of light needed to trigger it couldn't get through the lamp housing. In the end, we decided to use motion to interact with this lamp - tap, shake and rotation all use the accelerometer, and all three work really well!
The Code!
We've learned how to use time.monotonic()
to create non-blocking code, how to create a state machine to use multi-step inputs, and how to use generators to allow for interruptible animation cycles. Now we'll put it all together.
Load the file on your Circuit Playground Express, and give it a try! Double-tap to switch between color modes. Rotate left and hold to change brightness. Rotate right and hold to change the speed of the rainbow modes. Shake to turn it off. Rotate left and hold while it's off to turn it back on.
# SPDX-FileCopyrightText: 2018 Kattni Rembor for Adafruit Industries # # SPDX-License-Identifier: MIT import time from rainbowio import colorwheel from adafruit_circuitplayground.express import cpx # pylint: disable=redefined-outer-name def upright(x, y, z): return abs(x) < accel_threshold \ and abs(y) < accel_threshold \ and abs(9.8 - z) < accel_threshold def right_side(x, y, z): return abs(-9.8 - x) < accel_threshold \ and abs(y) < accel_threshold \ and abs(z) < accel_threshold def left_side(x, y, z): return abs(9.8 - x) < accel_threshold \ and abs(y) < accel_threshold \ and abs(z) < accel_threshold # pylint: enable=redefined-outer-name def cycle_sequence(seq): while True: for elem in seq: yield elem def rainbow_lamp(seq): g = cycle_sequence(seq) while True: # pylint: disable=stop-iteration-return cpx.pixels.fill(colorwheel(next(g))) yield def brightness_lamp(): brightness_value = cycle_sequence([0.4, 0.6, 0.8, 1, 0.2]) while True: # pylint: disable=stop-iteration-return cpx.pixels.brightness = next(brightness_value) yield color_sequences = cycle_sequence([ range(256), # rainbow_cycle [0], # red [10], # orange [30], # yellow [85], # green [137], # cyan [170], # blue [213], # purple [0, 10, 30, 85, 137, 170, 213], # party mode ]) heart_rates = cycle_sequence([0, 0.5, 1.0]) brightness = brightness_lamp() heart_rate = 0 last_heart_beat = time.monotonic() next_heart_beat = last_heart_beat + heart_rate rainbow = None state = None hold_end = None cpx.detect_taps = 2 accel_threshold = 2 cpx.pixels.brightness = 0.2 hold_time = 1 while True: now = time.monotonic() x, y, z = cpx.acceleration if left_side(x, y, z): if state is None or not state.startswith("left"): hold_end = now + hold_time state = "left" elif (state == "left" and hold_end is not None and now >= hold_end): state = "left-done" next(brightness) elif right_side(x, y, z): if state is None or not state.startswith("right"): hold_end = now + hold_time state = "right" elif (state == "right" and hold_end is not None and now >= hold_end): state = "right-done" heart_rate = next(heart_rates) last_heart_beat = now next_heart_beat = last_heart_beat + heart_rate elif upright(x, y, z): if state != "upright": hold_end = None state = "upright" if cpx.tapped or rainbow is None: rainbow = rainbow_lamp(next(color_sequences)) if now >= next_heart_beat: next(rainbow) last_heart_beat = now next_heart_beat = last_heart_beat + heart_rate if cpx.shake(shake_threshold=20): cpx.pixels.brightness = 0
We've combined everything we learned to create this amazingly interactive lamp! We've already learned in detail how to do everything we use in this program. Now we'll take a quick look at the code so we can see how it all fits together.
The Code!
We start with the wheel
code, and our definitions of upright
, right_side
and left_side
.
Next, we include all of our generators. We have our special cycle_sequence
generator and rainbow_lamp
. We also have brightness_lamp
which includes the different brightness levels. Then we have color_sequences
and heart_rates
.
We assign brightness_lamp()
to a variable so we can use it later in the code.
The next section sets up the time.monotonic()
variables.
Following that, we create the rainbow
, state
and hold_end
variables for later use.
Next, we set the code to look for double-taps and set the threshold for rotation orientation to 2
. We set the brightness on startup to 20% (expressed as 0.2
). We set the length of time required to hold in a rotated state to 1
second. If you'd like your state machine to require a different hold time, change this number!
With that, we start the loop! First, we get the current time and call acceleration.
Then we have our state machine. If you rotate left and hold, it cycles to the next brightness level in the list. If you rotate right and hold, it uses some of our time.monotonic()
variables to help with cycling to the next speed.
Next, the code waits for a double-tap to cycle to the next color mode.
The next section uses the current speed and our time.monotonic()
variables to determine how fast to display the rainbow color modes, by determining how fast to call next
on rainbow
.
And the last section turns the lamp off if you shake it.
And that's it!
Now you have an interactive creature friend to light up your life in all kinds of colors!
Text editor powered by tinymce.