diff --git a/cypress/e2e/working area/design_functions.cy.js b/cypress/e2e/01_working_area/design_functions.cy.js similarity index 100% rename from cypress/e2e/working area/design_functions.cy.js rename to cypress/e2e/01_working_area/design_functions.cy.js diff --git a/cypress/e2e/working area/designs/dxf_laser_job.cy.js b/cypress/e2e/01_working_area/designs/dxf_laser_job.cy.js similarity index 100% rename from cypress/e2e/working area/designs/dxf_laser_job.cy.js rename to cypress/e2e/01_working_area/designs/dxf_laser_job.cy.js diff --git a/cypress/e2e/working area/designs/jpg_laser_job.cy.js b/cypress/e2e/01_working_area/designs/jpg_laser_job.cy.js similarity index 100% rename from cypress/e2e/working area/designs/jpg_laser_job.cy.js rename to cypress/e2e/01_working_area/designs/jpg_laser_job.cy.js diff --git a/cypress/e2e/working area/designs/png_laser_job.cy.js b/cypress/e2e/01_working_area/designs/png_laser_job.cy.js similarity index 100% rename from cypress/e2e/working area/designs/png_laser_job.cy.js rename to cypress/e2e/01_working_area/designs/png_laser_job.cy.js diff --git a/cypress/e2e/working area/designs/quick_text_laser_job.cy.js b/cypress/e2e/01_working_area/designs/quick_text_laser_job.cy.js similarity index 100% rename from cypress/e2e/working area/designs/quick_text_laser_job.cy.js rename to cypress/e2e/01_working_area/designs/quick_text_laser_job.cy.js diff --git a/cypress/e2e/working area/designs/shapes/circle_shape.cy.js b/cypress/e2e/01_working_area/designs/shapes/circle_shape.cy.js similarity index 100% rename from cypress/e2e/working area/designs/shapes/circle_shape.cy.js rename to cypress/e2e/01_working_area/designs/shapes/circle_shape.cy.js diff --git a/cypress/e2e/working area/designs/shapes/heart_shape.cy.js b/cypress/e2e/01_working_area/designs/shapes/heart_shape.cy.js similarity index 100% rename from cypress/e2e/working area/designs/shapes/heart_shape.cy.js rename to cypress/e2e/01_working_area/designs/shapes/heart_shape.cy.js diff --git a/cypress/e2e/working area/designs/shapes/line_shape.cy.js b/cypress/e2e/01_working_area/designs/shapes/line_shape.cy.js similarity index 100% rename from cypress/e2e/working area/designs/shapes/line_shape.cy.js rename to cypress/e2e/01_working_area/designs/shapes/line_shape.cy.js diff --git a/cypress/e2e/working area/designs/shapes/rectangle_shape.cy.js b/cypress/e2e/01_working_area/designs/shapes/rectangle_shape.cy.js similarity index 100% rename from cypress/e2e/working area/designs/shapes/rectangle_shape.cy.js rename to cypress/e2e/01_working_area/designs/shapes/rectangle_shape.cy.js diff --git a/cypress/e2e/working area/designs/shapes/star_shape.cy.js b/cypress/e2e/01_working_area/designs/shapes/star_shape.cy.js similarity index 100% rename from cypress/e2e/working area/designs/shapes/star_shape.cy.js rename to cypress/e2e/01_working_area/designs/shapes/star_shape.cy.js diff --git a/cypress/e2e/working area/designs/svg_laser_job.cy.js b/cypress/e2e/01_working_area/designs/svg_laser_job.cy.js similarity index 100% rename from cypress/e2e/working area/designs/svg_laser_job.cy.js rename to cypress/e2e/01_working_area/designs/svg_laser_job.cy.js diff --git a/cypress/e2e/working area/designs/text/filled_text.cy.js b/cypress/e2e/01_working_area/designs/text/filled_text.cy.js similarity index 100% rename from cypress/e2e/working area/designs/text/filled_text.cy.js rename to cypress/e2e/01_working_area/designs/text/filled_text.cy.js diff --git a/cypress/e2e/working area/designs/text/stroke_text.cy.js b/cypress/e2e/01_working_area/designs/text/stroke_text.cy.js similarity index 100% rename from cypress/e2e/working area/designs/text/stroke_text.cy.js rename to cypress/e2e/01_working_area/designs/text/stroke_text.cy.js diff --git a/cypress/e2e/working area/left_side.cy.js b/cypress/e2e/01_working_area/left_side.cy.js similarity index 100% rename from cypress/e2e/working area/left_side.cy.js rename to cypress/e2e/01_working_area/left_side.cy.js diff --git a/cypress/e2e/working area/right_side.cy.js b/cypress/e2e/01_working_area/right_side.cy.js similarity index 100% rename from cypress/e2e/working area/right_side.cy.js rename to cypress/e2e/01_working_area/right_side.cy.js diff --git a/cypress/e2e/working area/settings/cut_engrave.cy.js b/cypress/e2e/01_working_area/settings/cut_engrave.cy.js similarity index 100% rename from cypress/e2e/working area/settings/cut_engrave.cy.js rename to cypress/e2e/01_working_area/settings/cut_engrave.cy.js diff --git a/cypress/e2e/working area/settings/cut_skip_another.cy.js b/cypress/e2e/01_working_area/settings/cut_skip_another.cy.js similarity index 100% rename from cypress/e2e/working area/settings/cut_skip_another.cy.js rename to cypress/e2e/01_working_area/settings/cut_skip_another.cy.js diff --git a/cypress/e2e/working area/settings/material_selection.cy.js b/cypress/e2e/01_working_area/settings/material_selection.cy.js similarity index 100% rename from cypress/e2e/working area/settings/material_selection.cy.js rename to cypress/e2e/01_working_area/settings/material_selection.cy.js diff --git a/cypress/e2e/settings/maintenance.cy.js b/cypress/e2e/settings/maintenance.cy.js index 62e55f1f5..2a504e620 100644 --- a/cypress/e2e/settings/maintenance.cy.js +++ b/cypress/e2e/settings/maintenance.cy.js @@ -37,6 +37,13 @@ describe("Maintenance", function () { expect(resp.status).to.eq(200); }); }); + cy.get('[data-test="maintenance-links-buy-now-heavy-duty-pre-filter"]') + .invoke("attr", "href") + .then((myLink) => { + cy.request(myLink).then((resp) => { + expect(resp.status).to.eq(200); + }); + }); }); // status code no exist it("Air Filter: Main-filter", function () { diff --git a/octoprint_mrbeam/analytics/usage_handler.py b/octoprint_mrbeam/analytics/usage_handler.py index a1c45607f..eb614bc97 100644 --- a/octoprint_mrbeam/analytics/usage_handler.py +++ b/octoprint_mrbeam/analytics/usage_handler.py @@ -979,11 +979,9 @@ def _calculate_af3_filter_usage(self, filter_stage): logger = self._logger # get the global values - pressure_loss = usage_data.get( - self.PRESSURE_KEY, AirFilter.MAX_PRESSURE_DIFFERENCE - ) + pressure_loss = usage_data.get(self.PRESSURE_KEY, 0) rpm_filter_test = self._get_airfilter_carbon_filter_usage_data().get( - self.FAN_TEST_RPM_KEY, AirFilter.MAX_FAN_TEST_RPM + self.FAN_TEST_RPM_KEY, 0 ) # this is saved in carbon filter stage # calculate the percentages diff --git a/octoprint_mrbeam/iobeam/airfilter.py b/octoprint_mrbeam/iobeam/airfilter.py index bada656a7..3b1a6c601 100644 --- a/octoprint_mrbeam/iobeam/airfilter.py +++ b/octoprint_mrbeam/iobeam/airfilter.py @@ -7,6 +7,7 @@ from flask_babel import gettext from octoprint_mrbeam.iobeam.iobeam_handler import IoBeamEvents +from octoprint_mrbeam.model.iobeam import exhaust from octoprint_mrbeam.mrbeam_events import MrBeamEvents from octoprint_mrbeam.mrb_logger import mrb_logger @@ -50,8 +51,7 @@ class AirFilter(object): FILTERSTAGES = [PREFILTER, CARBONFILTER] PRESSURE_VALUES_LIST_SIZE = 5 - MAX_PRESSURE_DIFFERENCE = 1880 - MAX_FAN_TEST_RPM = 10750 + AF3_MAX_PREFILTER_PRESSURE_CHANGE = ( 100 # The maximum pressure change in Pa for the prefilter of the AF3 ) @@ -69,7 +69,7 @@ class AirFilter(object): (950, 40), (1150, 60), (1500, 80), - (MAX_PRESSURE_DIFFERENCE, 100), + (1880, 100), ] AF3_PRESSURE_GRAPH_PREFILTER = [ (100, 0), @@ -83,7 +83,7 @@ class AirFilter(object): (9860, 0), (9900, 20), (10200, 70), - (MAX_FAN_TEST_RPM, 100), + (10750, 100), ] class ProfileParameters(Enum): @@ -128,8 +128,33 @@ def __init__(self, plugin): self._connected = None self._last_pressure_values = deque(maxlen=self.PRESSURE_VALUES_LIST_SIZE) self._profile = None + self._external_power = None + self._iobeam = None self._load_current_profile() + self._event_bus.subscribe( + MrBeamEvents.MRB_PLUGIN_INITIALIZED, self._on_mrbeam_plugin_initialized + ) + + def reset_data(self): + """Resets all data of the air filter.""" + self._serial = None + self._model_id = None + self._pressure1 = None + self._pressure2 = None + self._pressure3 = None + self._pressure4 = None + self._temperature1 = None + self._temperature2 = None + self._temperature3 = None + self._temperature4 = None + self._profile = None + self._connected = None + self._last_pressure_values = deque(maxlen=self.PRESSURE_VALUES_LIST_SIZE) + self._external_power = None + + def _on_mrbeam_plugin_initialized(self, event, payload): + self._iobeam = self._plugin.iobeam @property def model(self): @@ -223,7 +248,10 @@ def set_airfilter(self, model_id, serial): def _airfilter_changed(self): self._plugin.send_mrb_state() - self._event_bus.fire(MrBeamEvents.AIRFILTER_CHANGED) + self._event_bus.fire( + MrBeamEvents.AIRFILTER_CHANGED, + {"model_id": self._model_id, "serial": self.serial}, + ) def set_pressure( self, @@ -260,6 +288,29 @@ def set_pressure( elif self._pressure1 is not None and self.model_id in self.AIRFILTER2_MODELS: self._last_pressure_values.append(self._pressure1) + def set_device(self, device): + """ + Sets the device data of the air filter. + + Args: + device (exhaust.Device): + + Returns: + None + """ + if ( + self._external_power != device.ext_power + and device.ext_power + and device.serial_num == 0 + ): + self._logger.info( + "Exhaust fan is now connected to external power -> reset exhaust" + ) + self._iobeam.reset_exhaust() + return None + else: + self._external_power = device.ext_power + def _get_avg_pressure_differences(self): """Returns the average pressure differences of the last pressure readings. @@ -296,7 +347,7 @@ def pressure_drop_mainfilter(self): Returns: int: Pressure drop of the main filter """ - if self.model_id in self.AIRFILTER3_MODELS: + if self.is_airfilter3(): ( _, mainfilter_pressure_avg, @@ -306,8 +357,17 @@ def pressure_drop_mainfilter(self): return mainfilter_pressure_avg - fan_pressure_avg return None + def is_airfilter3(self): + """ + Return True if the current air filter is an air filter 3 + + Returns: + bool: True if the current air filter is an air filter 3 + """ + return self.model_id in self.AIRFILTER3_MODELS + def exhaust_hose_is_blocked(self): - if self.model_id in self.AIRFILTER3_MODELS: + if self.is_airfilter3(): return self._pressure2 < self.AF3_PRESSURE2_MIN return None @@ -318,7 +378,7 @@ def pressure_drop_prefilter(self): Returns: int: Pressure drop of the prefilter """ - if self.model_id in self.AIRFILTER3_MODELS: + if self.is_airfilter3(): ( prefilter_pressure_avg, mainfilter_pressure_avg, @@ -330,6 +390,8 @@ def pressure_drop_prefilter(self): @property def connected(self): + if self.is_airfilter3() and not self._external_power: + return False return self._connected @connected.setter @@ -346,7 +408,10 @@ def connected(self, connected): if self._connected != connected: self._connected = connected if connected: - self._event_bus.fire(IoBeamEvents.FAN_CONNECTED) + self._event_bus.fire( + IoBeamEvents.FAN_CONNECTED, + {"serial": self.serial, "model_id": self.model_id}, + ) # If the fan gets marked as connected but we don't have a serial number or model id # Then the af used is a non smart => AF1 or single if self.serial is None and self.model_id is None: @@ -361,7 +426,10 @@ def connected(self, connected): ) self._connected = connected # need to set it here again as the set_airfilter resets it else: - self._event_bus.fire(IoBeamEvents.FAN_DISCONNECTED) + self._event_bus.fire( + IoBeamEvents.FAN_DISCONNECTED, + {"serial": self.serial, "model_id": self.model_id}, + ) def set_temperatures( self, @@ -387,22 +455,6 @@ def set_temperatures( if temperature4 is not None: self._temperature4 = temperature4 - def reset_data(self): - """Resets all data of the air filter.""" - self._serial = None - self._model_id = None - self._pressure1 = None - self._pressure2 = None - self._pressure3 = None - self._pressure4 = None - self._temperature1 = None - self._temperature2 = None - self._temperature3 = None - self._temperature4 = None - self._profile = None - self._connected = None - self._last_pressure_values = deque(maxlen=self.PRESSURE_VALUES_LIST_SIZE) - def _load_current_profile(self): """Loads the current profile of the air filter and safes it in self._profile. diff --git a/octoprint_mrbeam/iobeam/iobeam_handler.py b/octoprint_mrbeam/iobeam/iobeam_handler.py index 50ad61668..2e730d8c4 100644 --- a/octoprint_mrbeam/iobeam/iobeam_handler.py +++ b/octoprint_mrbeam/iobeam/iobeam_handler.py @@ -15,6 +15,7 @@ HwMalfunction, HwMalfunctionHandler, ) +from octoprint_mrbeam.model.iobeam import exhaust from octoprint_mrbeam.mrb_logger import mrb_logger from octoprint_mrbeam.lib.rwlock import RWLock from flask.ext.babel import gettext @@ -140,6 +141,7 @@ class IoBeamHandler(object): MESSAGE_ACTION_FAN_TYPE = "type" MESSAGE_ACTION_FAN_EXHAUST = "exhaust" MESSAGE_ACTION_FAN_LINK_QUALITY = "link_quality" + MESSAGE_ACTION_EXHAUST_RESET = "reset" MESSAGE_ACTION_COMPRESSOR_ON = "on" # Possible datasets @@ -232,6 +234,12 @@ def shutdown(self, *args): def shutdown_fan(self): self.send_fan_command(self.MESSAGE_ACTION_FAN_OFF) + def reset_exhaust(self): + """ + Reset the exhaust fan. + """ + self.send_fan_command(self.MESSAGE_ACTION_EXHAUST_RESET) + def is_interlock_closed(self): return len(self._interlocks.keys()) == 0 @@ -1107,12 +1115,12 @@ def _handle_exhaust(self, dataset): :param dataset: :return: error count """ - device_dataset = dataset.get("device", {}) + device_dataset = exhaust.Device.from_dict(dataset.get("device", {})) pressure_dataset = dataset.get("pressure", {}) temperature_dataset = dataset.get("temperature", {}) if ( - device_dataset.get("serial_num") is None - and device_dataset.get("type") is None + device_dataset.serial_num is None + and device_dataset.type is None and "error" in pressure_dataset and "error" in temperature_dataset ): @@ -1122,9 +1130,10 @@ def _handle_exhaust(self, dataset): self._airfilter.set_airfilter(serial=self.UNKNOWN_SERIAL_KEY, model_id=1) else: self._airfilter.set_airfilter( - serial=device_dataset.get("serial_num"), - model_id=device_dataset.get("type"), + serial=device_dataset.serial_num, + model_id=device_dataset.type, ) + self._airfilter.set_device(device_dataset) self._airfilter.set_pressure( pressure1=pressure_dataset.get("pressure1"), pressure2=pressure_dataset.get("pressure2"), @@ -1138,10 +1147,10 @@ def _handle_exhaust(self, dataset): temperature4=temperature_dataset.get("temp4"), ) # get the pressure sensor reading this will come as dust with the current iobeam version - if "pressure" in device_dataset: - self._airfilter.set_pressure(pressure=device_dataset.get("pressure")) + if device_dataset: + self._airfilter.set_pressure(pressure=device_dataset.pressure) vals = { - "pressure": device_dataset.get("pressure"), + "pressure": device_dataset.pressure, } self._call_callback(IoBeamValueEvents.EXHAUST_DYNAMIC_VALUE, dataset, vals) return 0 diff --git a/octoprint_mrbeam/model/iobeam/__init__.py b/octoprint_mrbeam/model/iobeam/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/octoprint_mrbeam/model/iobeam/exhaust.py b/octoprint_mrbeam/model/iobeam/exhaust.py new file mode 100644 index 000000000..357ccc092 --- /dev/null +++ b/octoprint_mrbeam/model/iobeam/exhaust.py @@ -0,0 +1,67 @@ +class ExhaustModelInitializationError(Exception): + pass + + +class Device: + # FOR PYTHON3 + # dataset_type: int + # ext_power: bool + # ext_voltage: float + # fan_power: float + # mode: str + # pressure: int + # serial_num: int + # smart_lock: bool + # type: int + + def __init__( + self, + dataset_type, + ext_power, + ext_voltage, + fan_power, + mode, + pressure, + serial_num, + smart_lock, + type, + ): + self.dataset_type = dataset_type + self.ext_power = ext_power + self.ext_voltage = ext_voltage + self.fan_power = fan_power + self.mode = mode + self.pressure = pressure + self.serial_num = serial_num + self.smart_lock = smart_lock + self.type = type + + @staticmethod + # FOR PYTHON3 + # def from_dict(dictonary: dict) -> Device: + def from_dict(dictonary): + """ + Creates a Device object from a dict. + + Args: + dictonary (dict): dict with the device data + + Returns: + Device: Device object + """ + try: + return Device( + dictonary.get("dataset_type"), + dictonary.get("ext_power"), + dictonary.get("ext_voltage"), + dictonary.get("fan_power"), + dictonary.get("mode"), + dictonary.get("pressure"), + dictonary.get("serial_num"), + dictonary.get("smart_lock"), + dictonary.get("type"), + ) + except TypeError as e: + raise ExhaustModelInitializationError( + "Can't init device from dict: {} - e:{}".format(dictonary, e) + ) diff --git a/octoprint_mrbeam/static/img/air_filter/af3-mainfilter.png b/octoprint_mrbeam/static/img/air_filter/af3-mainfilter.png index 5978def28..32716f20b 100644 Binary files a/octoprint_mrbeam/static/img/air_filter/af3-mainfilter.png and b/octoprint_mrbeam/static/img/air_filter/af3-mainfilter.png differ diff --git a/octoprint_mrbeam/static/js/app/view-models/modal/ready-to-laser.js b/octoprint_mrbeam/static/js/app/view-models/modal/ready-to-laser.js index 39003ff23..886a69aca 100644 --- a/octoprint_mrbeam/static/js/app/view-models/modal/ready-to-laser.js +++ b/octoprint_mrbeam/static/js/app/view-models/modal/ready-to-laser.js @@ -312,6 +312,8 @@ $(function () { if ("fan_connected" in mrb_state) { if (mrb_state["fan_connected"] !== null) { self.is_fan_connected(mrb_state["fan_connected"]); + } else { + self.is_fan_connected(false); } } if ("rtl_mode" in mrb_state) { diff --git a/octoprint_mrbeam/static/js/app/view-models/settings/about.js b/octoprint_mrbeam/static/js/app/view-models/settings/about.js index f9674eb37..e2b19234a 100644 --- a/octoprint_mrbeam/static/js/app/view-models/settings/about.js +++ b/octoprint_mrbeam/static/js/app/view-models/settings/about.js @@ -11,7 +11,6 @@ $(function () { let self = this; window.mrbeam.viewModels["aboutSettingsViewModel"] = self; self.mrb_state = params[0]; - self.airfilter_serial = self.mrb_state.airfilter_serial; self.airfilter_model = self.mrb_state.airfilter_model; } diff --git a/octoprint_mrbeam/templates/calibration/user/view_corner_calibration.jinja2 b/octoprint_mrbeam/templates/calibration/user/view_corner_calibration.jinja2 index dc40eec17..48ff1c426 100644 --- a/octoprint_mrbeam/templates/calibration/user/view_corner_calibration.jinja2 +++ b/octoprint_mrbeam/templates/calibration/user/view_corner_calibration.jinja2 @@ -34,7 +34,7 @@ {{ _('Please perform a homing cycle first') }} -
  • {{ _('Wait until the engraving is finished and Mr Beam Status Lights are green. Open the safety lid again and be carefult NOT to touch the engraved material. The camera will now take a picture of the engraving.') }}
  • +
  • {{ _('Wait until the engraving is finished and Mr Beam Status Lights are green. Open the safety lid again and be careful NOT to touch the engraved material. The camera will now take a picture of the engraving.') }}
  • {{ _('Start the calibration by clicking on the button, and then follow these steps:') }} {% endif %} -
    +
    {{ _('Air Filter System') }}:
      -
    • {{ _('Serial number') }}:
    • {{ _('Model') }}:
    • diff --git a/octoprint_mrbeam/templates/settings/maintenance_settings.jinja2 b/octoprint_mrbeam/templates/settings/maintenance_settings.jinja2 index 809eb595f..b62903d7c 100644 --- a/octoprint_mrbeam/templates/settings/maintenance_settings.jinja2 +++ b/octoprint_mrbeam/templates/settings/maintenance_settings.jinja2 @@ -16,7 +16,7 @@