From 77cfe3d79aa5f68a0c35e6511f60564ac247da13 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 17 Apr 2025 19:18:21 +0200 Subject: [PATCH 1/4] No need for threading here. --- src/wwvb/wwvbtk.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/wwvb/wwvbtk.py b/src/wwvb/wwvbtk.py index 33d5fbd..bb9e1aa 100755 --- a/src/wwvb/wwvbtk.py +++ b/src/wwvb/wwvbtk.py @@ -7,7 +7,6 @@ from __future__ import annotations import functools -import threading import time from tkinter import Canvas, TclError, Tk from typing import TYPE_CHECKING, Any @@ -61,11 +60,10 @@ def main(colors: list[str], size: int, min_size: int | None) -> None: # noqa: P if min_size is None: min_size = size - def sleep_deadline(deadline: float) -> None: - """Sleep until a deadline""" + def deadline_ms(deadline: float) -> None: + """Compute the number of ms until a deadline""" now = time.time() - if deadline > now: - time.sleep(deadline - now) + return int(max(0, deadline - now) * 1000) def wwvbtick() -> Generator[tuple[float, wwvb.AmplitudeModulation], None, None]: """Yield consecutive values of the WWVB amplitude signal, going from minute to minute""" @@ -127,18 +125,23 @@ def main(colors: list[str], size: int, min_size: int | None) -> None: # noqa: P """Turn the canvas's virtual LED off""" canvas.itemconfigure(circle, fill=colors[i]) - def thread_func() -> None: - """Update the canvas virtual LED""" + def controller_func() -> None: + """Update the canvas virtual LED, yielding the number of ms until the next change""" for stamp, code in wwvbsmarttick(): - sleep_deadline(stamp) + yield deadline_ms(stamp) led_on(code) app.update() - sleep_deadline(stamp + 0.2 + 0.3 * int(code)) + yield deadline_ms(stamp + 0.2 + 0.3 * int(code)) led_off(code) app.update() - thread = threading.Thread(target=thread_func, daemon=True) - thread.start() + controller = controller_func().__next__ + + def after_func(): + """Repeatedly run the controller after the desired interval""" + app.after(controller(), after_func) + + app.after_idle(after_func) app.mainloop() From f5314e2a7ce40f2581fe0a7bb0048b6a51fc13d8 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 17 Apr 2025 19:18:53 +0200 Subject: [PATCH 2/4] improve documentation of --colors though it's still not super helpful --- src/wwvb/wwvbtk.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/wwvb/wwvbtk.py b/src/wwvb/wwvbtk.py index bb9e1aa..71a7d47 100755 --- a/src/wwvb/wwvbtk.py +++ b/src/wwvb/wwvbtk.py @@ -30,7 +30,7 @@ def validate_colors(ctx: Any, param: Any, value: str) -> list[str]: # noqa: ARG app = _app() colors = value.split() if len(colors) not in (2, 3, 4, 6): - raise click.BadParameter(f"Give 2, 3, 4 or 6 colors (not {len(colors)}") + raise click.BadParameter(f"Give 2, 3, 4 or 6 colors (not {len(colors)})") for c in colors: try: app.winfo_rgb(c) @@ -52,7 +52,13 @@ DEFAULT_COLORS = "#3c3c3c #3c3c3c #3c3c3c #cc3c3c #88883c #3ccc3c" @click.command -@click.option("--colors", callback=validate_colors, default=DEFAULT_COLORS) +@click.option( + "--colors", + callback=validate_colors, + default=DEFAULT_COLORS, + metavar="COLORS", + help="2, 3, 4, or 6 Tk color values", +) @click.option("--size", default=48) @click.option("--min-size", default=None) def main(colors: list[str], size: int, min_size: int | None) -> None: # noqa: PLR0915 From 4d1bfb8002b9b887c94c8013a7b5d8533a6edd53 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 17 Apr 2025 19:19:10 +0200 Subject: [PATCH 3/4] Remove a diagnostic ruff told me could "cause conflicts with the formatter" --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index dfc7a53..621ea5f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ dependencies = {file = "requirements.txt"} write_to = "src/wwvb/__version__.py" [tool.ruff.lint] select = ["E", "F", "D", "I", "N", "UP", "YTT", "BLE", "B", "FBT", "A", "COM", "C4", "DTZ", "FA", "ISC", "ICN", "PIE", "PYI", "Q", "RET", "SIM", "TID", "TCH", "ARG", "PTH", "C", "R", "W", "FLY", "RUF", "PL"] -ignore = ["D203", "D213", "D400", "D415", "ISC001", "E741", "C901", "PLR0911", "PLR2004", "PLR0913"] +ignore = ["D203", "D213", "D400", "D415", "ISC001", "E741", "C901", "PLR0911", "PLR2004", "PLR0913", "COM812"] [tool.ruff] line-length = 120 [project] From 16090774ed3019dacebbb57a4f09715220f0da8d Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Thu, 17 Apr 2025 19:20:54 +0200 Subject: [PATCH 4/4] fix type hints --- src/wwvb/wwvbtk.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/wwvb/wwvbtk.py b/src/wwvb/wwvbtk.py index 71a7d47..f9fd11a 100755 --- a/src/wwvb/wwvbtk.py +++ b/src/wwvb/wwvbtk.py @@ -66,7 +66,7 @@ def main(colors: list[str], size: int, min_size: int | None) -> None: # noqa: P if min_size is None: min_size = size - def deadline_ms(deadline: float) -> None: + def deadline_ms(deadline: float) -> int: """Compute the number of ms until a deadline""" now = time.time() return int(max(0, deadline - now) * 1000) @@ -131,7 +131,7 @@ def main(colors: list[str], size: int, min_size: int | None) -> None: # noqa: P """Turn the canvas's virtual LED off""" canvas.itemconfigure(circle, fill=colors[i]) - def controller_func() -> None: + def controller_func() -> Generator[int]: """Update the canvas virtual LED, yielding the number of ms until the next change""" for stamp, code in wwvbsmarttick(): yield deadline_ms(stamp) @@ -143,7 +143,7 @@ def main(colors: list[str], size: int, min_size: int | None) -> None: # noqa: P controller = controller_func().__next__ - def after_func(): + def after_func() -> None: """Repeatedly run the controller after the desired interval""" app.after(controller(), after_func)