|
19 | 19 | from .device.info import build_device_info_command, parse_device_response |
20 | 20 | from .display.text_renderer import render_text_to_png |
21 | 21 | from .exceptions import iPIXELConnectionError |
| 22 | +from .giftimer import build_timer_gif |
22 | 23 |
|
23 | 24 | _LOGGER = logging.getLogger(__name__) |
24 | 25 |
|
@@ -336,6 +337,107 @@ async def display_text_pypixelcolor( |
336 | 337 | _LOGGER.error("Error displaying pypixelcolor text: %s", err) |
337 | 338 | return False |
338 | 339 |
|
| 340 | + async def display_timer_gif( |
| 341 | + self, |
| 342 | + duration_seconds: int, |
| 343 | + text_color: tuple[int, int, int] = (0, 255, 0), |
| 344 | + bg_color: tuple[int, int, int] = (0, 0, 0), |
| 345 | + font_path: str | None = None, |
| 346 | + ) -> bool: |
| 347 | + """Generate and display a countdown timer GIF on the device. |
| 348 | +
|
| 349 | + Args: |
| 350 | + duration_seconds: Total countdown duration in seconds |
| 351 | + text_color: RGB tuple for text color (default: green) |
| 352 | + bg_color: RGB tuple for background color (default: black) |
| 353 | + font_path: Path to TTF font file, or None for default |
| 354 | +
|
| 355 | + Returns: |
| 356 | + True if timer GIF was sent successfully |
| 357 | + """ |
| 358 | + try: |
| 359 | + import tempfile |
| 360 | + import os |
| 361 | + from pathlib import Path |
| 362 | + |
| 363 | + # Get device dimensions |
| 364 | + device_info = await self.get_device_info() |
| 365 | + width = device_info["width"] |
| 366 | + height = device_info["height"] |
| 367 | + |
| 368 | + # Determine font size based on display height |
| 369 | + # Use a font size that fits well in the display |
| 370 | + font_size = max(8, height - 4) |
| 371 | + |
| 372 | + # Use default font if none specified |
| 373 | + if font_path is None: |
| 374 | + fonts_dir = Path(__file__).parent / "fonts" |
| 375 | + # Try to find a good monospace font for timer display |
| 376 | + for font_name in ["7x5.ttf", "5x5.ttf", "OpenSans-Light.ttf"]: |
| 377 | + potential_font = fonts_dir / font_name |
| 378 | + if potential_font.exists(): |
| 379 | + font_path = str(potential_font) |
| 380 | + break |
| 381 | + |
| 382 | + # Generate timer GIF to temporary file |
| 383 | + with tempfile.NamedTemporaryFile(suffix=".gif", delete=False) as tmp: |
| 384 | + tmp_path = tmp.name |
| 385 | + |
| 386 | + try: |
| 387 | + frame_count = build_timer_gif( |
| 388 | + duration_seconds=duration_seconds, |
| 389 | + output_path=tmp_path, |
| 390 | + font_size=font_size, |
| 391 | + bg=bg_color, |
| 392 | + fg=text_color, |
| 393 | + font_path=font_path, |
| 394 | + width=width, |
| 395 | + height=height, |
| 396 | + ) |
| 397 | + |
| 398 | + _LOGGER.debug( |
| 399 | + "Generated timer GIF: %d seconds, %d frames, %dx%d", |
| 400 | + duration_seconds, frame_count, width, height |
| 401 | + ) |
| 402 | + |
| 403 | + # Read the generated GIF |
| 404 | + with open(tmp_path, "rb") as f: |
| 405 | + gif_data = f.read() |
| 406 | + |
| 407 | + finally: |
| 408 | + # Clean up temp file |
| 409 | + if os.path.exists(tmp_path): |
| 410 | + os.unlink(tmp_path) |
| 411 | + |
| 412 | + # Generate image commands using pypixelcolor |
| 413 | + commands = make_image_command( |
| 414 | + image_bytes=gif_data, |
| 415 | + file_extension=".gif", |
| 416 | + resize_method="crop", |
| 417 | + device_info_dict=device_info |
| 418 | + ) |
| 419 | + |
| 420 | + # Send all command frames |
| 421 | + for i, command in enumerate(commands): |
| 422 | + _LOGGER.debug( |
| 423 | + "Sending timer GIF frame %d/%d: %d bytes", |
| 424 | + i + 1, len(commands), len(command) |
| 425 | + ) |
| 426 | + success = await self._bluetooth.send_command(command) |
| 427 | + if not success: |
| 428 | + _LOGGER.error("Failed to send timer GIF frame %d/%d", i + 1, len(commands)) |
| 429 | + return False |
| 430 | + |
| 431 | + _LOGGER.info( |
| 432 | + "Timer GIF sent: %d seconds countdown (%dx%d, %d bytes, %d frames)", |
| 433 | + duration_seconds, width, height, len(gif_data), len(commands) |
| 434 | + ) |
| 435 | + return True |
| 436 | + |
| 437 | + except Exception as err: |
| 438 | + _LOGGER.error("Error displaying timer GIF: %s", err) |
| 439 | + return False |
| 440 | + |
339 | 441 | def _notification_handler(self, sender: Any, data: bytearray) -> None: |
340 | 442 | """Handle notifications from the device.""" |
341 | 443 | _LOGGER.debug("Notification from %s: %s", sender, data.hex()) |
|
0 commit comments