diff --git a/plugins.json b/plugins.json index f900d57c..62e80fac 100644 --- a/plugins.json +++ b/plugins.json @@ -507,10 +507,10 @@ "plugin_path": "plugins/mqtt-notifications", "stars": 0, "downloads": 0, - "last_updated": "2026-07-31", + "last_updated": "2026-09-02", "verified": true, "screenshot": "", - "latest_version": "1.1.1" + "latest_version": "1.2.0" }, { "id": "music", @@ -975,10 +975,10 @@ "plugin_path": "plugins/youtube-stats", "stars": 0, "downloads": 0, - "last_updated": "2026-07-31", + "last_updated": "2026-09-02", "verified": true, "screenshot": "", - "latest_version": "1.1.1" + "latest_version": "1.2.0" }, { "id": "ledmatrix-elections", diff --git a/plugins/mqtt-notifications/config_schema.json b/plugins/mqtt-notifications/config_schema.json index cd6c8830..fffa6239 100644 --- a/plugins/mqtt-notifications/config_schema.json +++ b/plugins/mqtt-notifications/config_schema.json @@ -69,11 +69,16 @@ "items": { "type": "string" }, - "default": ["homeassistant/ledmatrix/+"], + "default": [ + "homeassistant/ledmatrix/+" + ], "description": "Array of MQTT topics to subscribe to. Supports wildcards: + (single level) and # (multi-level)" } }, - "required": ["host", "port"], + "required": [ + "host", + "port" + ], "additionalProperties": false }, "display": { @@ -115,7 +120,11 @@ }, "minItems": 3, "maxItems": 3, - "default": [255, 255, 255], + "default": [ + 255, + 255, + 255 + ], "description": "RGB text color [R, G, B]", "x-advanced": true }, @@ -128,7 +137,11 @@ }, "minItems": 3, "maxItems": 3, - "default": [0, 0, 0], + "default": [ + 0, + 0, + 0 + ], "description": "RGB background color [R, G, B]", "x-advanced": true }, @@ -174,8 +187,7 @@ "4x6-font.ttf", "5by7.regular.ttf", "5x7.bdf", - "4x6.bdf", - "cozette.bdf" + "4x6.bdf" ], "default": "PressStart2P-Regular.ttf", "x-advanced": true @@ -190,14 +202,22 @@ "x-advanced": true } }, - "x-propertyOrder": ["font", "font_size"], + "x-propertyOrder": [ + "font", + "font_size" + ], "additionalProperties": false } }, - "x-propertyOrder": ["message_text"], + "x-propertyOrder": [ + "message_text" + ], "additionalProperties": false } }, - "required": ["enabled", "mqtt"], + "required": [ + "enabled", + "mqtt" + ], "additionalProperties": false } diff --git a/plugins/mqtt-notifications/manager.py b/plugins/mqtt-notifications/manager.py index 3cd1dfe2..1a08a50d 100644 --- a/plugins/mqtt-notifications/manager.py +++ b/plugins/mqtt-notifications/manager.py @@ -180,6 +180,20 @@ def _load_font(self): self.logger.error("Failed to load font %s: %s", font_path, e) return ImageFont.load_default() + @staticmethod + def _bdf_pixel_size(path): + """The pixel size a .bdf font declares, or None if it does not.""" + try: + with open(path, "r", encoding="latin-1") as handle: + for line in handle: + if line.startswith("PIXEL_SIZE"): + return int(line.split()[1]) + if line.startswith("CHARS"): + break # past the header + except (OSError, ValueError, IndexError): + return None + return None + def _load_element_font(self, element_cfg: Dict[str, Any]): """Resolve the message_text customization font, or None for the default. @@ -213,6 +227,19 @@ def _load_element_font(self, element_cfg: Dict[str, Any]): font = ImageFont.truetype(path, size) break except Exception as e: + # A .bdf is a bitmap face and exists at exactly one pixel + # size; FreeType rejects any other with "invalid pixel + # size". Asking for the configured size therefore failed + # for every bitmap face in the picker and fell back to the + # default, so choosing one appeared to do nothing. Retry at + # the size the file declares. + native = self._bdf_pixel_size(path) + if native is not None and native != size: + try: + font = ImageFont.truetype(path, native) + break + except Exception: + pass self.logger.warning("Could not load font %s@%s: %s", name, size, e) if font is None and not any(os.path.exists(p) for p in candidates): self.logger.warning("Font file not found: %s; using default font", name) diff --git a/plugins/mqtt-notifications/manifest.json b/plugins/mqtt-notifications/manifest.json index 2a163af6..804e2989 100644 --- a/plugins/mqtt-notifications/manifest.json +++ b/plugins/mqtt-notifications/manifest.json @@ -1,7 +1,7 @@ { "id": "mqtt-notifications", "name": "MQTT Notifications", - "version": "1.1.1", + "version": "1.2.0", "author": "ChuckBuilds", "description": "Display text or images from HomeAssistant via MQTT. Supports dynamic MQTT topics with wildcard support for flexible notification handling that interrupts the normal display rotation.", "category": "integration", @@ -18,6 +18,12 @@ "entry_point": "manager.py", "class_name": "MQTTNotificationsPlugin", "versions": [ + { + "version": "1.2.0", + "released": "2026-09-02", + "notes": "Load a bitmap font at its own pixel size, and drop one that has no font file. A .bdf exists at exactly one pixel size and FreeType rejects any other, so every .bdf in the font picker failed at the configured font_size and silently fell back to the default face -- choosing one appeared to do nothing. Bitmap faces are now retried at the size the file's PIXEL_SIZE header declares. cozette.bdf is removed from the picker: the file is shipped by neither the core nor the plugin, so it could only ever fall back. Same fix already landed for clock-simple, news and tide-display, which share this loader.", + "ledmatrix_min_version": "2.0.0" + }, { "version": "1.1.1", "released": "2026-08-15", @@ -46,7 +52,7 @@ "ledmatrix_min_version": "2.0.0" } ], - "last_updated": "2026-07-31", + "last_updated": "2026-09-02", "stars": 0, "downloads": 0, "verified": true, diff --git a/plugins/youtube-stats/manager.py b/plugins/youtube-stats/manager.py index e11e0619..7aebd10f 100644 --- a/plugins/youtube-stats/manager.py +++ b/plugins/youtube-stats/manager.py @@ -89,6 +89,20 @@ def _rgb(value, default): else: self.logger.info("YouTube Stats plugin disabled") + @staticmethod + def _bdf_pixel_size(path): + """The pixel size a .bdf font declares, or None if it does not.""" + try: + with open(path, "r", encoding="latin-1") as handle: + for line in handle: + if line.startswith("PIXEL_SIZE"): + return int(line.split()[1]) + if line.startswith("CHARS"): + break # past the header + except (OSError, ValueError, IndexError): + return None + return None + def _load_element_font(self, element_cfg: Dict[str, Any]): """Resolve an element's configured font, or None for the default. @@ -120,6 +134,19 @@ def _load_element_font(self, element_cfg: Dict[str, Any]): font = ImageFont.truetype(path, size) break except Exception as e: + # A .bdf is a bitmap face and exists at exactly one pixel + # size; FreeType rejects any other with "invalid pixel + # size". Asking for the configured size therefore failed + # for every bitmap face in the picker and fell back to the + # default, so choosing one appeared to do nothing. Retry at + # the size the file declares. + native = self._bdf_pixel_size(path) + if native is not None and native != size: + try: + font = ImageFont.truetype(path, native) + break + except Exception: + pass self.logger.warning(f"Could not load font {name}@{size}: {e}") if font is None and not any(os.path.exists(p) for p in candidates): self.logger.warning(f"Font file not found: {name}; using default font") diff --git a/plugins/youtube-stats/manifest.json b/plugins/youtube-stats/manifest.json index 16b7b532..58aecb6d 100644 --- a/plugins/youtube-stats/manifest.json +++ b/plugins/youtube-stats/manifest.json @@ -1,7 +1,7 @@ { "id": "youtube-stats", "name": "YouTube Stats", - "version": "1.1.1", + "version": "1.2.0", "author": "ChuckBuilds", "description": "Display YouTube channel statistics including subscriber count, total views, and channel name on your LED matrix", "category": "social", @@ -33,6 +33,12 @@ } ], "versions": [ + { + "version": "1.2.0", + "released": "2026-09-02", + "ledmatrix_min_version": "2.0.0", + "notes": "Load a bitmap font at its own pixel size. A .bdf exists at exactly one pixel size and FreeType rejects any other, so every .bdf in the font picker failed at the configured font_size and silently fell back to the default face -- choosing one appeared to do nothing. Bitmap faces are now retried at the size the file's PIXEL_SIZE header declares. Same fix already landed for clock-simple, news and tide-display, which share this loader." + }, { "version": "1.1.1", "released": "2026-08-22", @@ -61,7 +67,7 @@ "ledmatrix_min": "2.0.0" } ], - "last_updated": "2026-07-31", + "last_updated": "2026-09-02", "compatible_versions": [ ">=2.0.0" ]