Add custom error View for `CameraConnectionError`
What changed, and why it matters
This commit adds a friendly, specific error screen for when the SeedSigner device cannot talk to its camera. Previously, camera connection failures fell through to a generic 'unhandled exception' screen. The change reroutes that specific error to a new screen that tells the user to check the camera cable. It is a user-experience and diagnostic improvement, not a security fix.
No security action required. Treat as a normal quality/user-experience commit. If reviewing for release notes, note improved camera-error messaging.
Security signals we found
No memory-safety, cryptographic, authentication, or authorization changes
Error-handling path only; no input parsing or trust-boundary crossing
No CVE, advisory, or vendor security disclosure referenced in commit
Diff is purely UX/diagnostic: friendlier message and clearer rerouting
Evidence from the diff
The patch introduces CameraConnectionErrorView in src/seedsigner/views/view.py and modifies UnhandledExceptionView.__post_init__ to detect when the first element of the captured error list equals CameraConnectionError.__name__, redirecting to the new view. The new view renders an ErrorScreen with a hardware-error title, camera-specific headline and advice, then returns to MainMenuView. Test mocks are narrowed from the whole seedsigner.hardware.camera module to seedsigner.hardware.camera.Camera, and screenshot generator configs are updated to exercise both the new view and a non-camera unhandled exception.
Changed components
src/seedsigner/views/view.pytests/base.pytests/screenshot_generator/generator.pyInspect captured patch +34 / −4
diff --git a/src/seedsigner/views/view.py b/src/seedsigner/views/view.py
index 5197b86..63d09d2 100644
--- a/src/seedsigner/views/view.py
+++ b/src/seedsigner/views/view.py
@@ -362,6 +362,20 @@ class NetworkMismatchErrorView(ErrorView):
class UnhandledExceptionView(View):
error: list[str]
+ def __post_init__(self):
+ from seedsigner.hardware.camera import CameraConnectionError
+ super().__post_init__()
+
+ # Camera errors bubble up to here. Reroute to their custom error View.
+ if self.error[0] == CameraConnectionError.__name__:
+ self.set_redirect(
+ Destination(
+ CameraConnectionErrorView,
+ skip_current_view=True,
+ )
+ )
+
+
def run(self):
self.run_screen(
ErrorScreen,
@@ -376,6 +390,21 @@ class UnhandledExceptionView(View):
+@dataclass
+class CameraConnectionErrorView(View):
+ def run(self):
+ self.run_screen(
+ ErrorScreen,
+ title=_("Hardware Error"),
+ status_headline=_("Cannot access camera"),
+ text=_("Disconnect power and check for a loose camera connection."),
+ button_data=[ButtonOption("Back to Main Menu")],
+ show_back_button=False,
+ )
+
+ return Destination(MainMenuView, clear_history=True)
+
+
@dataclass
class OptionDisabledView(View):
UPDATE_SETTING = ButtonOption("Update setting")
diff --git a/tests/base.py b/tests/base.py
index e89174c..eaf4693 100644
--- a/tests/base.py
+++ b/tests/base.py
@@ -10,7 +10,7 @@ sys.modules['seedsigner.gui.screens.screensaver'] = MagicMock()
sys.modules['seedsigner.gui.toast'] = MagicMock()
sys.modules['seedsigner.views.screensaver'] = MagicMock()
sys.modules['seedsigner.hardware.buttons'] = MagicMock()
-sys.modules['seedsigner.hardware.camera'] = MagicMock()
+sys.modules['seedsigner.hardware.camera.Camera'] = MagicMock()
sys.modules['seedsigner.hardware.st7789_mpy'] = MagicMock()
sys.modules['seedsigner.hardware.ili9341'] = MagicMock()
diff --git a/tests/screenshot_generator/generator.py b/tests/screenshot_generator/generator.py
index 2d3bbaf..5ab56d3 100644
--- a/tests/screenshot_generator/generator.py
+++ b/tests/screenshot_generator/generator.py
@@ -20,7 +20,7 @@ sys.modules['seedsigner.hardware.displays.ili9341'] = MagicMock()
sys.modules['seedsigner.views.screensaver.ScreensaverScreen'] = MagicMock()
sys.modules['RPi'] = MagicMock()
sys.modules['RPi.GPIO'] = MagicMock()
-sys.modules['seedsigner.hardware.camera'] = MagicMock()
+sys.modules['seedsigner.hardware.camera.Camera'] = MagicMock()
sys.modules['seedsigner.hardware.microsd'] = MagicMock()
from seedsigner.controller import Controller
@@ -42,7 +42,7 @@ from seedsigner.models.settings_definition import SettingsConstants, SettingsDef
from seedsigner.views import (MainMenuView, PowerOptionsView, RestartView, NotYetImplementedView, UnhandledExceptionView,
psbt_views, seed_views, settings_views, tools_views, scan_views)
from seedsigner.views.screensaver import OpeningSplashView
-from seedsigner.views.view import NetworkMismatchErrorView, OptionDisabledView, PowerOffView
+from seedsigner.views.view import CameraConnectionErrorView, NetworkMismatchErrorView, OptionDisabledView, PowerOffView
from .utils import ScreenshotComplete, ScreenshotConfig, ScreenshotRenderer
@@ -414,7 +414,8 @@ def generate_screenshots(locale):
],
"Misc Error Views": [
ScreenshotConfig(NotYetImplementedView),
- ScreenshotConfig(UnhandledExceptionView, dict(error=["CameraConnectionError", "camera.py, 44, in start_video_stream_mode", "Camera error. Check camera connection."])),
+ ScreenshotConfig(UnhandledExceptionView, dict(error=["IndexError", "line 1, in some_buggy_code.py", "list index out of range"])),
+ ScreenshotConfig(CameraConnectionErrorView),
ScreenshotConfig(NetworkMismatchErrorView, dict(derivation_path="m/84'/1'/0'")),
ScreenshotConfig(OptionDisabledView, dict(settings_attr=SettingsConstants.SETTING__MESSAGE_SIGNING)),
ScreenshotConfig(scan_views.ScanInvalidQRTypeView)
Why this scored 19/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.