From 3a6ecf8e9f39ec79efe3f1f42129423d842827ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Bedn=C3=A1rik?= Date: Fri, 13 Dec 2024 11:01:41 +0100 Subject: [PATCH] Code cleanup: Separate spoolman_client Bugfix: Unchanged extras must be sent to spoolman for them not to disappear Bugfix: Remove active tray from inactive spools Docs: Add note about the Base_URL --- README.md | 5 +++- app.py | 44 +++++++++++++--------------------- mqtt_bambulab.py | 59 ++++++++++++++++++++++++++++------------------ spoolman_client.py | 27 +++++++++++++++++++++ 4 files changed, 84 insertions(+), 51 deletions(-) create mode 100644 spoolman_client.py diff --git a/README.md b/README.md index 53f694c..d465b1f 100644 --- a/README.md +++ b/README.md @@ -40,11 +40,14 @@ How to setup: - On the website pick the slot you put your filament in - Done +Notes: + - If you change the BASE_URL of this app, you will need to reconfigure all NFC TAGS + TBD: - Filament remaining in AMS (I have only AMS lite, if you have AMS we can test together) - Filament spending based on printing - Evidently needed GUI improvements - Code cleanup - Deployment as Docker and Helm chart - - Easy configuration via env properties - Video showcase + - TODOs diff --git a/app.py b/app.py index 155c19e..8db9657 100644 --- a/app.py +++ b/app.py @@ -1,16 +1,17 @@ import json import uuid -import requests from flask import Flask, request, render_template_string -from config import BASE_URL, PRINTER_ID, SPOOLMAN_API_URL +from config import BASE_URL from filament import generate_filament_brand_code, generate_filament_temperatures from messages import AMS_FILAMENT_SETTING -from mqtt_bambulab import fetchSpools, getLastAMSConfig, publish, getMqttClient +from mqtt_bambulab import fetchSpools, getLastAMSConfig, publish, getMqttClient, setActiveTray +from spoolman_client import patchExtraTags, getSpoolById app = Flask(__name__) + @app.route("/spool_info") def spool_info(): tag_id = request.args.get("tag_id") @@ -68,6 +69,7 @@ def spool_info(): return render_template_string(html) + @app.route("/tray_load") def tray_load(): tag_id = request.args.get("tag_id") @@ -80,25 +82,15 @@ def tray_load(): try: # Update Spoolman with the selected tray - response = requests.patch(f"{SPOOLMAN_API_URL}/spool/{spool_id}", json={ - "extra": { - "tag": json.dumps(tag_id), - "active_tray": json.dumps(f"{PRINTER_ID}_{ams_id}_{tray_id}"), - } - }) - print(response.status_code) - print(response.text) + spool_data = getSpoolById(spool_id) - response = requests.get(f"{SPOOLMAN_API_URL}/spool/{spool_id}") - print(response.status_code) - print(response.text) + setActiveTray(spool_id, spool_data["extra"], ams_id, tray_id) - spool_data = json.loads(response.text) ams_message = AMS_FILAMENT_SETTING ams_message["print"]["sequence_id"] = 0 ams_message["print"]["ams_id"] = int(ams_id) ams_message["print"]["tray_id"] = int(tray_id) - ams_message["print"]["tray_color"] = spool_data["filament"]["color_hex"].upper()+"FF" + ams_message["print"]["tray_color"] = spool_data["filament"]["color_hex"].upper() + "FF" if "nozzle_temperature" in spool_data["filament"]["extra"]: nozzle_temperature_range = spool_data["filament"]["extra"]["nozzle_temperature"].strip("[]").split(",") @@ -112,11 +104,12 @@ def tray_load(): ams_message["print"]["tray_type"] = spool_data["filament"]["material"] filament_brand_code = generate_filament_brand_code(spool_data["filament"]["material"], - spool_data["filament"]["vendor"]["name"], - spool_data["filament"]["extra"].get("type", "")) + spool_data["filament"]["vendor"]["name"], + spool_data["filament"]["extra"].get("type", "")) ams_message["print"]["tray_info_idx"] = filament_brand_code["brand_code"] - #ams_message["print"]["tray_sub_brands"] = filament_brand_code["sub_brand_code"] + # TODO: test sub_brand_code + # ams_message["print"]["tray_sub_brands"] = filament_brand_code["sub_brand_code"] ams_message["print"]["tray_sub_brands"] = "" print(ams_message) @@ -129,10 +122,10 @@ def tray_load(): except Exception as e: return f"

Error

{str(e)}

" + @app.route("/") def home(): try: - # Update Spoolman with the selected tray spools = fetchSpools() last_ams_config = getLastAMSConfig() @@ -176,6 +169,7 @@ def home(): except Exception as e: return f"

Error

{str(e)}

" + @app.route("/assign_tag") def assign_tag(): spool_id = request.args.get("spool_id") @@ -185,15 +179,10 @@ def assign_tag(): myuuid = str(uuid.uuid4()) - resp = requests.patch(f"{SPOOLMAN_API_URL}/spool/{spool_id}", json={ - "extra": { - "tag": json.dumps(myuuid), - } + patchExtraTags(spool_id, {}, { + "tag": json.dumps(myuuid), }) - print(resp.status_code) - print(resp.raw) - return f"""
@@ -217,6 +206,7 @@ def assign_tag(): """ + @app.route('/', methods=['GET']) def health(): return "OK", 200 diff --git a/mqtt_bambulab.py b/mqtt_bambulab.py index 3975b1f..f52d35b 100644 --- a/mqtt_bambulab.py +++ b/mqtt_bambulab.py @@ -4,15 +4,16 @@ import traceback from threading import Thread import paho.mqtt.client as mqtt -import requests -from config import PRINTER_ID, PRINTER_CODE, PRINTER_IP, SPOOLMAN_API_URL +from config import PRINTER_ID, PRINTER_CODE, PRINTER_IP from messages import GET_VERSION, PUSH_ALL +from spoolman_client import fetchSpoolList, patchExtraTags def num2letter(num): return chr(ord("A") + int(num)) + def publish(client, msg): result = client.publish(f"device/{PRINTER_ID}/request", json.dumps(msg)) status = result[0] @@ -23,12 +24,14 @@ def publish(client, msg): print(f"Failed to send message to topic device/{PRINTER_ID}/request") return False + # Inspired by https://github.com/Donkie/Spoolman/issues/217#issuecomment-2303022970 def on_message(client, userdata, msg): global LAST_AMS_CONFIG # TODO: Consume spool try: data = json.loads(msg.payload.decode()) + print(data) if "print" in data and "vt_tray" in data["print"]: print(data) LAST_AMS_CONFIG["vt_tray"] = data["print"]["vt_tray"] @@ -42,7 +45,8 @@ def on_message(client, userdata, msg): print(f"AMS [{num2letter(ams['id'])}] (hum: {ams['humidity']}, temp: {ams['temp']}ÂșC)") for tray in ams["tray"]: if "tray_sub_brands" in tray: - print(f" - [{num2letter(ams['id'])}{tray['id']}] {tray['tray_sub_brands']} {tray['tray_color']} ({str(tray['remain']).zfill(3)}%) [[ {tray['tag_uid']} ]]") + print( + f" - [{num2letter(ams['id'])}{tray['id']}] {tray['tray_sub_brands']} {tray['tray_color']} ({str(tray['remain']).zfill(3)}%) [[ {tray['tag_uid']} ]]") found = False for spool in SPOOLS: @@ -54,40 +58,45 @@ def on_message(client, userdata, msg): found = True - resp = requests.patch(f"{SPOOLMAN_API_URL}/spool/{spool['id']}", json={ - "extra": { - "tag": spool["extra"]["tag"], - "active_tray": json.dumps(f"{PRINTER_ID}_{ams['id']}_{tray['id']}"), - } - }) + setActiveTray(spool['id'], spool["extra"], ams['id'], tray["id"]) - print(resp.text) - print(resp.status_code) - - #TODO: remove active_tray from inactive spools - #Doesn't work for AMS Lite - #requests.patch(f"http://{SPOOLMAN_IP}:7912/api/v1/spool/{spool['id']}", json={ + # TODO: filament remaining - Doesn't work for AMS Lite + # requests.patch(f"http://{SPOOLMAN_IP}:7912/api/v1/spool/{spool['id']}", json={ # "remaining_weight": tray["remain"] / 100 * tray["tray_weight"] - #}) + # }) - if not found: - print(" - Not found. Update spool tag!") + if not found: + print(" - Not found. Update spool tag!") except Exception as e: traceback.print_exc() + def on_connect(client, userdata, flags, rc): - print("Connected with result code "+str(rc)) + print("Connected with result code " + str(rc)) client.subscribe(f"device/{PRINTER_ID}/report") publish(client, GET_VERSION) publish(client, PUSH_ALL) + +def setActiveTray(spool_id, spool_extra, ams_id, tray_id): + patchExtraTags(spool_id, spool_extra, { + "active_tray": json.dumps(f"{PRINTER_ID}_{ams_id}_{tray_id}"), + }) + + # Remove active tray from inactive spools + for old_spool in SPOOLS: + if spool_id != old_spool["id"] and old_spool["extra"]["active_tray"] == json.dumps( + f"{PRINTER_ID}_{ams_id}_{tray_id}"): + patchExtraTags(old_spool["id"], old_spool["extra"], {"active_tray": json.dumps("")}) + + # Fetch spools from spoolman def fetchSpools(): global SPOOLS - response = requests.get(f"{SPOOLMAN_API_URL}/spool") - SPOOLS = response.json() + SPOOLS = fetchSpoolList() return SPOOLS + def async_subscribe(): global MQTT_CLIENT MQTT_CLIENT = mqtt.Client() @@ -102,18 +111,22 @@ def async_subscribe(): MQTT_CLIENT.connect(PRINTER_IP, 8883) MQTT_CLIENT.loop_forever() + # Start the asynchronous processing in a separate thread thread = Thread(target=async_subscribe) thread.start() + def getLastAMSConfig(): global LAST_AMS_CONFIG return LAST_AMS_CONFIG + def getMqttClient(): global MQTT_CLIENT return MQTT_CLIENT -MQTT_CLIENT = {} # Global variable storing MQTT Client + +MQTT_CLIENT = {} # Global variable storing MQTT Client LAST_AMS_CONFIG = {} # Global variable storing last AMS configuration -SPOOLS = fetchSpools() # Global variable storing latest spool from spoolman +SPOOLS = fetchSpools() # Global variable storing latest spool from spoolman diff --git a/spoolman_client.py b/spoolman_client.py new file mode 100644 index 0000000..930a39b --- /dev/null +++ b/spoolman_client.py @@ -0,0 +1,27 @@ +import requests +from config import SPOOLMAN_API_URL + + +def patchExtraTags(spool_id, old_extras, new_extras): + for key, value in new_extras.items(): + old_extras[key] = value + + resp = requests.patch(f"{SPOOLMAN_API_URL}/spool/{spool_id}", json={ + "extra": old_extras + }) + print(resp.text) + print(resp.status_code) + + +def getSpoolById(spool_id): + response = requests.get(f"{SPOOLMAN_API_URL}/spool/{spool_id}") + print(response.status_code) + print(response.text) + return response.json() + + +def fetchSpoolList(): + response = requests.get(f"{SPOOLMAN_API_URL}/spool") + print(response.status_code) + print(response.text) + return response.json()