Skip to content

system: promote better shared components for LED #75

Description

@bombsimon

Somewhat related to #13 and web_components.py, I want to improve shared logic to write to the LED. This is one of the most cumbersome parts when you write an app but in theory you should only need to implement this once. We do have pprint function but it's a bit tricky to use and have a lot of quirks.

A lot of companion functions live in odd namespaces (e.g. pprint in load_screen) and are marked as private (prefixed with _).

I though a bit about this when I rewrote wlan.py and created show_setup_on_led.

matrixbox/lib/wlan.py

Lines 114 to 120 in d48958b

def show_setup_on_led():
# Show wifi name and device IP for setup instructions.
# One space per pixel so name and IP align.
pprint("1. Connect to WiFi", line=1)
pprint(f" {macid}", line=2, color="yellow")
pprint("2. Go to", line=3)
pprint(f" http://{wifi.radio.ipv4_address_ap!s}", line=4)

The idea here being that no one should ever have to figure out how to print this to the LED, it should just be a call from any view or any app at any time.

We recently added _wrap_text which is more or less a copy from the app "dictaphone" but I think we did the same thing here. It's in an odd namespace (cmd, makes no sense since it's not related to cmd and would be odd to re-use e.g. from wlan) and it's prefixed with _ indicating it's private and shouldn't be used outside of the namespace.

Since this is private and a bit tricky to use, it's now forced and monkey patched when a user uses the /system/cmd endpoint. I think it would be better to leave that to the end user to decide, and with a good enough library it would be super easy to wrap the text when desired.

I suggest we build a good library for the LED display in a clear namespace (e.g. led.py) which holds all the logic on how to draw to the screen so anyone can use them from any app. Most of the time it's text we want to render so we should start with that.

I want to build a system that looks something like this (pseudo code). I don't know if it will be too size consuming, but I'm opening this as a tracking ticket to get closer to this goal. Hopefully we can compensate by remove a lot of duplicated code. It would work a lot like pprint but be extendable with a state to also support animations. In the future we can also improve shapes etc like the "paint" app.

from enum import Enum


class Scroll(Enum):
    NONE = 1
    HORIZONTAL = 2
    VERTICAL = 3


class ScrollStop(Enum):
    LOOP = 1   # keeps scrolling in a loop
    RESET = 2  # stops at the end and snaps back to start


class Font(Enum):
    MINI = 1
    SMALL = 2
    LARGE = 3


class Color(Enum):
    WHITE = "white"
    BRIGHT_WHITE = "brightwhite"
    RED = "red"
    GREEN = "green"
    BLUE = "blue"
    LIGHT_BLUE = "light_blue"
    YELLOW = "yellow"
    ORANGE = "orange"
    PINK = "pink"
    GREY = "grey"
    BLACK = "black"


class TextAlign(Enum):
    LEFT = 1
    CENTER = 2
    RIGHT = 3


class Text:
    def __init__(self, text, color=Color.WHITE, shadow_color=None):
        self.text = text
        self.color = color
        self.shadow_color = shadow_color


class Printer:
    PHASE_START = "start"
    PHASE_SCROLL = "scroll"
    PHASE_END = "end"

    def __init__(self, window=None):
        self.width = get_width()
        self.height = get_height()
        self.window=window
        self.x = 0
        self.y = 0
        self._rows = []

    def add_row(
        self,
        *content,
        font_size=Font.MINI,
        align=TextAlign.LEFT,
        wrap=False,
        scroll=Scroll.NONE,
        scroll_stop=ScrollStop.LOOP,
        scroll_speed=1,
        top_offset=0,
        bottom_offset=0,
        start_x=None, # Override self.x state
        start_y=None, # Override self.y state
        clear_after=True,
    ):
        """
        Add rows and has a shared state on where they should apply in the
        given window (which might be full display).
        """

    def print(self):
        """
        Print rich text, keeps track of position and screen.
        This means that if scroll is enabled, calling `print` multiple
        times will simply update the position and simulate a scrolling
        behavior for the caller without ever tracking previous position.

        Will need to figure out `line` and those bits but want a system
        that works for all font sizes and types.
        """

def clear():
    """Clears the whole screen making it all black"""

def draw_rect(x0, y0, x1, y1, color=Colro.WHITE, fill=None):
    """Draw a rectangle with green border and no fill"""

def draw_line(x0, y0, x1, y1, color=Colro.WHITE):
    """Draw a line from (x0,y0) to (x1,y1)"""

def draw_image(x, y, image_path):
    """Draw image `image_path` at (x, y)"""

def draw_logo():
    """Draw the MatrixBOX logo (with yellow BOX) at the first row"""

Then I'd just want to have an app which can combine a bunch of these calls to render on the LED without having to calculate offsets, x, y, spacing, scrolling and whatever, just handle it automatically with the state of the Printer.

import led

led.clear()

p = led.Printer()
p.add_row(
    ["Hello from ", Text("Another", color=Color.RED), " color"],
    font_size=Font.LARGE,
    align=TextAlign.CENTER,
    scroll=Scroll.HORIZONTAL,
)
p.add_row(["This is a row ", Text("after...", shadow_color=Color.GREEN)])
p.print() # Render all rows

I know this is a very open ended ticket but I want it tracked to get the initial plan rolling and have good building bricks for future changes. The main focus should be on printing to the display because if we can sort that out it would be super easy to hook up whatever command or external requet to just fill the LED with text scrolling in any direction and any speed, potentially without even building apps for it if it's triggered externally.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions