Created
May 18, 2021 19:29
-
-
Save kmatch98/011862be2d60bbff97a30585c182e820 to your computer and use it in GitHub Desktop.
Modified `adafruit_pyportal` library with scale parameter added.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# SPDX-FileCopyrightText: 2020 Melissa LeBlanc-Williams, written for Adafruit Industries | |
# | |
# SPDX-License-Identifier: Unlicense | |
""" | |
`adafruit_pyportal` | |
================================================================================ | |
CircuitPython driver for Adafruit PyPortal. | |
* Author(s): Limor Fried, Kevin J. Walters, Melissa LeBlanc-Williams | |
Implementation Notes | |
-------------------- | |
**Hardware:** | |
* `Adafruit PyPortal <https://www.adafruit.com/product/4116>`_ | |
**Software and Dependencies:** | |
* Adafruit CircuitPython firmware for the supported boards: | |
https://github.com/adafruit/circuitpython/releases | |
""" | |
import os | |
import gc | |
import time | |
import board | |
import terminalio | |
import supervisor | |
from adafruit_portalbase import PortalBase | |
from adafruit_pyportal.network import Network, CONTENT_JSON, CONTENT_TEXT | |
from adafruit_pyportal.graphics import Graphics | |
from adafruit_pyportal.peripherals import Peripherals | |
__version__ = "0.0.0-auto.0" | |
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_PyPortal.git" | |
class PyPortal(PortalBase): | |
"""Class representing the Adafruit PyPortal. | |
:param url: The URL of your data source. Defaults to ``None``. | |
:param headers: The headers for authentication, typically used by Azure API's. | |
:param json_path: The list of json traversal to get data out of. Can be list of lists for | |
multiple data points. Defaults to ``None`` to not use json. | |
:param regexp_path: The list of regexp strings to get data out (use a single regexp group). Can | |
be list of regexps for multiple data points. Defaults to ``None`` to not | |
use regexp. | |
:param convert_image: Determine whether or not to use the AdafruitIO image converter service. | |
Set as False if your image is already resized. Defaults to True. | |
:param default_bg: The path to your default background image file or a hex color. | |
Defaults to 0x000000. | |
:param status_neopixel: The pin for the status NeoPixel. Use ``board.NEOPIXEL`` for the on-board | |
NeoPixel. Defaults to ``None``, not the status LED | |
:param str text_font: The path to your font file for your data text display. | |
:param text_position: The position of your extracted text on the display in an (x, y) tuple. | |
Can be a list of tuples for when there's a list of json_paths, for example | |
:param text_color: The color of the text, in 0xRRGGBB format. Can be a list of colors for when | |
there's multiple texts. Defaults to ``None``. | |
:param text_wrap: Whether or not to wrap text (for long text data chunks). Defaults to | |
``False``, no wrapping. | |
:param text_maxlen: The max length of the text for text wrapping. Defaults to 0. | |
:param text_transform: A function that will be called on the text before display | |
:param int text_scale: The factor to scale the default size of the text by | |
:param json_transform: A function or a list of functions to call with the parsed JSON. | |
Changes and additions are permitted for the ``dict`` object. | |
:param image_json_path: The JSON traversal path for a background image to display. Defaults to | |
``None``. | |
:param image_resize: What size to resize the image we got from the json_path, make this a tuple | |
of the width and height you want. Defaults to ``None``. | |
:param image_position: The position of the image on the display as an (x, y) tuple. Defaults to | |
``None``. | |
:param image_dim_json_path: The JSON traversal path for the original dimensions of image tuple. | |
Used with fetch(). Defaults to ``None``. | |
:param success_callback: A function we'll call if you like, when we fetch data successfully. | |
Defaults to ``None``. | |
:param str caption_text: The text of your caption, a fixed text not changed by the data we get. | |
Defaults to ``None``. | |
:param str caption_font: The path to the font file for your caption. Defaults to ``None``. | |
:param caption_position: The position of your caption on the display as an (x, y) tuple. | |
Defaults to ``None``. | |
:param caption_color: The color of your caption. Must be a hex value, e.g. ``0x808000``. | |
:param image_url_path: The HTTP traversal path for a background image to display. | |
Defaults to ``None``. | |
:param esp: A passed ESP32 object, Can be used in cases where the ESP32 chip needs to be used | |
before calling the pyportal class. Defaults to ``None``. | |
:param busio.SPI external_spi: A previously declared spi object. Defaults to ``None``. | |
: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, | |
*, | |
url=None, | |
headers=None, | |
json_path=None, | |
regexp_path=None, | |
convert_image=True, | |
default_bg=0x000000, | |
status_neopixel=None, | |
text_font=terminalio.FONT, | |
text_position=None, | |
text_color=0x808080, | |
text_wrap=False, | |
text_maxlen=0, | |
text_transform=None, | |
text_scale=1, | |
json_transform=None, | |
image_json_path=None, | |
image_resize=None, | |
image_position=None, | |
image_dim_json_path=None, | |
caption_text=None, | |
caption_font=None, | |
caption_position=None, | |
caption_color=0x808080, | |
image_url_path=None, | |
success_callback=None, | |
esp=None, | |
external_spi=None, | |
debug=False, | |
secrets_data=None, | |
scale=1, | |
): | |
graphics = Graphics( | |
default_bg=default_bg, | |
debug=debug, | |
scale=scale, | |
) | |
self._default_bg = default_bg | |
if external_spi: # If SPI Object Passed | |
spi = external_spi | |
else: # Else: Make ESP32 connection | |
spi = board.SPI() | |
if image_json_path or image_url_path: | |
if debug: | |
print("Init image path") | |
if not image_position: | |
image_position = (0, 0) # default to top corner | |
if not image_resize: | |
image_resize = ( | |
self.display.width, | |
self.display.height, | |
) # default to full screen | |
network = Network( | |
status_neopixel=status_neopixel, | |
esp=esp, | |
external_spi=spi, | |
extract_values=False, | |
convert_image=convert_image, | |
image_url_path=image_url_path, | |
image_json_path=image_json_path, | |
image_resize=image_resize, | |
image_position=image_position, | |
image_dim_json_path=image_dim_json_path, | |
debug=debug, | |
secrets_data=secrets_data, | |
) | |
self.url = url | |
super().__init__( | |
network, | |
graphics, | |
url=url, | |
headers=headers, | |
json_path=json_path, | |
regexp_path=regexp_path, | |
json_transform=json_transform, | |
success_callback=success_callback, | |
debug=debug, | |
) | |
# Convenience Shortcuts for compatibility | |
self.peripherals = Peripherals( | |
spi, display=self.display, splash_group=self.splash, debug=debug | |
) | |
self.set_backlight = self.peripherals.set_backlight | |
self.sd_check = self.peripherals.sd_check | |
self.play_file = self.peripherals.play_file | |
self.image_converter_url = self.network.image_converter_url | |
self.wget = self.network.wget | |
# pylint: disable=invalid-name | |
self.show_QR = self.graphics.qrcode | |
self.hide_QR = self.graphics.hide_QR | |
# pylint: enable=invalid-name | |
if hasattr(self.peripherals, "touchscreen"): | |
self.touchscreen = self.peripherals.touchscreen | |
if hasattr(self.peripherals, "mouse_cursor"): | |
self.mouse_cursor = self.peripherals.mouse_cursor | |
if hasattr(self.peripherals, "cursor"): | |
self.cursor = self.peripherals.cursor | |
# show thank you and bootup file if available | |
for bootscreen in ("/thankyou.bmp", "/pyportal_startup.bmp"): | |
try: | |
os.stat(bootscreen) | |
for i in range(100, -1, -1): # dim down | |
self.set_backlight(i / 100) | |
time.sleep(0.005) | |
self.set_background(bootscreen) | |
try: | |
self.display.refresh(target_frames_per_second=60) | |
except AttributeError: | |
self.display.wait_for_frame() | |
for i in range(100): # dim up | |
self.set_backlight(i / 100) | |
time.sleep(0.005) | |
time.sleep(2) | |
except OSError: | |
pass # they removed it, skip! | |
try: | |
self.peripherals.play_file("pyportal_startup.wav") | |
except OSError: | |
pass # they deleted the file, no biggie! | |
if default_bg is not None: | |
self.graphics.set_background(default_bg) | |
if self._debug: | |
print("Init caption") | |
if caption_font: | |
self._caption_font = self._load_font(caption_font) | |
self.set_caption(caption_text, caption_position, caption_color) | |
if text_font: | |
if text_position is not None and isinstance( | |
text_position[0], (list, tuple) | |
): | |
num = len(text_position) | |
if not text_wrap: | |
text_wrap = [0] * num | |
if not text_maxlen: | |
text_maxlen = [0] * num | |
if not text_transform: | |
text_transform = [None] * num | |
if not isinstance(text_scale, (list, tuple)): | |
text_scale = [text_scale] * num | |
else: | |
num = 1 | |
text_position = (text_position,) | |
text_color = (text_color,) | |
text_wrap = (text_wrap,) | |
text_maxlen = (text_maxlen,) | |
text_transform = (text_transform,) | |
text_scale = (text_scale,) | |
for i in range(num): | |
self.add_text( | |
text_position=text_position[i], | |
text_font=text_font, | |
text_color=text_color[i], | |
text_wrap=text_wrap[i], | |
text_maxlen=text_maxlen[i], | |
text_transform=text_transform[i], | |
text_scale=text_scale[i], | |
) | |
else: | |
self._text_font = None | |
self._text = None | |
gc.collect() | |
def set_caption(self, caption_text, caption_position, caption_color): | |
# pylint: disable=line-too-long | |
"""A caption. Requires setting ``caption_font`` in init! | |
:param caption_text: The text of the caption. | |
:param caption_position: The position of the caption text. | |
:param caption_color: The color of your caption text. Must be a hex value, e.g. | |
``0x808000``. | |
""" | |
# pylint: enable=line-too-long | |
if self._debug: | |
print("Setting caption to", caption_text) | |
if (not caption_text) or (not self._caption_font) or (not caption_position): | |
return # nothing to do! | |
index = self.add_text( | |
text_position=caption_position, | |
text_font=self._caption_font, | |
text_color=caption_color, | |
is_data=False, | |
) | |
self.set_text(caption_text, index) | |
def fetch(self, refresh_url=None, timeout=10): | |
"""Fetch data from the url we initialized with, perfom any parsing, | |
and display text or graphics. This function does pretty much everything | |
Optionally update the URL | |
""" | |
if refresh_url: | |
self.url = refresh_url | |
response = self.network.fetch(self.url, headers=self._headers, timeout=timeout) | |
json_out = None | |
content_type = self.network.check_response(response) | |
json_path = self._json_path | |
if content_type == CONTENT_JSON: | |
if json_path is not None: | |
# Drill down to the json path and set json_out as that node | |
if isinstance(json_path, (list, tuple)) and ( | |
not json_path or not isinstance(json_path[0], (list, tuple)) | |
): | |
json_path = (json_path,) | |
try: | |
gc.collect() | |
json_out = response.json() | |
if self._debug: | |
print(json_out) | |
gc.collect() | |
except ValueError: # failed to parse? | |
print("Couldn't parse json: ", response.text) | |
raise | |
except MemoryError: | |
supervisor.reload() | |
try: | |
filename, position = self.network.process_image( | |
json_out, self.peripherals.sd_check() | |
) | |
if filename and position is not None: | |
self.graphics.set_background(filename, position) | |
except ValueError as error: | |
print("Error displaying cached image. " + error.args[0]) | |
if self._default_bg is not None: | |
self.graphics.set_background(self._default_bg) | |
except KeyError as error: | |
print("Error finding image data. '" + error.args[0] + "' not found.") | |
self.set_background(self._default_bg) | |
if content_type == CONTENT_JSON: | |
values = self.network.process_json(json_out, json_path) | |
elif content_type == CONTENT_TEXT: | |
values = self.network.process_text(response.text, self._regexp_path) | |
# if we have a callback registered, call it now | |
if self._success_callback: | |
self._success_callback(values) | |
self._fill_text_labels(values) | |
# Clean up | |
json_out = None | |
response = None | |
gc.collect() | |
if len(values) == 1: | |
values = values[0] | |
return values |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# SPDX-FileCopyrightText: 2020 Melissa LeBlanc-Williams, written for Adafruit Industries | |
# | |
# SPDX-License-Identifier: Unlicense | |
""" | |
`adafruit_pyportal.graphics` | |
================================================================================ | |
CircuitPython driver for Adafruit PyPortal. | |
* Author(s): Limor Fried, Kevin J. Walters, Melissa LeBlanc-Williams | |
Implementation Notes | |
-------------------- | |
**Hardware:** | |
* `Adafruit PyPortal <https://www.adafruit.com/product/4116>`_ | |
**Software and Dependencies:** | |
* Adafruit CircuitPython firmware for the supported boards: | |
https://github.com/adafruit/circuitpython/releases | |
""" | |
import board | |
from adafruit_portalbase.graphics import GraphicsBase | |
__version__ = "0.0.0-auto.0" | |
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_PyPortal.git" | |
class Graphics(GraphicsBase): | |
"""Graphics Helper Class for the PyPortal Library | |
:param default_bg: The path to your default background image file or a hex color. | |
Defaults to 0x000000. | |
:param debug: Turn on debug print outs. Defaults to False. | |
""" | |
# pylint: disable=too-few-public-methods | |
def __init__(self, *, default_bg=None, debug=False, scale=1): | |
super().__init__(board.DISPLAY, default_bg=default_bg, debug=debug, scale=scale,) | |
# Tracks whether we've hidden the background when we showed the QR code. | |
self._qr_only = False | |
# pylint: disable=arguments-differ | |
def qrcode(self, qr_data, *, qr_size=1, x=0, y=0, hide_background=False): | |
"""Display a QR code | |
:param qr_data: The data for the QR code. | |
:param int qr_size: The scale of the QR code. | |
:param x: The x position of upper left corner of the QR code on the display. | |
:param y: The y position of upper left corner of the QR code on the display. | |
""" | |
super().qrcode( | |
qr_data, | |
qr_size=qr_size, | |
x=x, | |
y=y, | |
) | |
if hide_background: | |
self.display.show(self._qr_group) | |
self._qr_only = hide_background | |
# pylint: enable=arguments-differ | |
def hide_QR(self): # pylint: disable=invalid-name | |
"""Clear any QR codes that are currently on the screen""" | |
if self._qr_only: | |
self.display.show(self.splash) | |
else: | |
try: | |
self._qr_group.pop() | |
except (IndexError, AttributeError): # later test if empty | |
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# SPDX-FileCopyrightText: 2020 Melissa LeBlanc-Williams, written for Adafruit Industries | |
# | |
# SPDX-License-Identifier: Unlicense | |
""" | |
`adafruit_pyportal.network` | |
================================================================================ | |
CircuitPython driver for Adafruit PyPortal. | |
* Author(s): Limor Fried, Kevin J. Walters, Melissa LeBlanc-Williams | |
Implementation Notes | |
-------------------- | |
**Hardware:** | |
* `Adafruit PyPortal <https://www.adafruit.com/product/4116>`_ | |
**Software and Dependencies:** | |
* Adafruit CircuitPython firmware for the supported boards: | |
https://github.com/adafruit/circuitpython/releases | |
""" | |
import gc | |
import neopixel | |
from adafruit_portalbase.wifi_coprocessor import WiFi | |
# pylint: disable=unused-import | |
from adafruit_portalbase.network import ( | |
NetworkBase, | |
CONTENT_JSON, | |
CONTENT_TEXT, | |
) | |
# pylint: enable=unused-import | |
__version__ = "0.0.0-auto.0" | |
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_PyPortal.git" | |
# you'll need to pass in an io username, width, height, format (bit depth), io key, and then url! | |
IMAGE_CONVERTER_SERVICE = ( | |
"https://io.adafruit.com/api/v2/%s/integrations/image-formatter?" | |
"x-aio-key=%s&width=%d&height=%d&output=BMP%d&url=%s" | |
) | |
class Network(NetworkBase): | |
"""Class representing the Adafruit PyPortal. | |
:param status_neopixel: The pin for the status NeoPixel. Use ``board.NEOPIXEL`` for the on-board | |
NeoPixel. Defaults to ``None``, not the status LED | |
:param esp: A passed ESP32 object, Can be used in cases where the ESP32 chip needs to be used | |
before calling the pyportal class. Defaults to ``None``. | |
:param busio.SPI external_spi: A previously declared spi object. Defaults to ``None``. | |
:param bool extract_values: If true, single-length fetched values are automatically extracted | |
from lists and tuples. Defaults to ``True``. | |
:param debug: Turn on debug print outs. Defaults to False. | |
:param convert_image: Determine whether or not to use the AdafruitIO image converter service. | |
Set as False if your image is already resized. Defaults to True. | |
:param image_url_path: The HTTP traversal path for a background image to display. | |
Defaults to ``None``. | |
:param image_json_path: The JSON traversal path for a background image to display. Defaults to | |
``None``. | |
:param image_resize: What size to resize the image we got from the json_path, make this a tuple | |
of the width and height you want. Defaults to ``None``. | |
:param image_position: The position of the image on the display as an (x, y) tuple. Defaults to | |
``None``. | |
:param image_dim_json_path: The JSON traversal path for the original dimensions of image tuple. | |
Used with fetch(). Defaults to ``None``. | |
""" | |
def __init__( | |
self, | |
*, | |
status_neopixel=None, | |
esp=None, | |
external_spi=None, | |
extract_values=True, | |
debug=False, | |
convert_image=True, | |
image_url_path=None, | |
image_json_path=None, | |
image_resize=None, | |
image_position=None, | |
image_dim_json_path=None, | |
secrets_data=None, | |
): | |
if status_neopixel: | |
status_led = neopixel.NeoPixel(status_neopixel, 1, brightness=0.2) | |
else: | |
status_led = None | |
wifi = WiFi(status_led=status_led, esp=esp, external_spi=external_spi) | |
super().__init__( | |
wifi, | |
extract_values=extract_values, | |
debug=debug, | |
secrets_data=secrets_data, | |
) | |
self._convert_image = convert_image | |
self._image_json_path = image_json_path | |
self._image_url_path = image_url_path | |
self._image_resize = image_resize | |
self._image_position = image_position | |
self._image_dim_json_path = image_dim_json_path | |
gc.collect() | |
@property | |
def ip_address(self): | |
"""Return the IP Address nicely formatted""" | |
return self._wifi.esp.pretty_ip(self._wifi.esp.ip_address) | |
def image_converter_url(self, image_url, width, height, color_depth=16): | |
"""Generate a converted image url from the url passed in, | |
with the given width and height. aio_username and aio_key must be | |
set in secrets.""" | |
try: | |
aio_username = self._secrets["aio_username"] | |
aio_key = self._secrets["aio_key"] | |
except KeyError as error: | |
raise KeyError( | |
"\n\nOur image converter service require a login/password to rate-limit. Please register for a free adafruit.io account and place the user/key in your secrets file under 'aio_username' and 'aio_key'" # pylint: disable=line-too-long | |
) from error | |
return IMAGE_CONVERTER_SERVICE % ( | |
aio_username, | |
aio_key, | |
width, | |
height, | |
color_depth, | |
image_url, | |
) | |
# pylint: disable=too-many-branches, too-many-statements | |
def process_image(self, json_data, sd_card=False): | |
""" | |
Process image content | |
:param json_data: The JSON data that we can pluck values from | |
:param bool sd_card: Whether or not we have an SD card inserted | |
""" | |
filename = None | |
position = None | |
image_url = None | |
if self._image_url_path: | |
image_url = self._image_url_path | |
if self._image_json_path: | |
image_url = self.json_traverse(json_data, self._image_json_path) | |
iwidth = 0 | |
iheight = 0 | |
if self._image_dim_json_path: | |
iwidth = int(self.json_traverse(json_data, self._image_dim_json_path[0])) | |
iheight = int(self.json_traverse(json_data, self._image_dim_json_path[1])) | |
print("image dim:", iwidth, iheight) | |
if image_url: | |
print("original URL:", image_url) | |
if self._convert_image: | |
if iwidth < iheight: | |
image_url = self.image_converter_url( | |
image_url, | |
int( | |
self._image_resize[1] | |
* self._image_resize[1] | |
/ self._image_resize[0] | |
), | |
self._image_resize[1], | |
) | |
else: | |
image_url = self.image_converter_url( | |
image_url, self._image_resize[0], self._image_resize[1] | |
) | |
print("convert URL:", image_url) | |
# convert image to bitmap and cache | |
# print("**not actually wgetting**") | |
filename = "/cache.bmp" | |
chunk_size = 4096 # default chunk size is 12K (for QSPI) | |
if sd_card: | |
filename = "/sd" + filename | |
chunk_size = 512 # current bug in big SD writes -> stick to 1 block | |
try: | |
self.wget(image_url, filename, chunk_size=chunk_size) | |
except OSError as error: | |
raise OSError( | |
"""\n\nNo writable filesystem found for saving datastream. Insert an SD card or set internal filesystem to be unsafe by setting 'disable_concurrent_write_protection' in the mount options in boot.py""" # pylint: disable=line-too-long | |
) from error | |
except RuntimeError as error: | |
raise RuntimeError("wget didn't write a complete file") from error | |
if iwidth < iheight: | |
pwidth = int( | |
self._image_resize[1] | |
* self._image_resize[1] | |
/ self._image_resize[0] | |
) | |
position = ( | |
self._image_position[0] + int((self._image_resize[0] - pwidth) / 2), | |
self._image_position[1], | |
) | |
else: | |
position = self._image_position | |
image_url = None | |
gc.collect() | |
return filename, position |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries | |
# SPDX-FileCopyrightText: Copyright (c) 2020 Melissa LeBlanc-Williams for Adafruit Industries | |
# | |
# SPDX-License-Identifier: MIT | |
""" | |
`adafruit_pyportal.peripherals` | |
================================================================================ | |
CircuitPython driver for Adafruit PyPortal. | |
* Author(s): Limor Fried, Kevin J. Walters, Melissa LeBlanc-Williams | |
Implementation Notes | |
-------------------- | |
**Hardware:** | |
* `Adafruit PyPortal <https://www.adafruit.com/product/4116>`_ | |
**Software and Dependencies:** | |
* Adafruit CircuitPython firmware for the supported boards: | |
https://github.com/adafruit/circuitpython/releases | |
""" | |
import gc | |
import board | |
from digitalio import DigitalInOut | |
import pwmio | |
import audioio | |
import audiocore | |
import storage | |
try: | |
import sdcardio | |
NATIVE_SD = True | |
except ImportError: | |
import adafruit_sdcard as sdcardio | |
NATIVE_SD = False | |
__version__ = "0.0.0-auto.0" | |
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_PyPortal.git" | |
class Peripherals: | |
"""Peripherals Helper Class for the PyPortal Library""" | |
# pylint: disable=too-many-instance-attributes, too-many-locals, too-many-branches, too-many-statements | |
def __init__(self, spi, display, splash_group, debug=False): | |
# Speaker Enable | |
self._speaker_enable = DigitalInOut(board.SPEAKER_ENABLE) | |
self._speaker_enable.switch_to_output(False) | |
self._display = display | |
if hasattr(board, "AUDIO_OUT"): | |
self.audio = audioio.AudioOut(board.AUDIO_OUT) | |
elif hasattr(board, "SPEAKER"): | |
self.audio = audioio.AudioOut(board.SPEAKER) | |
else: | |
raise AttributeError("Board does not have a builtin speaker!") | |
if debug: | |
print("Init SD Card") | |
sd_cs = board.SD_CS | |
if not NATIVE_SD: | |
sd_cs = DigitalInOut(sd_cs) | |
self._sdcard = None | |
try: | |
self._sdcard = sdcardio.SDCard(spi, sd_cs) | |
vfs = storage.VfsFat(self._sdcard) | |
storage.mount(vfs, "/sd") | |
except OSError as error: | |
print("No SD card found:", error) | |
try: | |
if hasattr(board, "TFT_BACKLIGHT"): | |
self._backlight = pwmio.PWMOut( | |
board.TFT_BACKLIGHT | |
) # pylint: disable=no-member | |
elif hasattr(board, "TFT_LITE"): | |
self._backlight = pwmio.PWMOut( | |
board.TFT_LITE | |
) # pylint: disable=no-member | |
except ValueError: | |
self._backlight = None | |
self.set_backlight(1.0) # turn on backlight | |
# pylint: disable=import-outside-toplevel | |
if hasattr(board, "TOUCH_XL"): | |
import adafruit_touchscreen | |
if debug: | |
print("Init touchscreen") | |
# pylint: disable=no-member | |
self.touchscreen = adafruit_touchscreen.Touchscreen( | |
board.TOUCH_XL, | |
board.TOUCH_XR, | |
board.TOUCH_YD, | |
board.TOUCH_YU, | |
calibration=((5200, 59000), (5800, 57000)), | |
size=(board.DISPLAY.width, board.DISPLAY.height), | |
) | |
# pylint: enable=no-member | |
self.set_backlight(1.0) # turn on backlight | |
elif hasattr(board, "BUTTON_CLOCK"): | |
from adafruit_cursorcontrol.cursorcontrol import Cursor | |
from adafruit_cursorcontrol.cursorcontrol_cursormanager import CursorManager | |
if debug: | |
print("Init cursor") | |
self.mouse_cursor = Cursor( | |
board.DISPLAY, display_group=splash_group, cursor_speed=8 | |
) | |
self.mouse_cursor.hide() | |
self.cursor = CursorManager(self.mouse_cursor) | |
else: | |
raise AttributeError( | |
"PyPortal module requires either a touchscreen or gamepad." | |
) | |
# pylint: enable=import-outside-toplevel | |
gc.collect() | |
def set_backlight(self, val): | |
"""Adjust the TFT backlight. | |
:param val: The backlight brightness. Use a value between ``0`` and ``1``, where ``0`` is | |
off, and ``1`` is 100% brightness. | |
""" | |
val = max(0, min(1.0, val)) | |
if self._backlight: | |
self._backlight.duty_cycle = int(val * 65535) | |
else: | |
self._display.auto_brightness = False | |
self._display.brightness = val | |
def play_file(self, file_name, wait_to_finish=True): | |
"""Play a wav file. | |
:param str file_name: The name of the wav file to play on the speaker. | |
""" | |
wavfile = open(file_name, "rb") | |
wavedata = audiocore.WaveFile(wavfile) | |
self._speaker_enable.value = True | |
self.audio.play(wavedata) | |
if not wait_to_finish: | |
return | |
while self.audio.playing: | |
pass | |
wavfile.close() | |
self._speaker_enable.value = False | |
def sd_check(self): | |
"""Returns True if there is an SD card preset and False | |
if there is no SD card. The _sdcard value is set in _init | |
""" | |
if self._sdcard: | |
return True | |
return False | |
@property | |
def speaker_disable(self): | |
""" | |
Enable or disable the speaker for power savings | |
""" | |
return not self._speaker_enable.value | |
@speaker_disable.setter | |
def speaker_disable(self, value): | |
self._speaker_enable.value = not value |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment