The slicer writes an external spool as -1 in ams_mapping, the same as an
unused filament, and names it only in ams_mapping2. Save AMS mapping read
the flat list alone, so dispatch sent the filament as unmapped and an H2C
stopped with 0700-8012 before the first layer. Resolve it through
ams_mapping2 to the global tray (254/255, by the target's nozzle count),
as the usage capture does since #3166.
Users with library:read_own saw every folder anyone had made, and the
folder counts and activity times included other users' files. Folders
now have an owner. A read_own user sees their own folders, shared ones,
folders holding their files and the parents leading there, and adds
files only to their own and shared folders. Admins can share a folder
with everyone. Owners can rename their folders and delete them when
everything inside is theirs.
On upgrade, a folder whose files all belong to one user becomes that
user's; every other folder is shared, so it stays visible as before.
Each hourly energy snapshot records the price, and the energy used until the
next one is costed at it. Prints and the Statistics energy cost follow the
price over time instead of applying one price after the fact. The price can
come from a Home Assistant sensor, read hourly and at print start and end.
Energy multipliers now accept any value above zero, for plugs that report
watt-seconds.
Groups can be limited to printers and whole locations, managed on the new
Printer access page, and jobs can wait for staff review before they print.
Merging onto the current dev:
- The Printer Locations page keeps to the same access rules: a rename keeps
its groups' access, deleting a granted location or moving printers into or
out of one needs an admin and drops the stale grant, and a user limited to
some printers sees and changes only their own locations.
- The overlay logo checks its token without a printer, which the stricter
overlay check now needs and the logo route doesn't have.
The MakerWorld page becomes Model Sources, with a MakerWorld tab and a
Manyfold tab. Bambuddy signs in to a self-hosted Manyfold with an OAuth
application (public read scopes), lists and searches its models with their
previews, and imports 3MF, STL and STEP files into the library, from where
they are sliced and printed like any other file.
New permissions manyfold:view and manyfold:import are granted once to the
groups that hold the matching MakerWorld permissions. The client secret is
never returned and is left out of GitHub backups. The Manyfold URL and every
redirect pass the LAN-service URL check. Old /makerworld links open the
MakerWorld tab.
Several files import at once: tick files in a model, or Select all, then
Import selected or Import all; or tick models in the grid and import all
their printable files. Files import one after another, failures don't stop
the rest, and a summary reports the outcome.
A printer kept dark between uses showed a black live view and sent black
notification photos (#1655). Settings → Camera can turn the light on for
all printers or for selected ones, for the live view, notification
photos, the finish photo and the snapshot API that Home Assistant uses,
and turns it off 15 seconds after the last use. It only turns off a
light it turned on, and switching the light by hand hands it back.
Snapshots wait an adjustable delay after the light comes on. Timelapse,
the Obico check and the in-print frame bank don't switch it, since they
would make it flash.
The camera card no longer lists every printer: external cameras are
added from a searchable printer picker and only those printers are
listed, so large farms get a short page.
The 3MF pass skipped a tray once it had charged it, so a second slicer
filament mapped to the same tray was never deducted, and a single-filament
print that switched trays and came back lost its last segment. Deduplicate
by slicer slot instead. Trays covered before the pass, and trays only
guessed by position that were already charged, are still skipped; charged
trays are still reported to the remain% fallback.
OrcaSlicer's Sync filaments finds a slot's preset by the slot's filament
ID alone. The Configure dialog looked up an Orca profile's ID in the
browser and quietly sent the generic for the material when that came back
empty, so Orca custom filaments reached the slicer as Generic and the log
showed nothing. configure now resolves the ID on the server from
orca_profile_id, follows inherits to the parent profile or the Bambu
filament it was copied from, logs the outcome and reports a fallback,
which the dialog shows as a warning.
Slot presets also store the filament ID they were written with, so the
slot card and the Configure dialog stop showing a preset once the slot is
changed from OrcaSlicer's Device tab or the printer.
Re-configuring a slot for another filament no longer carries over the old
filament's active K-profile, which switched the slot back to the old
filament.
A group can now be limited to a set of printers. Its members see and
control only those printers. Every other printer answers 404, as if it
didn't exist.
- Groups gain restrict_printers and a group_printers table (migration
for SQLite and Postgres). A user's printers are the union of their
limited groups. Groups without the flag don't limit anything, a user
in no limited group keeps every printer, and admins see all of them.
- core/printer_scope.py holds the scope. RequestPrinterScope and
RequirePrinterPermissionIfAuthEnabled apply it to routes: printer
routes, camera, queue and batches, archives, projects, stats, print
log, pipeline runs, inventory and Spoolman assignments, maintenance,
smart plugs, scheduled drying, firmware and Obico status.
- API keys, camera stream, Cam Wall, overlay and WebSocket tokens carry
the printers of whoever created them. WebSocket broadcasts are
filtered per connection, and the filtering fails closed.
- Scheduler: "Any <model>" jobs stay on their owner's printers. A job
pinned to a printer its owner lost waits with a reason. Callers with
no user identity and limited printers must queue to a specific printer.
- Group editor: new Printer access section, translated into all 15
locales. Saving a system group no longer resends unchanged
permissions, which the backend refused.
- Expired messages stay readable under a collapsed "Earlier" section, as
long as the feed keeps them (12 months, at most 50). They never count as
unread or raise a banner; withdrawn ones are gone everywhere.
- Each message is one row (level, date, title) that opens in place.
Unread ones carry a dot and a New chip, and opening one is what marks
it read; the banner's Read more opens the panel on its message.
- A fetch that brings a newer feed broadcasts an empty
announcements_changed event, so open pages show the new dot and banner
without a reload.
- The sidebar entry is a megaphone icon with an unread badge, in the
footer row left of System. Footer icons are 32px with no gap so seven
fit an expanded sidebar; with authentication on, logout used to wrap
onto a line of its own.
Fetch a signed feed.json from the public bambuddy-notifications repo on
GitHub at startup and every 6 hours. Nothing about the install is sent;
targeting (version, beta channel, install type) is decided locally.
- Ed25519 against a key built into the app; an older serial is refused so a
withdrawn message can't come back. The feed replaces the stored list, and
a failed or rejected fetch keeps the last good one.
- Sidebar entry above System with an unread count, a slide-over list, and a
banner for unread important/critical messages. Read state per user.
- Admins by default; Settings > General > Updates can show them to all
users or switch them off, which also stops the fetch.
- Plain text only; links to github.com and bambuddy.cool only.
ams_get_rfid was published and reported as success without reading the
printer's answer. X1Plus on base 01.08.02.00 answers FAIL / ERROR STATE,
so the user saw "Refreshing" and the K profile was re-applied to a slot
that was never read.
- Wait for the ams_get_rfid answer (matched by sequence_id).
- On a refusal from an idle X1/P1/A1, send the legacy M620 R<ams*4+slot>
gcode Bambu Studio uses for those models, AMS units 0-3 only. Never
during a job, never on newer-protocol models; printers that accept
ams_get_rfid never see it.
- Refused: the route returns 400 with the printer's reason and no PA
re-apply is scheduled. No answer still counts as accepted.
- Log the answers at INFO so refusals reach support bundles.
lldap and OpenLDAP's memberof overlay omit memberOf from "*", so every
lldap login fell through to the default group. Request memberOf by name
when the schema defines it, and on non-AD directories also search the
directory root for groupOfNames/groupOfUniqueNames entries listing the
user, since groups often sit outside the user search base and the
overlay tracks only one group class.
Also: skip ldap3's anonymous schema read after StartTLS, which AD and
Samba AD reject, so StartTLS works there; reword a server's StartTLS
refusal with an LDAPS hint; stop the bundle sanitizer masking part of
an OID as an IP; skip the sync right after auto-provisioning so the
default-group warning logs once.
Spoolman keeps a full spool's net weight on the spool (initial_weight)
and falls back to the filament's weight, but Bambuddy read only the
filament, so a 250 g spool of a 1000 g filament showed and synced as
1000 g. The list, weigh, AMS sync, SpoolBuddy scale, remain-% tracking,
fill bar and cost now share one lookup. Create writes initial_weight,
and a label-weight edit writes it instead of patching or duplicating the
filament. The spool form's cost per kg is converted at the spool's size
to and from Spoolman's per-spool price.
C11 is the P1P, C12 the P1S, C13 the X1E and N7 the P2S, as the virtual
printer and a real P1P 3MF already say. The frontend map had them shifted,
so discovery pre-filled a P1S as a P1P and an X1E as a P2S. The backend
map read C11/C12 as X1C/X1 and lacked N7, the firmware check sent C13 to
the P2S line, and the capability lists never matched BL-P001 because
their lookup strips the dash.
-----
Post work PR #3134
The changelog named Settings -> Print Queue -> Auto-Drying; the toggle
lives in the Queue Auto-Drying card under Settings -> Workflow. The
scheduler comment and a test docstring called print_drying a permission
overlay rather than a trigger, but since #1816 it starts mid-print
cycles on its own regardless of queue state.
POST /notifications/app-message delivers an app's message to every channel
with the new "Messages from connected apps" switch on (off by default),
through quiet hours, the digest and the log. API keys need the new "Send
notifications" permission, and their owner notifications:update; plain text,
http(s) links, 20 messages a minute per key. The electricity-price door and
this one now share one scoped-key check. /queue?batch=<id> opens and
highlights one batch order.
Print commands carry the external spool as -1 in the flat ams_mapping
(the firmware rejects 254/255 there) and the real target only in
ams_mapping2. We captured only the flat list, so external-spool prints
looked unmapped and the usage tracker's position-based fallback
charged them to the first loaded AMS tray.
- Resolve external spools from ams_mapping2 when capturing a
project_file (dual-nozzle keeps 254/255, single-nozzle -> 254)
- Tracker: an explicit -1 no longer falls back to a positional tray;
a mapping naming no tray for any used slot defers to tray_now
- Keep the #1822 H2S tray_now override working with resolved mappings
fix(#1898): keep Telegram link previews, and keep the outcome prompt's failures its own
Four follow-ups to the post-print outcome confirmation merged in #3047.
Telegram: link previews were switched off for every message rather than
only for the outcome prompt, so a print_complete template carrying
{finish_photo_url} lost its photo preview whenever the photo was too
large to attach. _send_telegram now takes link_preview, and only the
prompt turns it off, as the Slack unfurl change already did.
Archives: Reset left the new Unconfirmed filter on, so the list stayed
narrowed and the button seemed to do nothing.
Print start: when the external-print check hit a failed statement, it
rolled back the caller's whole transaction, which expired the printer
and the just-created archive; on an async session the next read of
either raises, and the start notification, energy reading and timelapse
baseline were skipped. The check's reads now run in a savepoint, and a
failed flag write reloads the archive and printer after its rollback.
Print complete: a failed outcome prompt left the notification session
needing a rollback, so the per-user print email sent on it next failed
too. The dispatch now rolls back on failure.
The support bundle gives each printer's connection diagnostic 15 s and
discarded the whole result on overrun, recording only "timed_out". A
14-printer farm's bundle carried that marker for every printer and
nothing else, so it could not say which check was slow.
run_connection_diagnostic now keeps an optional progress dict current
(finished checks + the step in flight). On timeout the snapshot records
stalled_in, elapsed_s and the checks that completed.
Follow-ups after merging PR #3128 (with the #2990 preview work):
- PDF preview failed on every browser without
Map.prototype.getOrInsertComputed (Chrome < 145, Firefox < 144,
older Safari): "This file cannot be previewed" for any PDF. Load
pdf.js's legacy build, which bundles the polyfills for page and
worker, and bundle the worker through Vite (?worker&url) so
build.target lowers its class static block for Safari 16.0-16.3.
The browser-baseline check only scanned .js output and never saw
the copied .mjs worker; it scans .mjs too.
- PDF grid thumbnails only existed after someone opened the preview.
Page one is now rendered server-side with PDFium (pypdfium2, new
dependency, prebuilt wheels for every shipped platform) on upload,
ZIP extraction and external-folder scans; Generate Thumbnails
backfills PDFs as well. Renders are serialised behind a lock
(PDFium is not thread-safe), run off the event loop, and scale
from the page size so a huge MediaBox cannot allocate a huge
bitmap. Unreadable PDFs land without a thumbnail and keep the
browser fallback.
- A large STEP file takes over a minute to mesh in the browser and
showed only a spinner. STEP loads now show "Converting STEP
model... N s" and a note that large files can take a minute or
more, in all 15 locales.
- Drop the two occt-import-js "externalized for browser
compatibility" build warnings (path/crypto are only required in
its Node branch); any other externalization still shows.
The Print / Schedule dialog's filament mapping is where the colour a slice
asked for is compared against the colour actually loaded, and only the
left-hand side of that comparison had a swatch. The slot, and every slot in
its dropdown, was text -- and the text cannot be trusted: a slot's colour name
is resolved from the Color Catalog or, failing that, from hue, so a
third-party beige is announced as "Orange". A "Color mismatch" warning then
gives no way to tell a real mismatch from two names for the same hex without
opening the printer card in another tab, which on a farm swapping twenty or
thirty non-Bambu colours between machines is a check made many times a day.
Each slot now carries its colour and its hex, and the slot whose colour is
exactly the one the slice asked for is ticked. This works for a slot bound to
an inventory spool and for one configured through Configure Slot or on the
printer itself: the second kind has no inventory row behind it, and the
printer's own tray colour is then what draws. A bound spool contributes what a
tray record cannot -- SlotSpoolIdentity gains extra_colors and effect_type, so
a two-tone or glittery spool draws as itself rather than as its base colour.
The same treatment goes to the filament-override picker used for model-based
assignment. It is the same choice on the other dispatch path, and leaving it
text-only would have made one decision read two ways.
Both controls stop being <select>s to do it, because an <option> renders text
and nothing else. SlotPicker keeps what the select gave for free -- arrow,
Home/End, Enter and Escape keys, listbox semantics, and the border colouring
that encodes match, same-type-different-colour and not-loaded -- and is
portaled with position:fixed so it is not clipped by the dialog's own scroll
container, flipping above the row when there is no room below.
Bambu sends two humidity fields that are not the same quantity.
humidity_raw is relative humidity in percent; humidity is a 1-5 drop
index, and it runs the other way -- OpenBambuAPI's push_info sample
pairs "humidity:30%" with "humidity_idx:4", so a high index means dry
where a high percentage means wet.
Four call sites used the index whenever no percentage arrived. A unit
sending only the index therefore rendered as "2%" in the green band
while being the second-wettest of the five steps, charted an average of
index values as a percentage, and sat under every humidity threshold
forever, since no index can reach one -- the alarm and auto-drying could
not fire for such a unit at all.
- utils/ams_humidity: one leaf helper, a percentage or None. The index
is never converted; None is what every caller already handles.
- routes/printers, printer_manager, print_scheduler, main, bambu_mqtt:
all five readings go through it, so the card, the websocket, the
chart, the alarm and auto-drying cannot answer differently.
- main: a unit that reports the index and no usable percentage says so
once per unit in the log, with its firmware versions requested. No
supported printer is known to do this, and "known" is doing work
there -- the alternative is a card that goes blank with no trace.
Three faults found while checking what else those paths touched:
- main: humidity_raw=float(x) if x else None stored NULL for a numeric
0% while writing 0.0 to humidity on the same row.
- main: that same expression was unguarded, unlike the parse above it,
so a non-numeric humidity_raw raised inside record_ams_history and
aborted the pass for every printer, not just the one that sent it.
- routes/ams_history: the averages were tested for truthiness, so a
window averaging exactly 0 reported no average while the min and max
beside it reported 0.0.
An affected unit now reports no humidity rather than a number that means
the opposite: the indicator is hidden, the chart leaves a gap, the alarm
and auto-drying skip the unit. Temperature is untouched. Auto-drying's
outcome is unchanged either way -- an index could never cross the
threshold -- so only the intent moves.
No supported printer is known to be affected; the report came from an
install running X1Plus, which Bambuddy does not support. Verified
against 7927 recorded samples from seven AMS units including an AMS-HT:
not one used the fallback. Two percentages that did fall through to the
index no longer do -- a reading with a decimal point, and "38.0", which
int() rejected.
A slicer holding the CA of two Bambuddy installs could only connect to
one of them. Each CA worked on its own; together, one stopped, with the
generic "Connect ... failed! [SN:..., code=-1]" that an install whose
CA was never imported gives.
Every install signed as exactly CN=Virtual Printer CA. A slicer's trust
store is a flat list of certificates and OpenSSL resolves an issuer by
Subject DN: it takes the first authority whose name matches and fails
the chain when that one turns out not to have signed the certificate,
rather than trying the next match. Whichever CA landed second in the
file lost -- decided by nothing but the order they were appended in.
Reproduced with openssl verify against a bundle holding two CAs: the
first leaf verifies, the second fails with "certificate signature
failure".
- certificate.py: a newly generated CA takes a suffix from its own key
identifier (CN=Virtual Printer CA D55808BE) and publishes that
identifier, which the printer certificate points back at.
- Existing CAs are untouched, so nothing has to be re-imported. A
printer certificate signed by one keeps exactly the shape it has
today: the authority key identifier is added only when the CA has an
identifier to name.
- tests: unique names per install, the identifier reaching the leaf,
an existing CA being reused unchanged, and both chains verifying
through openssl from a single trust store.
The collision goes away as soon as one of the two CAs is newer than
this change. Two installs that both predate it still collide until one
has its bbl_ca.crt/.key deleted and regenerated, which is a re-import
for that one -- documented in the wiki.
Reported by @Steven-Pierce.
The per-event Priority header from #990 never reached ntfy. The dialog
builds its rows from the provider's event toggles and stores the map
under those names -- on_print_complete, on_print_failed -- while every
sender is called with the bare event name, print_complete. The lookup
missed for all 18 events the dialog offers, so every notification went
out at the ntfy server's default with the configured priority sitting
untouched in the database.
Both ends looked healthy, which is why it shipped. The stored config
held exactly what was set, and the tests were green because they called
_send_ntfy directly with the prefixed name -- the one spelling the
running system never produces.
- notification_service.py: accept either spelling, bare first, so
existing configs keep working and nothing needs migrating.
- schemas/notification.py: document both key forms, and which one the
UI writes.
- tests: use the bare names, and add one that runs from a finished
print through to the outgoing request. Without the fix it fails on a
header dict holding only Title, which is the assertion that was
missing.
The daily digest is unchanged: send_digest sends with no event_type at
all, and the dialog offers no priority for it -- it is one message for
several events.