Skip to content

Commit bcf469d

Browse files
authored
Merge pull request mrbeam#1536 from mrbeam/SW-1338__back_propagation
SW-1338 back propagation
2 parents 1a0c0e0 + 2f12fe8 commit bcf469d

34 files changed

+750
-387
lines changed

README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,16 @@ Access from the browser:
3535
## Running unit tests
3636
Create a Python 2 virtual environment and activate it:
3737
```shell
38-
virtualenv -p /usr/bin/python2.7 tests_req
38+
virtualenv -p /usr/bin/python2.7 tests_venv
39+
source tests_venv/bin/activate
40+
```
41+
42+
Install test requirements:
43+
```shell
44+
pip install -r test-requirements.txt
3945
```
4046

4147
Run the unit tests directly from **Pycharm** or from the command line:
4248
```shell
43-
python -m pytest paht/to/MrBeamPlugin/tests/
49+
python -m pytest tests/
4450
```

octoprint_mrbeam/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import datetime
1414
import collections
1515
import logging
16+
import unicodedata
1617
from subprocess import check_output
1718

1819
import octoprint.plugin
@@ -38,6 +39,9 @@
3839
from ._version import get_versions
3940

4041
__version__ = get_versions()["version"]
42+
if isinstance(__version__, unicode):
43+
__version__ = unicodedata.normalize('NFKD', __version__).encode('ascii', 'ignore')
44+
4145
del get_versions
4246

4347
from octoprint_mrbeam.iobeam.iobeam_handler import ioBeamHandler, IoBeamEvents

octoprint_mrbeam/analytics/analytics_handler.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def analyticsHandler(plugin):
3636

3737
class AnalyticsHandler(object):
3838
QUEUE_MAXSIZE = 1000
39-
ANALYTICS_LOG_VERSION = 22 # bumped for SW-653 - added frontend event update_info_call_failure to see when this backend call fails
39+
ANALYTICS_LOG_VERSION = 23 # bumped for SW-1317 - added pressure to test fan rpm event
4040

4141
def __init__(self, plugin):
4242
self._plugin = plugin

octoprint_mrbeam/analytics/usage_handler.py

Lines changed: 77 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import os
22
import time
3+
import unicodedata
4+
35
import yaml
46

7+
from octoprint_mrbeam.iobeam.iobeam_handler import IoBeamValueEvents
58
from octoprint_mrbeam.mrb_logger import mrb_logger
69
from octoprint.events import Events as OctoPrintEvents
710
from octoprint_mrbeam.mrbeam_events import MrBeamEvents
@@ -18,7 +21,6 @@ def usageHandler(plugin):
1821

1922

2023
class UsageHandler(object):
21-
MAX_DUST_FACTOR = 2.0
2224
MIN_DUST_FACTOR = 0.5
2325
MAX_DUST_VALUE = 0.5
2426
MIN_DUST_VALUE = 0.2
@@ -40,12 +42,6 @@ def __init__(self, plugin):
4042
self.start_ntp_synced = None
4143

4244
self._last_dust_value = None
43-
self._dust_mapping_m = (self.MAX_DUST_FACTOR - self.MIN_DUST_FACTOR) / (
44-
self.MAX_DUST_VALUE - self.MIN_DUST_VALUE
45-
)
46-
self._dust_mapping_b = (
47-
self.MIN_DUST_FACTOR - self._dust_mapping_m * self.MIN_DUST_VALUE
48-
)
4945

5046
analyticsfolder = os.path.join(
5147
self._settings.getBaseFolder("base"),
@@ -78,12 +74,26 @@ def _on_mrbeam_plugin_initialized(self, event, payload):
7874
self._laser_head_serial = self._lh["serial"]
7975
else:
8076
self._laser_head_serial = "no_serial"
81-
77+
self._calculate_dust_mapping()
8278
self._init_missing_usage_data()
8379
self.log_usage()
8480

8581
self._subscribe()
8682

83+
def _calculate_dust_mapping(self):
84+
max_dust_factor = self._laserhead_handler.current_laserhead_max_dust_factor
85+
self._dust_mapping_m = (max_dust_factor - self.MIN_DUST_FACTOR) / (
86+
self.MAX_DUST_VALUE - self.MIN_DUST_VALUE
87+
)
88+
self._dust_mapping_b = (
89+
self.MIN_DUST_FACTOR - self._dust_mapping_m * self.MIN_DUST_VALUE
90+
)
91+
self._logger.debug(
92+
"new dust mapping -> {} - {} - {}".format(
93+
max_dust_factor, self._dust_mapping_m, self._dust_mapping_b
94+
)
95+
)
96+
8797
def log_usage(self):
8898
self._logger.info(
8999
"Usage: total_usage: {}, pre-filter: {}, main filter: {}, current laser head: {}, mechanics: {}, compressor: {} - {}".format(
@@ -117,6 +127,9 @@ def _subscribe(self):
117127
self._event_bus.subscribe(
118128
MrBeamEvents.LASER_HEAD_READ, self.event_laser_head_read
119129
)
130+
self._plugin.iobeam.subscribe(
131+
IoBeamValueEvents.LASERHEAD_CHANGED, self._event_laserhead_changed
132+
)
120133

121134
def event_laser_head_read(self, event, payload):
122135
# Update laser head info if necessary --> Only update if there is a serial number different than the previous
@@ -164,6 +177,17 @@ def event_stop(self, event, payload):
164177

165178
self.write_usage_analytics(action="job_finished")
166179

180+
def _event_laserhead_changed(self, event):
181+
"""
182+
will be triggered if the laser head changed,
183+
refreshes the laserhead max dust factor that will be used for the new laser head
184+
185+
Returns:
186+
187+
"""
188+
self._logger.debug("Laserhead changed recalculate dust mapping")
189+
self._calculate_dust_mapping()
190+
167191
def _set_time(self, job_duration):
168192
if job_duration is not None and job_duration > 0.0:
169193

@@ -356,36 +380,66 @@ def _load_usage_data(self):
356380
"Trying to recover from _backup_file file: %s", self._backup_file
357381
)
358382
recovery_try = True
359-
if os.path.isfile(self._backup_file):
383+
try:
384+
with open(self._backup_file, "r") as stream:
385+
data = yaml.safe_load(stream)
386+
if self._validate_data(data):
387+
data["restored"] = (
388+
data["restored"] + 1 if "restored" in data else 1
389+
)
390+
self._usage_data = data
391+
self._write_usage_data()
392+
success = True
393+
self._logger.info("Recovered from _backup_file file. Yayy!")
394+
except yaml.constructor.ConstructorError:
360395
try:
361-
data = None
362-
with open(self._backup_file, "r") as stream:
363-
data = yaml.safe_load(stream)
364-
if self._validate_data(data):
365-
data["restored"] = (
366-
data["restored"] + 1 if "restored" in data else 1
367-
)
368-
self._usage_data = data
369-
success = True
370-
self._write_usage_data()
371-
self._logger.info("Recovered from _backup_file file. Yayy!")
372-
except:
373-
self._logger.error("Can't read _backup_file file.")
396+
success = self._repair_backup_usage_data()
397+
except Exception:
398+
self._logger.error("Repair of the _backup_file failed.")
399+
except OSError:
400+
self._logger.error("There is no _backup_file file.")
401+
except yaml.YAMLError:
402+
self._logger.error("There was a YAMLError with the _backup_file file.")
403+
except:
404+
self._logger.error("Can't read _backup_file file.")
374405

375406
if not success:
376407
self._logger.warn("Resetting usage data. (marking as incomplete)")
377408
self._usage_data = self._get_usage_data_template()
378409
if recovery_try:
379410
self._write_usage_data()
380411

412+
def _repair_backup_usage_data(self):
413+
"""
414+
repairs a broken usage backup file, where the version is saved in unicode
415+
416+
Returns:
417+
boolean: successfull
418+
"""
419+
success = False
420+
with open(self._backup_file, "r") as stream:
421+
data = yaml.load(stream)
422+
if self._validate_data(data):
423+
#checks if the version is saved in unicode and converts it into a string see SW-1269
424+
if isinstance(data["version"], unicode):
425+
data["version"] = unicodedata.normalize('NFKD', data["version"]).encode('ascii', 'ignore')
426+
data["restored"] = (
427+
data["restored"] + 1 if "restored" in data else 1
428+
)
429+
self._usage_data = data
430+
success = True
431+
self._write_usage_data()
432+
self._logger.info("Could repair _backup_file file. Yayy!")
433+
return success
434+
381435
def _write_usage_data(self, file=None):
382436
self._usage_data["version"] = self._plugin_version
383437
self._usage_data["ts"] = time.time()
384438
self._usage_data["serial"] = self._device_serial
385439
file = self._storage_file if file is None else file
386440
try:
387441
with open(file, "w") as outfile:
388-
yaml.dump(self._usage_data, outfile, default_flow_style=False)
442+
yaml.safe_dump(self._usage_data, outfile, default_flow_style=False)
389443
except:
390444
self._logger.exception("Can't write file %s due to an exception: ", file)
391445

octoprint_mrbeam/decorator/__init__.py

Whitespace-only changes.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from functools import wraps
2+
3+
from octoprint_mrbeam.model import EmptyImport
4+
5+
6+
def prevent_execution_on_import_error(import_to_check, default_return=None, callable=None, params=None):
7+
def decorator(function):
8+
@wraps(function)
9+
def wrapper(*args, **kwargs):
10+
if is_empty_import(import_to_check):
11+
if callable is None:
12+
return default_return
13+
elif params is None:
14+
return callable()
15+
else:
16+
return callable(*params)
17+
18+
result = function(*args, **kwargs)
19+
return result
20+
21+
return wrapper
22+
23+
return decorator
24+
25+
26+
def is_empty_import(import_to_check):
27+
return isinstance(import_to_check, EmptyImport)

octoprint_mrbeam/dependencies.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
iobeam==1.0.0a0
2-
mrb-hw-info==1.0.0a0
3-
mrbeam-ledstrips==1.0.0a0
4-
mrbeamdoc==1.0.0a0
1+
iobeam==1.0.0
2+
mrb-hw-info==1.0.0
3+
mrbeam-ledstrips==1.0.0
4+
mrbeamdoc==1.0.0

octoprint_mrbeam/iobeam/dust_manager.py

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ def __init__(self, plugin):
7272
self._just_initialized = False
7373

7474
self._last_rpm_values = deque(maxlen=5)
75+
self._last_pressure_values = deque(maxlen=5)
7576
self._job_dust_values = []
7677

7778
self.extraction_limit = 0.3
@@ -120,6 +121,9 @@ def shutdown(self):
120121

121122
def _subscribe(self):
122123
self._iobeam.subscribe(IoBeamValueEvents.DYNAMIC_VALUE, self._handle_fan_data)
124+
self._iobeam.subscribe(
125+
IoBeamValueEvents.EXHAUST_DYNAMIC_VALUE, self._handle_exhaust_data
126+
)
123127
self._iobeam.subscribe(
124128
IoBeamValueEvents.FAN_ON_RESPONSE, self._on_command_response
125129
)
@@ -142,6 +146,25 @@ def _subscribe(self):
142146
self._event_bus.subscribe(OctoPrintEvents.PRINT_RESUMED, self._onEvent)
143147
self._event_bus.subscribe(OctoPrintEvents.SHUTDOWN, self._onEvent)
144148

149+
def _handle_exhaust_data(self, args):
150+
"""
151+
hanldes exhaust data comming from iobeam EXHAUST_DYNAMIC_VALUE event
152+
153+
Args:
154+
args: data from the iobeam event
155+
156+
Returns:
157+
158+
"""
159+
pressure = args.get("pressure", None)
160+
if pressure is not None:
161+
self._logger.debug(
162+
"last pressure values append {} - {}".format(
163+
pressure, self._last_pressure_values
164+
)
165+
)
166+
self._last_pressure_values.append(pressure)
167+
145168
def _handle_fan_data(self, args):
146169
err = False
147170
if args["state"] is not None:
@@ -247,19 +270,13 @@ def _finish_test_fan_rpm(self):
247270
try:
248271
# Write to analytics if the values are valid
249272
if self._validate_values():
250-
if len(self._last_rpm_values):
251-
rpm_average = sum(self._last_rpm_values) / len(
252-
self._last_rpm_values
253-
)
254-
else:
255-
rpm_average = -1
256-
257273
data = dict(
258-
rpm_val=rpm_average,
274+
rpm_val=list(self._last_rpm_values),
259275
fan_state=self._state,
260276
usage_count=self._usage_handler.get_total_usage(),
261277
prefilter_count=self._usage_handler.get_prefilter_usage(),
262278
carbon_filter_count=self._usage_handler.get_carbon_filter_usage(),
279+
pressure_val=list(self._last_pressure_values),
263280
)
264281
self._analytics_handler.add_fan_rpm_test(data)
265282

@@ -463,12 +480,15 @@ def _validate_values(self):
463480

464481
if not result and not self._plugin.is_boot_grace_period():
465482
msg = "Fan error: {errs}".format(errs=", ".join(errs))
466-
log_message = msg + " - Data from iobeam: state:{state}, rpm:{rpm}, dust:{dust}, connected:{connected}, age:{age:.2f}s".format(
467-
state=self._state,
468-
rpm=self._rpm,
469-
dust=self._dust,
470-
connected=self._connected,
471-
age=(time.time() - self._data_ts),
483+
log_message = (
484+
msg
485+
+ " - Data from iobeam: state:{state}, rpm:{rpm}, dust:{dust}, connected:{connected}, age:{age:.2f}s".format(
486+
state=self._state,
487+
rpm=self._rpm,
488+
dust=self._dust,
489+
connected=self._connected,
490+
age=(time.time() - self._data_ts),
491+
)
472492
)
473493
self._pause_laser(
474494
trigger=msg, analytics="invalid-old-fan-data", log_message=msg

octoprint_mrbeam/iobeam/iobeam_handler.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ class IoBeamValueEvents(object):
5555
RPM_VALUE = "iobeam.rpm.value"
5656
STATE_VALUE = "iobeam.state.value"
5757
DYNAMIC_VALUE = "iobeam.dynamic.value"
58+
EXHAUST_DYNAMIC_VALUE = "iobeam.exhaust.dynamic"
5859
CONNECTED_VALUE = "iobeam.connected.value"
5960
FAN_ON_RESPONSE = "iobeam.fan.on.response"
6061
FAN_OFF_RESPONSE = "iobeam.fan.off.response"
@@ -1068,7 +1069,13 @@ def _handle_exhaust(self, dataset):
10681069
:param dataset:
10691070
:return: error count
10701071
"""
1071-
# self._logger.info("exhaust dataset: '%s'", dataset)
1072+
self._logger.debug("exhaust dataset: '%s'", dataset)
1073+
# get the pressure sensor reading this will come as dust with the current iobeam version
1074+
if "dust" in dataset:
1075+
vals = {
1076+
"pressure": dataset["dust"],
1077+
}
1078+
self._call_callback(IoBeamValueEvents.EXHAUST_DYNAMIC_VALUE, dataset, vals)
10721079
return 0
10731080

10741081
def _handle_link_quality(self, dataset):

0 commit comments

Comments
 (0)