private-assistant-display-controller 0.2.1__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.1 → private_assistant_display_controller-0.2.2}/PKG-INFO +1 -1
- {private_assistant_display_controller-0.2.1 → private_assistant_display_controller-0.2.2}/pyproject.toml +1 -1
- {private_assistant_display_controller-0.2.1 → private_assistant_display_controller-0.2.2}/src/private_assistant_display_controller/__init__.py +1 -1
- {private_assistant_display_controller-0.2.1 → private_assistant_display_controller-0.2.2}/src/private_assistant_display_controller/controller.py +0 -1
- {private_assistant_display_controller-0.2.1 → private_assistant_display_controller-0.2.2}/src/private_assistant_display_controller/display.py +24 -29
- {private_assistant_display_controller-0.2.1 → private_assistant_display_controller-0.2.2}/LICENSE +0 -0
- {private_assistant_display_controller-0.2.1 → private_assistant_display_controller-0.2.2}/README.md +0 -0
- {private_assistant_display_controller-0.2.1 → private_assistant_display_controller-0.2.2}/src/private_assistant_display_controller/config.py +0 -0
- {private_assistant_display_controller-0.2.1 → private_assistant_display_controller-0.2.2}/src/private_assistant_display_controller/exceptions.py +0 -0
- {private_assistant_display_controller-0.2.1 → private_assistant_display_controller-0.2.2}/src/private_assistant_display_controller/main.py +0 -0
- {private_assistant_display_controller-0.2.1 → private_assistant_display_controller-0.2.2}/src/private_assistant_display_controller/models.py +0 -0
- {private_assistant_display_controller-0.2.1 → private_assistant_display_controller-0.2.2}/src/private_assistant_display_controller/mqtt_client.py +0 -0
- {private_assistant_display_controller-0.2.1 → private_assistant_display_controller-0.2.2}/src/private_assistant_display_controller/py.typed +0 -0
- {private_assistant_display_controller-0.2.1 → 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,13 +84,13 @@ class InkyDisplay(DisplayInterface):
|
|
|
85
84
|
|
|
86
85
|
@property
|
|
87
86
|
def width(self) -> int:
|
|
88
|
-
"""
|
|
89
|
-
return self.
|
|
87
|
+
"""Display width in pixels (hardware landscape dimension)."""
|
|
88
|
+
return self._width
|
|
90
89
|
|
|
91
90
|
@property
|
|
92
91
|
def height(self) -> int:
|
|
93
|
-
"""
|
|
94
|
-
return self.
|
|
92
|
+
"""Display height in pixels (hardware landscape dimension)."""
|
|
93
|
+
return self._height
|
|
95
94
|
|
|
96
95
|
async def show_image(self, image: Image.Image, saturation: float = 0.5) -> None:
|
|
97
96
|
"""Display an image on the Inky screen.
|
|
@@ -134,19 +133,18 @@ 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
|
|
|
145
|
-
|
|
146
|
-
# Hardware always expects landscape (raw) dimensions; ROTATE_90 is 90°
|
|
147
|
-
# counter-clockwise — adjust if the physical mounting requires the
|
|
148
|
-
# opposite direction.
|
|
149
|
-
display_image = image.transpose(Image.Transpose.ROTATE_90) if self._orientation == "portrait" else image
|
|
147
|
+
display_image = image
|
|
150
148
|
|
|
151
149
|
logger.info("Updating display (this takes ~20-25 seconds)...")
|
|
152
150
|
self._display.set_image(display_image, saturation=saturation) # type: ignore[union-attr]
|
|
@@ -221,12 +219,13 @@ class MockDisplay(DisplayInterface):
|
|
|
221
219
|
"""
|
|
222
220
|
_ = saturation # Unused in mock, but part of interface
|
|
223
221
|
|
|
224
|
-
#
|
|
222
|
+
# Normalise to landscape, same as the real display
|
|
223
|
+
if image.height > image.width:
|
|
224
|
+
image = image.transpose(Image.Transpose.ROTATE_90)
|
|
225
|
+
|
|
225
226
|
if image.size != (self._width, self._height):
|
|
226
227
|
raise DisplayError(
|
|
227
|
-
f"Image size {image.size[0]}x{image.size[1]} does not match "
|
|
228
|
-
f"display size {self._width}x{self._height}. "
|
|
229
|
-
"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}."
|
|
230
229
|
)
|
|
231
230
|
|
|
232
231
|
self._last_image = image.copy()
|
|
@@ -245,7 +244,6 @@ class MockDisplay(DisplayInterface):
|
|
|
245
244
|
|
|
246
245
|
def create_display(
|
|
247
246
|
mock: bool = False,
|
|
248
|
-
orientation: Literal["landscape", "portrait"] = "landscape",
|
|
249
247
|
mock_width: int = 1600,
|
|
250
248
|
mock_height: int = 1200,
|
|
251
249
|
) -> DisplayInterface:
|
|
@@ -253,7 +251,6 @@ def create_display(
|
|
|
253
251
|
|
|
254
252
|
Args:
|
|
255
253
|
mock: If True, create a MockDisplay for testing.
|
|
256
|
-
orientation: Display orientation for real hardware.
|
|
257
254
|
mock_width: Width for mock display (ignored for real hardware).
|
|
258
255
|
mock_height: Height for mock display (ignored for real hardware).
|
|
259
256
|
|
|
@@ -265,10 +262,8 @@ def create_display(
|
|
|
265
262
|
|
|
266
263
|
"""
|
|
267
264
|
if mock:
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
logger.info("Creating mock display (%dx%d)", w, h)
|
|
271
|
-
return MockDisplay(width=w, height=h)
|
|
265
|
+
logger.info("Creating mock display (%dx%d)", mock_width, mock_height)
|
|
266
|
+
return MockDisplay(width=mock_width, height=mock_height)
|
|
272
267
|
|
|
273
|
-
logger.info("Creating Inky display
|
|
274
|
-
return InkyDisplay(
|
|
268
|
+
logger.info("Creating Inky display")
|
|
269
|
+
return InkyDisplay()
|
{private_assistant_display_controller-0.2.1 → private_assistant_display_controller-0.2.2}/LICENSE
RENAMED
|
File without changes
|
{private_assistant_display_controller-0.2.1 → 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
|