diff --git a/.gitignore b/.gitignore
index 1f1c8ed..e69de29 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +0,0 @@
-/files/
\ No newline at end of file
diff --git a/files/MuEditor-Linux-1.2.0-x86_64.tar b/files/MuEditor-Linux-1.2.0-x86_64.tar
new file mode 100644
index 0000000..9e181a9
Binary files /dev/null and b/files/MuEditor-Linux-1.2.0-x86_64.tar differ
diff --git a/files/MuEditor-OSX-1.2.0.dmg b/files/MuEditor-OSX-1.2.0.dmg
new file mode 100644
index 0000000..805da4a
Binary files /dev/null and b/files/MuEditor-OSX-1.2.0.dmg differ
diff --git a/files/MuEditor-win64-1.2.0.msi b/files/MuEditor-win64-1.2.0.msi
new file mode 100644
index 0000000..457642a
Binary files /dev/null and b/files/MuEditor-win64-1.2.0.msi differ
diff --git a/files/adafruit-circuitpython-waveshare_rp2040_zero-en_GB-9.2.8.uf2 b/files/adafruit-circuitpython-waveshare_rp2040_zero-en_GB-9.2.8.uf2
new file mode 100644
index 0000000..e0d891a
Binary files /dev/null and b/files/adafruit-circuitpython-waveshare_rp2040_zero-en_GB-9.2.8.uf2 differ
diff --git a/files/adafruit_framebuf.py b/files/adafruit_framebuf.py
new file mode 100644
index 0000000..ef45f5c
--- /dev/null
+++ b/files/adafruit_framebuf.py
@@ -0,0 +1,612 @@
+# SPDX-FileCopyrightText: 2018 Kattni Rembor, Melissa LeBlanc-Williams
+# and Tony DiCola, for Adafruit Industries.
+# Original file created by Damien P. George
+#
+# SPDX-License-Identifier: MIT
+
+"""
+`adafruit_framebuf`
+====================================================
+
+CircuitPython pure-python framebuf module, based on the micropython framebuf module.
+
+Implementation Notes
+--------------------
+
+**Hardware:**
+
+* `Adafruit SSD1306 OLED displays `_
+* `Adafruit HT16K33 Matrix displays `_
+
+**Software and Dependencies:**
+
+* Adafruit CircuitPython firmware for the supported boards:
+ https://github.com/adafruit/circuitpython/releases
+
+"""
+
+__version__ = "0.0.0+auto.0"
+__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_framebuf.git"
+
+import os
+import struct
+
+# Framebuf format constants:
+MVLSB = 0 # Single bit displays (like SSD1306 OLED)
+RGB565 = 1 # 16-bit color displays
+GS4_HMSB = 2 # Unimplemented!
+MHMSB = 3 # Single bit displays like the Sharp Memory
+RGB888 = 4 # Neopixels and Dotstars
+GS2_HMSB = 5 # 2-bit color displays like the HT16K33 8x8 Matrix
+
+
+class GS2HMSBFormat:
+ """GS2HMSBFormat"""
+
+ @staticmethod
+ def set_pixel(framebuf, x, y, color):
+ """Set a given pixel to a color."""
+ index = (y * framebuf.stride + x) >> 2
+ pixel = framebuf.buf[index]
+
+ shift = (x & 0b11) << 1
+ mask = 0b11 << shift
+ color = (color & 0b11) << shift
+
+ framebuf.buf[index] = color | (pixel & (~mask))
+
+ @staticmethod
+ def get_pixel(framebuf, x, y):
+ """Get the color of a given pixel"""
+ index = (y * framebuf.stride + x) >> 2
+ pixel = framebuf.buf[index]
+
+ shift = (x & 0b11) << 1
+ return (pixel >> shift) & 0b11
+
+ @staticmethod
+ def fill(framebuf, color):
+ """completely fill/clear the buffer with a color"""
+ if color:
+ bits = color & 0b11
+ fill = (bits << 6) | (bits << 4) | (bits << 2) | (bits << 0)
+ else:
+ fill = 0x00
+
+ framebuf.buf = [fill for i in range(len(framebuf.buf))]
+
+ @staticmethod
+ def rect(framebuf, x, y, width, height, color):
+ """Draw the outline of a rectangle at the given location, size and color."""
+ for _x in range(x, x + width):
+ for _y in range(y, y + height):
+ if _x in {x, x + width} or _y in {y, y + height}:
+ GS2HMSBFormat.set_pixel(framebuf, _x, _y, color)
+
+ @staticmethod
+ def fill_rect(framebuf, x, y, width, height, color):
+ """Draw the outline and interior of a rectangle at the given location, size and color."""
+ for _x in range(x, x + width):
+ for _y in range(y, y + height):
+ GS2HMSBFormat.set_pixel(framebuf, _x, _y, color)
+
+
+class MHMSBFormat:
+ """MHMSBFormat"""
+
+ @staticmethod
+ def set_pixel(framebuf, x, y, color):
+ """Set a given pixel to a color."""
+ index = (y * framebuf.stride + x) // 8
+ offset = 7 - x & 0x07
+ framebuf.buf[index] = (framebuf.buf[index] & ~(0x01 << offset)) | ((color != 0) << offset)
+
+ @staticmethod
+ def get_pixel(framebuf, x, y):
+ """Get the color of a given pixel"""
+ index = (y * framebuf.stride + x) // 8
+ offset = 7 - x & 0x07
+ return (framebuf.buf[index] >> offset) & 0x01
+
+ @staticmethod
+ def fill(framebuf, color):
+ """completely fill/clear the buffer with a color"""
+ if color:
+ fill = 0xFF
+ else:
+ fill = 0x00
+ for i in range(len(framebuf.buf)):
+ framebuf.buf[i] = fill
+
+ @staticmethod
+ def fill_rect(framebuf, x, y, width, height, color):
+ """Draw a rectangle at the given location, size and color. The ``fill_rect`` method draws
+ both the outline and interior."""
+ for _x in range(x, x + width):
+ offset = 7 - _x & 0x07
+ for _y in range(y, y + height):
+ index = (_y * framebuf.stride + _x) // 8
+ framebuf.buf[index] = (framebuf.buf[index] & ~(0x01 << offset)) | (
+ (color != 0) << offset
+ )
+
+
+class MVLSBFormat:
+ """MVLSBFormat"""
+
+ @staticmethod
+ def set_pixel(framebuf, x, y, color):
+ """Set a given pixel to a color."""
+ index = (y >> 3) * framebuf.stride + x
+ offset = y & 0x07
+ framebuf.buf[index] = (framebuf.buf[index] & ~(0x01 << offset)) | ((color != 0) << offset)
+
+ @staticmethod
+ def get_pixel(framebuf, x, y):
+ """Get the color of a given pixel"""
+ index = (y >> 3) * framebuf.stride + x
+ offset = y & 0x07
+ return (framebuf.buf[index] >> offset) & 0x01
+
+ @staticmethod
+ def fill(framebuf, color):
+ """completely fill/clear the buffer with a color"""
+ if color:
+ fill = 0xFF
+ else:
+ fill = 0x00
+ for i in range(len(framebuf.buf)):
+ framebuf.buf[i] = fill
+
+ @staticmethod
+ def fill_rect(framebuf, x, y, width, height, color):
+ """Draw a rectangle at the given location, size and color. The ``fill_rect`` method draws
+ both the outline and interior."""
+ while height > 0:
+ index = (y >> 3) * framebuf.stride + x
+ offset = y & 0x07
+ for w_w in range(width):
+ framebuf.buf[index + w_w] = (framebuf.buf[index + w_w] & ~(0x01 << offset)) | (
+ (color != 0) << offset
+ )
+ y += 1
+ height -= 1
+
+
+class RGB565Format:
+ """
+ This class implements the RGB565 format
+ It assumes a little-endian byte order in the frame buffer
+ """
+
+ @staticmethod
+ def color_to_rgb565(color):
+ """Convert a color in either tuple or 24 bit integer form to RGB565,
+ and return as two bytes"""
+ if isinstance(color, tuple):
+ hibyte = (color[0] & 0xF8) | (color[1] >> 5)
+ lobyte = ((color[1] << 5) & 0xE0) | (color[2] >> 3)
+ else:
+ hibyte = ((color >> 16) & 0xF8) | ((color >> 13) & 0x07)
+ lobyte = ((color >> 5) & 0xE0) | ((color >> 3) & 0x1F)
+ return bytes([lobyte, hibyte])
+
+ def set_pixel(self, framebuf, x, y, color):
+ """Set a given pixel to a color."""
+ index = (y * framebuf.stride + x) * 2
+ framebuf.buf[index : index + 2] = self.color_to_rgb565(color)
+
+ @staticmethod
+ def get_pixel(framebuf, x, y):
+ """Get the color of a given pixel"""
+ index = (y * framebuf.stride + x) * 2
+ lobyte, hibyte = framebuf.buf[index : index + 2]
+ r = hibyte & 0xF8
+ g = ((hibyte & 0x07) << 5) | ((lobyte & 0xE0) >> 5)
+ b = (lobyte & 0x1F) << 3
+ return (r << 16) | (g << 8) | b
+
+ def fill(self, framebuf, color):
+ """completely fill/clear the buffer with a color"""
+ rgb565_color = self.color_to_rgb565(color)
+ for i in range(0, len(framebuf.buf), 2):
+ framebuf.buf[i : i + 2] = rgb565_color
+
+ def fill_rect(self, framebuf, x, y, width, height, color):
+ """Draw a rectangle at the given location, size and color. The ``fill_rect`` method draws
+ both the outline and interior."""
+ rgb565_color = self.color_to_rgb565(color)
+ for _y in range(2 * y, 2 * (y + height), 2):
+ offset2 = _y * framebuf.stride
+ for _x in range(2 * x, 2 * (x + width), 2):
+ index = offset2 + _x
+ framebuf.buf[index : index + 2] = rgb565_color
+
+
+class RGB888Format:
+ """RGB888Format"""
+
+ @staticmethod
+ def set_pixel(framebuf, x, y, color):
+ """Set a given pixel to a color."""
+ index = (y * framebuf.stride + x) * 3
+ if isinstance(color, tuple):
+ framebuf.buf[index : index + 3] = bytes(color)
+ else:
+ framebuf.buf[index : index + 3] = bytes(
+ ((color >> 16) & 255, (color >> 8) & 255, color & 255)
+ )
+
+ @staticmethod
+ def get_pixel(framebuf, x, y):
+ """Get the color of a given pixel"""
+ index = (y * framebuf.stride + x) * 3
+ return (
+ (framebuf.buf[index] << 16) | (framebuf.buf[index + 1] << 8) | framebuf.buf[index + 2]
+ )
+
+ @staticmethod
+ def fill(framebuf, color):
+ """completely fill/clear the buffer with a color"""
+ fill = (color >> 16) & 255, (color >> 8) & 255, color & 255
+ for i in range(0, len(framebuf.buf), 3):
+ framebuf.buf[i : i + 3] = bytes(fill)
+
+ @staticmethod
+ def fill_rect(framebuf, x, y, width, height, color):
+ """Draw a rectangle at the given location, size and color. The ``fill_rect`` method draws
+ both the outline and interior."""
+ fill = (color >> 16) & 255, (color >> 8) & 255, color & 255
+ for _x in range(x, x + width):
+ for _y in range(y, y + height):
+ index = (_y * framebuf.stride + _x) * 3
+ framebuf.buf[index : index + 3] = bytes(fill)
+
+
+class FrameBuffer:
+ """FrameBuffer object.
+
+ :param buf: An object with a buffer protocol which must be large enough to contain every
+ pixel defined by the width, height and format of the FrameBuffer.
+ :param width: The width of the FrameBuffer in pixel
+ :param height: The height of the FrameBuffer in pixel
+ :param buf_format: Specifies the type of pixel used in the FrameBuffer; permissible values
+ are listed under Constants below. These set the number of bits used to
+ encode a color value and the layout of these bits in ``buf``. Where a
+ color value c is passed to a method, c is a small integer with an encoding
+ that is dependent on the format of the FrameBuffer.
+ :param stride: The number of pixels between each horizontal line of pixels in the
+ FrameBuffer. This defaults to ``width`` but may need adjustments when
+ implementing a FrameBuffer within another larger FrameBuffer or screen. The
+ ``buf`` size must accommodate an increased step size.
+
+ """
+
+ def __init__(self, buf, width, height, buf_format=MVLSB, stride=None):
+ self.buf = buf
+ self.width = width
+ self.height = height
+ self.stride = stride
+ self._font = None
+ if self.stride is None:
+ self.stride = width
+ if buf_format == MVLSB:
+ self.format = MVLSBFormat()
+ elif buf_format == MHMSB:
+ self.format = MHMSBFormat()
+ elif buf_format == RGB888:
+ self.format = RGB888Format()
+ elif buf_format == RGB565:
+ self.format = RGB565Format()
+ elif buf_format == GS2_HMSB:
+ self.format = GS2HMSBFormat()
+ else:
+ raise ValueError("invalid format")
+ self._rotation = 0
+
+ @property
+ def rotation(self):
+ """The rotation setting of the display, can be one of (0, 1, 2, 3)"""
+ return self._rotation
+
+ @rotation.setter
+ def rotation(self, val):
+ if val not in {0, 1, 2, 3}:
+ raise RuntimeError("Bad rotation setting")
+ self._rotation = val
+
+ def fill(self, color):
+ """Fill the entire FrameBuffer with the specified color."""
+ self.format.fill(self, color)
+
+ def fill_rect(self, x, y, width, height, color):
+ """Draw a rectangle at the given location, size and color. The ``fill_rect`` method draws
+ both the outline and interior."""
+ self.rect(x, y, width, height, color, fill=True)
+
+ def pixel(self, x, y, color=None):
+ """If ``color`` is not given, get the color value of the specified pixel. If ``color`` is
+ given, set the specified pixel to the given color."""
+ if self.rotation == 1:
+ x, y = y, x
+ x = self.width - x - 1
+ if self.rotation == 2:
+ x = self.width - x - 1
+ y = self.height - y - 1
+ if self.rotation == 3:
+ x, y = y, x
+ y = self.height - y - 1
+
+ if x < 0 or x >= self.width or y < 0 or y >= self.height:
+ return None
+ if color is None:
+ return self.format.get_pixel(self, x, y)
+ self.format.set_pixel(self, x, y, color)
+ return None
+
+ def hline(self, x, y, width, color):
+ """Draw a horizontal line up to a given length."""
+ self.rect(x, y, width, 1, color, fill=True)
+
+ def vline(self, x, y, height, color):
+ """Draw a vertical line up to a given length."""
+ self.rect(x, y, 1, height, color, fill=True)
+
+ def circle(self, center_x, center_y, radius, color):
+ """Draw a circle at the given midpoint location, radius and color.
+ The ```circle``` method draws only a 1 pixel outline."""
+ x = radius - 1
+ y = 0
+ d_x = 1
+ d_y = 1
+ err = d_x - (radius << 1)
+ while x >= y:
+ self.pixel(center_x + x, center_y + y, color)
+ self.pixel(center_x + y, center_y + x, color)
+ self.pixel(center_x - y, center_y + x, color)
+ self.pixel(center_x - x, center_y + y, color)
+ self.pixel(center_x - x, center_y - y, color)
+ self.pixel(center_x - y, center_y - x, color)
+ self.pixel(center_x + y, center_y - x, color)
+ self.pixel(center_x + x, center_y - y, color)
+ if err <= 0:
+ y += 1
+ err += d_y
+ d_y += 2
+ if err > 0:
+ x -= 1
+ d_x += 2
+ err += d_x - (radius << 1)
+
+ def rect(self, x, y, width, height, color, *, fill=False):
+ """Draw a rectangle at the given location, size and color. The ```rect``` method draws only
+ a 1 pixel outline."""
+ # pylint: disable=too-many-arguments
+ if self.rotation == 1:
+ x, y = y, x
+ width, height = height, width
+ x = self.width - x - width
+ if self.rotation == 2:
+ x = self.width - x - width
+ y = self.height - y - height
+ if self.rotation == 3:
+ x, y = y, x
+ width, height = height, width
+ y = self.height - y - height
+
+ if (
+ width < 1
+ or height < 1
+ or (x + width) <= 0
+ or (y + height) <= 0
+ or y >= self.height
+ or x >= self.width
+ ):
+ return
+ x_end = min(self.width - 1, x + width - 1)
+ y_end = min(self.height - 1, y + height - 1)
+ x = max(x, 0)
+ y = max(y, 0)
+ if fill:
+ self.format.fill_rect(self, x, y, x_end - x + 1, y_end - y + 1, color)
+ else:
+ self.format.fill_rect(self, x, y, x_end - x + 1, 1, color)
+ self.format.fill_rect(self, x, y, 1, y_end - y + 1, color)
+ self.format.fill_rect(self, x, y_end, x_end - x + 1, 1, color)
+ self.format.fill_rect(self, x_end, y, 1, y_end - y + 1, color)
+
+ def line(self, x_0, y_0, x_1, y_1, color):
+ """Bresenham's line algorithm"""
+ d_x = abs(x_1 - x_0)
+ d_y = abs(y_1 - y_0)
+ x, y = x_0, y_0
+ s_x = -1 if x_0 > x_1 else 1
+ s_y = -1 if y_0 > y_1 else 1
+ if d_x > d_y:
+ err = d_x / 2.0
+ while x != x_1:
+ self.pixel(x, y, color)
+ err -= d_y
+ if err < 0:
+ y += s_y
+ err += d_x
+ x += s_x
+ else:
+ err = d_y / 2.0
+ while y != y_1:
+ self.pixel(x, y, color)
+ err -= d_x
+ if err < 0:
+ x += s_x
+ err += d_y
+ y += s_y
+ self.pixel(x, y, color)
+
+ def blit(self):
+ """blit is not yet implemented"""
+ raise NotImplementedError()
+
+ def scroll(self, delta_x, delta_y):
+ """shifts framebuf in x and y direction"""
+ if delta_x < 0:
+ shift_x = 0
+ xend = self.width + delta_x
+ dt_x = 1
+ else:
+ shift_x = self.width - 1
+ xend = delta_x - 1
+ dt_x = -1
+ if delta_y < 0:
+ y = 0
+ yend = self.height + delta_y
+ dt_y = 1
+ else:
+ y = self.height - 1
+ yend = delta_y - 1
+ dt_y = -1
+ while y != yend:
+ x = shift_x
+ while x != xend:
+ self.format.set_pixel(
+ self, x, y, self.format.get_pixel(self, x - delta_x, y - delta_y)
+ )
+ x += dt_x
+ y += dt_y
+
+ def text(self, string, x, y, color, *, font_name="font5x8.bin", size=1):
+ """Place text on the screen in variables sizes. Breaks on \n to next line.
+
+ Does not break on line going off screen.
+ """
+ # determine our effective width/height, taking rotation into account
+ frame_width = self.width
+ frame_height = self.height
+ if self.rotation in {1, 3}:
+ frame_width, frame_height = frame_height, frame_width
+
+ for chunk in string.split("\n"):
+ if not self._font or self._font.font_name != font_name:
+ # load the font!
+ self._font = BitmapFont(font_name)
+ width = self._font.font_width
+ height = self._font.font_height
+ for i, char in enumerate(chunk):
+ char_x = x + (i * (width + 1)) * size
+ if (
+ char_x + (width * size) > 0
+ and char_x < frame_width
+ and y + (height * size) > 0
+ and y < frame_height
+ ):
+ self._font.draw_char(char, char_x, y, self, color, size=size)
+ y += height * size
+
+ def image(self, img):
+ """Set buffer to value of Python Imaging Library image. The image should
+ be in 1 bit mode and a size equal to the display size."""
+ # determine our effective width/height, taking rotation into account
+ width = self.width
+ height = self.height
+ if self.rotation in {1, 3}:
+ width, height = height, width
+
+ if isinstance(self.format, (RGB565Format, RGB888Format)) and img.mode != "RGB":
+ raise ValueError("Image must be in mode RGB.")
+ if isinstance(self.format, (MHMSBFormat, MVLSBFormat)) and img.mode != "1":
+ raise ValueError("Image must be in mode 1.")
+
+ imwidth, imheight = img.size
+ if imwidth != width or imheight != height:
+ raise ValueError(f"Image must be same dimensions as display ({width}x{height}).")
+ # Grab all the pixels from the image, faster than getpixel.
+ pixels = img.load()
+ # Clear buffer
+ for i in range(len(self.buf)):
+ self.buf[i] = 0
+ # Iterate through the pixels
+ for x in range(width): # yes this double loop is slow,
+ for y in range(height): # but these displays are small!
+ if img.mode == "RGB":
+ self.pixel(x, y, pixels[(x, y)])
+ elif pixels[(x, y)]:
+ self.pixel(x, y, 1) # only write if pixel is true
+
+
+# MicroPython basic bitmap font renderer.
+# Author: Tony DiCola
+# License: MIT License (https://opensource.org/licenses/MIT)
+class BitmapFont:
+ """A helper class to read binary font tiles and 'seek' through them as a
+ file to display in a framebuffer. We use file access so we dont waste 1KB
+ of RAM on a font!"""
+
+ def __init__(self, font_name="font5x8.bin"):
+ # Specify the drawing area width and height, and the pixel function to
+ # call when drawing pixels (should take an x and y param at least).
+ # Optionally specify font_name to override the font file to use (default
+ # is font5x8.bin). The font format is a binary file with the following
+ # format:
+ # - 1 unsigned byte: font character width in pixels
+ # - 1 unsigned byte: font character height in pixels
+ # - x bytes: font data, in ASCII order covering all 255 characters.
+ # Each character should have a byte for each pixel column of
+ # data (i.e. a 5x8 font has 5 bytes per character).
+ self.font_name = font_name
+
+ # Open the font file and grab the character width and height values.
+ # Note that only fonts up to 8 pixels tall are currently supported.
+ try:
+ self._font = open(self.font_name, "rb")
+ self.font_width, self.font_height = struct.unpack("BB", self._font.read(2))
+ # simple font file validation check based on expected file size
+ if 2 + 256 * self.font_width != os.stat(font_name)[6]:
+ raise RuntimeError("Invalid font file: " + font_name)
+ except OSError:
+ print("Could not find font file", font_name)
+ raise
+ except OverflowError:
+ # os.stat can throw this on boards without long int support
+ # just hope the font file is valid and press on
+ pass
+
+ def deinit(self):
+ """Close the font file as cleanup."""
+ self._font.close()
+
+ def __enter__(self):
+ """Initialize/open the font file"""
+ self.__init__()
+ return self
+
+ def __exit__(self, exception_type, exception_value, traceback):
+ """cleanup on exit"""
+ self.deinit()
+
+ def draw_char(self, char, x, y, framebuffer, color, size=1):
+ """Draw one character at position (x,y) to a framebuffer in a given color"""
+ size = max(size, 1)
+ # Don't draw the character if it will be clipped off the visible area.
+ # if x < -self.font_width or x >= framebuffer.width or \
+ # y < -self.font_height or y >= framebuffer.height:
+ # return
+ # Go through each column of the character.
+ for char_x in range(self.font_width):
+ # Grab the byte for the current column of font data.
+ self._font.seek(2 + (ord(char) * self.font_width) + char_x)
+ try:
+ line = struct.unpack("B", self._font.read(1))[0]
+ except RuntimeError:
+ continue # maybe character isnt there? go to next
+ # Go through each row in the column byte.
+ for char_y in range(self.font_height):
+ # Draw a pixel for each bit that's flipped on.
+ if (line >> char_y) & 0x1:
+ framebuffer.fill_rect(x + char_x * size, y + char_y * size, size, size, color)
+
+ def width(self, text):
+ """Return the pixel width of the specified text message."""
+ return len(text) * (self.font_width + 1)
+
+
+class FrameBuffer1(FrameBuffer):
+ """FrameBuffer1 object. Inherits from FrameBuffer."""
diff --git a/files/adafruit_hcsr04.py b/files/adafruit_hcsr04.py
new file mode 100644
index 0000000..445f240
--- /dev/null
+++ b/files/adafruit_hcsr04.py
@@ -0,0 +1,180 @@
+# SPDX-FileCopyrightText: 2017 Mike Mabey
+#
+# SPDX-License-Identifier: MIT
+
+"""
+`adafruit_hcsr04`
+====================================================
+
+A CircuitPython library for the HC-SR04 ultrasonic range sensor.
+
+The HC-SR04 functions by sending an ultrasonic signal, which is reflected by
+many materials, and then sensing when the signal returns to the sensor. Knowing
+that sound travels through dry air at `343.2 meters per second (at 20 °C)
+`_, it's pretty straightforward
+to calculate how far away the object is by timing how long the signal took to
+go round-trip and do some simple arithmetic, which is handled for you by this
+library.
+
+.. warning::
+
+ The HC-SR04 uses 5V logic, so you will have to use a `level shifter
+ `_ or simple
+ voltage divider between it and your CircuitPython board (which uses 3.3V logic)
+
+* Authors:
+
+ - Mike Mabey
+ - Jerry Needell - modified to add timeout while waiting for echo (2/26/2018)
+ - ladyada - compatible with `distance` property standard, renaming, Pi compat
+"""
+
+import time
+
+from digitalio import DigitalInOut, Direction
+
+try:
+ from types import TracebackType
+ from typing import Optional, Type
+
+ from microcontroller import Pin
+except ImportError:
+ pass
+
+_USE_PULSEIO = False
+try:
+ from pulseio import PulseIn
+
+ _USE_PULSEIO = True
+except (ImportError, NotImplementedError):
+ pass # This is OK, we'll try to bitbang it!
+
+__version__ = "0.0.0+auto.0"
+__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_HCSR04.git"
+
+
+class HCSR04:
+ """Control a HC-SR04 ultrasonic range sensor.
+
+ Example use:
+
+ ::
+
+ import time
+ import board
+
+ import adafruit_hcsr04
+
+ sonar = adafruit_hcsr04.HCSR04(trigger_pin=board.D2, echo_pin=board.D3)
+
+
+ while True:
+ try:
+ print((sonar.distance,))
+ except RuntimeError:
+ print("Retrying!")
+ pass
+ time.sleep(0.1)
+ """
+
+ def __init__(self, trigger_pin: Pin, echo_pin: Pin, *, timeout: float = 0.1) -> None:
+ """
+ :param trigger_pin: The pin on the microcontroller that's connected to the
+ ``Trig`` pin on the HC-SR04.
+ :type trig_pin: microcontroller.Pin
+ :param echo_pin: The pin on the microcontroller that's connected to the
+ ``Echo`` pin on the HC-SR04.
+ :type echo_pin: microcontroller.Pin
+ :param float timeout: Max seconds to wait for a response from the
+ sensor before assuming it isn't going to answer. Should *not* be
+ set to less than 0.05 seconds!
+ """
+ self._timeout = timeout
+ self._trig = DigitalInOut(trigger_pin)
+ self._trig.direction = Direction.OUTPUT
+
+ if _USE_PULSEIO:
+ self._echo = PulseIn(echo_pin)
+ self._echo.pause()
+ self._echo.clear()
+ else:
+ self._echo = DigitalInOut(echo_pin)
+ self._echo.direction = Direction.INPUT
+
+ def __enter__(self) -> "HCSR04":
+ """Allows for use in context managers."""
+ return self
+
+ def __exit__(
+ self,
+ exc_type: Optional[Type[BaseException]],
+ exc_val: Optional[BaseException],
+ exc_tb: Optional[TracebackType],
+ ) -> None:
+ """Automatically de-initialize after a context manager."""
+ self.deinit()
+
+ def deinit(self) -> None:
+ """De-initialize the trigger and echo pins."""
+ self._trig.deinit()
+ self._echo.deinit()
+
+ @property
+ def distance(self) -> float:
+ """Return the distance measured by the sensor in cm.
+
+ This is the function that will be called most often in user code. The
+ distance is calculated by timing a pulse from the sensor, indicating
+ how long between when the sensor sent out an ultrasonic signal and when
+ it bounced back and was received again.
+
+ If no signal is received, we'll throw a RuntimeError exception. This means
+ either the sensor was moving too fast to be pointing in the right
+ direction to pick up the ultrasonic signal when it bounced back (less
+ likely), or the object off of which the signal bounced is too far away
+ for the sensor to handle. In my experience, the sensor can detect
+ objects over 460 cm away.
+
+ :return: Distance in centimeters.
+ :rtype: float
+ """
+ return self._dist_two_wire() # at this time we only support 2-wire meausre
+
+ def _dist_two_wire(self) -> float:
+ if _USE_PULSEIO:
+ self._echo.clear() # Discard any previous pulse values
+ self._trig.value = True # Set trig high
+ time.sleep(0.00001) # 10 micro seconds 10/1000/1000
+ self._trig.value = False # Set trig low
+
+ pulselen = None
+ timestamp = time.monotonic()
+ if _USE_PULSEIO:
+ self._echo.resume()
+ while not self._echo:
+ # Wait for a pulse
+ if (time.monotonic() - timestamp) > self._timeout:
+ self._echo.pause()
+ raise RuntimeError("Timed out")
+ self._echo.pause()
+ pulselen = self._echo[0]
+ else:
+ # OK no hardware pulse support, we'll just do it by hand!
+ # hang out while the pin is low
+ while not self._echo.value:
+ if time.monotonic() - timestamp > self._timeout:
+ raise RuntimeError("Timed out")
+ timestamp = time.monotonic()
+ # track how long pin is high
+ while self._echo.value:
+ if time.monotonic() - timestamp > self._timeout:
+ raise RuntimeError("Timed out")
+ pulselen = time.monotonic() - timestamp
+ pulselen *= 1000000 # convert to us to match pulseio
+ if pulselen >= 65535:
+ raise RuntimeError("Timed out")
+
+ # positive pulse time, in seconds, times 340 meters/sec, then
+ # divided by 2 gives meters. Multiply by 100 for cm
+ # 1/1000000 s/us * 340 m/s * 100 cm/m * 2 = 0.017
+ return pulselen * 0.017
diff --git a/files/adafruit_ssd1306.mpy b/files/adafruit_ssd1306.mpy
new file mode 100644
index 0000000..0922219
Binary files /dev/null and b/files/adafruit_ssd1306.mpy differ
diff --git a/files/neopixel.py b/files/neopixel.py
new file mode 100644
index 0000000..80fab71
--- /dev/null
+++ b/files/neopixel.py
@@ -0,0 +1,177 @@
+# SPDX-FileCopyrightText: 2016 Damien P. George
+# SPDX-FileCopyrightText: 2017 Scott Shawcroft for Adafruit Industries
+# SPDX-FileCopyrightText: 2019 Carter Nelson
+# SPDX-FileCopyrightText: 2019 Roy Hooper
+#
+# SPDX-License-Identifier: MIT
+
+"""
+`neopixel` - NeoPixel strip driver
+====================================================
+
+* Author(s): Damien P. George, Scott Shawcroft, Carter Nelson, Rose Hooper
+"""
+
+import sys
+
+import adafruit_pixelbuf
+import board
+import digitalio
+from neopixel_write import neopixel_write
+
+try:
+ # Used only for typing
+ from types import TracebackType
+ from typing import Optional, Type
+
+ import microcontroller
+except ImportError:
+ pass
+
+
+__version__ = "0.0.0+auto.0"
+__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_NeoPixel.git"
+
+
+# Pixel color order constants
+RGB = "RGB"
+"""Red Green Blue"""
+GRB = "GRB"
+"""Green Red Blue"""
+RGBW = "RGBW"
+"""Red Green Blue White"""
+GRBW = "GRBW"
+"""Green Red Blue White"""
+
+
+class NeoPixel(adafruit_pixelbuf.PixelBuf):
+ """
+ A sequence of neopixels.
+
+ :param ~microcontroller.Pin pin: The pin to output neopixel data on.
+ :param int n: The number of neopixels in the chain
+ :param int bpp: Bytes per pixel. 3 for RGB and 4 for RGBW pixels.
+ :param float brightness: Brightness of the pixels between 0.0 and 1.0 where 1.0 is full
+ brightness
+ :param bool auto_write: True if the neopixels should immediately change when set. If False,
+ `show` must be called explicitly.
+ :param str pixel_order: Set the pixel color channel order. The default is GRB if bpp is set
+ to 3, otherwise GRBW is used as the default.
+
+ Example for Circuit Playground Express:
+
+ .. code-block:: python
+
+ import neopixel
+ from board import *
+
+ RED = 0x100000 # (0x10, 0, 0) also works
+
+ pixels = neopixel.NeoPixel(NEOPIXEL, 10)
+ for i in range(len(pixels)):
+ pixels[i] = RED
+
+ Example for Circuit Playground Express setting every other pixel red using a slice:
+
+ .. code-block:: python
+
+ import neopixel
+ from board import *
+ import time
+
+ RED = 0x100000 # (0x10, 0, 0) also works
+
+ # Using ``with`` ensures pixels are cleared after we're done.
+ with neopixel.NeoPixel(NEOPIXEL, 10) as pixels:
+ pixels[::2] = [RED] * (len(pixels) // 2)
+ time.sleep(2)
+
+ .. py:method:: NeoPixel.show()
+
+ Shows the new colors on the pixels themselves if they haven't already
+ been autowritten.
+
+ The colors may or may not be showing after this function returns because
+ it may be done asynchronously.
+
+ .. py:method:: NeoPixel.fill(color)
+
+ Colors all pixels the given ***color***.
+
+ .. py:attribute:: brightness
+
+ Overall brightness of the pixel (0 to 1.0)
+
+ """
+
+ def __init__(
+ self,
+ pin: microcontroller.Pin,
+ n: int,
+ *,
+ bpp: int = 3,
+ brightness: float = 1.0,
+ auto_write: bool = True,
+ pixel_order: str = None,
+ ):
+ if not pixel_order:
+ pixel_order = GRB if bpp == 3 else GRBW
+ elif isinstance(pixel_order, tuple):
+ order_list = [RGBW[order] for order in pixel_order]
+ pixel_order = "".join(order_list)
+
+ self._power = None
+ if sys.implementation.version[0] >= 7 and getattr(board, "NEOPIXEL", None) == pin:
+ power = getattr(board, "NEOPIXEL_POWER_INVERTED", None)
+ polarity = power is None
+ if not power:
+ power = getattr(board, "NEOPIXEL_POWER", None)
+ if power:
+ try:
+ self._power = digitalio.DigitalInOut(power)
+ self._power.switch_to_output(value=polarity)
+ except ValueError:
+ pass
+
+ super().__init__(n, brightness=brightness, byteorder=pixel_order, auto_write=auto_write)
+
+ self.pin = digitalio.DigitalInOut(pin)
+ self.pin.direction = digitalio.Direction.OUTPUT
+
+ def deinit(self) -> None:
+ """Blank out the NeoPixels and release the pin."""
+ self.fill(0)
+ self.show()
+ self.pin.deinit()
+ if self._power:
+ self._power.deinit()
+
+ def __enter__(self):
+ return self
+
+ def __exit__(
+ self,
+ exception_type: Optional[Type[BaseException]],
+ exception_value: Optional[BaseException],
+ traceback: Optional[TracebackType],
+ ):
+ self.deinit()
+
+ def __repr__(self):
+ return "[" + ", ".join([str(x) for x in self]) + "]"
+
+ @property
+ def n(self) -> int:
+ """
+ The number of neopixels in the chain (read-only)
+ """
+ return len(self)
+
+ def write(self) -> None:
+ """.. deprecated: 1.0.0
+
+ Use ``show`` instead. It matches Micro:Bit and Arduino APIs."""
+ self.show()
+
+ def _transmit(self, buffer: bytearray) -> None:
+ neopixel_write(self.pin, buffer)
diff --git a/files/rr_sonar.py b/files/rr_sonar.py
new file mode 100644
index 0000000..cd7cf85
--- /dev/null
+++ b/files/rr_sonar.py
@@ -0,0 +1,53 @@
+import board
+import digitalio
+import time
+
+SPEED_OF_SOUND = 343 # m/s
+TRIGGER_DURATION = 0.00001 # 10 µs
+
+# Internal cache to track history per echo pin
+sensor_history = {}
+
+def measure_distance(trigger_pin, echo_pin, sample_window=5):
+ global sensor_history
+
+ with digitalio.DigitalInOut(trigger_pin) as trigger, digitalio.DigitalInOut(echo_pin) as echo:
+ trigger.direction = digitalio.Direction.OUTPUT
+ echo.direction = digitalio.Direction.INPUT
+
+ # Send trigger pulse
+ trigger.value = False
+ time.sleep(0.0001)
+ trigger.value = True
+ time.sleep(TRIGGER_DURATION)
+ trigger.value = False
+
+ # Timeout setup
+ start_time = time.monotonic_ns()
+ timeout = start_time + 3_000_000 # 3 ms
+
+ while not echo.value:
+ if time.monotonic_ns() > timeout:
+ break
+ pulse_start = time.monotonic_ns()
+
+ while echo.value:
+ if time.monotonic_ns() > timeout:
+ return None
+ pulse_end = time.monotonic_ns()
+
+ # Calculate raw distance
+ pulse_duration = (pulse_end - pulse_start) / 1_000_000_000
+ distance_cm = (pulse_duration * SPEED_OF_SOUND * 100) / 2
+
+ # Store reading in history buffer
+ key = str(echo_pin) # Use pin identity as key
+ if key not in sensor_history:
+ sensor_history[key] = []
+ history = sensor_history[key]
+ history.append(distance_cm)
+ if len(history) > sample_window:
+ history.pop(0)
+
+ smoothed = sum(history) / len(history)
+ return smoothed