Skip to content

Commit

Permalink
chore: add dev extra, remove watchdog and cython
Browse files Browse the repository at this point in the history
  • Loading branch information
sassanh committed Nov 12, 2023
1 parent 6b21956 commit e6cde1d
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 227 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## Version 0.4.0

- chore: add dev extra, remove watchdog and cython

## Version 0.3.7

- chore: update typing-extensions
Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,14 @@ You can install it using this handle: headless-kivy-pi@git+<https://github.com/u

```sh
# pip:
pip install headless-kivy-pi@git+https://github.com/ubopod/headless-kivy-pi.git
pip install headless-kivy-pi
# poetry:
poetry add headless-kivy-pi@git+https://github.com/ubopod/headless-kivy-pi.git
poetry add headless-kivy-pi
```

To work on a non-RPi environment, install `headless-kivy-pi[rpi]` version.
To install development packages, install `headless-kivy-pi[dev]` version.

## 🚀 Usage

1. Call setup_headless() before inheriting the `HeadlessWidget` class for the root widget of your application, and provide the optional parameters as needed. For example (these are all default values, you only need to provide the ones you want to change):
Expand Down
77 changes: 51 additions & 26 deletions headless_kivy_pi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
import digitalio
from adafruit_rgb_display.rgb import DisplaySPI as _DisplaySPI
from adafruit_rgb_display.st7789 import ST7789 as _ST7789

ST7789 = _ST7789
DisplaySPI = _DisplaySPI
else:
Expand All @@ -70,18 +71,30 @@
WIDTH = int(os.environ.get('HEADLESS_KIVY_PI_WIDTH', '240'))
HEIGHT = int(os.environ.get('HEADLESS_KIVY_PI_HEIGHT', '240'))
BAUDRATE = int(os.environ.get('HEADLESS_KIVY_PI_BAUDRATE', '60000000'))
DEBUG_MODE = strtobool(
os.environ.get('HEADLESS_KIVY_PI_DEBUG', 'False' if IS_RPI else 'True'),
) == 1
DOUBLE_BUFFERING = strtobool(
os.environ.get('HEADLESS_KIVY_PI_DOUBLE_BUFFERING', 'True'),
) == 1
SYNCHRONOUS_CLOCK = strtobool(
os.environ.get('HEADLESS_KIVY_PI_SYNCHRONOUS_CLOCK', 'True'),
) == 1
AUTOMATIC_FPS_CONTROL = strtobool(
os.environ.get('HEADLESS_KIVY_PI_AUTOMATIC_FPS_CONTROL', 'False'),
) == 1
DEBUG_MODE = (
strtobool(
os.environ.get('HEADLESS_KIVY_PI_DEBUG', 'False' if IS_RPI else 'True'),
)
== 1
)
DOUBLE_BUFFERING = (
strtobool(
os.environ.get('HEADLESS_KIVY_PI_DOUBLE_BUFFERING', 'True'),
)
== 1
)
SYNCHRONOUS_CLOCK = (
strtobool(
os.environ.get('HEADLESS_KIVY_PI_SYNCHRONOUS_CLOCK', 'True'),
)
== 1
)
AUTOMATIC_FPS_CONTROL = (
strtobool(
os.environ.get('HEADLESS_KIVY_PI_AUTOMATIC_FPS_CONTROL', 'False'),
)
== 1
)


class SetupHeadlessConfig(TypedDict):
Expand Down Expand Up @@ -132,8 +145,7 @@ def setup_headless(config: SetupHeadlessConfig | None = None) -> None:
baudrate = config.get('baudrate', BAUDRATE)
HeadlessWidget.debug_mode = config.get('debug_mode', DEBUG_MODE)
display_class: DisplaySPI = config.get('st7789', ST7789)
HeadlessWidget.double_buffering = config.get(
'double_buffering', DOUBLE_BUFFERING)
HeadlessWidget.double_buffering = config.get('double_buffering', DOUBLE_BUFFERING)
HeadlessWidget.synchronous_clock = config.get(
'synchronous_clock',
SYNCHRONOUS_CLOCK,
Expand Down Expand Up @@ -244,8 +256,7 @@ def __init__(self: HeadlessWidget, **kwargs: Any) -> None: # noqa: ANN401
self.rendered_frames = 0
self.skipped_frames = 0

self.pending_render_threads = Queue(
2 if HeadlessWidget.double_buffering else 1)
self.pending_render_threads = Queue(2 if HeadlessWidget.double_buffering else 1)
self.last_hash = 0
self.last_change = time.time()
self.fps = self.max_fps
Expand All @@ -265,7 +276,9 @@ def __init__(self: HeadlessWidget, **kwargs: Any) -> None: # noqa: ANN401
super().__init__(**kwargs)

self.render_on_display_event = Clock.create_trigger(
self.render_on_display, 0, True,
self.render_on_display,
0,
True,
)
self.render_on_display_event()

Expand Down Expand Up @@ -339,6 +352,7 @@ def reset_fps_control_queue(cls: type[HeadlessWidget]) -> None:
It is required in case `release_task` is waiting for the next frame based on
previous fps and now fps is increased and we don't want to wait that long.
"""

def task() -> None:
cls.fps_control_queue.release()
if cls.latest_release_thread:
Expand Down Expand Up @@ -388,9 +402,13 @@ def transfer_to_display(

# Only render when running on a Raspberry Pi
if IS_RPI:
HeadlessWidget._display._block(0, 0, # noqa: SLF001
HeadlessWidget.width - 1,
HeadlessWidget.height - 1, data_bytes)
HeadlessWidget._display._block(
0,
0, # noqa: SLF001
HeadlessWidget.width - 1,
HeadlessWidget.height - 1,
data_bytes,
)

def render_on_display(self: HeadlessWidget, *_: Any) -> None: # noqa: ANN401
"""Render the widget on display connected to the SPI controller."""
Expand All @@ -416,25 +434,32 @@ def render_on_display(self: HeadlessWidget, *_: Any) -> None: # noqa: ANN401
# If `synchronous_clock` is False, skip frames if there are more than one
# pending render in case `double_buffering` is enabled, or if there are ANY
# pending render in case `double_buffering` is disabled.
if not HeadlessWidget.synchronous_clock and \
self.pending_render_threads.qsize() > \
(1 if HeadlessWidget.double_buffering else 0):
if (
not HeadlessWidget.synchronous_clock
and self.pending_render_threads.qsize()
> (1 if HeadlessWidget.double_buffering else 0)
):
self.skipped_frames += 1
return

if self.debug_mode:
self.rendered_frames += 1

data = np.frombuffer(self.texture.pixels, dtype=np.uint8).reshape(
HeadlessWidget.width, HeadlessWidget.height, -1,
HeadlessWidget.width,
HeadlessWidget.height,
-1,
)
# Flip the image vertically
data = data[::-1, :, :3].astype(np.uint16)
data_hash = hash(data.data.tobytes())
if data_hash == self.last_hash:
# Only drop FPS when the screen has not changed for at least one second
if (self.automatic_fps_control and
time.time() - self.last_change > 1 and self.fps != self.min_fps):
if (
self.automatic_fps_control
and time.time() - self.last_change > 1
and self.fps != self.min_fps
):
logger.debug('Frame content has not changed for 1 second')
self.activate_low_fps_mode()

Expand Down
Loading

0 comments on commit e6cde1d

Please sign in to comment.