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

Feat: dynamic frequency #211

Merged
merged 5 commits into from
Dec 24, 2024
Merged
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
76 changes: 66 additions & 10 deletions scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import msgproto
import numpy as np
import pins
from collections import deque
from clocksync import SecondarySync
from configfile import ConfigWrapper
from gcode import GCodeCommand
Expand Down Expand Up @@ -60,6 +61,10 @@
THRESHOLD_STEP_MULTIPLIER = 10
# Require a qualified threshold to pass at 0.66 of the QUALIFY_SAMPLES
THRESHOLD_ACCEPTANCE_FACTOR = 0.66
SLIDING_WINDOW_SIZE = 50
SIGMA_MULTIPLIER = 3
THRESHOLD_MULTIPLIER = 1.2
SHORTED_COIL_VALUE = 0xFFFFFFF


class TriggerMethod(IntEnum):
Expand Down Expand Up @@ -170,7 +175,7 @@
)

atypes = {"median": "median", "average": "average"}
self.samples_config = {

Check warning on line 178 in scanner.py

View workflow job for this annotation

GitHub Actions / Standard Checks

Type of "samples_config" is partially unknown   Type of "samples_config" is "dict[str, Unknown]" (reportUnknownMemberType)

Check warning on line 178 in scanner.py

View workflow job for this annotation

GitHub Actions / K1 Checks

Type of "samples_config" is partially unknown   Type of "samples_config" is "dict[str, Unknown]" (reportUnknownMemberType)
"samples": config.getfloat("samples", 5, above=0.0),
"retract_dist": config.getfloat("samples_retract_dist", 5, above=0.0),
"tolerance": config.getfloat("samples_tolerance", 0.2, minval=0.0),
Expand Down Expand Up @@ -215,7 +220,7 @@
"scanner_touch_max_speed", 10, above=0, maxval=30
)
## NEW VARIABLES HERE
self.scanner_touch_config = {

Check warning on line 223 in scanner.py

View workflow job for this annotation

GitHub Actions / Standard Checks

Type of "scanner_touch_config" is partially unknown   Type of "scanner_touch_config" is "dict[str, Unknown]" (reportUnknownMemberType)

Check warning on line 223 in scanner.py

View workflow job for this annotation

GitHub Actions / K1 Checks

Type of "scanner_touch_config" is partially unknown   Type of "scanner_touch_config" is "dict[str, Unknown]" (reportUnknownMemberType)
"accel": config.getfloat("scanner_touch_accel", 100, above=0, minval=100),
"max_speed": max_speed_value,
"speed": config.getfloat("scanner_touch_speed", 3, maxval=max_speed_value),
Expand All @@ -241,7 +246,7 @@
raise self.printer.command_error(
"Please change detect_threshold_z to scanner_touch_threshold in printer.cfg"
)
self.detect_threshold_z = self.scanner_touch_config["threshold"]

Check warning on line 249 in scanner.py

View workflow job for this annotation

GitHub Actions / Standard Checks

Type of "detect_threshold_z" is unknown (reportUnknownMemberType)

Check warning on line 249 in scanner.py

View workflow job for this annotation

GitHub Actions / Standard Checks

Type of "scanner_touch_config" is partially unknown   Type of "scanner_touch_config" is "dict[str, Unknown]" (reportUnknownMemberType)

Check warning on line 249 in scanner.py

View workflow job for this annotation

GitHub Actions / K1 Checks

Type of "detect_threshold_z" is unknown (reportUnknownMemberType)

Check warning on line 249 in scanner.py

View workflow job for this annotation

GitHub Actions / K1 Checks

Type of "scanner_touch_config" is partially unknown   Type of "scanner_touch_config" is "dict[str, Unknown]" (reportUnknownMemberType)
self.previous_probe_success = None

self.cal_config = {
Expand Down Expand Up @@ -278,7 +283,7 @@
self._stream_buffer = []
self._stream_buffer_limit = STREAM_BUFFER_LIMIT_DEFAULT
self._stream_buffer_limit_new = self._stream_buffer_limit
self._stream_samples_queue = queue.Queue()

Check warning on line 286 in scanner.py

View workflow job for this annotation

GitHub Actions / Standard Checks

Type of "_stream_samples_queue" is partially unknown   Type of "_stream_samples_queue" is "Queue[Unknown]" (reportUnknownMemberType)

Check warning on line 286 in scanner.py

View workflow job for this annotation

GitHub Actions / K1 Checks

Type of "_stream_samples_queue" is partially unknown   Type of "_stream_samples_queue" is "Queue[Unknown]" (reportUnknownMemberType)
self._stream_flush_event = threading.Event()
self._log_stream = None
self._data_filter = AlphaBetaFilter(
Expand Down Expand Up @@ -314,7 +319,7 @@
)
self._mcu.register_config_callback(self._build_config)
self._mcu.register_response(
self._handle_scanner_data, self.sensor.lower() + "_data"

Check warning on line 322 in scanner.py

View workflow job for this annotation

GitHub Actions / Standard Checks

Type of "_handle_scanner_data" is partially unknown   Type of "_handle_scanner_data" is "(params: Unknown) -> None" (reportUnknownMemberType)

Check warning on line 322 in scanner.py

View workflow job for this annotation

GitHub Actions / Standard Checks

Argument type is partially unknown   Argument corresponds to parameter "callback" in function "register_response"   Argument type is "(params: Unknown) -> None" (reportUnknownArgumentType)

Check warning on line 322 in scanner.py

View workflow job for this annotation

GitHub Actions / K1 Checks

Type of "_handle_scanner_data" is partially unknown   Type of "_handle_scanner_data" is "(params: Unknown) -> None" (reportUnknownMemberType)

Check warning on line 322 in scanner.py

View workflow job for this annotation

GitHub Actions / K1 Checks

Argument type is partially unknown   Argument corresponds to parameter "callback" in function "register_response"   Argument type is "(params: Unknown) -> None" (reportUnknownArgumentType)
)
# Register webhooks
webhooks = self.printer.lookup_object("webhooks")
Expand Down Expand Up @@ -400,11 +405,11 @@
self.calibration_method = "scan"
self._start_calibration(gcmd)

def _get_common_variables(self, gcmd: GCodeCommand):

Check warning on line 408 in scanner.py

View workflow job for this annotation

GitHub Actions / Standard Checks

Return type, "dict[str, Unknown]", is partially unknown (reportUnknownParameterType)

Check warning on line 408 in scanner.py

View workflow job for this annotation

GitHub Actions / K1 Checks

Return type, "dict[str, Unknown]", is partially unknown (reportUnknownParameterType)
return {

Check warning on line 409 in scanner.py

View workflow job for this annotation

GitHub Actions / Standard Checks

Return type, "dict[str, Unknown]", is partially unknown (reportUnknownVariableType)

Check warning on line 409 in scanner.py

View workflow job for this annotation

GitHub Actions / K1 Checks

Return type, "dict[str, Unknown]", is partially unknown (reportUnknownVariableType)
"speed": gcmd.get_float(
"SPEED",
self.scanner_touch_config["speed"],

Check warning on line 412 in scanner.py

View workflow job for this annotation

GitHub Actions / Standard Checks

Type of "scanner_touch_config" is partially unknown   Type of "scanner_touch_config" is "dict[str, Unknown]" (reportUnknownMemberType)

Check warning on line 412 in scanner.py

View workflow job for this annotation

GitHub Actions / K1 Checks

Type of "scanner_touch_config" is partially unknown   Type of "scanner_touch_config" is "dict[str, Unknown]" (reportUnknownMemberType)
above=0,
maxval=self.scanner_touch_config["max_speed"],
),
Expand Down Expand Up @@ -1714,23 +1719,74 @@
# Streaming mode

def _check_hardware(self, sample):
# Validate sample input
if "data" not in sample or "freq" not in sample:
raise self._mcu.error("Sample must contain 'data' and 'freq' keys.")

# Initialize variables on the first call
if not hasattr(self, "freq_window"):
self.freq_window = deque(maxlen=SLIDING_WINDOW_SIZE) # Sliding window
self.min_threshold = None # Minimum frequency threshold

# Add the current frequency to the sliding window
freq = sample["freq"]
self.freq_window.append(freq)

# Calculate statistics from the sliding window
if len(self.freq_window) > 1:
freq_window_array = np.array(self.freq_window) # Convert deque to numpy array
f_avg = np.mean(freq_window_array)
f_std = np.std(freq_window_array)
dynamic_threshold = f_avg + SIGMA_MULTIPLIER * f_std # Dynamic threshold
else:
# Fallback during initialization
f_avg = freq
f_std = 0
dynamic_threshold = freq * THRESHOLD_MULTIPLIER # Fallback threshold


# Ensure a minimum threshold is set
if self.min_threshold is None:
self.min_threshold = freq * THRESHOLD_MULTIPLIER # Initial minimum threshold

# Final threshold (whichever is greater: dynamic or minimum)
final_threshold = max(dynamic_threshold, self.min_threshold)

# Debug log for threshold values
logging.debug(
f"Sliding Window Threshold Debug: freq={freq}, f_avg={f_avg}, "
f"f_std={f_std}, dynamic_threshold={dynamic_threshold}, "
f"min_threshold={self.min_threshold}, final_threshold={final_threshold}"
)

# Check for hardware issues
if not self.hardware_failure:
msg = None
if sample["data"] == 0xFFFFFFF:
msg = "coil is shorted or not connected"
elif self.fmin is not None and sample["freq"] > 1.35 * self.fmin:
msg = "coil expected max frequency exceeded"

if sample["data"] == SHORTED_COIL_VALUE:
msg = "Coil is shorted or not connected."
logging.debug(f"Debug: data={sample['data']} indicates connection issue.")
elif freq > final_threshold:
msg = "Coil expected max frequency exceeded (sliding window)."
logging.debug(
f"Frequency {freq} exceeded final threshold {final_threshold}."
)

if msg:
msg = "Scanner hardware issue: " + msg
self.hardware_failure = msg
logging.error(msg)
# Log and handle hardware failure
full_msg = f"Scanner hardware issue: {msg}"
self.hardware_failure = full_msg
logging.error(full_msg)
sbtoonz marked this conversation as resolved.
Show resolved Hide resolved

if self._stream_en:
self.printer.invoke_shutdown(msg)
self.printer.invoke_shutdown(full_msg)
else:
self.gcode.respond_raw("!! " + msg + "\n")
self.gcode.respond_raw(f"!! {full_msg}\n")
elif self._stream_en:
# Handle already detected hardware failure
self.printer.invoke_shutdown(self.hardware_failure)


def _enrich_sample_time(self, sample):
clock = sample["clock"] = self._mcu.clock32_to_clock64(sample["clock"])
sample["time"] = self._mcu.clock_to_print_time(clock)
Expand Down Expand Up @@ -3252,7 +3308,7 @@
pa = (begin_a, pos_p) if even else (end_a, pos_p)
pb = (end_a, pos_p) if even else (begin_a, pos_p)

l = (pa, pb)

Check failure on line 3311 in scanner.py

View workflow job for this annotation

GitHub Actions / Standard Checks

Ruff (E741)

scanner.py:3311:13: E741 Ambiguous variable name: `l`

Check failure on line 3311 in scanner.py

View workflow job for this annotation

GitHub Actions / K1 Checks

Ruff (E741)

scanner.py:3311:13: E741 Ambiguous variable name: `l`

if len(points) > 0 and corner_radius > 0:
# We need to insert an overscan corner. Basically we insert
Expand Down Expand Up @@ -3846,4 +3902,4 @@
scanner.register_model(name, model)
return model
else:
raise config.error("Unknown scanner config directive '%s'" % (name[7:],))
raise config.error("Unknown scanner config directive '%s'" % (name[7:],))
5 changes: 5 additions & 0 deletions typings/mcu.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ class MCUStatus(TypedDict):
class _CommandQueue:
pass

class error(Exception):
pass

class MCU:
_mcu_freq: float
_clocksync: ClockSync
Expand Down Expand Up @@ -59,6 +62,8 @@ class MCU:
pass
def is_fileoutput(self) -> bool:
pass
def error(self, message: str) -> string:
pass

@overload
def get_constant(self, name: str, default: type[sentinel] | str = sentinel) -> str:
Expand Down
Loading