You built an inventory screen with a scan button and a couple of barcodes, and every scan writes the first tag in your script. The cause is structural: the barcode-scanned event is generic, so every scan from every view calls the same handler, and a handler with a hard-coded tag path writes that tag for every code it receives. This guide routes that one event to many tags. You will attach a context value to each scan-barcode action, branch on it in a single handler, map scanned part numbers to tag paths, write from a submit action, and prove the wiring with a four-scan test. The same pattern runs a scan-to-login page and a cycle-count page on one screen.
Why every scan lands in the first tag
The barcode-scanned event your runtime hands you is generic by design: the scanner knows what it read, not why it was read. Every scan-barcode action on every view fires the same event, and the event arguments carry the decoded text plus one context object you control. A handler that ignores the context and writes [default]Inventory/Qty_BRG_6205 writes that tag for a bearing, a seal, or an operator badge alike. That is the whole bug, and it is why adding barcodes changed nothing.
Routing has two axes, and each needs its own key:
- Which purpose fired the scan — answered by the context value you set on the action, such as
loginorcycleCount. - Which exact tag the value belongs in — answered by the decoded text through a part-number-to-tag-path map.
Branch on the context for the source, and on the value for the destination inside that branch. If your screen only ever shows one scanning view at a time, route barcode scan data by view with a session property covers that variant; this article covers several scan purposes sharing one event.
| Scan purpose | Context value | Handler branch | Where the write lands |
|---|---|---|---|
| Operator login | login |
write the badge to the operator tag | [default]Inventory/OperatorID |
| Cycle count | cycleCount |
look up the part, open the count view | [default]Inventory/Qty_<part> |
| Goods receipt | receipt |
same lookup, sign chosen in the view | [default]Inventory/Qty_<part> |
receipt is a name you choose: contexts are your strings, not fixed vocabulary.
What you need
- One scan-barcode action per button, each with its context value filled in.
- One barcode-scanned event handler for the whole project — keep it to exactly one.
- A part-number-to-tag-path map with one entry per SKU. Barcode part number parameter lookup: PLC memory or PC database works through where that map should live, and verifying scanned barcodes against a PLC lookup table reuses the same data to catch wrong-bin scans.
- A count-entry view: a label bound to the part parameter, a numeric input, a submit button.
- The operator identity in a per-user session property, not a shared tag — barcode scanner session property per user, not a shared tag is the failure you avoid by doing so.
Route the scan in seven steps
-
Name the contexts first. One word per purpose:
login,cycleCount,receipt. The branch structure of your handler comes straight from this list, so write it before any script. -
Set the context on each scan-barcode action. On the login view the action carries
login; on the inventory view it carriescycleCount. An action without a context is a scan you cannot route, so give every one a value. - Branch before anything else runs. The first statement of the single barcode-scanned handler reads the context member and dispatches; no tag write sits ahead of that branch.
- Map part numbers to tag paths in one place. A dictionary lookup beats a chain of if-statements: 24 entries, one place to add SKU 25, one place to audit.
- Open the count view with the part number as a parameter. The scan event knows the code, not the quantity; the view collects the count from the operator and shows what was scanned.
- Write the tag from the submit button. Submit is the first moment that holds all three values — part, count, operator — so the write belongs there and nowhere else.
- Guard the edges. An unknown part pops a message and writes nothing; a repeat of the same code inside 1.5 s is swallowed as a duplicate.
The whole handler, with the test that proves it
# Illustrative handler. context is the event argument the runtime passes;
# "login" and "cycleCount" are the context strings set on the actions.
# Replace write_tag and open_view with your runtime's own calls.
PART_TO_TAG = { # dict[str, str]: part number -> tag path
"BRG-6205": "[default]Inventory/Qty_BRG_6205",
"SEAL-14": "[default]Inventory/Qty_SEAL_14",
}
route = context["context"] # str: "login" or "cycleCount"
code = str(decoded_text) # str: the decoded text from the event
if route == "login":
write_tag("[default]Inventory/OperatorID", code)
elif route == "cycleCount":
target = PART_TO_TAG.get(code) # str, or None when unknown
if target is None:
show_message("Unknown part: " + code) # str shown to the operator
else:
open_view("countEntry", part=code, target=target)
The two classic commissioning failure modes both show in one instrumented scan. Print route and target at the top of the handler, scan once, and read them back: comparing against the whole context object instead of its member sends every scan down the default path, and a lookup that quietly falls back to a hard-coded tag reproduces the original symptom.
# Test: scan "BRG-6205" with the action context set to "cycleCount".
# Pass: the count view opens showing part "BRG-6205"; entering 7 and
# submitting writes 7 to Qty_BRG_6205; OperatorID and every other
# Qty_ tag keep their previous values.
Write on submit, not on scan
At the instant the event fires you hold exactly two things: the decoded text and the context. The quantity arrives seconds later from a person, so a write inside the scan event either stores the wrong kind of value or stores nothing useful. The submit action is the first code that runs with part, count and operator in scope, which makes it the only honest place for the write — and it is where a fan-out to count, timestamp and operator belongs; setting several HMI tags from one button event walks through that pattern on the HMI side.
If the destination is a PLC tag rather than an HMI tag, the routing logic does not change, but the consumer does. A controller can read and clear your string within one scan cycle, so latch the barcode string on the pulse and handshake the reset with the program that consumes it.
Worked example: 24 SKUs, two contexts
Run the closet as built: 24 labelled bins, 24 entries in PART_TO_TAG, contexts login and cycleCount. A full count pass reads 24 codes in 96 s, which is 96 ÷ 24 = 4.0 s per scan at a steady desk pace. Set the duplicate guard to 1,500 ms: 1.5 s ÷ 4.0 s = 0.375, so the guard covers 37.5 percent of the average scan interval — enough to catch a doubled trigger from an inherited event, whose UI-side cause is covered in scan barcode action fires twice: fix inherited container events — while never swallowing the next genuine scan. One unknown code in the pass yields one popup and zero writes.
The decision, stated plainly: a 24-entry map in one dataset, the guard at 1,500 ms, one write per scan, and every routed scan logged with its context value and destination path.
When the tag is right but the part is wrong
Routing cannot fix a scan that decoded the wrong label. If two labels sit inside the field of view, the reader returns one string — whichever code it locked first — and the map faithfully routes bad data into a valid tag. Hold the image to one label before you blame the branch.
Worked numbers for a presentation station: a 1,440-pixel-wide decode image over a 120 mm field of view gives 1,440 ÷ 120 = 12 px/mm. A 0.25 mm narrow bar, typical of warehouse-grade Code 128 labels, then spans 0.25 × 12 = 3 px — the working minimum for dependable decode once blur takes its share. Invert the formula for the limit: 1,440 × 0.25 ÷ 3 = 120 mm is the widest field of view that keeps a 0.25 mm bar at 3 px.
If the shelf can present two labels 80 mm apart, 120 mm sees both, so tighten to 100 mm — 1,440 ÷ 100 = 14.4 px/mm, bar at 3.6 px — or fence the presentation so one label faces the reader. The camera resolution calculator runs this arithmetic for your own reader, and when the station genuinely must read several codes at once, read multiple QR codes in one image and pick the right one extends the same routing idea to selection by position. Torn, tilted or glary labels are an image problem, not a routing one: 1D barcode preprocessing for rotation, blur and glare is the next stop.
Hardware for a fixed scan station
When the closet grows into a station, swap the hand scanner for a reader that produces one event per presentation. For an overhead shelf pass, a smart camera with built-in illumination keeps the light moving with the lens:
- Interface
- GigE
- Resolution
- Up to 3 MP
A compact vision sensor does the same job at a presentation point where the part faces the reader:
- Interface
- Fast Ethernet
- Resolution
- Up to 3 MP
- Sensor format
- 1/2.9"
- Shutter
- Global
Decode happens on the device for both, over GigE or Fast Ethernet, so the station pushes one finished string per scan into your handler — exactly one event per presentation.
Add an LED illuminator to a fixed reader and drive it from one of the light controllers rather than a bench supply, so the light fires only during the exposure window; camera strobe output timing: exposure pulse and image timestamp sets that timing. A compact driver for that job:
- Output
- Constant voltage 24 V
- Channels
- 4 channels
- Control
- Digital
Treat any strobed illuminator as a bright source: mount it off the operator's line of sight, and let the low duty cycle keep it cool — a strobe that fires only during exposures is easier on eyes and wiring than a lamp left burning.
At a station a cell controller starts, trigger the decode from a PLC tag instead of a UI button keeps the event source singular, which is what the context branch expects. For power and trigger wiring at the station:
- Type
- Power and I/O cables
- Length
- 6-8 m
- Type
- Smart Camera Power and I/O Cables
The drag-chain lead is an M8 8-pin power and I/O cable, 8-core, 7 m; the second is a 17-pin M12 power cable with open leads, 7 m. Browse the vision sensor finder by interface and mount, or build the station in the configurator.
Four tests before you call it done
-
Login scan. From the login view, scan badge
OP-42. Pass:OperatorIDreadsOP-42, and everyQty_tag is unchanged. -
Known part. From the count view, scan
BRG-6205, enter 7, submit. Pass:Qty_BRG_6205reads 7 andOperatorIDis unchanged. -
Unknown part. Scan
UNLISTED-0000, a code absent fromPART_TO_TAG. Pass: the popup appears and no tag changes anywhere. -
Double scan. Scan
SEAL-14twice inside 1.5 s. Pass: one count view opens and one write lands; the guard eats the second read.
Go-live checklist
- Exactly one barcode-scanned handler, and the context branch is its first statement.
- Every scan-barcode action carries a context — no orphan actions anywhere in the project.
-
PART_TO_TAGlives in one dataset; adding a SKU adds one entry and touches no script. - Unknown codes write nothing and tell the operator why.
- Writes happen on submit; PLC-consumed strings are latched on a pulse.
- Duplicate guard at 1,500 ms; every routed scan logged with context value, decoded code, destination path and timestamp — barcode scan timestamps to track part cycle time per workstation turns that log into throughput data.
- Field of view sized to one label using the resolution arithmetic above.
Questions
Do I need a separate barcode-scanned event for each view?
No. The event is generic by design: one handler receives every scan. Configure a context string on each scan-barcode action — login, cycle count — and branch on it as the first statement of that single handler. Add a context when you add a scan purpose, not a new handler. A second handler earns its place only when a different physical device feeds the same screen, because the decoded text then arrives through different event arguments and merging them costs more than one branch.
Should I branch on the barcode value or on the context?
Both, in that order. The context answers why the scan happened, so the same printed code lands in an operator tag at login and in a quantity tag at the shelf. The value then picks the exact tag through your part-number-to-tag-path map. Branching on value alone fails the day an operator badge number collides with a part number, or the same carton is scanned for two purposes in one shift.
How many tags can one handler route to?
More than your inventory will ever hold. The map is a dictionary lookup, so 20 entries and 200 entries cost the same per scan. The practical limit is the tag provider, not the routing: keep one scan to one write, and push any fan-out — count, timestamp, operator — onto the submit action where the values already exist. A few hundred SKUs on one handler is routine.
Why does the first tag still receive every scan after I add context?
Two usual causes. First, the comparison reads the whole context object instead of its member, so context["context"] == "login" matches while context == "login" never does. Second, the branch runs but every path writes the same tag because the mapping was never consulted — a default write sits after the if-chain instead of inside an else. Print the context value and the resolved tag path for one scan and both faults show immediately.
Can the same routing write PLC tags instead of HMI tags?
Yes. The routing does not change — context picks the branch, the map picks the path — but a PLC consumer can read and clear your string within one scan cycle. Latch the written value on a rising edge and handshake the reset with the consuming program. Write the count as a number, keep the part number as its own string tag, and never pack both into one string you must parse apart downstream.




