Signed-off-by: Peter Siegmund <mars3142@noreply.mars3142.dev>
This commit is contained in:
@@ -17,6 +17,7 @@ jobs:
|
|||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
- name: Set Registry Domain
|
- name: Set Registry Domain
|
||||||
|
# Extracts the registry domain from the server URL
|
||||||
run: |
|
run: |
|
||||||
REGISTRY_DOMAIN=$(echo "${{ github.server_url }}" | sed 's|https://||' | sed 's|http://||')
|
REGISTRY_DOMAIN=$(echo "${{ github.server_url }}" | sed 's|https://||' | sed 's|http://||')
|
||||||
echo "REGISTRY_DOMAIN=$REGISTRY_DOMAIN" >> $GITHUB_ENV
|
echo "REGISTRY_DOMAIN=$REGISTRY_DOMAIN" >> $GITHUB_ENV
|
||||||
@@ -28,6 +29,10 @@ jobs:
|
|||||||
username: ${{ secrets.REGISTRY_USER }}
|
username: ${{ secrets.REGISTRY_USER }}
|
||||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Set version tags from Dockerfile
|
||||||
|
# Reads version from Dockerfile and sets MAJOR, MAJOR_MINOR, VERSION
|
||||||
|
run: ./.gitea/workflows/version_env.sh
|
||||||
|
|
||||||
- name: Build and push Docker image (multi-arch)
|
- name: Build and push Docker image (multi-arch)
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
@@ -35,4 +40,8 @@ jobs:
|
|||||||
file: ./Dockerfile
|
file: ./Dockerfile
|
||||||
push: true
|
push: true
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
tags: ${{ env.REGISTRY_DOMAIN }}/${{ github.repository }}:latest
|
tags: |
|
||||||
|
${{ env.REGISTRY_DOMAIN }}/${{ github.repository }}:latest
|
||||||
|
${{ env.REGISTRY_DOMAIN }}/${{ github.repository }}:${{ env.MAJOR }}
|
||||||
|
${{ env.REGISTRY_DOMAIN }}/${{ github.repository }}:${{ env.MAJOR_MINOR }}
|
||||||
|
${{ env.REGISTRY_DOMAIN }}/${{ github.repository }}:${{ env.VERSION }}
|
||||||
|
|||||||
11
.gitea/workflows/version_env.sh
Normal file
11
.gitea/workflows/version_env.sh
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# version_env.sh: Reads the version from the Dockerfile and sets MAJOR, MAJOR_MINOR, VERSION as environment variables
|
||||||
|
|
||||||
|
VERSION=$(grep 'org.opencontainers.image.version' Dockerfile | cut -d'=' -f2 | tr -d '"')
|
||||||
|
MAJOR=$(echo "$VERSION" | cut -d. -f1)
|
||||||
|
MAJOR_MINOR=$(echo "$VERSION" | awk -F. '{print $1 "." $2}')
|
||||||
|
|
||||||
|
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||||
|
echo "MAJOR=$MAJOR" >> $GITHUB_ENV
|
||||||
|
echo "MAJOR_MINOR=$MAJOR_MINOR" >> $GITHUB_ENV
|
||||||
32
Dockerfile
32
Dockerfile
@@ -1,11 +1,33 @@
|
|||||||
# Dockerfile für bambu.py
|
## Dockerfile for bambu.py
|
||||||
FROM python:3.11-slim
|
FROM python:3.11.7-alpine
|
||||||
|
|
||||||
|
LABEL maintainer="Peter Siegmund <mars3142@noreply.mars3142.dev>"
|
||||||
|
LABEL org.opencontainers.image.source="https://git.mars3142.dev/mars3142/bambu_mqtt"
|
||||||
|
LABEL org.opencontainers.image.description="MQTT-Bridge für BambuLab Drucker"
|
||||||
|
LABEL org.opencontainers.image.version="0.1.0"
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
## Copy only requirements.txt first for better caching
|
||||||
|
COPY requirements.txt /app/requirements.txt
|
||||||
|
RUN apk add --no-cache build-base libffi-dev openssl-dev \
|
||||||
|
&& python -m venv /app/.venv \
|
||||||
|
&& /app/.venv/bin/pip install --no-cache-dir -r /app/requirements.txt \
|
||||||
|
&& apk del build-base libffi-dev openssl-dev
|
||||||
|
|
||||||
|
## Now copy the actual code
|
||||||
COPY bambu.py /app/bambu.py
|
COPY bambu.py /app/bambu.py
|
||||||
|
|
||||||
# Optional: requirements.txt falls weitere Pakete benötigt werden
|
## Create non-root user
|
||||||
RUN pip install --no-cache-dir paho-mqtt dotenv
|
RUN adduser -D -h /app appuser \
|
||||||
|
&& chown -R appuser:appuser /app
|
||||||
|
USER appuser
|
||||||
|
|
||||||
CMD ["python", "-u", "bambu.py"]
|
ENV PATH="/app/.venv/bin:$PATH"
|
||||||
|
|
||||||
|
## Healthcheck: checks if the process is running (simple example)
|
||||||
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
||||||
|
CMD pgrep -f "python -u bambu.py" || exit 1
|
||||||
|
|
||||||
|
## ENTRYPOINT for better extensibility
|
||||||
|
ENTRYPOINT ["python", "-u", "bambu.py"]
|
||||||
|
|||||||
44
bambu.py
44
bambu.py
@@ -1,4 +1,4 @@
|
|||||||
# video url
|
# Video URL example
|
||||||
# ffplay -rtsp_transport tcp -tls_verify 0 "rtsps://bblp:<PRINTER_ACCESS_CODE>@<PRINTER_IP>:322/streaming/live/1"
|
# ffplay -rtsp_transport tcp -tls_verify 0 "rtsps://bblp:<PRINTER_ACCESS_CODE>@<PRINTER_IP>:322/streaming/live/1"
|
||||||
|
|
||||||
import ssl
|
import ssl
|
||||||
@@ -8,7 +8,7 @@ from dotenv import load_dotenv
|
|||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
# --- KONFIGURATION aus .env ---
|
# --- CONFIGURATION from .env ---
|
||||||
PRINTER_IP = os.getenv("PRINTER_IP")
|
PRINTER_IP = os.getenv("PRINTER_IP")
|
||||||
PRINTER_ACCESS_CODE = os.getenv("PRINTER_ACCESS_CODE")
|
PRINTER_ACCESS_CODE = os.getenv("PRINTER_ACCESS_CODE")
|
||||||
PRINTER_SERIAL = os.getenv("PRINTER_SERIAL")
|
PRINTER_SERIAL = os.getenv("PRINTER_SERIAL")
|
||||||
@@ -19,38 +19,40 @@ TARGET_USERNAME = os.getenv("TARGET_USERNAME")
|
|||||||
TARGET_PASSWORD = os.getenv("TARGET_PASSWORD")
|
TARGET_PASSWORD = os.getenv("TARGET_PASSWORD")
|
||||||
TARGET_PREFIX = os.getenv("TARGET_PREFIX", "bambulab/")
|
TARGET_PREFIX = os.getenv("TARGET_PREFIX", "bambulab/")
|
||||||
|
|
||||||
# --- ZIEL-CLIENT (SINK) ---
|
|
||||||
|
# --- TARGET CLIENT (SINK) ---
|
||||||
def setup_target():
|
def setup_target():
|
||||||
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
|
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
|
||||||
# TLS-Konfiguration für Ziel-Broker (Zertifikate werden geprüft)
|
# TLS configuration for target broker (certificates are verified)
|
||||||
client.tls_set(cert_reqs=ssl.CERT_REQUIRED, tls_version=ssl.PROTOCOL_TLSv1_2)
|
client.tls_set(cert_reqs=ssl.CERT_REQUIRED, tls_version=ssl.PROTOCOL_TLSv1_2)
|
||||||
# Authentifizierung
|
# Authentication
|
||||||
client.username_pw_set(TARGET_USERNAME, TARGET_PASSWORD)
|
client.username_pw_set(TARGET_USERNAME, TARGET_PASSWORD)
|
||||||
try:
|
try:
|
||||||
client.connect(TARGET_BROKER_IP, TARGET_PORT, 60)
|
client.connect(TARGET_BROKER_IP, TARGET_PORT, 60)
|
||||||
client.loop_start() # Läuft im Hintergrund-Thread
|
client.loop_start() # Runs in background thread
|
||||||
print(f"[Target] Verbunden mit {TARGET_BROKER_IP}")
|
print(f"[Target] Connected to {TARGET_BROKER_IP}")
|
||||||
return client
|
return client
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[Target] Fehler: {e}")
|
print(f"[Target] Error: {e}")
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
target_client = setup_target()
|
target_client = setup_target()
|
||||||
|
|
||||||
# --- QUELL-CLIENT (SOURCE - BAMBULAB) ---
|
|
||||||
|
# --- SOURCE CLIENT (BAMBULAB) ---
|
||||||
def on_connect_source(client, userdata, flags, rc, properties=None):
|
def on_connect_source(client, userdata, flags, rc, properties=None):
|
||||||
if rc == 0:
|
if rc == 0:
|
||||||
# Abonniere Haupt-Report Topic
|
# Subscribe to main report topic
|
||||||
topic = f"device/{PRINTER_SERIAL}/report"
|
topic = f"device/{PRINTER_SERIAL}/report"
|
||||||
client.subscribe(topic)
|
client.subscribe(topic)
|
||||||
else:
|
else:
|
||||||
print(f"[Source] Verbindung fehlgeschlagen. Code: {rc}")
|
print(f"[Source] Connection failed. Code: {rc}")
|
||||||
|
|
||||||
def on_message_source(client, userdata, msg):
|
def on_message_source(client, userdata, msg):
|
||||||
import datetime
|
import datetime
|
||||||
# Füge Prefix hinzu, z.B. bambu/device/SERIAL/report
|
# Add prefix, e.g. bambu/device/SERIAL/report
|
||||||
mirror_topic = f"{TARGET_PREFIX}{msg.topic}"
|
mirror_topic = f"{TARGET_PREFIX}{msg.topic}"
|
||||||
# Prüfe, ob sich die Daten geändert haben
|
# Check if the data has changed
|
||||||
if not hasattr(on_message_source, "last_payloads"):
|
if not hasattr(on_message_source, "last_payloads"):
|
||||||
on_message_source.last_payloads = {}
|
on_message_source.last_payloads = {}
|
||||||
last_payload = on_message_source.last_payloads.get(mirror_topic)
|
last_payload = on_message_source.last_payloads.get(mirror_topic)
|
||||||
@@ -58,31 +60,31 @@ def on_message_source(client, userdata, msg):
|
|||||||
target_client.publish(mirror_topic, msg.payload, qos=0, retain=False)
|
target_client.publish(mirror_topic, msg.payload, qos=0, retain=False)
|
||||||
on_message_source.last_payloads[mirror_topic] = msg.payload
|
on_message_source.last_payloads[mirror_topic] = msg.payload
|
||||||
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||||
print(f"[{now}] Empfangen & weitergeleitet: Topic={mirror_topic}, Bytes={len(msg.payload)}")
|
print(f"[{now}] Received & forwarded: Topic={mirror_topic}, Bytes={len(msg.payload)}")
|
||||||
else:
|
else:
|
||||||
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||||
print(f"[{now}] Empfangen, aber nicht weitergeleitet (keine Änderung): Topic={mirror_topic}")
|
print(f"[{now}] Received, but not forwarded (no change): Topic={mirror_topic}")
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
# BambuLab benötigt SSL/TLS, akzeptiert aber oft nur unsichere Zertifikate
|
# BambuLab requires SSL/TLS, but often only accepts insecure certificates
|
||||||
source_client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
|
source_client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
|
||||||
|
|
||||||
# TLS Konfiguration für Self-Signed Certs
|
# TLS configuration for self-signed certs
|
||||||
source_client.tls_set(cert_reqs=ssl.CERT_NONE, tls_version=ssl.PROTOCOL_TLSv1_2)
|
source_client.tls_set(cert_reqs=ssl.CERT_NONE, tls_version=ssl.PROTOCOL_TLSv1_2)
|
||||||
source_client.tls_insecure_set(True)
|
source_client.tls_insecure_set(True)
|
||||||
|
|
||||||
# Auth: User ist IMMER 'bblp', Passwort ist der Access Code
|
# Auth: User is ALWAYS 'bblp', password is the access code
|
||||||
source_client.username_pw_set("bblp", PRINTER_ACCESS_CODE)
|
source_client.username_pw_set("bblp", PRINTER_ACCESS_CODE)
|
||||||
|
|
||||||
source_client.on_connect = on_connect_source
|
source_client.on_connect = on_connect_source
|
||||||
source_client.on_message = on_message_source
|
source_client.on_message = on_message_source
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Port 8883 ist Standard für BambuLab MQTTS
|
# Port 8883 is default for BambuLab MQTTS
|
||||||
source_client.connect(PRINTER_IP, 8883, 60)
|
source_client.connect(PRINTER_IP, 8883, 60)
|
||||||
source_client.loop_forever() # Blockiert hier und hält das Script am Leben
|
source_client.loop_forever() # Blocks here and keeps the script alive
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("\nBeende...")
|
print("\nExiting...")
|
||||||
target_client.loop_stop()
|
target_client.loop_stop()
|
||||||
source_client.disconnect()
|
source_client.disconnect()
|
||||||
|
|
||||||
|
|||||||
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
paho-mqtt
|
||||||
|
python-dotenv
|
||||||
Reference in New Issue
Block a user