Skip to content

Workaround for cheap misreporting webcam#339

Open
blast-hardcheese wants to merge 1 commit into
mainsail-crew:v5from
blast-hardcheese:cheap-camera-workaround
Open

Workaround for cheap misreporting webcam#339
blast-hardcheese wants to merge 1 commit into
mainsail-crew:v5from
blast-hardcheese:cheap-camera-workaround

Conversation

@blast-hardcheese
Copy link
Copy Markdown

It looks like the camera advertises that it has a menu but then that component is just empty.

Adding this additional constraint fixed my webcam, but it's not obvious to the reader why this is the case.

The traceback I was chasing down is as follows:

[06/05/26 23:54:35] INFO: Detect available Devices
Traceback (most recent call last):
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "/home/klipper/crowsnest/crowsnest/__main__.py", line 8, in <module>
    asyncio.run(main())
  File "/usr/lib/python3.11/asyncio/runners.py", line 190, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/asyncio/base_events.py", line 653, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "/home/klipper/crowsnest/crowsnest/crowsnest.py", line 195, in main
    logging_helper.log_cams()
  File "/home/klipper/crowsnest/crowsnest/logging_helper.py", line 114, in log_cams
    uvc = camera.camera_manager.init_camera_type(camera.UVC)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/klipper/crowsnest/crowsnest/camera/camera_manager.py", line 22, in init_camera_type
    cams = obj.init_camera_type()
           ^^^^^^^^^^^^^^^^^^^^^^
  File "/home/klipper/crowsnest/crowsnest/camera/types/uvc.py", line 108, in init_camera_type
    return [
           ^
  File "/home/klipper/crowsnest/crowsnest/camera/types/uvc.py", line 109, in <listcomp>
    UVC(dev_path, other=other_paths)
  File "/home/klipper/crowsnest/crowsnest/camera/types/uvc.py", line 33, in __init__
    parsed_qc = v4l2.ctl.parse_qc_of_path(self.path, qc)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/klipper/crowsnest/crowsnest/v4l2/ctl.py", line 69, in parse_qc_of_path
    controls = parse_qc(fd, qc)
               ^^^^^^^^^^^^^^^^
  File "/home/klipper/crowsnest/crowsnest/v4l2/ctl.py", line 46, in parse_qc
    for menu in utils.ioctl_iter(
  File "/home/klipper/crowsnest/crowsnest/v4l2/utils.py", line 35, in ioctl_iter
    for i in range(start, stop, step):
             ^^^^^^^^^^^^^^^^^^^^^^^^
ValueError: range() arg 3 must not be zero

I'm open to a more structural change, but I'm new to Klipper and this was preventing me from getting going; I don't know what would be more appropriate than this quick patch.

Thank you for crowsnest, it's wonderful to see video showing up in Mainsail for the first time 🙂

It looks like the camera advertises that it has a menu but then that component is just empty.

Adding this additional constraint fixed my webcam, but it's not obvious to the reader why this is the case.

The traceback I was chasing down is as follows:

    [06/05/26 23:54:35] INFO: Detect available Devices
    Traceback (most recent call last):
      File "<frozen runpy>", line 198, in _run_module_as_main
      File "<frozen runpy>", line 88, in _run_code
      File "/home/klipper/crowsnest/crowsnest/__main__.py", line 8, in <module>
        asyncio.run(main())
      File "/usr/lib/python3.11/asyncio/runners.py", line 190, in run
        return runner.run(main)
               ^^^^^^^^^^^^^^^^
      File "/usr/lib/python3.11/asyncio/runners.py", line 118, in run
        return self._loop.run_until_complete(task)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/usr/lib/python3.11/asyncio/base_events.py", line 653, in run_until_complete
        return future.result()
               ^^^^^^^^^^^^^^^
      File "/home/klipper/crowsnest/crowsnest/crowsnest.py", line 195, in main
        logging_helper.log_cams()
      File "/home/klipper/crowsnest/crowsnest/logging_helper.py", line 114, in log_cams
        uvc = camera.camera_manager.init_camera_type(camera.UVC)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/home/klipper/crowsnest/crowsnest/camera/camera_manager.py", line 22, in init_camera_type
        cams = obj.init_camera_type()
               ^^^^^^^^^^^^^^^^^^^^^^
      File "/home/klipper/crowsnest/crowsnest/camera/types/uvc.py", line 108, in init_camera_type
        return [
               ^
      File "/home/klipper/crowsnest/crowsnest/camera/types/uvc.py", line 109, in <listcomp>
        UVC(dev_path, other=other_paths)
      File "/home/klipper/crowsnest/crowsnest/camera/types/uvc.py", line 33, in __init__
        parsed_qc = v4l2.ctl.parse_qc_of_path(self.path, qc)
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/home/klipper/crowsnest/crowsnest/v4l2/ctl.py", line 69, in parse_qc_of_path
        controls = parse_qc(fd, qc)
                   ^^^^^^^^^^^^^^^^
      File "/home/klipper/crowsnest/crowsnest/v4l2/ctl.py", line 46, in parse_qc
        for menu in utils.ioctl_iter(
      File "/home/klipper/crowsnest/crowsnest/v4l2/utils.py", line 35, in ioctl_iter
        for i in range(start, stop, step):
                 ^^^^^^^^^^^^^^^^^^^^^^^^
    ValueError: range() arg 3 must not be zero
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 7, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

The PR refines menu control detection in parse_qc by enforcing qc.minimum > 0 as an additional requirement alongside the existing control-type checks (menu/integer-menu).

Changes

Menu Control Condition Guard

Layer / File(s) Summary
Menu Control Condition
crowsnest/v4l2/ctl.py
parse_qc conditional for menu control mapping now requires qc.minimum > 0 in addition to control-type validation.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~5 minutes

Poem

🐰 A whisker of logic, a tweak so small,
Minimum values now guard them all,
Menu controls must pass the test—
Stricter conditions bring code rest!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Workaround for cheap misreporting webcam' directly describes the main change - adding a workaround for a webcam that misreports its menu control capabilities.
Description check ✅ Passed The description is clearly related to the changeset, explaining the camera issue, providing the traceback that motivated the fix, and justifying the code change.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crowsnest/v4l2/ctl.py (1)

41-53: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

The qc.minimum > 0 guard silently drops valid menu controls and doesn't fix the root cause.

The crash reported in the commit message (ValueError: range() arg 3 must not be zero) occurs because qc.step can be zero for menu-type controls. When utils.ioctl_iter calls range(start, stop, step) with a zero step, it fails. The current guard at line 44 masks this by skipping controls with minimum <= 0, but V4L2 allows menu controls to have any minimum value (including 0, which is common). This approach silently loses menu data on affected devices instead of handling the root cause.

A more robust fix checks that the bounds are valid and forces a non-zero iteration step:

Suggested patch
-    if qc.type in (
-        constants.V4L2_CTRL_TYPE_MENU,
-        constants.V4L2_CTRL_TYPE_INTEGER_MENU,
-    ) and qc.minimum > 0:
+    if qc.type in (
+        constants.V4L2_CTRL_TYPE_MENU,
+        constants.V4L2_CTRL_TYPE_INTEGER_MENU,
+    ) and qc.maximum >= qc.minimum:
         controls["menu"] = {}
+        menu_step = qc.step if qc.step and qc.step > 0 else 1
         for menu in utils.ioctl_iter(
             fd,
             raw.VIDIOC_QUERYMENU,
             raw.v4l2_querymenu(id=qc.id),
             qc.minimum,
             qc.maximum + 1,
-            qc.step,
+            menu_step,
             True,
         ):

This allows menu controls with minimum == 0 to be processed while preventing the range step-zero crash.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crowsnest/v4l2/ctl.py` around lines 41 - 53, The current guard "qc.minimum >
0" silently skips valid menu controls; instead remove that guard and fix the
root cause by ensuring ioctl_iter is invoked with a non-zero, valid step and
sane bounds: in the controls["menu"] block where utils.ioctl_iter(...) is called
with arguments (fd, raw.VIDIOC_QUERYMENU, raw.v4l2_querymenu(id=qc.id),
qc.minimum, qc.maximum + 1, qc.step, True), compute a safe_step = qc.step if
qc.step and qc.step != 0 else 1 and use safe_step, and ensure the start/stop
passed (qc.minimum and qc.maximum + 1) form a valid range (e.g., only call
ioctl_iter when qc.maximum + 1 > qc.minimum); keep references to qc,
utils.ioctl_iter, raw.v4l2_querymenu and controls["menu"] so the change is easy
to find.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@crowsnest/v4l2/ctl.py`:
- Around line 41-53: The current guard "qc.minimum > 0" silently skips valid
menu controls; instead remove that guard and fix the root cause by ensuring
ioctl_iter is invoked with a non-zero, valid step and sane bounds: in the
controls["menu"] block where utils.ioctl_iter(...) is called with arguments (fd,
raw.VIDIOC_QUERYMENU, raw.v4l2_querymenu(id=qc.id), qc.minimum, qc.maximum + 1,
qc.step, True), compute a safe_step = qc.step if qc.step and qc.step != 0 else 1
and use safe_step, and ensure the start/stop passed (qc.minimum and qc.maximum +
1) form a valid range (e.g., only call ioctl_iter when qc.maximum + 1 >
qc.minimum); keep references to qc, utils.ioctl_iter, raw.v4l2_querymenu and
controls["menu"] so the change is easy to find.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c5fe3cee-a42b-4f43-84a0-617185354e66

📥 Commits

Reviewing files that changed from the base of the PR and between 0d233f0 and db0a172.

📒 Files selected for processing (1)
  • crowsnest/v4l2/ctl.py

@mryel00
Copy link
Copy Markdown
Member

mryel00 commented May 7, 2026

Hi, first of all thank you for the PR.
Such edge cases are impossible to catch during testing sadly. Who would have thought that they can advertise such a thing 😅
The Code Rabbit made already a good first step into the correct direction, but I don't know if that change actually makes sense.

Can you revert your change locally and change it like this?

         constants.V4L2_CTRL_TYPE_MENU,
         constants.V4L2_CTRL_TYPE_INTEGER_MENU,
     ):
-        controls["menu"] = {}
+        controls["menu"] = {"min": qc.minimum, "max": qc.maximum + 1, "step": qc.step}
+        return controls
         for menu in utils.ioctl_iter(
             fd,
             raw.VIDIOC_QUERYMENU,

Then we should see the problematic values inside the menu in your log and can check if the Code Rabbit solution actually makes sense.
Currently you should already see the qc.mininum and qc.maximum values printed in the log but the step value is the actual problem.

@mryel00
Copy link
Copy Markdown
Member

mryel00 commented May 21, 2026

Hey, any updates on this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants