Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ef9a3cee2e | |||
| f6efc209d7 | |||
| 633ae3d903 |
@@ -1,31 +0,0 @@
|
||||
---
|
||||
name: KX-Bridge Changelog
|
||||
description: Generiert einen CHANGELOG.md Eintrag aus Git-Commits seit dem letzten Tag.
|
||||
tools:
|
||||
- run_command
|
||||
- read_file
|
||||
- write_file
|
||||
---
|
||||
|
||||
Du generierst CHANGELOG.md Einträge für KX-Bridge.
|
||||
|
||||
Vorgehen:
|
||||
1. Führe aus: `git log $(git describe --tags --abbrev=0)..HEAD --oneline`
|
||||
2. Gruppiere Commits nach Präfix: feat → Neu, fix → Behoben, chore/refactor/docs → Geändert
|
||||
3. Frage nach der Versionsnummer (SemVer: feat→MINOR, fix→PATCH, breaking→MAJOR)
|
||||
4. Schreibe den Abschnitt im Format:
|
||||
|
||||
```
|
||||
## [VERSION] - DATUM
|
||||
|
||||
### Neu
|
||||
- ...
|
||||
|
||||
### Behoben
|
||||
- ...
|
||||
|
||||
### Geändert
|
||||
- ...
|
||||
```
|
||||
|
||||
5. Füge den Abschnitt am Anfang der bestehenden CHANGELOG.md ein, ohne vorhandene Einträge zu ändern.
|
||||
@@ -1,32 +0,0 @@
|
||||
---
|
||||
name: KX-Bridge Docker Check
|
||||
description: Prüft Dockerfile, docker-compose und das gebaute Image auf häufige Probleme.
|
||||
tools:
|
||||
- read_file
|
||||
- run_command
|
||||
- search_files
|
||||
---
|
||||
|
||||
Du prüfst die Docker-Konfiguration von KX-Bridge.
|
||||
|
||||
**Dockerfile:**
|
||||
- Base-Image aktuell? (`python:3.11-slim` oder neuer)
|
||||
- `.dockerignore` vorhanden und vollständig?
|
||||
- Keine Secrets oder Zertifikate im Image (`anycubic_slicer.crt/.key` darf NICHT eingebettet sein)
|
||||
- Healthcheck vorhanden?
|
||||
- Kein `COPY . .` ohne `.dockerignore`
|
||||
|
||||
**docker-compose.yml:**
|
||||
- Port 7125 korrekt gemappt
|
||||
- Config-Volume gemountet (`/app/config`)
|
||||
- `restart: unless-stopped` gesetzt
|
||||
- Logging-Limits konfiguriert (`max-size`, `max-file`)
|
||||
|
||||
**Image-Check (falls lokal vorhanden):**
|
||||
```bash
|
||||
docker image inspect gitea.it-drui.de/viewit/kx-bridge:nightly
|
||||
```
|
||||
- Image-Größe sinnvoll (< 500MB)?
|
||||
- Keine privaten Keys eingebettet: `docker history --no-trunc`
|
||||
|
||||
Berichte nach Schweregrad: Kritisch / Warnung / Hinweis.
|
||||
@@ -1,23 +0,0 @@
|
||||
---
|
||||
name: KX-Bridge Moonraker Debug
|
||||
description: Analysiert Moonraker/Klipper Logs und KX-Bridge Ausgaben auf Fehlerursachen.
|
||||
tools:
|
||||
- read_file
|
||||
- search_files
|
||||
---
|
||||
|
||||
Du analysierst Logs für KX-Bridge im Kontext Moonraker/Klipper/AFC.
|
||||
|
||||
**Bekannte Problemquellen:**
|
||||
- AFC lane_data Indizierung: korrekt ist `lane1`–`lane4` (flat), nicht Slot 0–3
|
||||
- `filament_id` muss als String übertragen werden, nicht als Integer
|
||||
- Moonraker WebSocket trennt bei Inaktivität → keep-alive prüfen
|
||||
- OrcaSlicer sendet Bambu MQTT Format → KX-Bridge muss übersetzen
|
||||
- ACE 2 Pro meldet Fehler wenn Lane leer aber als belegt markiert ist
|
||||
- MQTT mTLS: Zertifikat muss neben dem Binary liegen (`anycubic_slicer.crt/.key`)
|
||||
|
||||
**Bei einem Log:**
|
||||
1. Identifiziere den **ersten** Fehler (nicht den letzten Symptom)
|
||||
2. Zeige den relevanten Log-Kontext (±10 Zeilen um den Fehler)
|
||||
3. Nenne die wahrscheinliche Ursache
|
||||
4. Schlage einen konkreten Fix vor (Datei + Funktion wenn möglich)
|
||||
@@ -1,25 +0,0 @@
|
||||
---
|
||||
name: KX-Bridge Nightly Prep
|
||||
description: Bereitet den PR von nightly nach main vor. Prüft ob alle Voraussetzungen für ein Stable Release erfüllt sind.
|
||||
tools:
|
||||
- run_command
|
||||
- read_file
|
||||
---
|
||||
|
||||
Du bereitest einen nightly → main Merge für KX-Bridge vor.
|
||||
|
||||
Führe folgende Checks aus und berichte:
|
||||
|
||||
1. `git log main..nightly --oneline` → alle Commits die noch nicht in main sind
|
||||
2. `git diff main..nightly -- CHANGELOG.md` → ist CHANGELOG.md für alle Änderungen aktualisiert?
|
||||
3. Prüfe ob `tests/` alle geänderten Module abdeckt
|
||||
4. Prüfe ob Dockerfile ein aktuelles Base-Image verwendet
|
||||
5. Schlage eine SemVer-Versionsnummer vor:
|
||||
- `feat:` Commits → MINOR erhöhen
|
||||
- `fix:` Commits → PATCH erhöhen
|
||||
- Breaking Change im Commit-Body → MAJOR erhöhen
|
||||
|
||||
Abschlussbericht:
|
||||
- ✅ Bereit für Release
|
||||
- ⚠️ Offen: [Liste]
|
||||
- ❌ Blockiert durch: [Grund]
|
||||
@@ -1,24 +0,0 @@
|
||||
---
|
||||
name: KX-Bridge Reviewer
|
||||
description: Reviewt geänderte Dateien vor einem PR auf nightly. Prüft Logik, Fehlerbehandlung, Moonraker-Kompatibilität und Stil.
|
||||
tools:
|
||||
- read_file
|
||||
- list_directory
|
||||
- search_files
|
||||
---
|
||||
|
||||
Du bist Code-Reviewer für KX-Bridge — eine Python-Bridge zwischen OrcaSlicer und Moonraker/Klipper für den Anycubic Kobra X.
|
||||
|
||||
Beim Review prüfst du:
|
||||
- Korrekte Fehlerbehandlung bei Moonraker HTTP/MQTT Calls (keine unbehandelten Exceptions)
|
||||
- Keine hardcodierten IPs oder Ports (müssen aus config.ini kommen)
|
||||
- Thread-Sicherheit bei parallelen Moonraker-Abfragen (asyncio korrekt verwendet)
|
||||
- AFC lane_data Struktur: flache Indizierung lane1–lane4, kein Slot-Mapping 0–3
|
||||
- Kein `print()` statt `logging` (außer in CLI-Hilfsfunktionen)
|
||||
- Typ-Annotationen vorhanden, Python 3.8+ kompatibel (kein `X | Y` Syntax)
|
||||
- Tests für neue öffentliche Funktionen vorhanden
|
||||
|
||||
Ausgabeformat:
|
||||
1. **Kritische Fehler** — blockieren den Merge
|
||||
2. **Warnungen** — sollten vor Merge behoben werden
|
||||
3. **Hinweise** — optional, für zukünftige Verbesserungen
|
||||
@@ -1,26 +0,0 @@
|
||||
---
|
||||
name: KX-Bridge Test Writer
|
||||
description: Leitet pytest-Tests aus geänderten oder neuen Python-Dateien ab.
|
||||
tools:
|
||||
- read_file
|
||||
- write_file
|
||||
- list_directory
|
||||
- search_files
|
||||
---
|
||||
|
||||
Du schreibst pytest-Tests für KX-Bridge.
|
||||
|
||||
Kontext:
|
||||
- Moonraker API läuft auf Port 7125 (HTTP + WebSocket)
|
||||
- AFC lane_data: flache Indizierung lane1–lane4
|
||||
- Externe HTTP-Calls zu Moonraker werden mit `unittest.mock` gemockt
|
||||
- Python 3.8+ Kompatibilität (kein `X | Y` Union-Syntax)
|
||||
|
||||
Für jede zu testende Funktion schreibst du:
|
||||
1. Happy Path (Normalfall mit validen Eingaben)
|
||||
2. Fehlerfall (Moonraker nicht erreichbar, Timeout, falsche Antwort)
|
||||
3. Grenzwerte (leere lane_data, ungültige filament_id, None-Werte)
|
||||
|
||||
Dateiname: `tests/test_<modulname>.py`
|
||||
Verwende pytest-Fixtures für Moonraker-Mock-Responses.
|
||||
Keine echten Netzwerkaufrufe in Tests.
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"project": "KX-Bridge",
|
||||
"language": "de",
|
||||
"defaultAgent": "reviewer",
|
||||
"context": {
|
||||
"repoBase": "gitea.it-drui.de/viewit/KX-Bridge-Release",
|
||||
"defaultBranch": "nightly",
|
||||
"stableBranch": "main",
|
||||
"registry": "gitea.it-drui.de/viewit/kx-bridge",
|
||||
"moonrakerPort": 7125
|
||||
}
|
||||
}
|
||||
30
.editorconfig
Normal file
30
.editorconfig
Normal file
@@ -0,0 +1,30 @@
|
||||
# EditorConfig helps maintain consistent coding styles across all files
|
||||
# https://editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
# Unix-style newlines, UTF-8 encoding for all files
|
||||
[*]
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
# Python: 4 spaces (PEP 8)
|
||||
[*.py]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
# JavaScript/JSON/YAML: 2 spaces (common web standard)
|
||||
[*.{js,json,yml,yaml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
# HTML/CSS: 2 spaces
|
||||
[*.{html,css}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
# Markdown: preserve formatting
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
@@ -1,29 +0,0 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Report a bug in KX-Bridge
|
||||
labels: bug
|
||||
---
|
||||
|
||||
## Description
|
||||
<!-- What is happening? -->
|
||||
|
||||
## Steps to Reproduce
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
## Actual Behavior
|
||||
|
||||
## Environment
|
||||
- KX-Bridge Version:
|
||||
- OrcaSlicer Version:
|
||||
- Moonraker/Klipper Version:
|
||||
- Operating System:
|
||||
- Installation: Docker / Binary
|
||||
|
||||
## Logs
|
||||
```
|
||||
<!-- docker logs kx-bridge --tail 50 -->
|
||||
```
|
||||
@@ -1,14 +0,0 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Suggest a new feature or improvement
|
||||
labels: enhancement
|
||||
---
|
||||
|
||||
## Description
|
||||
<!-- What should be added or improved? -->
|
||||
|
||||
## Motivation
|
||||
<!-- Why is this useful? What problem does it solve? -->
|
||||
|
||||
## Proposed Implementation
|
||||
<!-- Optional: How could this be implemented technically? -->
|
||||
@@ -1,21 +0,0 @@
|
||||
## Description
|
||||
<!-- What does this PR change? -->
|
||||
|
||||
## Related Issue
|
||||
Closes #
|
||||
|
||||
## Type
|
||||
- [ ] Bug fix
|
||||
- [ ] Feature
|
||||
- [ ] Documentation
|
||||
- [ ] Refactoring
|
||||
|
||||
## Tested with
|
||||
- OrcaSlicer Version:
|
||||
- Printer:
|
||||
- Moonraker/Klipper Version:
|
||||
|
||||
## Checklist
|
||||
- [ ] Tests added/updated
|
||||
- [ ] CHANGELOG.md updated
|
||||
- [ ] No debug code included
|
||||
@@ -1,132 +0,0 @@
|
||||
name: Nightly Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- nightly
|
||||
paths:
|
||||
- '**.py'
|
||||
- 'Dockerfile'
|
||||
- 'requirements.txt'
|
||||
- 'web/**'
|
||||
- 'data/**'
|
||||
schedule:
|
||||
- cron: '0 2 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: server-runner
|
||||
steps:
|
||||
- name: Checkout
|
||||
run: |
|
||||
if [ -d .git ]; then
|
||||
git fetch origin nightly
|
||||
git reset --hard origin/nightly
|
||||
git clean -fd
|
||||
else
|
||||
git clone --depth=1 --branch nightly https://gitea.it-drui.de/viewit/KX-Bridge-Release.git .
|
||||
fi
|
||||
|
||||
- name: Install Docker CLI
|
||||
run: |
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
ARCH=$(uname -m)
|
||||
if [ "$ARCH" = "x86_64" ]; then
|
||||
DARCH="x86_64"
|
||||
BARCH="amd64"
|
||||
else
|
||||
DARCH="aarch64"
|
||||
BARCH="arm64"
|
||||
fi
|
||||
wget -qO- "https://download.docker.com/linux/static/stable/${DARCH}/docker-27.5.1.tgz" \
|
||||
| tar xz --strip-components=1 -C /usr/local/bin docker/docker
|
||||
chmod +x /usr/local/bin/docker
|
||||
mkdir -p /usr/local/lib/docker/cli-plugins
|
||||
wget -qO /usr/local/lib/docker/cli-plugins/docker-buildx \
|
||||
"https://github.com/docker/buildx/releases/download/v0.23.0/buildx-v0.23.0.linux-${BARCH}"
|
||||
chmod +x /usr/local/lib/docker/cli-plugins/docker-buildx
|
||||
fi
|
||||
docker version --format '{{.Client.Version}}'
|
||||
|
||||
- name: Set up QEMU
|
||||
run: |
|
||||
docker run --rm --privileged tonistiigi/binfmt:latest --install all
|
||||
|
||||
- name: Set up buildx
|
||||
run: |
|
||||
docker buildx inspect kxbuilder 2>/dev/null || \
|
||||
docker buildx create --name kxbuilder --use
|
||||
docker buildx use kxbuilder
|
||||
|
||||
- name: Login to Gitea registry
|
||||
run: |
|
||||
echo "${{ secrets.REGISTRY_TOKEN }}" | \
|
||||
docker login gitea.it-drui.de -u "${{ secrets.REGISTRY_USER }}" --password-stdin
|
||||
|
||||
- name: Build & push (amd64 + arm64)
|
||||
run: |
|
||||
VERSION=$(cat VERSION)
|
||||
docker buildx build \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
--push \
|
||||
--provenance=false \
|
||||
--no-cache \
|
||||
-t "gitea.it-drui.de/viewit/kx-bridge:nightly" \
|
||||
-t "gitea.it-drui.de/viewit/kx-bridge:nightly-${VERSION}" \
|
||||
.
|
||||
|
||||
- name: Create Gitea Nightly Release
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
run: |
|
||||
VERSION=$(cat VERSION)
|
||||
TAG="nightly-${VERSION}"
|
||||
git config user.name "gitea-actions"
|
||||
git config user.email "actions@it-drui.de"
|
||||
|
||||
# Changelog aus CHANGES.md lesen (wird von release.sh aus dem Dev-Repo generiert)
|
||||
BODY_FILE=$(mktemp)
|
||||
if [ -f CHANGES.md ]; then
|
||||
cat CHANGES.md > "$BODY_FILE"
|
||||
else
|
||||
# Fallback falls CHANGES.md fehlt
|
||||
printf '## KX-Bridge %s -- Nightly Build\n\n' "$VERSION" > "$BODY_FILE"
|
||||
printf '[experimentell] Ungetestete Features, nur fuer Tester geeignet.\n\n' >> "$BODY_FILE"
|
||||
printf '- Automatischer Nightly-Build\n\n---\n\n' >> "$BODY_FILE"
|
||||
printf '### Docker-Image aktualisieren\n\n```bash\ndocker compose pull && docker compose up -d\n```\n\n' >> "$BODY_FILE"
|
||||
printf 'Image-Tag: `gitea.it-drui.de/viewit/kx-bridge:nightly`\n' >> "$BODY_FILE"
|
||||
fi
|
||||
|
||||
# Tag setzen
|
||||
git tag -f "$TAG"
|
||||
git push https://gitea-actions:${GITEA_TOKEN}@gitea.it-drui.de/viewit/KX-Bridge-Release.git "$TAG" --force
|
||||
|
||||
# curl installieren (BusyBox wget kann kein DELETE/POST mit Headers)
|
||||
if ! command -v curl >/dev/null 2>&1; then
|
||||
if ! apk add --no-cache curl 2>/dev/null; then
|
||||
wget -qO /usr/local/bin/curl \
|
||||
"https://github.com/moparisthebest/static-curl/releases/download/v8.6.0/curl-amd64"
|
||||
chmod +x /usr/local/bin/curl
|
||||
fi
|
||||
fi
|
||||
|
||||
# Altes Release loeschen falls vorhanden
|
||||
curl -s -X DELETE \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
"https://gitea.it-drui.de/api/v1/repos/viewit/KX-Bridge-Release/releases/tags/${TAG}" \
|
||||
2>/dev/null || true
|
||||
|
||||
# Release erstellen (JSON-Body via awk escapen)
|
||||
BODY_JSON=$(awk '{
|
||||
gsub(/\\/, "\\\\"); gsub(/"/, "\\\""); gsub(/\t/, "\\t");
|
||||
printf "%s\\n", $0
|
||||
}' "$BODY_FILE" | awk 'BEGIN{printf "\""} {printf "%s", $0} END{printf "\""}')
|
||||
JSON_PAYLOAD="{\"tag_name\":\"${TAG}\",\"name\":\"KX-Bridge ${VERSION} Nightly\",\"body\":${BODY_JSON},\"draft\":false,\"prerelease\":true}"
|
||||
printf '%s' "$JSON_PAYLOAD" > /tmp/release_body.json
|
||||
curl -s -X POST \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data-binary @/tmp/release_body.json \
|
||||
"https://gitea.it-drui.de/api/v1/repos/viewit/KX-Bridge-Release/releases"
|
||||
rm -f "$BODY_FILE" /tmp/release_body.json
|
||||
@@ -1,34 +0,0 @@
|
||||
name: PR Check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- nightly
|
||||
|
||||
jobs:
|
||||
lint-and-test:
|
||||
runs-on: server-runner
|
||||
steps:
|
||||
- name: Checkout
|
||||
run: |
|
||||
if [ -d .git ]; then
|
||||
git fetch origin
|
||||
git reset --hard origin/nightly
|
||||
git clean -fd
|
||||
else
|
||||
git clone --depth=1 --branch nightly https://gitea.it-drui.de/viewit/KX-Bridge-Release.git .
|
||||
fi
|
||||
|
||||
- name: Dependencies installieren
|
||||
run: pip3 install -r requirements.txt
|
||||
|
||||
- name: Lint
|
||||
run: |
|
||||
pip3 install flake8
|
||||
flake8 *.py --max-line-length=120 --extend-ignore=E501
|
||||
|
||||
- name: Tests
|
||||
run: |
|
||||
pip3 install pytest
|
||||
pytest tests/ -v
|
||||
if: ${{ hashFiles('tests/') != '' }}
|
||||
@@ -1,74 +0,0 @@
|
||||
name: Stable Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: server-runner
|
||||
steps:
|
||||
- name: Checkout
|
||||
run: |
|
||||
TAG="${GITHUB_REF#refs/tags/}"
|
||||
if [ -d .git ]; then
|
||||
git fetch --tags origin
|
||||
git checkout "$TAG"
|
||||
git clean -fd
|
||||
else
|
||||
git clone --depth=1 --branch "$TAG" https://gitea.it-drui.de/viewit/KX-Bridge-Release.git .
|
||||
fi
|
||||
|
||||
- name: Install Docker CLI
|
||||
run: |
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
ARCH=$(uname -m)
|
||||
if [ "$ARCH" = "x86_64" ]; then
|
||||
DARCH="x86_64"
|
||||
BARCH="amd64"
|
||||
else
|
||||
DARCH="aarch64"
|
||||
BARCH="arm64"
|
||||
fi
|
||||
wget -qO- "https://download.docker.com/linux/static/stable/${DARCH}/docker-27.5.1.tgz" \
|
||||
| tar xz --strip-components=1 -C /usr/local/bin docker/docker
|
||||
chmod +x /usr/local/bin/docker
|
||||
mkdir -p /usr/local/lib/docker/cli-plugins
|
||||
wget -qO /usr/local/lib/docker/cli-plugins/docker-buildx \
|
||||
"https://github.com/docker/buildx/releases/download/v0.23.0/buildx-v0.23.0.linux-${BARCH}"
|
||||
chmod +x /usr/local/lib/docker/cli-plugins/docker-buildx
|
||||
fi
|
||||
docker version --format '{{.Client.Version}}'
|
||||
|
||||
- name: Set up QEMU
|
||||
run: |
|
||||
docker run --rm --privileged tonistiigi/binfmt:latest --install all
|
||||
|
||||
- name: Set up buildx
|
||||
run: |
|
||||
docker buildx inspect kxbuilder 2>/dev/null || \
|
||||
docker buildx create --name kxbuilder --use
|
||||
docker buildx use kxbuilder
|
||||
|
||||
- name: Login to Gitea registry
|
||||
run: |
|
||||
echo "${{ secrets.REGISTRY_TOKEN }}" | \
|
||||
docker login gitea.it-drui.de -u "${{ secrets.REGISTRY_USER }}" --password-stdin
|
||||
|
||||
# Strip fuehrendes 'v' fuer den Image-Tag (VERSION-Datei hat kein 'v').
|
||||
- name: Build & push (amd64 + arm64)
|
||||
run: |
|
||||
VERSION="${GITHUB_REF#refs/tags/v}"
|
||||
docker buildx build \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
--push \
|
||||
--provenance=false \
|
||||
--no-cache \
|
||||
-t "gitea.it-drui.de/viewit/kx-bridge:latest" \
|
||||
-t "gitea.it-drui.de/viewit/kx-bridge:${VERSION}" \
|
||||
.
|
||||
|
||||
# Hinweis: Das Gitea-Release (inkl. englischem Auto-Changelog + Binaries als
|
||||
# Assets) erstellt release.sh synchron, da es die lokal via CodeBuilder
|
||||
# gebauten Binaries direkt hochlaedt. Dieser Workflow baut nur das Docker-Image.
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -8,6 +8,8 @@ releases/*/kx-bridge
|
||||
releases/*/extract_credentials
|
||||
releases/*/extract_credentials.exe
|
||||
|
||||
node_modules/*
|
||||
|
||||
!kx-bridge.spec
|
||||
|
||||
# Laufzeit-Daten und Drucker-Credentials — nie committen
|
||||
@@ -17,8 +19,3 @@ config/*.ini
|
||||
data/
|
||||
|
||||
!data/orca_filaments.json
|
||||
.runner-token
|
||||
|
||||
# Dev-only Dateien — nicht ins öffentliche Repo
|
||||
CLAUDE.md
|
||||
release.sh
|
||||
|
||||
33
CHANGES.md
33
CHANGES.md
@@ -1,33 +0,0 @@
|
||||
## KX-Bridge 0.9.27-nightly9 — Nightly Build
|
||||
|
||||
[experimentell] Ungetestete Features, nur für Tester geeignet.
|
||||
|
||||
### Änderungen seit `v0.9.26`
|
||||
|
||||
- fix(spoolman): Status-Dot beim Seitenload initialisieren
|
||||
- chore: CHANGES.md mit echtem Changelog aus Dev-Repo ins Release-Repo schreiben
|
||||
- fix(spoolman): SPOOLMAN_* env-Cache bei Restart leeren, Status-Dot nach Save aktualisieren
|
||||
- chore: README.es.md + CONTRIBUTING.md in release.sh-Sync aufnehmen
|
||||
- docs: Nightly-Sektion, Wartungshinweis + CONTRIBUTING.md, FR/IT-Sprachen, Downloads-Badge
|
||||
- fix(update): Nightly-Vergleich auf Versions-String umstellen (statt Datum)
|
||||
- feat(update): Nightly-Track vom Stable-Track trennen
|
||||
- fix(release): nightly immer auf nightly-Branch pushen, kein master-Push
|
||||
- fix(camera): exponentielles Backoff bei ffmpeg-Fehler + /api/camera/reset + ↺-Button
|
||||
- fix(release): Nightly-Release vom nightly-Branch erlauben
|
||||
- feat(i18n): fehlende UI-Übersetzungen ergänzt + Keys alphabetisch sortiert (PR #70 @fenopy)
|
||||
- fix: config/config.ini.example beim Release-Sync mitübertragen (Issue #72)
|
||||
- feat(ui): Integrationen-Tab in Settings (Spoolman + Obico-Hinweis)
|
||||
- feat(stack): KobraX Full Stack Compose für Portainer (KX-Bridge + Obico + Spoolman)
|
||||
- feat(release): Nightly/Stable Release-Workflow mit eigenem Docker-Tag
|
||||
- feat(spoolman): optionale Spoolman-Filamentverbrauch-Integration (PR #65, @p2l)
|
||||
- fix(release): Artifact-Download per HTTP statt lokalem Dateipfad
|
||||
|
||||
---
|
||||
|
||||
### Docker-Image aktualisieren
|
||||
|
||||
```bash
|
||||
docker compose pull && docker compose up -d
|
||||
```
|
||||
|
||||
Image-Tag: `gitea.it-drui.de/viewit/kx-bridge:nightly`
|
||||
102
CONTRIBUTING.md
102
CONTRIBUTING.md
@@ -1,102 +0,0 @@
|
||||
# Contributing to KX-Bridge
|
||||
|
||||
Thanks for taking the time to contribute! Here's everything you need to know.
|
||||
|
||||
---
|
||||
|
||||
## How to report a bug or request a feature
|
||||
|
||||
Use the issue tracker:
|
||||
|
||||
- **Bug:** [New Bug Report](https://gitea.it-drui.de/viewit/KX-Bridge-Release/issues/new?template=bug_report.md)
|
||||
- **Feature:** [New Feature Request](https://gitea.it-drui.de/viewit/KX-Bridge-Release/issues/new?template=feature_request.md)
|
||||
|
||||
Please fill in the template — especially the **KX-Bridge version** and **logs**.
|
||||
Issues without version info are hard to debug.
|
||||
|
||||
---
|
||||
|
||||
## How to submit a Pull Request
|
||||
|
||||
### 1. Fork the repository
|
||||
|
||||
Click **Fork** at the top of this page.
|
||||
You now have your own copy at `gitea.it-drui.de/your-username/KX-Bridge-Release`.
|
||||
|
||||
### 2. Clone your fork
|
||||
|
||||
```bash
|
||||
git clone https://gitea.it-drui.de/your-username/KX-Bridge-Release.git
|
||||
cd KX-Bridge-Release
|
||||
```
|
||||
|
||||
### 3. Create a branch
|
||||
|
||||
Always branch off `nightly`:
|
||||
|
||||
```bash
|
||||
git checkout nightly
|
||||
git checkout -b feature/my-feature # or fix/my-fix
|
||||
```
|
||||
|
||||
### 4. Make your changes
|
||||
|
||||
- Test your changes locally with Docker:
|
||||
```bash
|
||||
docker build -t kx-bridge:dev .
|
||||
docker run -p 7125:7125 -v ./config:/app/config kx-bridge:dev
|
||||
```
|
||||
- No debug `print()` statements — use `logging`
|
||||
- Keep commits focused; one thing per commit
|
||||
|
||||
### 5. Push and open a PR
|
||||
|
||||
```bash
|
||||
git push origin feature/my-feature
|
||||
```
|
||||
|
||||
Gitea will show a banner — click **"Create Pull Request"**.
|
||||
The PR template will be pre-filled. Set the target branch to **`nightly`**.
|
||||
|
||||
---
|
||||
|
||||
## Branch model
|
||||
|
||||
```
|
||||
master ← stable releases only (merged by maintainer)
|
||||
nightly ← integration branch — PRs go here
|
||||
feature/* ← your feature branch (in your fork)
|
||||
fix/* ← your bugfix branch (in your fork)
|
||||
```
|
||||
|
||||
Your PR always targets `nightly`. The maintainer periodically merges `nightly → master` for a new stable release.
|
||||
|
||||
---
|
||||
|
||||
## Commit style
|
||||
|
||||
Use conventional commit prefixes:
|
||||
|
||||
| Prefix | When |
|
||||
|---|---|
|
||||
| `feat:` | new feature |
|
||||
| `fix:` | bug fix |
|
||||
| `docs:` | documentation only |
|
||||
| `chore:` | maintenance, dependencies |
|
||||
| `refactor:` | code change without new feature or fix |
|
||||
|
||||
Example: `fix: prevent crash when printer is offline during startup`
|
||||
|
||||
---
|
||||
|
||||
## Language
|
||||
|
||||
- **Code and comments:** English
|
||||
- **Issue comments:** match the language of the issue (if someone writes in German, reply in German)
|
||||
- **Commit messages:** English
|
||||
|
||||
---
|
||||
|
||||
## Questions?
|
||||
|
||||
Open a [Discussion](https://gitea.it-drui.de/viewit/KX-Bridge-Release/issues) or leave a comment on the relevant issue.
|
||||
27
README.de.md
27
README.de.md
@@ -21,7 +21,7 @@ Feedback willkommen.</sub>
|
||||
|
||||
[](https://gitea.it-drui.de/viewit/KX-Bridge-Release/releases)
|
||||
|
||||
[](https://gitea.it-drui.de/viewit/KX-Bridge-Release/releases)
|
||||
[](https://gitea.it-drui.de/viewit/KX-Bridge-Release/releases)
|
||||
|
||||
[](https://www.youtube.com/watch?v=1Ql4wfH27fM)
|
||||
|
||||
@@ -29,11 +29,6 @@ Feedback willkommen.</sub>
|
||||
|
||||
</div>
|
||||
|
||||
> [!CAUTION]
|
||||
> **Laufende Wartungsarbeiten** — Wir strukturieren das Repository um (Branch-Modell, CI-Workflows, Beitragsprozess). Es kann zu Änderungen bei Branch-Namen, PR-Templates und der Art der Veröffentlichungen kommen. Wir entschuldigen uns für etwaige Unannehmlichkeiten. Handhabung, Workflow und langfristige Wartbarkeit werden dadurch deutlich verbessert.
|
||||
>
|
||||
> 👉 Möchtest du beitragen? Bitte zuerst [CONTRIBUTING.md](CONTRIBUTING.md) lesen.
|
||||
|
||||
---
|
||||
|
||||
## ✨ Was kann KX-Bridge?
|
||||
@@ -50,7 +45,7 @@ Feedback willkommen.</sub>
|
||||
| 🧩 | **Multi-Printer** — mehrere Drucker in **einer** Bridge-Instanz, Umschalten per Dropdown |
|
||||
| ➕ | **Drucker hinzufügen per Klick** — nur die IP eingeben, Zugangsdaten werden automatisch importiert |
|
||||
| 🔁 | **Robuster MQTT-Reconnect** — Bridge überlebt nächtlichen Drucker-Reboot ohne manuellen Neustart |
|
||||
| 🌐 | **Mehrsprachiges UI** — DE / EN / ES / FR / IT / 中文, Browser-Sprache automatisch erkannt |
|
||||
| 🌐 | **Mehrsprachiges UI** — DE / EN / ES / 中文, Browser-Sprache automatisch erkannt |
|
||||
| 🔄 | **Self-Update** — neue Versionen direkt im Browser installieren |
|
||||
| 🧠 | **OrcaSlicer** — volles Moonraker-Protokoll (HTTP + WebSocket); für korrekten Vendor-Match pro Slot den [OrcaSlicer-KX-Build](#-empfohlener-slicer) nutzen |
|
||||
|
||||
@@ -189,24 +184,6 @@ docker compose up -d --build # lokal selber bauen (statt zu pullen)
|
||||
|
||||
---
|
||||
|
||||
## 🌙 Nightly-Builds
|
||||
|
||||
Nightly-Builds enthalten die neuesten unveröffentlichten Features und werden automatisch bei jedem Entwicklungs-Push gebaut.
|
||||
Sie können instabil sein — für Tests oder frühen Zugriff auf neue Funktionen geeignet.
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.yml -f docker-compose.nightly.yml pull
|
||||
docker compose -f docker-compose.yml -f docker-compose.nightly.yml up -d
|
||||
```
|
||||
|
||||
Zurück zum stabilen Release:
|
||||
|
||||
```bash
|
||||
docker compose pull && docker compose up -d
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🩹 Troubleshooting
|
||||
|
||||
<details>
|
||||
|
||||
147
README.dev.md
147
README.dev.md
@@ -1,147 +0,0 @@
|
||||
<p align="center"><img src="knlogo.png" alt="KX-Bridge Logo" width="180"/></p>
|
||||
|
||||
# KX-Bridge – Dev Branch
|
||||
|
||||
> **Achtung:** Dies ist der Entwicklungs-Branch. Builds hier sind experimentell und nicht für den produktiven Einsatz geeignet.
|
||||
> Für stabile Releases → [KX-Bridge-Release](https://gitea.it-drui.de/viewit/KX-Bridge-Release/releases)
|
||||
|
||||
---
|
||||
|
||||
## Versionsschema
|
||||
|
||||
Dev-Builds verwenden das Format:
|
||||
|
||||
```
|
||||
<basis-version>-dev+<git-hash>
|
||||
```
|
||||
|
||||
**Beispiel:** `0.9.1-dev+04a6a20`
|
||||
|
||||
- `0.9.1` – Basis der aktuellen stabilen Version
|
||||
- `-dev` – kennzeichnet den Entwicklungs-Branch
|
||||
- `+04a6a20` – 7-stelliger Git-Commit-Hash, eindeutig je Build
|
||||
|
||||
---
|
||||
|
||||
## Dev-Binaries testen
|
||||
|
||||
Dev-Releases sind auf Gitea als Pre-Releases verfügbar:
|
||||
[Dev-Releases](https://gitea.it-drui.de/viewit/KX-Bridge-Release/releases)
|
||||
|
||||
### Docker (empfohlen)
|
||||
|
||||
```bash
|
||||
git clone <repo-url> -b dev
|
||||
cd kobrax
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### Linux-Binary
|
||||
|
||||
```bash
|
||||
# Dev-Release herunterladen (kx-bridge-linux.zip)
|
||||
unzip kx-bridge-linux.zip
|
||||
chmod +x kx-bridge
|
||||
./kx-bridge
|
||||
```
|
||||
|
||||
`config/config.ini` und `data/` (SQLite + GCode-Store) werden **neben dem Binary**
|
||||
angelegt. Beim Erststart ohne Drucker zeigt die UI auf `http://localhost:7125` den
|
||||
Drucker-Tab mit "+ Drucker hinzufügen" — dort nur die IP eingeben, der Rest wird
|
||||
automatisch importiert.
|
||||
|
||||
### Windows-EXE
|
||||
|
||||
```
|
||||
# Dev-Release herunterladen (kx-bridge-windows.zip)
|
||||
# kx-bridge.exe starten — config/ und data/ liegen daneben
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Update-Kanal
|
||||
|
||||
Dev-Versionen prüfen automatisch auf neue **Dev-Releases** — nicht auf stabile Releases.
|
||||
Im Settings-Modal → „Auf Updates prüfen" zeigt den neuesten Dev-Build an.
|
||||
|
||||
---
|
||||
|
||||
## Aktive Entwicklung (Stand 2026-05-10)
|
||||
|
||||
Stand `dev`-Branch über v0.9.7 hinaus:
|
||||
|
||||
| Feature | Status |
|
||||
|---------|--------|
|
||||
| MMU-Emulation (`/printer/objects/query?mmu`) für OrcaSlicer Filament-Sync | ✅ |
|
||||
| GCode Store (SQLite + Thumbnails) | ✅ |
|
||||
| Browser-Tab mit Suche/Filter/Sortierung | ✅ |
|
||||
| Filament-Dialog: Per-Kanal-Remapping (GCode-Kanal → AMS-Slot) | ✅ |
|
||||
| MQTT Print-Payload `ams_settings.ams_box_mapping` (nested) | ✅ |
|
||||
| Print-History in SQLite | ✅ |
|
||||
| Multi-Printer Support (Drucker-Tab + Header-Dropdown) | ✅ |
|
||||
| **Multi-Printer in einer Bridge-Instanz** (ein Prozess, N Listener) | ✅ |
|
||||
| Drucker-Emulator (`_archive/tools/kx_printer_emulator.py`) | ✅ |
|
||||
| i18n DE/EN für alle neuen UI-Elemente | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## Multi-Printer-Setup
|
||||
|
||||
Eine Bridge-Instanz kann jetzt mehrere Drucker gleichzeitig verwalten — ein Prozess,
|
||||
N MQTT-Verbindungen, N HTTP-Listener, geteilte SQLite + GCode-Verzeichnis.
|
||||
|
||||
### Konfiguration
|
||||
|
||||
In `config/config.ini` pro Drucker eine `[printer_N]`-Sektion anlegen:
|
||||
|
||||
```ini
|
||||
[printer_1]
|
||||
name = Kobra X
|
||||
printer_ip = <DRUCKER_IP_1>
|
||||
mqtt_port = 9883
|
||||
username = <MQTT_USER>
|
||||
password = <MQTT_PASSWORT>
|
||||
mode_id = 20030
|
||||
device_id = <DEVICE_ID_1>
|
||||
http_port = 7125
|
||||
|
||||
[printer_2]
|
||||
name = Drucker 2
|
||||
printer_ip = <DRUCKER_IP_2>
|
||||
mqtt_port = 9883
|
||||
username = <MQTT_USER>
|
||||
password = <MQTT_PASSWORT>
|
||||
mode_id = 20030
|
||||
device_id = <DEVICE_ID_2>
|
||||
http_port = 7126
|
||||
```
|
||||
|
||||
Credentials per `extract_credentials` oder `fetch_credentials` ermitteln (siehe Haupt-README).
|
||||
|
||||
`http_port` ist optional — Default ist `7125 + (N-1)`. Wenn keine `[printer_N]`-Sektionen
|
||||
existieren, läuft die Bridge im klassischen Einzel-Modus mit `[connection]` und einem Listener.
|
||||
|
||||
### Docker
|
||||
|
||||
`docker-compose.yml` exposed jetzt einen Port-Range `7125-7130`:
|
||||
|
||||
```yaml
|
||||
ports:
|
||||
- "7125-7130:7125-7130"
|
||||
```
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
# Drucker 1: http://localhost:7125
|
||||
# Drucker 2: http://localhost:7126
|
||||
```
|
||||
|
||||
OrcaSlicer / Mainsail richten den Klipper-Endpunkt pro Drucker auf den jeweiligen Port —
|
||||
keine Slicer-Anpassungen nötig.
|
||||
|
||||
---
|
||||
|
||||
## Stabile Version
|
||||
|
||||
Für den produktiven Einsatz bitte die stabile Version verwenden:
|
||||
[→ Zum stabilen Release](https://gitea.it-drui.de/viewit/KX-Bridge-Release/releases)
|
||||
61
README.es.md
61
README.es.md
@@ -20,7 +20,7 @@ ninguna está oficialmente probada ni soportada. Se agradece el feedback.</sub>
|
||||
|
||||
[](https://gitea.it-drui.de/viewit/KX-Bridge-Release/releases)
|
||||
|
||||
[](https://gitea.it-drui.de/viewit/KX-Bridge-Release/releases)
|
||||
[](https://gitea.it-drui.de/viewit/KX-Bridge-Release/releases)
|
||||
|
||||
[](https://www.youtube.com/watch?v=1Ql4wfH27fM)
|
||||
|
||||
@@ -28,11 +28,6 @@ ninguna está oficialmente probada ni soportada. Se agradece el feedback.</sub>
|
||||
|
||||
</div>
|
||||
|
||||
> [!CAUTION]
|
||||
> **Trabajos de mantenimiento en curso** — Estamos reestructurando el repositorio (modelo de ramas, flujos CI, proceso de contribución). Es posible que notes cambios en los nombres de ramas, plantillas de PR y la forma en que se publican las versiones. Pedimos disculpas por las molestias. El manejo, el flujo de trabajo y la mantenibilidad a largo plazo mejorarán considerablemente.
|
||||
>
|
||||
> 👉 ¿Quieres contribuir? Por favor lee [CONTRIBUTING.md](CONTRIBUTING.md) primero.
|
||||
|
||||
---
|
||||
|
||||
## ✨ Características
|
||||
@@ -49,7 +44,7 @@ ninguna está oficialmente probada ni soportada. Se agradece el feedback.</sub>
|
||||
| 🧩 | **Multi-impresora** — múltiples impresoras en **una** instancia del puente, cambia mediante un menú desplegable |
|
||||
| ➕ | **Añade una impresora con un clic** — solo introduce la IP, las credenciales se importan automáticamente |
|
||||
| 🔁 | **Reconexión MQTT robusta** — el puente sobrevive a reinicios nocturnos de la impresora sin reinicio manual |
|
||||
| 🌐 | **Interfaz multilingüe** — DE / EN / ES / FR / IT / 中文, detecta automáticamente el idioma del navegador |
|
||||
| 🌐 | **Interfaz multilingüe** — DE / EN / ES / 中文, detecta automáticamente el idioma del navegador |
|
||||
| 🔄 | **Actualización automática** — instala nuevas versiones directamente desde el navegador |
|
||||
| 🧠 | **OrcaSlicer** — protocolo Moonraker completo (HTTP + WebSocket); usa el [build OrcaSlicer-KX](#-slicer-recomendado) para emparejamiento correcto de vendor por ranura |
|
||||
|
||||
@@ -71,17 +66,43 @@ docker compose up -d
|
||||
|
||||
**Binario Linux (sin Docker):**
|
||||
```bash
|
||||
chmod +x kx-bridge && ./kx-bridge
|
||||
chmod +x kx-bridge-linux-amd64 && ./kx-bridge-linux-amd64
|
||||
```
|
||||
|
||||
**EXE Windows (sin Docker):**
|
||||
```
|
||||
kx-bridge.exe
|
||||
```
|
||||
> `config\` y `data\` se crean junto al EXE — instalación portátil.
|
||||
|
||||
> Con los binarios de Linux y Windows, `config/` y `data/` (configuración, SQLite, almacén de GCode)
|
||||
> viven junto al programa. Copia toda la carpeta para mover la instalación.
|
||||
> ⚠️ **Certificados TLS necesarios para el binario standalone**
|
||||
>
|
||||
> El bridge habla con el MQTT de la impresora vía mTLS y necesita dos
|
||||
> ficheros de certificado **junto al binario**:
|
||||
>
|
||||
> - `anycubic_slicer.crt`
|
||||
> - `anycubic_slicer.key`
|
||||
>
|
||||
> Ambos vienen en **`anycubic-certs.zip`** en la misma página de release.
|
||||
> Descárgalo y extrae los dos ficheros en el mismo directorio que
|
||||
> `kx-bridge-linux-amd64` o `kx-bridge.exe`. Sin ellos verás
|
||||
> `Verbindung fehlgeschlagen: TLS-Zertifikate fehlen …` (0.9.19.1+) o
|
||||
> `[Errno 2] No such file or directory` (versiones anteriores).
|
||||
>
|
||||
> Estructura correcta:
|
||||
> ```
|
||||
> ~/kx-bridge/
|
||||
> ├── kx-bridge-linux-amd64 (o kx-bridge.exe)
|
||||
> ├── anycubic_slicer.crt ← de anycubic-certs.zip
|
||||
> ├── anycubic_slicer.key ← de anycubic-certs.zip
|
||||
> └── config/ (se crea en el primer arranque)
|
||||
> ```
|
||||
>
|
||||
> Los usuarios de Docker no necesitan hacer esto — los certificados
|
||||
> están incluidos en la imagen.
|
||||
|
||||
> Con los binarios de Linux y Windows, `config/` y `data/` (configuración,
|
||||
> SQLite, almacén de GCode) viven junto al programa. Copia toda la carpeta
|
||||
> para mover la instalación.
|
||||
|
||||
**Python directamente:**
|
||||
```bash
|
||||
@@ -187,24 +208,6 @@ docker compose up -d --build # recompilar localmente (en lugar de desc
|
||||
|
||||
---
|
||||
|
||||
## 🌙 Builds nocturnos (Nightly)
|
||||
|
||||
Los builds nocturnos contienen las últimas funciones no publicadas y se generan automáticamente en cada push de desarrollo.
|
||||
Pueden ser inestables — úsalos para pruebas o acceso anticipado a nuevas funciones.
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.yml -f docker-compose.nightly.yml pull
|
||||
docker compose -f docker-compose.yml -f docker-compose.nightly.yml up -d
|
||||
```
|
||||
|
||||
Volver a la versión estable:
|
||||
|
||||
```bash
|
||||
docker compose pull && docker compose up -d
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🩹 Solución de problemas
|
||||
|
||||
<details>
|
||||
|
||||
27
README.md
27
README.md
@@ -20,7 +20,7 @@ officially tested or supported. Feedback welcome.</sub>
|
||||
|
||||
[](https://gitea.it-drui.de/viewit/KX-Bridge-Release/releases)
|
||||
|
||||
[](https://gitea.it-drui.de/viewit/KX-Bridge-Release/releases)
|
||||
[](https://gitea.it-drui.de/viewit/KX-Bridge-Release/releases)
|
||||
|
||||
[](https://www.youtube.com/watch?v=1Ql4wfH27fM)
|
||||
|
||||
@@ -28,11 +28,6 @@ officially tested or supported. Feedback welcome.</sub>
|
||||
|
||||
</div>
|
||||
|
||||
> [!CAUTION]
|
||||
> **Ongoing maintenance work** — We are restructuring the repository (branch model, CI workflows, contribution process). You may notice changes to branch names, PR templates, and how releases are published. We apologise for any inconvenience. Handling, workflow, and long-term maintainability will be significantly improved as a result.
|
||||
>
|
||||
> 👉 Want to contribute? Please read [CONTRIBUTING.md](CONTRIBUTING.md) first.
|
||||
|
||||
---
|
||||
|
||||
## ✨ Features
|
||||
@@ -49,7 +44,7 @@ officially tested or supported. Feedback welcome.</sub>
|
||||
| 🧩 | **Multi-printer** — multiple printers in **one** bridge instance, switch via dropdown |
|
||||
| ➕ | **Add a printer with one click** — just enter the IP, credentials are imported automatically |
|
||||
| 🔁 | **Robust MQTT reconnect** — bridge survives overnight printer reboots without manual restart |
|
||||
| 🌐 | **Multi-language UI** — DE / EN / ES / FR / IT / 中文, auto-detect browser locale |
|
||||
| 🌐 | **Multi-language UI** — DE / EN / ES / 中文, auto-detect browser locale |
|
||||
| 🔄 | **Self-update** — install new versions directly in the browser |
|
||||
| 🧠 | **OrcaSlicer** — full Moonraker protocol (HTTP + WebSocket); pair with the [OrcaSlicer-KX build](#-recommended-slicer) for proper per-slot vendor matching |
|
||||
|
||||
@@ -187,24 +182,6 @@ docker compose up -d --build # rebuild locally (instead of pulling)
|
||||
|
||||
---
|
||||
|
||||
## 🌙 Nightly Builds
|
||||
|
||||
Nightly builds contain the latest unreleased features and are built automatically on every development push.
|
||||
They may be unstable — use them for testing or early access to new functionality.
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.yml -f docker-compose.nightly.yml pull
|
||||
docker compose -f docker-compose.yml -f docker-compose.nightly.yml up -d
|
||||
```
|
||||
|
||||
To go back to the stable release:
|
||||
|
||||
```bash
|
||||
docker compose pull && docker compose up -d
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🩹 Troubleshooting
|
||||
|
||||
<details>
|
||||
|
||||
31
agents.md
31
agents.md
@@ -1,31 +0,0 @@
|
||||
# KX-Bridge Claude Agents
|
||||
|
||||
## Available Agents
|
||||
|
||||
| Agent | File | When to use |
|
||||
|---|---|---|
|
||||
| Reviewer | `.claude/agents/reviewer.md` | Before every PR — checks logic, error handling, Moonraker compatibility |
|
||||
| Changelog | `.claude/agents/changelog.md` | After merge to nightly — generates CHANGELOG.md entry from commits |
|
||||
| Test Writer | `.claude/agents/test-writer.md` | When adding new functions — derives pytest tests |
|
||||
| Nightly Prep | `.claude/agents/nightly-prep.md` | Before a release — checks readiness of nightly → main merge |
|
||||
| Docker Check | `.claude/agents/docker-check.md` | Before image push — validates Dockerfile and compose config |
|
||||
| Moonraker Debug | `.claude/agents/moonraker-debug.md` | On runtime errors — analyzes Moonraker/Klipper logs |
|
||||
|
||||
## Usage
|
||||
|
||||
In VS Code with Claude Code extension:
|
||||
```
|
||||
@reviewer → code review of current changes
|
||||
@changelog → generate CHANGELOG entry
|
||||
@test-writer → write tests for changed files
|
||||
@nightly-prep → check release readiness
|
||||
@docker-check → validate Docker config
|
||||
@moonraker-debug → analyze logs
|
||||
```
|
||||
|
||||
## Context
|
||||
|
||||
- Moonraker API: Port 7125
|
||||
- AFC lane_data: flat indexing lane1–lane4
|
||||
- Registry: `gitea.it-drui.de/viewit/kx-bridge`
|
||||
- Default PR target: `nightly`
|
||||
@@ -31,88 +31,6 @@ default_ams_slot = auto
|
||||
# Auto-Leveling vor jedem Druck (1 = an, 0 = aus)
|
||||
auto_leveling = 1
|
||||
|
||||
# Kamera-Stream bei Druckstart automatisch einschalten (1 = an, 0 = aus)
|
||||
camera_on_print = 0
|
||||
|
||||
# Warnung vor Druck von Web-Uploads (1 = an, 0 = aus)
|
||||
web_upload_warning = 1
|
||||
|
||||
# Nach Upload: Filament/Color-Selector automatisch öffnen (1 = an, 0 = aus)
|
||||
print_start_dialog = 1
|
||||
|
||||
# ─── Filament-Profile pro AMS-Slot (optional) ────────────────────────────────
|
||||
# Beim Slicer-Sync nimmt OrcaSlicer per Default immer "Generic PLA/PETG/...".
|
||||
# Mit diesen Mappings sendet die Bridge die konkrete Orca-Filament-ID +
|
||||
# Vendor mit (Anzeige im Slicer dann z.B. "PolyTerra PLA — Polymaker" statt
|
||||
# nur "Generic PLA"). Mapping wird über die Web-UI gepflegt.
|
||||
# Beispiel:
|
||||
# [filament_profiles]
|
||||
# slot_0_id = OGFL01
|
||||
# slot_0_vendor = Polymaker
|
||||
# slot_1_id = OGFG23
|
||||
# slot_1_vendor = Polymaker
|
||||
|
||||
[bridge]
|
||||
# Poll-Intervall in Sekunden
|
||||
poll_interval = 3
|
||||
|
||||
# ─── Multi-Printer (optional) ──────────────────────────────────────────────────
|
||||
# Mehrere Drucker können als [printer_1], [printer_2], … definiert werden.
|
||||
# Jede Bridge-Instanz verbindet sich mit einem Drucker (je eigener Port).
|
||||
# bridge_url zeigt auf die jeweilige Bridge-Instanz (für den /kx/printers-Endpunkt).
|
||||
# Die [connection]-Sektion wird weiterhin als Fallback für diese Instanz verwendet.
|
||||
#
|
||||
# Beispiel:
|
||||
# [printer_1]
|
||||
# name = Kobra X Links
|
||||
# bridge_url = http://192.168.178.95:7125
|
||||
# printer_ip = 192.168.178.95
|
||||
# mqtt_port = 9883
|
||||
# username = userXXXXXXXXXX
|
||||
# password = XXXXXXXXXXXXXXX
|
||||
# device_id = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
# mode_id = 20030
|
||||
#
|
||||
# [printer_2]
|
||||
# name = Kobra X Rechts
|
||||
# bridge_url = http://192.168.178.96:7125
|
||||
# printer_ip = 192.168.178.96
|
||||
# mqtt_port = 9883
|
||||
# username = userYYYYYYYYYY
|
||||
# password = YYYYYYYYYYYYYYY
|
||||
# device_id = yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
|
||||
# mode_id = 20030
|
||||
|
||||
[ace_dry_presets]
|
||||
# Vordefinierte Dry-Set Presets (Temp in °C, Dauer in Sekunden)
|
||||
pla_temp = 45
|
||||
pla_duration_sec = 14400
|
||||
pla_plus_temp = 45
|
||||
pla_plus_duration_sec = 14400
|
||||
petg_temp = 50
|
||||
petg_duration_sec = 14400
|
||||
tpu_temp = 55
|
||||
tpu_duration_sec = 14400
|
||||
abs_asa_temp = 45
|
||||
abs_asa_duration_sec = 28800
|
||||
pa_pc_temp = 55
|
||||
pa_pc_duration_sec = 43200
|
||||
|
||||
# Custom Presets (Name + Temp + Dauer)
|
||||
custom_1_name = Custom 1
|
||||
custom_1_temp = 45
|
||||
custom_1_duration_sec = 14400
|
||||
custom_2_name = Custom 2
|
||||
custom_2_temp = 45
|
||||
custom_2_duration_sec = 14400
|
||||
custom_3_name = Custom 3
|
||||
custom_3_temp = 45
|
||||
custom_3_duration_sec = 14400
|
||||
|
||||
[spoolman]
|
||||
# URL der Spoolman-Instanz (leer lassen um Spoolman zu deaktivieren)
|
||||
# server = http://192.168.x.x:7912
|
||||
|
||||
# Wie oft (Sekunden) der Filamentverbrauch während des Drucks gemeldet wird
|
||||
# (0 = nur beim Druckende)
|
||||
# sync_rate = 0
|
||||
|
||||
@@ -1,210 +0,0 @@
|
||||
# KobraX Full Stack — KX-Bridge + Obico Self-Hosted + Spoolman
|
||||
#
|
||||
# Für Portainer: Stack → Add Stack → Upload → diese Datei wählen
|
||||
#
|
||||
# Voraussetzung: Obico-Images einmalig in Gitea-Registry pushen:
|
||||
# docker tag obico-server-web:latest gitea.it-drui.de/viewit/obico-web:latest
|
||||
# docker tag obico-server-ml_api:latest gitea.it-drui.de/viewit/obico-ml:latest
|
||||
# docker tag obico-server-tasks:latest gitea.it-drui.de/viewit/obico-tasks:latest
|
||||
# docker push gitea.it-drui.de/viewit/obico-web:latest
|
||||
# docker push gitea.it-drui.de/viewit/obico-ml:latest
|
||||
# docker push gitea.it-drui.de/viewit/obico-tasks:latest
|
||||
#
|
||||
# Persistente Daten: /mnt/dockerdata/KobraXStack/<service>/
|
||||
#
|
||||
# Ports:
|
||||
# 7125 — KX-Bridge (Moonraker-API)
|
||||
# 3334 — Obico (Web-UI)
|
||||
# 7912 — Spoolman (Web-UI)
|
||||
#
|
||||
# Obico Admin-Account nach dem ersten Start:
|
||||
# docker exec obico-web python manage.py createsuperuser
|
||||
|
||||
x-obico-base: &obico-base
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- /mnt/dockerdata/KobraXStack/obico/data:/data
|
||||
- /mnt/dockerdata/KobraXStack/obico/frontend:/frontend
|
||||
depends_on:
|
||||
- obico-redis
|
||||
environment:
|
||||
DEBUG: "False"
|
||||
REDIS_URL: "redis://obico-redis:6379"
|
||||
DATABASE_URL: "sqlite:////data/db.sqlite3"
|
||||
INTERNAL_MEDIA_HOST: "http://obico-web:3334"
|
||||
ML_API_HOST: "http://obico-ml:3333"
|
||||
ACCOUNT_ALLOW_SIGN_UP: "False"
|
||||
SITE_USES_HTTPS: "False"
|
||||
SITE_IS_PUBLIC: "False"
|
||||
DJANGO_SECRET_KEY: "change-me-to-a-random-secret-key-before-use"
|
||||
WEBPACK_LOADER_ENABLED: "False"
|
||||
networks:
|
||||
- kobrax-stack
|
||||
|
||||
services:
|
||||
|
||||
# ── KX-Bridge ───────────────────────────────────────────────
|
||||
kx-bridge:
|
||||
image: gitea.it-drui.de/viewit/kx-bridge:latest
|
||||
container_name: kx-bridge
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "7125:7125"
|
||||
volumes:
|
||||
- /mnt/dockerdata/KobraXStack/kx-bridge/config:/app/config
|
||||
- /mnt/dockerdata/KobraXStack/kx-bridge/data:/app/data
|
||||
networks:
|
||||
- kobrax-stack
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# ── Spoolman ────────────────────────────────────────────────
|
||||
spoolman:
|
||||
image: ghcr.io/donkie/spoolman:latest
|
||||
container_name: spoolman
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "7912:8000"
|
||||
volumes:
|
||||
- /mnt/dockerdata/KobraXStack/spoolman:/home/app/.local/share/spoolman
|
||||
networks:
|
||||
- kobrax-stack
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# ── Obico Redis ─────────────────────────────────────────────
|
||||
obico-redis:
|
||||
image: redis:7.2-alpine
|
||||
container_name: obico-redis
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- /mnt/dockerdata/KobraXStack/obico/redis:/data
|
||||
networks:
|
||||
- kobrax-stack
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
start_period: 10s
|
||||
interval: 15s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "5m"
|
||||
max-file: "2"
|
||||
|
||||
# ── Obico ML API ────────────────────────────────────────────
|
||||
obico-ml:
|
||||
image: gitea.it-drui.de/viewit/obico-ml:latest
|
||||
container_name: obico-ml
|
||||
restart: unless-stopped
|
||||
command: bash -c "gunicorn --bind 0.0.0.0:3333 --workers 1 wsgi"
|
||||
working_dir: /app
|
||||
environment:
|
||||
DEBUG: "False"
|
||||
FLASK_APP: "server.py"
|
||||
networks:
|
||||
- kobrax-stack
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget -q --spider http://127.0.0.1:3333/hc/ || exit 1"]
|
||||
start_period: 30s
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# ── Obico Web ───────────────────────────────────────────────
|
||||
obico-web:
|
||||
<<: *obico-base
|
||||
image: gitea.it-drui.de/viewit/obico-web:latest
|
||||
container_name: obico-web
|
||||
ports:
|
||||
- "3334:3334"
|
||||
depends_on:
|
||||
- obico-ml
|
||||
- obico-redis
|
||||
command: >
|
||||
sh -c 'python manage.py migrate &&
|
||||
python manage.py shell -c "from django.contrib.sites.models import Site; s=Site.objects.first(); s.domain=\"192.168.178.204:3334\"; s.name=\"Obico\"; s.save()" &&
|
||||
python manage.py collectstatic --noinput &&
|
||||
daphne -b 0.0.0.0 -p 3334 config.routing:application'
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget -q --spider http://127.0.0.1:3334/hc/ || exit 1"]
|
||||
start_period: 60s
|
||||
interval: 90s
|
||||
timeout: 20s
|
||||
retries: 3
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# ── Obico Tasks (Celery) ────────────────────────────────────
|
||||
obico-tasks:
|
||||
<<: *obico-base
|
||||
image: gitea.it-drui.de/viewit/obico-tasks:latest
|
||||
container_name: obico-tasks
|
||||
command: sh -c "celery -A config worker --beat -l info -c 2 -Q realtime,celery"
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# ── moonraker-obico Plugin ──────────────────────────────────
|
||||
# Verbindet KX-Bridge mit dem Obico-Server (Spaghetti-Detektion, Remote-UI)
|
||||
# Voraussetzung: /mnt/dockerdata/KobraXStack/moonraker-obico/moonraker-obico.cfg
|
||||
# muss existieren und einen gültigen auth_token enthalten.
|
||||
#
|
||||
# Token holen (nach erstem obico-web Start):
|
||||
# docker exec obico-web python manage.py shell -c "
|
||||
# from app.models import OneTimeVerificationCode, User
|
||||
# from django.utils import timezone; from datetime import timedelta; import random
|
||||
# u = User.objects.first()
|
||||
# c = OneTimeVerificationCode.objects.create(user=u, code='%06d' % random.randint(100000,999999), expired_at=timezone.now()+timedelta(hours=2))
|
||||
# print('CODE:', c.code)"
|
||||
# curl -X POST 'http://localhost:3334/api/v1/octo/verify/?code=<CODE>'
|
||||
# → printer.auth_token aus der Antwort in die cfg eintragen
|
||||
moonraker-obico:
|
||||
image: gitea.it-drui.de/viewit/moonraker-obico:latest
|
||||
container_name: moonraker-obico
|
||||
restart: unless-stopped
|
||||
network_mode: host
|
||||
volumes:
|
||||
- /mnt/dockerdata/KobraXStack/moonraker-obico:/opt/printer_data/config
|
||||
- /mnt/dockerdata/KobraXStack/moonraker-obico/logs:/opt/printer_data/logs
|
||||
command: ["-c", "/opt/printer_data/config/moonraker-obico.cfg"]
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
networks:
|
||||
kobrax-stack:
|
||||
driver: bridge
|
||||
|
||||
# Verzeichnisse müssen auf dem Host existieren:
|
||||
# mkdir -p /mnt/dockerdata/KobraXStack/kx-bridge/config \
|
||||
# /mnt/dockerdata/KobraXStack/kx-bridge/data \
|
||||
# /mnt/dockerdata/KobraXStack/spoolman \
|
||||
# /mnt/dockerdata/KobraXStack/obico/data \
|
||||
# /mnt/dockerdata/KobraXStack/obico/frontend \
|
||||
# /mnt/dockerdata/KobraXStack/obico/redis \
|
||||
# /mnt/dockerdata/KobraXStack/moonraker-obico/logs
|
||||
# Spoolman benötigt UID/GID 1000:
|
||||
# sudo chown -R 1000:1000 /mnt/dockerdata/KobraXStack/spoolman
|
||||
#
|
||||
# moonraker-obico Config anlegen (auth_token nach Obico-Setup eintragen):
|
||||
# cp /path/to/moonraker-obico.cfg.example /mnt/dockerdata/KobraXStack/moonraker-obico/moonraker-obico.cfg
|
||||
@@ -602,23 +602,10 @@ class CameraCache:
|
||||
self._task_jpeg: "asyncio.Task | None" = None
|
||||
self._task_h264: "asyncio.Task | None" = None
|
||||
self._lock = asyncio.Lock()
|
||||
self._fail_count_jpeg: int = 0
|
||||
self._fail_count_h264: int = 0
|
||||
|
||||
def set_url(self, url: str):
|
||||
self._url = url
|
||||
|
||||
def reset(self):
|
||||
"""Backoff-Zähler zurücksetzen und laufende ffmpeg-Prozesse killen."""
|
||||
self._fail_count_jpeg = 0
|
||||
self._fail_count_h264 = 0
|
||||
for proc in (self._proc_jpeg, self._proc_h264):
|
||||
if proc is not None:
|
||||
try:
|
||||
proc.kill()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
async def ensure_running(self):
|
||||
if self._proc_jpeg is None or self._proc_jpeg.returncode is not None:
|
||||
self._task_jpeg = asyncio.create_task(self._run_jpeg_loop())
|
||||
@@ -642,13 +629,13 @@ class CameraCache:
|
||||
continue
|
||||
try:
|
||||
self._proc_jpeg = await asyncio.create_subprocess_exec(
|
||||
_find_ffmpeg(), "-loglevel", "warning",
|
||||
_find_ffmpeg(), "-loglevel", "quiet",
|
||||
*self._input_args(url), "-i", url,
|
||||
"-vf", "fps=2",
|
||||
"-f", "image2pipe", "-vcodec", "mjpeg", "-q:v", "3",
|
||||
"-flush_packets", "1", "pipe:1",
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.DEVNULL,
|
||||
)
|
||||
except Exception as e:
|
||||
log.warning(f"CameraCache: ffmpeg-jpeg start fehlgeschlagen: {e}")
|
||||
@@ -656,7 +643,6 @@ class CameraCache:
|
||||
continue
|
||||
|
||||
buf = b""
|
||||
rc = None
|
||||
try:
|
||||
while True:
|
||||
chunk = await self._proc_jpeg.stdout.read(self.TS_CHUNK)
|
||||
@@ -690,23 +676,8 @@ class CameraCache:
|
||||
await self._proc_jpeg.wait()
|
||||
except Exception:
|
||||
pass
|
||||
rc = self._proc_jpeg.returncode
|
||||
if rc:
|
||||
try:
|
||||
err = await self._proc_jpeg.stderr.read(500)
|
||||
if err:
|
||||
log.warning(f"CameraCache: ffmpeg-jpeg stderr: {err.decode(errors='replace').strip()}")
|
||||
except Exception:
|
||||
pass
|
||||
self._proc_jpeg = None
|
||||
if rc:
|
||||
self._fail_count_jpeg += 1
|
||||
delay = min(2.0 * (2 ** self._fail_count_jpeg), 300.0)
|
||||
log.warning(f"CameraCache: ffmpeg-jpeg exit {rc}, retry in {delay:.0f}s (Versuch {self._fail_count_jpeg})")
|
||||
await asyncio.sleep(delay)
|
||||
else:
|
||||
self._fail_count_jpeg = 0
|
||||
await asyncio.sleep(2.0)
|
||||
await asyncio.sleep(2.0) # restart delay
|
||||
|
||||
async def _run_h264_loop(self):
|
||||
"""Hält einen ffmpeg-Prozess am Leben der MPEG-TS an alle Subscriber fanoutet."""
|
||||
@@ -717,19 +688,18 @@ class CameraCache:
|
||||
continue
|
||||
try:
|
||||
self._proc_h264 = await asyncio.create_subprocess_exec(
|
||||
_find_ffmpeg(), "-loglevel", "warning",
|
||||
_find_ffmpeg(), "-loglevel", "quiet",
|
||||
*self._input_args(url), "-i", url,
|
||||
"-c:v", "copy", "-an",
|
||||
"-f", "mpegts", "pipe:1",
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.DEVNULL,
|
||||
)
|
||||
except Exception as e:
|
||||
log.warning(f"CameraCache: ffmpeg-h264 start fehlgeschlagen: {e}")
|
||||
await asyncio.sleep(3.0)
|
||||
continue
|
||||
|
||||
rc = None
|
||||
try:
|
||||
while True:
|
||||
chunk = await self._proc_h264.stdout.read(self.TS_CHUNK)
|
||||
@@ -759,23 +729,8 @@ class CameraCache:
|
||||
await self._proc_h264.wait()
|
||||
except Exception:
|
||||
pass
|
||||
rc = self._proc_h264.returncode
|
||||
if rc:
|
||||
try:
|
||||
err = await self._proc_h264.stderr.read(500)
|
||||
if err:
|
||||
log.warning(f"CameraCache: ffmpeg-h264 stderr: {err.decode(errors='replace').strip()}")
|
||||
except Exception:
|
||||
pass
|
||||
self._proc_h264 = None
|
||||
if rc:
|
||||
self._fail_count_h264 += 1
|
||||
delay = min(2.0 * (2 ** self._fail_count_h264), 300.0)
|
||||
log.warning(f"CameraCache: ffmpeg-h264 exit {rc}, retry in {delay:.0f}s (Versuch {self._fail_count_h264})")
|
||||
await asyncio.sleep(delay)
|
||||
else:
|
||||
self._fail_count_h264 = 0
|
||||
await asyncio.sleep(2.0)
|
||||
await asyncio.sleep(2.0)
|
||||
|
||||
|
||||
class SpoolmanClient:
|
||||
@@ -3908,16 +3863,6 @@ class KobraXBridge:
|
||||
self._camera_user_stopped = True
|
||||
return web.json_response({"result": "ok"})
|
||||
|
||||
async def handle_api_camera_reset(self, request):
|
||||
"""Backoff-Zähler zurücksetzen und ffmpeg sofort neu starten.
|
||||
Nützlich nach 429-Sperre (Retry-After abgelaufen) oder nach Drucker-Neustart."""
|
||||
self.camera_cache.reset()
|
||||
url = self._state.get("camera_url", "")
|
||||
if url:
|
||||
self.camera_cache.set_url(url)
|
||||
await self.camera_cache.ensure_running()
|
||||
return web.json_response({"result": "ok"})
|
||||
|
||||
async def handle_api_camera_snapshot(self, request):
|
||||
"""Letzter JPEG-Frame aus dem CameraCache — instant aus dem RAM,
|
||||
keine eigene ffmpeg-Instanz mehr (verhindert Single-Client-429 am
|
||||
@@ -4273,8 +4218,6 @@ class KobraXBridge:
|
||||
"filament_profiles": {str(k): v for k, v in self._filament_profiles.items()},
|
||||
"visible_vendors": self._visible_vendors,
|
||||
"ace_dry_presets": self._ace_dry_presets,
|
||||
"spoolman_server": getattr(self._args, "spoolman_server", "") or "",
|
||||
"spoolman_sync_rate": getattr(self._args, "spoolman_sync_rate", 0),
|
||||
})
|
||||
|
||||
async def handle_api_settings_post(self, request):
|
||||
@@ -4289,7 +4232,7 @@ class KobraXBridge:
|
||||
cfg.read(config_path, encoding="utf-8")
|
||||
|
||||
# Sections sicherstellen
|
||||
for section in ("connection", "print", "bridge", "ace_dry_presets", "spoolman"):
|
||||
for section in ("connection", "print", "bridge", "ace_dry_presets"):
|
||||
if not cfg.has_section(section):
|
||||
cfg.add_section(section)
|
||||
|
||||
@@ -4319,16 +4262,6 @@ class KobraXBridge:
|
||||
elif cfg.has_option("bridge", "printer_name"):
|
||||
cfg.remove_option("bridge", "printer_name")
|
||||
|
||||
# Spoolman
|
||||
if "spoolman_server" in data:
|
||||
cfg.set("spoolman", "server", str(data["spoolman_server"]).strip())
|
||||
if "spoolman_sync_rate" in data:
|
||||
try:
|
||||
sr = max(0, int(data["spoolman_sync_rate"]))
|
||||
except (TypeError, ValueError):
|
||||
sr = 30
|
||||
cfg.set("spoolman", "sync_rate", str(sr))
|
||||
|
||||
incoming_presets = data.get("ace_dry_presets") if isinstance(data, dict) else None
|
||||
presets = self._sanitize_ace_dry_presets(incoming_presets if isinstance(incoming_presets, dict) else self._ace_dry_presets)
|
||||
for key, val in presets.items():
|
||||
@@ -4488,8 +4421,7 @@ class KobraXBridge:
|
||||
for _k in ("PRINTER_IP", "MQTT_PORT", "MQTT_USERNAME", "MQTT_PASSWORD",
|
||||
"MODE_ID", "DEVICE_ID", "DEFAULT_AMS_SLOT", "AUTO_LEVELING",
|
||||
"CAMERA_ON_PRINT", "WEB_UPLOAD_WARNING", "PRINT_START_DIALOG",
|
||||
"FILE_READY_DIALOG", "BRIDGE_PRINTER_NAME",
|
||||
"SPOOLMAN_SERVER", "SPOOLMAN_SYNC_RATE"):
|
||||
"FILE_READY_DIALOG", "BRIDGE_PRINTER_NAME"):
|
||||
os.environ.pop(_k, None)
|
||||
|
||||
in_docker = os.path.exists("/.dockerenv") or os.environ.get("KX_IN_DOCKER")
|
||||
@@ -4525,10 +4457,9 @@ class KobraXBridge:
|
||||
|
||||
# ─── Update ──────────────────────────────────────────────────────────────
|
||||
|
||||
STABLE_RELEASE_API = "https://gitea.it-drui.de/api/v1/repos/viewit/KX-Bridge-Release/releases?limit=1"
|
||||
NIGHTLY_RELEASE_API = "https://gitea.it-drui.de/api/v1/repos/viewit/KX-Bridge-Release/releases?limit=5&pre-release=true"
|
||||
DEV_RELEASE_API = "https://gitea.it-drui.de/api/v1/repos/viewit/KX-Bridge-Release/releases?limit=10&pre-release=true"
|
||||
GITEA_RAW_BASE = "https://gitea.it-drui.de/viewit/KX-Bridge-Release/raw/tag"
|
||||
STABLE_RELEASE_API = "https://gitea.it-drui.de/api/v1/repos/viewit/KX-Bridge-Release/releases?limit=1"
|
||||
DEV_RELEASE_API = "https://gitea.it-drui.de/api/v1/repos/viewit/KX-Bridge-Release/releases?limit=10&pre-release=true"
|
||||
GITEA_RAW_BASE = "https://gitea.it-drui.de/viewit/KX-Bridge-Release/raw/tag"
|
||||
|
||||
def _read_version(self) -> str:
|
||||
# PyInstaller-Onefile entpackt VERSION (per kx-bridge.spec datas) nach
|
||||
@@ -4603,14 +4534,8 @@ class KobraXBridge:
|
||||
|
||||
async def handle_api_update_check(self, request):
|
||||
current = self._read_version()
|
||||
is_nightly = "nightly" in current
|
||||
is_dev = "-dev+" in current
|
||||
if is_nightly:
|
||||
api_url = self.NIGHTLY_RELEASE_API
|
||||
elif is_dev:
|
||||
api_url = self.DEV_RELEASE_API
|
||||
else:
|
||||
api_url = self.STABLE_RELEASE_API
|
||||
api_url = self.DEV_RELEASE_API if is_dev else self.STABLE_RELEASE_API
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(api_url, timeout=aiohttp.ClientTimeout(total=10)) as resp:
|
||||
@@ -4619,37 +4544,14 @@ class KobraXBridge:
|
||||
releases = await resp.json(content_type=None)
|
||||
if not releases:
|
||||
return web.json_response({"error": "Keine Releases gefunden"}, status=404)
|
||||
|
||||
if is_nightly:
|
||||
# Neuestes Prerelease mit nightly-Tag suchen
|
||||
nightly_releases = [r for r in releases if r.get("prerelease") and "nightly" in r.get("tag_name", "")]
|
||||
if not nightly_releases:
|
||||
return web.json_response({"error": "Keine Nightly-Releases gefunden"}, status=404)
|
||||
data = nightly_releases[0]
|
||||
tag = data.get("tag_name", "")
|
||||
# Tag-Format: "nightly-0.9.27-nightly4", current: "0.9.27-nightly4"
|
||||
tag_version = tag[len("nightly-"):] if tag.startswith("nightly-") else tag
|
||||
update_available = tag_version != current
|
||||
latest = tag
|
||||
return web.json_response({
|
||||
"current": current,
|
||||
"latest": latest,
|
||||
"update_available": update_available,
|
||||
"tag": tag,
|
||||
"docker_only": True,
|
||||
"changelog": data.get("body", ""),
|
||||
})
|
||||
elif is_dev:
|
||||
# Dev: neuestes Release mit "-dev+" im Tag suchen
|
||||
if is_dev:
|
||||
dev_releases = [r for r in releases if "-dev+" in r.get("tag_name", "")]
|
||||
if not dev_releases:
|
||||
return web.json_response({"error": "Keine Dev-Releases gefunden"}, status=404)
|
||||
data = dev_releases[0]
|
||||
else:
|
||||
# Stable: nur non-prerelease nehmen
|
||||
stable_releases = [r for r in releases if not r.get("prerelease")]
|
||||
if not stable_releases:
|
||||
return web.json_response({"error": "Keine Stable-Releases gefunden"}, status=404)
|
||||
data = stable_releases[0]
|
||||
data = releases[0]
|
||||
tag = data.get("tag_name", "")
|
||||
latest = tag.lstrip("v")
|
||||
if is_dev:
|
||||
@@ -4663,7 +4565,6 @@ class KobraXBridge:
|
||||
"update_available": update_available,
|
||||
"tag": tag,
|
||||
"download_url": download_url,
|
||||
"docker_only": False,
|
||||
"changelog": data.get("body", ""),
|
||||
})
|
||||
except Exception as e:
|
||||
@@ -4684,10 +4585,6 @@ class KobraXBridge:
|
||||
async def handle_api_update_apply(self, request):
|
||||
data = await request.json()
|
||||
new_tag = data.get("tag", "")
|
||||
if "nightly" in self._read_version():
|
||||
return web.json_response(
|
||||
{"error": "Nightly-Updates laufen über Docker: "
|
||||
"docker compose pull && docker compose up -d"}, status=400)
|
||||
if getattr(sys, "frozen", False):
|
||||
return web.json_response(
|
||||
{"error": "Self-Update wird im Binary-Modus nicht unterstützt – "
|
||||
@@ -5223,7 +5120,6 @@ def build_app(bridge: KobraXBridge) -> web.Application:
|
||||
r.add_get("/api/camera/snapshot", bridge.handle_api_camera_snapshot)
|
||||
r.add_post("/api/camera/start", bridge.handle_api_camera_start)
|
||||
r.add_post("/api/camera/stop", bridge.handle_api_camera_stop)
|
||||
r.add_post("/api/camera/reset", bridge.handle_api_camera_reset)
|
||||
r.add_get("/api/state", bridge.handle_api_state)
|
||||
r.add_get("/api/settings", bridge.handle_api_settings_get)
|
||||
r.add_post("/api/settings", bridge.handle_api_settings_post)
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
[server]
|
||||
url = http://127.0.0.1:3334
|
||||
auth_token = REPLACE_ME
|
||||
sentry_opt = out
|
||||
|
||||
[moonraker]
|
||||
host = 127.0.0.1
|
||||
port = 7125
|
||||
|
||||
[webcam]
|
||||
disable_video_streaming = False
|
||||
snapshot_url = http://127.0.0.1:7125/api/camera/snapshot
|
||||
stream_url = http://127.0.0.1:7125/api/camera/stream
|
||||
target_fps = 5
|
||||
|
||||
[logging]
|
||||
path = /opt/printer_data/logs/moonraker-obico.log
|
||||
level = INFO
|
||||
31
package-lock.json
generated
Normal file
31
package-lock.json
generated
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "kx-bridge",
|
||||
"version": "0.9.27",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "kx-bridge",
|
||||
"version": "0.9.27",
|
||||
"devDependencies": {
|
||||
"prettier": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.8.4",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.4.tgz",
|
||||
"integrity": "sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
23
package.json
Normal file
23
package.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "kx-bridge",
|
||||
"version": "0.9.27",
|
||||
"description": "Moonraker-compatible bridge for Anycubic Kobra X",
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"prettier": "^3.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"format:js": "prettier --write 'web/**/*.js' '*.js'",
|
||||
"format:json": "prettier --write '*.json' 'web/**/*.json'",
|
||||
"format:web": "prettier --write 'web/**/*.{js,json,html,css,yml,yaml,md}'",
|
||||
"format": "npm run format:web"
|
||||
},
|
||||
"prettier": {
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 100,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
## Description
|
||||
<!-- What does this PR change? -->
|
||||
|
||||
## Related Issue
|
||||
Closes #
|
||||
|
||||
## Type
|
||||
- [ ] Bug fix
|
||||
- [ ] Feature
|
||||
- [ ] Documentation
|
||||
- [ ] Refactoring
|
||||
|
||||
## Tested with
|
||||
- OrcaSlicer Version:
|
||||
- Printer:
|
||||
- Moonraker/Klipper Version:
|
||||
|
||||
## Checklist
|
||||
- [ ] Tests added/updated
|
||||
- [ ] CHANGELOG.md updated
|
||||
- [ ] No debug code included
|
||||
@@ -1,3 +0,0 @@
|
||||
[pytest]
|
||||
asyncio_mode = auto
|
||||
testpaths = tests
|
||||
@@ -1,11 +0,0 @@
|
||||
06873a3b6773e6e755deff33dba468dee3af09859f9b026b14a600501c8285cb anycubic-certs.zip
|
||||
68e08c6b6badb2bf86f61e2164e96b902944c885f083a598ea4e3c854c432e91 extract_credentials
|
||||
8d4d78bdc887e2512790805b711b941420536d5f0f30043b5f3bc74d4fa893d3 extract_credentials.exe
|
||||
9b79a5f8edc734018fc547ac232ddd822ec5fee050e99e3305ccf8f38b177c9e fetch_credentials
|
||||
b79615bc2f9df33bccd5471bfcd83c9bdc5291e857fe5b04927b171342bc1b48 fetch_credentials.exe
|
||||
37ae8e3f4dc7bb6cf434de980db4aed9c1047d995063ce289bdc268f7764380e kx-bridge.exe
|
||||
4f636f91fef7eb1ea1ad1ae8c7f7eaa64b725e4dc2e74db42a53ce5dc5808ba3 kx-bridge-linux-amd64
|
||||
b0ff6fa222cfbf79e0ab609aac4c9d3b1acd80b5c4df10c7847e0c7d125913bc kx-bridge-linux-amd64.zip
|
||||
0cd9448ceec03cc7f545f9c62a21b7075a866dba5ee396ec26b48816f927db1a kx-bridge-linux-arm64
|
||||
99c4641bbdcc000b2cf2142ee01c16942b13da47ea837885fa1be68e984f6101 kx-bridge-linux-arm64.zip
|
||||
31659f76913e1c785cfa9e0a9e87f893a483a6a55248006b046ac07b25b40870 kx-bridge-windows.zip
|
||||
Binary file not shown.
@@ -1,24 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEDTCCAvWgAwIBAgICAZAwDQYJKoZIhvcNAQEFBQAwgZsxCzAJBgNVBAYTAkNO
|
||||
MRIwEAYDVQQIDAlHdWFuZ2RvbmcxETAPBgNVBAcMCFNoZW56aGVuMREwDwYDVQQK
|
||||
DAhBbnljdWJpYzERMA8GA1UECwwIQW55Y3ViaWMxEzARBgNVBAMMCkFDIFJvb3Qg
|
||||
Q0ExKjAoBgkqhkiG9w0BCQEWG2FueWN1YmljX2Nsb3VkQGFueWN1YmljLmNvbTAg
|
||||
Fw0yMzA3MjAwMzI3NTFaGA8yMTIzMDcyMTAzMjc1MVowgZ8xCzAJBgNVBAYTAkNO
|
||||
MRIwEAYDVQQIDAlHdWFuZ2RvbmcxETAPBgNVBAcMCFNoZW56aGVuMREwDwYDVQQK
|
||||
DAhBbnljdWJpYzERMA8GA1UECwwIQW55Y3ViaWMxFzAVBgNVBAMMDkFueWN1Ymlj
|
||||
U2xpY2VyMSowKAYJKoZIhvcNAQkBFhthbnljdWJpY19jbG91ZEBhbnljdWJpYy5j
|
||||
b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDdoQ7g2F/yecfpdlqT
|
||||
b8W/84r3vQ4ZEWx2PbSTBcGD55HmzJp2lwABHFHbn4CltT9YzoJWpOiVMHYnyPep
|
||||
43tkNUIcGm7z0jrTD5djyYjVAzEitkNzJspKK/xcVmZe/V7Q3IAWXtzgWCd0YpVk
|
||||
K3J0HqoqJvcTSnYe4VXxbIGwbpeYyji9W/DuG1M4Z+sFiPDWeR9xo5IXRU5ZwaTP
|
||||
8OiCCLSBbeKgf0UFWTIZdJ1JXJ7efbbstZOjf5L9LhBIC0hLdL4jlMpF7r0ThecJ
|
||||
cTx9Bnw/hhy+i32rJTRzZDIaLhKg/bka9ZrORZdxxQRiPoMjLjoxtr4+AUaeLWkI
|
||||
ajSJAgMBAAGjUzBRMB0GA1UdDgQWBBRI4P3/uKdYYFPEcFIwYxdv1p9gETAfBgNV
|
||||
HSMEGDAWgBQlkDqpFERfr3u1rR9gNbNKtgrHIjAPBgNVHRMBAf8EBTADAQH/MA0G
|
||||
CSqGSIb3DQEBBQUAA4IBAQBP3ws80Y9eBR2lpjYP3rVvH8kA6+LnEXT4PpHj+fSw
|
||||
jciaNskzpiwNvBy00m32ACR5YKlMUjevlQuyyw+LQbTUwAEOwyy9SDQpiXdjL6q3
|
||||
SPQ4aB4A57nFXOGrthc/nb9yFcteWrZrKbwvVUu2vqU7U8n7lJKjhVuFRWSXS3SV
|
||||
sPc9JZ21kpPYWKbGtfD6jUlW0Ip+PurLw9FrbVwnEcOMf/ezSlrH5c8mfJyo8pVk
|
||||
aC/6PpReqijusOSRZ5oLyhPvtgddXseJFByun1Ud0CDlFA05nGGPmnVcXD+GMnHH
|
||||
i6baCTeifwp5Jpdzv4imcCPvayKUNuX32vYNfNkWC/R5
|
||||
-----END CERTIFICATE-----
|
||||
@@ -1,28 +0,0 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDdoQ7g2F/yecfp
|
||||
dlqTb8W/84r3vQ4ZEWx2PbSTBcGD55HmzJp2lwABHFHbn4CltT9YzoJWpOiVMHYn
|
||||
yPep43tkNUIcGm7z0jrTD5djyYjVAzEitkNzJspKK/xcVmZe/V7Q3IAWXtzgWCd0
|
||||
YpVkK3J0HqoqJvcTSnYe4VXxbIGwbpeYyji9W/DuG1M4Z+sFiPDWeR9xo5IXRU5Z
|
||||
waTP8OiCCLSBbeKgf0UFWTIZdJ1JXJ7efbbstZOjf5L9LhBIC0hLdL4jlMpF7r0T
|
||||
hecJcTx9Bnw/hhy+i32rJTRzZDIaLhKg/bka9ZrORZdxxQRiPoMjLjoxtr4+AUae
|
||||
LWkIajSJAgMBAAECggEASwRkC9lRiLqN30kvWW5g6hsec8KrTfLm2pMCVy2AlgxB
|
||||
B3VD51YvKzERyBwSKITT/1RPK9K/4xe3NrpAkmGsd3vLd8W+vorvXFePr7gct7VP
|
||||
4Wb+J7D+keKXlg2sswRiHqI0PN45Nzq/iBaCaJiIMiPbB0+PHBl9J/Cv7XsD3tq+
|
||||
9WKhvXf2g1g9GMrLaCCcWXWCqcu0LlbqJnw3yMnJLSltmyFTmlVLjDHM75bMVz97
|
||||
4emQzOlnRN2yA5cWWCaM+mgjNM2aWwUsXBZzCgwSqSaj1QD4B/epCuDBORWHS9D6
|
||||
jL15w8xjly9q8OS+4d6beR5h9GiPyMK4Ff2wXImCXQKBgQDwXxtrL+kVZrQ/qftj
|
||||
24F3+QDN0j5Z3lUMTfZPn6ng/E/aBfn8KcWJHj2vYkKZdB5wOXJr56BYe3Hukzfp
|
||||
QF0E2+g1WAGskF1mb/vVab54geox5Y6CA+ionRn2kcCwybVkktR/0JK2UV9Qjb/z
|
||||
k1WU+RUhNrW/GDBqYulaadnR+wKBgQDsCf2/yKGPxj4pIvAtn5RFSlfscddgkSnc
|
||||
ouBkDXEp5ta+5PGrlrdzS/F0vFhvBPbfbVJxVwRnM/Oqj8c0/bj7oc5RpPxirciO
|
||||
AaovKVPTiORaviytnB2HgkflkJfy5vdXv4ZQahAV/UwtSmLwBshe+Ya68MAFrQRa
|
||||
7M4z6k4QSwKBgQCm7OVVoofzXMeADsONrTpT3pA4XvD95/CYAuwyj2ah35Z0igH4
|
||||
o+mSN3YO/eXSO1mIBdz4Inqv98o/K+2ABjqSzUSNBvjipb63DL2Oj0i+1zmUPR6i
|
||||
G6TOs4r8OGvgWbOmjHEV8fpwskHG5ymONZsRQYjy79N3SY0V1GrJZwjlUQKBgD0x
|
||||
AeWcP7YkMK09b4KEYk3sTgrwIGPafj3Cw+VsTrAMNhPbCoPvWLO9NmWLBmoRoWae
|
||||
0sarRmry3vKSv5QPSsuBURl9aiiy4NFfwRzk2+R1Eq4rqy1+0XD152muKJZCJlFL
|
||||
R6jFNlJdDkiXhjqvp3ZnvfPswfs2tXBU/8gZsA8tAoGBALXfc5m9I5R1l1zN7tpa
|
||||
ncA0S3EKzqmuCc3KzlS6OS0e9Lz1MsmfEsvxvW3w4SrdfTbwQpEy9RNg89dlgPtc
|
||||
rdId1QdN2eWPY5M4lz9n9EYdzi9ufoKAEYu2a0lP+qz690JwmL1Jx49bvQEn5Nu0
|
||||
4swn72uwBRlhjAw46MF77SBQ
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -1,89 +0,0 @@
|
||||
pyinstaller --onefile --name extract_credentials /src/extract_credentials.py && cp /src/dist/extract_credentials.exe /out/ && pyinstaller --onefile --name fetch_credentials /src/fetch_credentials.py && cp /src/dist/fetch_credentials.exe /out/
|
||||
114 INFO: PyInstaller: 3.6
|
||||
114 INFO: Python: 3.7.5
|
||||
114 INFO: Platform: Windows-7-6.1.7601-SP1
|
||||
115 INFO: wrote Z:\src\extract_credentials.spec
|
||||
120 INFO: UPX is not available.
|
||||
121 INFO: Extending PYTHONPATH with paths
|
||||
['Z:\\src', 'Z:\\src']
|
||||
121 INFO: checking Analysis
|
||||
121 INFO: Building Analysis because Analysis-00.toc is non existent
|
||||
121 INFO: Initializing module dependency graph...
|
||||
124 INFO: Caching module graph hooks...
|
||||
130 INFO: Analyzing base_library.zip ...
|
||||
2142 INFO: Caching module dependency graph...
|
||||
2226 INFO: running Analysis Analysis-00.toc
|
||||
2234 INFO: Adding Microsoft.Windows.Common-Controls to dependent assemblies of final executable
|
||||
required by c:\Python37\python.exe
|
||||
2467 INFO: Analyzing \src\extract_credentials.py
|
||||
2497 INFO: Processing module hooks...
|
||||
2497 INFO: Loading module hook "hook-encodings.py"...
|
||||
2649 INFO: Loading module hook "hook-pydoc.py"...
|
||||
2650 INFO: Loading module hook "hook-xml.py"...
|
||||
2788 INFO: Looking for ctypes DLLs
|
||||
2791 INFO: Analyzing run-time hooks ...
|
||||
2794 INFO: Looking for dynamic libraries
|
||||
2909 INFO: Looking for eggs
|
||||
2909 INFO: Using Python library c:\Python37\python37.dll
|
||||
2909 INFO: Found binding redirects:
|
||||
[]
|
||||
2910 INFO: Warnings written to Z:\src\build\extract_credentials\warn-extract_credentials.txt
|
||||
2927 INFO: Graph cross-reference written to Z:\src\build\extract_credentials\xref-extract_credentials.html
|
||||
2939 INFO: checking PYZ
|
||||
2939 INFO: Building PYZ because PYZ-00.toc is non existent
|
||||
2939 INFO: Building PYZ (ZlibArchive) Z:\src\build\extract_credentials\PYZ-00.pyz
|
||||
3199 INFO: Building PYZ (ZlibArchive) Z:\src\build\extract_credentials\PYZ-00.pyz completed successfully.
|
||||
3203 INFO: checking PKG
|
||||
3203 INFO: Building PKG because PKG-00.toc is non existent
|
||||
3203 INFO: Building PKG (CArchive) PKG-00.pkg
|
||||
4599 INFO: Building PKG (CArchive) PKG-00.pkg completed successfully.
|
||||
4601 INFO: Bootloader c:\Python37\lib\site-packages\PyInstaller\bootloader\Windows-64bit\run.exe
|
||||
4601 INFO: checking EXE
|
||||
4601 INFO: Building EXE because EXE-00.toc is non existent
|
||||
4601 INFO: Building EXE from EXE-00.toc
|
||||
4601 INFO: Appending archive to EXE Z:\src\dist\extract_credentials.exe
|
||||
4604 INFO: Building EXE from EXE-00.toc completed successfully.
|
||||
77 INFO: PyInstaller: 3.6
|
||||
77 INFO: Python: 3.7.5
|
||||
77 INFO: Platform: Windows-7-6.1.7601-SP1
|
||||
77 INFO: wrote Z:\src\fetch_credentials.spec
|
||||
83 INFO: UPX is not available.
|
||||
84 INFO: Extending PYTHONPATH with paths
|
||||
['Z:\\src', 'Z:\\src']
|
||||
84 INFO: checking Analysis
|
||||
84 INFO: Building Analysis because Analysis-00.toc is non existent
|
||||
84 INFO: Initializing module dependency graph...
|
||||
88 INFO: Caching module graph hooks...
|
||||
94 INFO: Analyzing base_library.zip ...
|
||||
2079 INFO: Caching module dependency graph...
|
||||
2168 INFO: running Analysis Analysis-00.toc
|
||||
2170 INFO: Adding Microsoft.Windows.Common-Controls to dependent assemblies of final executable
|
||||
required by c:\Python37\python.exe
|
||||
2425 INFO: Analyzing \src\fetch_credentials.py
|
||||
2501 INFO: Processing module hooks...
|
||||
2501 INFO: Loading module hook "hook-encodings.py"...
|
||||
2630 INFO: Loading module hook "hook-pydoc.py"...
|
||||
2631 INFO: Loading module hook "hook-xml.py"...
|
||||
2765 INFO: Looking for ctypes DLLs
|
||||
2765 INFO: Analyzing run-time hooks ...
|
||||
2768 INFO: Looking for dynamic libraries
|
||||
2878 INFO: Looking for eggs
|
||||
2878 INFO: Using Python library c:\Python37\python37.dll
|
||||
2878 INFO: Found binding redirects:
|
||||
[]
|
||||
2880 INFO: Warnings written to Z:\src\build\fetch_credentials\warn-fetch_credentials.txt
|
||||
2896 INFO: Graph cross-reference written to Z:\src\build\fetch_credentials\xref-fetch_credentials.html
|
||||
2901 INFO: checking PYZ
|
||||
2901 INFO: Building PYZ because PYZ-00.toc is non existent
|
||||
2901 INFO: Building PYZ (ZlibArchive) Z:\src\build\fetch_credentials\PYZ-00.pyz
|
||||
3159 INFO: Building PYZ (ZlibArchive) Z:\src\build\fetch_credentials\PYZ-00.pyz completed successfully.
|
||||
3163 INFO: checking PKG
|
||||
3163 INFO: Building PKG because PKG-00.toc is non existent
|
||||
3163 INFO: Building PKG (CArchive) PKG-00.pkg
|
||||
4428 INFO: Building PKG (CArchive) PKG-00.pkg completed successfully.
|
||||
4430 INFO: Bootloader c:\Python37\lib\site-packages\PyInstaller\bootloader\Windows-64bit\run.exe
|
||||
4430 INFO: checking EXE
|
||||
4430 INFO: Building EXE because EXE-00.toc is non existent
|
||||
4430 INFO: Building EXE from EXE-00.toc
|
||||
4430 INFO: Appending archive to EXE Z:\src\dist\fetch_credentials.exe
|
||||
4434 INFO: Building EXE from EXE-00.toc completed successfully.
|
||||
@@ -1,56 +0,0 @@
|
||||
32 INFO: PyInstaller: 6.19.0, contrib hooks: 2026.4
|
||||
32 INFO: Python: 3.12.3
|
||||
33 INFO: Platform: Linux-6.17.0-35-generic-x86_64-with-glibc2.39
|
||||
33 INFO: Python environment: /usr
|
||||
33 INFO: wrote /home/coding/Source/KX-Bridge-Release/build/extract_credentials.spec
|
||||
34 INFO: Module search paths (PYTHONPATH):
|
||||
['/usr/lib/python312.zip',
|
||||
'/usr/lib/python3.12',
|
||||
'/usr/lib/python3.12/lib-dynload',
|
||||
'/home/coding/.local/lib/python3.12/site-packages',
|
||||
'/usr/local/lib/python3.12/dist-packages',
|
||||
'/usr/lib/python3/dist-packages',
|
||||
'/usr/lib/python3.12/dist-packages',
|
||||
'/home/coding/Source/KX-Bridge-Release']
|
||||
352 INFO: checking Analysis
|
||||
352 INFO: Building Analysis because Analysis-00.toc is non existent
|
||||
352 INFO: Looking for Python shared library...
|
||||
364 WARNING: Unrecognised line of output 'Der Cache wurde generiert von: ldconfig (Ubuntu GLIBC 2.39-0ubuntu8.7) stable release version 2.39' from ldconfig
|
||||
364 INFO: Using Python shared library: /lib/x86_64-linux-gnu/libpython3.12.so.1.0
|
||||
364 INFO: Running Analysis Analysis-00.toc
|
||||
364 INFO: Target bytecode optimization level: 0
|
||||
364 INFO: Initializing module dependency graph...
|
||||
366 INFO: Initializing module graph hook caches...
|
||||
380 INFO: Analyzing modules for base_library.zip ...
|
||||
1005 INFO: Processing standard module hook 'hook-encodings.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
|
||||
1923 INFO: Processing standard module hook 'hook-pickle.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
|
||||
2491 INFO: Processing standard module hook 'hook-heapq.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
|
||||
2739 INFO: Caching module dependency graph...
|
||||
2764 INFO: Analyzing /home/coding/Source/KX-Bridge-Release/extract_credentials.py
|
||||
2790 INFO: Processing standard module hook 'hook-platform.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
|
||||
2801 INFO: Processing standard module hook 'hook-_ctypes.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
|
||||
2808 INFO: Processing module hooks (post-graph stage)...
|
||||
2812 INFO: Performing binary vs. data reclassification (1 entries)
|
||||
2818 INFO: Looking for ctypes DLLs
|
||||
2825 INFO: Analyzing run-time hooks ...
|
||||
2825 INFO: Including run-time hook 'pyi_rth_inspect.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks/rthooks'
|
||||
2829 INFO: Creating base_library.zip...
|
||||
2841 INFO: Looking for dynamic libraries
|
||||
2987 INFO: Warnings written to /home/coding/Source/KX-Bridge-Release/build/pyinstaller/extract_credentials/warn-extract_credentials.txt
|
||||
2993 INFO: Graph cross-reference written to /home/coding/Source/KX-Bridge-Release/build/pyinstaller/extract_credentials/xref-extract_credentials.html
|
||||
3000 INFO: checking PYZ
|
||||
3000 INFO: Building PYZ because PYZ-00.toc is non existent
|
||||
3000 INFO: Building PYZ (ZlibArchive) /home/coding/Source/KX-Bridge-Release/build/pyinstaller/extract_credentials/PYZ-00.pyz
|
||||
3099 INFO: Building PYZ (ZlibArchive) /home/coding/Source/KX-Bridge-Release/build/pyinstaller/extract_credentials/PYZ-00.pyz completed successfully.
|
||||
3105 INFO: checking PKG
|
||||
3105 INFO: Building PKG because PKG-00.toc is non existent
|
||||
3105 INFO: Building PKG (CArchive) extract_credentials.pkg
|
||||
5376 INFO: Building PKG (CArchive) extract_credentials.pkg completed successfully.
|
||||
5376 INFO: Bootloader /home/coding/.local/lib/python3.12/site-packages/PyInstaller/bootloader/Linux-64bit-intel/run
|
||||
5376 INFO: checking EXE
|
||||
5376 INFO: Building EXE because EXE-00.toc is non existent
|
||||
5376 INFO: Building EXE from EXE-00.toc
|
||||
5376 INFO: Copying bootloader EXE to /home/coding/Source/KX-Bridge-Release/releases/0.9.27/extract_credentials
|
||||
5376 INFO: Appending PKG archive to custom ELF section in EXE
|
||||
5386 INFO: Building EXE from EXE-00.toc completed successfully.
|
||||
5386 INFO: Build complete! The results are available in: /home/coding/Source/KX-Bridge-Release/releases/0.9.27
|
||||
@@ -1,91 +0,0 @@
|
||||
35 INFO: PyInstaller: 6.19.0, contrib hooks: 2026.4
|
||||
35 INFO: Python: 3.12.3
|
||||
36 INFO: Platform: Linux-6.17.0-35-generic-x86_64-with-glibc2.39
|
||||
36 INFO: Python environment: /usr
|
||||
36 INFO: wrote /home/coding/Source/KX-Bridge-Release/build/fetch_credentials.spec
|
||||
37 WARNING: collect_data_files - skipping data collection for module 'pycryptodome' as it is not a package.
|
||||
37 WARNING: collect_dynamic_libs - skipping library collection for module 'pycryptodome' as it is not a package.
|
||||
117 INFO: Module search paths (PYTHONPATH):
|
||||
['/usr/lib/python312.zip',
|
||||
'/usr/lib/python3.12',
|
||||
'/usr/lib/python3.12/lib-dynload',
|
||||
'/home/coding/.local/lib/python3.12/site-packages',
|
||||
'/usr/local/lib/python3.12/dist-packages',
|
||||
'/usr/lib/python3/dist-packages',
|
||||
'/usr/lib/python3.12/dist-packages',
|
||||
'/home/coding/Source/KX-Bridge-Release']
|
||||
458 INFO: checking Analysis
|
||||
458 INFO: Building Analysis because Analysis-00.toc is non existent
|
||||
458 INFO: Looking for Python shared library...
|
||||
475 WARNING: Unrecognised line of output 'Der Cache wurde generiert von: ldconfig (Ubuntu GLIBC 2.39-0ubuntu8.7) stable release version 2.39' from ldconfig
|
||||
475 INFO: Using Python shared library: /lib/x86_64-linux-gnu/libpython3.12.so.1.0
|
||||
475 INFO: Running Analysis Analysis-00.toc
|
||||
475 INFO: Target bytecode optimization level: 0
|
||||
476 INFO: Initializing module dependency graph...
|
||||
476 INFO: Initializing module graph hook caches...
|
||||
491 INFO: Analyzing modules for base_library.zip ...
|
||||
1017 INFO: Processing standard module hook 'hook-encodings.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
|
||||
1871 INFO: Processing standard module hook 'hook-pickle.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
|
||||
2469 INFO: Processing standard module hook 'hook-heapq.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
|
||||
2692 INFO: Caching module dependency graph...
|
||||
2717 INFO: Analyzing /home/coding/Source/KX-Bridge-Release/fetch_credentials.py
|
||||
2750 INFO: Processing standard module hook 'hook-urllib3.py' from '/home/coding/.local/lib/python3.12/site-packages/_pyinstaller_hooks_contrib/stdhooks'
|
||||
2817 INFO: Processing pre-safe-import-module hook 'hook-typing_extensions.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks/pre_safe_import_module'
|
||||
2818 INFO: SetuptoolsInfo: initializing cached setuptools info...
|
||||
3154 INFO: Processing standard module hook 'hook-multiprocessing.util.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
|
||||
3216 INFO: Processing standard module hook 'hook-xml.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
|
||||
3402 INFO: Processing standard module hook 'hook-_ctypes.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
|
||||
4175 INFO: Processing standard module hook 'hook-chardet.py' from '/home/coding/.local/lib/python3.12/site-packages/_pyinstaller_hooks_contrib/stdhooks'
|
||||
4759 INFO: Processing standard module hook 'hook-cryptography.py' from '/home/coding/.local/lib/python3.12/site-packages/_pyinstaller_hooks_contrib/stdhooks'
|
||||
5142 INFO: hook-cryptography: cryptography uses dynamically-linked OpenSSL: '/lib/x86_64-linux-gnu/libssl.so.3'
|
||||
5351 INFO: Processing standard module hook 'hook-bcrypt.py' from '/home/coding/.local/lib/python3.12/site-packages/_pyinstaller_hooks_contrib/stdhooks'
|
||||
5426 INFO: Processing standard module hook 'hook-certifi.py' from '/home/coding/.local/lib/python3.12/site-packages/_pyinstaller_hooks_contrib/stdhooks'
|
||||
5502 INFO: Processing standard module hook 'hook-Crypto.py' from '/home/coding/.local/lib/python3.12/site-packages/_pyinstaller_hooks_contrib/stdhooks'
|
||||
5706 INFO: Processing standard module hook 'hook-pycparser.py' from '/home/coding/.local/lib/python3.12/site-packages/_pyinstaller_hooks_contrib/stdhooks'
|
||||
5802 INFO: Processing standard module hook 'hook-setuptools.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
|
||||
5806 INFO: Processing pre-safe-import-module hook 'hook-distutils.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks/pre_safe_import_module'
|
||||
5861 INFO: Processing standard module hook 'hook-sysconfig.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
|
||||
5896 INFO: Processing standard module hook 'hook-platform.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
|
||||
5906 INFO: Processing standard module hook 'hook-_osx_support.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
|
||||
6028 INFO: Processing pre-safe-import-module hook 'hook-importlib_metadata.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks/pre_safe_import_module'
|
||||
6141 INFO: Processing pre-safe-import-module hook 'hook-packaging.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks/pre_safe_import_module'
|
||||
6502 INFO: Processing standard module hook 'hook-pkg_resources.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
|
||||
7089 INFO: Processing standard module hook 'hook-xml.dom.domreg.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
|
||||
7323 INFO: Processing standard module hook 'hook-sqlite3.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
|
||||
7648 INFO: Processing standard module hook 'hook-difflib.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
|
||||
7995 INFO: Processing module hooks (post-graph stage)...
|
||||
7997 WARNING: Hidden import "pycparser.lextab" not found!
|
||||
7997 WARNING: Hidden import "pycparser.yacctab" not found!
|
||||
8231 INFO: Processing pre-safe-import-module hook 'hook-platformdirs.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks/pre_safe_import_module'
|
||||
8237 INFO: Processing standard module hook 'hook-platformdirs.py' from '/home/coding/.local/lib/python3.12/site-packages/_pyinstaller_hooks_contrib/stdhooks'
|
||||
8304 INFO: Processing standard module hook 'hook-setuptools._vendor.importlib_metadata.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
|
||||
8391 INFO: Processing standard module hook 'hook-setuptools._vendor.jaraco.text.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks'
|
||||
8490 INFO: Performing binary vs. data reclassification (51 entries)
|
||||
8605 INFO: Looking for ctypes DLLs
|
||||
8618 INFO: Analyzing run-time hooks ...
|
||||
8620 INFO: Including run-time hook 'pyi_rth_inspect.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks/rthooks'
|
||||
8622 INFO: Including run-time hook 'pyi_rth_pkgutil.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks/rthooks'
|
||||
8623 INFO: Including run-time hook 'pyi_rth_multiprocessing.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks/rthooks'
|
||||
8625 INFO: Including run-time hook 'pyi_rth_setuptools.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks/rthooks'
|
||||
8626 INFO: Including run-time hook 'pyi_rth_pkgres.py' from '/home/coding/.local/lib/python3.12/site-packages/PyInstaller/hooks/rthooks'
|
||||
8628 INFO: Including run-time hook 'pyi_rth_cryptography_openssl.py' from '/home/coding/.local/lib/python3.12/site-packages/_pyinstaller_hooks_contrib/rthooks'
|
||||
8635 INFO: Creating base_library.zip...
|
||||
8664 INFO: Looking for dynamic libraries
|
||||
9293 INFO: Warnings written to /home/coding/Source/KX-Bridge-Release/build/pyinstaller/fetch_credentials/warn-fetch_credentials.txt
|
||||
9315 INFO: Graph cross-reference written to /home/coding/Source/KX-Bridge-Release/build/pyinstaller/fetch_credentials/xref-fetch_credentials.html
|
||||
9327 INFO: checking PYZ
|
||||
9327 INFO: Building PYZ because PYZ-00.toc is non existent
|
||||
9327 INFO: Building PYZ (ZlibArchive) /home/coding/Source/KX-Bridge-Release/build/pyinstaller/fetch_credentials/PYZ-00.pyz
|
||||
9755 INFO: Building PYZ (ZlibArchive) /home/coding/Source/KX-Bridge-Release/build/pyinstaller/fetch_credentials/PYZ-00.pyz completed successfully.
|
||||
9766 INFO: checking PKG
|
||||
9766 INFO: Building PKG because PKG-00.toc is non existent
|
||||
9766 INFO: Building PKG (CArchive) fetch_credentials.pkg
|
||||
14335 INFO: Building PKG (CArchive) fetch_credentials.pkg completed successfully.
|
||||
14336 INFO: Bootloader /home/coding/.local/lib/python3.12/site-packages/PyInstaller/bootloader/Linux-64bit-intel/run
|
||||
14336 INFO: checking EXE
|
||||
14336 INFO: Building EXE because EXE-00.toc is non existent
|
||||
14336 INFO: Building EXE from EXE-00.toc
|
||||
14336 INFO: Copying bootloader EXE to /home/coding/Source/KX-Bridge-Release/releases/0.9.27/fetch_credentials
|
||||
14336 INFO: Appending PKG archive to custom ELF section in EXE
|
||||
14360 INFO: Building EXE from EXE-00.toc completed successfully.
|
||||
14361 INFO: Build complete! The results are available in: /home/coding/Source/KX-Bridge-Release/releases/0.9.27
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
2
requirements-dev.txt
Normal file
2
requirements-dev.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
# Code formatting
|
||||
black>=24.1.0
|
||||
@@ -1,68 +0,0 @@
|
||||
"""
|
||||
Shared fixtures für KX-Bridge Tests.
|
||||
Startet die Bridge in-process mit einem Mock-MQTT-Client (kein Drucker nötig).
|
||||
"""
|
||||
import sys, types, argparse, pytest, pytest_asyncio
|
||||
from unittest.mock import MagicMock
|
||||
from aiohttp.test_utils import TestClient, TestServer
|
||||
|
||||
# ── Pfad ──────────────────────────────────────────────────────────────────────
|
||||
sys.path.insert(0, str(__import__("pathlib").Path(__file__).parent.parent / "bridge"))
|
||||
|
||||
# ── env_loader mocken (keine .env nötig) ──────────────────────────────────────
|
||||
env_mod = types.ModuleType("env_loader")
|
||||
env_mod.PRINTER_IP = ""
|
||||
env_mod.MQTT_PORT = 9883
|
||||
env_mod.USERNAME = ""
|
||||
env_mod.PASSWORD = ""
|
||||
env_mod.MODE_ID = "20030"
|
||||
env_mod.DEVICE_ID = ""
|
||||
sys.modules["env_loader"] = env_mod
|
||||
|
||||
# ── Bridge + App importieren ───────────────────────────────────────────────────
|
||||
from kobrax_moonraker_bridge import KobraXBridge, build_app # noqa: E402
|
||||
|
||||
|
||||
def make_mock_client():
|
||||
"""Minimaler Mock-MQTT-Client — keine Verbindung, keine Threads."""
|
||||
c = MagicMock()
|
||||
c.callbacks = {}
|
||||
c.connected = False
|
||||
return c
|
||||
|
||||
|
||||
def make_args(**overrides):
|
||||
args = argparse.Namespace(
|
||||
printer_ip = "",
|
||||
mqtt_port = 9883,
|
||||
username = "",
|
||||
password = "",
|
||||
mode_id = "20030",
|
||||
device_id = "",
|
||||
host = "127.0.0.1",
|
||||
port = 7125,
|
||||
)
|
||||
for k, v in overrides.items():
|
||||
setattr(args, k, v)
|
||||
return args
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def client():
|
||||
"""TestClient mit frischer Bridge-Instanz, ohne MQTT-Verbindung."""
|
||||
mock_client = make_mock_client()
|
||||
bridge = KobraXBridge(mock_client, args=make_args())
|
||||
app = build_app(bridge)
|
||||
async with TestClient(TestServer(app)) as c:
|
||||
yield c, bridge
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def client_configured():
|
||||
"""TestClient mit bereits konfigurierten Zugangsdaten."""
|
||||
mock_client = make_mock_client()
|
||||
args = make_args(printer_ip="192.168.1.100", device_id="abc123deadbeef")
|
||||
bridge = KobraXBridge(mock_client, args=args)
|
||||
app = build_app(bridge)
|
||||
async with TestClient(TestServer(app)) as c:
|
||||
yield c, bridge
|
||||
@@ -1,3 +0,0 @@
|
||||
pytest
|
||||
pytest-asyncio
|
||||
aiohttp
|
||||
@@ -1,113 +0,0 @@
|
||||
"""
|
||||
Tests für /api/state — Drucker-Zustandsabfrage.
|
||||
"""
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_state_returns_200(client):
|
||||
c, _ = client
|
||||
resp = await c.get("/api/state")
|
||||
assert resp.status == 200
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_state_schema(client):
|
||||
"""Alle erwarteten Felder müssen vorhanden und typsicher sein."""
|
||||
c, _ = client
|
||||
resp = await c.get("/api/state")
|
||||
data = await resp.json()
|
||||
|
||||
assert isinstance(data["print_state"], str)
|
||||
assert isinstance(data["kobra_state"], str)
|
||||
assert isinstance(data["nozzle_temp"], float)
|
||||
assert isinstance(data["nozzle_target"], float)
|
||||
assert isinstance(data["bed_temp"], float)
|
||||
assert isinstance(data["bed_target"], float)
|
||||
assert isinstance(data["progress"], float)
|
||||
assert isinstance(data["print_duration"], int)
|
||||
assert isinstance(data["remain_time"], int)
|
||||
assert isinstance(data["curr_layer"], int)
|
||||
assert isinstance(data["total_layers"], int)
|
||||
assert isinstance(data["filename"], str)
|
||||
assert isinstance(data["fan_speed"], int)
|
||||
assert isinstance(data["light_on"], bool)
|
||||
assert isinstance(data["ams_slots"], list)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_state_initial_values(client):
|
||||
"""Im Offline-Start müssen Temperaturen 0 und Zustand 'standby' sein."""
|
||||
c, _ = client
|
||||
data = await (await c.get("/api/state")).json()
|
||||
|
||||
assert data["print_state"] == "standby"
|
||||
assert data["nozzle_temp"] == 0.0
|
||||
assert data["bed_temp"] == 0.0
|
||||
assert data["progress"] == 0.0
|
||||
assert data["filename"] == ""
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_state_updates_after_mqtt_print_report(client):
|
||||
"""Simuliert ein eingehendes print/report MQTT-Paket und prüft State-Update."""
|
||||
c, bridge = client
|
||||
|
||||
# Simuliere MQTT-Nachricht wie vom echten Drucker
|
||||
bridge._on_print({
|
||||
"state": "printing",
|
||||
"data": {
|
||||
"filename": "test.gcode",
|
||||
"progress": 42,
|
||||
"print_time": 10, # Minuten → 600s
|
||||
"remain_time": 5, # Minuten → 300s
|
||||
"curr_layer": 20,
|
||||
"total_layers": 100,
|
||||
}
|
||||
})
|
||||
|
||||
data = await (await c.get("/api/state")).json()
|
||||
|
||||
assert data["print_state"] == "printing"
|
||||
assert data["filename"] == "test.gcode"
|
||||
assert data["progress"] == pytest.approx(0.42)
|
||||
assert data["print_duration"] == 600
|
||||
assert data["remain_time"] == 300
|
||||
assert data["curr_layer"] == 20
|
||||
assert data["total_layers"] == 100
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_state_updates_after_mqtt_temp_report(client):
|
||||
"""Simuliert ein tempature/report Paket."""
|
||||
c, bridge = client
|
||||
|
||||
bridge._on_temp({
|
||||
"data": {
|
||||
"curr_nozzle_temp": 215.3,
|
||||
"target_nozzle_temp": 220.0,
|
||||
"curr_hotbed_temp": 59.8,
|
||||
"target_hotbed_temp": 60.0,
|
||||
}
|
||||
})
|
||||
|
||||
data = await (await c.get("/api/state")).json()
|
||||
assert data["nozzle_temp"] == pytest.approx(215.3)
|
||||
assert data["nozzle_target"] == pytest.approx(220.0)
|
||||
assert data["bed_temp"] == pytest.approx(59.8)
|
||||
assert data["bed_target"] == pytest.approx(60.0)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_state_resets_on_cancel(client):
|
||||
"""Nach 'stoped' müssen Progress und Filename zurückgesetzt werden."""
|
||||
c, bridge = client
|
||||
|
||||
# Erst Druck simulieren
|
||||
bridge._on_print({"state": "printing", "data": {"filename": "x.gcode", "progress": 50}})
|
||||
# Dann Abbruch
|
||||
bridge._on_print({"state": "stoped", "data": {}})
|
||||
|
||||
data = await (await c.get("/api/state")).json()
|
||||
assert data["progress"] == 0.0
|
||||
assert data["filename"] == ""
|
||||
@@ -1,109 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# test_install.sh – Smoke-Test: Release-Repo klonen, start.sh ausführen, HTTP prüfen.
|
||||
#
|
||||
# Simuliert den Weg eines anonymen Nutzers:
|
||||
# 1. Release-Repo klonen
|
||||
# 2. start.sh ausführen (baut Docker-Image, startet Container)
|
||||
# 3. HTTP-Endpunkte prüfen
|
||||
# 4. Aufräumen
|
||||
#
|
||||
# Voraussetzung: Docker installiert, Port 7125 frei
|
||||
#
|
||||
# Verwendung:
|
||||
# bash tests/test_install.sh
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
GITEA_URL="https://gitea.it-drui.de/viewit/KX-Bridge-Release"
|
||||
WORK_DIR=$(mktemp -d /tmp/kx-bridge-test-XXXXXX)
|
||||
PASS=0; FAIL=0
|
||||
|
||||
ok() { echo " ✓ $*"; PASS=$((PASS+1)); }
|
||||
fail() { echo " ✗ $*"; FAIL=$((FAIL+1)); }
|
||||
|
||||
cleanup() {
|
||||
echo ""
|
||||
echo "[cleanup] Stoppe Container und lösche Testverzeichnis ..."
|
||||
cd "$WORK_DIR/KX-Bridge-Release" 2>/dev/null && docker-compose down 2>/dev/null || true
|
||||
rm -rf "$WORK_DIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
echo "=== KX-Bridge Installations-Smoke-Test ==="
|
||||
echo ""
|
||||
|
||||
# ── Schritt 1: Repo klonen ────────────────────────────────────────────────────
|
||||
echo "[1/5] Klone Release-Repo ..."
|
||||
git clone --depth=1 "$GITEA_URL" "$WORK_DIR/KX-Bridge-Release" > /dev/null 2>&1 \
|
||||
&& ok "Repo geklont" \
|
||||
|| { fail "git clone fehlgeschlagen"; exit 1; }
|
||||
|
||||
cd "$WORK_DIR/KX-Bridge-Release"
|
||||
|
||||
# ── Schritt 2: Erwartete Dateien vorhanden ────────────────────────────────────
|
||||
echo "[2/5] Prüfe Dateien im Repo ..."
|
||||
for f in start.sh docker-compose.yml Dockerfile kobrax_moonraker_bridge.py \
|
||||
anycubic_slicer.crt anycubic_slicer.key .env.example; do
|
||||
[[ -f "$f" ]] && ok "$f vorhanden" || fail "$f FEHLT"
|
||||
done
|
||||
|
||||
# Dockerfile darf keine 05_scripts/-Pfade enthalten
|
||||
if grep -q "05_scripts/" Dockerfile; then
|
||||
fail "Dockerfile enthält '05_scripts/' – falsches Dockerfile im Release-Repo!"
|
||||
else
|
||||
ok "Dockerfile Pfade korrekt (kein 05_scripts/-Präfix)"
|
||||
fi
|
||||
|
||||
# ── Schritt 3: start.sh ausführen ────────────────────────────────────────────
|
||||
echo "[3/5] Führe start.sh aus ..."
|
||||
chmod +x start.sh
|
||||
./start.sh > /tmp/kx-bridge-start.log 2>&1 \
|
||||
&& ok "start.sh erfolgreich" \
|
||||
|| { fail "start.sh fehlgeschlagen (siehe /tmp/kx-bridge-start.log)"; cat /tmp/kx-bridge-start.log; exit 1; }
|
||||
|
||||
# Kurz warten bis Bridge hochgefahren
|
||||
sleep 3
|
||||
|
||||
# ── Schritt 4: HTTP-Endpunkte prüfen ─────────────────────────────────────────
|
||||
echo "[4/5] Prüfe HTTP-Endpunkte ..."
|
||||
BASE="http://localhost:7125"
|
||||
|
||||
check_endpoint() {
|
||||
local path="$1"
|
||||
local desc="$2"
|
||||
local http_code
|
||||
http_code=$(curl -s -o /dev/null -w "%{http_code}" "$BASE$path")
|
||||
[[ "$http_code" == "200" ]] \
|
||||
&& ok "$desc ($path → $http_code)" \
|
||||
|| fail "$desc ($path → $http_code)"
|
||||
}
|
||||
|
||||
check_endpoint "/" "Web-UI (index.html)"
|
||||
check_endpoint "/api/state" "GET /api/state"
|
||||
check_endpoint "/api/settings" "GET /api/settings"
|
||||
check_endpoint "/server/info" "GET /server/info (Moonraker)"
|
||||
check_endpoint "/printer/info" "GET /printer/info (Moonraker)"
|
||||
check_endpoint "/printer/objects/list" "GET /printer/objects/list"
|
||||
check_endpoint "/api/version" "GET /api/version (OctoPrint compat)"
|
||||
|
||||
# Beim ersten Start: printer_ip muss leer sein → Settings-Modal würde sich öffnen
|
||||
SETTINGS=$(curl -s "$BASE/api/settings")
|
||||
PRINTER_IP=$(echo "$SETTINGS" | python3 -c "import sys,json; print(json.load(sys.stdin).get('printer_ip',''))" 2>/dev/null || echo "ERROR")
|
||||
[[ -z "$PRINTER_IP" ]] \
|
||||
&& ok "Erstkonfiguration erkannt: printer_ip leer → Settings-Modal öffnet sich" \
|
||||
|| fail "printer_ip sollte beim Erststart leer sein, ist: '$PRINTER_IP'"
|
||||
|
||||
# ── Schritt 5: Container läuft stabil ────────────────────────────────────────
|
||||
echo "[5/5] Prüfe Container-Stabilität ..."
|
||||
sleep 2
|
||||
RUNNING=$(docker-compose ps --services --filter "status=running" 2>/dev/null || true)
|
||||
[[ -n "$RUNNING" ]] \
|
||||
&& ok "Container läuft stabil" \
|
||||
|| fail "Container ist nicht mehr aktiv"
|
||||
|
||||
# ── Ergebnis ──────────────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo "══════════════════════════════════════"
|
||||
echo " Ergebnis: $PASS bestanden, $FAIL fehlgeschlagen"
|
||||
echo "══════════════════════════════════════"
|
||||
[[ $FAIL -eq 0 ]] && exit 0 || exit 1
|
||||
@@ -1,77 +0,0 @@
|
||||
"""
|
||||
Tests für Moonraker-kompatible Endpunkte die OrcaSlicer aufruft.
|
||||
"""
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_server_info(client):
|
||||
c, _ = client
|
||||
resp = await c.get("/server/info")
|
||||
assert resp.status == 200
|
||||
data = await resp.json()
|
||||
assert data["result"]["klippy_state"] in ("ready", "standby", "error")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_printer_info(client):
|
||||
c, _ = client
|
||||
resp = await c.get("/printer/info")
|
||||
assert resp.status == 200
|
||||
data = await resp.json()
|
||||
assert "hostname" in data["result"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_objects_list(client):
|
||||
c, _ = client
|
||||
resp = await c.get("/printer/objects/list")
|
||||
assert resp.status == 200
|
||||
data = await resp.json()
|
||||
objects = data["result"]["objects"]
|
||||
# OrcaSlicer erwartet mindestens diese Objekte
|
||||
for obj in ("print_stats", "heater_bed", "extruder", "display_status"):
|
||||
assert obj in objects
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_objects_query_print_stats(client):
|
||||
c, _ = client
|
||||
resp = await c.get("/printer/objects/query?print_stats")
|
||||
assert resp.status == 200
|
||||
data = await resp.json()
|
||||
ps = data["result"]["status"]["print_stats"]
|
||||
assert "state" in ps
|
||||
assert "filename" in ps
|
||||
assert "print_duration" in ps
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_objects_query_temperatures(client):
|
||||
c, _ = client
|
||||
resp = await c.get("/printer/objects/query?extruder&heater_bed")
|
||||
assert resp.status == 200
|
||||
data = await resp.json()
|
||||
status = data["result"]["status"]
|
||||
assert "temperature" in status["extruder"]
|
||||
assert "temperature" in status["heater_bed"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_octoprint_version(client):
|
||||
"""OrcaSlicer probt /api/version um Drucker-Typ zu erkennen."""
|
||||
c, _ = client
|
||||
resp = await c.get("/api/version")
|
||||
assert resp.status == 200
|
||||
data = await resp.json()
|
||||
assert "server" in data
|
||||
assert "api" in data
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_files_list(client):
|
||||
c, _ = client
|
||||
resp = await c.get("/server/files/list")
|
||||
assert resp.status == 200
|
||||
data = await resp.json()
|
||||
assert isinstance(data["result"], list)
|
||||
@@ -1,82 +0,0 @@
|
||||
"""
|
||||
Tests für /api/settings — Lesen und Schreiben der Verbindungseinstellungen.
|
||||
"""
|
||||
import pytest
|
||||
import tempfile
|
||||
import pathlib
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_settings_get_returns_200(client):
|
||||
c, _ = client
|
||||
resp = await c.get("/api/settings")
|
||||
assert resp.status == 200
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_settings_get_schema(client):
|
||||
c, _ = client
|
||||
data = await (await c.get("/api/settings")).json()
|
||||
for key in ("printer_ip", "mqtt_port", "username", "password", "device_id", "mode_id"):
|
||||
assert key in data
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_settings_get_empty_when_unconfigured(client):
|
||||
"""Frische Bridge ohne Zugangsdaten → printer_ip und device_id leer."""
|
||||
c, _ = client
|
||||
data = await (await c.get("/api/settings")).json()
|
||||
assert data["printer_ip"] == ""
|
||||
assert data["device_id"] == ""
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_settings_get_returns_configured_values(client_configured):
|
||||
"""Bridge mit Zugangsdaten → Werte korrekt zurückgegeben."""
|
||||
c, _ = client_configured
|
||||
data = await (await c.get("/api/settings")).json()
|
||||
assert data["printer_ip"] == "192.168.1.100"
|
||||
assert data["device_id"] == "abc123deadbeef"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_settings_post_writes_env(client):
|
||||
"""POST /api/settings schreibt Werte in .env-Datei."""
|
||||
c, bridge = client
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
env_path = pathlib.Path(tmpdir) / ".env"
|
||||
env_path.write_text("")
|
||||
bridge._find_env_path = lambda: env_path
|
||||
|
||||
resp = await c.post("/api/settings", json={
|
||||
"printer_ip": "10.0.0.5",
|
||||
"mqtt_port": 9883,
|
||||
"username": "userABCD",
|
||||
"password": "secret123",
|
||||
"device_id": "deadbeef01234567",
|
||||
"mode_id": "20030",
|
||||
})
|
||||
assert resp.status == 200
|
||||
|
||||
content = env_path.read_text()
|
||||
assert "PRINTER_IP=10.0.0.5" in content
|
||||
assert "MQTT_USERNAME=userABCD" in content
|
||||
assert "DEVICE_ID=deadbeef01234567" in content
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_settings_post_preserves_existing_keys(client):
|
||||
"""POST darf unbekannte Keys in .env nicht löschen (z.B. GITEA_TOKEN)."""
|
||||
c, bridge = client
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
env_path = pathlib.Path(tmpdir) / ".env"
|
||||
env_path.write_text("GITEA_TOKEN=mytoken\nPRINTER_IP=old\n")
|
||||
bridge._find_env_path = lambda: env_path
|
||||
|
||||
await c.post("/api/settings", json={"printer_ip": "10.0.0.99"})
|
||||
|
||||
content = env_path.read_text()
|
||||
assert "GITEA_TOKEN=mytoken" in content
|
||||
assert "PRINTER_IP=10.0.0.99" in content
|
||||
@@ -53,19 +53,8 @@ function _loadSpoolmanStatus(){
|
||||
fetch(_apiUrl('/kx/spoolman/status')).then(function(r){return r.json();}).then(function(d){
|
||||
_spoolmanStatus=d;
|
||||
_slotSpoolMap=d.slot_spools||{};
|
||||
_updateSpoolmanStatusDot();
|
||||
}).catch(function(){});
|
||||
}
|
||||
function _updateSpoolmanStatusDot(){
|
||||
var dot=document.getElementById('spoolman-status-dot');
|
||||
var lbl=document.getElementById('spoolman-status-lbl');
|
||||
if(!dot||!lbl)return;
|
||||
if(_spoolmanStatus.configured){
|
||||
dot.style.color='var(--ok)';lbl.textContent=_spoolmanStatus.server||'verbunden';
|
||||
} else {
|
||||
dot.style.color='var(--txt2)';lbl.textContent='nicht konfiguriert';
|
||||
}
|
||||
}
|
||||
|
||||
function _buildSpoolmanSection(){
|
||||
var sec=document.getElementById('fd-spoolman-section');
|
||||
@@ -396,11 +385,6 @@ function applyLang(){
|
||||
setText('setcat-lbl-display',T.settings_cat_display||'Darstellung');
|
||||
setText('setcat-lbl-display2',T.settings_cat_display||'Darstellung');
|
||||
setText('setcat-lbl-filament',T.settings_cat_filament||'Filament');
|
||||
setText('setcat-lbl-integrations',T.settings_integrations||'Integrationen');
|
||||
setText('modal-sec-spoolman',T.modal_sec_spoolman||'Spoolman');
|
||||
setText('lbl-spoolman-url',T.lbl_spoolman_url||'Server-URL');
|
||||
setText('lbl-spoolman-sync-rate',T.lbl_spoolman_sync_rate||'Sync-Rate (s, 0=aus)');
|
||||
setText('modal-sec-obico',T.modal_sec_obico||'Obico');
|
||||
setText('setcat-lbl-system',T.settings_version||'System');
|
||||
setText('lbl-set-lang',T.settings_cat_language||'Sprache');
|
||||
setText('lbl-set-theme',T.settings_cat_theme||'Hell / Dunkel umschalten');
|
||||
@@ -550,7 +534,6 @@ function ensureAceDryCards(){
|
||||
// defer until DOM ready
|
||||
window.addEventListener('DOMContentLoaded',function(){
|
||||
setLanguage(currentLang).catch(function(){});
|
||||
_loadSpoolmanStatus();
|
||||
// Kein Drucker konfiguriert? → direkt in den Drucker-Tab (zeigt "+ Drucker hinzufügen")
|
||||
fetch('/kx/printers').then(function(r){return r.json()}).then(function(d){
|
||||
if(!d.result||!d.result.length){showPanel('printers');loadPrinterTab();}
|
||||
@@ -1075,10 +1058,6 @@ function openSettings(){
|
||||
pi.value=sec;
|
||||
}
|
||||
renderFilamentMapping(d.filament_profiles||{});
|
||||
// Spoolman
|
||||
var su=document.getElementById('s-spoolman-url');if(su)su.value=d.spoolman_server||'';
|
||||
var sr=document.getElementById('s-spoolman-sync-rate');if(sr)sr.value=(d.spoolman_sync_rate!==undefined?d.spoolman_sync_rate:30);
|
||||
_updateSpoolmanStatusDot();
|
||||
});
|
||||
// Sprach-Select im Settings-Panel mit aktueller Sprache spiegeln
|
||||
var ls=document.getElementById('s-lang-select');
|
||||
@@ -1647,15 +1626,12 @@ function saveSettings(){
|
||||
print_start_dialog: parseInt((document.getElementById('s-file-ready-mode')||{}).value||'1',10),
|
||||
web_upload_warning:webUploadWarning,
|
||||
poll_interval: Math.min(60,Math.max(1,parseInt((document.getElementById('s-poll-interval')||{}).value,10)||3)),
|
||||
spoolman_server: (document.getElementById('s-spoolman-url')||{}).value||'',
|
||||
spoolman_sync_rate: Math.max(0,parseInt((document.getElementById('s-spoolman-sync-rate')||{}).value||'30',10)),
|
||||
}).then(function(){
|
||||
btn.textContent=T.update_restarting;
|
||||
setTimeout(function(){
|
||||
btn.disabled=false;
|
||||
setText('btn-save-settings',T.settings_save);
|
||||
closeSettings();
|
||||
_loadSpoolmanStatus();
|
||||
poll();
|
||||
},4000);
|
||||
}).catch(function(e){
|
||||
@@ -1663,33 +1639,21 @@ function saveSettings(){
|
||||
clog('Settings-Fehler: '+e,'msg-err');
|
||||
});
|
||||
}
|
||||
var _updateDockerOnly=false;
|
||||
function checkUpdate(){
|
||||
var sb=document.getElementById('update-status');
|
||||
sb.textContent=T.update_checking;
|
||||
document.getElementById('btn-update-apply').style.display='none';
|
||||
_updateTag='';_updateUrl='';_updateDockerOnly=false;
|
||||
_updateTag='';_updateUrl='';
|
||||
fetch(_apiUrl('/api/update/check')).then(function(r){return r.json()}).then(function(d){
|
||||
if(d.error){sb.textContent=T.update_error+': '+d.error;return;}
|
||||
var cl=document.getElementById('update-changelog');
|
||||
if(d.changelog&&d.changelog.trim()){
|
||||
// Changelog als Markdown-Text (pre-formatiert) anzeigen
|
||||
cl.textContent=d.changelog;cl.style.display='block';
|
||||
} else {cl.style.display='none';}
|
||||
_updateDockerOnly=!!d.docker_only;
|
||||
if(d.changelog&&d.changelog.trim()){cl.textContent=d.changelog;cl.style.display='block';}
|
||||
else{cl.style.display='none';}
|
||||
if(d.update_available){
|
||||
sb.textContent=d.latest+' '+T.update_available;
|
||||
sb.textContent='v'+d.latest+' '+T.update_available;
|
||||
sb.style.color='var(--ok)';
|
||||
_updateTag=d.tag;_updateUrl=d.download_url||'';
|
||||
var btn=document.getElementById('btn-update-apply');
|
||||
if(_updateDockerOnly){
|
||||
btn.textContent=T.update_docker||'docker compose pull';
|
||||
btn.title='docker compose pull && docker compose up -d';
|
||||
} else {
|
||||
btn.textContent=T.update_apply;
|
||||
btn.title='';
|
||||
}
|
||||
btn.style.display='inline-block';
|
||||
_updateTag=d.tag;_updateUrl=d.download_url;
|
||||
document.getElementById('btn-update-apply').style.display='inline-block';
|
||||
} else {
|
||||
sb.textContent=T.update_none;
|
||||
sb.style.color='var(--txt2)';
|
||||
@@ -1697,21 +1661,9 @@ function checkUpdate(){
|
||||
}).catch(function(e){sb.textContent=T.update_error+': '+e;});
|
||||
}
|
||||
function applyUpdate(){
|
||||
if(!_updateUrl)return;
|
||||
var sb=document.getElementById('update-status');
|
||||
var btn=document.getElementById('btn-update-apply');
|
||||
if(_updateDockerOnly){
|
||||
// Nightly: kein Self-Update, Docker-Befehl in Zwischenablage kopieren
|
||||
var cmd='docker compose pull && docker compose up -d';
|
||||
if(navigator.clipboard){
|
||||
navigator.clipboard.writeText(cmd).then(function(){
|
||||
sb.textContent=T.update_docker_copied||'Befehl kopiert: '+cmd;
|
||||
});
|
||||
} else {
|
||||
sb.textContent=cmd;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if(!_updateUrl)return;
|
||||
btn.disabled=true;sb.textContent=T.update_applying;
|
||||
post('/api/update/apply',{download_url:_updateUrl,tag:_updateTag}).then(function(){
|
||||
sb.textContent=T.update_restarting;
|
||||
@@ -1897,7 +1849,6 @@ function camStart(){
|
||||
post('/api/camera/start',{}).then(function(){
|
||||
camOn=true;
|
||||
document.getElementById('cam-toggle-btn').textContent=tr('btn_cam_stop');
|
||||
var rb=document.getElementById('cam-reset-btn');if(rb)rb.style.display='';
|
||||
clog(tr('log_cam_start'),'msg-ok');
|
||||
setTimeout(function(){ sp.style.display='none'; img.style.display='block'; },1200);
|
||||
if(/Android/i.test(navigator.userAgent)){
|
||||
@@ -1932,7 +1883,6 @@ function camStop(){
|
||||
camOn=false;
|
||||
camUserStopped=true; // suppress auto-restart for remainder of this print
|
||||
document.getElementById('cam-toggle-btn').textContent=tr('btn_cam_start');
|
||||
var rb=document.getElementById('cam-reset-btn');if(rb)rb.style.display='none';
|
||||
clog(tr('log_cam_stop'),'msg-ok');
|
||||
}
|
||||
|
||||
@@ -2183,13 +2133,6 @@ function aceDryToggle(aceId,on){
|
||||
|
||||
function toggleCam(){if(camOn)camStop();else camStart()}
|
||||
|
||||
function resetCamera(){
|
||||
post('/api/camera/reset',{}).then(function(){
|
||||
var btn=document.getElementById('cam-reset-btn');
|
||||
if(btn){btn.textContent='↻';setTimeout(function(){btn.textContent='↺';},1000);}
|
||||
}).catch(function(){});
|
||||
}
|
||||
|
||||
function aceDryStop(aceId){
|
||||
aceId=(typeof aceId==='number'&&aceId>=0)?aceId:0;
|
||||
return post('/api/ace/dry',{action:'stop',ace_id:aceId})
|
||||
|
||||
@@ -157,7 +157,6 @@
|
||||
<div style="font-size:12px;color:#fff" id="cam-fname"></div>
|
||||
</div>
|
||||
<button class="cam-toggle" onclick="toggleCam()" id="cam-toggle-btn">▶ Kamera</button>
|
||||
<button class="cam-toggle" onclick="resetCamera()" id="cam-reset-btn" style="display:none;margin-left:4px" title="Kamera-Stream neu verbinden (nach 429-Sperre)">↺</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -436,7 +435,6 @@
|
||||
<button class="set-cat" id="setcat-printer" onclick="showSettingsCat('printer')"><span>🖨</span> <span id="setcat-lbl-printer">Drucker</span></button>
|
||||
<button class="set-cat" id="setcat-display" onclick="showSettingsCat('display')"><span>🎨</span> <span id="setcat-lbl-display">Darstellung</span></button>
|
||||
<button class="set-cat" id="setcat-filament" onclick="showSettingsCat('filament')"><span>🧵</span> <span id="setcat-lbl-filament">Filament</span></button>
|
||||
<button class="set-cat" id="setcat-integrations" onclick="showSettingsCat('integrations')"><span>⚡</span> <span id="setcat-lbl-integrations">Integrationen</span></button>
|
||||
<button class="set-cat" id="setcat-system" onclick="showSettingsCat('system')"><span>⚙</span> <span id="setcat-lbl-system">System</span></button>
|
||||
</div>
|
||||
|
||||
@@ -572,33 +570,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Integrationen -->
|
||||
<div class="set-group" id="setgrp-integrations">
|
||||
<!-- Spoolman -->
|
||||
<div class="card">
|
||||
<div class="card-title"><span>🧵</span> <span id="modal-sec-spoolman">Spoolman</span></div>
|
||||
<div class="set-row">
|
||||
<label id="lbl-spoolman-url">Server-URL</label>
|
||||
<input type="text" id="s-spoolman-url" placeholder="http://spoolman:7912" style="width:200px">
|
||||
</div>
|
||||
<div class="set-row">
|
||||
<label id="lbl-spoolman-sync-rate">Sync-Rate (s, 0=aus)</label>
|
||||
<input type="number" id="s-spoolman-sync-rate" min="0" max="3600" value="30" style="width:80px">
|
||||
</div>
|
||||
<div id="spoolman-status-row" style="margin-top:6px;font-size:12px;color:var(--txt2)">
|
||||
<span id="spoolman-status-dot">●</span> <span id="spoolman-status-lbl"></span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Obico -->
|
||||
<div class="card" style="margin-top:10px">
|
||||
<div class="card-title"><span>🕵</span> <span id="modal-sec-obico">Obico</span></div>
|
||||
<div style="font-size:12px;color:var(--txt2);line-height:1.6" id="obico-info-box">
|
||||
Obico wird über den <code>moonraker-obico</code>-Container konfiguriert.<br>
|
||||
Config-Datei: <code id="obico-cfg-path">/mnt/dockerdata/KobraXStack/moonraker-obico/moonraker-obico.cfg</code>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- System -->
|
||||
<div class="set-group" id="setgrp-system">
|
||||
<div class="card">
|
||||
|
||||
@@ -123,8 +123,6 @@
|
||||
"lbl_light": "💡 Licht",
|
||||
"lbl_remaining": "Restzeit:",
|
||||
"lbl_slicer_time": "Slicer-Schätzung:",
|
||||
"lbl_spoolman_sync_rate": "Sync-Rate (s, 0=aus)",
|
||||
"lbl_spoolman_url": "Server-URL",
|
||||
"lbl_unload": "Ausziehen",
|
||||
"lbl_zpos": "Z (mm)",
|
||||
"log_auto": "⬇ Auto",
|
||||
@@ -158,8 +156,6 @@
|
||||
"log_topic_label": "Thema:",
|
||||
"log_topic_print": "Druck",
|
||||
"log_topic_status": "Status",
|
||||
"modal_sec_obico": "Obico",
|
||||
"modal_sec_spoolman": "Spoolman",
|
||||
"nav_ams": "AMS",
|
||||
"nav_browser": "Browser",
|
||||
"nav_console": "Konsole",
|
||||
@@ -235,7 +231,6 @@
|
||||
"settings_file_ready_banner": "Druckleiste",
|
||||
"settings_file_ready_dialog": "Druckdialog",
|
||||
"settings_file_ready_mode": "Nach Upload: Druckstart-Verhalten",
|
||||
"settings_integrations": "Integrationen",
|
||||
"settings_language": "Sprache",
|
||||
"settings_mode_id": "Mode-ID",
|
||||
"settings_mode_id_placeholder": "20030",
|
||||
@@ -321,8 +316,6 @@
|
||||
"update_available": "verfügbar",
|
||||
"update_check": "Auf Updates prüfen",
|
||||
"update_checking": "Prüfe...",
|
||||
"update_docker": "Befehl kopieren",
|
||||
"update_docker_copied": "Kopiert! Ausführen: docker compose pull && docker compose up -d",
|
||||
"update_error": "Fehler",
|
||||
"update_none": "Bereits aktuell",
|
||||
"update_restarting": "Starte neu..."
|
||||
|
||||
@@ -123,8 +123,6 @@
|
||||
"lbl_light": "💡 Light",
|
||||
"lbl_remaining": "Remaining:",
|
||||
"lbl_slicer_time": "Slicer estimate:",
|
||||
"lbl_spoolman_sync_rate": "Sync rate (s, 0=off)",
|
||||
"lbl_spoolman_url": "Server URL",
|
||||
"lbl_unload": "Unload",
|
||||
"lbl_zpos": "Z (mm)",
|
||||
"log_auto": "⬇ Auto",
|
||||
@@ -158,8 +156,6 @@
|
||||
"log_topic_label": "Topic:",
|
||||
"log_topic_print": "Print",
|
||||
"log_topic_status": "Status",
|
||||
"modal_sec_obico": "Obico",
|
||||
"modal_sec_spoolman": "Spoolman",
|
||||
"nav_ams": "AMS",
|
||||
"nav_browser": "Browser",
|
||||
"nav_console": "Console",
|
||||
@@ -235,7 +231,6 @@
|
||||
"settings_file_ready_banner": "Print bar",
|
||||
"settings_file_ready_dialog": "Print dialog",
|
||||
"settings_file_ready_mode": "After upload: Start print behavior",
|
||||
"settings_integrations": "Integrations",
|
||||
"settings_language": "Language",
|
||||
"settings_mode_id": "Mode ID",
|
||||
"settings_mode_id_placeholder": "20030",
|
||||
@@ -321,8 +316,6 @@
|
||||
"update_available": "available",
|
||||
"update_check": "Check for Updates",
|
||||
"update_checking": "Checking...",
|
||||
"update_docker": "Copy Command",
|
||||
"update_docker_copied": "Copied! Run: docker compose pull && docker compose up -d",
|
||||
"update_error": "Error",
|
||||
"update_none": "Already up to date",
|
||||
"update_restarting": "Restarting..."
|
||||
|
||||
@@ -123,8 +123,6 @@
|
||||
"lbl_light": "💡 Luz",
|
||||
"lbl_remaining": "Restante:",
|
||||
"lbl_slicer_time": "Estimación del slicer:",
|
||||
"lbl_spoolman_sync_rate": "Tasa de sincronización (s, 0=desact.)",
|
||||
"lbl_spoolman_url": "URL del servidor",
|
||||
"lbl_unload": "Descargar",
|
||||
"lbl_zpos": "Z (mm)",
|
||||
"log_auto": "⬇ Auto",
|
||||
@@ -158,8 +156,6 @@
|
||||
"log_topic_label": "Tema:",
|
||||
"log_topic_print": "Impresión",
|
||||
"log_topic_status": "Estado",
|
||||
"modal_sec_obico": "Obico",
|
||||
"modal_sec_spoolman": "Spoolman",
|
||||
"nav_ams": "AMS",
|
||||
"nav_browser": "Explorador",
|
||||
"nav_console": "Consola",
|
||||
@@ -235,7 +231,6 @@
|
||||
"settings_file_ready_banner": "Barra de impresión",
|
||||
"settings_file_ready_dialog": "Diálogo de impresión",
|
||||
"settings_file_ready_mode": "Después de carga: Comportamiento de inicio de impresión",
|
||||
"settings_integrations": "Integraciones",
|
||||
"settings_language": "Idioma",
|
||||
"settings_mode_id": "ID de modo",
|
||||
"settings_mode_id_placeholder": "20030",
|
||||
@@ -321,8 +316,6 @@
|
||||
"update_available": "disponible",
|
||||
"update_check": "Buscar actualizaciones",
|
||||
"update_checking": "Comprobando...",
|
||||
"update_docker": "Copiar comando",
|
||||
"update_docker_copied": "Copiado. Ejecutar: docker compose pull && docker compose up -d",
|
||||
"update_error": "Error",
|
||||
"update_none": "Ya actualizado",
|
||||
"update_restarting": "Reiniciando..."
|
||||
|
||||
@@ -123,8 +123,6 @@
|
||||
"lbl_light": "💡 Lumière",
|
||||
"lbl_remaining": "Restant :",
|
||||
"lbl_slicer_time": "Estimation slicer :",
|
||||
"lbl_spoolman_sync_rate": "Taux de sync. (s, 0=désact.)",
|
||||
"lbl_spoolman_url": "URL du serveur",
|
||||
"lbl_unload": "Décharger",
|
||||
"lbl_zpos": "Z (mm)",
|
||||
"log_auto": "⬇ Auto",
|
||||
@@ -158,8 +156,6 @@
|
||||
"log_topic_label": "Sujet :",
|
||||
"log_topic_print": "Impression",
|
||||
"log_topic_status": "Statut",
|
||||
"modal_sec_obico": "Obico",
|
||||
"modal_sec_spoolman": "Spoolman",
|
||||
"nav_ams": "AMS",
|
||||
"nav_browser": "Navigateur",
|
||||
"nav_console": "Console",
|
||||
@@ -235,7 +231,6 @@
|
||||
"settings_file_ready_banner": "Barre d'impression",
|
||||
"settings_file_ready_dialog": "Dialogue d'impression",
|
||||
"settings_file_ready_mode": "Après téléchargement : Comportement de démarrage d'impression",
|
||||
"settings_integrations": "Intégrations",
|
||||
"settings_language": "Langue",
|
||||
"settings_mode_id": "ID du mode",
|
||||
"settings_mode_id_placeholder": "20030",
|
||||
@@ -321,8 +316,6 @@
|
||||
"update_available": "disponible",
|
||||
"update_check": "Vérifier les mises à jour",
|
||||
"update_checking": "Vérification…",
|
||||
"update_docker": "Copier la commande",
|
||||
"update_docker_copied": "Copié ! Exécuter : docker compose pull && docker compose up -d",
|
||||
"update_error": "Erreur",
|
||||
"update_none": "Déjà à jour",
|
||||
"update_restarting": "Redémarrage…"
|
||||
|
||||
@@ -123,8 +123,6 @@
|
||||
"lbl_light": "💡 Luce",
|
||||
"lbl_remaining": "Rimanente:",
|
||||
"lbl_slicer_time": "Stima slicer:",
|
||||
"lbl_spoolman_sync_rate": "Frequenza sync (s, 0=disatt.)",
|
||||
"lbl_spoolman_url": "URL server",
|
||||
"lbl_unload": "Rimuovi",
|
||||
"lbl_zpos": "Z (mm)",
|
||||
"log_auto": "⬇ Auto",
|
||||
@@ -158,8 +156,6 @@
|
||||
"log_topic_label": "Argomento:",
|
||||
"log_topic_print": "Stampa",
|
||||
"log_topic_status": "Stato",
|
||||
"modal_sec_obico": "Obico",
|
||||
"modal_sec_spoolman": "Spoolman",
|
||||
"nav_ams": "AMS",
|
||||
"nav_browser": "Browser",
|
||||
"nav_console": "Console",
|
||||
@@ -235,7 +231,6 @@
|
||||
"settings_file_ready_banner": "Barra di stampa",
|
||||
"settings_file_ready_dialog": "Finestra di dialogo stampa",
|
||||
"settings_file_ready_mode": "Dopo il caricamento: Comportamento di avvio stampa",
|
||||
"settings_integrations": "Integrazioni",
|
||||
"settings_language": "Lingua",
|
||||
"settings_mode_id": "ID modalità",
|
||||
"settings_mode_id_placeholder": "20030",
|
||||
@@ -321,8 +316,6 @@
|
||||
"update_available": "disponibile",
|
||||
"update_check": "Controlla aggiornamenti",
|
||||
"update_checking": "Verifica in corso...",
|
||||
"update_docker": "Copia comando",
|
||||
"update_docker_copied": "Copiato! Eseguire: docker compose pull && docker compose up -d",
|
||||
"update_error": "Errore",
|
||||
"update_none": "Già aggiornato",
|
||||
"update_restarting": "Riavvio in corso..."
|
||||
|
||||
@@ -123,8 +123,6 @@
|
||||
"lbl_light": "💡 灯光",
|
||||
"lbl_remaining": "剩余时间:",
|
||||
"lbl_slicer_time": "切片预估:",
|
||||
"lbl_spoolman_sync_rate": "同步频率(秒,0=关闭)",
|
||||
"lbl_spoolman_url": "服务器地址",
|
||||
"lbl_unload": "退料",
|
||||
"lbl_zpos": "Z (mm)",
|
||||
"log_auto": "⬇ 自动",
|
||||
@@ -158,8 +156,6 @@
|
||||
"log_topic_label": "主题:",
|
||||
"log_topic_print": "打印",
|
||||
"log_topic_status": "状态",
|
||||
"modal_sec_obico": "Obico",
|
||||
"modal_sec_spoolman": "Spoolman",
|
||||
"nav_ams": "AMS",
|
||||
"nav_browser": "浏览器",
|
||||
"nav_console": "控制台",
|
||||
@@ -235,7 +231,6 @@
|
||||
"settings_file_ready_banner": "打印栏",
|
||||
"settings_file_ready_dialog": "打印对话框",
|
||||
"settings_file_ready_mode": "上传后:开始打印行为",
|
||||
"settings_integrations": "集成",
|
||||
"settings_language": "语言",
|
||||
"settings_mode_id": "模式 ID",
|
||||
"settings_mode_id_placeholder": "20030",
|
||||
@@ -321,8 +316,6 @@
|
||||
"update_available": "可用",
|
||||
"update_check": "检查更新",
|
||||
"update_checking": "检查中...",
|
||||
"update_docker": "复制命令",
|
||||
"update_docker_copied": "已复制!执行:docker compose pull && docker compose up -d",
|
||||
"update_error": "错误",
|
||||
"update_none": "已是最新版本",
|
||||
"update_restarting": "重启中..."
|
||||
|
||||
Reference in New Issue
Block a user