From 7dc3d4c651fb8c9e0d519e5916a18d1e2fb0fab4 Mon Sep 17 00:00:00 2001 From: Tobias Vogler Date: Sat, 3 Jan 2026 22:34:32 +0100 Subject: [PATCH] Version 1.5.0 does crash on my device (SAP6_IA) quite frequently. This happens particularly predictably when alternating correct measurements with errors (magnetic, laser). Debugging revealed that most frequently there occured memory errors in "create_big_text_group" followed by "measure" (pops up as an "outer memory error") and quite seldomly somewhere else, e.g. when opening the menu. Apparently, there is no real memory problem, but the crappy garbage collection of embedded python cannot handle the fast switches or some such. To reduce memory impact and so indirectly the gc problem, I replaced the most frequent error messages by images (you could of cause create an image with the message text - I did more like some pictograms :). The result is much better but not perfect. Therefore, I also put some try/catch around the memory intensive processes. this seems to come without any obvious penalty and does not impact sending measurements via bluetooth. Just there is no text displayed in case of a caught memory error and in most cases the gc seems to catch up then somewhere downstream. So with these two modifications my device now works like a charm, with no critical out of memory errors whatsoever, anymore. In case the described problem occurs for others, too, I propose to include these modifications in the main branch (or at least the image-part). --- firmware/images/error_laser.bmp | Bin 0 -> 1088 bytes firmware/images/error_magnetic.bmp | Bin 0 -> 1088 bytes firmware/images/error_movement.bmp | Bin 0 -> 1088 bytes firmware/measure.py | 80 +++++++++++++++++------------ firmware/versions/display128x64.py | 52 ++++++++++++++----- 5 files changed, 87 insertions(+), 45 deletions(-) create mode 100644 firmware/images/error_laser.bmp create mode 100644 firmware/images/error_magnetic.bmp create mode 100644 firmware/images/error_movement.bmp diff --git a/firmware/images/error_laser.bmp b/firmware/images/error_laser.bmp new file mode 100644 index 0000000000000000000000000000000000000000..b94c42a77c7361b3f96a4e72f2491cfaa420c3ae GIT binary patch literal 1088 zcmcJOL2lbH5Jf-3!~~K?>e34%)-tQ0O9M4zqx;^$y@7#t(M>DfAV~;# zRmexn3!t(bfSF|<;Vrua7po6ZxAd%*V$NYbtWJ{QqJb%v>~c9#n>B|Vr&Jb z7+cIR;pz@M6Gmw9$uhoUZrT0)uc6mzL*etZq3UMZ(AvdGLwoledsCoy&zUPyt7`_I zTU>D)_x1M~(;gwr2Ecros84~AWb;)&u`Z01YaUQmp2H_8%x_R^DlUmm;6!?5(x`>ytcI)D#=#@ez@9Mfsz4Eius>HV%6u{s4B3}^3$!MggVM0umO9o5}Z3F`gAOC`-N_1gxl}c1F)e-pAd0)UJS*O?uy;u}7m# zgVCeJpzqgKlc5dtisLv_)odo7xyjl>9>6{Dj}qMpkel8*akG1T7Nj1K}&X!^%Iuk zCTv3sVNA^FJf}R`F*nekrhJZDW3i67O{@~>ARP1Iee9%M^;w@y9>`Y8mdg&mGjUaZH5pzF1&>&be8il^$g z9*58f*qcFG@0;-gY>)9_+7HxxQmiocJxu%oS;%{mjWF%E2m&^mZ~m2OKh9~{sJl3W zR0LRX2c)`(TDTNY0|+t(BuiBDat;{MjV#y&1QAlZr9#;ztzZl2=(I^D6%|*B&62Ks N3@|fSJ##4T_kRSI(Sra0 literal 0 HcmV?d00001 diff --git a/firmware/images/error_movement.bmp b/firmware/images/error_movement.bmp new file mode 100644 index 0000000000000000000000000000000000000000..2980dd2ab21332f07b9bf0933be65072f2c25d45 GIT binary patch literal 1088 zcmb`FzfQw25XL`agtz7mQbs0bhN?yEyh{g$43(0qFLu0%D|TeWk^y19JIAF#VuFh< z=bz6$|N9;u?=NAt=KqHNM8BsyergxYU*8tD4*qh^wfP^hA5T^M0QmgL<;D5}xygBe zFWitfV;|uo)Ry#&kl>PW%b6c=ELfPQ@@#cLSz`qR0T|dt>l$duwTtYUn4ztlmS9ON zT++a*Em#tbHfS!bv=+J3oj|AEF$OJc+}T25SQ&dcanQV%ge_wj)_PK7-FO(<8T*OD zJ|gP4q0yTH*;SUxEU5X)GGoytzw-z2&&HvQ_u}`;5)L+B8-@43F)tr45^BC_ws7P! z{~+Y;@toUjdLvK!lkiDC%4xshW6pk@GvW`%8QbBEzwgiTu`*?#`O1+pFkabxyNTbW zyqC#<`7GrTCD-mCsqmB)+ bool: # take a reading try: - mag, grav, distance = await get_raw_measurement(devices, disp, True) - if cfg.calib is None: - raise NotCalibrated() - # noinspection PyTypeChecker - azimuth, inclination, _ = cfg.calib.get_angles(mag, grav) - distance += cfg.laser_cal - logger.debug(f"Distance: {distance}m") - if cfg.anomaly_strictness is not None: + try: + mag, grav, distance = await get_raw_measurement(devices, disp, True) + if cfg.calib is None: + raise NotCalibrated() # noinspection PyTypeChecker - cfg.calib.raise_if_anomaly(mag, grav, cfg.anomaly_strictness) - except tuple(ERROR_MESSAGES.keys()) as exc: - for key in ERROR_MESSAGES.keys(): - if isinstance(exc, key): - disp.show_big_info(ERROR_MESSAGES[key]) - logger.info(f"Measurement error: {repr(exc)}") - if not isinstance(exc, asyncio.TimeoutError): - # don't wibble the laser if it's timed out, it'll just get more confused - devices.flash_laser(5,0.1) - devices.beep_sad() - await asyncio.sleep(0) - return False - else: - leg = Leg(azimuth, inclination, distance) - readings.store_reading(leg, cfg) - devices.bt.disto.send_data(azimuth, inclination, distance) - if readings.triple_shot(): - devices.flash_laser(2,0.2) - devices.beep_happy() + azimuth, inclination, _ = cfg.calib.get_angles(mag, grav) + distance += cfg.laser_cal + logger.debug(f"Distance: {distance}m") + if cfg.anomaly_strictness is not None: + # noinspection PyTypeChecker + cfg.calib.raise_if_anomaly(mag, grav, cfg.anomaly_strictness) + except tuple(ERROR_MESSAGES.keys()) as exc: + for key in ERROR_MESSAGES.keys(): + if isinstance(exc, key): + # spic17: for error messages happening often during measurement display bitmaps instead of text + # because this saves a lot of memory and thus significantly reduces + # the number of out of memory exceptions + if (key == MagneticAnomalyError) or (key == DipAnomalyError): + disp.show_bitmap_info('error_magnetic') + elif (key == GravityAnomalyError): + disp.show_bitmap_info('error_movement') + elif (key == LaserError): + disp.show_bitmap_info('error_laser') + else: + # all other error messages + disp.show_big_info(ERROR_MESSAGES[key]) + logger.info(f"Measurement error: {repr(exc)}") + if not isinstance(exc, asyncio.TimeoutError): + # don't wibble the laser if it's timed out, it'll just get more confused + devices.flash_laser(5,0.1) + devices.beep_sad() + await asyncio.sleep(0) + return False else: - devices.beep_bip() - await asyncio.sleep(0) - return True - - + leg = Leg(azimuth, inclination, distance) + readings.store_reading(leg, cfg) + devices.bt.disto.send_data(azimuth, inclination, distance) + if readings.triple_shot(): + devices.flash_laser(2,0.2) + devices.beep_happy() + else: + devices.beep_bip() + await asyncio.sleep(0) + return True + except MemoryError: + # spic17: better not do anything not strictly neccessary in these delicate moments after a memory error + # TODO: do something more drastic here after maybe 3 occurences (reboot?) + # because the device may not be able to recover otherwise? + return False + async def take_multiple_readings(devices, disp, fname, prelude, reminder): devices.laser_enable(True) disp.show_info(prelude) diff --git a/firmware/versions/display128x64.py b/firmware/versions/display128x64.py index ae744e0..4d6313b 100644 --- a/firmware/versions/display128x64.py +++ b/firmware/versions/display128x64.py @@ -55,17 +55,26 @@ def __init__(self, oled: BusDisplay, config: Config): @staticmethod def create_big_text_group(big_text: Sequence[str], index_txt): - measurement_group = displayio.Group() - azimuth = label.Label(font_20, text=big_text[0], color=0xffffff, x=1, y=9) - inclination = label.Label(font_20, text=big_text[1], color=0xffffff, x=1, y=31) - distance = label.Label(font_20, text=big_text[2], color=0xffffff, x=1, y=53) - reading_index = label.Label(terminalio.FONT, text=index_txt, color=0xffffff) - reading_index.anchored_position = (127, 32) - reading_index.anchor_point = (1.0, 0.5) - measurement_group.append(azimuth) - measurement_group.append(inclination) - measurement_group.append(distance) - measurement_group.append(reading_index) + # spic17: watchout for memory exceptions here. + try: + measurement_group = displayio.Group() + azimuth = label.Label(font_20, text=big_text[0], color=0xffffff, x=1, y=9) + inclination = label.Label(font_20, text=big_text[1], color=0xffffff, x=1, y=31) + distance = label.Label(font_20, text=big_text[2], color=0xffffff, x=1, y=53) + reading_index = label.Label(terminalio.FONT, text=index_txt, color=0xffffff) + reading_index.anchored_position = (127, 32) + reading_index.anchor_point = (1.0, 0.5) + measurement_group.append(azimuth) + measurement_group.append(inclination) + measurement_group.append(distance) + measurement_group.append(reading_index) + except MemoryError: + # Out of memory error. Catch and display nothing. Oftentimes we will recover afterwards + # (this seems purely a matter of garbage collection mechanisms, which seem not very + # predictable in this embedded system) + # Maybe use 'BitmapLabel' for optimization? + # Maybe call check_mem to try invoking garbage collection? + measurement_group = displayio.Group() return measurement_group def _set_group_with_icons(self, group): @@ -153,8 +162,25 @@ def show_info(self, text, clean=False): def show_big_info(self, text): group = self.create_big_text_group(text.splitlines(), "") - self.show_group(group) - + logger.debug("show_big_info tvo") + try: + self.show_group(group) + except MemoryError: + # spic17: do nothing when memory is spent. We may still recover when gc occurs. + pass + + def show_bitmap_info(self, bitmap_name): + group = displayio.Group() + info_bmp = bitmaps[bitmap_name] + info_tile = displayio.TileGrid(info_bmp, pixel_shader=palette, x=0, y=0) + group.append(info_tile) + try: + self._set_group_with_icons(group) + self.refresh() + except MemoryError: + # spic17: do nothing. Doing anything here may lead to an outer memory error. + # hopefully, we recover - otherwise we are off no worse than when crashing directly. + pass def show_group(self, group: Optional[displayio.Group]): self.oled.root_group = group self.refresh()