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