from __future__ import annotations import pytest from app.display.matrix_driver import MatrixDriverOptions, validate_driver_options from app.display.native_model import ( HEIGHT, HARDWARE_MAPPING, HUB75_PINS, PI_BANK_OFFSET, PI_DATA_OFFSET, PIO_BASE, PWM_BASE, PWM_BITS, REFRESH_RATE_LIMITS, RESERVED_PI_BITS, SCAN_ROWS, WIDTH, address_mask, bitplane_index, build_bitplanes, map_channel, oe_ticks_for_plane, ) def test_walnutpi_pin_mapping_matches_fixed_hub75_contract(): assert [pin.signal for pin in HUB75_PINS] == [ "R1", "G1", "B1", "R2", "G2", "B2", "A", "B", "C", "D", "E", "CLK", "OE", "LAT", ] assert [pin.physical for pin in HUB75_PINS] == [29, 31, 33, 35, 37, 8, 10, 28, 27, 15, 16, 38, 40, 36] assert [pin.bit for pin in HUB75_PINS] == [0, 1, 2, 3, 4, 5, 6, 9, 10, 11, 12, 13, 14, 15] assert not ({pin.bit for pin in HUB75_PINS} & RESERVED_PI_BITS) assert RESERVED_PI_BITS == {7, 8, 16} assert HUB75_PINS[-2].gpio == "PI14/PWM4" assert HUB75_PINS[-1].gpio == "PI15" assert HARDWARE_MAPPING == "walnutpi-pi-bank-pwm-oe-v2" def test_h618_pi_register_addresses_are_fixed(): assert PIO_BASE == 0x0300B000 assert PWM_BASE == 0x0300A000 assert PI_BANK_OFFSET == 0x120 assert PI_DATA_OFFSET == 0x10 assert PIO_BASE + PI_BANK_OFFSET + PI_DATA_OFFSET == 0x0300B130 def test_row_address_uses_abcde_bits_in_pi_bank(): assert address_mask(0) == 0 assert address_mask(1) == 1 << 6 assert address_mask(2) == 1 << 9 assert address_mask(16) == 1 << 12 assert address_mask(31) == sum(1 << bit for bit in (6, 9, 10, 11, 12)) with pytest.raises(ValueError): address_mask(32) def test_bitplanes_pair_top_and_bottom_rows_without_address_or_control_bits(): frame = bytearray(WIDTH * HEIGHT * 3) frame[0:3] = bytes((0xFF, 0x80, 0x40)) bottom = SCAN_ROWS * WIDTH * 3 frame[bottom:bottom + 3] = bytes((0x20, 0x10, 0x08)) planes = build_bitplanes(bytes(frame)) expected = [0] * PWM_BITS values = (0xFF, 0x80, 0x40, 0x20, 0x10, 0x08) for component, value in enumerate(values): mapped = map_channel(value, 100) >> 4 for plane in range(PWM_BITS): if mapped & (1 << plane): expected[plane] |= 1 << component assert [planes[bitplane_index(0, plane, 0)] for plane in range(PWM_BITS)] == expected assert all(word & ~0x3F == 0 for word in planes) def test_white_frame_sets_all_six_color_bits_in_all_seven_planes(): planes = build_bitplanes(bytes([0xFF]) * (WIDTH * HEIGHT * 3)) assert len(planes) == SCAN_ROWS * PWM_BITS * WIDTH assert set(planes) == {0x3F} def test_bitplane_model_rejects_wrong_frame_size_and_coordinates(): with pytest.raises(ValueError, match="64x64"): build_bitplanes(b"bad") with pytest.raises(ValueError, match="out of range"): bitplane_index(0, PWM_BITS, 0) @pytest.mark.parametrize("refresh_rate", sorted(REFRESH_RATE_LIMITS)) def test_native_driver_accepts_only_documented_refresh_rates(refresh_rate): validate_driver_options(MatrixDriverOptions(limit_refresh_rate_hz=refresh_rate)) def test_native_driver_rejects_geometry_pwm_mapping_and_unknown_rate_changes(): for options in ( MatrixDriverOptions(rows=32), MatrixDriverOptions(cols=128), MatrixDriverOptions(scan_rows=16), MatrixDriverOptions(row_address_bits=4), MatrixDriverOptions(pwm_bits=6), MatrixDriverOptions(hardware_mapping="other"), MatrixDriverOptions(limit_refresh_rate_hz=70), ): with pytest.raises(ValueError): validate_driver_options(options) def test_cie_brightness_is_monotonic_and_pwm4_weights_are_fixed(): mapped = [map_channel(255, brightness) for brightness in range(1, 101)] assert mapped == sorted(mapped) assert mapped[-1] == 2047 for brightness in range(1, 101): for plane in range(PWM_BITS): ticks, remainder = oe_ticks_for_plane(10_250_000, brightness, plane, 999) assert ticks == 1 << (plane + 4) assert remainder == 0 def test_brightness_changes_bitplanes_instead_of_temporally_dithering_oe(): white = bytes([0xFF]) * (WIDTH * HEIGHT * 3) low = build_bitplanes(white, brightness=20) high = build_bitplanes(white, brightness=100) assert low != high assert set(high) == {0x3F} @pytest.mark.parametrize("arguments", [ (0, 1, 0, 0), (5_000_000, 0, 0, 0), (5_000_000, 1, PWM_BITS, 0), (5_000_000, 1, 0, -1), ]) def test_pwm4_fixed_point_rejects_invalid_inputs(arguments): with pytest.raises(ValueError): oe_ticks_for_plane(*arguments)