Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added historical calibration database to the plugin and safeguard #83

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
env/
108 changes: 104 additions & 4 deletions z_calibration.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,84 @@

# Klipper plugin for a selfcalibrating Z offset.
#
# Copyright (C) 2021-2022 Titus Meyer <[email protected]>
# Contributors: JR Lomas <[email protected]>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import logging
from mcu import MCU_endstop
import sqlite3
import time
import math
import statistics

def create_database(config):
"""Create the database for storing calibration data."""
conn = sqlite3.connect(config.get('database', 'database.db'))
c = conn.cursor()
c.execute('''CREATE TABLE IF NOT EXISTS nozzle_run(nozzle_run_id integer not null primary key autoincrement, unix_time integer not null)''')
c.execute('''CREATE TABLE IF NOT EXISTS
calibration(nozzle_run_id integer, unix_time integer, endstop real, nozzle real, switch real, probe real, offset real)''')
conn.commit()
c.close()
conn.close()


def insert_data(config, endstop, nozzle, switch, probe, offset):
"""Insert data into the database."""
conn = sqlite3.connect(config.get('database', 'database.db'))
c = conn.cursor()
# Get unix time
unix_time = int(time.time())
res = c.execute('''SELECT MAX(nozzle_run_id) FROM nozzle_run''',)
nozzle_run_id = None
row = res.fetchone()
if row is not None and row[0] is not None:
nozzle_run_id = row[0]
else:
nozzle_run_id = reset_nozzle_offset(config)
c.execute('''INSERT INTO calibration VALUES(?,?,?,?,?,?,?)''',
(nozzle_run_id, unix_time, endstop, nozzle, switch, probe, offset))
conn.commit()
c.close()
conn.close()

def get_historical_offset(config):
"""Get the historical offset from the database."""
conn = sqlite3.connect(config.get('database', 'database.db'))
c = conn.cursor()
res = c.execute('''SELECT MAX(nozzle_run_id) as last_nozzle_run_id FROM nozzle_run''',)
nozzle_run_id = res.fetchone()[0]
# select number of rows
res = c.execute('''SELECT COUNT(*) as count FROM calibration WHERE nozzle_run_id = ?''', (nozzle_run_id,))
count = res.fetchone()[0]
if count < 10:
return None, None
samples = 10
res = c.execute('''SELECT offset FROM calibration ORDER BY nozzle_run_id DESC LIMIT ?''',(samples,))
offsets = res.fetchall()
avg_offset = 0
proper_offsets = []
for offset in offsets:
avg_offset += offset[0]/samples
proper_offsets.append(offset[0])
stdev_offset = statistics.stdev(proper_offsets)
offset_3_sigma = 3*stdev_offset
c.close()
conn.close()
return avg_offset, offset_3_sigma

def reset_nozzle_offset(config):
"""Reset the nozzle offset."""
conn = sqlite3.connect(config.get('database', 'database.db'))
c = conn.cursor()
unix_time = int(time.time())
c.execute('''INSERT INTO nozzle_run(unix_time) VALUES(?)''', (unix_time,))
res = c.execute('''SELECT MAX(nozzle_run_id) as last_nozzle_run_id FROM nozzle_run''',)
nozzle_run_id = res.fetchone()[0]
conn.commit()
c.close()
conn.close()
return nozzle_run_id

class ZCalibrationHelper:
def __init__(self, config):
Expand Down Expand Up @@ -56,6 +129,9 @@ def __init__(self, config):
self.gcode.register_command('PROBE_Z_ACCURACY',
self.cmd_PROBE_Z_ACCURACY,
desc=self.cmd_PROBE_Z_ACCURACY_help)
self.gcode.register_command('RESET_HISTORICAL_OFFSET',
self.cmd_RESET_HISTORICAL_OFFSET,
desc=self.cmd_RESET_HISTORICAL_OFFSET_help)
def get_status(self, eventtime):
return {'last_query': self.last_state,
'last_z_offset': self.last_z_offset}
Expand Down Expand Up @@ -104,6 +180,14 @@ def handle_home_rails_end(self, homing_state, rails):
self.position_min = rail.position_min
def _build_config(self):
pass

cmd_RESET_HISTORICAL_OFFSET_help = ("Resets the historical offset"
"database")
def cmd_RESET_HISTORICAL_OFFSET(self, gcmd):
reset_nozzle_offset(self.config)
gcmd.respond_info("Historical offset reset")


cmd_CALIBRATE_Z_help = ("Automatically calibrates the nozzle offset"
" to the print surface")
def cmd_CALIBRATE_Z(self, gcmd):
Expand All @@ -121,7 +205,7 @@ def cmd_CALIBRATE_Z(self, gcmd):
# a round mesh/bed would not work here so far...
try:
mesh = self.printer.lookup_object('bed_mesh', default=None)
rri = mesh.bmc.relative_reference_index
rri = mesh.bmc.relative_reference_index
self.bed_site = mesh.bmc.points[rri]
logging.debug("Z-CALIBRATION probe bed_x=%.3f bed_y=%.3f"
% (self.bed_site[0], self.bed_site[1]))
Expand Down Expand Up @@ -181,7 +265,7 @@ def cmd_PROBE_Z_ACCURACY(self, gcmd):
gcmd.respond_info(
"probe accuracy results: maximum %.6f, minimum %.6f, range %.6f,"
" average %.6f, median %.6f, standard deviation %.6f" % (
max_value, min_value, range_value, avg_value, median, sigma))
max_value, min_value, range_value, avg_value, median, sigma))
def _get_site(self, name, legacy_prefix, optional=False):
legacy_x = self.config.getfloat("%s_x" % (legacy_prefix), -1.0)
legacy_y = self.config.getfloat("%s_y" % (legacy_prefix), -1.0)
Expand Down Expand Up @@ -345,6 +429,7 @@ def calibrate_z(self):
" SWITCH=%.3f PROBE=%.3f --> OFFSET=%.6f"
% (self.helper.z_homing, nozzle_zero,
switch_zero, probe_zero, offset))
insert_data(self.helper.config, self.helper.z_homing, nozzle_zero, switch_zero,probe_zero, offset)
# check max deviation
if abs(offset) > self.helper.max_deviation:
self.helper.end_gcode.run_gcode_from_command()
Expand All @@ -353,11 +438,26 @@ def calibrate_z(self):
" MAX_DEVIATION=%.3f"
% (offset,
self.helper.max_deviation))

avg_offset, offset_99 = get_historical_offset(self.helper.config)

if avg_offset is not None and offset_99 is not None:
self.gcmd.respond_info("Z-CALIBRATION: HISTORICAL_OFFSET=%.3f"
" VARIANCE=%.3f SIGMA=%.3f 3_SIGMA=%.3f"
% (avg_offset, abs(avg_offset - offset), offset_99/3.0, offset_99))
# check if offset is far from historical values
if avg_offset is not None and abs(avg_offset - offset) > offset_99:
self.helper.end_gcode.run_gcode_from_command()
raise self.helper.printer.command_error("Offset is larger than 3 sigma"
" allowed: OFFSET=%.3f"
" HISTORICAL_OFFSET=%.3f. Did you change the nozzle, if so, please issue RESET_HISTORICAL_OFFSET on the console."
% (offset, offset_99))
# set new offset
self._set_new_gcode_offset(offset)
# set states
self.helper.last_state = True
self.helper.last_z_offset = offset
self.helper.end_gcode.run_gcode_from_command()
def load_config(config):
return ZCalibrationHelper(config)
create_database(config)
return ZCalibrationHelper(config)