Table of Contents

The GEM Pass-Through Bridge

A pass-through sits between a fab host and a tool that already exists, and adds to it. The fab host connects to the pass-through instead of to the tool, sees one GEM interface, and is not changed. The tool is not changed either: nothing is installed on it and nothing is asked of its vendor.

What the pass-through adds are variables, events, alarms and traces the original tool cannot produce — a value read from a sensor the tool does not know about, an event derived from a signal on an MQTT topic, an alarm the tool has no ALID for. Everything the tool does report is relayed untouched, in both directions, including the host's configuration of reports, events, alarms and traces.

The whole of the configuration is one project, and the one thing in it you must set by hand is the range of ids the pass-through owns. Everything else follows from that range.

1. The two halves

A pass-through is two GEM interfaces, and in TransSECS they are two tabs of the same project.

Tab What it is Connection
BridgeHost The host half. It dials the existing tool and the bridge drives it from here. It needs no ids of its own: its model is derived from the PassThroughTool, less the side-channel range, so only the tool half is configured. dials the tool, usually port 5010
PassThroughTool The tool the fab host connects to. The bridge runs inside it. listens on 41825

They are one project on purpose. The bridge holds direct object references to both controllers rather than talking to itself over a socket, so both must be in one JVM — and in a deployment only the PassThroughTool has a main. Its startup trigger, the StartBridge server, starts BridgeHost and then the bridge.

Two consequences of the single project that catch people out:

needs: PassThroughTool starts BridgeHost itself, so BridgeHost must not also be running in its

  own tab.
* **Devices are live in the editor whether or not you press Run.** The StartBridge trigger
  therefore ticks while you are simply looking at the tab. It checks whether the tool interface is
  actually running and says so at debug level rather than connecting to the real tool behind your
  back.

2. Side-channel ids: the one thing you must configure

The bridge has to know, for every id, whether it belongs to the existing tool or to the pass-through. That single fact decides everything it does:

For a tool-owned id For a pass-through (“side channel”) id
An S1F3 or S1F4 from the fab host is answered from the tool Answered here
The host's report definitions (S2F33/F35) are projected down to the tool Kept here
An S2F23 trace is split, and the tool's part requested from the tool Sampled here
An event arriving from the tool is re-fired upward to the fab host Fired here in the first place
An S5F3 alarm enable is projected down Kept here

It is declared in StartBridge, in the project, with one call:

bridge.setSideChannelIds("100000-");
Form Means
“100000-” 100000 and up
“100000-199999” that range, inclusive
“100000” that one id
“5000-5999,100000-” several, separated by commas

setSideChannelVids, setSideChannelCeids and setSideChannelAlids override it for one kind of id and default to it, so a single range is normally all you write. VIDs, CEIDs and ALIDs are separate id spaces: an ALID 311 and a CEID 311 are unrelated.

There is deliberately one list, and it is this one — the ids the pass-through owns. Everything else the PassThroughTool knows is the existing tool's. The earlier design kept two lists (the characterization said what the tool owned, the project said what it knew) and nothing kept them in step: a PassThroughTool that was a subset of the real tool left the BridgeHost with a list that was wrong. Deriving both from one range removed that, and it is also why a tool that cannot be characterized needs no characterization and no model file at all.

<WRAP center round important 60%> Leave the range undeclared and the bridge falls back to the characterization on disk for ownership. An empty or stale BridgeHostModel.json then makes every ownership test false: nothing projects, nothing refreshes, nothing re-fires, and the bridge runs while doing none of its job — with no error to say so. Declare the range even when the tool below has been characterized. </WRAP>

Why a range and not, say, a device binding? Because a device binding is not a statement about ownership. A side-channel variable may have no device at all (a script writes it), and a tool-owned variable may be given a device for local simulation. A range says what you mean, and it reads at a glance in a review.

3. The workflow

  1. Open the sample project. It ships as Projects/PassThrough — two tabs, a bare

PassThroughTool, an MQTT side channel as the worked example, and the StartBridge trigger. Copy

  it to a new folder before you start work.
- **BridgeHost tab: point it at the tool.** Set ''HostName'' and the port, then **Run**.
- **Characterize — if you can, and it is worth it.** The host asks the tool what it has (S1F11,
  S1F21, S1F23, S2F29, S5F5, S6F17 per event) and writes ''BridgeHostModel.json'' into the project
  folder. This is **not** a precondition for the bridge: see the box below. Its value is that the
  next step imports it, which saves typing every id by hand, and that the bridge then reports
  where the file and the project disagree.
- **Stop BridgeHost, then Build it.** If you characterized, do it //before// you build: the build
  also produces ''BridgeHostDigitalTwin'', which carries the model. The PassThroughTool deployment
  needs BridgeHost's jar either way — so BridgeHost is always built first.
- **Switch to the PassThroughTool tab.** That stops BridgeHost.
- **Populate PassThroughTool** with what the tool reports — see section 4.
- **Add what this pass-through contributes** — see section 5.
- **Set the side-channel range** in StartBridge to match the ids you just added.
- **Build, then Run.** The trigger starts BridgeHost from the classes its build left behind,
  applies the range, and bridges the two. The fab host now connects to 41825.
- **Characterize the fab host against the pass-through**, exactly as if it were the tool.

<WRAP center round tip 60%> The characterization is optional, and the range is what makes it so. With SIDE_CHANNEL set in StartBridge, ownership comes from the range and BridgeHost's model is derived from the PassThroughTool, so the bridge starts with no BridgeHostModel.json at all and only the tool half is ever configured. A characterization that does exist is then used for two things: the import in section 4, and a cross-check — the bridge logs an id the file has and this project does not (one the tool below reports and the pass-through cannot carry), and an id this project has outside the range that the file does not. A host model that was characterized is kept rather than overwritten by the derived one, because it carries real value types.

The one case that needs the file is an undeclared range: then it is the only statement of ownership there is, and StartBridge defers rather than start a bridge that would do nothing. </WRAP>

The fab host sees the existing tool's ids plus the side channel, with no way to tell which is which — which is the point.

4. Populating the PassThroughTool

The PassThroughTool must know every id the fab host may ask about: the existing tool's as well as its own. That is what the fab host characterizes. The BridgeHost needs no ids of its own — its model is derived from the PassThroughTool minus the side-channel range.

There are three ways to get the tool's ids in, and they can be mixed.

4.1 Import the characterization (the normal path)

File → Import Tool Model (JSON), and choose BridgeHostModel.json.

Every variable, event and alarm the tool reported is added, or updated in place if it is already there. Importing again after re-characterizing updates rather than duplicates, and leaves your own additions alone — so a tool that gains ids in a vendor upgrade is picked up by re-characterizing and re-importing.

An import deliberately leaves alone what a model cannot know:

older models carry wrong codes.

control-state group).

4.2 By hand

Right-click the VIDs, CEIDs or ALIDs node and add what you need. Reasonable for a handful of ids; for a tool with two hundred, use an import and then edit.

4.3 Import UsingScript — a vendor's own listing

Tools often come with a listing instead of a live interface: the console output of the vendor's own svidgen-style utility, a spreadsheet export, a report. One vendor's report format is one customer's file, so the reader for it is not part of TransSECS. It is delivered as a jar and a script, and TransSECS loads it.

If you have been given a jar and a .js:

  1. Put the jar in the Builder's addons folder, MIStudioSuite/TransSECS/Builder/addons (the

project's own addons folder works too).

  1. Restart TransSECS. Jars are read when it starts; one dropped in while it is running is not

seen.

  1. Open the project and select the tab of the tool to import into. The import writes into the

tab in front of you.

  1. File → Import UsingScript, and choose the .js.
  2. The script then asks for the listing itself. Choose it, and the ids are written into the

equipment tree: status variables, equipment constants, alarms, events, and a data variable for

  each event whose text the tool substitutes at run time.

The same rules apply as to a JSON import: a second import of the same listing updates rather than adds, and the names TransSECS owns are not renamed by a file. Rows the parse did not understand are printed, not dropped — read the console, or wrapper.log, after an import. A listing that is quietly missing ids is the failure this path exists to make visible.

The reference implementation is GemIdListingImport.jar with ImportSvidListing.js, which reads a plain-text svidgen listing. Writing a reader for another format means subclassing ParseGemIdList and supplying parse(File) — the half that turns what was read into tree nodes without duplicating or renaming is already in TransSECS, and needs no editor, project or licence to test.

5. Adding to the tool: what the side channel can and cannot do

5.1 What it can do

To add Do this
A variable the tool does not have Add the tag to the side-channel device, create the VID with an id in the side-channel range, and set its Device and Device Tag.
A variable a script computes Create the VID as above with no device, and have a script write it.
An event when a variable changes Create the CEID (id in the range), then set the variable's CEID field to it. The variable fires it on every change.
An event on a condition Create the CEID, and trigger it from a script or from a boolean device tag on the CEID row.
An alarm Create the ALID (id in the range) and bind a boolean device tag to it: true sets the alarm, false clears it. Set CEID and Clear CEID make each emit an event as well.
An alarm a script decides Create the ALID, and set it from a script.
A trace over added variables Nothing to do. An S2F23 naming tool and side-channel variables together is split: the tool's part is requested from the tool, the rest sampled here.

The variable's CEID field is the “change event”, and it is the most useful thing here: no wiring, no script. Two details decide whether it fires:

change. -1 means no deadband.

limits-monitored (zone transitions, S2F45/F47) and no longer fires its CEID on change. You get

  one or the other.
* **Non-numeric variables** (a string, a boolean) fire on any change of value. This worked only
  from September 2026; before that a non-numeric variable with a CEID attached could never fire
  it.

5.2 Scripts: where they go

A script belongs on the device tag, in the Devices tree — not on the GEM id. It runs when a value arrives at that tag, with the arriving value in scope as incomingValue, and it can read and write anything else in the project:

// script on the Tank2LotPresent side-channel tag
if (incomingValue.getQuality() == 0) {                       // 0 is good quality
  var TransSecsController = Java.type("com.ergotech.transsecs.secs.TransSecsController");
  var gem = TransSecsController.findController("PassThroughTool").getGemHandler();
 
  var temp = gem.getServerForName("VID.Tank2Temp").getFloatValue();
 
  // true sets the alarm and sends S5F1, false clears it
  gem.getServerForName("ALID.ColdBathWithLot").setBoolValue(temp < 90 && incomingValue.getBoolValue());
 
  // and/or fire an event
  gem.getServerForName("CEID.LotEnteredTank2").setIntValue(1);
}

matter.

raw reading.

from a bean's own last value rather than remembered in the script.

Edge triggering applies to a CEID driven this way. With transsecs.ceidedgetrigger=true — the default in every shipped configuration — a trigger only fires on the transition to true; writing 1 over 1 is dropped (logged at debug). That is what you want for a level from a PLC and a nuisance when you mean “send it again”: for the latter, the event has to be sent forced, through GemHandler.sendEventReport(ceid, dvvals, true).

5.3 What it cannot do

Not possible Detail
A script on an SVID or DVID The Script tab appears for an ECID only, and addSVID/addDVID discard a script even if one is in the file. So a value arriving from the tool cannot run a script of its own.
A script on a CEID or an ALID Disabled in the editor, and addCEID/addALID pass an empty script at registration.
The Condition and Condition Value fields on a variable They are saved in the project and printed in the generated SECS manual, and nothing reads them at run time. Ignore them.
A derived event combining two tool values Neither value can run a script when it arrives (above), so there is nothing to combine them in. Put the logic on a side-channel tag that one of them is mirrored to, or poll from a timed trigger.

The runtime is capable of all of these — every GEM id is a scriptable bean and the script hook is already on the path the bridge delivers on — but the registration path only applies a script to an ECID. If a derived-from-the-tool event is what a job needs, that is a small, contained product change, not a workaround to be invented per project.

6. Deployment

Set the deployment type to Pass-Through Tool. The build then puts BridgeHost, the bridge and the characterization into PassThroughToolDeployment, so the folder runs on its own:

cd PassThroughToolDeployment && sh run.sh

It needs GEMBridge.jar, which the GEMBridge module installs into MIStudio/jars. That jar is referenced only by the pass-through build, so an ordinary tool project neither needs nor sees it.

While the existing tool is unreachable the bridge closes the fab host's port rather than answering for a tool it cannot reach, and reopens it when the tool comes back. A fab host that prefers a connected-but-silent pass-through can turn that off with bridge.setStopListeningWhileToolDown(false) in StartBridge.

7. Troubleshooting

Symptom Cause
Bridge startup deferred: this script declares no side channel and there is no BridgeHostModel.json yet Set SIDE_CHANNEL in StartBridge, which is the better answer, or characterize the tool with BridgeHost. The trigger retries, so either clears by itself — the order of starting is not fixed.
No deploy.BridgeHost.EquipmentController BridgeHost has not been built, or was built before PassThroughTool with a TransSECS older than 2026-10-23, whose build deleted the other tab's classes.
Bridge startup waiting: PassThroughTool is not running Normal in the editor with the tab open and not running.
The bridge starts but nothing reaches the fab host The side-channel range is not declared and the characterization is empty, so nothing is tool-owned. See section 2.
An event fires on the tool and the fab host never sees it Either the fab host has not enabled it (the bridge projects exactly what the host asks for — bridge.ensureEnabled(ceid) subscribes on the bridge's own account), or edge triggering dropped a repeat.
Trying to send invalid message in ATTEMPT_ONLINE state during characterization A pre-2026-09-25 build, in which a failed S1F1 left the interface stuck in ATTEMPT_ONLINE and refusing every outgoing primary.
An added variable's event never fires It has LimitMin/LimitMax set, which makes it limits-monitored instead.

8. The sample project

Projects/PassThrough is the two tabs, ready to work in:

four tags wired to VIDs 100001-100004, and DemoECID at 100005, as the worked example of what

  a pass-through adds.
* **BridgeHost** — pointed at ''localhost:5010''.
* **StartBridge.js** — the readable copy of the startup trigger. The one that runs is the script
  property of the StartBridge server inside ''PassThroughTool.tsx''; **edit them together**.
* **PlasmaEtcherModel.json** — a characterization of a plasma etcher that does not exist: 56
  variables, 23 events, 12 alarms. Give it to a digital twin and you have a tool to bridge without
  needing a real one. ''README.md'' in the project folder says how.

Written 2026-10-24 (J Redman with Claude Opus 5).