private-assistant-display-controller 0.2.0__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {private_assistant_display_controller-0.2.0 → private_assistant_display_controller-0.2.2}/PKG-INFO +1 -1
- {private_assistant_display_controller-0.2.0 → private_assistant_display_controller-0.2.2}/pyproject.toml +1 -1
- {private_assistant_display_controller-0.2.0 → private_assistant_display_controller-0.2.2}/src/private_assistant_display_controller/__init__.py +1 -1
- {private_assistant_display_controller-0.2.0 → private_assistant_display_controller-0.2.2}/src/private_assistant_display_controller/controller.py +0 -1
- {private_assistant_display_controller-0.2.0 → private_assistant_display_controller-0.2.2}/src/private_assistant_display_controller/display.py +22 -19
- {private_assistant_display_controller-0.2.0 → private_assistant_display_controller-0.2.2}/LICENSE +0 -0
- {private_assistant_display_controller-0.2.0 → private_assistant_display_controller-0.2.2}/README.md +0 -0
- {private_assistant_display_controller-0.2.0 → private_assistant_display_controller-0.2.2}/src/private_assistant_display_controller/config.py +0 -0
- {private_assistant_display_controller-0.2.0 → private_assistant_display_controller-0.2.2}/src/private_assistant_display_controller/exceptions.py +0 -0
- {private_assistant_display_controller-0.2.0 → private_assistant_display_controller-0.2.2}/src/private_assistant_display_controller/main.py +0 -0
- {private_assistant_display_controller-0.2.0 → private_assistant_display_controller-0.2.2}/src/private_assistant_display_controller/models.py +0 -0
- {private_assistant_display_controller-0.2.0 → private_assistant_display_controller-0.2.2}/src/private_assistant_display_controller/mqtt_client.py +0 -0
- {private_assistant_display_controller-0.2.0 → private_assistant_display_controller-0.2.2}/src/private_assistant_display_controller/py.typed +0 -0
- {private_assistant_display_controller-0.2.0 → private_assistant_display_controller-0.2.2}/src/private_assistant_display_controller/s3_client.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "uv_build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "private-assistant-display-controller"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.2"
|
|
8
8
|
description = "Package is intended to be used in conjunction with the display skill to control a display device."
|
|
9
9
|
authors = [
|
|
10
10
|
{ name = "sktr22", email = "sktr22@github.com" }
|
|
@@ -4,7 +4,7 @@ This package provides a daemon that receives MQTT commands from the
|
|
|
4
4
|
picture-display-skill and displays images on an Inky Impression e-ink display.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
__version__ = "0.2.
|
|
7
|
+
__version__ = "0.2.2"
|
|
8
8
|
|
|
9
9
|
from private_assistant_display_controller.config import Settings, load_settings
|
|
10
10
|
from private_assistant_display_controller.controller import DisplayController
|
|
@@ -41,7 +41,6 @@ class DisplayController:
|
|
|
41
41
|
self._s3 = S3ImageClient()
|
|
42
42
|
self._display: DisplayInterface = create_display(
|
|
43
43
|
mock=settings.display.mock,
|
|
44
|
-
orientation=settings.display.orientation,
|
|
45
44
|
mock_width=settings.display.mock_width,
|
|
46
45
|
mock_height=settings.display.mock_height,
|
|
47
46
|
)
|
|
@@ -4,7 +4,6 @@ import asyncio
|
|
|
4
4
|
import logging
|
|
5
5
|
from abc import ABC, abstractmethod
|
|
6
6
|
from concurrent.futures import ThreadPoolExecutor
|
|
7
|
-
from typing import Literal
|
|
8
7
|
|
|
9
8
|
from PIL import Image
|
|
10
9
|
|
|
@@ -48,11 +47,13 @@ class InkyDisplay(DisplayInterface):
|
|
|
48
47
|
so it runs in a dedicated thread pool to avoid blocking the async loop.
|
|
49
48
|
|
|
50
49
|
Dimensions are auto-detected from the hardware during initialization.
|
|
50
|
+
The Inky library itself handles any internal rotation required by the
|
|
51
|
+
physical mounting — the public API always expects landscape images
|
|
52
|
+
(wider than tall).
|
|
51
53
|
"""
|
|
52
54
|
|
|
53
55
|
def __init__(
|
|
54
56
|
self,
|
|
55
|
-
orientation: Literal["landscape", "portrait"] = "landscape",
|
|
56
57
|
executor: ThreadPoolExecutor | None = None,
|
|
57
58
|
) -> None:
|
|
58
59
|
"""Initialize the Inky display wrapper.
|
|
@@ -60,14 +61,12 @@ class InkyDisplay(DisplayInterface):
|
|
|
60
61
|
Connects to hardware immediately to detect display dimensions.
|
|
61
62
|
|
|
62
63
|
Args:
|
|
63
|
-
orientation: Display orientation.
|
|
64
64
|
executor: Optional thread pool executor for display operations.
|
|
65
65
|
|
|
66
66
|
Raises:
|
|
67
67
|
DisplayError: If the display cannot be initialized.
|
|
68
68
|
|
|
69
69
|
"""
|
|
70
|
-
self._orientation = orientation
|
|
71
70
|
self._executor = executor or ThreadPoolExecutor(max_workers=1, thread_name_prefix="inky")
|
|
72
71
|
self._lock = asyncio.Lock()
|
|
73
72
|
|
|
@@ -85,12 +84,12 @@ class InkyDisplay(DisplayInterface):
|
|
|
85
84
|
|
|
86
85
|
@property
|
|
87
86
|
def width(self) -> int:
|
|
88
|
-
"""Display width in pixels."""
|
|
87
|
+
"""Display width in pixels (hardware landscape dimension)."""
|
|
89
88
|
return self._width
|
|
90
89
|
|
|
91
90
|
@property
|
|
92
91
|
def height(self) -> int:
|
|
93
|
-
"""Display height in pixels."""
|
|
92
|
+
"""Display height in pixels (hardware landscape dimension)."""
|
|
94
93
|
return self._height
|
|
95
94
|
|
|
96
95
|
async def show_image(self, image: Image.Image, saturation: float = 0.5) -> None:
|
|
@@ -134,16 +133,21 @@ class InkyDisplay(DisplayInterface):
|
|
|
134
133
|
DisplayError: If image dimensions don't match display dimensions.
|
|
135
134
|
|
|
136
135
|
"""
|
|
137
|
-
#
|
|
136
|
+
# Normalise to landscape: the Inky library always expects the wider
|
|
137
|
+
# dimension as width, and handles any physical mounting rotation
|
|
138
|
+
# internally (hard-coded rot90 in InkyEL133UF1.show()).
|
|
139
|
+
if image.height > image.width:
|
|
140
|
+
image = image.transpose(Image.Transpose.ROTATE_90)
|
|
141
|
+
|
|
138
142
|
if image.size != (self.width, self.height):
|
|
139
143
|
raise DisplayError(
|
|
140
|
-
f"Image size {image.size[0]}x{image.size[1]} does not match "
|
|
141
|
-
f"display size {self.width}x{self.height}. "
|
|
142
|
-
"The skill must provide correctly sized images."
|
|
144
|
+
f"Image size {image.size[0]}x{image.size[1]} does not match display size {self.width}x{self.height}."
|
|
143
145
|
)
|
|
144
146
|
|
|
147
|
+
display_image = image
|
|
148
|
+
|
|
145
149
|
logger.info("Updating display (this takes ~20-25 seconds)...")
|
|
146
|
-
self._display.set_image(
|
|
150
|
+
self._display.set_image(display_image, saturation=saturation) # type: ignore[union-attr]
|
|
147
151
|
try:
|
|
148
152
|
self._display.show(busy_wait=True) # type: ignore[union-attr]
|
|
149
153
|
except FileNotFoundError as e:
|
|
@@ -215,12 +219,13 @@ class MockDisplay(DisplayInterface):
|
|
|
215
219
|
"""
|
|
216
220
|
_ = saturation # Unused in mock, but part of interface
|
|
217
221
|
|
|
218
|
-
#
|
|
222
|
+
# Normalise to landscape, same as the real display
|
|
223
|
+
if image.height > image.width:
|
|
224
|
+
image = image.transpose(Image.Transpose.ROTATE_90)
|
|
225
|
+
|
|
219
226
|
if image.size != (self._width, self._height):
|
|
220
227
|
raise DisplayError(
|
|
221
|
-
f"Image size {image.size[0]}x{image.size[1]} does not match "
|
|
222
|
-
f"display size {self._width}x{self._height}. "
|
|
223
|
-
"The skill must provide correctly sized images."
|
|
228
|
+
f"Image size {image.size[0]}x{image.size[1]} does not match display size {self._width}x{self._height}."
|
|
224
229
|
)
|
|
225
230
|
|
|
226
231
|
self._last_image = image.copy()
|
|
@@ -239,7 +244,6 @@ class MockDisplay(DisplayInterface):
|
|
|
239
244
|
|
|
240
245
|
def create_display(
|
|
241
246
|
mock: bool = False,
|
|
242
|
-
orientation: Literal["landscape", "portrait"] = "landscape",
|
|
243
247
|
mock_width: int = 1600,
|
|
244
248
|
mock_height: int = 1200,
|
|
245
249
|
) -> DisplayInterface:
|
|
@@ -247,7 +251,6 @@ def create_display(
|
|
|
247
251
|
|
|
248
252
|
Args:
|
|
249
253
|
mock: If True, create a MockDisplay for testing.
|
|
250
|
-
orientation: Display orientation for real hardware.
|
|
251
254
|
mock_width: Width for mock display (ignored for real hardware).
|
|
252
255
|
mock_height: Height for mock display (ignored for real hardware).
|
|
253
256
|
|
|
@@ -262,5 +265,5 @@ def create_display(
|
|
|
262
265
|
logger.info("Creating mock display (%dx%d)", mock_width, mock_height)
|
|
263
266
|
return MockDisplay(width=mock_width, height=mock_height)
|
|
264
267
|
|
|
265
|
-
logger.info("Creating Inky display
|
|
266
|
-
return InkyDisplay(
|
|
268
|
+
logger.info("Creating Inky display")
|
|
269
|
+
return InkyDisplay()
|
{private_assistant_display_controller-0.2.0 → private_assistant_display_controller-0.2.2}/LICENSE
RENAMED
|
File without changes
|
{private_assistant_display_controller-0.2.0 → private_assistant_display_controller-0.2.2}/README.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|