forked from viewit/KX-Bridge-Release
release: v0.9.8
This commit is contained in:
@@ -1,5 +1,29 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [0.9.8] – 2026-05-12
|
||||||
|
|
||||||
|
### Neu
|
||||||
|
- **Multi-Printer in einer Bridge-Instanz:** Ein Prozess verwaltet jetzt mehrere Drucker gleichzeitig — N MQTT-Verbindungen + N HTTP-Listener (Ports 7125, 7126, …), geteilte SQLite + GCode-Store. Konfiguration über `[printer_1]`-, `[printer_2]`-… Sektionen in `config.ini`. Einzel-Modus (`[connection]`) funktioniert unverändert weiter. `docker-compose.yml` exposed einen Port-Range `7125-7130`.
|
||||||
|
- **Drucker per UI hinzufügen:** „+ Drucker hinzufügen"-Button im Drucker-Tab — nur die IP eingeben, Zugangsdaten (Username, Passwort, Device-ID) werden automatisch vom Drucker geholt und entschlüsselt. Weitere Drucker bekommen den nächsten freien Port (7126, 7127, …).
|
||||||
|
- **Drucker per UI entfernen:** „✕"-Button auf jeder Drucker-Karte mit Bestätigung — entfernt die `[printer_N]`-Sektion und nummeriert die übrigen um. Beim Entfernen des letzten Druckers wird auch `[connection]` geleert (leerer Zustand).
|
||||||
|
- **GCode Store:** Hochgeladene Dateien werden in SQLite gespeichert, inkl. Thumbnail-Extraktion. Neue `/kx/files`-API.
|
||||||
|
- **Browser-Tab:** Grid-Ansicht aller hochgeladenen Dateien — Thumbnail, Status-Badge (✓/✗), letzte Druckdauer, plus Suche, Filter und Sortierung.
|
||||||
|
- **Druckhistorie:** Druckaufträge (Start/Ende/Status) werden in SQLite protokolliert, Status pro Datei im Browser-Tab sichtbar.
|
||||||
|
- **Filament-Dialog:** Per-Kanal-Remapping vor dem Druckstart — jeder GCode-Farbkanal wird einem physischen AMS-Slot zugewiesen (wie im Anycubic Slicer). Verfügbar im Browser-Tab und im Upload-Banner.
|
||||||
|
- **MMU-Emulation:** `GET /printer/objects/query?mmu` liefert eine Happy-Hare-kompatible Struktur, damit OrcaSlicers Filament-Sync die AMS-Slots erkennt.
|
||||||
|
- **Drucker-Tab:** Live-Status aller Drucker-Instanzen, IP auf jeder Karte, „Wechseln →"-Button.
|
||||||
|
- **Editierbarer Drucker-Name:** Eigener Name in den Einstellungen (gespeichert in `[bridge] printer_name`, hat Vorrang vor dem vom Drucker gemeldeten Namen).
|
||||||
|
- **Standalone-tauglich:** Linux-Binary / Windows-EXE laufen ohne Docker — `config/` und `data/` liegen neben dem Programm (portabel). Erststart ohne konfigurierten Drucker zeigt den Drucker-Tab mit „+ Drucker hinzufügen" statt des Einstellungs-Dialogs.
|
||||||
|
- **i18n:** Alle neuen UI-Elemente auf Deutsch und Englisch.
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
- **CORS:** CORS-Middleware auf allen Endpunkten — Cross-Instance-Fetches in der Multi-Printer-UI funktionieren zuverlässig.
|
||||||
|
- **Einstellungen / Update-Check** zeigen im Multi-Printer-Modus jetzt die aktive Bridge-Instanz (via `_apiUrl`).
|
||||||
|
- **Bridge-Neustart:** Config-abhängige Umgebungsvariablen werden vor einem Neustart gelöscht (der Config-Loader cachte sie, wodurch Config-Änderungen erst nach einem Kaltstart sichtbar wurden). Der Neustart ist jetzt plattformabhängig: Docker/systemd → Prozess-Exit (Supervisor startet neu), Linux standalone → `os.execv`, Windows → detachter Subprozess.
|
||||||
|
- **`--data-dir`-Default** ist jetzt plattformabhängig — der `/app/data`-Default greift nur in Docker (per `ENV` gesetzt), Standalone-Binaries nutzen `<exe-dir>/data`. Behebt einen Startup-Crash beim Ausführen ohne Docker.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## [0.9.7] – 2026-05-08
|
## [0.9.7] – 2026-05-08
|
||||||
|
|
||||||
### Neu
|
### Neu
|
||||||
|
|||||||
24
CHANGELOG.md
24
CHANGELOG.md
@@ -1,5 +1,29 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [0.9.8] – 2026-05-12
|
||||||
|
|
||||||
|
### New
|
||||||
|
- **Multi-printer in a single bridge instance:** One process now manages multiple printers — N MQTT connections + N HTTP listeners (ports 7125, 7126, …), shared SQLite + GCode store. Configure via `[printer_1]`, `[printer_2]` … sections in `config.ini`. Single-printer mode (`[connection]` only) keeps working unchanged. `docker-compose.yml` exposes a port range `7125-7130`.
|
||||||
|
- **Add printer from the UI:** "+ Add printer" button in the Printers tab — just enter the printer IP, the credentials (username, password, device ID) are fetched and decrypted from the printer automatically. Adding more printers assigns the next free port (7126, 7127, …).
|
||||||
|
- **Remove printer from the UI:** "✕" button on each printer card with a confirmation dialog — removes the `[printer_N]` section and renumbers the rest. Removing the last printer clears `[connection]` too, leaving an empty state.
|
||||||
|
- **GCode Store:** Uploaded files are persisted in SQLite with thumbnail extraction. New `/kx/files` API.
|
||||||
|
- **Browser tab:** Grid view of all uploaded files — thumbnail, status badge (✓/✗), last print duration, plus search, filter and sort.
|
||||||
|
- **Print history:** Print jobs (start/end/status) are recorded in SQLite, status shown per file in the Browser tab.
|
||||||
|
- **Filament dialog:** Per-channel remapping before print start — assign each GCode color channel to a physical AMS slot (like the Anycubic Slicer does). Available in the Browser tab and the upload banner.
|
||||||
|
- **MMU emulation:** `GET /printer/objects/query?mmu` returns a Happy-Hare-compatible structure so OrcaSlicer's filament sync detects the AMS slots.
|
||||||
|
- **Printers tab:** Live status of all printer instances, IP shown on each card, "Switch →" button.
|
||||||
|
- **Editable printer name:** Set a custom name in Settings (stored in `[bridge] printer_name`, takes precedence over the MQTT-reported name).
|
||||||
|
- **Standalone friendly:** Linux binary / Windows EXE run without Docker — `config/` and `data/` are placed next to the executable (portable). First start with no printer configured shows the Printers tab with "+ Add printer" instead of the settings modal.
|
||||||
|
- **i18n:** All new UI elements available in German and English.
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
- **CORS:** CORS middleware added to all endpoints — cross-instance fetches in the multi-printer UI work reliably.
|
||||||
|
- **Settings / update check** now reflect the active bridge instance in multi-printer mode (via `_apiUrl`).
|
||||||
|
- **Bridge restart:** Config-dependent environment variables are cleared before a restart (the config loader cached them, which made config changes invisible until the next cold start). Restart is now platform-aware: Docker/systemd → process exit (supervisor restarts), Linux standalone → `os.execv`, Windows → detached subprocess.
|
||||||
|
- **`--data-dir` default** is now platform-dependent — the `/app/data` default only applies inside Docker (set via `ENV`), standalone binaries use `<exe-dir>/data`. Fixes a startup crash when running the binary without Docker.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## [0.9.7] – 2026-05-08
|
## [0.9.7] – 2026-05-08
|
||||||
|
|
||||||
### New
|
### New
|
||||||
|
|||||||
@@ -16,7 +16,12 @@ COPY config/config.ini.example /app/config/config.ini.example
|
|||||||
|
|
||||||
# config/ ist ein Volume-Mountpoint – beim Start wird config.ini aus .env migriert
|
# config/ ist ein Volume-Mountpoint – beim Start wird config.ini aus .env migriert
|
||||||
# falls noch keine config.ini vorhanden ist.
|
# falls noch keine config.ini vorhanden ist.
|
||||||
RUN mkdir -p /app/config
|
RUN mkdir -p /app/config && mkdir -p /app/data
|
||||||
|
|
||||||
|
# Daten-Verzeichnis fest auf /app/data (sonst würde der Binary-Default <exe-dir>/data greifen)
|
||||||
|
# und Container-Erkennung für den Bridge-Restart (Supervisor startet neu statt subprocess).
|
||||||
|
ENV KX_DATA_DIR=/app/data
|
||||||
|
ENV KX_IN_DOCKER=1
|
||||||
|
|
||||||
EXPOSE 7125
|
EXPOSE 7125
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
# KX-Bridge – Anycubic Kobra X
|
# KX-Bridge – Anycubic Kobra X
|
||||||
|
|
||||||
**Version:** 0.9.7
|
**Version:** 0.9.8
|
||||||
|
|
||||||
Steuere deinen **Anycubic Kobra X** mit OrcaSlicer — ohne Klipper, ohne Raspberry Pi.
|
Steuere deinen **Anycubic Kobra X** mit OrcaSlicer — ohne Klipper, ohne Raspberry Pi.
|
||||||
KX-Bridge ist eine Moonraker-kompatible Bridge die direkt mit dem Drucker kommuniziert.
|
KX-Bridge ist eine Moonraker-kompatible Bridge die direkt mit dem Drucker kommuniziert.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
# KX-Bridge – Anycubic Kobra X
|
# KX-Bridge – Anycubic Kobra X
|
||||||
|
|
||||||
**Version:** 0.9.7
|
**Version:** 0.9.8
|
||||||
|
|
||||||
Control your **Anycubic Kobra X** with OrcaSlicer — no Klipper, no Raspberry Pi.
|
Control your **Anycubic Kobra X** with OrcaSlicer — no Klipper, no Raspberry Pi.
|
||||||
KX-Bridge is a Moonraker-compatible bridge that communicates directly with the printer.
|
KX-Bridge is a Moonraker-compatible bridge that communicates directly with the printer.
|
||||||
|
|||||||
36
config/config.ini.example
Normal file
36
config/config.ini.example
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# KX-Bridge Konfigurationsdatei
|
||||||
|
# Kopiere diese Datei nach config.ini und trage deine Werte ein:
|
||||||
|
# cp config.ini.example config.ini
|
||||||
|
#
|
||||||
|
# Credentials automatisch eintragen:
|
||||||
|
# python3 tools/fetch_credentials.py --ip 192.168.x.x --write-config
|
||||||
|
# Alternativ (Windows, ohne Drucker-IP bekannt):
|
||||||
|
# extract_credentials.exe --write-env (liest aus laufendem AnycubicSlicerNext)
|
||||||
|
|
||||||
|
[connection]
|
||||||
|
# IP-Adresse des Druckers im lokalen Netzwerk
|
||||||
|
printer_ip = 192.168.x.x
|
||||||
|
|
||||||
|
# MQTT-Port (Anycubic Kobra X Standard: 9883)
|
||||||
|
mqtt_port = 9883
|
||||||
|
|
||||||
|
# MQTT-Zugangsdaten (druckerspezifisch, beginnt mit "user")
|
||||||
|
username = userXXXXXXXXXX
|
||||||
|
password = XXXXXXXXXXXXXXX
|
||||||
|
|
||||||
|
# Geräte-ID (32-stelliger Hex-String, druckerspezifisch)
|
||||||
|
device_id = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||||
|
|
||||||
|
# Modell-ID (Kobra X Standard: 20030)
|
||||||
|
mode_id = 20030
|
||||||
|
|
||||||
|
[print]
|
||||||
|
# Standard-AMS-Slot für Einfarbdruck (auto = alle belegten Slots, 0-3 = fixer Slot)
|
||||||
|
default_ams_slot = auto
|
||||||
|
|
||||||
|
# Auto-Leveling vor jedem Druck (1 = an, 0 = aus)
|
||||||
|
auto_leveling = 1
|
||||||
|
|
||||||
|
[bridge]
|
||||||
|
# Poll-Intervall in Sekunden
|
||||||
|
poll_interval = 3
|
||||||
@@ -59,6 +59,7 @@ def _load_config_file(path: pathlib.Path):
|
|||||||
"DEVICE_ID": (CONFIG_SECTION_CONNECTION, "device_id"),
|
"DEVICE_ID": (CONFIG_SECTION_CONNECTION, "device_id"),
|
||||||
"DEFAULT_AMS_SLOT": (CONFIG_SECTION_PRINT, "default_ams_slot"),
|
"DEFAULT_AMS_SLOT": (CONFIG_SECTION_PRINT, "default_ams_slot"),
|
||||||
"AUTO_LEVELING": (CONFIG_SECTION_PRINT, "auto_leveling"),
|
"AUTO_LEVELING": (CONFIG_SECTION_PRINT, "auto_leveling"),
|
||||||
|
"BRIDGE_PRINTER_NAME": (CONFIG_SECTION_BRIDGE, "printer_name"),
|
||||||
}
|
}
|
||||||
for env_key, (section, option) in mapping.items():
|
for env_key, (section, option) in mapping.items():
|
||||||
if env_key not in os.environ:
|
if env_key not in os.environ:
|
||||||
@@ -128,6 +129,39 @@ elif _env_path:
|
|||||||
_config_path = _target
|
_config_path = _target
|
||||||
|
|
||||||
|
|
||||||
|
def list_printers() -> list[dict]:
|
||||||
|
"""Liest alle [printer_N]-Sektionen aus config.ini.
|
||||||
|
|
||||||
|
Jede Sektion kann folgende Keys haben:
|
||||||
|
name, printer_ip, mqtt_port, username, password, mode_id, device_id,
|
||||||
|
bridge_url, default_ams_slot, auto_leveling
|
||||||
|
|
||||||
|
Gibt eine leere Liste zurück wenn keine [printer_N]-Sektionen vorhanden sind
|
||||||
|
(Single-Printer-Betrieb via [connection]).
|
||||||
|
"""
|
||||||
|
path = _find_config_file()
|
||||||
|
if not path:
|
||||||
|
return []
|
||||||
|
cfg = configparser.ConfigParser()
|
||||||
|
cfg.read(path, encoding="utf-8")
|
||||||
|
printers: list[dict] = []
|
||||||
|
idx = 1
|
||||||
|
while True:
|
||||||
|
section = f"printer_{idx}"
|
||||||
|
if not cfg.has_section(section):
|
||||||
|
break
|
||||||
|
p = dict(cfg[section])
|
||||||
|
p.setdefault("id", str(idx))
|
||||||
|
if "mqtt_port" in p:
|
||||||
|
try:
|
||||||
|
p["mqtt_port"] = int(p["mqtt_port"])
|
||||||
|
except ValueError:
|
||||||
|
p["mqtt_port"] = 9883
|
||||||
|
printers.append(p)
|
||||||
|
idx += 1
|
||||||
|
return printers
|
||||||
|
|
||||||
|
|
||||||
def get(key: str, default: str = "") -> str:
|
def get(key: str, default: str = "") -> str:
|
||||||
return os.environ.get(key, default)
|
return os.environ.get(key, default)
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ services:
|
|||||||
build: .
|
build: .
|
||||||
volumes:
|
volumes:
|
||||||
- ./config:/app/config
|
- ./config:/app/config
|
||||||
|
- ./data:/app/data
|
||||||
- ./.env:/app/.env:ro
|
- ./.env:/app/.env:ro
|
||||||
ports:
|
ports:
|
||||||
- "7125:7125"
|
- "7125-7130:7125-7130"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
logging:
|
logging:
|
||||||
driver: json-file
|
driver: json-file
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user