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