Yosuke Tsuchiya - Fab Futures - Data Science
Home About

Get Prusa 3D Printer Status with Python (rev.1)¶

Still Unstable... need to revise more This notebook is a update version from the first version of printer-serial.ipynb

Warning

This notebook should run in your local PC, not fabcloud Jupyter server

Reference:

  • Prusa firmware specific g-code command
  • RepRap G-code Documentation

We can get current printer status with sending G-Code via serial communication, as show in Previous version.

For example:

  • M105 : Could get Temperture status of the printer
  • M114 : Could get X,Y,Z position and extrusion amount or position of the extruder

Moreover, I found the following command.

  • M155 : Count output Temperature, fan status and position automatically

But, connecting to my Prusa Core One and try to send this command, only temperature data could output. Searching the documents of Prusa firmware and other website, at present, I cannot get printer position data automatically.

Therefore, whereas getting temperature data automatically, we need to continueing poll "M114" command continuously.

So, I asked Chat GPT as following to revise previous version:

Please revise previous version of code to get temperture and positon data from Prusa To get temperature data, use M155 command, and use M114 command to get position data.

Then he replied the folliwng code.

First, import serial library and time library

In [10]:
import serial
import threading
import time
from datetime import datetime
import serial.tools.list_ports as list_ports

Then, check the serial port path

In [11]:
ports = list_ports.comports()

for port,desc,hwid in sorted(ports):
    print(f"{port}: {desc} [{hwid}]")
/dev/cu.AnkerPowerConf: n/a [n/a]
/dev/cu.Bluetooth-Incoming-Port: n/a [n/a]
/dev/cu.debug-console: n/a [n/a]
/dev/cu.usbmodem2101: Original Prusa COREONE [USB VID:PID=2C99:001F SER=13052-4742441644809810 LOCATION=2-1]

Set Configurations

In [12]:
PORT = "/dev/cu.usbmodem2101" 
BAUD = 115200 
TEMP_LOG_FILE = "./printer_data_temp_infill.txt"
POS_LOG_FILE = "./printer_data_position_infill.txt"
PRINT_DEBUG = True 

stop_event = threading.Event()

This is the function to save the data from the printer.

In [13]:
def log_line(filepath: str, line: str) -> None:
    """タイムスタンプ付きで1行ログファイルに書き込む"""
    ts = datetime.now().strftime("%Y/%m/%d-%H:%M:%S.%f")
    with open(filepath, "a", encoding="utf-8") as f:
        f.write(f"{ts} {line}\n")

Here is the code that parse logdata and detect the temperature data or position data. Only catching up temperature and position. Other data (like error message, status message from the printer) are dropped here.

(I dropped those status data, but maybe it could be used for analyzing.... )

In [14]:
def classify_and_log(line: str) -> None:
    
    # busy
    if "busy: processing" in line:
        if PRINT_DEBUG:
            print("BUSY:", line)
        return

    # echo メッセージ
    if line.startswith("echo:"):
        if PRINT_DEBUG:
            print("ECHO:", line)
        return

    # 温度行(M155のオートレポート + M105 の応答など)
    if "T:" in line and "B:" in line:
        # 念のため 'ok ' が頭についていたら削除
        data = line.lstrip("ok ").strip()
        log_line(TEMP_LOG_FILE, data)
        if PRINT_DEBUG:
            print("TEMP:", data)
        return

    # 位置行(M114の応答)
    if "X:" in line and "Y:" in line and "Z:" in line and "Count" in line:
        data = line.lstrip("ok ").strip()
        log_line(POS_LOG_FILE, data)
        if PRINT_DEBUG:
            print("POS:", data)
        return

    # その他
    if PRINT_DEBUG:
        print("OTHER:", line)

This function is to keep reading serial data to get temperatura data coming automatically.

In [15]:
def reader_loop(ser: serial.Serial) -> None:

    while not stop_event.is_set():
        try:
            raw = ser.readline()
        except Exception as e:
            print("Serial read error:", e)
            break

        if not raw:
            continue

        line = raw.decode("utf-8", errors="ignore").strip()
        if not line:
            continue

        classify_and_log(line)

This function is to keep writing "M114" command to get position data.

In [16]:
def writer_loop(ser: serial.Serial) -> None:

    while not stop_event.is_set():
        try:
            ser.write(b"M114\n")
            ser.flush()
        except Exception as e:
            print("Serial write error:", e)
            break

        time.sleep(1.0)

Here is main function.

The most important point to use "threding.Thread" to get temperature data with asynchronous processing.

In [ ]:
print(f"Opening serial port {PORT} @ {BAUD}...")
ser = serial.Serial(PORT, BAUD, timeout=1)

time.sleep(2.0)
ser.reset_input_buffer()

print("Enable temperature autoreport: M155 S1")
ser.write(b"M155 S1\n")
ser.flush()

t_reader = threading.Thread(target=reader_loop, args=(ser,), daemon=True)
t_reader.start()

try:

    writer_loop(ser)

except KeyboardInterrupt:
    print("Ctrl+C detected, stopping...")

finally:

    stop_event.set()
    time.sleep(0.5)

    try:
        print("Disable temperature autoreport: M155 S0")
        ser.write(b"M155 S0\n")
        ser.flush()
        time.sleep(0.2)
    except Exception as e:
        print("Error while disabling M155:", e)

    ser.close()
    print("Serial closed.")