System designTroubleshooting

USB camera timeout on reopen: stop acquisition before you close

Why a USB3 camera hits a timeout when you reopen it, the acquisition state behind it, the stop-and-wait close that fixes it, and the host-side numbers.

LumaSpec Vision10 min read Share on LinkedIn

On this page 11 sections
  1. 01Symptom: first open works, second open times out
  2. 02Most likely cause: the camera is still acquiring
  3. 03The close sequence: stop, poll to idle, drain, close
  4. 04Second cause: feature writes while acquisition runs
  5. 05Third cause: usbfs memory on the embedded host
  6. 06Worked example: two 12 MP cameras at 30 fps on one ARM host
  7. 07Fourth cause: hubs, power and cable length
  8. 08Prevention: open once, trigger many, close once
  9. 09When the usual fixes do not apply
  10. 10Checklist before the next run
  11. 11Questions

A USB3 camera that runs once and then times out on every reopen is rarely a bad camera. The failure almost always starts in the previous session: acquisition was started and never stopped, the camera reports itself busy, and the next open waits for an idle device until the SDK raises its timeout error. This page is for integrators running triggered, single-frame capture loops in Python on Linux, including embedded ARM hosts. You get the test that separates a stuck acquisition state from a host or bandwidth problem, the stop-and-wait close sequence, the usbfs and bandwidth arithmetic for a two-camera rig, and a lifecycle pattern that removes the reopen path entirely.

Symptom: first open works, second open times out

The pattern is distinctive. After a fresh boot the first session opens, configures and captures normally. Somewhere in the next open-close cycle the camera stops cooperating: the open call raises a timeout from inside the SDK (code -12 in the report that matches this setup), or the first frame wait comes back with an incomplete-frame error (code -19). Rebooting the host or unplugging the USB cable restores one or two good runs, and then the failure returns.

That ordering is the diagnosis. Hardware faults and bandwidth limits misbehave from the first frame. A fault that appears only after a close points at state left behind by the close, and a loop that opens and closes the camera on every trigger event exercises exactly that path.

If your camera times out even on the first open after a clean boot, work through the USB camera timeout diagnosis across host ports first, because the host, not the session, is then the suspect.

Most likely cause: the camera is still acquiring

A GenICam camera runs an acquisition engine independent of your session: AcquisitionMode set to Continuous, started with AcquisitionStart, stopped only by AcquisitionStop. Closing your handle does not remove power — USB keeps the camera powered while the host is up — so the engine's state outlives the session. The next open then finds a camera that reports acquisition active, refuses feature writes and stalls until the SDK gives up.

Capture-loop code makes this near-certain when it re-enters the camera context on every pass and runs the full setup — AcquisitionMode, ExposureTime, AcquisitionFrameRate, TriggerSelector, TriggerMode — inside it. From the second pass on, that setup lands on an engine still running from the first.

The one-line test settles it: after a failure, reopen the camera, read the AcquisitionStatus feature and print it. True while your program has not started acquisition means a stuck engine, and the close sequence below is the fix. False means move on to the host-side causes.

The close sequence: stop, poll to idle, drain, close

Never close a busy engine. The deterministic close is:

import time

def close_camera(cam, poll_s=0.02, max_wait_s=2.0):
    # Stop the engine if it is still running.
    if cam.get_feature_by_name("AcquisitionStatus").get():
        cam.get_feature_by_name("AcquisitionStop").run()
        waited = 0.0
        while cam.get_feature_by_name("AcquisitionStatus").get():
            time.sleep(poll_s)
            waited += poll_s
            if waited >= max_wait_s:
                raise RuntimeError("acquisition not idle before close")
    # Drain queued frames here, then release the handle.
    cam.close()

Two numbers make it robust. Poll every 20 ms; faster gains nothing and slower wastes close time. Budget two frame periods before declaring the session wedged: 2 s at the 1 fps this class of rig runs, 66 ms at 30 fps. A camera that has not reached idle within that budget needs a power cycle, not another open attempt stacked on a running engine.

Drain the queue before the final close so no frame is left marked busy; the stop with busy buffers walkthrough covers the flush step by step.

Second cause: feature writes while acquisition runs

GenICam marks most acquisition features read-only while the engine runs. Writing ExposureTime, AcquisitionFrameRate or the trigger selector mid-stream either raises or queues behind the running engine, and a stalled write inside the open path surfaces as the same timeout. Configure once, before AcquisitionStart; after start, touch nothing but the trigger and the frame data. The 40 ms exposure and 1 fps frame rate in the failing loop are set-once values — nothing in a single-shot capture needs to rewrite them per frame.

If a node refuses writes for another reason, a read-only access state rather than a running engine, the GenICam node access state checklist tells the two apart.

Third cause: usbfs memory on the embedded host

Linux caps the memory usbfs may reserve for USB buffers at 16 MB by default. One 12 MB mono frame plus queue overhead sits at that ceiling, so additional frames arrive incomplete or the wait times out. Raise the limit at boot to twice the worst-case demand: frames in flight per camera × frame size × cameras, then double it. Two cameras holding five 12 MB mono frames each need about 120 MB, so a 1000 MB limit covers them with room for color or higher resolution later.

The report's host had already been raised to 6000 MB — fifty times that demand — so with usbfs ruled out, the residual random timeouts had to come from the engine state or the link, not the buffer pool. Raising it again cannot help. The full tuning sequence, including the exposure interaction, is in the USB3 frame rate and usbfs memory fix.

Worked example: two 12 MP cameras at 30 fps on one ARM host

Inputs, with their sources. Frame size 4000 × 3000 pixels and a 30 fps ceiling: the printed output of a camera such as the LS-CMR-0166. Assume 8-bit mono on the wire, the usual format for this capture style; 24-bit color triples every number below. Camera count 2 on one USB3 controller through an active hub: the rig in the report. Controller budget about 400 MB/s of payload: USB3 runs 5 Gbit/s on the wire, 8b/10b encoding leaves 4 Gbit/s (500 MB/s) of raw capacity, and protocol overhead takes roughly a fifth.

Area scan cameras12 MP USB3 area scan camera, IMX226, 1/1.7" rolling shutter, 30 fps, 4000 × 3000 resolution (LS-CMR-0166)LS-CMR-0166
Sensor format
1/1.7"
Interface
USB3
Resolution
12 MP
Shutter
Rolling
$560.00View partBuild a system with it

The arithmetic, one step at a time:

frame_bytes = 4000 × 3000 × 1 = 12,000,000 bytes ≈ 12 MB per_camera = 12 MB × 30 fps = 360 MB/s total = 360 × 2 = 720 MB/s headroom = 720 / 400 = 1.8, i.e. 80% over budget

Decision, per case. Above about 350 MB/s planned per controller, split the cameras across two controllers or cut the rate: 400 MB/s ÷ 12 MB ≈ 33 fps total, or 16 fps per camera at this frame size. The report's rig ran 1 fps: 12 MB × 1 × 2 = 24 MB/s, about 6% of the budget, so its random incomplete frames were not bandwidth. At 1 fps, look at the engine state and hub power first; at 30 fps, look at the controller budget first.

Fourth cause: hubs, power and cable length

An active hub shares one upstream link between the cameras — the budget above — and must also feed both ports from its own supply. A USB3 port delivers up to 900 mA at 5 V, about 4.5 W per camera; a hub that cannot hold that on both ports produces the connect-disconnect churn that shows up as random timeouts. Keep passive cables to 3 m and use active cables beyond that.

The isolation test takes five minutes: one camera per controller port, a short certified cable, an hour-long run. If the incomplete frames disappear, the hub was the cause. If the timeout on reopen persists with idle cameras, the engine state is still the problem. Where the fix is a host with dedicated ports, a controller built for camera work closes it:

Vision controllersHikrobot MV-VC2140-128G40-1T VC2100 Vision Controller, N97, 8GB+128G SSD+ 1THDDMV-VC2140-128G40-1T
LAN ports
3-4 LAN ports
USB 3 ports
2 USB 3 ports
Light outputs
4 built-in channels
Memory
8 GB
$1,679.00View partBuild a system with it

Prevention: open once, trigger many, close once

Restructure the loop so the reopen path disappears:

  1. At process start: open the camera once, write AcquisitionMode Continuous, ExposureTime, frame rate and trigger settings once, then send AcquisitionStart.
  2. Per shot: issue the software trigger, wait for the frame with a per-frame timeout, save it, return to idle.
  3. At shutdown: drain the queue, send AcquisitionStop, poll AcquisitionStatus to false, close.

Size the frame wait from the settings: 40 ms exposure + 1000 ms frame period = 1040 ms of worst-case latency, so a 1.5-3 s wait covers it with margin. The 3000 ms default in the failing loop was never the problem; the reopen loop around it was. The one wait call per frame pattern keeps the wait honest, and closing a stream without ending the app shows the long-lived-session shape for services that idle between batches.

Frame size scales every buffer in the chain — usbfs demand, queue memory, recovery time. A 6 MP mono frame is about 6 MB, half the worked example above, which is why compact sensors are the default for single-shot rigs. For that build:

Area scan camerasHikrobot MV-CT0MU0060/R/NN/NN 6 MP Area Scan Camera, 3rd Gen., USB3.0, Mono, BASEMV-CT0MU0060/R/NN/NN
Sensor format
1/1.8"
Lens mount
C-mount
Interface
USB3
Resolution
6-10 MP
$969.00View partBuild a system with it

When the usual fixes do not apply

Three fixes circulate for this failure, and all three miss it.

  • Raise usbfs memory again. Past about twice your buffer demand, more changes nothing; the report's host already ran 6000 MB against roughly 120 MB of demand.
  • Sleep after close. A fixed sleep is the non-deterministic version of polling AcquisitionStatus; on a loaded host it fails exactly when the restart matters. Poll the flag instead.
  • Redesign around async callbacks. The report tried it and traded an open timeout for a handler that died after a minute: a stalled engine starves callbacks as surely as it starves synchronous waits. Fix the state machine first, then redesign.

A timeout on every restart even from a verified idle state points at the transport rather than the engine; the stream restart timeout diagnosis walks that path.

Checklist before the next run

Run down these before restarting the capture service:

  • Engine state: AcquisitionStatus read at every open and close; AcquisitionStop sent before close; poll to false with a two-frame-period budget; queue drained first.
  • Features: everything written once before AcquisitionStart; after start, only the trigger.
  • Frame wait: at least exposure + one frame period, plus half again as margin.
  • usbfs: limit ≥ 2 × frames in flight × frame size × cameras, confirmed raised from the 16 MB default.
  • Bandwidth: planned payload per controller ≤ 350 MB/s, about 29 fps of 12 MB frames.
  • Physical: one camera per controller port where possible; hub rated 900 mA per port; passive cable ≤ 3 m.
  • Logs: the status read at open and close in every session's log, so one run tells you whether the engine, the host or the link failed.

When the software is settled, the hardware decisions are plain: USB3 interface, honest frame-rate budget, and a host with enough ports. Browse the USB3 area scan cameras, filter interface and resolution in the camera finder, or step into the camera configurator, and pair the cameras with vision controllers when the host side is what you are replacing. Send your frame size, rate and trigger plan through the contact page and we will check the budget with you before you order. Related fixes across the stack sit on the systems topic page.

Questions

Why does the camera work after a reboot and then time out again?

USB keeps the camera powered for as long as the host is up, so the acquisition engine's state survives your application exiting or the handle closing. A reboot removes USB power entirely and clears that state, which is why the first run after a reboot succeeds. Stopping acquisition before every close removes the need to reboot at all.

How long should AcquisitionStop take to return the camera to idle?

Within about one frame period. At the 1 fps this class of rig runs, poll every 20 ms for up to 2 s; at 30 fps the same budget is 66 ms. If AcquisitionStatus has not cleared within two frame periods, the session is wedged: log the status, release the handle, and recover with a USB power cycle rather than reopening on top of a running engine.

How much usbfs memory do two USB3 cameras actually need?

Take frames in flight per camera × frame size × camera count, then double the result for headroom. Five frames in flight at 12 MB on two cameras works out to roughly 120 MB of demand, so even a 1000 MB limit leaves room for color or higher resolution later. The Linux default of 16 MB fails a single 12 MP camera; 6000 MB is already generous and raising it further fixes nothing.

Does the SDK's context manager stop acquisition when it closes?

It releases your handle, and it is not required to stop the acquisition engine. Some camera firmware keeps the last acquisition state after close, which is exactly the state the next open trips over. Treat the context manager as bookkeeping and send AcquisitionStop, poll AcquisitionStatus to false, and drain queued frames yourself before leaving the block.

What frame-wait timeout should a software-triggered single-shot loop use?

At least exposure plus one frame period, plus half again for margin. For the 40 ms exposure and 1 fps rig in this guide: 40 + 1000 = 1040 ms worst case, so a 1.5-3 s wait passes. A wait shorter than exposure plus period times out on every legitimate frame and looks identical to the stuck-engine failure, so size it before you debug.