From b8efb9e56469b29abfee21aaf71ce984f8aa9dd6 Mon Sep 17 00:00:00 2001 From: Chuck <33324927+ChuckBuilds@users.noreply.github.com> Date: Wed, 2 Sep 2026 21:12:54 -0400 Subject: [PATCH] fix(web-ui-info): stop clipping the address, and document every setting On a 64x32 panel -- the most common size -- "at ledmatrix:5000" is 68px of 4x6 text in 64px of panel, and PIL clips silently at draw time, so it rendered as "t ledmatrix:500" with both ends sliced off. The render harness passes this: it measures the bounding box it was asked to draw, not the pixels that survived. The address line now shortens in stages instead -- drop the "at " prefix, then wrap the address -- and the block is centred vertically rather than pinned 5px from the top, which left the text stranded at the top of a tall panel. The README gains real rendered screenshots for the panel-size and hostname-length behaviour, the hostname/IP alternation the old README never mentioned, the five-step IP detection order, and a note that the three transition.* settings are read by nothing (#381). The docs renderer can now pin socket.gethostname, so these images render identically on any machine instead of baking in whoever ran them. Co-Authored-By: Claude Opus 5 --- README.md | 2 +- docs/assets/web-ui-info/hero.png | Bin 0 -> 1143 bytes docs/assets/web-ui-info/hostname-length.png | Bin 0 -> 11875 bytes docs/assets/web-ui-info/narrow-wrap.png | Bin 0 -> 7811 bytes docs/assets/web-ui-info/panel-sizes.png | Bin 0 -> 12667 bytes docs/assets/web-ui-info/shots.json | 121 ++++++++++ plugins.json | 4 +- plugins/web-ui-info/README.md | 236 ++++++++++++++++--- plugins/web-ui-info/manager.py | 47 +++- plugins/web-ui-info/manifest.json | 9 +- plugins/web-ui-info/test/harness.json | 2 +- scripts/docs_render_support/sitecustomize.py | 11 + scripts/render_docs_assets.py | 8 +- 13 files changed, 388 insertions(+), 52 deletions(-) create mode 100644 docs/assets/web-ui-info/hero.png create mode 100644 docs/assets/web-ui-info/hostname-length.png create mode 100644 docs/assets/web-ui-info/narrow-wrap.png create mode 100644 docs/assets/web-ui-info/panel-sizes.png create mode 100644 docs/assets/web-ui-info/shots.json diff --git a/README.md b/README.md index 9fae8532..186940c9 100644 --- a/README.md +++ b/README.md @@ -175,7 +175,7 @@ curl -X POST http://your-pi-ip:5000/api/v3/plugins/install \ | Plugin | Description | Preview | |--------|-------------|---------| -| [Web UI Info](./plugins/web-ui-info/) | Displays web UI URL for device access | | +| [Web UI Info](./plugins/web-ui-info/) | Displays web UI URL for device access | web-ui-info on an LED panel | ### Development (1) diff --git a/docs/assets/web-ui-info/hero.png b/docs/assets/web-ui-info/hero.png new file mode 100644 index 0000000000000000000000000000000000000000..fc6a6977c4fc5459921a3fad1a35247ba5fe812e GIT binary patch literal 1143 zcmeAS@N?(olHy`uVBq!ia0y~yU<>k^Lt+Bg61Vbgg&9Oei^U}i34Ws443y*^~w>s|H+ z+&asXOKs%1?|u1Lapb*p!LyZ)I~bX;IC6z%y3@6yvun1ri}sox&C;B`+LY(;oBLBr zw=x{!6+m~eKvaT*h{>Ly^>1pHYG0p~Gj-eBV}(88Uw@n`+q!1wwePi4zgx{e&;9jT z1I)KjpMo5T2Si`F$#m^?*>2x?GJNe<_a1#(G}Fd%u3ufub9E(fKs z-+$jczshd@WcBN>x1Kr~^Y~OtCNLo(Spy2++@Bj*Tc!!mw&8qR&L-dQ{%7BLQCHp_ z>t3hT&)qV0+CTg3n3*saBE<*Lds{NL-}ar?^H{<(jc4iQN~PpO7pto!x2kbny>&Ip zcp)$XVweA&yy4F7=VzF-XNym}Tz)#z@^9(1*>zLj{rUdA{9xo?X>H-W29(4AN*hkx zJ>P%-`qi~_rPaiXxt>d3Z!u%k%4J^KcRcxEw9fX`ftlaJZq3U4uQKKA;Z>!tE~SO7 zl?(Hmy=IN<%+K#K_v`!(Ij1in8GGVexY`}mR0Im!7BPt}OV@;MIrhKu^xx^<&3KD- z%sbb-amlSr1w|t$vBA;_FsK|}TSag1eHk*hP$u2pl*f5bR#;%jth%Y2i>&TyPBng? zxieNbn|bQlmaMlbI@epG*0pcF!@1tH^USFSY#iu`2v4-qR_fnt_8D3GG*{UATm@O= M>FVdQ&MBb@0KoHE?f?J) literal 0 HcmV?d00001 diff --git a/docs/assets/web-ui-info/hostname-length.png b/docs/assets/web-ui-info/hostname-length.png new file mode 100644 index 0000000000000000000000000000000000000000..e52f4a82296d03f32a33f7840626e158deeaf472 GIT binary patch literal 11875 zcmcIq2{e^!+uqfw_R&B^D${WaMNuS~iwvPuhHc&n31!SY>;@`?L)ek*B#Dh3lG!ei z%*m8F^GxPx5C8pk(&tm>TmSmk_giaQ%YMDja6k8OUDy5io;$0wg<%f^3WeHoTKUuk z6lxtm3bnQby%xS1+raIDLJ9JpK6O&lrFpc?-B!~qT9(FV(DSGALG_tvi49CH!1 zMm1PfoH(4b>E2Gij2nAJ_`BC$NS(J2PRkn5^fh?qz1#ngV8+Y!FDmxq*B`yG|DNjM z^pDEImy*|S@-g7uzvkpk3hlUTuCRr2Bro^xc}BR5ws?X=bbr(_n`jw=*QZD^o82f> zmY(G}>?^97%#|_5eW#+%! zZp(G9juM|@xJ&fVUx(rs_u9fCadfAD0H<`0S$TM>&eh(su+lu@*~b#=Hf~MAklIax zq@8$1LmRp4Nm$&+Cns@~Zmy-dah#8#0BgE=HK(wta{A@UPoF*sN*MIEG%MsXq6{?B zvF6p$o^x%^WIAn$7-6AOlVR0pQTx0N){|jbpE!NnqWRUJp>rFH+~+JilT7Qjw6wGo z)2tASWEDp)RElfZY`;@k=mCxAYIbY4o0*!PVzjrnuZos-_j#vPkh}&JQ�=W^P_` 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 C_-LB| literal 0 HcmV?d00001 diff --git a/docs/assets/web-ui-info/panel-sizes.png b/docs/assets/web-ui-info/panel-sizes.png new file mode 100644 index 0000000000000000000000000000000000000000..49290dd7e8e26e7cdc0b95cfb413d753abb87b4f GIT binary patch literal 12667 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:5000` — the normal form +2. `

:5000` — the `at ` prefix is dropped when it no longer fits +3. the address wraps onto extra lines when even that is too wide + +On a 64x32 panel — where a 4x6 font gives you about 16 characters — both the +second and third stages come into play: + +![A long hostname wrapping on a 64x32 panel](../../docs/assets/web-ui-info/narrow-wrap.png) + +If you would rather see a short address than a wrapped one, rename the device +(`sudo hostnamectl set-hostname ledmatrix`) or wait out the 10-second swap and +read the IP instead. + +--- + +## Verifying it loaded + +Installed plugins appear under **Installed Plugins** in the **Plugin Manager** +tab. From SSH: + +```bash +sudo journalctl -u ledmatrix -f | grep web-ui-info +``` + +You should see the hostname and IP the plugin settled on at startup: + +```text +Web UI Info plugin initialized - Hostname: ledmatrix, IP: 192.168.1.42 +``` + +--- + +## Troubleshooting + +**The panel says `at localhost:5000`** +Every IP-detection step failed, which usually means no network interface is +up. Check `hostname -I` over SSH — if that prints nothing, the plugin has +nothing to show either. + +**The IP on the panel is out of date** +It is re-read every 30 seconds, but only while the plugin's `update()` runs. +If the display rotation is long, give it a full cycle before assuming it is +stuck. + +**It always shows `192.168.4.1`** +That is the AP-mode address, and it is reported whenever `hostapd` is running. +If the Pi is meant to be on your normal network, `hostapd` is still active: +`sudo systemctl stop hostapd`. + +**Changing `transition.*` does nothing** +Correct — see [above](#the-transition-settings-do-nothing). + +--- ## Notes -- This is a built-in plugin that comes with the LEDMatrix repository -- No external dependencies required -- The plugin does not fetch any external data - it only displays the local device information +- Ships with the LEDMatrix repository; no external dependencies. +- Makes no network requests. It reads the local hostname and interface + addresses only. +- The images in this README are produced by + [`scripts/render_docs_assets.py`](../../scripts/render_docs_assets.py) from + [`docs/assets/web-ui-info/shots.json`](../../docs/assets/web-ui-info/shots.json), + with the hostname pinned so they render the same on any machine. + +## License +GPL-3.0, same as the LEDMatrix project. diff --git a/plugins/web-ui-info/manager.py b/plugins/web-ui-info/manager.py index d473bb98..eba0a1a4 100644 --- a/plugins/web-ui-info/manager.py +++ b/plugins/web-ui-info/manager.py @@ -13,7 +13,7 @@ import subprocess import time from pathlib import Path -from typing import Dict, Any +from typing import Any, Dict, List from PIL import Image, ImageDraw, ImageFont from src.plugin_system.base_plugin import BasePlugin @@ -203,6 +203,33 @@ def update(self) -> None: self.device_ip = new_ip self.last_ip_refresh = current_time + def _fit_address(self, draw, font, width: int, address: str) -> List[str]: + """Lay ``address`` out in as few lines as fit ``width`` pixels. + + The 4x6 face is a fixed size, so a long hostname on a narrow panel + cannot be shrunk to fit. Drop the "at " prefix first, and only then + wrap the address itself -- a wrapped hostname is still readable, while + a clipped one is not. + """ + def text_width(text: str) -> int: + bbox = draw.textbbox((0, 0), text, font=font) + return bbox[2] - bbox[0] + + if text_width(f"at {address}") <= width: + return [f"at {address}"] + if text_width(address) <= width: + return [address] + + lines: List[str] = [] + remaining = address + while remaining: + take = len(remaining) + while take > 1 and text_width(remaining[:take]) > width: + take -= 1 + lines.append(remaining[:take]) + remaining = remaining[take:] + return lines + def display(self, force_clear: bool = False) -> None: """ Display the web UI URL message. @@ -263,15 +290,17 @@ def display(self, force_clear: bool = False) -> None: else: address = self.device_id - # Prepare text to display - lines = [ - "visit web ui", - f"at {address}:5000" - ] - - # Calculate text positions (centered) - y_start = 5 + # Prepare text to display. PIL clips silently at draw time, so a + # line wider than the panel just loses its ends: on a 64x32 panel + # "at ledmatrix:5000" used to render as "t ledmatrix:500". + lines = ["visit web ui"] + self._fit_address( + draw, font_small, width, f"{address}:5000") + + # Centre the block vertically rather than pinning it 5px from the + # top, which left the text stranded at the top of a tall panel. line_height = 8 + block_height = (len(lines) - 1) * line_height + 6 + y_start = max(0, (height - block_height) // 2) # Draw each line for i, line in enumerate(lines): diff --git a/plugins/web-ui-info/manifest.json b/plugins/web-ui-info/manifest.json index 19eca646..4dd896ac 100644 --- a/plugins/web-ui-info/manifest.json +++ b/plugins/web-ui-info/manifest.json @@ -1,7 +1,7 @@ { "id": "web-ui-info", "name": "Web UI Info", - "version": "1.0.2", + "version": "1.1.0", "author": "LEDMatrix", "description": "Displays the web UI URL for easy access. Shows 'visit web ui at http://[deviceID]:5000'", "entry_point": "manager.py", @@ -19,6 +19,11 @@ "update_interval": 300, "default_duration": 10, "versions": [ + { + "version": "1.1.0", + "released": "2026-09-02", + "ledmatrix_min": "2.0.0" + }, { "version": "1.0.2", "released": "2026-07-31", @@ -35,7 +40,7 @@ "ledmatrix_min": "2.0.0" } ], - "last_updated": "2026-07-31", + "last_updated": "2026-09-02", "compatible_versions": [ ">=2.0.0" ] diff --git a/plugins/web-ui-info/test/harness.json b/plugins/web-ui-info/test/harness.json index 2684fbed..e623d75d 100644 --- a/plugins/web-ui-info/test/harness.json +++ b/plugins/web-ui-info/test/harness.json @@ -1,5 +1,5 @@ { - "_comment": "Harness config for the web-UI info panel. The rendered address line contains the host machine's IP, which differs per machine, so this plugin ships no golden images — the harness still exercises crash/overflow checks on every panel size deterministically enough (layout is fixed; only the digits vary).", + "_comment": "Harness config for the web-UI info panel. The rendered address line carries the host machine's own hostname (and, once it rotates, its IP), both of which differ per machine, so this plugin ships no golden images -- the harness still exercises crash and overflow checks on every panel size. Reproducible screenshots live in docs/assets/web-ui-info, where the renderer pins the hostname.", "config": { "enabled": true } diff --git a/scripts/docs_render_support/sitecustomize.py b/scripts/docs_render_support/sitecustomize.py index 20844d5e..18300239 100644 --- a/scripts/docs_render_support/sitecustomize.py +++ b/scripts/docs_render_support/sitecustomize.py @@ -52,3 +52,14 @@ def today(cls): _docs_http_replay.install() except Exception: # never let doc tooling break the render it is measuring pass + + +# A plugin that shows the device hostname would otherwise bake whoever ran the +# renderer into the committed image, and --check would then fail for everyone +# else. Pinning it keeps the screenshot generic and reproducible. +_HOSTNAME = os.environ.get("LEDMATRIX_DOCS_HOSTNAME") +if _HOSTNAME: + import socket as _socket + + _socket.gethostname = lambda: _HOSTNAME + _socket.getfqdn = lambda *_a: _HOSTNAME diff --git a/scripts/render_docs_assets.py b/scripts/render_docs_assets.py index 42a1d93d..f5824922 100644 --- a/scripts/render_docs_assets.py +++ b/scripts/render_docs_assets.py @@ -263,12 +263,18 @@ def render_shot( # clock, a countdown, or a "starts in 2h" line differs on every run. freeze_time = shot.get("freeze_time", defaults.get("freeze_time")) http_replay = shot.get("http_replay", defaults.get("http_replay")) - if freeze_time or http_replay: + # A plugin that prints the device hostname would otherwise bake whoever ran + # the renderer into the committed image, and --check would fail for anyone + # else. Pinning it keeps the screenshot generic and reproducible. + hostname = shot.get("hostname", defaults.get("hostname")) + if freeze_time or http_replay or hostname: support_dir = str(Path(__file__).resolve().parent / "docs_render_support") existing = env.get("PYTHONPATH") env["PYTHONPATH"] = f"{support_dir}{os.pathsep}{existing}" if existing else support_dir if freeze_time: env["LEDMATRIX_DOCS_FREEZE_TIME"] = str(freeze_time) + if hostname: + env["LEDMATRIX_DOCS_HOSTNAME"] = str(hostname) if http_replay: replay_path = (shot_list_dir / http_replay).resolve() if not replay_path.is_file():