Commit Graph

199 Commits

Author SHA1 Message Date
884bdfc0c3 fix(ams): log rejected multiColorBox setInfo with the triggering request
The printer's failure payload for a rejected slot assignment carries
no slot/type/color info of its own, only an opaque
["multi_color_box", [{"filaments": {"id": N}, "id": box_id}]] shape -
useless for diagnosing why an assignment was rejected.

Remember the last setInfo request (_last_ams_set_request) and log it
alongside the failure so bridge logs alone can answer "what exact
type/color was sent, and to which slot" without asking the user to
dig it out manually or run extra reproduction steps.
2026-07-24 14:01:04 +02:00
8f14580e30 fix(ams): don't crash on rejected multiColorBox setInfo (Issue #100)
The printer replies with state="failed" and data as a 2-element list
(["multi_color_box", [...]]) instead of the usual dict when it rejects
a manual ACE slot filament assignment (e.g. custom-RFID/third-party
material). _on_multicolor_box called data.get(...) unconditionally,
crashing with AttributeError and silently dropping the report -
including the regular slot-state update that would otherwise follow.

Add a state="failed" guard plus a defensive isinstance check, and
surface the rejection via _state["last_ams_set_error"] so the caller
of handle_api_ams_set_slot's optimistic cache update isn't left
showing a false success.

Note: this fixes the crash, not necessarily the underlying rejection
itself - the printer/ACE may still refuse assignments for material
types it doesn't recognize.
2026-07-24 13:57:17 +02:00
20daa6b6b8 Merge PR #98: fix(ams) don't map AMS placeholders to empty trays
Printing via OrcaSlicer "Upload and Print" failed (or fed the wrong
spool) when a slot below the used filament was empty. Gap placeholders
in _build_auto_ams_box_mapping now point their ams_index at a loaded
tray instead of the gap's own empty index. All-slots-full path
unchanged.
2026-07-24 13:48:08 +02:00
2f222a0b93 fix(ams): point gap placeholders at a loaded tray, not an empty one
Printing via OrcaSlicer "Upload and print" failed or fed the wrong spool
when the slot below the used filament was empty (e.g. Filament 4 with slot
3 empty); all-slots-full worked.

_start_print -> _build_auto_ams_box_mapping keeps the AMS mapping positional
(entry N = TN) by inserting a placeholder at each gap. The placeholder's
ams_index pointed at the gap's own index, which for an empty slot references
a physically empty tray. The printer rejects a mapping entry aimed at an
empty tray even for a tool the GCode never calls, so the print broke.

Point gap placeholders (ams_index/color/material) at a definitely-loaded
fallback tray (the highest loaded slot) instead. Positional alignment is
preserved; the all-full path is unchanged.

Adds tests/test_auto_ams_box_mapping_empty_slot.py (invariant: every mapping
entry references a loaded/status-5 tray).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 18:31:10 -07:00
6e72346129 fix(ams): support multiple daisy-chained ACE units (Issue #95)
ace_direct mode (no toolhead buffer) only kept the first reported ACE
unit and silently dropped any others. Affects e.g. the Kobra S1 with two
ACE Pro units - the dashboard and OrcaSlicer sync only ever saw 4 of 8
slots. Global slot index is now box_id * 4 + local slot across all
units, matching the existing //4-%4 fallback in _global_to_box_slot.
Single-ACE (Kobra X) behavior is unchanged (unit id 0 -> same indices
as before).

feat(store): multi-select + bulk delete in the GCode browser (Issue #94)

Checkbox on every file card enters select mode; clicking anywhere on a
selected-mode card toggles it, existing per-file actions keep working via
stopPropagation. 'Select All' only affects the currently filtered/visible
files. Bulk delete is N parallel calls to the existing single-file DELETE
endpoint (no new backend route) with one confirmation dialog.
2026-07-19 18:44:46 +02:00
99bc1797c8 fix(camera): share one ffmpeg connection for /api/camera/stream
Manually integrated from PR #91 (Pavulon87) - the PR branch itself had
unresolved git conflict markers in handle_camera_stream from a stale
rebase against nightly, so it couldn't be merged directly.

/api/camera/stream previously spawned a dedicated ffmpeg process and
printer connection per HTTP client. The printer only tolerates a very
limited number of concurrent camera connections, so two simultaneous
viewers (dashboard + a Moonraker client, two browser tabs, ...) could
already exhaust that limit and cause intermittent 429/"stream
unavailable" failures.

Adds a third CameraCache fanout channel (mjpeg_subscribers), mirroring
the existing h264 pattern, so all /api/camera/stream consumers share one
ffmpeg process. Also carries over two related fixes found in the PR:
ensure_running() checked self._proc_* instead of self._task_*, leaving a
race window where two callers could each spawn a duplicate ffmpeg
process before the first task got scheduled; and reset() didn't cancel
the owning task, so a loop stuck in its exponential backoff sleep (up to
300s) wouldn't restart immediately.
2026-07-16 22:33:50 +02:00
e1b9480098 Merge branch 'pr92' into nightly 2026-07-16 21:37:09 +02:00
03089db6af fix: self._db did not exist, use self._store 2026-07-16 14:48:23 +02:00
cd4a8ce48e feat(dashboard): show printer pause reason on the dashboard 2026-07-16 14:42:48 +02:00
bf3f043888 chore: add missing PR #88 entry to nightly changelog
The stale-slot-profile-guard fix (walterioo, merged as bf94a8f) was never
mentioned in a nightly release body — it landed between two changelog
overwrites.
2026-07-16 11:36:09 +02:00
b9594d4a22 feat(dashboard): free drag+resize grid with custom presets (Issue #89)
Replace the fixed dashboard layout with a fully customizable GridStack.js
grid (12-col snap grid, vendored + inlined so it also works in OrcaSlicer's
embedded webview). Cards can be dragged, resized, hidden and rearranged;
layout persists per browser. Includes two built-in presets (Standard,
Wide desktop per the original Issue #89 proposal) plus the ability to
save/apply/delete named custom presets.

fix(camera): stream freeze after ~15-30min from non-monotonic FLV
timestamps — ffmpeg's realtime pacing stalls on PTS jumps in the printer's
stream. Fixed with -use_wallclock_as_timestamps on both ffmpeg call sites
(CameraCache._input_args, _run_h264_loop). Issue #90.

test: fix tests/conftest.py referencing a stale bridge/ subfolder path and
missing args.data_dir (pre-existing breakage, unrelated to this feature);
rewrite the two test_settings.py cases that still mocked the removed
_find_env_path from the old .env-based settings storage.
2026-07-16 11:34:34 +02:00
bf94a8f563 fix(filament): suppress stale slot profile when AMS material family changes
A per-slot filament profile override (config.ini [filament_profiles]) stores
only {vendor, name, id} and is sticky: swapping the physical filament updates
the AMS colour + type live, but the saved profile persisted. Loading yellow PLA
into a slot that held "KINGROON PETG Basic" kept showing/sending PETG in the
panel and the OrcaSlicer lane hint, and survived restarts (config.ini).

Resolve the effective profile per slot as the saved override only when its
material *family* still matches the loaded AMS material — PLA / PLA+ / PLA SILK /
PLA MATTE are one family, so within-family swaps never invalidate a valid
profile (guards against the earlier over-strict material compare). On a family
change the override is suppressed (slot falls back to the generic default) but
NOT deleted, so re-loading the original material reactivates it. Profile
material is resolved from the Orca filament library; unknown → never suppress.

Wired into the three resolution sites: handle_kx_filament_slots (panel),
_build_lane_data (OrcaSlicer AMS array), _build_mmu_object (gate_filament_name).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 23:09:16 -07:00
eda14db897 feat: pull image from registry instead of local build, ask stable/nightly
start.sh now pulls gitea.it-drui.de/viewit/kx-bridge:latest or :nightly
depending on user choice (interactive prompt or ./start.sh <channel>
argument), instead of building the image locally from source.
2026-07-07 15:11:19 +02:00
dcf852db49 chore: update nightly changelog 2026-07-07 01:26:29 +02:00
d2c92c2deb fix(settings): stale env vars survived bridge restart, reverting saved changes
_restart_bridge() cleared a hardcoded, manually-maintained list of env
keys before restarting — every setting added since (vibration_compensation,
host_ip, poll_interval, verbose_http_log) was missing from it. The old
process's env var therefore survived into the new process and silently
overrode the freshly written config.ini value, making toggles appear to
revert right after saving.

Fixed at the root: config_loader.CONFIG_ENV_MAPPING is now the single
source of truth for which env keys back which config.ini options, and
_restart_bridge() derives its cleanup list from it. A newly added
setting can't be forgotten here again.
2026-07-07 01:23:01 +02:00
b541aafc74 feat(settings): make the HTTP access log toggleable in the UI
Previous commit hard-disabled aiohttp's per-request access log via
setLevel(WARNING), with no way to turn it back on. Added a
'verbose_http_log' setting (default off) — toggle in Settings, persisted
to config.ini, applied on bridge start via _set_verbose_http_log().
2026-07-07 01:20:34 +02:00
f2f4447809 chore: quiet aiohttp per-request access log
Every HTTP request logged an INFO line via aiohttp's access logger,
drowning out the bridge's own logs given the frontend's 2s poll
interval. Raised the aiohttp.access logger threshold to WARNING.
2026-07-07 01:12:30 +02:00
a3deb33b97 fix(spoolman): status dot showed green when Spoolman was unreachable
/kx/spoolman/status only exposed 'configured' (server URL is set),
not actual reachability — health_check() ran once at boot and was
only logged, never surfaced to the API or rechecked afterwards.

Now the poll loop rechecks reachability every 30s, the status endpoint
returns 'reachable', and the frontend dot shows red + '(unreachable)'
when Spoolman is configured but not responding.
2026-07-07 01:08:41 +02:00
2e2061a269 fix(settings): poll_interval was saved but never applied
The setting was persisted to config.ini and returned by /api/settings,
but --poll-interval was never registered as an argparse argument and
POLL_INTERVAL was missing from config_loader's env mapping, so
self._args.poll_interval never existed. The poll loop used a hardcoded
3.0s wait regardless of the configured value.
2026-07-07 01:04:44 +02:00
e4bf2c9b95 chore: nightly changelog 2026-07-07 00:13:34 +02:00
aea6e457f3 refactor: dedupe upload response, log user-relevant swallowed errors
- OctoPrint upload response was built twice in handle_file_upload
- gcode_filaments cache load failure on dashboard reprint now logs a
  warning (silent failure would degrade the Issue #84 fix unnoticed)
- filament metadata backfill failures now log at debug level
2026-07-06 22:39:09 +02:00
0ae8ae59be refactor: extract shared print payload builder and skip-state reset
The print/start payload was duplicated in three places (upload path,
KX store, Moonraker API) and had drifted: the settings-based
vibration_compensation value was missing in the upload path. All three
paths now use _build_print_payload() and _reset_skip_state().
2026-07-06 22:35:56 +02:00
0322ade606 refactor: translate all logs, comments and API error strings to English
Logs are user-facing across all locales; comments and docstrings switch
to English for external contributors. No behavior change.
2026-07-06 22:33:25 +02:00
786fa08ca0 fix: dashboard reprint slot-shift, startup IP log, resonance compensation toggle
- Dashboard reprint now delegates to _start_print with gcode_filaments from DB
  so the used_paint_indices filter applies correctly (Issue #84)
- Startup log no longer shows 0.0.0.0 — actual LAN IP is displayed (Issue #86)
- New vibration_compensation setting: toggle in Settings UI activates resonance
  compensation before each print, follows exact auto_leveling pattern (Issue #85)
2026-07-06 13:54:44 +02:00
cd11542352 fix(filament): PLA-Varianten (PLA+, Silk, Matte) korrekt erkennen und an OrcaSlicer übermitteln
- _normalize_material() normalisiert Drucker-Typen auf kanonische Keys
- _TRAY_INFO_IDX erweitert um Silk/Matte/CF-Varianten und Schreibweisen
- _default_filament_name() mappt Varianten auf korrekte Generic-Profile
- Filament-Dropdown zeigt Hersteller-Profile der jeweiligen Variante
- Material-Buttons: PLA+, PLA Silk, PLA Matte hinzugefügt

Fixes #82
2026-07-02 21:54:42 +02:00
51f22947c5 Merge pull request #83: fix(spoolman): repair dead slot-map persistence + isolate it per printer 2026-07-02 21:36:59 +02:00
a39226d2dd fix(spoolman): show vendor name in the spool dropdown (was "[object Object]")
The print-dialog spool dropdown built its option label from
sp.filament.vendor (the whole vendor object) instead of
sp.filament.vendor.name, so options rendered as "#5 [object Object] PLA+
(1000g)". The sibling builder in the slot card already uses .vendor.name;
this aligns the two.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 22:21:59 -07:00
2a13f1f0dd fix(spoolman): repair dead slot-map persistence + isolate it per printer
The AMS-slot -> Spoolman-spool persistence never worked: KobraXBridge
referenced `config_loader` in both the load (__init__) and save
(handle_kx_spoolman_set_active) paths, but the module alias is `env_loader`
(kobrax_moonraker_bridge.py:32). The resulting NameError was swallowed by a
bare `except`, so the map was neither loaded on startup nor written on change
- it only appeared to persist.

The map also lived in a single global `[spoolman] slot_spools` key, so on a
multi-printer bridge two AMS units clobbered each other's mapping (same class
of bug as #74/#75 for filament profiles).

- config_loader: add list_spool_map()/save_spool_map(printer_id) using a
  per-printer `[spoolman_<id>]` section with read-fallback to the legacy
  global key, mirroring _filament_section/list_filament_profiles. The global
  `[spoolman]` section keeps server/sync_rate.
- bridge: load via config_loader.list_spool_map(self._printer_id); persist via
  save_spool_map(..., self._printer_id); surface failures via log.warning
  instead of a silent except.
- _build_mmu_object: emit real gate_spool_id from the per-printer map (was
  hardcoded [-1]*num_gates) so Happy-Hare/OrcaSlicer can show the bound spool.
- config.ini.example: document the [spoolman] section.
- tests: tests/test_spoolman_slot_map.py (per-printer isolation, persistence
  round-trip, server/sync_rate preservation, parser robustness).

Verified on a 2-printer bridge: after restart KX1 loads its spools and KX2
loads its own, isolated; a real multicolor print deducted per slot (white spool
1.02g vs 0.98g slicer estimate) against the correct printer's spools.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 21:42:40 -07:00
a16062f44f fix(ams): ams_box_mapping mit Platzhaltern für fehlende Paint-Indizes auffüllen
Drucker interpretiert ams_box_mapping als geordnete Liste (Eintrag N = TN).
Bei Drucken die T0 nicht nutzen wurden die Einträge um 1 verschoben,
sodass T2 (rot) auf den Slot von T3 (weiß) zeigte.

Fixes #78
2026-07-01 20:51:12 +02:00
4f5aa8d126 Revert "fix(ams): paint_index im auto-mapping auf global_index setzen statt enumerate-Zähler"
This reverts commit c313e014ad.
2026-06-30 23:01:03 +02:00
c313e014ad fix(ams): paint_index im auto-mapping auf global_index setzen statt enumerate-Zähler
Bei Multicolor-Drucken mit nicht bei 0 startenden Paint-Indizes (T2, T3...)
wurde paint_index als 0,1,2... statt als tatsächlicher GCode-T-Index gesendet.
Drucker hat dadurch die falschen Slots für die falschen Farben verwendet.

Fixes #78
2026-06-30 22:56:34 +02:00
6e9ba0672f fix(spoolman): Slot-Spool-Zuordnung in config.ini persistieren + beim Start laden; API-Feldname-Kompatibilität (slot_spools/slot_map) 2026-06-30 15:43:20 +02:00
44383fabec fix(docker): gcc + python3-dev für pycryptodome arm/v7 Kompilierung 2026-06-30 14:30:10 +02:00
48bec55611 feat(ci): linux/arm/v7 Platform zu Docker-Build hinzugefügt (Raspberry Pi 2/3 32-bit) 2026-06-30 12:21:20 +02:00
ab44e234be fix(ui): AMS-Spool-Dropdown bleibt offen während Poll-Tick (kein innerHTML-Reset bei fokussiertem Select) 2026-06-30 12:17:03 +02:00
74fc2ddab0 feat: color picker, unified UI styling, filament mismatch detection, Spoolman slot assignment
- Slot color editor: Pickr HSV color picker (offline, served from lib/),
  recent swatches (up to 16, localStorage), copy color from other slot
- Unified axes control panel: XY+Z merged, shared step size + custom mm input
- Language selector moved from header to Settings → Appearance
- Filament mismatch detection blocks Upload-and-Print on material mismatch,
  slot mapper opens automatically
- Spoolman spool-per-slot assignment in AMS status tab and Filaments settings
- Fix: Spoolman sync rate label — 0=end of print, not disabled (Issue #76)
- Fix: lib/ assets served by bridge static handler for offline use
- UI: global unified select + input styling, set-row labels match modal-field
2026-06-30 11:13:34 +02:00
771599be0c Merge pull request 'fix: isolate filament profiles per printer in multi-printer bridge' (#75) from walterioo/KX-Bridge-Release:fix/per-printer-filament-profiles into nightly 2026-06-30 10:21:56 +02:00
0e1d46ee7f fix: isolate filament profiles per printer in multi-printer bridge (#74)
Per-printer [filament_profiles_<id>] sections so configuring one printer no
longer overwrites another (read-fallback to the legacy global section keeps
single-printer setups unchanged). Dropdown/switch links now navigate to each
printer's own bridge_url. Adds pytest coverage and a CHANGELOG entry.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 07:13:10 +02:00
15e28244af fix(ci): jq static binary installieren falls nicht vorhanden 2026-06-29 09:57:46 +02:00
c1a3b9238d fix(ci): jq statt python3 für JSON-Serialisierung (python3 not found im Runner) 2026-06-29 07:03:09 +02:00
aad3833301 fix(ci): SIGPIPE bei head -1 in Pipelines beheben — read+drain statt head 2026-06-29 06:58:13 +02:00
a2f658e701 fix(ci): Python-Skript ausgelagert — kein Inline-Code in YAML mehr 2026-06-29 06:57:00 +02:00
ab2cf6e4ed fix(ci): Heredoc aus YAML entfernen — python3 -c statt <<PYEOF 2026-06-29 06:55:31 +02:00
1b05362c2b fix(ci): JSON-Serialisierung via python3 statt awk-Pipeline (SIGPIPE/exit-141-Fix) 2026-06-28 23:24:49 +02:00
cfe70430d3 feat: unified axes panel, language selector cleanup, filament mismatch detection, Spoolman slot assignment
- Merged XY + Z axis cards into single Axes Control panel with shared step
  size row and free-text mm input; Home XY/Z placed below respective pads;
  Home All + Motors Off centred at bottom
- Language selector removed from header bar, now only in Settings → Appearance
- Upload-and-Print blocked on GCode/AMS material mismatch; slot mapper dialog
  opens automatically with mismatch warning highlighting affected slots
- Spoolman spool-per-slot assignment: dropdown in AMS status tab per kachel,
  dedicated card in Filaments settings tab with save button
- nightly.yml: NIGHTLY_CHANGELOG.md takes precedence over auto-generated
  commit log when present; CI uses it as release body verbatim
2026-06-28 23:17:58 +02:00
2c8a62f130 fix(ci): Changelog nur feat/fix-Commits seit letztem Stable-Tag 2026-06-28 21:59:38 +02:00
e4b0716330 fix(ci): --tags beim fetch, kein --depth=1 (Tags für Versionsberechnung nötig) 2026-06-28 19:16:31 +02:00
d9fcc15c53 fix(ci): kein VERSION-Commit im CI (verhindert Push-Loop + Konflikt) 2026-06-28 19:16:11 +02:00
31dcf4c8fd ci: nightly.yml in paths-Filter aufnehmen (Workflow-Änderungen triggern Build) 2026-06-28 19:14:34 +02:00
319a8d5ccb fix(ci): Nightly-Version automatisch aus letztem Stable-Tag berechnen
Keine manuelle VERSION-Pflege mehr für Nightlies. CI ermittelt:
- letzten Stable-Tag (z.B. v0.9.27)
- nächste Patch-Version (0.9.28)
- laufenden Nightly-Counter aus vorhandenen Tags (nightly-0.9.28-nightlyN)

VERSION-Datei im Repo bleibt auf dem letzten Stable, wird vom CI
für jeden Nightly-Build überschrieben und committet.
2026-06-28 19:09:08 +02:00