Code readingHow-to

Same barcode scanned twice: handle repeat scans with a timeout

The same barcode scanned twice disappears into duplicate filtering. Time the repeat window from line speed and part pitch, then accept only real repeat scans.

LumaSpec Vision11 min read Share on LinkedIn

On this page 9 sections
  1. 01Why does the same barcode scan only once?
  2. 02Should you fix the reader or your own code?
  3. 03How long should the repeat-scan timeout be?
  4. 04What do you need before you start?
  5. 05How do you write the last-value timeout check?
  6. 06How do you verify the check without stopping the line?
  7. 07Which mistakes cause lost scans or double inserts?
  8. 08Which fixed code readers deliver every decode to your program?
  9. 09Questions

A fixed reader that sees the same code twice is not misreading — on most lines the repeat is exactly what the process means: the same tote returns, one recipe code sits on ten identical parts, a jam parks a single label under the optics and fires decodes in a burst. Then a filter, in the reader or in the queue that receives the scan, quietly deletes the repeats, and counts and audit trails come up short. This guide shows where the second scan goes, how to size a repeat-scan timeout from belt speed and part pitch, and how to write the last-value check that accepts a genuine repeat and drops a double trigger in about ten lines.

Why does the same barcode scan only once?

Two filters can swallow the second read of an identical code: the reader's same-symbol transmit control, which holds the repeat until the code leaves and re-enters the field, and a uniqueness check in the receiving queue; a two-second bench test tells you which one is eating your scans.

The reader-side control is real and documented: many scanners ship with a same-symbol transmit setting — one widely used manual spells it "Infinite Same Symbol" — that passes a code once, then stays silent until the symbol leaves and re-enters the read field. At a packing bench that stops the double beep. On a line where the same label is meant to read again, it deletes data before your program ever sees it.

The client-side filter is quieter: a queue or table that treats the scanned string as a unique key accepts the first insert and drops every later one, no matter how much time passed between them. If your duplicates come from polling that reads the same row twice, that is a different fault with its own fix — the event-over-polling change in Stop duplicate barcode scans in a batch table: event over polling.

Bench test to find which filter holds your scans: read one code, wait about 2 s, read it again without letting it leave the field. A second decode that only arrives after you lifted the code out and back points at the reader; no second decode at all points at your receiving code.

Should you fix the reader or your own code?

Fix it in your own code and leave the reader transmitting every decode, because only your software knows the numbers that matter — a window derived from pitch and belt speed — while the reader's duplicate control is a fixed step that deletes the event before anything can log it.

On a conveyor the answer is software for three reasons. The window you need is a physical time — pitch divided by belt speed — and the reader has no belt-speed input. The reader's duplicate control moves in fixed steps that cannot be tuned to your pitch. And suppression deletes the event, so you lose the record of how often it happened.

Keep the reader in a mode that transmits every decode — free-run, or triggered by a photoelectric sensor so each part opens a fresh read window — and debounce where the scan arrives. The one place the reader-side control is the right tool is a handheld used point-and-scan by an operator: there, the same-symbol setting in the set-up menu of handheld code readers is the documented way to stop nuisance repeats.

How long should the repeat-scan timeout be?

Set the window at three quarters of the gap between legitimate repeat reads: the gap is part pitch divided by belt speed, so 0.6 m of pitch at 0.3 m/s gives a 2.0 s gap and a 1,500 ms window.

gap = pitch / belt speed window = 0.75 × gap

The quarter you shaved off is the margin, and it has a job: speed the belt up by a third to 0.4 m/s and the gap shrinks to 0.6 m ÷ 0.4 m/s = 1.5 s, which still meets the window, because a scan at exactly the window passes. Ordinary speed jitter of ±20% moves the gap between 1.67 s and 2.50 s, and the window must stay under the short end.

What moves the number: belt speed, which the window follows as 1/speed, and pitch, which it follows in proportion. Double the belt speed and you halve the window, so recalculate after every speed or pitch change.

For a handheld with no belt, derive the window from the fastest unintended repeat you can produce: fire the trigger ten times as fast as you can, take the shortest gap between decodes, and set the window at twice that. On a packing bench that lands near 2 s; where operators legitimately re-scan the same tote within seconds, use 5 s and log the ignored events.

What do you need before you start?

You need three things: the decoded string, the arrival time in milliseconds, and the scanner ID whenever more than one reader feeds the same queue — with those recorded, a last-value check against the derived window is about ten lines at the receive point.

  • A reader sending every decode: free-run, or triggered by a photoelectric sensor so each part opens a fresh read window.
  • A state home that survives between scans — client variables, or a PLC string buffer plus a millisecond counter; the register layouts in Storing ASCII barcode strings in PLC memory: bytes, words and clears map directly.
  • A monotonic millisecond clock at the receive point, not a wall-clock stamp that a midnight rollover or an NTP correction can rewind.
  • The window from the section above, in milliseconds — the worked line's value for TIMEOUT_MS, or 2,000 ms for a handheld bench.

How do you write the last-value timeout check?

Store the last accepted value and its arrival time, then accept a new decode only when the value differs or the gap reaches the window — with the worked window, the same code twice inside 1.5 s lands as one scan and the repeat after 1.5 s passes.

  1. Declare the state where the scan event runs: last_value (string, starts empty), last_time_ms (integer, starts 0) and TIMEOUT_MS (integer, the worked line's 1500). Keep one pair per scanner when several readers share the handler.
  2. On each decode, read the monotonic clock in milliseconds into now_ms — the same clock discipline that keeps a serial read-abort honest in Abort a serial barcode read when the scanner reads nothing.
  3. Compare: accept when value differs from last_value, or when now_ms - last_time_ms reaches TIMEOUT_MS; otherwise ignore the scan and add one to a duplicate counter.
  4. On accept, forward the scan to your queue, database or PLC, then set last_value = value and last_time_ms = now_ms.
  5. Leave the window editable per job: a recipe change that alters pitch changes the window, and a hard-coded constant hides that.

The names are illustrative; wire them to your runtime's own scan event and clock call.

last_value = {}    # scanner id -> last accepted string, starts empty
last_time_ms = {}  # scanner id -> last accepted time in ms, starts 0
TIMEOUT_MS = 1500  # from pitch / belt speed, see the worked example

def on_decode(scanner_id, value, now_ms):
    prev   = last_value.get(scanner_id, "")
    prev_t = last_time_ms.get(scanner_id, 0)
    if value == prev and (now_ms - prev_t) < TIMEOUT_MS:
        return "ignored"  # same code inside the window
    queue_insert(scanner_id, value, now_ms)  # your write
    last_value[scanner_id] = value
    last_time_ms[scanner_id] = now_ms
    return "accepted"

How do you verify the check without stopping the line?

Replay five decodes of one code at 500 ms intervals and require exactly one accepted and four ignored, then a sixth scan 3,000 ms after the first — twice the window — must pass; run that sequence on the bench before the line sees the change.

Run the replay through the real handler, not a test double: any split other than one accepted and four ignored means the comparison or the clock is wrong, and the usual culprit is a wall-clock stamp that jumped.

On the line, watch the duplicate counter during a jam. Parts parked under the optics fire a burst of identical decodes; the first is accepted, the rest are ignored, and the counter records how often it happened. Then confirm the change cost nothing in responsiveness: an accepted scan still raises the PLC flag in the same instant, which is the scan-to-flag latency measured in Barcode scanner to PLC response time: cut serial scan-to-flag latency.

Boundary check: a repeat that lands at exactly the window passes, because the code ignores only gaps shorter than the window. If your process needs strictly longer than the window, change the comparison to a strict inequality and say so in the comment.

Which mistakes cause lost scans or double inserts?

The failures sit on either side of the window: a timeout above the physical gap swallows a real part — anything over 2.0 s on the 0.6 m, 0.3 m/s line — while one shared last-value pair for two readers drops the second reader's first, legitimate read of every part.

  • Wall-clock timestamps across a midnight, a DST change or an NTP correction: elapsed time goes negative and every repeat passes. Use a monotonic counter.
  • One shared last-value pair for all scanners: reader B's first read of a part is suppressed by reader A's earlier read. Key the state on scanner ID.
  • A window guessed instead of derived: after a speed change from 0.3 m/s to 0.4 m/s, yesterday's window sits exactly on the new 1.5 s gap. Recalculate.
  • Inserting on the decode-valid pulse while the string arrives one scan later: the pulse and the payload race, and the fix in Barcode string disappears after one PLC scan: latch it on the pulse comes before any dedup logic.
  • Dedup on one path and handshake-copy on another: if the new-read handshake in Copy barcode data to a string only on a new read handshake feeds one consumer while the raw event feeds this check, one path repeats what the other filtered. Pick one receive point.

Which fixed code readers deliver every decode to your program?

Any reader that reports one decode event per trigger works with this check, so choose on interface, resolution and working distance rather than on duplicate handling: the readers below cover Fast Ethernet, GigE and USB, from a 60-120 mm bench position to 12 MP logistics frames.

The readers below all deliver a decode event your handler can timestamp:

Fixed code readersHikrobot MV-ID813M-05S-WBN-SR 1.3 MP resolution, 4.9 mm focal length, White light, SR, GigE, Static cableMV-ID813M-05S-WBN-SR
Interface
GigE
Resolution
Up to 3 MP
$719.00View part
Fixed code readersCompact fixed industrial barcode reader, 1.2 MP, USB 2.0, 60-120 mm reading distance (LS-CRD-0047)LS-CRD-0047
Resolution
Up to 3 MP
Shutter
Global
$559.00View part
Fixed code readersHikrobot MV-ID5120RM-08M-RBN Industrial Code ReaderMV-ID5120RM-08M-RBN
Interface
GigE
Resolution
12 MP
$1,969.00View part

For a Fast Ethernet line, the MV-ID3013PM-12M-WBN is a compact 1.3 MP head with a 12 mm focal length and a global shutter.

Fixed code readersHikrobot MV-ID3013PM-12M-WBN 12mm focal length 1.3 MP Compact Industrial Code ReaderMV-ID3013PM-12M-WBN
Interface
Fast Ethernet
Resolution
Up to 3 MP
Shutter
Global
Lens mount
M12
$1,979.00View part

Short-range USB points belong to the LS-CRD-0047, with a 60-120 mm reading distance and 1.2 MP; 12 MP frames over GigE are the MV-ID5120RM-08M-RBN's territory when codes sit in a larger field. Trigger wiring, mounting brackets and light boards for either live with the code reader accessories, and the full range sits in the fixed code readers category, filterable by interface and resolution in the camera finder.

Set the window from your own pitch and speed — three quarters of the gap is the starting rule — wire the check at the receive point, and prove it with the five-decode replay before the shift starts. More decode-side fixes sit on the code-reading topic page; if you want a second pair of eyes on your pitch and speed numbers, the free expert support that comes with every order will check the gap arithmetic against your line data.

Build the whole station

The configurator matches a camera, lens, light, light controller and cables to each other, step by step, and lists only parts that fit what you already picked.

Start with the camera

If the station also needs triggering parts or a stand, add them at the extras step of the same configurator.

Questions

What repeat-scan window suits a handheld scanner at a packing bench?

Start at 2,000 ms. That covers the fastest unintended repeat on a bench: a bounced trigger or a second squeeze lands well inside one second, so 2,000 ms separates nuisance doubles from a deliberate re-scan. If operators legitimately re-scan the same tote every few seconds to confirm placement, raise the window to 5,000 ms and log the ignored events so nothing is silently lost.

How does the check work when two scanners read the same part?

Key the memory on scanner ID as well as value: last_value and last_time_ms exist per reader, so scanner B's first read of a part is never suppressed by scanner A's earlier read of the same code. Cross-reader duplicates, where both readers report one part twice, are a separate fault; give each reader its own handler or add the ID to the queue row.

Should an ignored duplicate still reach the database?

Yes, as a flagged row. Write the same fields — scanner ID, value, timestamp — with a duplicate flag, and the audit trail survives a swallowed beep or a dropped event. The flag also gives you the real inter-arrival times of repeats, which is the data you need to re-tune the window after a line-speed change.

The reader itself never sends a second decode of the same code — what now?

Open the reader's configuration and look for the duplicate-read or same-symbol transmit control, then set it to send every read. If the parameter is fixed or missing, add a photoelectric sensor as a trigger so each part opens a fresh read window, or lift the code out of and back into the field to force a re-transmit before blaming your software.

Does the window delay an accepted scan from reaching the PLC?

No. The window only gates acceptance; a scan that passes the value-or-timeout test is forwarded in the same handler call, so nothing waits for the window to expire. Only identical codes inside the window are held, and holding them is the point. If scan-to-flag latency still matters on your serial link, the checks that cut it live in the transport layer, not in this dedup step.