From c66123f177bcf350507b5bf5c5cbc1d5c47e3698 Mon Sep 17 00:00:00 2001 From: Melissa LeBlanc-Williams Date: Mon, 31 Aug 2020 12:21:14 -0700 Subject: [PATCH] Added middle graphics layer and additional Adafruit IO functions --- adafruit_matrixportal/graphics.py | 112 +++++++++++++++++++++++ adafruit_matrixportal/matrixportal.py | 123 ++++++++++++++------------ adafruit_matrixportal/network.py | 70 +++++++++++++-- 3 files changed, 239 insertions(+), 66 deletions(-) create mode 100755 adafruit_matrixportal/graphics.py diff --git a/adafruit_matrixportal/graphics.py b/adafruit_matrixportal/graphics.py new file mode 100755 index 0000000..491f050 --- /dev/null +++ b/adafruit_matrixportal/graphics.py @@ -0,0 +1,112 @@ +# SPDX-FileCopyrightText: 2020 Melissa LeBlanc-Williams, written for Adafruit Industries +# +# SPDX-License-Identifier: Unlicense +""" +`adafruit_matrixportal.graphics` +================================================================================ + +Helper library for the Adafruit RGB Matrix Shield + Metro M4 Airlift Lite. + +* Author(s): Melissa LeBlanc-Williams + +Implementation Notes +-------------------- + +**Hardware:** + +* `Adafruit Metro M4 Express AirLift `_ +* `Adafruit RGB Matrix Shield `_ +* `64x32 RGB LED Matrix `_ + +**Software and Dependencies:** + +* Adafruit CircuitPython firmware for the supported boards: + https://github.com/adafruit/circuitpython/releases + +""" + +import gc +import displayio +from adafruit_matrixportal.matrix import Matrix + +__version__ = "0.0.0-auto.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_MatrixPortal.git" + + +class Graphics: + """Graphics Helper Class for the MatrixPortal Library + + :param default_bg: The path to your default background image file or a hex color. + Defaults to 0x000000. + :param width: The width of the display in Pixels. Defaults to 64. + :param height: The height of the display in Pixels. Defaults to 32. + :param int bit_depth: The number of bits per color channel. Defaults to 2. + :param debug: Turn on debug print outs. Defaults to False. + + """ + + # pylint: disable=too-many-instance-attributes, too-many-locals, too-many-branches, too-many-statements + def __init__( + self, *, default_bg=0x000000, width=64, height=32, bit_depth=2, debug=False + ): + + self._debug = debug + matrix = Matrix(bit_depth=bit_depth, width=width, height=height) + self.display = matrix.display + + if self._debug: + print("Init display") + self.splash = displayio.Group(max_size=15) + + if self._debug: + print("Init background") + self._bg_group = displayio.Group(max_size=1) + self._bg_file = None + self._default_bg = default_bg + self.splash.append(self._bg_group) + + # set the default background + self.set_background(self._default_bg) + self.display.show(self.splash) + + gc.collect() + + def set_background(self, file_or_color, position=None): + """The background image to a bitmap file. + + :param file_or_color: The filename of the chosen background image, or a hex color. + + """ + print("Set background to ", file_or_color) + while self._bg_group: + self._bg_group.pop() + + if not position: + position = (0, 0) # default in top corner + + if not file_or_color: + return # we're done, no background desired + if self._bg_file: + self._bg_file.close() + if isinstance(file_or_color, str): # its a filenme: + self._bg_file = open(file_or_color, "rb") + background = displayio.OnDiskBitmap(self._bg_file) + self._bg_sprite = displayio.TileGrid( + background, + pixel_shader=displayio.ColorConverter(), + x=position[0], + y=position[1], + ) + elif isinstance(file_or_color, int): + # Make a background color fill + color_bitmap = displayio.Bitmap(self.display.width, self.display.height, 1) + color_palette = displayio.Palette(1) + color_palette[0] = file_or_color + self._bg_sprite = displayio.TileGrid( + color_bitmap, pixel_shader=color_palette, x=position[0], y=position[1], + ) + else: + raise RuntimeError("Unknown type of background") + self._bg_group.append(self._bg_sprite) + self.display.refresh() + gc.collect() diff --git a/adafruit_matrixportal/matrixportal.py b/adafruit_matrixportal/matrixportal.py index f01d6b5..7082f72 100755 --- a/adafruit_matrixportal/matrixportal.py +++ b/adafruit_matrixportal/matrixportal.py @@ -26,12 +26,12 @@ """ import gc +from time import sleep import terminalio -import displayio from adafruit_bitmap_font import bitmap_font from adafruit_display_text.label import Label from adafruit_matrixportal.network import Network -from adafruit_matrixportal.matrix import Matrix +from adafruit_matrixportal.graphics import Graphics __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_MatrixPortal.git" @@ -79,8 +79,10 @@ def __init__( ): self._debug = debug - matrix = Matrix(bit_depth=bit_depth, width=64, height=32) - self.display = matrix.display + self._graphics = Graphics( + default_bg=default_bg, bit_depth=bit_depth, width=64, height=32, debug=debug + ) + self.display = self._graphics.display self._network = Network( status_neopixel=status_neopixel, @@ -98,20 +100,7 @@ def __init__( self._regexp_path = regexp_path - if self._debug: - print("Init display") - self.splash = displayio.Group(max_size=15) - - if self._debug: - print("Init background") - self._bg_group = displayio.Group(max_size=1) - self._bg_file = None - self._default_bg = default_bg - self.splash.append(self._bg_group) - - # set the default background - self.set_background(self._default_bg) - self.display.show(self.splash) + self.splash = self._graphics.splash # Add any JSON translators if json_transform: @@ -200,39 +189,7 @@ def set_background(self, file_or_color, position=None): :param file_or_color: The filename of the chosen background image, or a hex color. """ - print("Set background to ", file_or_color) - while self._bg_group: - self._bg_group.pop() - - if not position: - position = (0, 0) # default in top corner - - if not file_or_color: - return # we're done, no background desired - if self._bg_file: - self._bg_file.close() - if isinstance(file_or_color, str): # its a filenme: - self._bg_file = open(file_or_color, "rb") - background = displayio.OnDiskBitmap(self._bg_file) - self._bg_sprite = displayio.TileGrid( - background, - pixel_shader=displayio.ColorConverter(), - x=position[0], - y=position[1], - ) - elif isinstance(file_or_color, int): - # Make a background color fill - color_bitmap = displayio.Bitmap(self.display.width, self.display.height, 1) - color_palette = displayio.Palette(1) - color_palette[0] = file_or_color - self._bg_sprite = displayio.TileGrid( - color_bitmap, pixel_shader=color_palette, x=position[0], y=position[1], - ) - else: - raise RuntimeError("Unknown type of background") - self._bg_group.append(self._bg_sprite) - self.display.refresh() - gc.collect() + self._graphics.set_background(file_or_color, position) def preload_font(self, glyphs=None): # pylint: disable=line-too-long @@ -307,10 +264,47 @@ def _get_next_scrollable_text_index(self): if index == self._scrolling_index: return None + def push_to_io(self, feed_key, data): + """Push data to an adafruit.io feed + + :param str feed_key: Name of feed key to push data to. + :param data: data to send to feed + + """ + + self._network.push_to_io(feed_key, data) + + def get_io_data(self, feed_key): + """Return all values from the Adafruit IO Feed Data that matches the feed key + + :param str feed_key: Name of feed key to receive data from. + + """ + + return self._network.get_io_data(feed_key) + + def get_io_feed(self, feed_key, detailed=False): + """Return the Adafruit IO Feed that matches the feed key + + :param str feed_key: Name of feed key to match. + :param bool detailed: Whether to return additional detailed information + + """ + return self._network.get_io_feed(feed_key, detailed) + + def get_io_group(self, group_key): + """Return the Adafruit IO Group that matches the group key + + :param str group_key: Name of group key to match. + + """ + return self._network.get_io_group(group_key) + def scroll(self): - """Scroll any text that needs scrolling. We also want to queue up - multiple lines one after another. To get simultaneous lines, we can - simply use a line break.""" + """Scroll any text that needs scrolling by a single frame. We also + we want to queue up multiple lines one after another. To get + simultaneous lines, we can simply use a line break. + """ if self._scrolling_index is None: # Not initialized yet next_index = self._get_next_scrollable_text_index() @@ -318,14 +312,29 @@ def scroll(self): return self._scrolling_index = next_index - # set line to label with self._scrolling_index - self._text[self._scrolling_index].x = self._text[self._scrolling_index].x - 1 line_width = self._text[self._scrolling_index].bounding_box[2] if self._text[self._scrolling_index].x < -line_width: # Find the next line self._scrolling_index = self._get_next_scrollable_text_index() - self._text[self._scrolling_index].x = self.display.width + self._text[self._scrolling_index].x = self._graphics.display.width + + def scroll_text(self, frame_delay=0.02): + """Scroll the entire text all the way across. We also + we want to queue up multiple lines one after another. To get + simultaneous lines, we can simply use a line break. + """ + if self._scrolling_index is None: # Not initialized yet + next_index = self._get_next_scrollable_text_index() + if next_index is None: + return + self._scrolling_index = next_index + + self._text[self._scrolling_index].x = self._graphics.display.width + line_width = self._text[self._scrolling_index].bounding_box[2] + for _ in range(self._graphics.display.width + line_width + 1): + self.scroll() + sleep(frame_delay) def fetch(self, refresh_url=None, timeout=10): """Fetch data from the url we initialized with, perfom any parsing, diff --git a/adafruit_matrixportal/network.py b/adafruit_matrixportal/network.py index 841c4eb..d3fc97e 100755 --- a/adafruit_matrixportal/network.py +++ b/adafruit_matrixportal/network.py @@ -279,14 +279,7 @@ def connect(self): print("Retrying in 3 seconds...") time.sleep(3) - def push_to_io(self, feed_key, data): - """Push data to an adafruit.io feed - - :param str feed_key: Name of feed key to push data to. - :param data: data to send to feed - - """ - + def _get_io_client(self): try: aio_username = secrets["aio_username"] aio_key = secrets["aio_key"] @@ -295,7 +288,17 @@ def push_to_io(self, feed_key, data): "Adafruit IO secrets are kept in secrets.py, please add them there!\n\n" ) from KeyError - io_client = IO_HTTP(aio_username, aio_key, self._wifi.manager(secrets)) + return IO_HTTP(aio_username, aio_key, self._wifi.manager(secrets)) + + def push_to_io(self, feed_key, data): + """Push data to an adafruit.io feed + + :param str feed_key: Name of feed key to push data to. + :param data: data to send to feed + + """ + + io_client = self._get_io_client() while True: try: @@ -319,6 +322,55 @@ def push_to_io(self, feed_key, data): continue break + def get_io_feed(self, feed_key, detailed=False): + """Return the Adafruit IO Feed that matches the feed key + + :param str feed_key: Name of feed key to match. + :param bool detailed: Whether to return additional detailed information + + """ + io_client = self._get_io_client() + + while True: + try: + return io_client.get_feed(feed_key, detailed=detailed) + except RuntimeError as exception: + print("An error occured, retrying! 1 -", exception) + continue + break + + def get_io_group(self, group_key): + """Return the Adafruit IO Group that matches the group key + + :param str group_key: Name of group key to match. + + """ + io_client = self._get_io_client() + + while True: + try: + return io_client.get_group(group_key) + except RuntimeError as exception: + print("An error occured, retrying! 1 -", exception) + continue + break + + def get_io_data(self, feed_key): + """Return all values from Adafruit IO Feed Data that matches the feed key + + :param str feed_key: Name of feed key to receive data from. + + """ + io_client = self._get_io_client() + + while True: + try: + return io_client.receive_all_data(feed_key) + except RuntimeError as exception: + print("An error occured, retrying! 1 -", exception) + continue + break + def fetch(self, url, *, headers=None, timeout=10): """Fetch data from the specified url and return a response object""" gc.collect()