diff --git a/tests/conftest.py b/tests/conftest.py index 11d0fd92..a4e2f40f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -27,6 +27,7 @@ def pytest_configure(config): config.addinivalue_line("markers", "mock: tests that run with FakeI2C (no hardware)") config.addinivalue_line("markers", "hardware: tests that require a real board") config.addinivalue_line("markers", "manual: tests that require human validation") + config.addinivalue_line("markers", "board: board qualification tests (no driver, hardware only)") def pytest_collection_modifyitems(config, items): diff --git a/tests/runner/mpremote_bridge.py b/tests/runner/mpremote_bridge.py index b7323256..11bba3b9 100644 --- a/tests/runner/mpremote_bridge.py +++ b/tests/runner/mpremote_bridge.py @@ -142,6 +142,21 @@ def run_script( last_line = output.strip().rsplit("\n", 1)[-1] return json.loads(last_line) + def run_raw_script(self, script): + """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``. + """ + code = ( + f"import json\n" + f"{script}\n" + f"print(json.dumps(result))" + ) + output = self._run(code) + last_line = output.strip().rsplit("\n", 1)[-1] + return json.loads(last_line) + def scan_bus(self, i2c_config): """Scan I2C bus and return list of addresses.""" i2c_init = _i2c_init_code(i2c_config) diff --git a/tests/scenarios/board_buttons.yaml b/tests/scenarios/board_buttons.yaml new file mode 100644 index 00000000..f23f62bf --- /dev/null +++ b/tests/scenarios/board_buttons.yaml @@ -0,0 +1,287 @@ +type: board +name: "board_buttons" + +# --- Polling tests (one per button) --- +# LED_GREEN signals when the board is ready to read the button. + +tests: + - name: "Button A press (polling)" + action: hardware_script + pre_prompt: "Test polling boutons : appuyez sur chaque bouton quand la LED verte s'allume (5s). Commençons par A." + wait: false + script: | + from machine import Pin + from time import sleep_ms, ticks_ms, ticks_diff + led = Pin("LED_GREEN", Pin.OUT) + btn = Pin("A_BUTTON", Pin.IN, Pin.PULL_UP) + t_rel = ticks_ms() + while btn.value() == 0: + if ticks_diff(ticks_ms(), t_rel) > 3000: + break + sleep_ms(20) + led.on() + detected = False + t0 = ticks_ms() + while ticks_diff(ticks_ms(), t0) < 5000: + if btn.value() == 0: + detected = True + break + sleep_ms(20) + led.off() + result = detected + expect_true: true + mode: [hardware] + + - name: "Button B press (polling)" + action: hardware_script + pre_prompt: "Bouton B" + wait: false + script: | + from machine import Pin + from time import sleep_ms, ticks_ms, ticks_diff + led = Pin("LED_GREEN", Pin.OUT) + btn = Pin("B_BUTTON", Pin.IN, Pin.PULL_UP) + t_rel = ticks_ms() + while btn.value() == 0: + if ticks_diff(ticks_ms(), t_rel) > 3000: + break + sleep_ms(20) + led.on() + detected = False + t0 = ticks_ms() + while ticks_diff(ticks_ms(), t0) < 5000: + if btn.value() == 0: + detected = True + break + sleep_ms(20) + led.off() + result = detected + expect_true: true + mode: [hardware] + + - name: "Button MENU press (polling)" + action: hardware_script + pre_prompt: "Bouton MENU" + wait: false + script: | + from machine import Pin + from time import sleep_ms, ticks_ms, ticks_diff + led = Pin("LED_GREEN", Pin.OUT) + btn = Pin("MENU_BUTTON", Pin.IN, Pin.PULL_UP) + t_rel = ticks_ms() + while btn.value() == 0: + if ticks_diff(ticks_ms(), t_rel) > 3000: + break + sleep_ms(20) + led.on() + detected = False + t0 = ticks_ms() + while ticks_diff(ticks_ms(), t0) < 5000: + if btn.value() == 0: + detected = True + break + sleep_ms(20) + led.off() + result = detected + expect_true: true + mode: [hardware] + + # --- D-PAD buttons via MCP23009E (I2C 0x20, GPIO register 0x09) --- + # Read directly via I2C without importing the driver. + # Bit mapping: UP=bit7, DOWN=bit5, LEFT=bit6, RIGHT=bit4. + + - name: "D-PAD UP press (polling)" + action: hardware_script + pre_prompt: "Test polling D-PAD : appuyez sur chaque bouton quand la LED verte s'allume (5s). Commençons par UP." + wait: false + script: | + from machine import Pin, I2C + from time import sleep_ms, ticks_ms, ticks_diff + # Release MCP23009E from reset + rst = Pin("RST_EXPANDER", Pin.OUT) + rst.on() + sleep_ms(10) + led = Pin("LED_GREEN", Pin.OUT) + i2c = I2C(1) + bit = 7 + led.on() + detected = False + t0 = ticks_ms() + while ticks_diff(ticks_ms(), t0) < 5000: + gpio = i2c.readfrom_mem(0x20, 0x09, 1)[0] + if not (gpio & (1 << bit)): + detected = True + break + sleep_ms(20) + led.off() + result = detected + expect_true: true + mode: [hardware] + + - name: "D-PAD DOWN press (polling)" + action: hardware_script + pre_prompt: "Bouton DOWN" + wait: false + script: | + from machine import Pin, I2C + from time import sleep_ms, ticks_ms, ticks_diff + led = Pin("LED_GREEN", Pin.OUT) + i2c = I2C(1) + bit = 5 + led.on() + detected = False + t0 = ticks_ms() + while ticks_diff(ticks_ms(), t0) < 5000: + gpio = i2c.readfrom_mem(0x20, 0x09, 1)[0] + if not (gpio & (1 << bit)): + detected = True + break + sleep_ms(20) + led.off() + result = detected + expect_true: true + mode: [hardware] + + - name: "D-PAD LEFT press (polling)" + action: hardware_script + pre_prompt: "Bouton LEFT" + wait: false + script: | + from machine import Pin, I2C + from time import sleep_ms, ticks_ms, ticks_diff + led = Pin("LED_GREEN", Pin.OUT) + i2c = I2C(1) + bit = 6 + led.on() + detected = False + t0 = ticks_ms() + while ticks_diff(ticks_ms(), t0) < 5000: + gpio = i2c.readfrom_mem(0x20, 0x09, 1)[0] + if not (gpio & (1 << bit)): + detected = True + break + sleep_ms(20) + led.off() + result = detected + expect_true: true + mode: [hardware] + + - name: "D-PAD RIGHT press (polling)" + action: hardware_script + pre_prompt: "Bouton RIGHT" + wait: false + script: | + from machine import Pin, I2C + from time import sleep_ms, ticks_ms, ticks_diff + led = Pin("LED_GREEN", Pin.OUT) + i2c = I2C(1) + bit = 4 + led.on() + detected = False + t0 = ticks_ms() + while ticks_diff(ticks_ms(), t0) < 5000: + gpio = i2c.readfrom_mem(0x20, 0x09, 1)[0] + if not (gpio & (1 << bit)): + detected = True + break + sleep_ms(20) + led.off() + result = detected + expect_true: true + mode: [hardware] + + # --- Interrupt tests (GPIO direct buttons) --- + # LED_GREEN signals when the board is ready; uses Pin.irq() to detect press. + + - name: "Button A press (interrupt)" + action: hardware_script + pre_prompt: "Test interrupt boutons : appuyez brièvement sur chaque bouton quand la LED verte s'allume (5s). Commençons par A." + wait: false + script: | + from machine import Pin + from time import sleep_ms, ticks_ms, ticks_diff + led = Pin("LED_GREEN", Pin.OUT) + btn = Pin("A_BUTTON", Pin.IN, Pin.PULL_UP) + t_rel = ticks_ms() + while btn.value() == 0: + if ticks_diff(ticks_ms(), t_rel) > 3000: + break + sleep_ms(20) + detected = False + def on_press(pin): + global detected + detected = True + btn.irq(trigger=Pin.IRQ_FALLING, handler=on_press) + led.on() + t0 = ticks_ms() + while ticks_diff(ticks_ms(), t0) < 5000: + if detected: + break + sleep_ms(20) + btn.irq(handler=None) + led.off() + result = detected + expect_true: true + mode: [hardware] + + - name: "Button B press (interrupt)" + action: hardware_script + pre_prompt: "Bouton B" + wait: false + script: | + from machine import Pin + from time import sleep_ms, ticks_ms, ticks_diff + led = Pin("LED_GREEN", Pin.OUT) + btn = Pin("B_BUTTON", Pin.IN, Pin.PULL_UP) + t_rel = ticks_ms() + while btn.value() == 0: + if ticks_diff(ticks_ms(), t_rel) > 3000: + break + sleep_ms(20) + detected = False + def on_press(pin): + global detected + detected = True + btn.irq(trigger=Pin.IRQ_FALLING, handler=on_press) + led.on() + t0 = ticks_ms() + while ticks_diff(ticks_ms(), t0) < 5000: + if detected: + break + sleep_ms(20) + btn.irq(handler=None) + led.off() + result = detected + expect_true: true + mode: [hardware] + + - name: "Button MENU press (interrupt)" + action: hardware_script + pre_prompt: "Bouton MENU" + wait: false + script: | + from machine import Pin + from time import sleep_ms, ticks_ms, ticks_diff + led = Pin("LED_GREEN", Pin.OUT) + btn = Pin("MENU_BUTTON", Pin.IN, Pin.PULL_UP) + t_rel = ticks_ms() + while btn.value() == 0: + if ticks_diff(ticks_ms(), t_rel) > 3000: + break + sleep_ms(20) + detected = False + def on_press(pin): + global detected + detected = True + btn.irq(trigger=Pin.IRQ_FALLING, handler=on_press) + led.on() + t0 = ticks_ms() + while ticks_diff(ticks_ms(), t0) < 5000: + if detected: + break + sleep_ms(20) + btn.irq(handler=None) + led.off() + result = detected + expect_true: true + mode: [hardware] diff --git a/tests/scenarios/board_buzzer.yaml b/tests/scenarios/board_buzzer.yaml new file mode 100644 index 00000000..2f5550e5 --- /dev/null +++ b/tests/scenarios/board_buzzer.yaml @@ -0,0 +1,83 @@ +type: board +name: "board_buzzer" + +tests: + - name: "Buzzer tone 440Hz" + action: hardware_script + script: | + import pyb + from time import sleep_ms + timer = pyb.Timer(1, freq=440) + ch = timer.channel(4, pyb.Timer.PWM, pin=pyb.Pin("SPEAKER")) + ch.pulse_width_percent(50) + sleep_ms(500) + ch.pulse_width_percent(0) + timer.deinit() + result = True + expect_true: true + mode: [hardware] + + - name: "Buzzer 440Hz audible" + action: manual + prompt: "Avez-vous entendu un bip (La 440Hz) ?" + expect_true: true + mode: [hardware] + + - name: "Buzzer frequency sweep" + action: hardware_script + script: | + import pyb + from time import sleep_ms + timer = pyb.Timer(1, freq=200) + ch = timer.channel(4, pyb.Timer.PWM, pin=pyb.Pin("SPEAKER")) + for f in range(200, 2001, 100): + timer.freq(f) + ch.pulse_width_percent(50) + sleep_ms(200) + ch.pulse_width_percent(0) + timer.deinit() + result = True + expect_true: true + mode: [hardware] + + - name: "Buzzer sweep audible" + action: manual + prompt: "Avez-vous entendu un balayage de fréquence (grave vers aigu) ?" + expect_true: true + mode: [hardware] + + - name: "Buzzer Ode to Joy" + action: hardware_script + script: | + import pyb + from time import sleep_ms + timer = pyb.Timer(1, freq=440) + ch = timer.channel(4, pyb.Timer.PWM, pin=pyb.Pin("SPEAKER")) + # Ode to Joy (Beethoven) - first phrase + # (frequency Hz, duration ms) + melody = [ + (330, 300), (330, 300), (349, 300), (392, 300), + (392, 300), (349, 300), (330, 300), (294, 300), + (262, 300), (262, 300), (294, 300), (330, 300), + (330, 450), (294, 150), (294, 600), + (330, 300), (330, 300), (349, 300), (392, 300), + (392, 300), (349, 300), (330, 300), (294, 300), + (262, 300), (262, 300), (294, 300), (330, 300), + (294, 450), (262, 150), (262, 600), + ] + for freq, dur in melody: + timer.freq(freq) + ch.pulse_width_percent(50) + sleep_ms(dur) + ch.pulse_width_percent(0) + sleep_ms(30) + timer.deinit() + result = True + expect_true: true + mode: [hardware] + + - name: "Buzzer Ode to Joy audible" + action: manual + prompt: "Avez-vous reconnu l'Hymne à la joie (Beethoven) ?" + expect_true: true + mode: [hardware] diff --git a/tests/scenarios/board_i2c_scan.yaml b/tests/scenarios/board_i2c_scan.yaml new file mode 100644 index 00000000..8f51352c --- /dev/null +++ b/tests/scenarios/board_i2c_scan.yaml @@ -0,0 +1,112 @@ +type: board +name: "board_i2c_scan" + +tests: + - name: "I2C bus scan finds expected devices" + action: hardware_script + script: | + from machine import I2C, Pin + # Release MCP23009E from reset so it responds to scan + rst = Pin("RST_EXPANDER", Pin.OUT) + rst.on() + from time import sleep_ms + sleep_ms(10) + i2c = I2C(1) + found = i2c.scan() + # Expected I2C addresses on STeaMi board (7-bit): + # 0x1E=LIS2MDL, 0x20=MCP23009E, 0x29=VL53L1X, 0x39=APDS9960 + # 0x3B=STM32F103CB, 0x55=BQ27441, 0x5D=WSEN-PADS + # 0x5F=HTS221/WSEN-HIDS, 0x6B=ISM330DL + expected = [0x1E, 0x20, 0x29, 0x39, 0x3B, 0x55, 0x5D, 0x5F, 0x6B] + missing = sorted([hex(a) for a in expected if a not in found]) + result = missing + expect: [] + mode: [hardware] + + - name: "No unexpected I2C devices" + action: hardware_script + script: | + from machine import I2C, Pin + rst = Pin("RST_EXPANDER", Pin.OUT) + rst.on() + from time import sleep_ms + sleep_ms(10) + i2c = I2C(1) + found = i2c.scan() + known = [0x1E, 0x20, 0x29, 0x39, 0x3B, 0x55, 0x5D, 0x5F, 0x6B] + unexpected = sorted([hex(a) for a in found if a not in known]) + result = unexpected + expect: [] + mode: [hardware] + + # --- WHO_AM_I / Device ID checks --- + # Each test reads the identification register of one component. + + - name: "LIS2MDL WHO_AM_I (0x1E)" + action: hardware_script + script: | + from machine import I2C + i2c = I2C(1) + result = i2c.readfrom_mem(0x1E, 0x4F, 1)[0] + expect: 0x40 + mode: [hardware] + + - name: "MCP23009E IODIR default (0x20)" + action: hardware_script + script: | + from machine import I2C, Pin + rst = Pin("RST_EXPANDER", Pin.OUT) + rst.on() + from time import sleep_ms + sleep_ms(10) + i2c = I2C(1) + # IODIR register default value after reset = 0xFF (all inputs) + result = i2c.readfrom_mem(0x20, 0x00, 1)[0] + expect: 0xFF + mode: [hardware] + + - name: "VL53L1X model ID (0x29)" + action: hardware_script + script: | + from machine import I2C + i2c = I2C(1) + data = i2c.readfrom_mem(0x29, 0x010F, 2, addrsize=16) + result = (data[0] << 8) | data[1] + expect: 0xEACC + mode: [hardware] + + - name: "APDS9960 device ID (0x39)" + action: hardware_script + script: | + from machine import I2C + i2c = I2C(1) + result = i2c.readfrom_mem(0x39, 0x92, 1)[0] + expect: 0xAB + mode: [hardware] + + - name: "WSEN-PADS device ID (0x5D)" + action: hardware_script + script: | + from machine import I2C + i2c = I2C(1) + result = i2c.readfrom_mem(0x5D, 0x0F, 1)[0] + expect: 0xB3 + mode: [hardware] + + - name: "HTS221/WSEN-HIDS device ID (0x5F)" + action: hardware_script + script: | + from machine import I2C + i2c = I2C(1) + result = i2c.readfrom_mem(0x5F, 0x0F, 1)[0] + expect: 0xBC + mode: [hardware] + + - name: "ISM330DL WHO_AM_I (0x6B)" + action: hardware_script + script: | + from machine import I2C + i2c = I2C(1) + result = i2c.readfrom_mem(0x6B, 0x0F, 1)[0] + expect: 0x6A + mode: [hardware] diff --git a/tests/scenarios/board_leds.yaml b/tests/scenarios/board_leds.yaml new file mode 100644 index 00000000..71e06f33 --- /dev/null +++ b/tests/scenarios/board_leds.yaml @@ -0,0 +1,107 @@ +type: board +name: "board_leds" + +tests: + - name: "LED RED on/off" + action: hardware_script + pre_prompt: "Appuyez sur Entrée pour lancer le test LED RGB, puis observez la carte." + script: | + from machine import Pin + from time import sleep_ms + led = Pin("LED_RED", Pin.OUT) + led.on() + sleep_ms(1000) + led.off() + result = True + expect_true: true + mode: [hardware] + + - name: "LED RED visible" + action: manual + prompt: "La LED rouge s'est-elle allumée puis éteinte ?" + expect_true: true + mode: [hardware] + + - name: "LED GREEN on/off" + action: hardware_script + script: | + from machine import Pin + from time import sleep_ms + led = Pin("LED_GREEN", Pin.OUT) + led.on() + sleep_ms(1000) + led.off() + result = True + expect_true: true + mode: [hardware] + + - name: "LED GREEN visible" + action: manual + prompt: "La LED verte s'est-elle allumée puis éteinte ?" + expect_true: true + mode: [hardware] + + - name: "LED BLUE on/off" + action: hardware_script + script: | + from machine import Pin + from time import sleep_ms + led = Pin("LED_BLUE", Pin.OUT) + led.on() + sleep_ms(1000) + led.off() + result = True + expect_true: true + mode: [hardware] + + - name: "LED BLUE visible" + action: manual + prompt: "La LED bleue s'est-elle allumée puis éteinte ?" + expect_true: true + mode: [hardware] + + - name: "RGB LED full cycle" + action: hardware_script + script: | + from machine import Pin + from time import sleep_ms + r = Pin("LED_RED", Pin.OUT) + g = Pin("LED_GREEN", Pin.OUT) + b = Pin("LED_BLUE", Pin.OUT) + for led in [r, g, b]: + led.on() + sleep_ms(500) + led.off() + # White = all on + r.on(); g.on(); b.on() + sleep_ms(500) + r.off(); g.off(); b.off() + result = True + expect_true: true + mode: [hardware] + + - name: "RGB LED full cycle visible" + action: manual + prompt: "La LED a-t-elle affiché rouge, vert, bleu, puis blanc ?" + expect_true: true + mode: [hardware] + + - name: "LED BLE on/off" + action: hardware_script + pre_prompt: "Appuyez sur Entrée pour lancer le test LED BLE, puis observez la carte." + script: | + from machine import Pin + from time import sleep_ms + led = Pin("LED_BLE", Pin.OUT) + led.on() + sleep_ms(1000) + led.off() + result = True + expect_true: true + mode: [hardware] + + - name: "LED BLE visible" + action: manual + prompt: "La LED BLE (bleue) s'est-elle allumée puis éteinte ?" + expect_true: true + mode: [hardware] diff --git a/tests/scenarios/board_pins.yaml b/tests/scenarios/board_pins.yaml new file mode 100644 index 00000000..6e5572b6 --- /dev/null +++ b/tests/scenarios/board_pins.yaml @@ -0,0 +1,119 @@ +type: board +name: "board_pins" + +# Smoke tests for user-accessible pins on the edge connector and croc pads. +# ADC tests verify that the pin can be configured and return a raw reading. +# Digital tests verify the internal pull-up reads high when the pin is floating. +# +# Note: These are basic configuration/readback smoke tests. Full electrical +# continuity testing requires a physical test bench (jig) with known voltages +# and will be added once the test bench hardware is designed. + +tests: + - name: "Pin P0 (PC4) analog read" + action: hardware_script + script: | + from machine import ADC, Pin + adc = ADC(Pin("P0")) + result = adc.read_u16() + expect_range: [0, 65535] + mode: [hardware] + + - name: "Pin P1 (PA5) analog read" + action: hardware_script + script: | + from machine import ADC, Pin + adc = ADC(Pin("P1")) + result = adc.read_u16() + expect_range: [0, 65535] + mode: [hardware] + + - name: "Pin P2 (PC5) analog read" + action: hardware_script + script: | + from machine import ADC, Pin + adc = ADC(Pin("P2")) + result = adc.read_u16() + expect_range: [0, 65535] + mode: [hardware] + + - name: "Pin P3 (PA2) analog read" + action: hardware_script + script: | + from machine import ADC, Pin + adc = ADC(Pin("P3")) + result = adc.read_u16() + expect_range: [0, 65535] + mode: [hardware] + + - name: "Pin P4 (PA4) analog read" + action: hardware_script + script: | + from machine import ADC, Pin + adc = ADC(Pin("P4")) + result = adc.read_u16() + expect_range: [0, 65535] + mode: [hardware] + + - name: "Pin P10 (PA6) analog read" + action: hardware_script + script: | + from machine import ADC, Pin + adc = ADC(Pin("P10")) + result = adc.read_u16() + expect_range: [0, 65535] + mode: [hardware] + + - name: "Pin P6 (PC3) digital pull-up read" + action: hardware_script + script: | + from machine import Pin + p = Pin("P6", Pin.IN, Pin.PULL_UP) + result = p.value() + expect: 1 + mode: [hardware] + + - name: "Pin P7 (PA9) digital pull-up read" + action: hardware_script + script: | + from machine import Pin + p = Pin("P7", Pin.IN, Pin.PULL_UP) + result = p.value() + expect: 1 + mode: [hardware] + + - name: "Pin P8 (PA15) digital pull-up read" + action: hardware_script + script: | + from machine import Pin + p = Pin("P8", Pin.IN, Pin.PULL_UP) + result = p.value() + expect: 1 + mode: [hardware] + + - name: "Pin P9 (PC2) digital pull-up read" + action: hardware_script + script: | + from machine import Pin + p = Pin("P9", Pin.IN, Pin.PULL_UP) + result = p.value() + expect: 1 + mode: [hardware] + + - name: "Pin P12 (PC6) digital pull-up read" + action: hardware_script + script: | + from machine import Pin + p = Pin("P12", Pin.IN, Pin.PULL_UP) + result = p.value() + expect: 1 + mode: [hardware] + + - name: "Pin P16 (PE4) digital pull-up read" + action: hardware_script + script: | + from machine import Pin + p = Pin("P16", Pin.IN, Pin.PULL_UP) + result = p.value() + expect: 1 + mode: [hardware] diff --git a/tests/test_scenarios.py b/tests/test_scenarios.py index 4744e9b0..d17fccd1 100644 --- a/tests/test_scenarios.py +++ b/tests/test_scenarios.py @@ -20,6 +20,9 @@ def _print_result(result, test): """Print the measured value for report capture.""" + if isinstance(result, bool): + # Boolean smoke tests (True/False) — nothing useful to print + return if isinstance(result, float): print(f"{result:.2f}") elif isinstance(result, int) and "expect" in test and isinstance(test["expect"], int) and test["expect"] > 0xFF: @@ -36,12 +39,17 @@ def iter_scenario_tests(): with open(yaml_file, encoding="utf-8") as f: scenario = yaml.safe_load(f) - driver = scenario["driver"] + is_board = scenario.get("type") == "board" + name = scenario.get("name", scenario.get("driver", yaml_file.stem)) for test in scenario.get("tests", []): modes = test.get("mode", ["mock", "hardware"]) + if is_board: + # Board scenarios are hardware-only + modes = [m for m in modes if m == "hardware"] for mode in modes: - test_id = f"{driver}/{test['name']}/{mode}" - yield pytest.param(scenario, test, mode, id=test_id) + test_id = f"{name}/{test['name']}/{mode}" + marks = [pytest.mark.board, pytest.mark.hardware] if is_board else [] + yield pytest.param(scenario, test, mode, id=test_id, marks=marks) def make_mock_instance(scenario): @@ -84,6 +92,8 @@ def test_scenario(scenario, test, mode, port): bridge = MpremoteBridge(port=port) action = test["action"] + is_board = scenario.get("type") == "board" + if action == "manual": # Skip manual tests when stdin is not available (no -s flag) import sys @@ -91,59 +101,66 @@ def test_scenario(scenario, test, mode, port): pytest.skip("manual test requires interactive mode (-s)") # Display values before prompting if 'display' is defined - for display in test.get("display", []): - value = bridge.call_method( + # (board scenarios have no driver, so display is not supported) + if not is_board: + for display in test.get("display", []): + value = bridge.call_method( + scenario["driver"], + scenario["driver_class"], + scenario["i2c"], + display["method"], + display.get("args"), + module_name=scenario.get("module_name"), + hardware_init=scenario.get("hardware_init"), + i2c_address=scenario.get("i2c_address"), + ) + label = display.get("label", display["method"]) + unit = display.get("unit", "") + if isinstance(value, float): + print(f" {label}: {value:.2f} {unit}") + else: + print(f" {label}: {value} {unit}") + prompt = test.get("prompt", "Manual check") + result = prompt_yes_no(prompt) + elif action in ("call", "read_register", "interactive"): + if is_board: + pytest.fail( + f"Board scenarios do not support '{action}' action; " + f"use 'hardware_script' or 'manual' instead" + ) + if action == "call": + result = bridge.call_method( scenario["driver"], scenario["driver_class"], scenario["i2c"], - display["method"], - display.get("args"), + test["method"], + test.get("args"), + module_name=scenario.get("module_name"), + hardware_init=scenario.get("hardware_init"), + i2c_address=scenario.get("i2c_address"), + ) + elif action == "read_register": + result = bridge.read_register( + scenario["i2c"], + scenario["i2c_address"], + test["register"], + ) + else: # interactive + import sys + if not sys.stdin.isatty(): + pytest.skip("interactive test requires interactive mode (-s)") + pre_prompt = test.get("pre_prompt", "Perform action then press Enter") + input(f" [INTERACTIVE] {pre_prompt} ") + result = bridge.call_method( + scenario["driver"], + scenario["driver_class"], + scenario["i2c"], + test["method"], + test.get("args"), module_name=scenario.get("module_name"), hardware_init=scenario.get("hardware_init"), i2c_address=scenario.get("i2c_address"), ) - label = display.get("label", display["method"]) - unit = display.get("unit", "") - if isinstance(value, float): - print(f" {label}: {value:.2f} {unit}") - else: - print(f" {label}: {value} {unit}") - prompt = test.get("prompt", "Manual check") - result = prompt_yes_no(prompt) - elif action == "call": - result = bridge.call_method( - scenario["driver"], - scenario["driver_class"], - scenario["i2c"], - test["method"], - test.get("args"), - module_name=scenario.get("module_name"), - hardware_init=scenario.get("hardware_init"), - i2c_address=scenario.get("i2c_address"), - ) - elif action == "read_register": - result = bridge.read_register( - scenario["i2c"], - scenario["i2c_address"], - test["register"], - ) - elif action == "interactive": - # Prompt user first, then call method - import sys - if not sys.stdin.isatty(): - pytest.skip("interactive test requires interactive mode (-s)") - pre_prompt = test.get("pre_prompt", "Perform action then press Enter") - input(f" [INTERACTIVE] {pre_prompt} ") - result = bridge.call_method( - scenario["driver"], - scenario["driver_class"], - scenario["i2c"], - test["method"], - test.get("args"), - module_name=scenario.get("module_name"), - hardware_init=scenario.get("hardware_init"), - i2c_address=scenario.get("i2c_address"), - ) elif action == "hardware_script": import sys if not sys.stdin.isatty(): @@ -154,15 +171,18 @@ def test_scenario(scenario, test, mode, port): input(f" [INTERACTIVE] {pre_prompt} ") else: print(f" [INTERACTIVE] {pre_prompt}") - result = bridge.run_script( - scenario["driver"], - scenario["driver_class"], - scenario["i2c"], - test["script"], - module_name=scenario.get("module_name"), - hardware_init=scenario.get("hardware_init"), - i2c_address=scenario.get("i2c_address"), - ) + if is_board: + result = bridge.run_raw_script(test["script"]) + else: + result = bridge.run_script( + scenario["driver"], + scenario["driver_class"], + scenario["i2c"], + test["script"], + module_name=scenario.get("module_name"), + hardware_init=scenario.get("hardware_init"), + i2c_address=scenario.get("i2c_address"), + ) else: pytest.fail(f"Unknown action: {action}")