Remote Connection Examples - Adafruit Circuit Playground Bluefruit¶
The following short Python programs will demonstrate linking together Bluefruit board across the network. These require the use of a Python program running on a laptop or desktop to forward data to and from a partner via the IDeATe MQTT server.
Each can be run by copying the program into code.py
on the CIRCUITPY drive
offered by the board. The text can be pasted directly from this page, or each
file can be downloaded from the CircuitPython sample code folder on this site.
Related Pages
Contents
Remote Touch¶
Direct download: cpb_remote_touch.py.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | # cpb_remote_touch.py
# Use touch sensitive pads and sound to control a lighting display. The display
# models a diffusion process, with each touch pad contributing an activation. A
# loud sound or physical tap will trigger a single flash.
# The same process can be coupled to a remote device via the serial port, e.g. by
# retransmitting the serial stream via a MQTT server. The same touch and sound data
# generates output messages, and input messages are processed the same as local input.
# This program uses only onboard hardware: capacitive touch sensing, sound level
# sensor, NeoPixel LEDs.
# ----------------------------------------------------------------
# Import the standard Python math library and time functions.
import math, time, sys
# Import the board-specific input/output library.
from adafruit_circuitplayground import cp
# Import the runtime for checking serial port status.
import supervisor
# Configure the NeoPixel LED array for bulk update using show().
cp.pixels.auto_write = False
# ----------------------------------------------------------------
# Initialize global variables for the main loop.
# The display update rate is precisely regulated using the following timer variables.
# The default is 20 Hz frame rate as set by step_interval.
step_timer = 0.0
last_time = time.monotonic()
step_interval = 0.05
# The core model state is an array of state variables. Note that the state is
# modeled for 12 uniformly spaced positions, even though only ten are populated
# with NeoPixels. This state is abstract but might represent heat moving
# through a ring of material.
state = [0.0] * 12 # create 12 element array of zeros
# The remote touch state can be updated via serial port messages.
remote_touches = [False]*6
# Measure the time since the last remote message, and reset after a period without data.
remote_touch_timer = False
# The serial port output rate is regulated using the following timer variables.
serial_timer = 0.0
serial_interval = 0.5
# ----------------------------------------------------------------
# Begin the main processing loop.
while True:
# Measure elapsed time and wait until the update timer has elapsed.
now = time.monotonic()
interval = now - last_time
last_time = now
step_timer -= interval
if step_timer < 0.0:
step_timer += step_interval
# Apply a first-order diffusion model to the state. This loop creates a new
# array to replace the previous state. The modulo operator (%) is used to 'wrap
# around' the end of the array to close the loop.
N = len(state)
new = [0.0] * N
for i in range(N):
new[i] = 0.9 * state[i] + 0.05 * state[(i-1) % N] + 0.05 * state[(i+1) % N]
# Replace the previous state.
state = new
# Apply a slow decay to represent leakage outside the ring.
for i in range(N):
state[i] = 0.97 * state[i]
# Check the serial input
if supervisor.runtime.serial_bytes_available:
line = sys.stdin.readline()
tokens = line.split()
if len(tokens) == 6:
try:
remote_touches = [int(token) > 0 for token in tokens]
remote_touch_timer = 4.0
except ValueError:
pass
# Apply touch inputs as excitation blended into nearby samples.
if cp.touch_A1 or remote_touches[0]: state[8] += 0.1 * (1.0 - state[ 8])
if cp.touch_A2 or remote_touches[1]: state[10] += 0.1 * (1.0 - state[10])
if cp.touch_A3 or remote_touches[2]: state[11] += 0.1 * (1.0 - state[11])
if cp.touch_A4 or remote_touches[3]: state[ 2] += 0.1 * (1.0 - state[ 2])
if cp.touch_A5 or remote_touches[4]: state[ 3] += 0.1 * (1.0 - state[ 3])
if cp.touch_A6 or remote_touches[5]: state[ 4] += 0.1 * (1.0 - state[ 4])
# Update the LED colors to reflect the state. Note that states 0 and 6
# are invisible as these positions do not have LEDs.
for i, s in enumerate(state):
if i != 0 and i != 6:
# Clamp the state value between 0.0 and 1.0.
value = min(max(s, 0.0), 1.0)
# Map the state index to an LED position index.
pos = i-1 if i < 6 else i-2
# Map 0.0 to dim blue and 1.0 to bright red.
cp.pixels[pos] = (255 * value, 0, 50 * (1.0 - value))
# If a loud sound is detected, override the normal display to flash white.
# A physical tap will also trigger the flash.
if cp.loud_sound():
# cp.pixels.fill((255,255,255))
pass
# Send new data to the physical LED chain.
cp.pixels.show()
serial_timer -= interval
if serial_timer < 0.0:
serial_timer += serial_interval
touches = ["1" if i else "0" for i in [cp.touch_A1, cp.touch_A2, cp.touch_A3, cp.touch_A4, cp.touch_A5, cp.touch_A6]]
print(" ".join(touches))
# if the remote touch is active, check for timeout
if remote_touch_timer is not False:
remote_touch_timer -= interval
if remote_touch_timer < 0.0:
remote_touch_timer = False
remote_touches = [False]*6
|
Remote Tones¶
Direct download: cpb_remote_tones.py.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 | # cpb_remote_tones.py
# Use touch sensitive pads to control a tone generator.
# The same process can be coupled to a remote device via the serial port,
# e.g. by retransmitting the serial stream via a MQTT server. The same touch
# data generates output messages, and input messages are processed the same as
# local input.
# This program uses only onboard hardware: capacitive touch sensing, speaker.
# ----------------------------------------------------------------
# Import the standard Python math library and time functions.
import math, time, sys
# Import the board-specific input/output library.
from adafruit_circuitplayground import cp
# Import the runtime for checking serial port status.
import supervisor
#================================================================
# Define a class to represent the algorithmic composition task.
class Arpeggiator(object):
# Define a dictionary of arpeggio patterns as a class attribute.
patterns = { 'maj': (0, 4, 7, 12),
'min': (0, 3, 7, 12),
'maj7': (0, 4, 7, 11),
'min7': (0, 3, 7, 11),
'dim7': (0, 3, 6, 10),
}
# Initialize an instance of the class.
def __init__(self):
# Current compositional state.
self.root_note = 60 # middle-C as a MIDI note
self.tonality = 'maj' # key for the patterns dictionary
self.tempo = 60 # beats per minute
# Internal state variables.
self._index = 0 # arpeggio index of next note to play
self._direction = 1 # index step direction
self._next_time = time.monotonic_ns() # clock time in nsec for next note update
return
# Update function to be called frequently to recompute outputs.
def poll(self):
now = time.monotonic_ns()
if now >= self._next_time:
self._next_time += 60000000000 // int(self.tempo) # add nanoseconds per beat
# look up the current arpeggio pattern
pattern = self.patterns[self.tonality]
# Select the next note play and advance the position.
if self._index <= 0:
# choose the root note of the arpeggio and advance up one step
note = self.root_note
self._index = 1
self._direction = 1
elif self._index >= len(pattern)-1:
# choose the top note of the arpeggio and advance down one step
note = self.root_note + pattern[-1]
self._index = len(pattern)-2
self._direction = -1
else:
# play either a rising or falling tone within the arpeggio
note = self.root_note + pattern[self._index]
self._index += self._direction
# Compute the tone to play and update the speaker output.
freq = self.midi_to_freq(note)
cp.stop_tone()
cp.start_tone(freq)
# ----------------------------------------------------------------
# Convert MIDI note value to frequency. This applies an equal temperament scale.
def midi_to_freq(self, midi_note):
MIDI_A0 = 21
freq_A0 = 27.5
return freq_A0 * math.pow(2.0, (midi_note - MIDI_A0) / 12.0)
#================================================================
# Initialize global variables for the main loop.
# Create an tone generator object, an instance of the Arpeggiator class.
arpeggiator = Arpeggiator()
# The local touch state can be updated from the capacitance sensing inputs.
local_touches = [False]*6
# Regulate the rate at which the touch sensors are sampled.
sensing_timer = time.monotonic_ns()
# The remote touch state can be updated via serial port messages.
remote_touches = [False]*6
# Measure the time since the last touch and reset after a period without data.
melody_timer = False
# Regulate the rate of local touch data sent to the serial port output.
serial_timer = time.monotonic_ns()
# ----------------------------------------------------------------
# Begin the main event processing loop.
while True:
# Read the nanosecond clock.
now = time.monotonic_ns()
# Check the serial input for a new line of remote data.
if supervisor.runtime.serial_bytes_available:
line = sys.stdin.readline()
tokens = line.split()
if len(tokens) == 6:
try:
# convert a list of six numbers into True/False values
remote_touches = [int(token) > 0 for token in tokens]
melody_timer = now + 4000000000 # 4 second timeout
except ValueError:
# ignore any input with a formatting error
pass
# Measure elapsed time and wait until the input sensing timer has elapsed.
if now >= sensing_timer:
sensing_timer += 100000000 # 0.1 second
# Read all the local touch sensors.
local_touches = [cp.touch_A1, cp.touch_A2, cp.touch_A3, cp.touch_A4, cp.touch_A5, cp.touch_A6]
# If touched, reset the fadeout timer.
if any(local_touches):
melody_timer = now + 4000000000 # 4 second timeout
# If the serial rate timer has elapsed, also send it to the network.
if now >= serial_timer:
serial_timer += 500000000 # 0.5 second
touches = ["1" if i else "0" for i in local_touches]
print(" ".join(touches))
# If anything has been recently touched, update the tone generation.
if any(local_touches) or any(remote_touches):
if local_touches[0] or remote_touches[0]:
arpeggiator.root_note = 60
elif local_touches[1] or remote_touches[1]:
arpeggiator.root_note = 72
elif local_touches[2] or remote_touches[2]:
arpeggiator.root_note = 84
if local_touches[3] or remote_touches[3]:
arpeggiator.tonality = 'maj'
elif local_touches[4] or remote_touches[4]:
arpeggiator.tonality = 'min'
elif local_touches[5] or remote_touches[5]:
arpeggiator.tonality = 'dim7'
arpeggiator.tempo = 120
# clear any events
local_touches = [False]*6
remote_touches = [False]*6
# If the melody is active, run the tone generator then check the fadeout process.
if melody_timer is not False:
# Run the tone generator.
arpeggiator.poll()
# If the melody timer has elapsed, advance the fadeout process.
if now > melody_timer:
# If all done, stop playing.
if arpeggiator.tempo <= 30:
melody_timer = False
cp.stop_tone()
else:
melody_timer += 1000000000 # 1 sec
arpeggiator.tempo *= 0.9
|