Prusa-Firmware/tools/tml_decode

99 lines
3.2 KiB
Python
Executable File

#!/usr/bin/env python3
import argparse
import re
import struct
TM_INTV = 270 # internal temperature regulation interval (ms)
FAN_MAX_LAG = 2500 # maximum fan lag for reporting (ms)
def parse_therm_dump(path):
# header
yield ['sample', 'ms', 'int', 'pwm', 't_nozzle', 't_ambient', 'fan']
cnt = 0
fan = float('NAN')
fan_lag = 0
for line in open(path):
# opportunistically parse M155 fan values
m = re.search(r'\bE0:\d+ RPM PRN1:\d+ RPM E0@:\d+ \bPRN1@:(\d+)$', line)
if m is not None:
fan = int(m.group(1))
fan_lag = 0
elif fan_lag > int(FAN_MAX_LAG/TM_INTV):
fan = float('NAN')
# search for the D70 TML output signature
m = re.search(r'\bTML (\d+) (\d+) ([0-9a-f]+) ([0-9a-f]+) ([0-9a-f]+)$', line)
if m is None:
continue
# decode fields
skip = int(m.group(1))
intv = int(m.group(2)) + TM_INTV
pwm = int(m.group(3), 16)
t = struct.unpack('f', int(m.group(4), 16).to_bytes(4, 'little'))[0]
a = struct.unpack('f', int(m.group(5), 16).to_bytes(4, 'little'))[0]
# output values
ms = cnt * TM_INTV
yield [cnt, ms, intv, pwm, t, a, fan]
smp = skip + 1
cnt += smp
fan_lag += smp
def plot_therm_dump(data, output, title):
import matplotlib.pyplot as plt
import numpy as np
plt.gcf().set_size_inches(20, 5)
ts = np.array(data['ms']) / 1000
colors = iter([x['color'] for x in plt.rcParams['axes.prop_cycle']])
ax1 = plt.gca()
ax1.plot(ts, data['t_nozzle'], c=next(colors), label='Nozzle (C)')
ax1.axhline(data['t_ambient'][0], ls='-.', c='k', label='Ambient baseline (C)')
ax1.plot(ts, data['t_ambient'], c=next(colors), label='Ambient (C)')
ax1.set_ylabel('Temperature (C)')
ax1.set_xlabel('Time (s)')
ax1.legend(loc='upper left')
ax2 = plt.twinx()
ax2.plot(ts, np.array(data['fan']) / 255 * 100, c=next(colors), label='Fan (%)')
ax2.plot(ts, np.array(data['pwm']) / 127 * 100, c=next(colors), label='Heater (%)')
ax2.set_ylim(-1, 101)
ax2.set_ylabel('Power (%)')
ax2.legend(loc='upper right')
plt.title(title)
plt.tight_layout()
plt.savefig(output, dpi=300)
def main():
ap = argparse.ArgumentParser(description='Decode (or plot) the TML trace contained in the serial LOG',
epilog="""
The TML trace needs to be enabled by issuing "M155 S1 C3" and "D70 S1" to the printer.
Output the decoded trace to standard output by default. If --plot is provided, produce a
graph into IMG directly instead.
""")
ap.add_argument('log', metavar='LOG', help='Serial log file containing D70 debugging traces')
ap.add_argument('--plot', '-p', metavar='IMG', help='Plot trace into IMG (eg: output.png)')
args = ap.parse_args()
if args.plot is None:
# just output the streaming trace
for line in parse_therm_dump(args.log):
print('\t'.join(map(str, line)))
else:
# convert to dict and plot
it = parse_therm_dump(args.log)
data = dict(zip(next(it), zip(*it)))
plot_therm_dump(data, args.plot, args.log)
if __name__ == '__main__':
exit(main())