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
This commit is contained in:
Filip Bednárik 2024-12-13 11:01:41 +01:00
parent a56c78b24a
commit 3a6ecf8e9f
4 changed files with 84 additions and 51 deletions

View File

@ -40,11 +40,14 @@ How to setup:
- On the website pick the slot you put your filament in - On the website pick the slot you put your filament in
- Done - Done
Notes:
- If you change the BASE_URL of this app, you will need to reconfigure all NFC TAGS
TBD: TBD:
- Filament remaining in AMS (I have only AMS lite, if you have AMS we can test together) - Filament remaining in AMS (I have only AMS lite, if you have AMS we can test together)
- Filament spending based on printing - Filament spending based on printing
- Evidently needed GUI improvements - Evidently needed GUI improvements
- Code cleanup - Code cleanup
- Deployment as Docker and Helm chart - Deployment as Docker and Helm chart
- Easy configuration via env properties
- Video showcase - Video showcase
- TODOs

44
app.py
View File

@ -1,16 +1,17 @@
import json import json
import uuid import uuid
import requests
from flask import Flask, request, render_template_string 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 filament import generate_filament_brand_code, generate_filament_temperatures
from messages import AMS_FILAMENT_SETTING 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 = Flask(__name__)
@app.route("/spool_info") @app.route("/spool_info")
def spool_info(): def spool_info():
tag_id = request.args.get("tag_id") tag_id = request.args.get("tag_id")
@ -68,6 +69,7 @@ def spool_info():
return render_template_string(html) return render_template_string(html)
@app.route("/tray_load") @app.route("/tray_load")
def tray_load(): def tray_load():
tag_id = request.args.get("tag_id") tag_id = request.args.get("tag_id")
@ -80,25 +82,15 @@ def tray_load():
try: try:
# Update Spoolman with the selected tray # Update Spoolman with the selected tray
response = requests.patch(f"{SPOOLMAN_API_URL}/spool/{spool_id}", json={ spool_data = getSpoolById(spool_id)
"extra": {
"tag": json.dumps(tag_id),
"active_tray": json.dumps(f"{PRINTER_ID}_{ams_id}_{tray_id}"),
}
})
print(response.status_code)
print(response.text)
response = requests.get(f"{SPOOLMAN_API_URL}/spool/{spool_id}") setActiveTray(spool_id, spool_data["extra"], ams_id, tray_id)
print(response.status_code)
print(response.text)
spool_data = json.loads(response.text)
ams_message = AMS_FILAMENT_SETTING ams_message = AMS_FILAMENT_SETTING
ams_message["print"]["sequence_id"] = 0 ams_message["print"]["sequence_id"] = 0
ams_message["print"]["ams_id"] = int(ams_id) ams_message["print"]["ams_id"] = int(ams_id)
ams_message["print"]["tray_id"] = int(tray_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"]: if "nozzle_temperature" in spool_data["filament"]["extra"]:
nozzle_temperature_range = spool_data["filament"]["extra"]["nozzle_temperature"].strip("[]").split(",") 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"] ams_message["print"]["tray_type"] = spool_data["filament"]["material"]
filament_brand_code = generate_filament_brand_code(spool_data["filament"]["material"], filament_brand_code = generate_filament_brand_code(spool_data["filament"]["material"],
spool_data["filament"]["vendor"]["name"], spool_data["filament"]["vendor"]["name"],
spool_data["filament"]["extra"].get("type", "")) spool_data["filament"]["extra"].get("type", ""))
ams_message["print"]["tray_info_idx"] = filament_brand_code["brand_code"] 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"] = "" ams_message["print"]["tray_sub_brands"] = ""
print(ams_message) print(ams_message)
@ -129,10 +122,10 @@ def tray_load():
except Exception as e: except Exception as e:
return f"<h1>Error</h1><p>{str(e)}</p>" return f"<h1>Error</h1><p>{str(e)}</p>"
@app.route("/") @app.route("/")
def home(): def home():
try: try:
# Update Spoolman with the selected tray
spools = fetchSpools() spools = fetchSpools()
last_ams_config = getLastAMSConfig() last_ams_config = getLastAMSConfig()
@ -176,6 +169,7 @@ def home():
except Exception as e: except Exception as e:
return f"<h1>Error</h1><p>{str(e)}</p>" return f"<h1>Error</h1><p>{str(e)}</p>"
@app.route("/assign_tag") @app.route("/assign_tag")
def assign_tag(): def assign_tag():
spool_id = request.args.get("spool_id") spool_id = request.args.get("spool_id")
@ -185,15 +179,10 @@ def assign_tag():
myuuid = str(uuid.uuid4()) myuuid = str(uuid.uuid4())
resp = requests.patch(f"{SPOOLMAN_API_URL}/spool/{spool_id}", json={ patchExtraTags(spool_id, {}, {
"extra": { "tag": json.dumps(myuuid),
"tag": json.dumps(myuuid),
}
}) })
print(resp.status_code)
print(resp.raw)
return f""" return f"""
<html> <html>
<header> <header>
@ -217,6 +206,7 @@ def assign_tag():
</html> </html>
""" """
@app.route('/', methods=['GET']) @app.route('/', methods=['GET'])
def health(): def health():
return "OK", 200 return "OK", 200

View File

@ -4,15 +4,16 @@ import traceback
from threading import Thread from threading import Thread
import paho.mqtt.client as mqtt 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 messages import GET_VERSION, PUSH_ALL
from spoolman_client import fetchSpoolList, patchExtraTags
def num2letter(num): def num2letter(num):
return chr(ord("A") + int(num)) return chr(ord("A") + int(num))
def publish(client, msg): def publish(client, msg):
result = client.publish(f"device/{PRINTER_ID}/request", json.dumps(msg)) result = client.publish(f"device/{PRINTER_ID}/request", json.dumps(msg))
status = result[0] status = result[0]
@ -23,12 +24,14 @@ def publish(client, msg):
print(f"Failed to send message to topic device/{PRINTER_ID}/request") print(f"Failed to send message to topic device/{PRINTER_ID}/request")
return False return False
# Inspired by https://github.com/Donkie/Spoolman/issues/217#issuecomment-2303022970 # Inspired by https://github.com/Donkie/Spoolman/issues/217#issuecomment-2303022970
def on_message(client, userdata, msg): def on_message(client, userdata, msg):
global LAST_AMS_CONFIG global LAST_AMS_CONFIG
# TODO: Consume spool # TODO: Consume spool
try: try:
data = json.loads(msg.payload.decode()) data = json.loads(msg.payload.decode())
print(data)
if "print" in data and "vt_tray" in data["print"]: if "print" in data and "vt_tray" in data["print"]:
print(data) print(data)
LAST_AMS_CONFIG["vt_tray"] = data["print"]["vt_tray"] 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)") print(f"AMS [{num2letter(ams['id'])}] (hum: {ams['humidity']}, temp: {ams['temp']}ºC)")
for tray in ams["tray"]: for tray in ams["tray"]:
if "tray_sub_brands" in 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 found = False
for spool in SPOOLS: for spool in SPOOLS:
@ -54,40 +58,45 @@ def on_message(client, userdata, msg):
found = True found = True
resp = requests.patch(f"{SPOOLMAN_API_URL}/spool/{spool['id']}", json={ setActiveTray(spool['id'], spool["extra"], ams['id'], tray["id"])
"extra": {
"tag": spool["extra"]["tag"],
"active_tray": json.dumps(f"{PRINTER_ID}_{ams['id']}_{tray['id']}"),
}
})
print(resp.text) # TODO: filament remaining - Doesn't work for AMS Lite
print(resp.status_code) # requests.patch(f"http://{SPOOLMAN_IP}:7912/api/v1/spool/{spool['id']}", json={
#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={
# "remaining_weight": tray["remain"] / 100 * tray["tray_weight"] # "remaining_weight": tray["remain"] / 100 * tray["tray_weight"]
#}) # })
if not found: if not found:
print(" - Not found. Update spool tag!") print(" - Not found. Update spool tag!")
except Exception as e: except Exception as e:
traceback.print_exc() traceback.print_exc()
def on_connect(client, userdata, flags, rc): 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") client.subscribe(f"device/{PRINTER_ID}/report")
publish(client, GET_VERSION) publish(client, GET_VERSION)
publish(client, PUSH_ALL) 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 # Fetch spools from spoolman
def fetchSpools(): def fetchSpools():
global SPOOLS global SPOOLS
response = requests.get(f"{SPOOLMAN_API_URL}/spool") SPOOLS = fetchSpoolList()
SPOOLS = response.json()
return SPOOLS return SPOOLS
def async_subscribe(): def async_subscribe():
global MQTT_CLIENT global MQTT_CLIENT
MQTT_CLIENT = mqtt.Client() MQTT_CLIENT = mqtt.Client()
@ -102,18 +111,22 @@ def async_subscribe():
MQTT_CLIENT.connect(PRINTER_IP, 8883) MQTT_CLIENT.connect(PRINTER_IP, 8883)
MQTT_CLIENT.loop_forever() MQTT_CLIENT.loop_forever()
# Start the asynchronous processing in a separate thread # Start the asynchronous processing in a separate thread
thread = Thread(target=async_subscribe) thread = Thread(target=async_subscribe)
thread.start() thread.start()
def getLastAMSConfig(): def getLastAMSConfig():
global LAST_AMS_CONFIG global LAST_AMS_CONFIG
return LAST_AMS_CONFIG return LAST_AMS_CONFIG
def getMqttClient(): def getMqttClient():
global MQTT_CLIENT global MQTT_CLIENT
return 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 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

27
spoolman_client.py Normal file
View File

@ -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()