Skip to content

Commit

Permalink
SW-3833 fix 1027 error (#1798)
Browse files Browse the repository at this point in the history
* improve error handling for fan pcb
* only report hardware malfunction for fan stuff once and don't flood the log
* dimiss notifications
* don't cancel laser job on fan not spinning error only pause it
and only report the errors if they appear outside the boot grace periode
  • Loading branch information
Josef-MrBeam authored Sep 18, 2023
1 parent d05606b commit 2ee9177
Show file tree
Hide file tree
Showing 5 changed files with 237 additions and 34 deletions.
175 changes: 141 additions & 34 deletions octoprint_mrbeam/iobeam/dust_manager.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import time
import threading
from octoprint.events import Events as OctoPrintEvents
from octoprint.util import monotonic_time

from octoprint_mrbeam.iobeam.hw_malfunction_handler import HwMalfunctionHandler
from octoprint_mrbeam.mrbeam_events import MrBeamEvents
from octoprint_mrbeam.iobeam.iobeam_handler import IoBeamValueEvents, IoBeamEvents
from octoprint_mrbeam.mrb_logger import mrb_logger
from octoprint_mrbeam.util.errors import ErrorCodes
from collections import deque

# singleton
Expand Down Expand Up @@ -42,6 +46,8 @@ class DustManager(object):

FAN_TEST_RPM_PERCENTAGE = 50
FAN_TEST_DURATION = 35 # seconds
FAN_NOT_SPINNING_TIMEOUT = 10 # time in seconds before reporting the error
FAN_DATA_MISSING_TIMEOUT = 10 # time in seconds before reporting the error

def __init__(self, plugin):
self._plugin = plugin
Expand All @@ -58,7 +64,6 @@ def __init__(self, plugin):
self._connected = None
self._data_ts = 0

self._init_ts = time.time()
self._last_event = None
self._shutting_down = False
self._final_extraction_thread = None
Expand All @@ -70,6 +75,11 @@ def __init__(self, plugin):
self._fan_timers = []
self._last_command = dict(action=None, value=None)
self._just_initialized = False
self._fan_not_spinning_ts = None
self._fan_data_missing_ts = None
self._fan_data_missing_reported = None
self._fan_data_too_old_reported = None
self._fan_not_spinning_reported = None

self._last_rpm_values = deque(maxlen=5)
self._last_pressure_values = deque(maxlen=5)
Expand Down Expand Up @@ -167,29 +177,22 @@ def _handle_exhaust_data(self, args):

def _handle_fan_data(self, args):
err = False
if args["state"] is not None:
self._state = args["state"]
else:
err = True
if args["dust"] is not None:
self._dust = args["dust"]
self._state = args.get("state")
self._dust = args.get("dust")
self._rpm = args.get("rpm")
self._connected = args.get("connected")

if self._printer.is_printing():
self._job_dust_values.append(self._dust)
else:
err = True
if args["rpm"] is not None:
self._rpm = args["rpm"]
else:
if self._rpm is None or self._state is None or self._dust is None:
err = True

self._connected = args["connected"]
if self._dust is not None and self._printer.is_printing():
self._job_dust_values.append(self._dust)

if self._connected is not None:
self._unboost_timer_interval()

if not err:
self._data_ts = time.time()
self._data_ts = monotonic_time()

self._validate_values()
self._send_dust_to_analytics(self._dust)
Expand Down Expand Up @@ -382,7 +385,7 @@ def __do_final_extraction_threaded(self):
self.is_final_extraction_mode = False
self._logger.debug(
"Final extraction: DUSTING_MODE_START end. duration was: %s",
time.time() - dust_start_ts,
monotonic_time() - dust_start_ts,
)
if self._continue_final_extraction:
self._start_final_extraction_phase2(
Expand Down Expand Up @@ -468,19 +471,24 @@ def _send_dust_to_analytics(self, val):
def _validate_values(self):
result = True
errs = []
if time.time() - self._data_ts > self.DEFAUL_DUST_MAX_AGE:
result = False
errs.append("data too old. age:{:.2f}".format(time.time() - self._data_ts))

if self._state is None:
result = False
errs.append("fan state:{}".format(self._state))
if self._rpm is None or self._rpm <= 0:
# check if one of the values is None
value_is_none_result, value_is_none_errs = self._check_if_one_value_is_none()
if not value_is_none_result:
result = False
errs.append("rpm:{}".format(self._rpm))
if self._dust is None:
errs.extend(value_is_none_errs)

# check if received data is too old
if monotonic_time() - self._data_ts > self.DEFAUL_DUST_MAX_AGE:
result = False
errs.append("dust:{}".format(self._dust))
errs.append(
"data too old. age:{:.2f}".format(monotonic_time() - self._data_ts)
)
else:
self._fan_data_too_old_reported = False

# check if fan is not spinning
self._check_if_fan_is_spinning_during_a_running_job()

if self._one_button_handler.is_printing() and self._state == 0:
self._logger.warn("Restarting fan since _state was 0 in printing state.")
Expand All @@ -495,16 +503,12 @@ def _validate_values(self):
rpm=self._rpm,
dust=self._dust,
connected=self._connected,
age=(time.time() - self._data_ts),
age=(monotonic_time() - self._data_ts),
)
)
self._pause_laser(
trigger=msg, analytics="invalid-old-fan-data", log_message=msg
trigger=msg, analytics="invalid-old-fan-data", log_message=log_message
)
if self._one_button_handler.is_printing():
self._plugin.hw_malfunction_handler.report_hw_malfunction(
{"err_fan_not_spinning": {"code": "E-00FF-1027"}},
)

elif self._connected == False:
result = False
Expand All @@ -513,13 +517,116 @@ def _validate_values(self):
rpm=self._rpm,
dust=self._dust,
connected=self._connected,
age=(time.time() - self._data_ts),
age=(monotonic_time() - self._data_ts),
)
self._pause_laser(trigger="Air filter not connected.", log_message=msg)

# TODO: check for error case in connected val (currently, connected == True/False/None)
self._check_and_report_error()
return result

def _check_if_one_value_is_none(self):
"""
Checks if one of the values is None and sets the _fan_data_missing_ts flag.
Returns: (bool, list): True if one of the values is None, False otherwise. And List of error messages.
"""
errs = []
result = False
if self._state is None or self._rpm is None or self._dust is None:
result = True
if self._fan_data_missing_ts is None:
self._fan_data_missing_ts = monotonic_time()
self._fan_data_missing_reported = False

if self._state is None:
errs.append("fan state:{}".format(self._state))

if self._dust is None:
errs.append("dust:{}".format(self._dust))

if self._rpm is None:
errs.append("rpm:{}".format(self._rpm))
else:
# reset counter or timer
self._fan_data_missing_ts = None
return result, errs

def _check_if_fan_is_spinning_during_a_running_job(self):
"""
Checks if the fan is not spinning during a running job and sets the _fan_not_spinning_ts flag.
Returns:
None
"""
if self._rpm <= 0 and self._one_button_handler.is_printing():
if self._fan_not_spinning_ts is None:
self._fan_not_spinning_ts = monotonic_time()
self._fan_not_spinning_reported = False
self._logger.warn(
"fan not spinning. rpm:{} since {:.2f}".format(
self._rpm, monotonic_time() - self._fan_not_spinning_ts
)
)
else:
self._fan_not_spinning_ts = None

def _check_and_report_error(self):
if not self._plugin.is_boot_grace_period():
if (
self._fan_not_spinning_ts is not None
and monotonic_time() - self._fan_not_spinning_ts
> self.FAN_NOT_SPINNING_TIMEOUT
and not self._fan_not_spinning_reported
):
self._logger.error("Fan is not spinning. Raising error to user.")
self._plugin.hw_malfunction_handler.report_hw_malfunction(
{
HwMalfunctionHandler.FAN_NOT_SPINNING: {
"code": ErrorCodes.E_1027,
"stop_laser": False, # don't cancel the laser job
}
},
)
self._fan_not_spinning_reported = True
self._pause_laser(
trigger="fan-not-spinning",
analytics="fan-not-spinning",
log_message="Fan is not spinning.",
)

if (
self._fan_data_missing_ts is not None
and monotonic_time() - self._fan_data_missing_ts
> self.FAN_DATA_MISSING_TIMEOUT
and not self._fan_data_missing_reported
):
self._logger.warn("Fan data is missing. Raising error to user.")
self._plugin.hw_malfunction_handler.report_hw_malfunction(
{
HwMalfunctionHandler.I2C_BUS_MALFUNCTION: {
"code": ErrorCodes.E_1030,
"stop_laser": False, # don't cancel the laser job
}
},
)
self._fan_data_missing_reported = True

if (
monotonic_time() - self._data_ts > self.DEFAUL_DUST_MAX_AGE
and not self._fan_data_too_old_reported
):
self._logger.warn("Fan data is to old. Raising error to user.")
self._plugin.hw_malfunction_handler.report_hw_malfunction(
{
HwMalfunctionHandler.I2C_BUS_MALFUNCTION: {
"code": ErrorCodes.E_1014,
"stop_laser": False, # don't cancel the laser job
}
},
)
self._fan_data_too_old_reported = True

def _validation_timer_callback(self):
try:
# self._request_value(self.DATA_TYPE_DYNAMIC)
Expand Down
10 changes: 10 additions & 0 deletions octoprint_mrbeam/static/js/app/view-models/user-notification.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ $(function () {
),
type: "error",
hide: false,
before_close: (pnotify) => self._dismissNotification(pnotify),
},
err_bottom_open: {
title: gettext("Bottom plate not detected"),
Expand All @@ -79,6 +80,7 @@ $(function () {
),
type: "error",
hide: false,
before_close: (pnotify) => self._dismissNotification(pnotify),
},
err_hardware_malfunction_i2c: {
title: gettext("Hardware malfunction"),
Expand All @@ -97,6 +99,7 @@ $(function () {
),
type: "error",
hide: false,
before_close: (pnotify) => self._dismissNotification(pnotify),
},
err_unknown_malfunction: {
title: gettext("Unknown malfunction"),
Expand All @@ -110,6 +113,7 @@ $(function () {
),
type: "error",
hide: false,
before_close: (pnotify) => self._dismissNotification(pnotify),
},
err_compressor_malfunction: {
title: gettext("Compressor malfunction"),
Expand All @@ -128,6 +132,7 @@ $(function () {
),
type: "error",
hide: false,
before_close: (pnotify) => self._dismissNotification(pnotify),
},
err_one_button_malfunction: {
title: gettext("One Button malfunction"),
Expand All @@ -143,6 +148,7 @@ $(function () {
),
type: "error",
hide: false,
before_close: (pnotify) => self._dismissNotification(pnotify),
},
err_interlock_malfunction: {
title: gettext("Interlock malfunction"),
Expand All @@ -161,6 +167,7 @@ $(function () {
),
type: "error",
hide: false,
before_close: (pnotify) => self._dismissNotification(pnotify),
},
warn_cam_conn_err: {
title: gettext("Camera busy"),
Expand Down Expand Up @@ -211,6 +218,7 @@ $(function () {
),
type: "error",
hide: false,
before_close: (pnotify) => self._dismissNotification(pnotify),
},
err_job_cancelled_due_to_internal_error: {
title: gettext("Laser job canceled"),
Expand Down Expand Up @@ -242,6 +250,7 @@ $(function () {
),
type: "error",
hide: false,
before_close: (pnotify) => self._dismissNotification(pnotify),
},
err_fan_not_spinning: {
title: gettext("Hardware malfunction"),
Expand All @@ -257,6 +266,7 @@ $(function () {
),
type: "error",
hide: false,
before_close: (pnotify) => self._dismissNotification(pnotify),
},
msg_cam_image_analytics_sent: {
title: gettext("Thank you"),
Expand Down
2 changes: 2 additions & 0 deletions octoprint_mrbeam/util/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,5 @@ class ErrorCodes:
E_1026 = "E-01FF-1026"
E_1027 = "E-00FF-1027"
E_1028 = "E-01FF-1028"
E_1029 = "E-00FF-1029"
E_1030 = "E-00FF-1030"
2 changes: 2 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,7 @@ def mrbeam_plugin():
mrbeam_plugin._event_bus.fire(Events.STARTUP)
mrbeam_plugin.user_notification_system = MagicMock()
mrbeam_plugin._printer = MagicMock()
mrbeam_plugin.hw_malfunction_handler = MagicMock()
mrbeam_plugin.is_boot_grace_period = MagicMock(return_value=False)

yield mrbeam_plugin
Loading

0 comments on commit 2ee9177

Please sign in to comment.