/ adafruit_clue.py
adafruit_clue.py
  1  # SPDX-FileCopyrightText: Copyright (c) 2020 Kattni Rembor for Adafruit Industries
  2  #
  3  # SPDX-License-Identifier: MIT
  4  # The MIT License (MIT)
  5  #
  6  # Copyright (c) 2020 Kattni Rembor for Adafruit Industries
  7  #
  8  # Permission is hereby granted, free of charge, to any person obtaining a copy
  9  # of this software and associated documentation files (the "Software"), to deal
 10  # in the Software without restriction, including without limitation the rights
 11  # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 12  # copies of the Software, and to permit persons to whom the Software is
 13  # furnished to do so, subject to the following conditions:
 14  #
 15  # The above copyright notice and this permission notice shall be included in
 16  # all copies or substantial portions of the Software.
 17  #
 18  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 19  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 20  # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 21  # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 22  # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 23  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 24  # THE SOFTWARE.
 25  """
 26  `adafruit_clue`
 27  ================================================================================
 28  
 29  A high level library representing all the features of the Adafruit CLUE.
 30  
 31  
 32  * Author(s): Kattni Rembor
 33  
 34  Implementation Notes
 35  --------------------
 36  
 37  **Hardware:**
 38  
 39  .. "* `Adafruit CLUE - nRF52840 Express with Bluetooth LE <https://www.adafruit.com/product/4500>`_"
 40  
 41  **Software and Dependencies:**
 42  
 43  * Adafruit CircuitPython firmware for the supported boards:
 44    https://github.com/adafruit/circuitpython/releases
 45  
 46   * Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
 47   * Adafruit's Register library: https://github.com/adafruit/Adafruit_CircuitPython_Register
 48   * Adafruit's LSM6DS CircuitPython Library:
 49     https://github.com/adafruit/Adafruit_CircuitPython_LSM6DS
 50   * Adafruit's LIS3MDL CircuitPython Library:
 51     https://github.com/adafruit/Adafruit_CircuitPython_LIS3MDL
 52   * Adafruit's APDS9960 CircuitPython Library:
 53     https://github.com/adafruit/Adafruit_CircuitPython_APDS9960
 54   * Adafruit's BMP280 CircuitPython Library:
 55     https://github.com/adafruit/Adafruit_CircuitPython_BMP280
 56   * Adafruit's SHT31D CircuitPython Library:
 57     https://github.com/adafruit/Adafruit_CircuitPython_SHT31D
 58   * Adafruit's NeoPixel CircuitPython Library:
 59     https://github.com/adafruit/Adafruit_CircuitPython_NeoPixel
 60  """
 61  
 62  import time
 63  import array
 64  import math
 65  import board
 66  import digitalio
 67  import neopixel
 68  import adafruit_apds9960.apds9960
 69  import adafruit_bmp280
 70  import adafruit_lis3mdl
 71  import adafruit_lsm6ds.lsm6ds33
 72  import adafruit_sht31d
 73  import audiobusio
 74  import audiopwmio
 75  import audiocore
 76  import gamepad
 77  import touchio
 78  
 79  __version__ = "0.0.0-auto.0"
 80  __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_CLUE.git"
 81  
 82  
 83  class _ClueSimpleTextDisplay:
 84      """Easily display lines of text on CLUE display."""
 85  
 86      def __init__(  # pylint: disable=too-many-arguments
 87          self,
 88          title=None,
 89          title_color=0xFFFFFF,
 90          title_scale=1,
 91          text_scale=1,
 92          font=None,
 93          colors=None,
 94      ):
 95          # pylint: disable=import-outside-toplevel
 96          import displayio
 97          import terminalio
 98          from adafruit_display_text import label
 99  
100          # pylint: enable=import-outside-toplevel
101  
102          if not colors:
103              colors = (
104                  Clue.VIOLET,
105                  Clue.GREEN,
106                  Clue.RED,
107                  Clue.CYAN,
108                  Clue.ORANGE,
109                  Clue.BLUE,
110                  Clue.MAGENTA,
111                  Clue.SKY,
112                  Clue.YELLOW,
113                  Clue.PURPLE,
114              )
115  
116          self._colors = colors
117          self._label = label
118          self._display = board.DISPLAY
119          self._font = terminalio.FONT
120          if font:
121              self._font = font
122  
123          self.text_group = displayio.Group(max_size=20, scale=text_scale)
124  
125          if title:
126              # Fail gracefully if title is longer than 60 characters.
127              if len(title) > 60:
128                  raise ValueError("Title must be 60 characters or less.")
129  
130              title = label.Label(
131                  self._font,
132                  text=title,
133                  max_glyphs=60,
134                  color=title_color,
135                  scale=title_scale,
136              )
137              title.x = 0
138              title.y = 8
139              self._y = title.y + 18
140  
141              self.text_group.append(title)
142          else:
143              self._y = 3
144  
145          self._lines = []
146          for num in range(1):
147              self._lines.append(self.add_text_line(color=colors[num % len(colors)]))
148  
149      def __getitem__(self, item):
150          """Fetch the Nth text line Group"""
151          if len(self._lines) - 1 < item:
152              for _ in range(item - (len(self._lines) - 1)):
153                  self._lines.append(
154                      self.add_text_line(color=self._colors[item % len(self._colors)])
155                  )
156          return self._lines[item]
157  
158      def add_text_line(self, color=0xFFFFFF):
159          """Adds a line on the display of the specified color and returns the label object."""
160          text_label = self._label.Label(self._font, text="", max_glyphs=45, color=color)
161          text_label.x = 0
162          text_label.y = self._y
163          self._y = text_label.y + 13
164          self.text_group.append(text_label)
165  
166          return text_label
167  
168      def show(self):
169          """Call show() to display the data list."""
170          self._display.show(self.text_group)
171  
172      def show_terminal(self):
173          """Revert to terminalio screen."""
174          self._display.show(None)
175  
176  
177  class Clue:  # pylint: disable=too-many-instance-attributes, too-many-public-methods
178      """Represents a single CLUE."""
179  
180      # Color variables available for import.
181      RED = (255, 0, 0)
182      YELLOW = (255, 255, 0)
183      ORANGE = (255, 150, 0)
184      GREEN = (0, 255, 0)
185      TEAL = (0, 255, 120)
186      CYAN = (0, 255, 255)
187      BLUE = (0, 0, 255)
188      PURPLE = (180, 0, 255)
189      MAGENTA = (255, 0, 150)
190      WHITE = (255, 255, 255)
191      BLACK = (0, 0, 0)
192  
193      GOLD = (255, 222, 30)
194      PINK = (242, 90, 255)
195      AQUA = (50, 255, 255)
196      JADE = (0, 255, 40)
197      AMBER = (255, 100, 0)
198      VIOLET = (255, 0, 255)
199      SKY = (0, 180, 255)
200  
201      RAINBOW = (RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE)
202  
203      def __init__(self):
204          # Define I2C:
205          self._i2c = board.I2C()
206  
207          # Define touch:
208          # Initially, self._touches stores the pin used for a particular touch. When that touch is
209          # used for the first time, the pin is replaced with the corresponding TouchIn object.
210          # This saves a little RAM over using a separate read-only pin tuple.
211          # For example, after `clue.touch_2`, self._touches is equivalent to:
212          # [board.D0, board.D1, touchio.TouchIn(board.D2)]
213          self._touches = [board.D0, board.D1, board.D2]
214          self._touch_threshold_adjustment = 0
215  
216          # Define buttons:
217          self._a = digitalio.DigitalInOut(board.BUTTON_A)
218          self._a.switch_to_input(pull=digitalio.Pull.UP)
219          self._b = digitalio.DigitalInOut(board.BUTTON_B)
220          self._b.switch_to_input(pull=digitalio.Pull.UP)
221          self._gamepad = gamepad.GamePad(self._a, self._b)
222  
223          # Define LEDs:
224          self._white_leds = digitalio.DigitalInOut(board.WHITE_LEDS)
225          self._white_leds.switch_to_output()
226          self._pixel = neopixel.NeoPixel(board.NEOPIXEL, 1)
227          self._red_led = digitalio.DigitalInOut(board.L)
228          self._red_led.switch_to_output()
229  
230          # Define audio:
231          self._mic = audiobusio.PDMIn(
232              board.MICROPHONE_CLOCK,
233              board.MICROPHONE_DATA,
234              sample_rate=16000,
235              bit_depth=16,
236          )
237          self._sample = None
238          self._samples = None
239          self._sine_wave = None
240          self._sine_wave_sample = None
241  
242          # Define sensors:
243          # Accelerometer/gyroscope:
244          self._accelerometer = adafruit_lsm6ds.lsm6ds33.LSM6DS33(self._i2c)
245  
246          # Magnetometer:
247          self._magnetometer = adafruit_lis3mdl.LIS3MDL(self._i2c)
248  
249          # DGesture/proximity/color/light sensor:
250          self._sensor = adafruit_apds9960.apds9960.APDS9960(self._i2c)
251  
252          # Humidity sensor:
253          self._humidity = adafruit_sht31d.SHT31D(self._i2c)
254  
255          # Barometric pressure sensor:
256          self._pressure = adafruit_bmp280.Adafruit_BMP280_I2C(self._i2c)
257  
258          # Create displayio object for passing.
259          self.display = board.DISPLAY
260  
261      def _touch(self, i):
262          if not isinstance(self._touches[i], touchio.TouchIn):
263              # First time referenced. Get the pin from the slot for this touch
264              # and replace it with a TouchIn object for the pin.
265              self._touches[i] = touchio.TouchIn(self._touches[i])
266              self._touches[i].threshold += self._touch_threshold_adjustment
267          return self._touches[i].value
268  
269      @property
270      def touch_0(self):
271          """Detect touch on capacitive touch pad 0.
272  
273          .. image :: ../docs/_static/pad_0.jpg
274            :alt: Pad 0
275  
276          This example prints when pad 0 is touched.
277  
278          To use with the CLUE:
279  
280          .. code-block:: python
281  
282            from adafruit_clue import clue
283  
284            while True:
285                if clue.touch_0:
286                    print("Touched pad 0")
287          """
288          return self._touch(0)
289  
290      @property
291      def touch_1(self):
292          """Detect touch on capacitive touch pad 1.
293  
294          .. image :: ../docs/_static/pad_1.jpg
295            :alt: Pad 1
296  
297          This example prints when pad 1 is touched.
298  
299          To use with the CLUE:
300  
301          .. code-block:: python
302  
303            from adafruit_clue import clue
304  
305            while True:
306                if clue.touch_1:
307                    print("Touched pad 1")
308          """
309          return self._touch(1)
310  
311      @property
312      def touch_2(self):
313          """Detect touch on capacitive touch pad 2.
314  
315          .. image :: ../docs/_static/pad_2.jpg
316            :alt: Pad 2
317  
318          This example prints when pad 2 is touched.
319  
320          To use with the CLUE:
321  
322          .. code-block:: python
323  
324            from adafruit_clue import clue
325  
326            while True:
327                if clue.touch_2:
328                    print("Touched pad 2")
329          """
330          return self._touch(2)
331  
332      @property
333      def button_a(self):
334          """``True`` when Button A is pressed. ``False`` if not.
335  
336          .. image :: ../docs/_static/button_a.jpg
337            :alt: Button A
338  
339          This example prints when button A is pressed.
340  
341          To use with the CLUE:
342  
343          .. code-block:: python
344  
345            from adafruit_clue import clue
346  
347            while True:
348                if clue.button_a:
349                    print("Button A pressed")
350          """
351          return not self._a.value
352  
353      @property
354      def button_b(self):
355          """``True`` when Button B is pressed. ``False`` if not.
356  
357          .. image :: ../docs/_static/button_b.jpg
358            :alt: Button B
359  
360          This example prints when button B is pressed.
361  
362          To use with the CLUE:
363  
364          .. code-block:: python
365  
366            from adafruit_clue import clue
367  
368            while True:
369                if clue.button_b:
370                    print("Button B pressed")
371          """
372          return not self._b.value
373  
374      @property
375      def were_pressed(self):
376          """Returns a set of the buttons that have been pressed.
377  
378          .. image :: ../docs/_static/button_b.jpg
379            :alt: Button B
380  
381          To use with the CLUE:
382  
383          .. code-block:: python
384  
385            from adafruit_clue import clue
386  
387            while True:
388                print(clue.were_pressed)
389          """
390          ret = set()
391          pressed = self._gamepad.get_pressed()
392          for button, mask in (("A", 0x01), ("B", 0x02)):
393              if mask & pressed:
394                  ret.add(button)
395          return ret
396  
397      def shake(self, shake_threshold=30, avg_count=10, total_delay=0.1):
398          """
399          Detect when the accelerometer is shaken. Optional parameters:
400  
401          :param shake_threshold: Increase or decrease to change shake sensitivity. This
402                                  requires a minimum value of 10. 10 is the total
403                                  acceleration if the board is not moving, therefore
404                                  anything less than 10 will erroneously report a constant
405                                  shake detected. (Default 30)
406  
407          :param avg_count: The number of readings taken and used for the average
408                            acceleration. (Default 10)
409  
410          :param total_delay: The total time in seconds it takes to obtain avg_count
411                              readings from acceleration. (Default 0.1)
412           """
413          shake_accel = (0, 0, 0)
414          for _ in range(avg_count):
415              # shake_accel creates a list of tuples from acceleration data.
416              # zip takes multiple tuples and zips them together, as in:
417              # In : zip([-0.2, 0.0, 9.5], [37.9, 13.5, -72.8])
418              # Out: [(-0.2, 37.9), (0.0, 13.5), (9.5, -72.8)]
419              # map applies sum to each member of this tuple, resulting in a
420              # 3-member list. tuple converts this list into a tuple which is
421              # used as shake_accel.
422              shake_accel = tuple(map(sum, zip(shake_accel, self.acceleration)))
423              time.sleep(total_delay / avg_count)
424          avg = tuple(value / avg_count for value in shake_accel)
425          total_accel = math.sqrt(sum(map(lambda x: x * x, avg)))
426          return total_accel > shake_threshold
427  
428      @property
429      def acceleration(self):
430          """Obtain acceleration data from the x, y and z axes.
431  
432          .. image :: ../docs/_static/accelerometer.jpg
433            :alt: Accelerometer
434  
435          This example prints the values. Try moving the board to see how the printed values change.
436  
437          To use with the CLUE:
438  
439          .. code-block:: python
440  
441            from adafruit_clue import clue
442  
443            while True:
444                print("Accel: {:.2f} {:.2f} {:.2f}".format(*clue.acceleration))
445          """
446          return self._accelerometer.acceleration
447  
448      @property
449      def gyro(self):
450          """Obtain x, y, z angular velocity values in degrees/second.
451  
452          .. image :: ../docs/_static/accelerometer.jpg
453            :alt: Gyro
454  
455          This example prints the values. Try moving the board to see how the printed values change.
456  
457          To use with the CLUE:
458  
459          .. code-block:: python
460  
461            from adafruit_clue import clue
462  
463            while True:
464                print("Gyro: {:.2f} {:.2f} {:.2f}".format(*clue.gyro))
465          """
466          return self._accelerometer.gyro
467  
468      @property
469      def magnetic(self):
470          """Obtain x, y, z magnetic values in microteslas.
471  
472          .. image :: ../docs/_static/magnetometer.jpg
473            :alt: Magnetometer
474  
475          This example prints the values. Try moving the board to see how the printed values change.
476  
477          To use with the CLUE:
478  
479          .. code-block:: python
480  
481            from adafruit_clue import clue
482  
483            while True:
484                print("Magnetic: {:.3f} {:.3f} {:.3f}".format(*clue.magnetic))
485          """
486          return self._magnetometer.magnetic
487  
488      @property
489      def proximity(self):
490          """A relative proximity to the sensor in values from 0 - 255.
491  
492          .. image :: ../docs/_static/proximity.jpg
493            :alt: Proximity sensor
494  
495          This example prints the value. Try moving your hand towards and away from the front of the
496          board to see how the printed values change.
497  
498          To use with the CLUE:
499  
500          .. code-block:: python
501  
502            from adafruit_clue import clue
503  
504            while True:
505                print("Proximity: {}".format(clue.proximity))
506          """
507          self._sensor.enable_proximity = True
508          return self._sensor.proximity
509  
510      @property
511      def color(self):
512          """The red, green, blue, and clear light values. (r, g, b, c)
513  
514          .. image :: ../docs/_static/proximity.jpg
515            :alt: Color sensor
516  
517          This example prints the values. Try holding something up to the sensor to see the values
518          change. Works best with white LEDs enabled.
519  
520          To use with the CLUE:
521  
522          .. code-block:: python
523  
524            from adafruit_clue import clue
525  
526            while True:
527                print("Color: R: {} G: {} B: {} C: {}".format(*clue.color))
528          """
529          self._sensor.enable_color = True
530          return self._sensor.color_data
531  
532      @property
533      def gesture(self):
534          """A gesture code if gesture is detected. Shows ``0`` if no gesture detected.
535          ``1`` if an UP gesture is detected, ``2`` if DOWN, ``3`` if LEFT, and ``4`` if RIGHT.
536  
537          .. image :: ../docs/_static/proximity.jpg
538            :alt: Gesture sensor
539  
540          This example prints the gesture values. Try moving your hand up, down, left or right over
541          the sensor to see the value change.
542  
543          To use with the CLUE:
544  
545          .. code-block:: python
546  
547            from adafruit_clue import clue
548  
549            while True:
550                print("Gesture: {}".format(clue.gesture))
551          """
552          self._sensor.enable_gesture = True
553          return self._sensor.gesture()
554  
555      @property
556      def humidity(self):
557          """The measured relative humidity in percent.
558  
559          .. image :: ../docs/_static/humidity.jpg
560            :alt: Humidity sensor
561  
562          This example prints the value. Try breathing on the sensor to see the values change.
563  
564          To use with the CLUE:
565  
566          .. code-block:: python
567  
568            from adafruit_clue import clue
569  
570            while True:
571                print("Humidity: {:.1f}%".format(clue.humidity))
572          """
573          return self._humidity.relative_humidity
574  
575      @property
576      def pressure(self):
577          """The barometric pressure in hectoPascals.
578  
579          .. image :: ../docs/_static/pressure.jpg
580            :alt: Barometric pressure sensor
581  
582          This example prints the value.
583  
584          To use with the CLUE:
585  
586          .. code-block:: python
587  
588              from adafruit_clue import clue
589  
590              print("Pressure: {:.3f}hPa".format(clue.pressure))
591          """
592          return self._pressure.pressure
593  
594      @property
595      def temperature(self):
596          """The temperature in degrees Celsius.
597  
598          .. image :: ../docs/_static/pressure.jpg
599            :alt: Temperature sensor
600  
601          This example prints the value. Try touching the sensor to see the value change.
602  
603          To use with the CLUE:
604  
605          .. code-block:: python
606  
607              from adafruit_clue import clue
608  
609              print("Temperature: {:.1f}C".format(clue.temperature))
610          """
611          return self._pressure.temperature
612  
613      @property
614      def altitude(self):
615          """The altitude in meters based on the sea level pressure at your location. You must set
616          ``sea_level_pressure`` to receive an accurate reading.
617  
618          .. image :: ../docs/_static/pressure.jpg
619            :alt: Altitude sensor
620  
621          This example prints the value. Try moving the board vertically to see the value change.
622  
623          .. code-block:: python
624  
625              from adafruit_clue import clue
626  
627              clue.sea_level_pressure = 1015
628  
629              print("Altitude: {:.1f}m".format(clue.altitude))
630          """
631          return self._pressure.altitude
632  
633      @property
634      def sea_level_pressure(self):
635          """Set to the pressure at sea level at your location, before reading altitude for
636          the most accurate altitude measurement.
637  
638          .. image :: ../docs/_static/pressure.jpg
639            :alt: Barometric pressure sensor
640  
641          This example prints the value.
642  
643          To use with the CLUE:
644  
645          .. code-block:: python
646  
647              from adafruit_clue import clue
648  
649              clue.sea_level_pressure = 1015
650  
651              print("Pressure: {:.3f}hPa".format(clue.pressure))
652          """
653          return self._pressure.sea_level_pressure
654  
655      @sea_level_pressure.setter
656      def sea_level_pressure(self, value):
657          self._pressure.sea_level_pressure = value
658  
659      @property
660      def white_leds(self):
661          """The red led next to the USB plug labeled LED.
662  
663          .. image :: ../docs/_static/white_leds.jpg
664            :alt: White LEDs
665  
666          This example turns on the white LEDs.
667  
668          To use with the CLUE:
669  
670          .. code-block:: python
671  
672              from adafruit_clue import clue
673  
674              clue.white_leds = True
675          """
676          return self._white_leds.value
677  
678      @white_leds.setter
679      def white_leds(self, value):
680          self._white_leds.value = value
681  
682      @property
683      def red_led(self):
684          """The red led next to the USB plug labeled LED.
685  
686          .. image :: ../docs/_static/red_led.jpg
687            :alt: Red LED
688  
689          This example turns on the red LED.
690  
691          To use with the CLUE:
692  
693          .. code-block:: python
694  
695              from adafruit_clue import clue
696  
697              clue.red_led = True
698          """
699          return self._red_led.value
700  
701      @red_led.setter
702      def red_led(self, value):
703          self._red_led.value = value
704  
705      @property
706      def pixel(self):
707          """The NeoPixel RGB LED.
708  
709          .. image :: ../docs/_static/neopixel.jpg
710            :alt: NeoPixel
711  
712          This example turns the NeoPixel purple.
713  
714          To use with the CLUE:
715  
716          .. code-block:: python
717  
718              from adafruit_clue import clue
719  
720              while True:
721                  clue.pixel.fill((255, 0, 255))
722          """
723          return self._pixel
724  
725      @staticmethod
726      def _sine_sample(length):
727          tone_volume = (2 ** 15) - 1
728          shift = 2 ** 15
729          for i in range(length):
730              yield int(tone_volume * math.sin(2 * math.pi * (i / length)) + shift)
731  
732      def _generate_sample(self, length=100):
733          if self._sample is not None:
734              return
735          self._sine_wave = array.array("H", self._sine_sample(length))
736          self._sample = audiopwmio.PWMAudioOut(board.SPEAKER)
737          self._sine_wave_sample = audiocore.RawSample(self._sine_wave)
738  
739      def play_tone(self, frequency, duration):
740          """ Produce a tone using the speaker. Try changing frequency to change
741          the pitch of the tone.
742  
743          :param int frequency: The frequency of the tone in Hz
744          :param float duration: The duration of the tone in seconds
745  
746          .. image :: ../docs/_static/speaker.jpg
747            :alt: Speaker
748  
749          This example plays a 880 Hz tone for a duration of 1 second.
750  
751          To use with the CLUE:
752  
753          .. code-block:: python
754  
755              from adafruit_clue import clue
756  
757              clue.play_tone(880, 1)
758          """
759          # Play a tone of the specified frequency (hz).
760          self.start_tone(frequency)
761          time.sleep(duration)
762          self.stop_tone()
763  
764      def start_tone(self, frequency):
765          """ Produce a tone using the speaker. Try changing frequency to change
766          the pitch of the tone.
767  
768          :param int frequency: The frequency of the tone in Hz
769  
770          .. image :: ../docs/_static/speaker.jpg
771            :alt: Speaker
772  
773          This example plays a 523Hz tone when button A is pressed and a 587Hz tone when button B is
774          pressed, only while the buttons are being pressed.
775  
776          To use with the CLUE:
777  
778          .. code-block:: python
779  
780               from adafruit_clue import clue
781  
782               while True:
783                   if clue.button_a:
784                       clue.start_tone(523)
785                   elif clue.button_b:
786                       clue.start_tone(587)
787                   else:
788                       clue.stop_tone()
789          """
790          length = 100
791          if length * frequency > 350000:
792              length = 350000 // frequency
793          self._generate_sample(length)
794          # Start playing a tone of the specified frequency (hz).
795          self._sine_wave_sample.sample_rate = int(len(self._sine_wave) * frequency)
796          if not self._sample.playing:
797              self._sample.play(self._sine_wave_sample, loop=True)
798  
799      def stop_tone(self):
800          """ Use with start_tone to stop the tone produced.
801  
802          .. image :: ../docs/_static/speaker.jpg
803            :alt: Speaker
804  
805          This example plays a 523Hz tone when button A is pressed and a 587Hz tone when button B is
806          pressed, only while the buttons are being pressed.
807  
808          To use with the CLUE:
809  
810          .. code-block:: python
811  
812               from adafruit_clue import clue
813  
814               while True:
815                   if clue.button_a:
816                       clue.start_tone(523)
817                   elif clue.button_b:
818                       clue.start_tone(587)
819                   else:
820                       clue.stop_tone()
821          """
822          # Stop playing any tones.
823          if self._sample is not None and self._sample.playing:
824              self._sample.stop()
825              self._sample.deinit()
826              self._sample = None
827  
828      @staticmethod
829      def _normalized_rms(values):
830          mean_values = int(sum(values) / len(values))
831          return math.sqrt(
832              sum(
833                  float(sample - mean_values) * (sample - mean_values)
834                  for sample in values
835              )
836              / len(values)
837          )
838  
839      @property
840      def sound_level(self):
841          """Obtain the sound level from the microphone (sound sensor).
842  
843          .. image :: ../docs/_static/microphone.jpg
844            :alt: Microphone (sound sensor)
845  
846          This example prints the sound levels. Try clapping or blowing on
847          the microphone to see the levels change.
848  
849          .. code-block:: python
850  
851            from adafruit_clue import clue
852  
853            while True:
854                print(clue.sound_level)
855          """
856          if self._sample is None:
857              self._samples = array.array("H", [0] * 160)
858          self._mic.record(self._samples, len(self._samples))
859          return self._normalized_rms(self._samples)
860  
861      def loud_sound(self, sound_threshold=200):
862          """Utilise a loud sound as an input.
863  
864          :param int sound_threshold: Threshold sound level must exceed to return true (Default: 200)
865  
866          .. image :: ../docs/_static/microphone.jpg
867            :alt: Microphone (sound sensor)
868  
869          This example turns the NeoPixel LED blue each time you make a loud sound.
870          Try clapping or blowing onto the microphone to trigger it.
871  
872          .. code-block:: python
873  
874            from adafruit_clue import clue
875  
876            while True:
877                if clue.loud_sound():
878                    clue.pixel.fill((0, 50, 0))
879                else:
880                    clue.pixel.fill(0)
881  
882          You may find that the code is not responding how you would like.
883          If this is the case, you can change the loud sound threshold to
884          make it more or less responsive. Setting it to a higher number
885          means it will take a louder sound to trigger. Setting it to a
886          lower number will take a quieter sound to trigger. The following
887          example shows the threshold being set to a higher number than
888          the default.
889  
890          .. code-block:: python
891  
892            from adafruit_clue import clue
893  
894            while True:
895                if clue.loud_sound(sound_threshold=300):
896                    clue.pixel.fill((0, 50, 0))
897                else:
898                    clue.pixel.fill(0)
899          """
900  
901          return self.sound_level > sound_threshold
902  
903      @staticmethod
904      def simple_text_display(  # pylint: disable=too-many-arguments
905          title=None,
906          title_color=(255, 255, 255),
907          title_scale=1,
908          text_scale=1,
909          font=None,
910          colors=None,
911      ):
912          """Display lines of text on the CLUE display. Lines of text are created in order as shown
913          in the example below. If you skip a number, the line will be shown blank on the display,
914          e.g. if you include ``[0]`` and ``[2]``, the second line on the display will be empty, and
915          the text specified for lines 0 and 2 will be displayed on the first and third line.
916          Remember, Python begins counting at 0, so the first line on the display is 0 in the code.
917  
918          Setup occurs before the loop. For data to be dynamically updated on the display, you must
919          include the data call in the loop by using ``.text =``. For example, if setup is saved as
920          ``clue_data = simple_text_display()`` then ``clue_data[0].text = clue.proximity`` must be
921          inside the ``while True:`` loop for the proximity data displayed to update as the
922          values change. You must call ``show()`` at the end of the list for anything to display.
923          See example below for usage.
924  
925          :param str title: The title displayed above the data. Set ``title="Title text"`` to provide
926                            a title. Defaults to None.
927          :param title_color: The color of the title. Not necessary if no title is provided. Defaults
928                              to white (255, 255, 255).
929          :param int title_scale: Scale the size of the title. Not necessary if no title is provided.
930                                  Defaults to 1.
931          :param int text_scale: Scale the size of the data lines. Scales the title as well.
932                                 Defaults to 1.
933          :param str font: The font to use to display the title and data. Defaults to built in
934                       ``terminalio.FONT``.
935          :param colors: A list of colors for the lines of data on the display. If you provide a
936                         single color, all lines will be that color. Otherwise it will cycle through
937                         the list you provide if the list is less than the number of lines displayed.
938                         Default colors are used if ``colors`` is not set. For example, if creating
939                         two lines of data, ``colors=((255, 255, 255), (255, 0, 0))`` would set the
940                         first line white and the second line red, and if you created four lines of
941                         data with the same setup, it would alternate white and red.
942  
943          .. image :: ../docs/_static/display_clue_data.jpg
944            :alt: Display Clue Data demo
945  
946          This example displays three lines with acceleration, gyro and magnetic data on the display.
947          Remember to call ``show()`` after the list to update the display.
948  
949          .. code-block:: python
950  
951            from adafruit_clue import clue
952  
953            clue_data = clue.simple_text_display(title="CLUE Sensor Data!", title_scale=2)
954  
955            while True:
956                clue_data[0].text = "Acceleration: {:.2f} {:.2f} {:.2f}".format(*clue.acceleration)
957                clue_data[1].text = "Gyro: {:.2f} {:.2f} {:.2f}".format(*clue.gyro)
958                clue_data[2].text = "Magnetic: {:.3f} {:.3f} {:.3f}".format(*clue.magnetic)
959                clue_data.show()
960          """
961          return _ClueSimpleTextDisplay(
962              title=title,
963              title_color=title_color,
964              title_scale=title_scale,
965              text_scale=text_scale,
966              font=font,
967              colors=colors,
968          )
969  
970  
971  clue = Clue()  # pylint: disable=invalid-name
972  """Object that is automatically created on import.
973  
974     To use, simply import it from the module:
975  
976     .. code-block:: python
977  
978     from adafruit_clue import clue
979  """