# SPDX-FileCopyrightText: 2016 Scott Shawcroft for Adafruit Industries
# SPDX-FileCopyrightText: 2017-2019 Kattni Rembor for Adafruit Industries
# SPDX-FileCopyrightText: 2022 Ryan Keith for Adafruit Industries
#
# SPDX-License-Identifier: MIT
# We have a lot of attributes for this complex library, as well as a lot of documentation.
# pylint: disable=too-many-instance-attributes, too-many-lines
"""
`adafruit_circuitplayground.circuit_playground_base`
====================================================
CircuitPython base class for Circuit Playground.
* `Circuit Playground Express <https://www.adafruit.com/product/3333>`_
* `Circuit Playground Bluefruit <https://www.adafruit.com/product/4333>`_.
* Author(s): Kattni Rembor, Scott Shawcroft, Ryan Keith
"""
import math
import array
import time
import os
import audiocore
import analogio
import board
import busio
import digitalio
import adafruit_lis3dh
import adafruit_thermistor
import neopixel
import touchio
try:
from typing import Optional, Iterator, List
from typing_extensions import Literal
from microcontroller import Pin
except ImportError:
pass
__version__ = "0.0.0+auto.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_CircuitPlayground.git"
[docs]
class Photocell:
"""Simple driver for analog photocell on the Circuit Playground Express and Bluefruit."""
# pylint: disable=too-few-public-methods
def __init__(self, pin: Pin) -> None:
self._photocell = analogio.AnalogIn(pin)
# TODO(tannewt): Calibrate this against another calibrated sensor.
@property
def light(self) -> int:
"""Light level."""
return self._photocell.value * 330 // (2**16)
[docs]
class CircuitPlaygroundBase: # pylint: disable=too-many-public-methods
"""Circuit Playground base class."""
_audio_out = None
SINE_WAVE = 0
SQUARE_WAVE = 1
def __init__(self) -> None:
# Define switch:
self._switch = digitalio.DigitalInOut(board.SLIDE_SWITCH)
self._switch.switch_to_input(pull=digitalio.Pull.UP)
# Define LEDs:
self._led = digitalio.DigitalInOut(board.D13)
self._led.switch_to_output()
self._pixels = neopixel.NeoPixel(board.NEOPIXEL, 10)
# Define sensors:
self._temp = adafruit_thermistor.Thermistor(
board.TEMPERATURE, 10000, 10000, 25, 3950
)
self._light = Photocell(board.LIGHT)
# Define touch:
# Initially, self._touches is an empty dictionary. When a touch is used
# for the first time, the pin is added as a key to the dictionary, with
# the corresponding TouchIn object added as the value. This saves a
# little RAM by only populating the dictionary as needed. For example,
# after `cp.touch_A2`, self._touches is equivalent to:
# { board.A2: TouchIn(board.A2) }
self._touches = {}
self._touch_threshold_adjustment = 0
# Define acceleration:
self._i2c = busio.I2C(board.ACCELEROMETER_SCL, board.ACCELEROMETER_SDA)
self._int1 = digitalio.DigitalInOut(board.ACCELEROMETER_INTERRUPT)
self._lis3dh = adafruit_lis3dh.LIS3DH_I2C(
self._i2c, address=0x19, int1=self._int1
)
self._lis3dh.range = adafruit_lis3dh.RANGE_8_G
# Define audio:
self._speaker_enable = digitalio.DigitalInOut(board.SPEAKER_ENABLE)
self._speaker_enable.switch_to_output(value=False)
self._sample = None
self._wave = None
self._wave_sample = None
# Initialise tap:
self._detect_taps = 1
self.detect_taps = 1
# Initialise buttons:
self._a = None
self._b = None
@property
def detect_taps(self) -> Literal[1, 2]:
"""Configure what type of tap is detected by ``cp.tapped``. Use ``1`` for single-tap
detection and ``2`` for double-tap detection. This does nothing without ``cp.tapped``.
.. image :: ../docs/_static/accelerometer.jpg
:alt: Accelerometer
To use with the Circuit Playground Express or Bluefruit:
.. code-block:: python
from adafruit_circuitplayground import cp
cp.detect_taps = 1
while True:
if cp.tapped:
print("Single tap detected!")
"""
return self._detect_taps
@staticmethod
def _default_tap_threshold(tap: Literal[1, 2]) -> int:
if (
"nRF52840" in os.uname().machine
): # If we're on a CPB, use a higher tap threshold
return 100 if tap == 1 else 70
# If we're on a CPX
return 90 if tap == 1 else 60
@detect_taps.setter
def detect_taps(self, value: Literal[1, 2]) -> None:
self._detect_taps = value
if value == 1:
self._lis3dh.set_tap(
value,
self._default_tap_threshold(value),
time_limit=4,
time_latency=50,
time_window=255,
)
if value == 2:
self._lis3dh.set_tap(
value,
self._default_tap_threshold(value),
time_limit=10,
time_latency=50,
time_window=255,
)
@property
def tapped(self) -> bool:
"""True once after a detecting a tap. Requires ``cp.detect_taps``.
.. image :: ../docs/_static/accelerometer.jpg
:alt: Accelerometer
Tap the Circuit Playground once for a single-tap, or quickly tap twice for a double-tap.
To use with Circuit Playground Express or Bluefruit:
.. code-block:: python
from adafruit_circuitplayground import cp
cp.detect_taps = 1
while True:
if cp.tapped:
print("Single tap detected!")
To use single and double tap together, you must have a delay between them. It
will not function properly without it. This example uses both by counting a
specified number of each type of tap before moving on in the code.
.. code-block:: python
from adafruit_circuitplayground import cp
# Set to check for single-taps.
cp.detect_taps = 1
tap_count = 0
# We're looking for 2 single-taps before moving on.
while tap_count < 2:
if cp.tapped:
tap_count += 1
print("Reached 2 single-taps!")
# Now switch to checking for double-taps
tap_count = 0
cp.detect_taps = 2
# We're looking for 2 double-taps before moving on.
while tap_count < 2:
if cp.tapped:
tap_count += 1
print("Reached 2 double-taps!")
print("Done.")
"""
return self._lis3dh.tapped
@property
def acceleration(self) -> adafruit_lis3dh.AccelerationTuple:
"""Obtain data from the x, y and z axes.
.. image :: ../docs/_static/accelerometer.jpg
:alt: Accelerometer
This example prints the values. Try moving the board to see how the
printed values change.
To use with the Circuit Playground Express or Bluefruit:
.. code-block:: python
from adafruit_circuitplayground import cp
while True:
x, y, z = cp.acceleration
print(x, y, z)
"""
return self._lis3dh.acceleration
[docs]
def shake(self, shake_threshold: int = 30) -> bool:
"""Detect when device is shaken.
:param int shake_threshold: The threshold shake must exceed to return true (Default: 30)
.. image :: ../docs/_static/accelerometer.jpg
:alt: Accelerometer
To use with the Circuit Playground Express or Bluefruit:
.. code-block:: python
from adafruit_circuitplayground import cp
while True:
if cp.shake():
print("Shake detected!")
Decreasing ``shake_threshold`` increases shake sensitivity, i.e. the code
will return a shake detected more easily with a lower ``shake_threshold``.
Increasing it causes the opposite. ``shake_threshold`` requires a minimum
value of 10 - 10 is the value when the board is not moving, therefore
anything less than 10 will erroneously report a constant shake detected.
.. code-block:: python
from adafruit_circuitplayground import cp
while True:
if cp.shake(shake_threshold=20):
print("Shake detected more easily than before!")
"""
return self._lis3dh.shake(shake_threshold=shake_threshold)
def _touch(self, pin: Pin) -> bool:
touchin = self._touches.get(pin)
if not touchin:
# First time referenced. Make TouchIn object for the pin
touchin = touchio.TouchIn(pin)
touchin.threshold += self._touch_threshold_adjustment
self._touches[pin] = touchin
return touchin.value
# We chose these verbose touch_A# names so that beginners could use it without understanding
# lists and the capital A to match the pin name. The capitalization is not strictly Python
# style, so everywhere we use these names, we whitelist the errors using:
@property
def touch_A1(self) -> bool: # pylint: disable=invalid-name
"""Detect touch on capacitive touch pad A1.
.. image :: ../docs/_static/capacitive_touch_pad_A1.jpg
:alt: Capacitive touch pad A1
To use with the Circuit Playground Express or Bluefruit:
.. code-block:: python
from adafruit_circuitplayground import cp
while True:
if cp.touch_A1:
print('Touched pad A1')
"""
return self._touch(board.A1)
@property
def touch_A2(self) -> bool: # pylint: disable=invalid-name
"""Detect touch on capacitive touch pad A2.
.. image :: ../docs/_static/capacitive_touch_pad_A2.jpg
:alt: Capacitive touch pad A2
To use with the Circuit Playground Express or Bluefruit:
.. code-block:: python
from adafruit_circuitplayground import cp
while True:
if cp.touch_A2:
print('Touched pad A2')
"""
return self._touch(board.A2)
@property
def touch_A3(self) -> bool: # pylint: disable=invalid-name
"""Detect touch on capacitive touch pad A3.
.. image :: ../docs/_static/capacitive_touch_pad_A3.jpg
:alt: Capacitive touch pad A3
To use with the Circuit Playground Express or Bluefruit:
.. code-block:: python
from adafruit_circuitplayground import cp
while True:
if cp.touch_A3:
print('Touched pad A3')
"""
return self._touch(board.A3)
@property
def touch_A4(self) -> bool: # pylint: disable=invalid-name
"""Detect touch on capacitive touch pad A4.
.. image :: ../docs/_static/capacitive_touch_pad_A4.jpg
:alt: Capacitive touch pad A4
To use with the Circuit Playground Express or Bluefruit:
.. code-block:: python
from adafruit_circuitplayground import cp
while True:
if cp.touch_A4:
print('Touched pad A4')
"""
return self._touch(board.A4)
@property
def touch_A5(self) -> bool: # pylint: disable=invalid-name
"""Detect touch on capacitive touch pad A5.
.. image :: ../docs/_static/capacitive_touch_pad_A5.jpg
:alt: Capacitive touch pad A5
To use with the Circuit Playground Express or Bluefruit:
.. code-block:: python
from adafruit_circuitplayground import cp
while True:
if cp.touch_A5:
print('Touched pad A5')
"""
return self._touch(board.A5)
@property
def touch_A6(self) -> bool: # pylint: disable=invalid-name
"""Detect touch on capacitive touch pad A6.
.. image :: ../docs/_static/capacitive_touch_pad_A6.jpg
:alt: Capacitive touch pad A6
To use with the Circuit Playground Express or Bluefruit:
.. code-block:: python
from adafruit_circuitplayground import cp
while True:
if cp.touch_A6:
print('Touched pad A6'
"""
return self._touch(board.A6)
@property
def touch_TX(self) -> bool: # pylint: disable=invalid-name
"""Detect touch on capacitive touch pad TX (also known as A7 on the Circuit Playground
Express) Note: can be called as ``touch_A7`` on Circuit Playground Express.
.. image :: ../docs/_static/capacitive_touch_pad_A7.jpg
:alt: Capacitive touch pad TX
To use with the Circuit Playground Express or Bluefruit:
.. code-block:: python
from adafruit_circuitplayground import cp
while True:
if cp.touch_A7:
print('Touched pad A7')
"""
return self._touch(board.TX)
[docs]
def adjust_touch_threshold(self, adjustment: int) -> None:
"""Adjust the threshold needed to activate the capacitive touch pads.
Higher numbers make the touch pads less sensitive.
:param int adjustment: The desired threshold increase
.. image :: ../docs/_static/capacitive_touch_pads.jpg
:alt: Capacitive touch pads
To use with the Circuit Playground Express or Bluefruit:
.. code-block:: python
from adafruit_circuitplayground import cp
cp.adjust_touch_threshold(200)
while True:
if cp.touch_A1:
print('Touched pad A1')
"""
for touch_in in self._touches.values():
touch_in.threshold += adjustment
self._touch_threshold_adjustment += adjustment
@property
def touch_pins(self) -> List[Pin]:
"""A list of all the pins that are set up as touchpad inputs"""
return list(self._touches.keys())
@property
def touched(self) -> List[Pin]:
"""A list of all the pins that are currently registering a touch"""
return [pin for pin, touchpad in self._touches.items() if touchpad.value]
@property
def pixels(self) -> neopixel.NeoPixel:
"""Sequence-like object representing the ten NeoPixels around the outside
of the Circuit Playground. Each pixel is at a certain index in the sequence
as labeled below. Colors can be RGB hex like 0x110000 for red where each
two digits are a color (0xRRGGBB) or a tuple like (17, 0, 0) where (R, G, B).
Set the global brightness using any number from 0 to 1 to represent a
percentage, i.e. 0.3 sets global brightness to 30%.
See `neopixel.NeoPixel` for more info.
.. image :: ../docs/_static/neopixel_numbering.jpg
:alt: NeoPixel order diagram
Here is an example that sets the first pixel green and the ninth red.
To use with the Circuit Playground Express or Bluefruit:
.. code-block:: python
from adafruit_circuitplayground import cp
cp.pixels.brightness = 0.3
cp.pixels[0] = 0x00FF00
cp.pixels[9] = (255, 0, 0)
"""
return self._pixels
@property
def button_a(self) -> bool:
"""``True`` when Button A is pressed. ``False`` if not.
.. image :: ../docs/_static/button_a.jpg
:alt: Button A
To use with the Circuit Playground Express or Bluefruit:
.. code-block:: python
from adafruit_circuitplayground import cp
while True:
if cp.button_a:
print("Button A pressed!")
"""
if self._a is None:
self._a = digitalio.DigitalInOut(board.BUTTON_A)
self._a.switch_to_input(pull=digitalio.Pull.DOWN)
return self._a.value
@property
def button_b(self) -> bool:
"""``True`` when Button B is pressed. ``False`` if not.
.. image :: ../docs/_static/button_b.jpg
:alt: Button B
To use with the Circuit Playground Express or Bluefruit:
.. code-block:: python
from adafruit_circuitplayground import cp
while True:
if cp.button_b:
print("Button B pressed!")
"""
if self._b is None:
self._b = digitalio.DigitalInOut(board.BUTTON_B)
self._b.switch_to_input(pull=digitalio.Pull.DOWN)
return self._b.value
@property
def switch(self) -> bool:
"""``True`` when the switch is to the left next to the music notes.
``False`` when it is to the right towards the ear.
.. image :: ../docs/_static/slide_switch.jpg
:alt: Slide switch
To use with the Circuit Playground Express or Bluefruit:
.. code-block:: python
from adafruit_circuitplayground import cp
import time
while True:
print("Slide switch:", cp.switch)
time.sleep(0.1)
"""
return self._switch.value
@property
def temperature(self) -> float:
"""The temperature in Celsius.
.. image :: ../docs/_static/thermistor.jpg
:alt: Temperature sensor
Converting this to Fahrenheit is easy!
To use with the Circuit Playground Express or Bluefruit:
.. code-block:: python
from adafruit_circuitplayground import cp
import time
while True:
temperature_c = cp.temperature
temperature_f = temperature_c * 1.8 + 32
print("Temperature celsius:", temperature_c)
print("Temperature fahrenheit:", temperature_f)
time.sleep(1)
"""
return self._temp.temperature
@property
def light(self) -> int:
"""The light level.
.. image :: ../docs/_static/light_sensor.jpg
:alt: Light sensor
Try covering the sensor next to the eye to see it change.
To use with the Circuit Playground Express or Bluefruit:
.. code-block:: python
from adafruit_circuitplayground import cp
import time
while True:
print("Light:", cp.light)
time.sleep(1)
"""
return self._light.light
@property
def red_led(self) -> bool:
"""The red led next to the USB plug marked D13.
.. image :: ../docs/_static/red_led.jpg
:alt: D13 LED
To use with the Circuit Playground Express or Bluefruit:
.. code-block:: python
from adafruit_circuitplayground import cp
import time
while True:
cp.red_led = True
time.sleep(0.5)
cp.red_led = False
time.sleep(0.5)
"""
return self._led.value
@red_led.setter
def red_led(self, value: bool) -> None:
self._led.value = value
@staticmethod
def _sine_sample(length: int) -> Iterator[int]:
tone_volume = (2**15) - 1
# Amplitude shift up in order to not have negative numbers
shift = 2**15
for i in range(length):
yield int(tone_volume * math.sin(2 * math.pi * (i / length)) + shift)
@staticmethod
def _square_sample(length: int) -> Iterator[int]:
# Square waves are MUCH louder than then sine
tone_volume = (2**16) - 1
half_length = length // 2
for _ in range(half_length):
yield tone_volume
for _ in range(half_length):
yield 0
def _generate_sample(self, length: int = 100, waveform: int = SINE_WAVE) -> None:
if self._sample is not None:
return
if waveform == self.SQUARE_WAVE:
self._wave = array.array("H", self._square_sample(length))
else:
self._wave = array.array("H", self._sine_sample(length))
self._sample = self._audio_out(board.SPEAKER) # pylint: disable=not-callable
self._wave_sample = audiocore.RawSample(self._wave)
[docs]
def play_tone(
self, frequency: int, duration: float, waveform: int = SINE_WAVE
) -> None:
"""Produce a tone using the speaker. Try changing frequency to change
the pitch of the tone.
:param int frequency: The frequency of the tone in Hz
:param float duration: The duration of the tone in seconds
:param int waveform: Type of waveform to be generated [SINE_WAVE, SQUARE_WAVE].
Default is SINE_WAVE.
.. image :: ../docs/_static/speaker.jpg
:alt: Onboard speaker
To use with the Circuit Playground Express or Bluefruit:
.. code-block:: python
from adafruit_circuitplayground import cp
cp.play_tone(440, 1)
"""
# Play a tone of the specified frequency (hz).
self.start_tone(frequency, waveform)
time.sleep(duration)
self.stop_tone()
[docs]
def start_tone(self, frequency: int, waveform: int = SINE_WAVE) -> None:
"""Produce a tone using the speaker. Try changing frequency to change
the pitch of the tone.
:param int frequency: The frequency of the tone in Hz
:param int waveform: Type of waveform to be generated [SINE_WAVE, SQUARE_WAVE].
Default is SINE_WAVE.
.. image :: ../docs/_static/speaker.jpg
:alt: Onboard speaker
To use with the Circuit Playground Express or Bluefruit:
.. code-block:: python
from adafruit_circuitplayground import cp
while True:
if cp.button_a:
cp.start_tone(262)
elif cp.button_b:
cp.start_tone(294)
else:
cp.stop_tone()
"""
self._speaker_enable.value = True
length = 100
if length * frequency > 350000:
length = 350000 // frequency
self._generate_sample(length, waveform)
# Start playing a tone of the specified frequency (hz).
self._wave_sample.sample_rate = int(len(self._wave) * frequency)
if not self._sample.playing:
self._sample.play(self._wave_sample, loop=True)
[docs]
def stop_tone(self) -> None:
"""Use with start_tone to stop the tone produced.
.. image :: ../docs/_static/speaker.jpg
:alt: Onboard speaker
To use with the Circuit Playground Express or Bluefruit:
.. code-block:: python
from adafruit_circuitplayground import cp
while True:
if cp.button_a:
cp.start_tone(262)
elif cp.button_b:
cp.start_tone(294)
else:
cp.stop_tone()
"""
# Stop playing any tones.
if self._sample is not None and self._sample.playing:
self._sample.stop()
self._sample.deinit()
self._sample = None
self._speaker_enable.value = False
[docs]
def play_file(self, file_name: str) -> None:
"""Play a .wav file using the onboard speaker.
:param file_name: The name of your .wav file in quotation marks including .wav
.. image :: ../docs/_static/speaker.jpg
:alt: Onboard speaker
To use with the Circuit Playground Express or Bluefruit:
.. code-block:: python
from adafruit_circuitplayground import cp
while True:
if cp.button_a:
cp.play_file("laugh.wav")
elif cp.button_b:
cp.play_file("rimshot.wav")
"""
# Play a specified file.
self.stop_tone()
self._speaker_enable.value = True
with self._audio_out( # pylint: disable=not-callable
board.SPEAKER
) as audio, audiocore.WaveFile(open(file_name, "rb")) as wavefile:
audio.play(wavefile)
while audio.playing:
pass
self._speaker_enable.value = False