First push of QtPy RP2040
This commit is contained in:
BIN
QtPy-RP2040-Tem-Hum/.DS_Store
vendored
Normal file
BIN
QtPy-RP2040-Tem-Hum/.DS_Store
vendored
Normal file
Binary file not shown.
55
QtPy-RP2040-Tem-Hum/code.py
Normal file
55
QtPy-RP2040-Tem-Hum/code.py
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import time
|
||||||
|
import board
|
||||||
|
import adafruit_sht4x
|
||||||
|
import busio
|
||||||
|
import displayio
|
||||||
|
import terminalio
|
||||||
|
import microcontroller
|
||||||
|
from adafruit_display_text import bitmap_label as label
|
||||||
|
from adafruit_displayio_sh1107 import SH1107, DISPLAY_OFFSET_ADAFRUIT_128x128_OLED_5297
|
||||||
|
|
||||||
|
displayio.release_displays()
|
||||||
|
|
||||||
|
i2c = busio.I2C(board.SCL1, board.SDA1)
|
||||||
|
sht = adafruit_sht4x.SHT4x(i2c)
|
||||||
|
sht.mode = adafruit_sht4x.Mode.NOHEAT_HIGHPRECISION
|
||||||
|
display_bus = displayio.I2CDisplay(i2c, device_address=0x3D)
|
||||||
|
|
||||||
|
WIDTH = 128
|
||||||
|
HEIGHT = 128
|
||||||
|
ROTATION = 0
|
||||||
|
|
||||||
|
BORDER = 2
|
||||||
|
|
||||||
|
display = SH1107(
|
||||||
|
display_bus,
|
||||||
|
width=WIDTH,
|
||||||
|
height=HEIGHT,
|
||||||
|
display_offset=DISPLAY_OFFSET_ADAFRUIT_128x128_OLED_5297,
|
||||||
|
rotation=ROTATION,
|
||||||
|
)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
temperature, relative_humidity = sht.measurements;
|
||||||
|
selfTemp = microcontroller.cpu.temperature;
|
||||||
|
# Make the display context
|
||||||
|
splash = displayio.Group()
|
||||||
|
display.show(splash)
|
||||||
|
|
||||||
|
# Draw some label text
|
||||||
|
size_text = "%0.fc" % temperature;
|
||||||
|
size_text_area = label.Label(
|
||||||
|
terminalio.FONT, text=size_text, scale=3, color=0xFFFFFF, x=38, y=42
|
||||||
|
)
|
||||||
|
splash.append(size_text_area)
|
||||||
|
oled_text = "%0.f%%" % relative_humidity;
|
||||||
|
oled_text_area = label.Label(
|
||||||
|
terminalio.FONT, text=oled_text, scale=3, color=0xFFFFFF, x=58, y=74
|
||||||
|
)
|
||||||
|
splash.append(oled_text_area)
|
||||||
|
self_text = "%0.f" % selfTemp;
|
||||||
|
self_text_area = label.Label(
|
||||||
|
terminalio.FONT, text=self_text, scale=1, color=0xFFFFFF, x=115, y=120
|
||||||
|
)
|
||||||
|
splash.append(self_text_area)
|
||||||
|
time.sleep(60)
|
||||||
448
QtPy-RP2040-Tem-Hum/lib/adafruit_display_text/__init__.py
Executable file
448
QtPy-RP2040-Tem-Hum/lib/adafruit_display_text/__init__.py
Executable file
@ -0,0 +1,448 @@
|
|||||||
|
# SPDX-FileCopyrightText: 2020 Tim C, 2021 Jeff Epler for Adafruit Industries
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
"""
|
||||||
|
`adafruit_display_text`
|
||||||
|
=======================
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
from typing import Optional, Union, List, Tuple
|
||||||
|
from fontio import BuiltinFont
|
||||||
|
from adafruit_bitmap_font.bdf import BDF
|
||||||
|
from adafruit_bitmap_font.pcf import PCF
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
from displayio import Group, Palette
|
||||||
|
|
||||||
|
|
||||||
|
def wrap_text_to_pixels(
|
||||||
|
string: str,
|
||||||
|
max_width: int,
|
||||||
|
font: Optional[Union[BuiltinFont, BDF, PCF]] = None,
|
||||||
|
indent0: str = "",
|
||||||
|
indent1: str = "",
|
||||||
|
) -> List[str]:
|
||||||
|
# pylint: disable=too-many-branches, too-many-locals
|
||||||
|
|
||||||
|
"""wrap_text_to_pixels function
|
||||||
|
A helper that will return a list of lines with word-break wrapping.
|
||||||
|
Leading and trailing whitespace in your string will be removed. If
|
||||||
|
you wish to use leading whitespace see ``indent0`` and ``indent1``
|
||||||
|
parameters.
|
||||||
|
|
||||||
|
:param str string: The text to be wrapped.
|
||||||
|
:param int max_width: The maximum number of pixels on a line before wrapping.
|
||||||
|
:param font: The font to use for measuring the text.
|
||||||
|
:type font: ~BuiltinFont, ~BDF, or ~PCF
|
||||||
|
:param str indent0: Additional character(s) to add to the first line.
|
||||||
|
:param str indent1: Additional character(s) to add to all other lines.
|
||||||
|
|
||||||
|
:return: A list of the lines resulting from wrapping the
|
||||||
|
input text at ``max_width`` pixels size
|
||||||
|
:rtype: List[str]
|
||||||
|
|
||||||
|
"""
|
||||||
|
if font is None:
|
||||||
|
|
||||||
|
def measure(text):
|
||||||
|
return len(text)
|
||||||
|
|
||||||
|
else:
|
||||||
|
if hasattr(font, "load_glyphs"):
|
||||||
|
font.load_glyphs(string)
|
||||||
|
|
||||||
|
def measure(text):
|
||||||
|
return sum(font.get_glyph(ord(c)).shift_x for c in text)
|
||||||
|
|
||||||
|
lines = []
|
||||||
|
partial = [indent0]
|
||||||
|
width = measure(indent0)
|
||||||
|
swidth = measure(" ")
|
||||||
|
firstword = True
|
||||||
|
for line_in_input in string.split("\n"):
|
||||||
|
for index, word in enumerate(line_in_input.split(" ")):
|
||||||
|
wwidth = measure(word)
|
||||||
|
word_parts = []
|
||||||
|
cur_part = ""
|
||||||
|
|
||||||
|
if wwidth > max_width:
|
||||||
|
for char in word:
|
||||||
|
if (
|
||||||
|
measure("".join(partial))
|
||||||
|
+ measure(cur_part)
|
||||||
|
+ measure(char)
|
||||||
|
+ measure("-")
|
||||||
|
> max_width
|
||||||
|
):
|
||||||
|
word_parts.append("".join(partial) + cur_part + "-")
|
||||||
|
cur_part = char
|
||||||
|
partial = [indent1]
|
||||||
|
else:
|
||||||
|
cur_part += char
|
||||||
|
if cur_part:
|
||||||
|
word_parts.append(cur_part)
|
||||||
|
for line in word_parts[:-1]:
|
||||||
|
lines.append(line)
|
||||||
|
partial.append(word_parts[-1])
|
||||||
|
width = measure(word_parts[-1])
|
||||||
|
if firstword:
|
||||||
|
firstword = False
|
||||||
|
else:
|
||||||
|
if firstword:
|
||||||
|
partial.append(word)
|
||||||
|
firstword = False
|
||||||
|
width += wwidth
|
||||||
|
elif width + swidth + wwidth < max_width:
|
||||||
|
if index > 0:
|
||||||
|
partial.append(" ")
|
||||||
|
partial.append(word)
|
||||||
|
width += wwidth + swidth
|
||||||
|
else:
|
||||||
|
lines.append("".join(partial))
|
||||||
|
partial = [indent1, word]
|
||||||
|
width = measure(indent1) + wwidth
|
||||||
|
|
||||||
|
lines.append("".join(partial))
|
||||||
|
partial = [indent1]
|
||||||
|
width = measure(indent1)
|
||||||
|
|
||||||
|
return lines
|
||||||
|
|
||||||
|
|
||||||
|
def wrap_text_to_lines(string: str, max_chars: int) -> List[str]:
|
||||||
|
"""wrap_text_to_lines function
|
||||||
|
A helper that will return a list of lines with word-break wrapping
|
||||||
|
|
||||||
|
:param str string: The text to be wrapped
|
||||||
|
:param int max_chars: The maximum number of characters on a line before wrapping
|
||||||
|
|
||||||
|
:return: A list of lines where each line is separated based on the amount
|
||||||
|
of ``max_chars`` provided
|
||||||
|
:rtype: List[str]
|
||||||
|
"""
|
||||||
|
|
||||||
|
def chunks(lst, n):
|
||||||
|
"""Yield successive n-sized chunks from lst."""
|
||||||
|
for i in range(0, len(lst), n):
|
||||||
|
yield lst[i : i + n]
|
||||||
|
|
||||||
|
string = string.replace("\n", "").replace("\r", "") # Strip confusing newlines
|
||||||
|
words = string.split(" ")
|
||||||
|
the_lines = []
|
||||||
|
the_line = ""
|
||||||
|
for w in words:
|
||||||
|
if len(w) > max_chars:
|
||||||
|
if the_line: # add what we had stored
|
||||||
|
the_lines.append(the_line)
|
||||||
|
parts = []
|
||||||
|
for part in chunks(w, max_chars - 1):
|
||||||
|
parts.append("{}-".format(part))
|
||||||
|
the_lines.extend(parts[:-1])
|
||||||
|
the_line = parts[-1][:-1]
|
||||||
|
continue
|
||||||
|
|
||||||
|
if len(the_line + " " + w) <= max_chars:
|
||||||
|
the_line += " " + w
|
||||||
|
elif not the_line and len(w) == max_chars:
|
||||||
|
the_lines.append(w)
|
||||||
|
else:
|
||||||
|
the_lines.append(the_line)
|
||||||
|
the_line = "" + w
|
||||||
|
if the_line: # Last line remaining
|
||||||
|
the_lines.append(the_line)
|
||||||
|
# Remove any blank lines
|
||||||
|
while not the_lines[0]:
|
||||||
|
del the_lines[0]
|
||||||
|
# Remove first space from first line:
|
||||||
|
if the_lines[0][0] == " ":
|
||||||
|
the_lines[0] = the_lines[0][1:]
|
||||||
|
return the_lines
|
||||||
|
|
||||||
|
|
||||||
|
class LabelBase(Group):
|
||||||
|
# pylint: disable=too-many-instance-attributes
|
||||||
|
|
||||||
|
"""Superclass that all other types of labels will extend. This contains
|
||||||
|
all of the properties and functions that work the same way in all labels.
|
||||||
|
|
||||||
|
**Note:** This should be treated as an abstract base class.
|
||||||
|
|
||||||
|
Subclasses should implement ``_set_text``, ``_set_font``, and ``_set_line_spacing`` to
|
||||||
|
have the correct behavior for that type of label.
|
||||||
|
|
||||||
|
:param font: A font class that has ``get_bounding_box`` and ``get_glyph``.
|
||||||
|
Must include a capital M for measuring character size.
|
||||||
|
:type font: ~BuiltinFont, ~BDF, or ~PCF
|
||||||
|
:param str text: Text to display
|
||||||
|
:param int color: Color of all text in RGB hex
|
||||||
|
:param int background_color: Color of the background, use `None` for transparent
|
||||||
|
:param float line_spacing: Line spacing of text to display
|
||||||
|
:param bool background_tight: Set `True` only if you want background box to tightly
|
||||||
|
surround text. When set to 'True' Padding parameters will be ignored.
|
||||||
|
:param int padding_top: Additional pixels added to background bounding box at top
|
||||||
|
:param int padding_bottom: Additional pixels added to background bounding box at bottom
|
||||||
|
:param int padding_left: Additional pixels added to background bounding box at left
|
||||||
|
:param int padding_right: Additional pixels added to background bounding box at right
|
||||||
|
:param (float,float) anchor_point: Point that anchored_position moves relative to.
|
||||||
|
Tuple with decimal percentage of width and height.
|
||||||
|
(E.g. (0,0) is top left, (1.0, 0.5): is middle right.)
|
||||||
|
:param (int,int) anchored_position: Position relative to the anchor_point. Tuple
|
||||||
|
containing x,y pixel coordinates.
|
||||||
|
:param int scale: Integer value of the pixel scaling
|
||||||
|
:param bool base_alignment: when True allows to align text label to the baseline.
|
||||||
|
This is helpful when two or more labels need to be aligned to the same baseline
|
||||||
|
:param (int,str) tab_replacement: tuple with tab character replace information. When
|
||||||
|
(4, " ") will indicate a tab replacement of 4 spaces, defaults to 4 spaces by
|
||||||
|
tab character
|
||||||
|
:param str label_direction: string defining the label text orientation. See the
|
||||||
|
subclass documentation for the possible values."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
font: Union[BuiltinFont, BDF, PCF],
|
||||||
|
x: int = 0,
|
||||||
|
y: int = 0,
|
||||||
|
text: str = "",
|
||||||
|
color: int = 0xFFFFFF,
|
||||||
|
background_color: int = None,
|
||||||
|
line_spacing: float = 1.25,
|
||||||
|
background_tight: bool = False,
|
||||||
|
padding_top: int = 0,
|
||||||
|
padding_bottom: int = 0,
|
||||||
|
padding_left: int = 0,
|
||||||
|
padding_right: int = 0,
|
||||||
|
anchor_point: Tuple[float, float] = None,
|
||||||
|
anchored_position: Tuple[int, int] = None,
|
||||||
|
scale: int = 1,
|
||||||
|
base_alignment: bool = False,
|
||||||
|
tab_replacement: Tuple[int, str] = (4, " "),
|
||||||
|
label_direction: str = "LTR",
|
||||||
|
**kwargs, # pylint: disable=unused-argument
|
||||||
|
) -> None:
|
||||||
|
# pylint: disable=too-many-arguments, too-many-locals
|
||||||
|
|
||||||
|
super().__init__(x=x, y=y, scale=1)
|
||||||
|
|
||||||
|
self._font = font
|
||||||
|
self._text = text
|
||||||
|
self._palette = Palette(2)
|
||||||
|
self._color = 0xFFFFFF
|
||||||
|
self._background_color = None
|
||||||
|
self._line_spacing = line_spacing
|
||||||
|
self._background_tight = background_tight
|
||||||
|
self._padding_top = padding_top
|
||||||
|
self._padding_bottom = padding_bottom
|
||||||
|
self._padding_left = padding_left
|
||||||
|
self._padding_right = padding_right
|
||||||
|
self._anchor_point = anchor_point
|
||||||
|
self._anchored_position = anchored_position
|
||||||
|
self._base_alignment = base_alignment
|
||||||
|
self._label_direction = label_direction
|
||||||
|
self._tab_replacement = tab_replacement
|
||||||
|
self._tab_text = self._tab_replacement[1] * self._tab_replacement[0]
|
||||||
|
|
||||||
|
if "max_glyphs" in kwargs:
|
||||||
|
print("Please update your code: 'max_glyphs' is not needed anymore.")
|
||||||
|
|
||||||
|
self._ascent, self._descent = self._get_ascent_descent()
|
||||||
|
self._bounding_box = None
|
||||||
|
|
||||||
|
self.color = color
|
||||||
|
self.background_color = background_color
|
||||||
|
|
||||||
|
# local group will hold background and text
|
||||||
|
# the self group scale should always remain at 1, the self._local_group will
|
||||||
|
# be used to set the scale of the label
|
||||||
|
self._local_group = Group(scale=scale)
|
||||||
|
self.append(self._local_group)
|
||||||
|
|
||||||
|
self._baseline = -1.0
|
||||||
|
|
||||||
|
if self._base_alignment:
|
||||||
|
self._y_offset = 0
|
||||||
|
else:
|
||||||
|
self._y_offset = self._ascent // 2
|
||||||
|
|
||||||
|
def _get_ascent_descent(self) -> Tuple[int, int]:
|
||||||
|
""" Private function to calculate ascent and descent font values """
|
||||||
|
if hasattr(self.font, "ascent") and hasattr(self.font, "descent"):
|
||||||
|
return self.font.ascent, self.font.descent
|
||||||
|
|
||||||
|
# check a few glyphs for maximum ascender and descender height
|
||||||
|
glyphs = "M j'" # choose glyphs with highest ascender and lowest
|
||||||
|
try:
|
||||||
|
self._font.load_glyphs(glyphs)
|
||||||
|
except AttributeError:
|
||||||
|
# Builtin font doesn't have or need load_glyphs
|
||||||
|
pass
|
||||||
|
# descender, will depend upon font used
|
||||||
|
ascender_max = descender_max = 0
|
||||||
|
for char in glyphs:
|
||||||
|
this_glyph = self._font.get_glyph(ord(char))
|
||||||
|
if this_glyph:
|
||||||
|
ascender_max = max(ascender_max, this_glyph.height + this_glyph.dy)
|
||||||
|
descender_max = max(descender_max, -this_glyph.dy)
|
||||||
|
return ascender_max, descender_max
|
||||||
|
|
||||||
|
@property
|
||||||
|
def font(self) -> Union[BuiltinFont, BDF, PCF]:
|
||||||
|
"""Font to use for text display."""
|
||||||
|
return self._font
|
||||||
|
|
||||||
|
def _set_font(self, new_font: Union[BuiltinFont, BDF, PCF]) -> None:
|
||||||
|
raise NotImplementedError("{} MUST override '_set_font'".format(type(self)))
|
||||||
|
|
||||||
|
@font.setter
|
||||||
|
def font(self, new_font: Union[BuiltinFont, BDF, PCF]) -> None:
|
||||||
|
self._set_font(new_font)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def color(self) -> int:
|
||||||
|
"""Color of the text as an RGB hex number."""
|
||||||
|
return self._color
|
||||||
|
|
||||||
|
@color.setter
|
||||||
|
def color(self, new_color: int):
|
||||||
|
self._color = new_color
|
||||||
|
if new_color is not None:
|
||||||
|
self._palette[1] = new_color
|
||||||
|
self._palette.make_opaque(1)
|
||||||
|
else:
|
||||||
|
self._palette[1] = 0
|
||||||
|
self._palette.make_transparent(1)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def background_color(self) -> int:
|
||||||
|
"""Color of the background as an RGB hex number."""
|
||||||
|
return self._background_color
|
||||||
|
|
||||||
|
def _set_background_color(self, new_color):
|
||||||
|
raise NotImplementedError(
|
||||||
|
"{} MUST override '_set_background_color'".format(type(self))
|
||||||
|
)
|
||||||
|
|
||||||
|
@background_color.setter
|
||||||
|
def background_color(self, new_color: int) -> None:
|
||||||
|
self._set_background_color(new_color)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def anchor_point(self) -> Tuple[float, float]:
|
||||||
|
"""Point that anchored_position moves relative to.
|
||||||
|
Tuple with decimal percentage of width and height.
|
||||||
|
(E.g. (0,0) is top left, (1.0, 0.5): is middle right.)"""
|
||||||
|
return self._anchor_point
|
||||||
|
|
||||||
|
@anchor_point.setter
|
||||||
|
def anchor_point(self, new_anchor_point: Tuple[float, float]) -> None:
|
||||||
|
if new_anchor_point[1] == self._baseline:
|
||||||
|
self._anchor_point = (new_anchor_point[0], -1.0)
|
||||||
|
else:
|
||||||
|
self._anchor_point = new_anchor_point
|
||||||
|
|
||||||
|
# update the anchored_position using setter
|
||||||
|
self.anchored_position = self._anchored_position
|
||||||
|
|
||||||
|
@property
|
||||||
|
def anchored_position(self) -> Tuple[int, int]:
|
||||||
|
"""Position relative to the anchor_point. Tuple containing x,y
|
||||||
|
pixel coordinates."""
|
||||||
|
return self._anchored_position
|
||||||
|
|
||||||
|
@anchored_position.setter
|
||||||
|
def anchored_position(self, new_position: Tuple[int, int]) -> None:
|
||||||
|
self._anchored_position = new_position
|
||||||
|
# Calculate (x,y) position
|
||||||
|
if (self._anchor_point is not None) and (self._anchored_position is not None):
|
||||||
|
self.x = int(
|
||||||
|
new_position[0]
|
||||||
|
- (self._bounding_box[0] * self.scale)
|
||||||
|
- round(self._anchor_point[0] * (self._bounding_box[2] * self.scale))
|
||||||
|
)
|
||||||
|
if self._anchor_point[1] == self._baseline:
|
||||||
|
self.y = int(new_position[1] - (self._y_offset * self.scale))
|
||||||
|
else:
|
||||||
|
self.y = int(
|
||||||
|
new_position[1]
|
||||||
|
- (self._bounding_box[1] * self.scale)
|
||||||
|
- round(self._anchor_point[1] * self._bounding_box[3] * self.scale)
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def scale(self) -> int:
|
||||||
|
"""Set the scaling of the label, in integer values"""
|
||||||
|
return self._local_group.scale
|
||||||
|
|
||||||
|
@scale.setter
|
||||||
|
def scale(self, new_scale: int) -> None:
|
||||||
|
self._local_group.scale = new_scale
|
||||||
|
self.anchored_position = self._anchored_position # update the anchored_position
|
||||||
|
|
||||||
|
def _set_text(self, new_text: str, scale: int) -> None:
|
||||||
|
raise NotImplementedError("{} MUST override '_set_text'".format(type(self)))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def text(self) -> str:
|
||||||
|
"""Text to be displayed."""
|
||||||
|
return self._text
|
||||||
|
|
||||||
|
@text.setter # Cannot set color or background color with text setter, use separate setter
|
||||||
|
def text(self, new_text: str) -> None:
|
||||||
|
self._set_text(new_text, self.scale)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def bounding_box(self) -> Tuple[int, int]:
|
||||||
|
"""An (x, y, w, h) tuple that completely covers all glyphs. The
|
||||||
|
first two numbers are offset from the x, y origin of this group"""
|
||||||
|
return tuple(self._bounding_box)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def height(self) -> int:
|
||||||
|
"""The height of the label determined from the bounding box."""
|
||||||
|
return self._bounding_box[3] - self._bounding_box[1]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def width(self) -> int:
|
||||||
|
"""The width of the label determined from the bounding box."""
|
||||||
|
return self._bounding_box[2] - self._bounding_box[0]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def line_spacing(self) -> float:
|
||||||
|
"""The amount of space between lines of text, in multiples of the font's
|
||||||
|
bounding-box height. (E.g. 1.0 is the bounding-box height)"""
|
||||||
|
return self._line_spacing
|
||||||
|
|
||||||
|
def _set_line_spacing(self, new_line_spacing: float) -> None:
|
||||||
|
raise NotImplementedError(
|
||||||
|
"{} MUST override '_set_line_spacing'".format(type(self))
|
||||||
|
)
|
||||||
|
|
||||||
|
@line_spacing.setter
|
||||||
|
def line_spacing(self, new_line_spacing: float) -> None:
|
||||||
|
self._set_line_spacing(new_line_spacing)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def label_direction(self) -> str:
|
||||||
|
"""Set the text direction of the label"""
|
||||||
|
return self._label_direction
|
||||||
|
|
||||||
|
def _set_label_direction(self, new_label_direction: str) -> None:
|
||||||
|
raise NotImplementedError(
|
||||||
|
"{} MUST override '_set_label_direction'".format(type(self))
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_valid_label_directions(self) -> Tuple[str, ...]:
|
||||||
|
raise NotImplementedError(
|
||||||
|
"{} MUST override '_get_valid_label_direction'".format(type(self))
|
||||||
|
)
|
||||||
|
|
||||||
|
@label_direction.setter
|
||||||
|
def label_direction(self, new_label_direction: str) -> None:
|
||||||
|
"""Set the text direction of the label"""
|
||||||
|
if new_label_direction not in self._get_valid_label_directions():
|
||||||
|
raise RuntimeError("Please provide a valid text direction")
|
||||||
|
self._set_label_direction(new_label_direction)
|
||||||
|
|
||||||
|
def _replace_tabs(self, text: str) -> str:
|
||||||
|
return self._tab_text.join(text.split("\t"))
|
||||||
553
QtPy-RP2040-Tem-Hum/lib/adafruit_display_text/bitmap_label.py
Executable file
553
QtPy-RP2040-Tem-Hum/lib/adafruit_display_text/bitmap_label.py
Executable file
@ -0,0 +1,553 @@
|
|||||||
|
# SPDX-FileCopyrightText: 2020 Kevin Matocha
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
"""
|
||||||
|
`adafruit_display_text.bitmap_label`
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Text graphics handling for CircuitPython, including text boxes
|
||||||
|
|
||||||
|
|
||||||
|
* Author(s): Kevin Matocha
|
||||||
|
|
||||||
|
Implementation Notes
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
**Hardware:**
|
||||||
|
|
||||||
|
**Software and Dependencies:**
|
||||||
|
|
||||||
|
* Adafruit CircuitPython firmware for the supported boards:
|
||||||
|
https://circuitpython.org/downloads
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
__version__ = "2.21.2"
|
||||||
|
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Display_Text.git"
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from typing import Union, Optional, Tuple
|
||||||
|
from fontio import BuiltinFont
|
||||||
|
from adafruit_bitmap_font.bdf import BDF
|
||||||
|
from adafruit_bitmap_font.pcf import PCF
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
import displayio
|
||||||
|
|
||||||
|
from adafruit_display_text import LabelBase
|
||||||
|
|
||||||
|
|
||||||
|
class Label(LabelBase):
|
||||||
|
"""A label displaying a string of text that is stored in a bitmap.
|
||||||
|
Note: This ``bitmap_label.py`` library utilizes a :py:class:`~displayio.Bitmap`
|
||||||
|
to display the text. This method is memory-conserving relative to ``label.py``.
|
||||||
|
|
||||||
|
For further reduction in memory usage, set ``save_text=False`` (text string will not
|
||||||
|
be stored and ``line_spacing`` and ``font`` are immutable with ``save_text``
|
||||||
|
set to ``False``).
|
||||||
|
|
||||||
|
The origin point set by ``x`` and ``y``
|
||||||
|
properties will be the left edge of the bounding box, and in the center of a M
|
||||||
|
glyph (if its one line), or the (number of lines * linespacing + M)/2. That is,
|
||||||
|
it will try to have it be center-left as close as possible.
|
||||||
|
|
||||||
|
:param font: A font class that has ``get_bounding_box`` and ``get_glyph``.
|
||||||
|
Must include a capital M for measuring character size.
|
||||||
|
:type font: ~BuiltinFont, ~BDF, or ~PCF
|
||||||
|
:param str text: Text to display
|
||||||
|
:param int color: Color of all text in RGB hex
|
||||||
|
:param int background_color: Color of the background, use `None` for transparent
|
||||||
|
:param float line_spacing: Line spacing of text to display
|
||||||
|
:param bool background_tight: Set `True` only if you want background box to tightly
|
||||||
|
surround text. When set to 'True' Padding parameters will be ignored.
|
||||||
|
:param int padding_top: Additional pixels added to background bounding box at top
|
||||||
|
:param int padding_bottom: Additional pixels added to background bounding box at bottom
|
||||||
|
:param int padding_left: Additional pixels added to background bounding box at left
|
||||||
|
:param int padding_right: Additional pixels added to background bounding box at right
|
||||||
|
:param (float,float) anchor_point: Point that anchored_position moves relative to.
|
||||||
|
Tuple with decimal percentage of width and height.
|
||||||
|
(E.g. (0,0) is top left, (1.0, 0.5): is middle right.)
|
||||||
|
:param (int,int) anchored_position: Position relative to the anchor_point. Tuple
|
||||||
|
containing x,y pixel coordinates.
|
||||||
|
:param int scale: Integer value of the pixel scaling
|
||||||
|
:param bool save_text: Set True to save the text string as a constant in the
|
||||||
|
label structure. Set False to reduce memory use.
|
||||||
|
:param bool base_alignment: when True allows to align text label to the baseline.
|
||||||
|
This is helpful when two or more labels need to be aligned to the same baseline
|
||||||
|
:param (int,str) tab_replacement: tuple with tab character replace information. When
|
||||||
|
(4, " ") will indicate a tab replacement of 4 spaces, defaults to 4 spaces by
|
||||||
|
tab character
|
||||||
|
:param str label_direction: string defining the label text orientation. There are 5
|
||||||
|
configurations possibles ``LTR``-Left-To-Right ``RTL``-Right-To-Left
|
||||||
|
``UPD``-Upside Down ``UPR``-Upwards ``DWR``-Downwards. It defaults to ``LTR``"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, font: Union[BuiltinFont, BDF, PCF], save_text: bool = True, **kwargs
|
||||||
|
) -> None:
|
||||||
|
|
||||||
|
self._bitmap = None
|
||||||
|
|
||||||
|
super().__init__(font, **kwargs)
|
||||||
|
|
||||||
|
self._save_text = save_text
|
||||||
|
self._text = self._replace_tabs(self._text)
|
||||||
|
|
||||||
|
if self._label_direction == "RTL":
|
||||||
|
self._text = "".join(reversed(self._text))
|
||||||
|
|
||||||
|
# call the text updater with all the arguments.
|
||||||
|
self._reset_text(
|
||||||
|
font=font,
|
||||||
|
text=self._text,
|
||||||
|
line_spacing=self._line_spacing,
|
||||||
|
scale=self.scale,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _reset_text(
|
||||||
|
self,
|
||||||
|
font: Optional[Union[BuiltinFont, BDF, PCF]] = None,
|
||||||
|
text: Optional[str] = None,
|
||||||
|
line_spacing: Optional[float] = None,
|
||||||
|
scale: Optional[int] = None,
|
||||||
|
) -> None:
|
||||||
|
# pylint: disable=too-many-branches, too-many-statements
|
||||||
|
|
||||||
|
# Store all the instance variables
|
||||||
|
if font is not None:
|
||||||
|
self._font = font
|
||||||
|
if line_spacing is not None:
|
||||||
|
self._line_spacing = line_spacing
|
||||||
|
|
||||||
|
# if text is not provided as a parameter (text is None), use the previous value.
|
||||||
|
if (text is None) and self._save_text:
|
||||||
|
text = self._text
|
||||||
|
|
||||||
|
if self._save_text: # text string will be saved
|
||||||
|
self._text = self._replace_tabs(text)
|
||||||
|
if self._label_direction == "RTL":
|
||||||
|
self._text = "".join(reversed(self._text))
|
||||||
|
else:
|
||||||
|
self._text = None # save a None value since text string is not saved
|
||||||
|
|
||||||
|
# Check for empty string
|
||||||
|
if (text == "") or (
|
||||||
|
text is None
|
||||||
|
): # If empty string, just create a zero-sized bounding box and that's it.
|
||||||
|
|
||||||
|
self._bounding_box = (
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0, # zero width with text == ""
|
||||||
|
0, # zero height with text == ""
|
||||||
|
)
|
||||||
|
# Clear out any items in the self._local_group Group, in case this is an
|
||||||
|
# update to the bitmap_label
|
||||||
|
for _ in self._local_group:
|
||||||
|
self._local_group.pop(0)
|
||||||
|
|
||||||
|
else: # The text string is not empty, so create the Bitmap and TileGrid and
|
||||||
|
# append to the self Group
|
||||||
|
|
||||||
|
# Calculate the text bounding box
|
||||||
|
|
||||||
|
# Calculate both "tight" and "loose" bounding box dimensions to match label for
|
||||||
|
# anchor_position calculations
|
||||||
|
(
|
||||||
|
box_x,
|
||||||
|
tight_box_y,
|
||||||
|
x_offset,
|
||||||
|
tight_y_offset,
|
||||||
|
loose_box_y,
|
||||||
|
loose_y_offset,
|
||||||
|
) = self._text_bounding_box(
|
||||||
|
text,
|
||||||
|
self._font,
|
||||||
|
) # calculate the box size for a tight and loose backgrounds
|
||||||
|
|
||||||
|
if self._background_tight:
|
||||||
|
box_y = tight_box_y
|
||||||
|
y_offset = tight_y_offset
|
||||||
|
|
||||||
|
else: # calculate the box size for a loose background
|
||||||
|
box_y = loose_box_y
|
||||||
|
y_offset = loose_y_offset
|
||||||
|
|
||||||
|
# Calculate the background size including padding
|
||||||
|
box_x = box_x + self._padding_left + self._padding_right
|
||||||
|
box_y = box_y + self._padding_top + self._padding_bottom
|
||||||
|
|
||||||
|
# Create the bitmap and TileGrid
|
||||||
|
self._bitmap = displayio.Bitmap(box_x, box_y, len(self._palette))
|
||||||
|
|
||||||
|
# Place the text into the Bitmap
|
||||||
|
self._place_text(
|
||||||
|
self._bitmap,
|
||||||
|
text,
|
||||||
|
self._font,
|
||||||
|
self._padding_left - x_offset,
|
||||||
|
self._padding_top + y_offset,
|
||||||
|
)
|
||||||
|
|
||||||
|
if self._base_alignment:
|
||||||
|
label_position_yoffset = 0
|
||||||
|
else:
|
||||||
|
label_position_yoffset = self._ascent // 2
|
||||||
|
|
||||||
|
self._tilegrid = displayio.TileGrid(
|
||||||
|
self._bitmap,
|
||||||
|
pixel_shader=self._palette,
|
||||||
|
width=1,
|
||||||
|
height=1,
|
||||||
|
tile_width=box_x,
|
||||||
|
tile_height=box_y,
|
||||||
|
default_tile=0,
|
||||||
|
x=-self._padding_left + x_offset,
|
||||||
|
y=label_position_yoffset - y_offset - self._padding_top,
|
||||||
|
)
|
||||||
|
|
||||||
|
if self._label_direction == "UPR":
|
||||||
|
self._tilegrid.transpose_xy = True
|
||||||
|
self._tilegrid.flip_x = True
|
||||||
|
if self._label_direction == "DWR":
|
||||||
|
self._tilegrid.transpose_xy = True
|
||||||
|
self._tilegrid.flip_y = True
|
||||||
|
if self._label_direction == "UPD":
|
||||||
|
self._tilegrid.flip_x = True
|
||||||
|
self._tilegrid.flip_y = True
|
||||||
|
|
||||||
|
# Clear out any items in the local_group Group, in case this is an update to
|
||||||
|
# the bitmap_label
|
||||||
|
for _ in self._local_group:
|
||||||
|
self._local_group.pop(0)
|
||||||
|
self._local_group.append(
|
||||||
|
self._tilegrid
|
||||||
|
) # add the bitmap's tilegrid to the group
|
||||||
|
|
||||||
|
# Update bounding_box values. Note: To be consistent with label.py,
|
||||||
|
# this is the bounding box for the text only, not including the background.
|
||||||
|
if self._label_direction in ("UPR", "DWR"):
|
||||||
|
self._bounding_box = (
|
||||||
|
self._tilegrid.x,
|
||||||
|
self._tilegrid.y,
|
||||||
|
tight_box_y,
|
||||||
|
box_x,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self._bounding_box = (
|
||||||
|
self._tilegrid.x,
|
||||||
|
self._tilegrid.y,
|
||||||
|
box_x,
|
||||||
|
tight_box_y,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
scale is not None
|
||||||
|
): # Scale will be defined in local_group (Note: self should have scale=1)
|
||||||
|
self.scale = scale # call the setter
|
||||||
|
|
||||||
|
# set the anchored_position with setter after bitmap is created, sets the
|
||||||
|
# x,y positions of the label
|
||||||
|
self.anchored_position = self._anchored_position
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _line_spacing_ypixels(
|
||||||
|
font: Union[BuiltinFont, BDF, PCF], line_spacing: float
|
||||||
|
) -> int:
|
||||||
|
# Note: Scaling is provided at the Group level
|
||||||
|
return_value = int(line_spacing * font.get_bounding_box()[1])
|
||||||
|
return return_value
|
||||||
|
|
||||||
|
def _text_bounding_box(
|
||||||
|
self, text: str, font: Union[BuiltinFont, BDF, PCF]
|
||||||
|
) -> Tuple[int, int, int, int, int, int]:
|
||||||
|
# pylint: disable=too-many-locals
|
||||||
|
|
||||||
|
ascender_max, descender_max = self._ascent, self._descent
|
||||||
|
|
||||||
|
lines = 1
|
||||||
|
|
||||||
|
xposition = (
|
||||||
|
x_start
|
||||||
|
) = yposition = y_start = 0 # starting x and y position (left margin)
|
||||||
|
|
||||||
|
left = None
|
||||||
|
right = x_start
|
||||||
|
top = bottom = y_start
|
||||||
|
|
||||||
|
y_offset_tight = self._ascent // 2
|
||||||
|
|
||||||
|
newline = False
|
||||||
|
line_spacing = self._line_spacing
|
||||||
|
|
||||||
|
for char in text:
|
||||||
|
|
||||||
|
if char == "\n": # newline
|
||||||
|
newline = True
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
my_glyph = font.get_glyph(ord(char))
|
||||||
|
|
||||||
|
if my_glyph is None: # Error checking: no glyph found
|
||||||
|
print("Glyph not found: {}".format(repr(char)))
|
||||||
|
else:
|
||||||
|
if newline:
|
||||||
|
newline = False
|
||||||
|
xposition = x_start # reset to left column
|
||||||
|
yposition = yposition + self._line_spacing_ypixels(
|
||||||
|
font, line_spacing
|
||||||
|
) # Add a newline
|
||||||
|
lines += 1
|
||||||
|
if xposition == x_start:
|
||||||
|
if left is None:
|
||||||
|
left = my_glyph.dx
|
||||||
|
else:
|
||||||
|
left = min(left, my_glyph.dx)
|
||||||
|
xright = xposition + my_glyph.width + my_glyph.dx
|
||||||
|
xposition += my_glyph.shift_x
|
||||||
|
|
||||||
|
right = max(right, xposition, xright)
|
||||||
|
|
||||||
|
if yposition == y_start: # first line, find the Ascender height
|
||||||
|
top = min(top, -my_glyph.height - my_glyph.dy + y_offset_tight)
|
||||||
|
bottom = max(bottom, yposition - my_glyph.dy + y_offset_tight)
|
||||||
|
|
||||||
|
if left is None:
|
||||||
|
left = 0
|
||||||
|
|
||||||
|
final_box_width = right - left
|
||||||
|
|
||||||
|
final_box_height_tight = bottom - top
|
||||||
|
final_y_offset_tight = -top + y_offset_tight
|
||||||
|
|
||||||
|
final_box_height_loose = (lines - 1) * self._line_spacing_ypixels(
|
||||||
|
font, line_spacing
|
||||||
|
) + (ascender_max + descender_max)
|
||||||
|
final_y_offset_loose = ascender_max
|
||||||
|
|
||||||
|
# return (final_box_width, final_box_height, left, final_y_offset)
|
||||||
|
|
||||||
|
return (
|
||||||
|
final_box_width,
|
||||||
|
final_box_height_tight,
|
||||||
|
left,
|
||||||
|
final_y_offset_tight,
|
||||||
|
final_box_height_loose,
|
||||||
|
final_y_offset_loose,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _place_text(
|
||||||
|
self,
|
||||||
|
bitmap: displayio.Bitmap,
|
||||||
|
text: str,
|
||||||
|
font: Union[BuiltinFont, BDF, PCF],
|
||||||
|
xposition: int,
|
||||||
|
yposition: int,
|
||||||
|
skip_index: int = 0, # set to None to write all pixels, other wise skip this palette index
|
||||||
|
# when copying glyph bitmaps (this is important for slanted text
|
||||||
|
# where rectangular glyph boxes overlap)
|
||||||
|
) -> Tuple[int, int, int, int]:
|
||||||
|
# pylint: disable=too-many-arguments, too-many-locals
|
||||||
|
|
||||||
|
# placeText - Writes text into a bitmap at the specified location.
|
||||||
|
#
|
||||||
|
# Note: scale is pushed up to Group level
|
||||||
|
|
||||||
|
x_start = xposition # starting x position (left margin)
|
||||||
|
y_start = yposition
|
||||||
|
|
||||||
|
left = None
|
||||||
|
right = x_start
|
||||||
|
top = bottom = y_start
|
||||||
|
line_spacing = self._line_spacing
|
||||||
|
|
||||||
|
for char in text:
|
||||||
|
|
||||||
|
if char == "\n": # newline
|
||||||
|
xposition = x_start # reset to left column
|
||||||
|
yposition = yposition + self._line_spacing_ypixels(
|
||||||
|
font, line_spacing
|
||||||
|
) # Add a newline
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
my_glyph = font.get_glyph(ord(char))
|
||||||
|
|
||||||
|
if my_glyph is None: # Error checking: no glyph found
|
||||||
|
print("Glyph not found: {}".format(repr(char)))
|
||||||
|
else:
|
||||||
|
if xposition == x_start:
|
||||||
|
if left is None:
|
||||||
|
left = my_glyph.dx
|
||||||
|
else:
|
||||||
|
left = min(left, my_glyph.dx)
|
||||||
|
|
||||||
|
right = max(
|
||||||
|
right,
|
||||||
|
xposition + my_glyph.shift_x,
|
||||||
|
xposition + my_glyph.width + my_glyph.dx,
|
||||||
|
)
|
||||||
|
if yposition == y_start: # first line, find the Ascender height
|
||||||
|
top = min(top, -my_glyph.height - my_glyph.dy)
|
||||||
|
bottom = max(bottom, yposition - my_glyph.dy)
|
||||||
|
|
||||||
|
glyph_offset_x = (
|
||||||
|
my_glyph.tile_index * my_glyph.width
|
||||||
|
) # for type BuiltinFont, this creates the x-offset in the glyph bitmap.
|
||||||
|
# for BDF loaded fonts, this should equal 0
|
||||||
|
|
||||||
|
y_blit_target = yposition - my_glyph.height - my_glyph.dy
|
||||||
|
|
||||||
|
# Clip glyph y-direction if outside the font ascent/descent metrics.
|
||||||
|
# Note: bitmap.blit will automatically clip the bottom of the glyph.
|
||||||
|
y_clip = 0
|
||||||
|
if y_blit_target < 0:
|
||||||
|
y_clip = -y_blit_target # clip this amount from top of bitmap
|
||||||
|
y_blit_target = 0 # draw the clipped bitmap at y=0
|
||||||
|
|
||||||
|
print(
|
||||||
|
'Warning: Glyph clipped, exceeds Ascent property: "{}"'.format(
|
||||||
|
char
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (y_blit_target + my_glyph.height) > bitmap.height:
|
||||||
|
print(
|
||||||
|
'Warning: Glyph clipped, exceeds descent property: "{}"'.format(
|
||||||
|
char
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self._blit(
|
||||||
|
bitmap,
|
||||||
|
xposition + my_glyph.dx,
|
||||||
|
y_blit_target,
|
||||||
|
my_glyph.bitmap,
|
||||||
|
x_1=glyph_offset_x,
|
||||||
|
y_1=y_clip,
|
||||||
|
x_2=glyph_offset_x + my_glyph.width,
|
||||||
|
y_2=my_glyph.height,
|
||||||
|
skip_index=skip_index, # do not copy over any 0 background pixels
|
||||||
|
)
|
||||||
|
|
||||||
|
xposition = xposition + my_glyph.shift_x
|
||||||
|
|
||||||
|
# bounding_box
|
||||||
|
return left, top, right - left, bottom - top
|
||||||
|
|
||||||
|
def _blit(
|
||||||
|
self,
|
||||||
|
bitmap: displayio.Bitmap, # target bitmap
|
||||||
|
x: int, # target x upper left corner
|
||||||
|
y: int, # target y upper left corner
|
||||||
|
source_bitmap: displayio.Bitmap, # source bitmap
|
||||||
|
x_1: int = 0, # source x start
|
||||||
|
y_1: int = 0, # source y start
|
||||||
|
x_2: int = None, # source x end
|
||||||
|
y_2: int = None, # source y end
|
||||||
|
skip_index: int = None, # palette index that will not be copied
|
||||||
|
# (for example: the background color of a glyph)
|
||||||
|
) -> None:
|
||||||
|
# pylint: disable=no-self-use, too-many-arguments
|
||||||
|
|
||||||
|
if hasattr(bitmap, "blit"): # if bitmap has a built-in blit function, call it
|
||||||
|
# this function should perform its own input checks
|
||||||
|
bitmap.blit(
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
source_bitmap,
|
||||||
|
x1=x_1,
|
||||||
|
y1=y_1,
|
||||||
|
x2=x_2,
|
||||||
|
y2=y_2,
|
||||||
|
skip_index=skip_index,
|
||||||
|
)
|
||||||
|
|
||||||
|
else: # perform pixel by pixel copy of the bitmap
|
||||||
|
|
||||||
|
# Perform input checks
|
||||||
|
|
||||||
|
if x_2 is None:
|
||||||
|
x_2 = source_bitmap.width
|
||||||
|
if y_2 is None:
|
||||||
|
y_2 = source_bitmap.height
|
||||||
|
|
||||||
|
# Rearrange so that x_1 < x_2 and y1 < y2
|
||||||
|
if x_1 > x_2:
|
||||||
|
x_1, x_2 = x_2, x_1
|
||||||
|
if y_1 > y_2:
|
||||||
|
y_1, y_2 = y_2, y_1
|
||||||
|
|
||||||
|
# Ensure that x2 and y2 are within source bitmap size
|
||||||
|
x_2 = min(x_2, source_bitmap.width)
|
||||||
|
y_2 = min(y_2, source_bitmap.height)
|
||||||
|
|
||||||
|
for y_count in range(y_2 - y_1):
|
||||||
|
for x_count in range(x_2 - x_1):
|
||||||
|
x_placement = x + x_count
|
||||||
|
y_placement = y + y_count
|
||||||
|
|
||||||
|
if (bitmap.width > x_placement >= 0) and (
|
||||||
|
bitmap.height > y_placement >= 0
|
||||||
|
): # ensure placement is within target bitmap
|
||||||
|
|
||||||
|
# get the palette index from the source bitmap
|
||||||
|
this_pixel_color = source_bitmap[
|
||||||
|
y_1
|
||||||
|
+ (
|
||||||
|
y_count * source_bitmap.width
|
||||||
|
) # Direct index into a bitmap array is speedier than [x,y] tuple
|
||||||
|
+ x_1
|
||||||
|
+ x_count
|
||||||
|
]
|
||||||
|
|
||||||
|
if (skip_index is None) or (this_pixel_color != skip_index):
|
||||||
|
bitmap[ # Direct index into a bitmap array is speedier than [x,y] tuple
|
||||||
|
y_placement * bitmap.width + x_placement
|
||||||
|
] = this_pixel_color
|
||||||
|
elif y_placement > bitmap.height:
|
||||||
|
break
|
||||||
|
|
||||||
|
def _set_line_spacing(self, new_line_spacing: float) -> None:
|
||||||
|
if self._save_text:
|
||||||
|
self._reset_text(line_spacing=new_line_spacing, scale=self.scale)
|
||||||
|
else:
|
||||||
|
raise RuntimeError("line_spacing is immutable when save_text is False")
|
||||||
|
|
||||||
|
def _set_font(self, new_font: Union[BuiltinFont, BDF, PCF]) -> None:
|
||||||
|
self._font = new_font
|
||||||
|
if self._save_text:
|
||||||
|
self._reset_text(font=new_font, scale=self.scale)
|
||||||
|
else:
|
||||||
|
raise RuntimeError("font is immutable when save_text is False")
|
||||||
|
|
||||||
|
def _set_text(self, new_text: str, scale: int) -> None:
|
||||||
|
self._reset_text(text=self._replace_tabs(new_text), scale=self.scale)
|
||||||
|
|
||||||
|
def _set_background_color(self, new_color: Optional[int]):
|
||||||
|
self._background_color = new_color
|
||||||
|
if new_color is not None:
|
||||||
|
self._palette[0] = new_color
|
||||||
|
self._palette.make_opaque(0)
|
||||||
|
else:
|
||||||
|
self._palette[0] = 0
|
||||||
|
self._palette.make_transparent(0)
|
||||||
|
|
||||||
|
def _set_label_direction(self, new_label_direction: str) -> None:
|
||||||
|
self._label_direction = new_label_direction
|
||||||
|
self._reset_text(text=str(self._text)) # Force a recalculation
|
||||||
|
|
||||||
|
def _get_valid_label_directions(self) -> Tuple[str, ...]:
|
||||||
|
return "LTR", "RTL", "UPD", "UPR", "DWR"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def bitmap(self) -> displayio.Bitmap:
|
||||||
|
"""
|
||||||
|
The Bitmap object that the text and background are drawn into.
|
||||||
|
|
||||||
|
:rtype: displayio.Bitmap
|
||||||
|
"""
|
||||||
|
return self._bitmap
|
||||||
427
QtPy-RP2040-Tem-Hum/lib/adafruit_display_text/label.py
Executable file
427
QtPy-RP2040-Tem-Hum/lib/adafruit_display_text/label.py
Executable file
@ -0,0 +1,427 @@
|
|||||||
|
# SPDX-FileCopyrightText: 2019 Scott Shawcroft for Adafruit Industries
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
"""
|
||||||
|
`adafruit_display_text.label`
|
||||||
|
====================================================
|
||||||
|
|
||||||
|
Displays text labels using CircuitPython's displayio.
|
||||||
|
|
||||||
|
* Author(s): Scott Shawcroft
|
||||||
|
|
||||||
|
Implementation Notes
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
**Hardware:**
|
||||||
|
|
||||||
|
**Software and Dependencies:**
|
||||||
|
|
||||||
|
* Adafruit CircuitPython firmware for the supported boards:
|
||||||
|
https://circuitpython.org/downloads
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
__version__ = "2.21.2"
|
||||||
|
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Display_Text.git"
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from typing import Union, Optional, Tuple
|
||||||
|
from fontio import BuiltinFont
|
||||||
|
from adafruit_bitmap_font.bdf import BDF
|
||||||
|
from adafruit_bitmap_font.pcf import PCF
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
from displayio import Bitmap, Palette, TileGrid
|
||||||
|
|
||||||
|
from adafruit_display_text import LabelBase
|
||||||
|
|
||||||
|
|
||||||
|
class Label(LabelBase):
|
||||||
|
# pylint: disable=too-many-instance-attributes
|
||||||
|
|
||||||
|
"""A label displaying a string of text. The origin point set by ``x`` and ``y``
|
||||||
|
properties will be the left edge of the bounding box, and in the center of a M
|
||||||
|
glyph (if its one line), or the (number of lines * linespacing + M)/2. That is,
|
||||||
|
it will try to have it be center-left as close as possible.
|
||||||
|
|
||||||
|
:param font: A font class that has ``get_bounding_box`` and ``get_glyph``.
|
||||||
|
Must include a capital M for measuring character size.
|
||||||
|
:type font: ~BuiltinFont, ~BDF, or ~PCF
|
||||||
|
:param str text: Text to display
|
||||||
|
:param int color: Color of all text in RGB hex
|
||||||
|
:param int background_color: Color of the background, use `None` for transparent
|
||||||
|
:param float line_spacing: Line spacing of text to display
|
||||||
|
:param bool background_tight: Set `True` only if you want background box to tightly
|
||||||
|
surround text. When set to 'True' Padding parameters will be ignored.
|
||||||
|
:param int padding_top: Additional pixels added to background bounding box at top.
|
||||||
|
This parameter could be negative indicating additional pixels subtracted from the
|
||||||
|
background bounding box.
|
||||||
|
:param int padding_bottom: Additional pixels added to background bounding box at bottom.
|
||||||
|
This parameter could be negative indicating additional pixels subtracted from the
|
||||||
|
background bounding box.
|
||||||
|
:param int padding_left: Additional pixels added to background bounding box at left.
|
||||||
|
This parameter could be negative indicating additional pixels subtracted from the
|
||||||
|
background bounding box.
|
||||||
|
:param int padding_right: Additional pixels added to background bounding box at right.
|
||||||
|
This parameter could be negative indicating additional pixels subtracted from the
|
||||||
|
background bounding box.
|
||||||
|
:param (float,float) anchor_point: Point that anchored_position moves relative to.
|
||||||
|
Tuple with decimal percentage of width and height.
|
||||||
|
(E.g. (0,0) is top left, (1.0, 0.5): is middle right.)
|
||||||
|
:param (int,int) anchored_position: Position relative to the anchor_point. Tuple
|
||||||
|
containing x,y pixel coordinates.
|
||||||
|
:param int scale: Integer value of the pixel scaling
|
||||||
|
:param bool base_alignment: when True allows to align text label to the baseline.
|
||||||
|
This is helpful when two or more labels need to be aligned to the same baseline
|
||||||
|
:param (int,str) tab_replacement: tuple with tab character replace information. When
|
||||||
|
(4, " ") will indicate a tab replacement of 4 spaces, defaults to 4 spaces by
|
||||||
|
tab character
|
||||||
|
:param str label_direction: string defining the label text orientation. There are 5
|
||||||
|
configurations possibles ``LTR``-Left-To-Right ``RTL``-Right-To-Left
|
||||||
|
``TTB``-Top-To-Bottom ``UPR``-Upwards ``DWR``-Downwards. It defaults to ``LTR``"""
|
||||||
|
|
||||||
|
def __init__(self, font: Union[BuiltinFont, BDF, PCF], **kwargs) -> None:
|
||||||
|
self._background_palette = Palette(1)
|
||||||
|
self._added_background_tilegrid = False
|
||||||
|
|
||||||
|
super().__init__(font, **kwargs)
|
||||||
|
|
||||||
|
text = self._replace_tabs(self._text)
|
||||||
|
|
||||||
|
self._width = len(text)
|
||||||
|
self._height = self._font.get_bounding_box()[1]
|
||||||
|
|
||||||
|
# Create the two-color text palette
|
||||||
|
self._palette[0] = 0
|
||||||
|
self._palette.make_transparent(0)
|
||||||
|
|
||||||
|
if text is not None:
|
||||||
|
self._reset_text(str(text))
|
||||||
|
|
||||||
|
def _create_background_box(self, lines: int, y_offset: int) -> TileGrid:
|
||||||
|
"""Private Class function to create a background_box
|
||||||
|
:param lines: int number of lines
|
||||||
|
:param y_offset: int y pixel bottom coordinate for the background_box"""
|
||||||
|
|
||||||
|
left = self._bounding_box[0]
|
||||||
|
if self._background_tight: # draw a tight bounding box
|
||||||
|
box_width = self._bounding_box[2]
|
||||||
|
box_height = self._bounding_box[3]
|
||||||
|
x_box_offset = 0
|
||||||
|
y_box_offset = self._bounding_box[1]
|
||||||
|
|
||||||
|
else: # draw a "loose" bounding box to include any ascenders/descenders.
|
||||||
|
ascent, descent = self._ascent, self._descent
|
||||||
|
|
||||||
|
if self._label_direction in ("UPR", "DWR", "TTB"):
|
||||||
|
box_height = (
|
||||||
|
self._bounding_box[3] + self._padding_top + self._padding_bottom
|
||||||
|
)
|
||||||
|
x_box_offset = -self._padding_bottom
|
||||||
|
box_width = (
|
||||||
|
(ascent + descent)
|
||||||
|
+ int((lines - 1) * self._width * self._line_spacing)
|
||||||
|
+ self._padding_left
|
||||||
|
+ self._padding_right
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
box_width = (
|
||||||
|
self._bounding_box[2] + self._padding_left + self._padding_right
|
||||||
|
)
|
||||||
|
x_box_offset = -self._padding_left
|
||||||
|
box_height = (
|
||||||
|
(ascent + descent)
|
||||||
|
+ int((lines - 1) * self._height * self._line_spacing)
|
||||||
|
+ self._padding_top
|
||||||
|
+ self._padding_bottom
|
||||||
|
)
|
||||||
|
|
||||||
|
if self._base_alignment:
|
||||||
|
y_box_offset = -ascent - self._padding_top
|
||||||
|
else:
|
||||||
|
y_box_offset = -ascent + y_offset - self._padding_top
|
||||||
|
|
||||||
|
box_width = max(0, box_width) # remove any negative values
|
||||||
|
box_height = max(0, box_height) # remove any negative values
|
||||||
|
|
||||||
|
if self._label_direction == "UPR":
|
||||||
|
movx = left + x_box_offset
|
||||||
|
movy = -box_height - x_box_offset
|
||||||
|
elif self._label_direction == "DWR":
|
||||||
|
movx = left + x_box_offset
|
||||||
|
movy = x_box_offset
|
||||||
|
elif self._label_direction == "TTB":
|
||||||
|
movx = left + x_box_offset
|
||||||
|
movy = x_box_offset
|
||||||
|
else:
|
||||||
|
movx = left + x_box_offset
|
||||||
|
movy = y_box_offset
|
||||||
|
|
||||||
|
background_bitmap = Bitmap(box_width, box_height, 1)
|
||||||
|
tile_grid = TileGrid(
|
||||||
|
background_bitmap,
|
||||||
|
pixel_shader=self._background_palette,
|
||||||
|
x=movx,
|
||||||
|
y=movy,
|
||||||
|
)
|
||||||
|
|
||||||
|
return tile_grid
|
||||||
|
|
||||||
|
def _set_background_color(self, new_color: Optional[int]) -> None:
|
||||||
|
"""Private class function that allows updating the font box background color
|
||||||
|
|
||||||
|
:param int new_color: Color as an RGB hex number, setting to None makes it transparent
|
||||||
|
"""
|
||||||
|
|
||||||
|
if new_color is None:
|
||||||
|
self._background_palette.make_transparent(0)
|
||||||
|
if self._added_background_tilegrid:
|
||||||
|
self._local_group.pop(0)
|
||||||
|
self._added_background_tilegrid = False
|
||||||
|
else:
|
||||||
|
self._background_palette.make_opaque(0)
|
||||||
|
self._background_palette[0] = new_color
|
||||||
|
self._background_color = new_color
|
||||||
|
|
||||||
|
lines = self._text.rstrip("\n").count("\n") + 1
|
||||||
|
y_offset = self._ascent // 2
|
||||||
|
|
||||||
|
if self._bounding_box is None:
|
||||||
|
# Still in initialization
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self._added_background_tilegrid: # no bitmap is in the self Group
|
||||||
|
# add bitmap if text is present and bitmap sizes > 0 pixels
|
||||||
|
if (
|
||||||
|
(len(self._text) > 0)
|
||||||
|
and (
|
||||||
|
self._bounding_box[2] + self._padding_left + self._padding_right > 0
|
||||||
|
)
|
||||||
|
and (
|
||||||
|
self._bounding_box[3] + self._padding_top + self._padding_bottom > 0
|
||||||
|
)
|
||||||
|
):
|
||||||
|
self._local_group.insert(
|
||||||
|
0, self._create_background_box(lines, y_offset)
|
||||||
|
)
|
||||||
|
self._added_background_tilegrid = True
|
||||||
|
|
||||||
|
else: # a bitmap is present in the self Group
|
||||||
|
# update bitmap if text is present and bitmap sizes > 0 pixels
|
||||||
|
if (
|
||||||
|
(len(self._text) > 0)
|
||||||
|
and (
|
||||||
|
self._bounding_box[2] + self._padding_left + self._padding_right > 0
|
||||||
|
)
|
||||||
|
and (
|
||||||
|
self._bounding_box[3] + self._padding_top + self._padding_bottom > 0
|
||||||
|
)
|
||||||
|
):
|
||||||
|
self._local_group[0] = self._create_background_box(
|
||||||
|
lines, self._y_offset
|
||||||
|
)
|
||||||
|
else: # delete the existing bitmap
|
||||||
|
self._local_group.pop(0)
|
||||||
|
self._added_background_tilegrid = False
|
||||||
|
|
||||||
|
def _update_text(self, new_text: str) -> None:
|
||||||
|
# pylint: disable=too-many-branches,too-many-statements
|
||||||
|
|
||||||
|
x = 0
|
||||||
|
y = 0
|
||||||
|
if self._added_background_tilegrid:
|
||||||
|
i = 1
|
||||||
|
else:
|
||||||
|
i = 0
|
||||||
|
tilegrid_count = i
|
||||||
|
if self._base_alignment:
|
||||||
|
self._y_offset = 0
|
||||||
|
else:
|
||||||
|
self._y_offset = self._ascent // 2
|
||||||
|
|
||||||
|
if self._label_direction == "RTL":
|
||||||
|
left = top = bottom = 0
|
||||||
|
right = None
|
||||||
|
elif self._label_direction == "LTR":
|
||||||
|
right = top = bottom = 0
|
||||||
|
left = None
|
||||||
|
else:
|
||||||
|
top = right = left = 0
|
||||||
|
bottom = 0
|
||||||
|
|
||||||
|
for character in new_text:
|
||||||
|
if character == "\n":
|
||||||
|
y += int(self._height * self._line_spacing)
|
||||||
|
x = 0
|
||||||
|
continue
|
||||||
|
glyph = self._font.get_glyph(ord(character))
|
||||||
|
if not glyph:
|
||||||
|
continue
|
||||||
|
|
||||||
|
position_x, position_y = 0, 0
|
||||||
|
|
||||||
|
if self._label_direction in ("LTR", "RTL"):
|
||||||
|
bottom = max(bottom, y - glyph.dy + self._y_offset)
|
||||||
|
if y == 0: # first line, find the Ascender height
|
||||||
|
top = min(top, -glyph.height - glyph.dy + self._y_offset)
|
||||||
|
position_y = y - glyph.height - glyph.dy + self._y_offset
|
||||||
|
|
||||||
|
if self._label_direction == "LTR":
|
||||||
|
right = max(right, x + glyph.shift_x, x + glyph.width + glyph.dx)
|
||||||
|
if x == 0:
|
||||||
|
if left is None:
|
||||||
|
left = glyph.dx
|
||||||
|
else:
|
||||||
|
left = min(left, glyph.dx)
|
||||||
|
position_x = x + glyph.dx
|
||||||
|
else:
|
||||||
|
left = max(
|
||||||
|
left, abs(x) + glyph.shift_x, abs(x) + glyph.width + glyph.dx
|
||||||
|
)
|
||||||
|
if x == 0:
|
||||||
|
if right is None:
|
||||||
|
right = glyph.dx
|
||||||
|
else:
|
||||||
|
right = max(right, glyph.dx)
|
||||||
|
position_x = x - glyph.width
|
||||||
|
|
||||||
|
elif self._label_direction == "TTB":
|
||||||
|
if x == 0:
|
||||||
|
if left is None:
|
||||||
|
left = glyph.dx
|
||||||
|
else:
|
||||||
|
left = min(left, glyph.dx)
|
||||||
|
if y == 0:
|
||||||
|
top = min(top, -glyph.dy)
|
||||||
|
|
||||||
|
bottom = max(bottom, y + glyph.height, y + glyph.height + glyph.dy)
|
||||||
|
right = max(
|
||||||
|
right, x + glyph.width + glyph.dx, x + glyph.shift_x + glyph.dx
|
||||||
|
)
|
||||||
|
position_y = y + glyph.dy
|
||||||
|
position_x = x - glyph.width // 2 + self._y_offset
|
||||||
|
|
||||||
|
elif self._label_direction == "UPR":
|
||||||
|
if x == 0:
|
||||||
|
if bottom is None:
|
||||||
|
bottom = -glyph.dx
|
||||||
|
|
||||||
|
if y == 0: # first line, find the Ascender height
|
||||||
|
bottom = min(bottom, -glyph.dy)
|
||||||
|
left = min(left, x - glyph.height + self._y_offset)
|
||||||
|
top = min(top, y - glyph.width - glyph.dx, y - glyph.shift_x)
|
||||||
|
right = max(right, x + glyph.height, x + glyph.height - glyph.dy)
|
||||||
|
position_y = y - glyph.width - glyph.dx
|
||||||
|
position_x = x - glyph.height - glyph.dy + self._y_offset
|
||||||
|
|
||||||
|
elif self._label_direction == "DWR":
|
||||||
|
if y == 0:
|
||||||
|
if top is None:
|
||||||
|
top = -glyph.dx
|
||||||
|
top = min(top, -glyph.dx)
|
||||||
|
if x == 0:
|
||||||
|
left = min(left, -glyph.dy)
|
||||||
|
left = min(left, x, x - glyph.dy - self._y_offset)
|
||||||
|
bottom = max(bottom, y + glyph.width + glyph.dx, y + glyph.shift_x)
|
||||||
|
right = max(right, x + glyph.height)
|
||||||
|
position_y = y + glyph.dx
|
||||||
|
position_x = x + glyph.dy - self._y_offset
|
||||||
|
|
||||||
|
if glyph.width > 0 and glyph.height > 0:
|
||||||
|
face = TileGrid(
|
||||||
|
glyph.bitmap,
|
||||||
|
pixel_shader=self._palette,
|
||||||
|
default_tile=glyph.tile_index,
|
||||||
|
tile_width=glyph.width,
|
||||||
|
tile_height=glyph.height,
|
||||||
|
x=position_x,
|
||||||
|
y=position_y,
|
||||||
|
)
|
||||||
|
|
||||||
|
if self._label_direction == "UPR":
|
||||||
|
face.transpose_xy = True
|
||||||
|
face.flip_x = True
|
||||||
|
if self._label_direction == "DWR":
|
||||||
|
face.transpose_xy = True
|
||||||
|
face.flip_y = True
|
||||||
|
|
||||||
|
if tilegrid_count < len(self._local_group):
|
||||||
|
self._local_group[tilegrid_count] = face
|
||||||
|
else:
|
||||||
|
self._local_group.append(face)
|
||||||
|
tilegrid_count += 1
|
||||||
|
|
||||||
|
if self._label_direction == "RTL":
|
||||||
|
x = x - glyph.shift_x
|
||||||
|
if self._label_direction == "TTB":
|
||||||
|
if glyph.height < 2:
|
||||||
|
y = y + glyph.shift_x
|
||||||
|
else:
|
||||||
|
y = y + glyph.height + 1
|
||||||
|
if self._label_direction == "UPR":
|
||||||
|
y = y - glyph.shift_x
|
||||||
|
if self._label_direction == "DWR":
|
||||||
|
y = y + glyph.shift_x
|
||||||
|
if self._label_direction == "LTR":
|
||||||
|
x = x + glyph.shift_x
|
||||||
|
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
if self._label_direction == "LTR" and left is None:
|
||||||
|
left = 0
|
||||||
|
if self._label_direction == "RTL" and right is None:
|
||||||
|
right = 0
|
||||||
|
if self._label_direction == "TTB" and top is None:
|
||||||
|
top = 0
|
||||||
|
|
||||||
|
while len(self._local_group) > tilegrid_count: # i:
|
||||||
|
self._local_group.pop()
|
||||||
|
|
||||||
|
if self._label_direction == "RTL":
|
||||||
|
# pylint: disable=invalid-unary-operand-type
|
||||||
|
# type-checkers think left can be None
|
||||||
|
self._bounding_box = (-left, top, left - right, bottom - top)
|
||||||
|
if self._label_direction == "TTB":
|
||||||
|
self._bounding_box = (left, top, right - left, bottom - top)
|
||||||
|
if self._label_direction == "UPR":
|
||||||
|
self._bounding_box = (left, top, right, bottom - top)
|
||||||
|
if self._label_direction == "DWR":
|
||||||
|
self._bounding_box = (left, top, right, bottom - top)
|
||||||
|
if self._label_direction == "LTR":
|
||||||
|
self._bounding_box = (left, top, right - left, bottom - top)
|
||||||
|
|
||||||
|
self._text = new_text
|
||||||
|
|
||||||
|
if self._background_color is not None:
|
||||||
|
self._set_background_color(self._background_color)
|
||||||
|
|
||||||
|
def _reset_text(self, new_text: str) -> None:
|
||||||
|
current_anchored_position = self.anchored_position
|
||||||
|
self._update_text(str(self._replace_tabs(new_text)))
|
||||||
|
self.anchored_position = current_anchored_position
|
||||||
|
|
||||||
|
def _set_font(self, new_font: Union[BuiltinFont, BDF, PCF]) -> None:
|
||||||
|
old_text = self._text
|
||||||
|
current_anchored_position = self.anchored_position
|
||||||
|
self._text = ""
|
||||||
|
self._font = new_font
|
||||||
|
self._height = self._font.get_bounding_box()[1]
|
||||||
|
self._update_text(str(old_text))
|
||||||
|
self.anchored_position = current_anchored_position
|
||||||
|
|
||||||
|
def _set_line_spacing(self, new_line_spacing: float) -> None:
|
||||||
|
self._line_spacing = new_line_spacing
|
||||||
|
self.text = self._text # redraw the box
|
||||||
|
|
||||||
|
def _set_text(self, new_text: str, scale: int) -> None:
|
||||||
|
self._reset_text(new_text)
|
||||||
|
|
||||||
|
def _set_label_direction(self, new_label_direction: str) -> None:
|
||||||
|
self._label_direction = new_label_direction
|
||||||
|
self._update_text(str(self._text))
|
||||||
|
|
||||||
|
def _get_valid_label_directions(self) -> Tuple[str, ...]:
|
||||||
|
return "LTR", "RTL", "UPR", "DWR", "TTB"
|
||||||
208
QtPy-RP2040-Tem-Hum/lib/adafruit_displayio_sh1107.py
Executable file
208
QtPy-RP2040-Tem-Hum/lib/adafruit_displayio_sh1107.py
Executable file
@ -0,0 +1,208 @@
|
|||||||
|
# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
|
||||||
|
# SPDX-FileCopyrightText: Copyright (c) 2020 Mark Roberts for Adafruit Industries
|
||||||
|
# SPDX-FileCopyrightText: 2021 James Carr
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
"""
|
||||||
|
`adafruit_displayio_sh1107`
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
DisplayIO driver for SH1107 monochrome displays
|
||||||
|
|
||||||
|
|
||||||
|
* Author(s): Scott Shawcroft, Mark Roberts (mdroberts1243), James Carr
|
||||||
|
|
||||||
|
Implementation Notes
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
**Hardware:**
|
||||||
|
|
||||||
|
* `Adafruit FeatherWing 128 x 64 OLED - SH1107 128x64 OLED <https://www.adafruit.com/product/4650>`_
|
||||||
|
|
||||||
|
**Software and Dependencies:**
|
||||||
|
|
||||||
|
* Adafruit CircuitPython (version 6+) firmware for the supported boards:
|
||||||
|
https://github.com/adafruit/circuitpython/releases
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import displayio
|
||||||
|
from micropython import const
|
||||||
|
|
||||||
|
__version__ = "1.5.0"
|
||||||
|
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_DisplayIO_SH1107.git"
|
||||||
|
|
||||||
|
|
||||||
|
DISPLAY_OFFSET_ADAFRUIT_FEATHERWING_OLED_4650 = const(0x60)
|
||||||
|
"""
|
||||||
|
The hardware display offset to use when configuring the SH1107 for the
|
||||||
|
`Adafruit Featherwing 128x64 OLED <https://www.adafruit.com/product/4650>`_.
|
||||||
|
This is the default if not passed in.
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
from adafruit_displayio_sh1107 import SH1107, DISPLAY_OFFSET_ADAFRUIT_FEATHERWING_OLED_4650
|
||||||
|
|
||||||
|
# Constructor for the Adafruit FeatherWing 128x64 OLED
|
||||||
|
display = SH1107(bus, width=128, height=64,
|
||||||
|
display_offset=DISPLAY_OFFSET_ADAFRUIT_FEATHERWING_OLED_4650)
|
||||||
|
# Or as it's the default
|
||||||
|
display = SH1107(bus, width=128, height=64)
|
||||||
|
"""
|
||||||
|
|
||||||
|
DISPLAY_OFFSET_ADAFRUIT_128x128_OLED_5297 = const(0x00)
|
||||||
|
"""
|
||||||
|
The hardware display offset to use when configuring the SH1107 for the
|
||||||
|
`Adafruit Monochrome 1.12" 128x128 OLED <https://www.adafruit.com/product/5297>`_.
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
from adafruit_displayio_sh1107 import SH1107, DISPLAY_OFFSET_ADAFRUIT_128x128_OLED_5297
|
||||||
|
|
||||||
|
# Constructor for the Adafruit Monochrome 1.12" 128x128 OLED
|
||||||
|
display = SH1107(bus, width=128, height=128,
|
||||||
|
display_offset=DISPLAY_OFFSET_ADAFRUIT_128x128_OLED_5297, rotation=90)
|
||||||
|
"""
|
||||||
|
|
||||||
|
DISPLAY_OFFSET_PIMORONI_MONO_OLED_PIM374 = const(0x00)
|
||||||
|
"""
|
||||||
|
The hardware display offset to use when configuring the SH1107 for the
|
||||||
|
`Pimoroni Mono 128x128 OLED <https://shop.pimoroni.com/products/1-12-oled-breakout>`_
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
from adafruit_displayio_sh1107 import SH1107, DISPLAY_OFFSET_PIMORONI_MONO_OLED_PIM374
|
||||||
|
|
||||||
|
# Constructor for the Pimoroni Mono 128x128 OLED
|
||||||
|
display = SH1107(bus, width=128, height=128,
|
||||||
|
display_offset=DISPLAY_OFFSET_PIMORONI_MONO_OLED_PIM374)
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# Sequence from sh1107 framebuf driver formatted for displayio init
|
||||||
|
# we fixed sh110x addressing in 7, so we have slightly different setups
|
||||||
|
if sys.implementation.name == "circuitpython" and sys.implementation.version[0] < 7:
|
||||||
|
# if sys.implementation.version[0] < 7:
|
||||||
|
_INIT_SEQUENCE = (
|
||||||
|
b"\xae\x00" # display off, sleep mode
|
||||||
|
b"\xdc\x01\x00" # display start line = 0 (POR = 0)
|
||||||
|
b"\x81\x01\x2f" # contrast setting = 0x2f
|
||||||
|
b"\x21\x00" # vertical (column) addressing mode (POR=0x20)
|
||||||
|
b"\xa0\x00" # segment remap = 1 (POR=0, down rotation)
|
||||||
|
b"\xcf\x00" # common output scan direction = 15 (n-1 to 0) (POR=0)
|
||||||
|
b"\xa8\x01\x7f" # multiplex ratio = 128 (POR)
|
||||||
|
b"\xd3\x01\x60" # set display offset mode = 0x60
|
||||||
|
b"\xd5\x01\x51" # divide ratio/oscillator: divide by 2, fOsc (POR)
|
||||||
|
b"\xd9\x01\x22" # pre-charge/dis-charge period mode: 2 DCLKs/2 DCLKs (POR)
|
||||||
|
b"\xdb\x01\x35" # VCOM deselect level = 0.770 (POR)
|
||||||
|
b"\xb0\x00" # set page address = 0 (POR)
|
||||||
|
b"\xa4\x00" # entire display off, retain RAM, normal status (POR)
|
||||||
|
b"\xa6\x00" # normal (not reversed) display
|
||||||
|
b"\xaf\x00" # DISPLAY_ON
|
||||||
|
)
|
||||||
|
_PIXELS_IN_ROW = True
|
||||||
|
_ROTATION_OFFSET = 0
|
||||||
|
else:
|
||||||
|
_INIT_SEQUENCE = (
|
||||||
|
b"\xae\x00" # display off, sleep mode
|
||||||
|
b"\xdc\x01\x00" # set display start line 0
|
||||||
|
b"\x81\x01\x4f" # contrast setting = 0x4f
|
||||||
|
b"\x20\x00" # vertical (column) addressing mode (POR=0x20)
|
||||||
|
b"\xa0\x00" # segment remap = 1 (POR=0, down rotation)
|
||||||
|
b"\xc0\x00" # common output scan direction = 0 (0 to n-1 (POR=0))
|
||||||
|
b"\xa8\x01\x7f" # multiplex ratio = 128 (POR=0x7F)
|
||||||
|
b"\xd3\x01\x60" # set display offset mode = 0x60
|
||||||
|
# b"\xd5\x01\x51" # divide ratio/oscillator: divide by 2, fOsc (POR)
|
||||||
|
b"\xd9\x01\x22" # pre-charge/dis-charge period mode: 2 DCLKs/2 DCLKs (POR)
|
||||||
|
b"\xdb\x01\x35" # VCOM deselect level = 0.770 (POR)
|
||||||
|
# b"\xb0\x00" # set page address = 0 (POR)
|
||||||
|
b"\xa4\x00" # entire display off, retain RAM, normal status (POR)
|
||||||
|
b"\xa6\x00" # normal (not reversed) display
|
||||||
|
b"\xaf\x00" # DISPLAY_ON
|
||||||
|
)
|
||||||
|
_PIXELS_IN_ROW = False
|
||||||
|
_ROTATION_OFFSET = 90
|
||||||
|
|
||||||
|
|
||||||
|
class SH1107(displayio.Display):
|
||||||
|
"""
|
||||||
|
SH1107 driver for use with DisplayIO
|
||||||
|
|
||||||
|
:param bus: The bus that the display is connected to.
|
||||||
|
:param int width: The width of the display. Maximum of 128
|
||||||
|
:param int height: The height of the display. Maximum of 128
|
||||||
|
:param int rotation: The rotation of the display. 0, 90, 180 or 270.
|
||||||
|
:param int display_offset: The display offset that the first column is wired to.
|
||||||
|
This will be dependent on the OLED display and two displays with the
|
||||||
|
same dimensions could have different offsets. This defaults to
|
||||||
|
`DISPLAY_OFFSET_ADAFRUIT_FEATHERWING_OLED_4650`
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
bus,
|
||||||
|
display_offset=DISPLAY_OFFSET_ADAFRUIT_FEATHERWING_OLED_4650,
|
||||||
|
rotation=0,
|
||||||
|
**kwargs
|
||||||
|
):
|
||||||
|
rotation = (rotation + _ROTATION_OFFSET) % 360
|
||||||
|
if rotation in (0, 180):
|
||||||
|
multiplex = kwargs["width"] - 1
|
||||||
|
else:
|
||||||
|
multiplex = kwargs["height"] - 1
|
||||||
|
init_sequence = bytearray(_INIT_SEQUENCE)
|
||||||
|
init_sequence[16] = multiplex
|
||||||
|
init_sequence[19] = display_offset
|
||||||
|
super().__init__(
|
||||||
|
bus,
|
||||||
|
init_sequence,
|
||||||
|
**kwargs,
|
||||||
|
color_depth=1,
|
||||||
|
grayscale=True,
|
||||||
|
pixels_in_byte_share_row=_PIXELS_IN_ROW, # in vertical (column) mode
|
||||||
|
data_as_commands=True, # every byte will have a command byte preceding
|
||||||
|
brightness_command=0x81,
|
||||||
|
single_byte_bounds=True,
|
||||||
|
rotation=rotation,
|
||||||
|
# for sh1107 use column and page addressing.
|
||||||
|
# lower column command = 0x00 - 0x0F
|
||||||
|
# upper column command = 0x10 - 0x17
|
||||||
|
# set page address = 0xB0 - 0xBF (16 pages)
|
||||||
|
SH1107_addressing=True,
|
||||||
|
)
|
||||||
|
self._is_awake = True # Display starts in active state (_INIT_SEQUENCE)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_awake(self):
|
||||||
|
"""
|
||||||
|
The power state of the display. (read-only)
|
||||||
|
|
||||||
|
`True` if the display is active, `False` if in sleep mode.
|
||||||
|
|
||||||
|
:type: bool
|
||||||
|
"""
|
||||||
|
return self._is_awake
|
||||||
|
|
||||||
|
def sleep(self):
|
||||||
|
"""
|
||||||
|
Put display into sleep mode. The display uses < 5uA in sleep mode
|
||||||
|
|
||||||
|
Sleep mode does the following:
|
||||||
|
|
||||||
|
1) Stops the oscillator and DC-DC circuits
|
||||||
|
2) Stops the OLED drive
|
||||||
|
3) Remembers display data and operation mode active prior to sleeping
|
||||||
|
4) The MP can access (update) the built-in display RAM
|
||||||
|
"""
|
||||||
|
if self._is_awake:
|
||||||
|
self.bus.send(int(0xAE), "") # 0xAE = display off, sleep mode
|
||||||
|
self._is_awake = False
|
||||||
|
|
||||||
|
def wake(self):
|
||||||
|
"""
|
||||||
|
Wake display from sleep mode
|
||||||
|
"""
|
||||||
|
if not self._is_awake:
|
||||||
|
self.bus.send(int(0xAF), "") # 0xAF = display on
|
||||||
|
self._is_awake = True
|
||||||
239
QtPy-RP2040-Tem-Hum/lib/adafruit_sht4x.py
Executable file
239
QtPy-RP2040-Tem-Hum/lib/adafruit_sht4x.py
Executable file
@ -0,0 +1,239 @@
|
|||||||
|
# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
|
||||||
|
# SPDX-FileCopyrightText: Copyright (c) 2021 ladyada for Adafruit
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
"""
|
||||||
|
`adafruit_sht4x`
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Python library for Sensirion SHT4x temperature and humidity sensors
|
||||||
|
|
||||||
|
* Author(s): ladyada
|
||||||
|
|
||||||
|
Implementation Notes
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
**Hardware:**
|
||||||
|
|
||||||
|
* `Adafruit SHT40 Temperature & Humidity Sensor
|
||||||
|
<https://www.adafruit.com/product/4885>`_ (Product ID: 4885)
|
||||||
|
|
||||||
|
**Software and Dependencies:**
|
||||||
|
|
||||||
|
* Adafruit CircuitPython firmware for the supported boards:
|
||||||
|
https://circuitpython.org/downloads
|
||||||
|
|
||||||
|
* Adafruit's Bus Device library:
|
||||||
|
https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import time
|
||||||
|
import struct
|
||||||
|
from adafruit_bus_device import i2c_device
|
||||||
|
from micropython import const
|
||||||
|
|
||||||
|
__version__ = "1.0.7"
|
||||||
|
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_SHT4x.git"
|
||||||
|
|
||||||
|
|
||||||
|
_SHT4X_DEFAULT_ADDR = const(0x44) # SHT4X I2C Address
|
||||||
|
_SHT4X_READSERIAL = const(0x89) # Read Out of Serial Register
|
||||||
|
_SHT4X_SOFTRESET = const(0x94) # Soft Reset
|
||||||
|
|
||||||
|
|
||||||
|
class CV:
|
||||||
|
"""struct helper"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def add_values(cls, value_tuples):
|
||||||
|
"""Add CV values to the class"""
|
||||||
|
cls.string = {}
|
||||||
|
cls.delay = {}
|
||||||
|
|
||||||
|
for value_tuple in value_tuples:
|
||||||
|
name, value, string, delay = value_tuple
|
||||||
|
setattr(cls, name, value)
|
||||||
|
cls.string[value] = string
|
||||||
|
cls.delay[value] = delay
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def is_valid(cls, value):
|
||||||
|
"""Validate that a given value is a member"""
|
||||||
|
return value in cls.string
|
||||||
|
|
||||||
|
|
||||||
|
class Mode(CV):
|
||||||
|
"""Options for ``power_mode``"""
|
||||||
|
|
||||||
|
pass # pylint: disable=unnecessary-pass
|
||||||
|
|
||||||
|
|
||||||
|
Mode.add_values(
|
||||||
|
(
|
||||||
|
("NOHEAT_HIGHPRECISION", 0xFD, "No heater, high precision", 0.01),
|
||||||
|
("NOHEAT_MEDPRECISION", 0xF6, "No heater, med precision", 0.005),
|
||||||
|
("NOHEAT_LOWPRECISION", 0xE0, "No heater, low precision", 0.002),
|
||||||
|
("HIGHHEAT_1S", 0x39, "High heat, 1 second", 1.1),
|
||||||
|
("HIGHHEAT_100MS", 0x32, "High heat, 0.1 second", 0.11),
|
||||||
|
("MEDHEAT_1S", 0x2F, "Med heat, 1 second", 1.1),
|
||||||
|
("MEDHEAT_100MS", 0x24, "Med heat, 0.1 second", 0.11),
|
||||||
|
("LOWHEAT_1S", 0x1E, "Low heat, 1 second", 1.1),
|
||||||
|
("LOWHEAT_100MS", 0x15, "Low heat, 0.1 second", 0.11),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class SHT4x:
|
||||||
|
"""
|
||||||
|
A driver for the SHT4x temperature and humidity sensor.
|
||||||
|
|
||||||
|
:param ~busio.I2C i2c_bus: The I2C bus the SHT4x is connected to.
|
||||||
|
:param int address: The I2C device address. Default is :const:`0x44`
|
||||||
|
|
||||||
|
|
||||||
|
**Quickstart: Importing and using the SHT4x temperature and humidity sensor**
|
||||||
|
|
||||||
|
Here is an example of using the :class:`SHT4x`.
|
||||||
|
First you will need to import the libraries to use the sensor
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
import board
|
||||||
|
import adafruit_sht4x
|
||||||
|
|
||||||
|
Once this is done you can define your `board.I2C` object and define your sensor object
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
i2c = board.I2C() # uses board.SCL and board.SDA
|
||||||
|
sht = adafruit_sht4x.SHT4x(i2c)
|
||||||
|
|
||||||
|
You can now make some initial settings on the sensor
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
sht.mode = adafruit_sht4x.Mode.NOHEAT_HIGHPRECISION
|
||||||
|
|
||||||
|
Now you have access to the temperature and humidity using the :attr:`measurements`.
|
||||||
|
It will return a tuple with the :attr:`temperature` and :attr:`relative_humidity`
|
||||||
|
measurements
|
||||||
|
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
temperature, relative_humidity = sht.measurements
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, i2c_bus, address=_SHT4X_DEFAULT_ADDR):
|
||||||
|
self.i2c_device = i2c_device.I2CDevice(i2c_bus, address)
|
||||||
|
self._buffer = bytearray(6)
|
||||||
|
self.reset()
|
||||||
|
self._mode = Mode.NOHEAT_HIGHPRECISION # pylint: disable=no-member
|
||||||
|
|
||||||
|
@property
|
||||||
|
def serial_number(self):
|
||||||
|
"""The unique 32-bit serial number"""
|
||||||
|
self._buffer[0] = _SHT4X_READSERIAL
|
||||||
|
with self.i2c_device as i2c:
|
||||||
|
i2c.write(self._buffer, end=1)
|
||||||
|
time.sleep(0.01)
|
||||||
|
i2c.readinto(self._buffer)
|
||||||
|
|
||||||
|
ser1 = self._buffer[0:2]
|
||||||
|
ser1_crc = self._buffer[2]
|
||||||
|
ser2 = self._buffer[3:5]
|
||||||
|
ser2_crc = self._buffer[5]
|
||||||
|
|
||||||
|
# check CRC of bytes
|
||||||
|
if ser1_crc != self._crc8(ser1) or ser2_crc != self._crc8(ser2):
|
||||||
|
raise RuntimeError("Invalid CRC calculated")
|
||||||
|
|
||||||
|
serial = (ser1[0] << 24) + (ser1[1] << 16) + (ser2[0] << 8) + ser2[1]
|
||||||
|
return serial
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
"""Perform a soft reset of the sensor, resetting all settings to their power-on defaults"""
|
||||||
|
self._buffer[0] = _SHT4X_SOFTRESET
|
||||||
|
with self.i2c_device as i2c:
|
||||||
|
i2c.write(self._buffer, end=1)
|
||||||
|
time.sleep(0.001)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mode(self):
|
||||||
|
"""The current sensor reading mode (heater and precision)"""
|
||||||
|
return self._mode
|
||||||
|
|
||||||
|
@mode.setter
|
||||||
|
def mode(self, new_mode):
|
||||||
|
|
||||||
|
if not Mode.is_valid(new_mode):
|
||||||
|
raise AttributeError("mode must be a Mode")
|
||||||
|
self._mode = new_mode
|
||||||
|
|
||||||
|
@property
|
||||||
|
def relative_humidity(self):
|
||||||
|
"""The current relative humidity in % rH. This is a value from 0-100%."""
|
||||||
|
return self.measurements[1]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def temperature(self):
|
||||||
|
"""The current temperature in degrees Celsius"""
|
||||||
|
return self.measurements[0]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def measurements(self):
|
||||||
|
"""both `temperature` and `relative_humidity`, read simultaneously"""
|
||||||
|
|
||||||
|
temperature = None
|
||||||
|
humidity = None
|
||||||
|
command = self._mode
|
||||||
|
|
||||||
|
with self.i2c_device as i2c:
|
||||||
|
self._buffer[0] = command
|
||||||
|
i2c.write(self._buffer, end=1)
|
||||||
|
time.sleep(Mode.delay[self._mode])
|
||||||
|
i2c.readinto(self._buffer)
|
||||||
|
|
||||||
|
# separate the read data
|
||||||
|
temp_data = self._buffer[0:2]
|
||||||
|
temp_crc = self._buffer[2]
|
||||||
|
humidity_data = self._buffer[3:5]
|
||||||
|
humidity_crc = self._buffer[5]
|
||||||
|
|
||||||
|
# check CRC of bytes
|
||||||
|
if temp_crc != self._crc8(temp_data) or humidity_crc != self._crc8(
|
||||||
|
humidity_data
|
||||||
|
):
|
||||||
|
raise RuntimeError("Invalid CRC calculated")
|
||||||
|
|
||||||
|
# decode data into human values:
|
||||||
|
# convert bytes into 16-bit signed integer
|
||||||
|
# convert the LSB value to a human value according to the datasheet
|
||||||
|
temperature = struct.unpack_from(">H", temp_data)[0]
|
||||||
|
temperature = -45.0 + 175.0 * temperature / 65535.0
|
||||||
|
|
||||||
|
# repeat above steps for humidity data
|
||||||
|
humidity = struct.unpack_from(">H", humidity_data)[0]
|
||||||
|
humidity = -6.0 + 125.0 * humidity / 65535.0
|
||||||
|
humidity = max(min(humidity, 100), 0)
|
||||||
|
|
||||||
|
return (temperature, humidity)
|
||||||
|
|
||||||
|
## CRC-8 formula from page 14 of SHTC3 datasheet
|
||||||
|
# https://media.digikey.com/pdf/Data%20Sheets/Sensirion%20PDFs/HT_DS_SHTC3_D1.pdf
|
||||||
|
# Test data [0xBE, 0xEF] should yield 0x92
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _crc8(buffer):
|
||||||
|
"""verify the crc8 checksum"""
|
||||||
|
crc = 0xFF
|
||||||
|
for byte in buffer:
|
||||||
|
crc ^= byte
|
||||||
|
for _ in range(8):
|
||||||
|
if crc & 0x80:
|
||||||
|
crc = (crc << 1) ^ 0x31
|
||||||
|
else:
|
||||||
|
crc = crc << 1
|
||||||
|
return crc & 0xFF # return the bottom 8 bits
|
||||||
2
QtPy-RP2040-Tem-Hum/readme.md
Normal file
2
QtPy-RP2040-Tem-Hum/readme.md
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# Temperature + Humidity
|
||||||
|
This is for the QtPy RP2040 from Adafruit with the SHT40 sensor attached as well as the SH1107 OLED display.
|
||||||
Reference in New Issue
Block a user