From 46b6945e10a6c3eae1cad51f621104e633f0d75b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20NEDJAR?= Date: Fri, 13 Mar 2026 17:44:26 +0100 Subject: [PATCH 1/6] tests: Add multidriver temperature comparison scenario for calibration. --- .../board_temperature_comparison.yaml | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 tests/scenarios/board_temperature_comparison.yaml diff --git a/tests/scenarios/board_temperature_comparison.yaml b/tests/scenarios/board_temperature_comparison.yaml new file mode 100644 index 00000000..1efbdaeb --- /dev/null +++ b/tests/scenarios/board_temperature_comparison.yaml @@ -0,0 +1,106 @@ +type: board +name: "Temperature comparison (all sensors)" + +i2c: + id: 1 + +tests: + - name: "Read all temperature sensors" + action: hardware_script + script: | + import sys + sys.path.insert(0, '/flash/lib/hts221') + sys.path.insert(0, '/flash/lib/wsen-hids') + sys.path.insert(0, '/flash/lib/wsen-pads') + sys.path.insert(0, '/flash/lib/lis2mdl') + sys.path.insert(0, '/flash/lib/bq27441') + + from machine import I2C + from time import sleep_ms + + i2c = I2C(1) + + temps = {} + + # HTS221 + from hts221.device import HTS221 + hts = HTS221(i2c) + hts.poweroff() + sleep_ms(20) + hts.poweron() + sleep_ms(50) + temps['HTS221'] = hts.temperature() + + # WSEN-HIDS + from wsen_hids.device import WSEN_HIDS + hids = WSEN_HIDS(i2c) + temps['WSEN-HIDS'] = hids.temperature() + + # WSEN-PADS + from wsen_pads.device import WSEN_PADS + pads = WSEN_PADS(i2c) + temps['WSEN-PADS'] = pads.temperature() + + # LIS2MDL + from lis2mdl.device import LIS2MDL + mag = LIS2MDL(i2c) + temps['LIS2MDL'] = mag.read_temperature_c() + + # BQ27441 + from bq27441.device import BQ27441, TempMeasureType + bq = BQ27441(i2c) + raw = bq.temperature(TempMeasureType(1)) + temps['BQ27441'] = raw / 10.0 - 273.15 + + # Print comparison table + print('--- Temperature Comparison ---') + for name, t in temps.items(): + print(f' {name:12s}: {t:6.2f} C') + + # Check all in plausible range + all_ok = all(5.0 <= t <= 50.0 for t in temps.values()) + + # Check spread between dedicated thermometers + dedicated = [temps['HTS221'], temps['WSEN-HIDS']] + spread = max(dedicated) - min(dedicated) + print(f' Spread (HTS221 vs WSEN-HIDS): {spread:.2f} C') + + result = all_ok and spread < 3.0 + expect_true: true + mode: [hardware] + + - name: "Temperature spread with barometer" + action: hardware_script + script: | + import sys + sys.path.insert(0, '/flash/lib/hts221') + sys.path.insert(0, '/flash/lib/wsen-pads') + + from machine import I2C + from time import sleep_ms + + i2c = I2C(1) + + from hts221.device import HTS221 + hts = HTS221(i2c) + hts.poweroff() + sleep_ms(20) + hts.poweron() + sleep_ms(50) + t_hts = hts.temperature() + + from wsen_pads.device import WSEN_PADS + pads = WSEN_PADS(i2c) + t_pads = pads.temperature() + + spread = abs(t_hts - t_pads) + print(f' HTS221: {t_hts:.2f} C | WSEN-PADS: {t_pads:.2f} C | Spread: {spread:.2f} C') + result = spread < 5.0 + expect_true: true + mode: [hardware] + + - name: "Temperature values feel correct" + action: manual + prompt: "Les temperatures lues sont-elles coherentes entre elles et avec l'ambiance ?" + expect_true: true + mode: [hardware] From 7665d7e7f7b490f27697a957f635f248117152c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20NEDJAR?= Date: Fri, 13 Mar 2026 19:46:12 +0100 Subject: [PATCH 2/6] tests: Fix multidriver board scenario to mount lib/ via mpremote. --- tests/runner/mpremote_bridge.py | 7 +++-- .../board_temperature_comparison.yaml | 28 ++++++++++++------- tests/test_scenarios.py | 7 ++++- 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/tests/runner/mpremote_bridge.py b/tests/runner/mpremote_bridge.py index 11bba3b9..e1e4d46a 100644 --- a/tests/runner/mpremote_bridge.py +++ b/tests/runner/mpremote_bridge.py @@ -142,18 +142,21 @@ def run_script( last_line = output.strip().rsplit("\n", 1)[-1] return json.loads(last_line) - def run_raw_script(self, script): + def run_raw_script(self, script, mount_dir=None): """Run a raw MicroPython script on the board without driver context. The script must set a ``result`` variable. Returns the JSON-decoded value of ``result``. + + Args: + mount_dir: optional local directory to mount on the board. """ code = ( f"import json\n" f"{script}\n" f"print(json.dumps(result))" ) - output = self._run(code) + output = self._run(code, mount_dir=mount_dir) last_line = output.strip().rsplit("\n", 1)[-1] return json.loads(last_line) diff --git a/tests/scenarios/board_temperature_comparison.yaml b/tests/scenarios/board_temperature_comparison.yaml index 1efbdaeb..c7bcaa2f 100644 --- a/tests/scenarios/board_temperature_comparison.yaml +++ b/tests/scenarios/board_temperature_comparison.yaml @@ -4,16 +4,24 @@ name: "Temperature comparison (all sensors)" i2c: id: 1 +# List of drivers needed — triggers mounting lib/ via mpremote +drivers: + - hts221 + - wsen-hids + - wsen-pads + - lis2mdl + - bq27441 + tests: - name: "Read all temperature sensors" action: hardware_script script: | import sys - sys.path.insert(0, '/flash/lib/hts221') - sys.path.insert(0, '/flash/lib/wsen-hids') - sys.path.insert(0, '/flash/lib/wsen-pads') - sys.path.insert(0, '/flash/lib/lis2mdl') - sys.path.insert(0, '/flash/lib/bq27441') + sys.path.insert(0, '/remote/hts221') + sys.path.insert(0, '/remote/wsen-hids') + sys.path.insert(0, '/remote/wsen-pads') + sys.path.insert(0, '/remote/lis2mdl') + sys.path.insert(0, '/remote/bq27441') from machine import I2C from time import sleep_ms @@ -55,7 +63,7 @@ tests: # Print comparison table print('--- Temperature Comparison ---') for name, t in temps.items(): - print(f' {name:12s}: {t:6.2f} C') + print(' ' + name.ljust(12) + ': ' + str(round(t, 2)) + ' C') # Check all in plausible range all_ok = all(5.0 <= t <= 50.0 for t in temps.values()) @@ -63,7 +71,7 @@ tests: # Check spread between dedicated thermometers dedicated = [temps['HTS221'], temps['WSEN-HIDS']] spread = max(dedicated) - min(dedicated) - print(f' Spread (HTS221 vs WSEN-HIDS): {spread:.2f} C') + print(' Spread (HTS221 vs WSEN-HIDS): ' + str(round(spread, 2)) + ' C') result = all_ok and spread < 3.0 expect_true: true @@ -73,8 +81,8 @@ tests: action: hardware_script script: | import sys - sys.path.insert(0, '/flash/lib/hts221') - sys.path.insert(0, '/flash/lib/wsen-pads') + sys.path.insert(0, '/remote/hts221') + sys.path.insert(0, '/remote/wsen-pads') from machine import I2C from time import sleep_ms @@ -94,7 +102,7 @@ tests: t_pads = pads.temperature() spread = abs(t_hts - t_pads) - print(f' HTS221: {t_hts:.2f} C | WSEN-PADS: {t_pads:.2f} C | Spread: {spread:.2f} C') + print(' HTS221: ' + str(round(t_hts, 2)) + ' C | WSEN-PADS: ' + str(round(t_pads, 2)) + ' C | Spread: ' + str(round(spread, 2)) + ' C') result = spread < 5.0 expect_true: true mode: [hardware] diff --git a/tests/test_scenarios.py b/tests/test_scenarios.py index d17fccd1..16e2aa6d 100644 --- a/tests/test_scenarios.py +++ b/tests/test_scenarios.py @@ -172,7 +172,12 @@ def test_scenario(scenario, test, mode, port): else: print(f" [INTERACTIVE] {pre_prompt}") if is_board: - result = bridge.run_raw_script(test["script"]) + mount_dir = None + if scenario.get("drivers"): + mount_dir = Path(__file__).parent.parent / "lib" + result = bridge.run_raw_script( + test["script"], mount_dir=mount_dir, + ) else: result = bridge.run_script( scenario["driver"], From 57f648f185179926cd6799aa9ad202409421dddd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20NEDJAR?= Date: Fri, 13 Mar 2026 19:59:01 +0100 Subject: [PATCH 3/6] tests: Fix multidriver temperature scenario after hardware validation. --- tests/runner/mpremote_bridge.py | 11 +++++---- .../board_temperature_comparison.yaml | 23 +++++++------------ 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/tests/runner/mpremote_bridge.py b/tests/runner/mpremote_bridge.py index e1e4d46a..d7409713 100644 --- a/tests/runner/mpremote_bridge.py +++ b/tests/runner/mpremote_bridge.py @@ -49,9 +49,8 @@ def _run(self, code, mount_dir=None): result = subprocess.run(cmd, capture_output=True, text=True, timeout=30, check=False) if result.returncode != 0: - raise RuntimeError( - f"mpremote failed: {result.stderr.strip()}" - ) + detail = result.stderr.strip() or result.stdout.strip() + raise RuntimeError(f"mpremote failed: {detail}") # mpremote mount adds "Local directory ... is mounted at /remote" # to stdout; filter it out and keep only the script output lines = [ @@ -157,7 +156,11 @@ def run_raw_script(self, script, mount_dir=None): f"print(json.dumps(result))" ) output = self._run(code, mount_dir=mount_dir) - last_line = output.strip().rsplit("\n", 1)[-1] + lines = output.strip().splitlines() + # Print non-JSON lines (diagnostic output from the script) + for line in lines[:-1]: + print(line) + last_line = lines[-1] if lines else "" return json.loads(last_line) def scan_bus(self, i2c_config): diff --git a/tests/scenarios/board_temperature_comparison.yaml b/tests/scenarios/board_temperature_comparison.yaml index c7bcaa2f..48710154 100644 --- a/tests/scenarios/board_temperature_comparison.yaml +++ b/tests/scenarios/board_temperature_comparison.yaml @@ -10,7 +10,6 @@ drivers: - wsen-hids - wsen-pads - lis2mdl - - bq27441 tests: - name: "Read all temperature sensors" @@ -21,7 +20,6 @@ tests: sys.path.insert(0, '/remote/wsen-hids') sys.path.insert(0, '/remote/wsen-pads') sys.path.insert(0, '/remote/lis2mdl') - sys.path.insert(0, '/remote/bq27441') from machine import I2C from time import sleep_ms @@ -49,31 +47,26 @@ tests: pads = WSEN_PADS(i2c) temps['WSEN-PADS'] = pads.temperature() - # LIS2MDL + # LIS2MDL (auxiliary, offset not guaranteed by datasheet) from lis2mdl.device import LIS2MDL mag = LIS2MDL(i2c) temps['LIS2MDL'] = mag.read_temperature_c() - # BQ27441 - from bq27441.device import BQ27441, TempMeasureType - bq = BQ27441(i2c) - raw = bq.temperature(TempMeasureType(1)) - temps['BQ27441'] = raw / 10.0 - 273.15 - # Print comparison table print('--- Temperature Comparison ---') for name, t in temps.items(): - print(' ' + name.ljust(12) + ': ' + str(round(t, 2)) + ' C') + pad = ' ' * (12 - len(name)) + print(' ' + name + pad + ': ' + str(round(t, 2)) + ' C') - # Check all in plausible range - all_ok = all(5.0 <= t <= 50.0 for t in temps.values()) + # Check dedicated thermometers in plausible range + dedicated = [temps['HTS221'], temps['WSEN-HIDS'], temps['WSEN-PADS']] + all_ok = all(5.0 <= t <= 50.0 for t in dedicated) # Check spread between dedicated thermometers - dedicated = [temps['HTS221'], temps['WSEN-HIDS']] spread = max(dedicated) - min(dedicated) - print(' Spread (HTS221 vs WSEN-HIDS): ' + str(round(spread, 2)) + ' C') + print(' Spread (dedicated): ' + str(round(spread, 2)) + ' C') - result = all_ok and spread < 3.0 + result = all_ok and spread < 8.0 expect_true: true mode: [hardware] From 9f7d5efda236cb67e1b0c22cd38cbf108ab009c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20NEDJAR?= Date: Fri, 13 Mar 2026 20:09:48 +0100 Subject: [PATCH 4/6] tests: Add 25C empirical offset to LIS2MDL in temperature comparison. --- tests/scenarios/board_temperature_comparison.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/scenarios/board_temperature_comparison.yaml b/tests/scenarios/board_temperature_comparison.yaml index 48710154..a91345fe 100644 --- a/tests/scenarios/board_temperature_comparison.yaml +++ b/tests/scenarios/board_temperature_comparison.yaml @@ -48,9 +48,10 @@ tests: temps['WSEN-PADS'] = pads.temperature() # LIS2MDL (auxiliary, offset not guaranteed by datasheet) + # Add 25°C empirical offset (raw value has no guaranteed zero point) from lis2mdl.device import LIS2MDL mag = LIS2MDL(i2c) - temps['LIS2MDL'] = mag.read_temperature_c() + temps['LIS2MDL'] = 25.0 + mag.read_temperature_raw() / 8.0 # Print comparison table print('--- Temperature Comparison ---') From 7e716ecdcf37e5c87d75c88f812a09c42670684a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20NEDJAR?= Date: Sat, 14 Mar 2026 07:57:20 +0100 Subject: [PATCH 5/6] tests: Use driver read_temperature_c() for LIS2MDL in comparison test. --- tests/scenarios/board_temperature_comparison.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/scenarios/board_temperature_comparison.yaml b/tests/scenarios/board_temperature_comparison.yaml index a91345fe..48710154 100644 --- a/tests/scenarios/board_temperature_comparison.yaml +++ b/tests/scenarios/board_temperature_comparison.yaml @@ -48,10 +48,9 @@ tests: temps['WSEN-PADS'] = pads.temperature() # LIS2MDL (auxiliary, offset not guaranteed by datasheet) - # Add 25°C empirical offset (raw value has no guaranteed zero point) from lis2mdl.device import LIS2MDL mag = LIS2MDL(i2c) - temps['LIS2MDL'] = 25.0 + mag.read_temperature_raw() / 8.0 + temps['LIS2MDL'] = mag.read_temperature_c() # Print comparison table print('--- Temperature Comparison ---') From 38223742be125648d4662dff779a86a0732e145e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20NEDJAR?= Date: Sat, 14 Mar 2026 08:05:10 +0100 Subject: [PATCH 6/6] tests: Address Copilot review on PR #100. --- tests/runner/mpremote_bridge.py | 13 +++++++++++-- tests/scenarios/board_temperature_comparison.yaml | 14 +++++++------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/tests/runner/mpremote_bridge.py b/tests/runner/mpremote_bridge.py index d7409713..58465195 100644 --- a/tests/runner/mpremote_bridge.py +++ b/tests/runner/mpremote_bridge.py @@ -49,7 +49,10 @@ def _run(self, code, mount_dir=None): result = subprocess.run(cmd, capture_output=True, text=True, timeout=30, check=False) if result.returncode != 0: - detail = result.stderr.strip() or result.stdout.strip() + stderr = result.stderr.strip() + stdout = result.stdout.strip() + parts = [p for p in (stderr, stdout) if p] + detail = "\n".join(parts) or "(no output)" raise RuntimeError(f"mpremote failed: {detail}") # mpremote mount adds "Local directory ... is mounted at /remote" # to stdout; filter it out and keep only the script output @@ -161,7 +164,13 @@ def run_raw_script(self, script, mount_dir=None): for line in lines[:-1]: print(line) last_line = lines[-1] if lines else "" - return json.loads(last_line) + try: + return json.loads(last_line) + except json.JSONDecodeError: + full = "\n".join(lines) + raise RuntimeError( + f"Script did not produce valid JSON result.\nFull output:\n{full}" + ) def scan_bus(self, i2c_config): """Scan I2C bus and return list of addresses.""" diff --git a/tests/scenarios/board_temperature_comparison.yaml b/tests/scenarios/board_temperature_comparison.yaml index 48710154..11a1b654 100644 --- a/tests/scenarios/board_temperature_comparison.yaml +++ b/tests/scenarios/board_temperature_comparison.yaml @@ -58,13 +58,13 @@ tests: pad = ' ' * (12 - len(name)) print(' ' + name + pad + ': ' + str(round(t, 2)) + ' C') - # Check dedicated thermometers in plausible range - dedicated = [temps['HTS221'], temps['WSEN-HIDS'], temps['WSEN-PADS']] - all_ok = all(5.0 <= t <= 50.0 for t in dedicated) + # Check thermometers in plausible range (HTS221, WSEN-HIDS, WSEN-PADS) + thermometers = [temps['HTS221'], temps['WSEN-HIDS'], temps['WSEN-PADS']] + all_ok = all(5.0 <= t <= 50.0 for t in thermometers) - # Check spread between dedicated thermometers - spread = max(dedicated) - min(dedicated) - print(' Spread (dedicated): ' + str(round(spread, 2)) + ' C') + # Check spread between thermometers + spread = max(thermometers) - min(thermometers) + print(' Spread: ' + str(round(spread, 2)) + ' C') result = all_ok and spread < 8.0 expect_true: true @@ -102,6 +102,6 @@ tests: - name: "Temperature values feel correct" action: manual - prompt: "Les temperatures lues sont-elles coherentes entre elles et avec l'ambiance ?" + prompt: "Les températures lues sont-elles cohérentes entre elles et avec l'ambiance ?" expect_true: true mode: [hardware]