diff --git a/CHANGELOG.md b/CHANGELOG.md index d8cc9d598..9228d71db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ All notable changes to Bambuddy will be documented in this file. ## [1.2.6b1] - Unreleased ### Added +- **Home Assistant sensors can be bound to a storage location, so a drybox reports its own humidity (#2824, requested and contributed by @MagicMelody84 in #2827)** — A temperature, humidity or battery entity can be attached to any storage location, and its reading shows where the filament in that location is already being looked at: on the spool cards in Inventory, and in three optional Inventory table columns that are off by default. Values can be colourised against the location's own thresholds, so a rack of dryboxes reads at a glance rather than one number at a time. Each binding carries its own alert rule — above/below for a numeric sensor, on/off for a binary one — and can raise a notification when it crosses into that state, on the edge only: a drybox that was already too humid when Bambuddy restarted has not just become too humid, and re-announcing every pre-existing alert on every restart is how people learn to ignore the alert. That notification is its own event with its own provider toggle and template rather than sharing the printer sensors' one, because a provider can be scoped to a single printer and a location has no printer to scope by — sharing the column would have delivered drybox alerts to a channel deliberately narrowed to one machine, with no way to have one without the other. A background poller reads the bound entities on a configurable cadence, defaulting to two minutes and floored at one, and polls before it sleeps so a restart does not leave every card blank for a full interval. Nothing is polled for a location that has no sensor bound, and in table view nothing is polled at all while the three columns are hidden, so the installs that never touch Home Assistant — which is most of them — issue no extra requests. New bindings are seeded from defaults that live on the server, not in the browser, since they become the alert rule written onto each row and two admins binding sensors from different machines must not seed different rules; the colour choices stay per-browser, because those are only how one person likes to read the page. Home Assistant's `moisture` class is deliberately not offered: it is a binary wet/dry leak detector rather than a humidity percentage, and treating it as one put "wet" in a percent-formatted column and let it block the real hygrometer on the same location. One entity can be bound to a location once, enforced by a unique index rather than by the check alone, because that check is read-then-insert and two concurrent creates could both pass it; the upgrade collapses any duplicate that slipped in before the index existed instead of failing to build over it. An API key can read the bindings but not create or edit them, matching the printer bindings exactly. Translated in all 13 locales, with backend and frontend regression tests throughout. - **A fault's description is in the status response, so a client no longer needs its own copy of the table (#2926, proposed and analysed by @sadontsev)** — `HMS_ERROR_DESCRIPTIONS` has been in the backend all along and the status response never carried it, so every consumer that wanted to tell a user *why* a print halted resolved the same 853 codes from its own duplicate of the same sentences — this repo's Python table, the frontend modal's, and at least one third-party iOS client whose catalogue exists purely because the server would not say. Each aged separately, and a push relay watching a printer could only manage "your printer needs attention" while the server already knew it was "Filament ran out. Please load new filament." `hms_errors[]` entries now carry `description`, defaulting to null so a client that has never seen the field is unaffected. It is resolved once, where the fault is parsed, rather than at the boundary that happened to prompt the request: there are three separate serializers of a fault — the status response, the WebSocket broadcast, and the print-completion payload the queue's failure reason is built from — and adding it to only the first would have delivered half the feature to a relay watching the stream, which is the likelier consumer. The queue's failure reason now quotes the same sentence instead of resolving the code a fourth time, and the notification path reads it rather than re-deriving its own. Resolving in one place is also what makes the three unable to drift, which is pinned by a test that asserts they agree. Resolution is exactly what the codebase already did, verified rather than assumed: an 8-char `print_error` is the catalogue's `MMMM_EEEE` key split in half, and a 16-char `hms[]` identifier is tried whole and then collapsed to its first and last groups, which is how the notification path, the queue's failure-reason helper and the frontend modal have always resolved those. The collapse is lossy — #2728 counts 65 documented faults falling onto `0300_0001` alone — and it is kept rather than tightened here because refusing it would not read the same data more strictly, it would stop describing faults that are described today and leave this field null while the UI shows text for the same fault. Narrowing it belongs with #2728, where both key spaces can move together. A fault the catalogue does not cover reports null and is still reported in full; `full_code` identifies it either way. The equivalence is pinned by a test that checks every catalogue code in both fault shapes across all three alert levels, so a future change to the lookup cannot silently stop notifications from firing. The text is English only and unlocalized, which the schema says next to the field. The frontend keeps resolving its own text for now; switching it over would change what `filterKnownHMSErrors` counts across eight call sites, which is #1840 and #2728's argument rather than this one's. - **A virtual printer can be told which address to advertise, so uploads work on Docker bridge networking (#2930, reported and diagnosed by @sebimarkgraf)** — `VIRTUAL_PRINTER_ADVERTISE_ADDRESS` sets the address written into the MQTT status that BambuStudio and OrcaSlicer read their FTP upload destination from. It exists for deployments where that address is not one of the container's own interfaces: on bridge networking the virtual printer is reached on the host's LAN IP but binds a private one like `172.24.0.2`, and that private address is what the slicer was handed — so it opened an FTP connection to an address that does not exist on its network, which is the upload stalling around 10% with "Failed to send" that the troubleshooting page has been describing as a limitation with no fix. Set it to the address slicers use, alongside the `VIRTUAL_PRINTER_PASV_ADDRESS` that already existed for the passive-data channel. The log line that arms the rewrite names its source, so `(VIRTUAL_PRINTER_ADVERTISE_ADDRESS)` versus `(bind_address)` says whether the variable reached the container. A value that is not a dotted-quad IPv4 is refused with one warning naming it and the address that would have been used before is used instead — deliberately, because refusing to rewrite at all would put the *real printer's* IP back in front of the slicer, which is the leak this path exists to close and strictly worse than the wrong local address. `0.0.0.0` counts as unset, and surrounding whitespace is tolerated for the sake of values pasted into a compose file. This is an environment variable rather than a change to how the advertised address is resolved, and that was the decision worth making carefully: the virtual printer already has a "Network Interface Override" field, but it feeds SSDP and the certificate's SAN list only, and reading it here would have moved the upload destination on every install that has one set — the multi-NIC, VLAN and Tailscale setups, which are the ones most likely to have been arrived at by hand and the least likely to survive being second-guessed. Unset, nothing about the resolution changes, which is pinned by a test. Host and macvlan networking still need none of this and remain what Virtual Printer is developed against; the variable removes one blocker rather than making bridge mode equivalent, and the wiki now says so in the three places that previously stated the host address could not be discovered at all. - **Printer file downloads can be selected in ranges and print-history videos can be downloaded (#2850, requested and contributed by @logikal in #2853)** — The printer file browser's multi-select download now prepares large selections on the app data volume instead of buffering them in server and browser memory, uses per-file compression (videos stored, G-code/3MF compressed), reports partial results and preparation progress, supports cancellation, rejects over-large or under-space selections, and preserves the legacy API contract. Shift-click selects a contiguous visible range and hidden selections are discarded when navigating or filtering. Print History now offers attached timelapses, matching printer timelapses, and `/ipcam` chunks when available; offline or unreadable storage is distinguished from an empty directory. Download tokens are single-use and resource-bound, API-key printer allowlists are enforced, FTP short reads are rejected, and abandoned staging is pruned. Translated in all locales; wiki updated. Covered by backend and frontend regression tests. @@ -12,6 +13,8 @@ All notable changes to Bambuddy will be documented in this file. - **The K value is on the AMS slot itself, not only in the popover (#2532, requested and contributed by @gyrene2083)** — Reading back a slot's pressure-advance value meant hovering it: the K factor lived in the filament popover alone, so checking whether a calibration had actually taken across four slots was four hovers, and comparing two of them side by side was not possible at all. Every slot card now carries the value under the material name, the way Bambu Studio shows it per slot — on regular AMS units, on AMS-HT, and on the external spool of a dual-nozzle machine. Only a value the printer actually reported is shown: a loaded but never-calibrated slot stays blank rather than inheriting the 0.020 that fills the popover's own field, and a slot the firmware reports as exactly 0 counts as uncalibrated the same way the stored K-profiles do. The label is shortened to **K** with the full localized name on hover, because "K Factor", "K-Faktor" and "Facteur K" ate the value itself — the whole point of the line — on cards under about 350px, and the figure is set in tabular numerals so it measures the same in Safari as in Chromium. Where one slot of a unit is calibrated and its neighbours are not, the neighbours hold the same row open so the fill bars stay level across the card. ### Changed +- **Home Assistant sensors moved out of the Smart Plugs settings tab into their own (#2824)** — Sensors are things you read and plugs are things you switch, and with storage-location bindings joining the printer ones the two no longer belong on one page. Both now live under **Settings → Sensors**; nothing about the printer bindings themselves changed, only where they are. The template that carried the printer alert is renamed from "Home Assistant Sensor Alert" to "Printer Sensor Alert", along with the toggle and badge labels that name it, because once a storage-location alert existed beside it the old name no longer said which one it was; the rename only touches templates still holding the old default name, so one that was edited keeps whatever it was called. A battery-class printer sensor now draws a battery icon instead of the generic gauge — the printer map never had an entry for it and the shared one does, which reads as the omission it was rather than a choice worth preserving. +- **Storage locations sort the way they are named (#2824)** — The locations list was ordered by `ORDER BY name`, which puts "Drybox 10" between "Drybox 1" and "Drybox 2". It is now sorted on the numbers inside the name, so a rack numbered past nine reads in rack order everywhere the list appears. - **The Watchtower we recommend for daily builds is the maintained fork (#2917, reported by @CamelT0E)** — The daily-build instructions in the README, on Docker Hub and in every daily prerelease pointed at containrrr.dev/watchtower. That project has been archived and read-only since December 2025 and its last release, v1.7.1, is from November 2023, so anyone following the recommendation was being handed a container with Docker socket access that had not received a fix in over two years. Development continues in Nicholas Fedor's fork, which ships as `nickfedor/watchtower` and released v1.21.0 this month. All four references now point at watchtower.nickfedor.com and name the image, including the release-notes template in `docker-publish-daily-beta.sh` that produced the screenshot in the report — the READMEs alone would have left every future daily prerelease repeating the dead link. Existing images keep working; only the recommendation changed. - **The Windows installer build is split in two so a signing request can wait for a human (SignPath Foundation)** — Release tags are Authenticode-signed through the SignPath Foundation OSS programme, and the production certificate does not sign on demand the way the self-signed test certificate does: every request has to be approved by hand in the SignPath UI, because the Foundation verifies what is being signed and which build it came from. The submitting action waits for that approval with a default timeout of 600 seconds, which is ample when the test policy approves automatically in seconds and far too short once the wait is a person noticing a tag went out. A tag pushed at night would have failed the run ten minutes later with the installer already compiled and thrown away. The compile now ends in its own job that uploads the unsigned artifact and stops; a second job downloads it, signs it, and does the release-facing work, with the wait raised to an hour. Because the artifact is uploaded before the wait begins and is addressed by id, a missed approval window is recovered by re-running the second job alone rather than rebuilding the installer — which is the reason to separate them rather than simply raise the timeout in place. The second job runs for unsigned builds too, so the daily prereleases that are deliberately left unsigned to preserve the signing quota keep going out through exactly one set of alias, artifact and release steps. The property that matters is unchanged and now recorded next to the steps that depend on it: none of the alias, upload or release-attach steps carry `always()`, so GitHub skips all three when signing fails or times out, and an unsigned `.exe` cannot reach a release. Nothing about the signed output changes, and the restructure behaves identically under the test policy — the request simply completes immediately instead of waiting — so it can be proven green before the production certificate arrives.