test: add bit board and OV5642_ID type to `tests/test_camera.py` (#715)
What changed, and why it matters
This commit adds support for a new hardware board called 'bit' and a new camera sensor identifier (OV5642_ID) to the Krux project. It is almost entirely test code and board-specific configuration, with no apparent security relevance. The only production code change is adding camera sensor thresholds and mapping the new sensor to existing brightness configuration logic, plus a minor change to default threshold handling for one sensor.
No security action required. This is a routine hardware-support and test-coverage commit.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit introduces OV5642_ID in src/krux/camera.py, adds LUM_TH entries for it, maps it to the existing _config_ovxx40_lum method, and changes the GC2145 luminance default from (0, 0) to (0x20, 0xF2) while removing a bounds check. The rest of the diff is test infrastructure: adding a board_bit mock fixture, including ‘bit’ in parametrized device tests, adding a snapshot test for bit-specific lens/rotation correction, and adding a stop_sensor test. No security-sensitive logic such as cryptography, key handling, memory safety, authentication, or input validation is modified.
Changed components
src/krux/camera.pytests/conftest.pytests/pages/test_flash_tools.pytests/shared_mocks.pytests/test_camera.pytests/test_power.pyInspect captured patch +152 / −21
diff --git a/src/krux/camera.py b/src/krux/camera.py
index 8f61f47..840b88e 100644
--- a/src/krux/camera.py
+++ b/src/krux/camera.py
@@ -53,6 +53,11 @@ LUM_TH = {
(OV2640_ID, ENTROPY_MODE): (0x68, 0x78),
(OV2640_ID, BINARY_GRID_MODE): (0x44, 0x48),
(OV2640_ID, ZOOMED_MODE): (0x35, 0x50),
+ (OV5642_ID, QR_SCAN_MODE): (0x60, 0x70),
+ (OV5642_ID, ANTI_GLARE_MODE): (0x20, 0x28),
+ (OV5642_ID, ENTROPY_MODE): (0x68, 0x78),
+ (OV5642_ID, BINARY_GRID_MODE): (0x44, 0x48),
+ (OV5642_ID, ZOOMED_MODE): (0x35, 0x50),
(OV7740_ID, QR_SCAN_MODE): (0x60, 0x70),
(OV7740_ID, ANTI_GLARE_MODE): (0x20, 0x28),
(OV7740_ID, ENTROPY_MODE): (0x68, 0x78),
@@ -178,6 +183,7 @@ class Camera:
GC0328_ID: self._config_gc0328_lum,
OV2640_ID: self._config_ovxx40_lum,
OV7740_ID: self._config_ovxx40_lum, # Same as OV2640
+ OV5642_ID: self._config_ovxx40_lum, # Same as OV2640
GC2145_ID: self._config_gc2145_lum,
}
@@ -217,11 +223,7 @@ class Camera:
def _config_gc2145_lum(self):
key = (self.cam_id, self.mode)
- thresholds = LUM_TH.get(key, (0, 0)) # Default to (0, 0) if key not found
- low, high = thresholds
-
- if low < 0x10 or high > 0xF0:
- return
+ low, high = LUM_TH.get(key, (0x20, 0xF2)) # Default to (0, 0) if key not found
# Set register bank 1
sensor.__write_reg(0xFE, 0x01)
diff --git a/tests/conftest.py b/tests/conftest.py
index 9f0f7c9..2b01749 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -8,6 +8,7 @@ from .shared_mocks import (
board_m5stickv,
board_wonder_mv,
board_yahboom,
+ board_bit,
encode_to_string,
encode,
statvfs,
@@ -153,6 +154,17 @@ def wonder_mv(monkeypatch, mp_modules):
reset_krux_modules()
-@pytest.fixture(params=["amigo", "m5stickv", "dock", "cube", "yahboom", "wonder_mv"])
+@pytest.fixture
+def bit(monkeypatch, mp_modules):
+ import sys
+
+ monkeypatch.setitem(sys.modules, "board", board_bit())
+ monkeypatch.setitem(sys.modules, "pmu", None)
+ reset_krux_modules()
+
+
+@pytest.fixture(
+ params=["amigo", "m5stickv", "dock", "cube", "yahboom", "wonder_mv", "bit"]
+)
def multiple_devices(request):
return request.getfixturevalue(request.param)
diff --git a/tests/pages/test_flash_tools.py b/tests/pages/test_flash_tools.py
index 41094bc..3054511 100644
--- a/tests/pages/test_flash_tools.py
+++ b/tests/pages/test_flash_tools.py
@@ -89,6 +89,7 @@ def test_tc_flash_hash(multiple_devices, mocker):
"cube": 208,
"yahboom": DOCK_FW_POS,
"wonder_mv": DOCK_FW_POS,
+ "bit": DOCK_FW_POS,
}
users_data_words_positions = {
"amigo": 331,
@@ -97,6 +98,7 @@ def test_tc_flash_hash(multiple_devices, mocker):
"cube": 222,
"yahboom": DOCK_USER_POS,
"wonder_mv": DOCK_USER_POS,
+ "bit": DOCK_USER_POS,
}
fw_words_pos = fw_words_positions[board.config["type"]]
u_data_words_pos = users_data_words_positions[board.config["type"]]
diff --git a/tests/shared_mocks.py b/tests/shared_mocks.py
index 2aa392d..d890215 100644
--- a/tests/shared_mocks.py
+++ b/tests/shared_mocks.py
@@ -630,6 +630,29 @@ def board_wonder_mv():
)
+def board_bit():
+ return mock.MagicMock(
+ config={
+ "type": "bit",
+ "lcd": {"height": 240, "width": 320, "invert": 0, "lcd_type": 0},
+ "sdcard": {"sclk": 27, "mosi": 28, "miso": 26, "cs": 29},
+ "board_info": {
+ "BOOT_KEY": 16,
+ "LED_R": 13,
+ "LED_G": 12,
+ "LED_B": 14,
+ "MIC0_WS": 19,
+ "MIC0_DATA": 20,
+ "MIC0_BCK": 18,
+ },
+ "krux": {
+ "pins": {"BUTTON_A": 22, "BUTTON_B": 21, "BUTTON_C": 16},
+ "display": {"touch": False, "font": [8, 16], "font_wide": [16, 16]},
+ },
+ }
+ )
+
+
def mock_context(mocker):
import board
@@ -775,3 +798,27 @@ def mock_context(mocker):
draw_hcentered_text=mocker.MagicMock(return_value=1),
),
)
+
+ elif board.config["type"] == "bit":
+ return mocker.MagicMock(
+ input=mocker.MagicMock(
+ touch=None,
+ enter_event=mocker.MagicMock(return_value=False),
+ page_event=mocker.MagicMock(return_value=False),
+ page_prev_event=mocker.MagicMock(return_value=False),
+ touch_event=mocker.MagicMock(return_value=False),
+ ),
+ display=mocker.MagicMock(
+ font_width=8,
+ font_height=16,
+ total_lines=20, # 320 / 16
+ width=mocker.MagicMock(return_value=240),
+ height=mocker.MagicMock(return_value=320),
+ usable_width=mocker.MagicMock(return_value=(240 - 2 * 10)),
+ usable_pixels_in_line=mocker.MagicMock(return_value=(240 - 2 * 10)),
+ ascii_chars_per_line=mocker.MagicMock(return_value=(240 - 2 * 10) // 8),
+ to_lines=mocker.MagicMock(return_value=[""]),
+ max_menu_lines=mocker.MagicMock(return_value=9),
+ draw_hcentered_text=mocker.MagicMock(return_value=1),
+ ),
+ )
diff --git a/tests/test_camera.py b/tests/test_camera.py
index aceb4a2..4f04531 100644
--- a/tests/test_camera.py
+++ b/tests/test_camera.py
@@ -13,16 +13,27 @@ def test_init(mocker, m5stickv):
def test_initialize_sensors(mocker, multiple_devices):
import board
import krux
- from krux.camera import Camera, OV7740_ID, OV2640_ID, GC0328_ID, GC2145_ID
+ from krux.camera import (
+ Camera,
+ OV7740_ID,
+ OV5642_ID,
+ OV2640_ID,
+ GC0328_ID,
+ GC2145_ID,
+ )
SENSORS_LIST = [
(OV7740_ID, "config_ov_7740"),
(OV2640_ID, "config_ov_2640"),
+ (OV5642_ID, None),
(GC0328_ID, None),
(GC2145_ID, "config_gc_2145"),
]
+ n = 0
for sensor_id, config_method in SENSORS_LIST:
+ print(f"case {n}")
+ n += 1
mocker.patch("krux.camera.sensor.get_id", lambda: sensor_id)
c = Camera()
if config_method:
@@ -35,24 +46,38 @@ def test_initialize_sensors(mocker, multiple_devices):
krux.camera.sensor.set_vflip.assert_called_with(1)
else:
krux.camera.sensor.set_vflip.assert_not_called()
+
krux.camera.sensor.set_vflip.reset_mock()
- if board.config["type"] == "cube":
+ if board.config["type"] == "cube" or c.cam_id == OV5642_ID:
krux.camera.sensor.set_hmirror.assert_called_with(1)
else:
krux.camera.sensor.set_hmirror.assert_not_called()
- krux.camera.sensor.reset.assert_called()
- krux.camera.sensor.set_pixformat.assert_called()
- assert (
- krux.camera.sensor.set_pixformat.call_args.args[0]._extract_mock_name()
- == "mock.RGB565"
- )
- krux.camera.sensor.set_framesize.assert_called()
- assert (
- krux.camera.sensor.set_framesize.call_args.args[0]._extract_mock_name()
- == "mock.QVGA"
- )
+ krux.camera.sensor.set_hmirror.reset_mock()
+
+ krux.camera.sensor.reset.assert_called()
+ krux.camera.sensor.reset.reset_mock()
+
+ krux.camera.sensor.set_pixformat.assert_called()
+ assert (
+ krux.camera.sensor.set_pixformat.call_args.args[0]._extract_mock_name()
+ == "mock.RGB565"
+ )
+ krux.camera.sensor.set_pixformat.reset_mock()
+
+ krux.camera.sensor.set_framesize.assert_called()
+ if board.config["type"] != "bit":
+ assert (
+ krux.camera.sensor.set_framesize.call_args.args[0]._extract_mock_name()
+ == "mock.QVGA"
+ )
+ else:
+ assert (
+ krux.camera.sensor.set_framesize.call_args.args[0]._extract_mock_name()
+ == "mock.CIF"
+ )
+ krux.camera.sensor.set_framesize.reset_mock()
def test_fail_to_initialize_sensor(mocker, m5stickv):
@@ -113,6 +138,7 @@ def test_toggle_antiglare(mocker, m5stickv):
from krux.camera import (
Camera,
OV7740_ID,
+ OV5642_ID,
OV2640_ID,
GC0328_ID,
GC2145_ID,
@@ -121,7 +147,7 @@ def test_toggle_antiglare(mocker, m5stickv):
ZOOMED_MODE,
)
- SENSORS_LIST = [OV7740_ID, OV2640_ID, GC0328_ID, GC2145_ID]
+ SENSORS_LIST = [OV7740_ID, OV5642_ID, OV2640_ID, GC0328_ID, GC2145_ID]
for sensor_id in SENSORS_LIST:
mocker.patch("krux.camera.sensor.get_id", lambda: sensor_id)
@@ -136,3 +162,45 @@ def test_toggle_antiglare(mocker, m5stickv):
assert c.mode == ZOOMED_MODE
c.toggle_camera_mode()
assert c.mode == QR_SCAN_MODE
+
+
+def test_snapshot(mocker, multiple_devices):
+ import krux
+ import board
+ from krux.camera import Camera
+
+ if board.config["type"] == "bit":
+ image = mocker.MagicMock(
+ lens_corr=mocker.MagicMock(), rotation_corr=mocker.MagicMock()
+ )
+ mock_snapshot = mocker.MagicMock(return_value=image)
+ else:
+ mock_snapshot = mocker.MagicMock()
+
+ mocker.patch("krux.camera.sensor.snapshot", side_effect=mock_snapshot)
+
+ c = Camera()
+ c.initialize_sensor()
+ c.snapshot()
+
+ krux.camera.sensor.snapshot.assert_called()
+
+ if board.config["type"] == "bit":
+ image.lens_corr.assert_called_with(strength=1.1)
+ image.rotation_corr.assert_called_with(z_rotation=180)
+
+
+def test_stop_sensor(mocker, multiple_devices):
+ import krux
+ import board
+ from krux.camera import Camera
+
+ mocker.patch("krux.camera.gc.collect")
+ mocker.patch("krux.camera.sensor.run")
+
+ c = Camera()
+ c.initialize_sensor()
+ c.stop_sensor()
+
+ krux.camera.gc.collect.assert_called()
+ krux.camera.sensor.run.assert_called_with(0)
diff --git a/tests/test_power.py b/tests/test_power.py
index 7645886..1f70981 100644
--- a/tests/test_power.py
+++ b/tests/test_power.py
@@ -15,7 +15,7 @@ def test_pmu(mocker, multiple_devices):
manager = PowerManager()
- if board.config["type"] in ("dock", "yahboom", "wonder_mv"):
+ if board.config["type"] in ("dock", "yahboom", "wonder_mv", "bit"):
assert manager.pmu is None
assert manager.has_battery() is False
else:
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.