# SPDX-FileCopyrightText: Copyright (c) 2023 Jose D. Montoya
#
# SPDX-License-Identifier: MIT
"""
`display_ht16k33.segments`
================================================================================
On Display Simulation for an HT16K33 driver. Works with 7x4 Segments.
Based on some code from https://github.com/adafruit/Adafruit_CircuitPython_HT16K33.git
Authors: Radomir Dopieralski and Tony DiCola License: MIT
* Author(s): Jose D. Montoya
"""
import time
from vectorio import Polygon, Circle
import displayio
try:
from typing import Optional, Dict, Tuple, Union
except ImportError:
pass
__version__ = "0.0.0+auto.0"
__repo__ = "https://github.com/jposada202020/CircuitPython_DISPLAY_HT16K33.git"
# fmt: off
CHARS = (
0b00000000, 0b00000000, #
0b01000000, 0b00000110, # !
0b00000010, 0b00100000, # "
0b00010010, 0b11001110, # #
0b00010010, 0b11101101, # $
0b00001100, 0b00100100, # %
0b00100011, 0b01011101, # &
0b00000100, 0b00000000, # '
0b00100100, 0b00000000, # (
0b00001001, 0b00000000, # )
0b00111111, 0b11000000, # *
0b00010010, 0b11000000, # +
0b00001000, 0b00000000, # ,
0b00000000, 0b11000000, # -
0b00000000, 0b00000000, # .
0b00001100, 0b00000000, # /
0b00001100, 0b00111111, # 0
0b00000000, 0b00000110, # 1
0b00000000, 0b11011011, # 2
0b00000000, 0b10001111, # 3
0b00000000, 0b11100110, # 4
0b00100000, 0b01101001, # 5
0b00000000, 0b11111101, # 6
0b00000000, 0b00000111, # 7
0b00000000, 0b11111111, # 8
0b00000000, 0b11101111, # 9
0b00010010, 0b00000000, # :
0b00001010, 0b00000000, # ;
0b00100100, 0b01000000, # <
0b00000000, 0b11001000, # =
0b00001001, 0b10000000, # >
0b01100000, 0b10100011, # ?
0b00000010, 0b10111011, # @
0b00000000, 0b11110111, # A
0b00010010, 0b10001111, # B
0b00000000, 0b00111001, # C
0b00010010, 0b00001111, # D
0b00000000, 0b11111001, # E
0b00000000, 0b01110001, # F
0b00000000, 0b10111101, # G
0b00000000, 0b11110110, # H
0b00010010, 0b00000000, # I
0b00000000, 0b00011110, # J
0b00100100, 0b01110000, # K
0b00000000, 0b00111000, # L
0b00000101, 0b00110110, # M
0b00100001, 0b00110110, # N
0b00000000, 0b00111111, # O
0b00000000, 0b11110011, # P
0b00100000, 0b00111111, # Q
0b00100000, 0b11110011, # R
0b00000000, 0b11101101, # S
0b00010010, 0b00000001, # T
0b00000000, 0b00111110, # U
0b00001100, 0b00110000, # V
0b00101000, 0b00110110, # W
0b00101101, 0b00000000, # X
0b00010101, 0b00000000, # Y
0b00001100, 0b00001001, # Z
0b00000000, 0b00111001, # [
0b00100001, 0b00000000, # \
0b00000000, 0b00001111, # ]
0b00001100, 0b00000011, # ^
0b00000000, 0b00001000, # _
0b00000001, 0b00000000, # `
0b00010000, 0b01011000, # a
0b00100000, 0b01111000, # b
0b00000000, 0b11011000, # c
0b00001000, 0b10001110, # d
0b00001000, 0b01011000, # e
0b00000000, 0b01110001, # f
0b00000100, 0b10001110, # g
0b00010000, 0b01110000, # h
0b00010000, 0b00000000, # i
0b00000000, 0b00001110, # j
0b00110110, 0b00000000, # k
0b00000000, 0b00110000, # l
0b00010000, 0b11010100, # m
0b00010000, 0b01010000, # n
0b00000000, 0b11011100, # o
0b00000001, 0b01110000, # p
0b00000100, 0b10000110, # q
0b00000000, 0b01010000, # r
0b00100000, 0b10001000, # s
0b00000000, 0b01111000, # t
0b00000000, 0b00011100, # u
0b00100000, 0b00000100, # v
0b00101000, 0b00010100, # w
0b00101000, 0b11000000, # x
0b00100000, 0b00001100, # y
0b00001000, 0b01001000, # z
0b00001001, 0b01001001, # {
0b00010010, 0b00000000, # |
0b00100100, 0b10001001, # }
0b00000101, 0b00100000, # ~
0b00111111, 0b11111111,
)
# fmt: on
NUMBERS = (
0x3F, # 0
0x06, # 1
0x5B, # 2
0x4F, # 3
0x66, # 4
0x6D, # 5
0x7D, # 6
0x07, # 7
0x7F, # 8
0x6F, # 9
0x77, # a
0x7C, # b
0x39, # C
0x5E, # d
0x79, # E
0x71, # F
0x3D, # G
0x76, # H
0x30, # I
0x1E, # J
0x40, # -
0x38, # L
0x40, # -
0x54, # n
0x5C, # o
0x73, # P
0x67, # q
0x50, # R
0x6D, # S
0x78, # t
0x3E, # U
0x1C, # v
0x40, # -
0x40, # -
0x6E, # y
0x40, # -
0x40, # -
0x00, # Null
)
# pylint: disable=too-many-arguments, too-many-instance-attributes, too-few-public-methods, too-many-lines
[docs]class SEG7x4:
"""
Main class to display the 7x4 segments on the screen
:param int x: x coordinates in pixels for the segment to start. This is the top
left corner of the first digit
:param int y: y coordinates in pixels for the segment to start. This is the top
left corner of the first digit
:param int height: segment height in pixels. Defaults to :const:`40` pixels
:param int length: segment length in pixels. Defaults to :const:`40` pixels
:param int stroke: segment width in pixels. Defaults to :const:`4` pixels
:param int|tuple color_off: (*RGB tuple or 24-bit hex value*) segment fill color
when segment is on. Defaults to :const:`0x123456` Blue.
:param int|tuple color_on: (*RGB tuple or 24-bit hex value*) segment fill color
when segment is on. Defaults to :const:`0xFF5500` orange.
:param dict char_dict: An optional dictionary mapping strings to bit settings
integers used for defining how to display custom letters
"""
def __init__(
self,
x: int,
y: int,
height: int = 40,
length: int = 40,
space: int = 70,
stroke: int = 4,
color_off: Union[int, Tuple] = 0x123456,
color_on: Union[int, Tuple] = 0xFF5500,
char_dict: Optional[Dict[str, int]] = None,
) -> None:
self._x = x
self.y = y
self._digits = [None, None, None, None]
self._two_points_container = []
self._period_container = []
self._colon = False
self._points = False
self._chardict = char_dict
self.group = displayio.Group()
self._palette = displayio.Palette(3)
self._palette.make_transparent(0)
self._palette[1] = color_off
self._palette[2] = color_on
self._stroke = stroke
self._length = length
self._height = height
self._space = space
self._pointsh = [
(0, 0),
(self._stroke, self._stroke // 2),
(self._length - self._stroke, self._stroke // 2),
(self._length, 0),
(self._length - self._stroke, -self._stroke // 2),
(self._stroke, -self._stroke // 2),
]
self._pointsv = [
(0, 0),
(-self._stroke // 2, self._stroke),
(-self._stroke // 2, self._height - self._stroke),
(0, self._height),
(self._stroke // 2, self._height - self._stroke),
(self._stroke // 2, self._stroke),
]
self._draw_digits(self._x, 3)
self._draw_digits(self._x + self._space, 2)
self._draw_digits(self._x + self._space * 2, 1)
self._draw_digits(self._x + self._space * 3, 0)
self._draw_two_points()
def _draw_two_points(self) -> None:
"""
Internal function to draw the two points hour indicators
"""
value = Circle(
pixel_shader=self._palette,
radius=self._height // 8,
x=self._x + self._space + self._length + (self._space - self._length) // 2,
y=self.y + self._height // 2 - (self._height // 16),
color_index=1,
)
self.group.append(value)
self._two_points_container.append(value)
value = Circle(
pixel_shader=self._palette,
radius=self._height // 8,
x=self._x + self._space + self._length + (self._space - self._length) // 2,
y=self.y + self._height + self._height // 2 - (self._height // 16),
color_index=1,
)
self.group.append(value)
self._two_points_container.append(value)
def _draw_digits(self, x: int, pos: int) -> None:
"""
Internal function to draw the segments
:param int x: digits x distance in pixels
:param int pos: digit's position
"""
posx = x
segments = []
# Segment A
value = Polygon(
pixel_shader=self._palette,
points=self._pointsh,
x=posx,
y=self.y,
color_index=1,
)
segments.append(value)
self.group.append(value)
# Segment B
value = Polygon(
pixel_shader=self._palette,
points=self._pointsv,
x=posx + self._length - self._stroke // 2,
y=self.y,
color_index=1,
)
segments.append(value)
self.group.append(value)
# Segment C
value = Polygon(
pixel_shader=self._palette,
points=self._pointsv,
x=posx + self._length - self._stroke // 2,
y=self.y + self._height,
color_index=1,
)
segments.append(value)
self.group.append(value)
# Segment D
value = Polygon(
pixel_shader=self._palette,
points=self._pointsh,
x=posx,
y=self.y + self._length * 2,
color_index=1,
)
segments.append(value)
self.group.append(value)
# Segment E
value = Polygon(
pixel_shader=self._palette,
points=self._pointsv,
x=posx,
y=self.y + self._height,
color_index=1,
)
segments.append(value)
self.group.append(value)
# Segment F
value = Polygon(
pixel_shader=self._palette,
points=self._pointsv,
x=posx,
y=self.y,
color_index=1,
)
segments.append(value)
self.group.append(value)
# Segment G
value = Polygon(
pixel_shader=self._palette,
points=self._pointsh,
x=posx,
y=self.y + self._height,
color_index=1,
)
segments.append(value)
self.group.append(value)
self._digits[pos] = segments
value = Circle(
pixel_shader=self._palette,
radius=self._height // 8,
x=posx + self._length + (self._height // 4),
y=self.y + 2 * self._height - (self._height // 8),
color_index=1,
)
self.group.append(value)
self._period_container.append(value)
[docs] def print(self, value: Union[int, str]) -> None:
"""
Print the value given. Only work with strings
:param int|str value: String to be put in the 7x4 segment
"""
self.clear()
if isinstance(value, float):
self._number(value)
return
if isinstance(value, int):
value = str(value)
if ":" in value:
value = value.replace(":", "")
self._two_points(True)
if "." in value:
self._number(value)
return
value_string = str(value)
for i in range(len(value_string)):
self.print_digit(i, value_string[len(value_string) - 1 - i])
[docs] def print_digit(self, pos: int, char: str) -> None:
"""
Print a specific digit
:param int pos: position in the 7x4 segment
:param str char: character to be printed
"""
char = char.lower()
if char in "abcdefghijklmnopqrstuvwxy":
character = ord(char) - 97 + 10
elif char == "-":
character = 36
elif char in "0123456789":
character = ord(char) - 48
elif char == "*":
character = 37
elif char == " ":
character = 37
if self._chardict and char in self._chardict:
new_value = self._chardict[char]
else:
new_value = NUMBERS[character]
for i in range(7):
biff = new_value >> i & 1
if biff:
self._digits[pos][i].color_index = 2
else:
self._digits[pos][i].color_index = 1
[docs] def set_digit_raw(self, pos, value):
"""
Set digit raw
"""
new_value = value & 0x7F
for i in range(7):
biff = new_value >> i & 1
if biff:
self._digits[pos][i].color_index = 2
else:
self._digits[pos][i].color_index = 1
[docs] def clear(self) -> None:
"""
Clear the digits
"""
for i in range(4):
self.print_digit(i, "*")
self._two_points(False)
self.points = False
def __setitem__(self, key: int, value: str) -> None:
self.print_digit(key, value)
def _two_points(self, show: bool = True):
if show:
for i in range(2):
self._two_points_container[i].color_index = 2
else:
for i in range(2):
self._two_points_container[i].color_index = 1
[docs] def fill(self, value: int) -> None:
"""
Fill function. to be compatible with the Hardware version
of the library
"""
if value:
pass
self.clear()
@property
def colon(self):
"""
Colon property
"""
return self._colon
@colon.setter
def colon(self, value: bool):
self._two_points(value)
@property
def points(self):
"""
points property
"""
return self._points
@points.setter
def points(self, show: bool):
if show:
for i in range(4):
self._period_container[i].color_index = 2
else:
for i in range(4):
self._period_container[i].color_index = 1
[docs] def marquee(self, text: str, delay: float = 0.25, loop: bool = True) -> None:
"""
Automatically scroll the text at the specified delay between characters
:param str text: Text to display
:param float delay: Delay in seconds to pause before scrolling to the next
character. Defaults to 0.25 seconds
:param bool loop: Whether to endlessly loop the text. Defaults to `True`
"""
def cycle(text, delay):
for i in range(len(text)):
self.print(text[i : 4 + i])
time.sleep(delay)
text = text + " " * 4
if loop:
while True:
cycle(text, delay)
else:
cycle(text, delay)
def _number(self, number: Union[float, str]) -> None:
stnum = str(number)
if stnum.count(".") > 1:
raise RuntimeError("String with more than two periods are not implemented")
dot = stnum.find(".")
stnum = stnum.replace(".", "")
for i in range(len(stnum)):
self.print_digit(i, stnum[len(stnum) - 1 - i])
self._period_container[dot].color_index = 2
[docs]class SEG14x4:
"""
Main class to display the 14x4 segments on the screen
:param int x: x coordinates in pixels for the segment to start. This is the top left
corner of the first digit
:param int y: y coordinates in pixels for the segment to start. This is the top left
corner of the first digit
:param int height: segment height in pixels. Defaults to :const:`40` pixels
:param int length: segment length in pixels. Defaults to :const:`40` pixels
:param int stroke: segment width in pixels. Defaults to :const:`4` pixels
:param int|tuple color_off: (*RGB tuple or 24-bit hex value*) segment fill color when segment
is on. Defaults to :const:`0x123456` Blue.
:param int|tuple color_on: (*RGB tuple or 24-bit hex value*) segment fill color when segment
is on. Defaults to :const:`0xFF5500` orange.
:param dict char_dict: An optional dictionary mapping strings to bit settings integers used
for defining how to display custom letters
"""
def __init__(
self,
x: int,
y: int,
height: int = 40,
length: int = 40,
space: int = 70,
stroke: int = 4,
color_off: int = 0x123456,
color_on: int = 0xFF5500,
char_dict: Optional[Dict[str, int]] = None,
) -> None:
self._x = x
self.y = y
self._digits = [None, None, None, None]
self._digits_high = [None, None, None, None]
self._two_points_container = []
self._chardict = char_dict
self.buffer = [None, None, None, None]
self.value_string = None
self.group = displayio.Group()
self._palette = displayio.Palette(3)
self._palette.make_transparent(0)
self._palette[1] = color_off
self._palette[2] = color_on
self._stroke = stroke
self._length = length
self._height = height
self._space = space
self._index = 0
self._delay = 0.25
self._last_nb_scroll_time = -1
self.marquee_text = None
self.loop = True
self._pointsh = [
(0, 0),
(self._stroke, self._stroke // 2),
(self._length - self._stroke, self._stroke // 2),
(self._length, 0),
(self._length - self._stroke, -self._stroke // 2),
(self._stroke, -self._stroke // 2),
]
self._pointsh_half = [
(0, 0),
(self._stroke, self._stroke // 2),
(self._length // 2 - self._stroke, self._stroke // 2),
(self._length // 2, 0),
(self._length // 2 - self._stroke, -self._stroke // 2),
(self._stroke, -self._stroke // 2),
]
self._pointsv = [
(0, 0),
(-self._stroke // 2, self._stroke),
(-self._stroke // 2, self._height - self._stroke),
(0, self._height),
(self._stroke // 2, self._height - self._stroke),
(self._stroke // 2, self._stroke),
]
self._pointsv_half = [
(0, 0),
(-self._stroke // 2, self._stroke),
(-self._stroke // 2, self._height - self._stroke - self._stroke),
(0, self._height - self._stroke),
(self._stroke // 2, self._height - self._stroke - self._stroke),
(self._stroke // 2, self._stroke),
]
xfinal = self._length // 2 - 2 * self._stroke
yfinal = -self._height + 2 * stroke
self._pointsd_right_bot = [
(0, 0),
(0, -2 * self._stroke),
(
xfinal - self._stroke,
yfinal,
),
(xfinal, yfinal),
(
xfinal,
yfinal + self._stroke,
),
(self._stroke, 0),
]
self._pointsd_left_bot = [
(0, 0),
(0, -2 * self._stroke),
(
-xfinal + self._stroke,
yfinal,
),
(-xfinal + self._stroke // 2, yfinal),
(
-xfinal + self._stroke // 2,
yfinal + self._stroke,
),
(-self._stroke, 0),
]
self._pointsd_right_up = [
(0, 0),
(0, 2 * self._stroke),
(
self._length // 2 - 2 * self._stroke - self._stroke,
self._height - 2 * self._stroke,
),
(
self._length // 2 - 2 * self._stroke,
self._height - 2 * self._stroke - self._stroke,
),
(self._stroke, 0),
]
self._pointsd_left_up = [
(0, 0),
(0, 2 * self._stroke),
(
-self._length // 2 + 2 * self._stroke + self._stroke,
self._height - 2 * self._stroke,
),
(
-self._length // 2 + 2 * self._stroke,
self._height - 2 * self._stroke - self._stroke,
),
(-self._stroke, 0),
]
# h= height - 2 * stroke
self._draw_digits(self._x, 3)
self._draw_digits(self._x + self._space, 2)
self._draw_digits(self._x + self._space * 2, 1)
self._draw_digits(self._x + self._space * 3, 0)
self._draw_two_points()
def _draw_digits(self, x, pos):
"""
Internal function to draw the segments
:param int x: digits x distance in pixels
:param int pos: digit's position
"""
posx = x
segments = []
segments_high = []
# Segment A
value = Polygon(
pixel_shader=self._palette,
points=self._pointsh,
x=posx,
y=self.y,
color_index=1,
)
segments.append(value)
self.group.append(value)
# Segment B
value = Polygon(
pixel_shader=self._palette,
points=self._pointsv,
x=posx + self._length - self._stroke // 2,
y=self.y,
color_index=1,
)
segments.append(value)
self.group.append(value)
# Segment C
value = Polygon(
pixel_shader=self._palette,
points=self._pointsv,
x=posx + self._length - self._stroke // 2,
y=self.y + self._height,
color_index=1,
)
segments.append(value)
self.group.append(value)
# Segment D
value = Polygon(
pixel_shader=self._palette,
points=self._pointsh,
x=posx,
y=self.y + self._length * 2,
color_index=1,
)
segments.append(value)
self.group.append(value)
# Segment E
value = Polygon(
pixel_shader=self._palette,
points=self._pointsv,
x=posx,
y=self.y + self._height,
color_index=1,
)
segments.append(value)
self.group.append(value)
# Segment F
value = Polygon(
pixel_shader=self._palette,
points=self._pointsv,
x=posx,
y=self.y,
color_index=1,
)
segments.append(value)
self.group.append(value)
# Segment G1
value = Polygon(
pixel_shader=self._palette,
points=self._pointsh_half,
x=posx,
y=self.y + self._height,
color_index=1,
)
segments.append(value)
self.group.append(value)
# Segment G2
value = Polygon(
pixel_shader=self._palette,
points=self._pointsh_half,
x=posx + self._length // 2,
y=self.y + self._height,
color_index=1,
)
segments.append(value)
self.group.append(value)
# Segment H
value = Polygon(
pixel_shader=self._palette,
points=self._pointsd_right_up,
x=posx + self._stroke,
y=self.y + self._stroke,
color_index=1,
)
segments_high.append(value)
self.group.append(value)
# Segment J
value = Polygon(
pixel_shader=self._palette,
points=self._pointsv_half,
x=posx + self._length // 2,
y=self.y + self._stroke // 2,
color_index=1,
)
segments_high.append(value)
self.group.append(value)
# Segment K
value = Polygon(
pixel_shader=self._palette,
points=self._pointsd_left_up,
x=posx + self._length - self._stroke - self._stroke // 2,
y=self.y + self._stroke,
color_index=1,
)
segments_high.append(value)
self.group.append(value)
# Segment L
value = Polygon(
pixel_shader=self._palette,
points=self._pointsd_right_bot,
x=posx + self._stroke,
y=self.y + self._height * 2 - self._stroke,
color_index=1,
)
segments_high.append(value)
self.group.append(value)
# Segment M
value = Polygon(
pixel_shader=self._palette,
points=self._pointsv_half,
x=posx + self._length // 2,
y=self.y + self._height + self._stroke // 2,
color_index=1,
)
segments_high.append(value)
self.group.append(value)
# Segment N
value = Polygon(
pixel_shader=self._palette,
points=self._pointsd_left_bot,
x=posx + self._length - self._stroke - self._stroke // 2,
y=self.y + self._height * 2 - self._stroke,
color_index=1,
)
segments_high.append(value)
self.group.append(value)
self._digits[pos] = segments
self._digits_high[pos] = segments_high
value = Circle(
pixel_shader=self._palette,
radius=self._height // 8,
x=posx + self._length + (self._height // 4),
y=self.y + 2 * self._height - (self._height // 8),
color_index=1,
)
self.group.append(value)
def _two_points(self, show=True):
if show:
for i in range(2):
self._two_points_container[i].color_index = 2
else:
for i in range(2):
self._two_points_container[i].color_index = 1
def _draw_two_points(self):
"""
Internal function to draw the two points hour indicators
"""
value = Circle(
pixel_shader=self._palette,
radius=self._height // 8,
x=self._x + self._space + self._length + (self._space - self._length) // 2,
y=self.y + self._height // 2 - (self._height // 16),
color_index=1,
)
self.group.append(value)
self._two_points_container.append(value)
value = Circle(
pixel_shader=self._palette,
radius=self._height // 8,
x=self._x + self._space + self._length + (self._space - self._length) // 2,
y=self.y + self._height + self._height // 2 - (self._height // 16),
color_index=1,
)
self.group.append(value)
self._two_points_container.append(value)
[docs] def print_digit(self, pos, char):
"""
Print a specific digit
"""
character = ord(char) * 2 - 64
if char == "*":
character = 37
new_value_high = CHARS[character]
new_value_low = CHARS[character + 1]
for i in range(8):
biff_low = new_value_low >> i & 1
if biff_low:
self._digits[pos][i].color_index = 2
else:
self._digits[pos][i].color_index = 1
for i in range(6):
biff_high = new_value_high >> i & 1
if biff_high:
self._digits_high[pos][i].color_index = 2
else:
self._digits_high[pos][i].color_index = 1
[docs] def print(self, value):
"""
Print the value given. Only work with strings
:param str value: String to be put in the 7x4 segment
"""
self.clear()
if ":" in value:
value = value.replace(":", "")
self._two_points(True)
self.value_string = str(value)
for i in range(len(self.value_string)):
self.print_digit(i, self.value_string[len(self.value_string) - 1 - i])
[docs] def clear(self):
"""
Clear the digits
"""
for i in range(4):
self.print_digit(i, ".")
self._two_points(False)
def __setitem__(self, key: int, value: str) -> None:
self.print_digit(key, value)
[docs] def marquee(self, text: str, delay: float = 0.25, loop: bool = True) -> None:
"""
Automatically scroll the text at the specified delay between characters
:param str text: Text to display
:param float delay: Delay in seconds to pause before scrolling to the next
character. Defaults to 0.25 seconds
:param bool loop: Whether to endlessly loop the text. Defaults to `True`
"""
def cycle(text, delay):
for i in range(len(text)):
self.print(text[i : 4 + i])
time.sleep(delay)
text = text + " " * 4
if loop:
while True:
cycle(text, delay)
else:
cycle(text, delay)
[docs] def non_blocking_marquee(
self, text: str, delay: float = 0.25, loop: bool = True
) -> None:
"""
Non Blocking Marquee Definition function
:param str text: Text to display
:param float delay: Delay in seconds to pause before scrolling to the next
character. Defaults to 0.25 seconds
:param bool loop: Whether to endlessly loop the text. Defaults to `True`
"""
self.marquee_text = text + " " * 4
self.loop = loop
self._index = 0
self._delay = delay
[docs] def non_blocking_marquee_update(self):
"""
Non Blocking Marquee update function
"""
if self.loop:
now = time.monotonic()
if now >= self._last_nb_scroll_time + self._delay:
self._last_nb_scroll_time = now
self.print(self.marquee_text[self._index : 4 + self._index])
self._index = self._index + 1
if self._index > len(self.marquee_text) - 4:
self._index = 0