====== 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: * **Only the tab in front runs.** Switching tabs stops the other interface. That is what this 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. 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. 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 ===== - **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. **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. 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: * **Event descriptions.** A characterization has no event text. * **Alarm codes (ALCD).** Characterization recorded 6 for every alarm until September 2026, so older models carry wrong codes. * **Alarm names.** A model's alarm name is its ALTX. * **The types of the variables TransSECS defines itself** (''MDLN'', ''SOFTREV'', the 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'': - Put the jar in the Builder's addons folder, ''MIStudioSuite/TransSECS/Builder/addons'' (the project's own ''addons'' folder works too). - **Restart TransSECS.** Jars are read when it starts; one dropped in while it is running is not seen. - Open the project and **select the tab of the tool to import into**. The import writes into the tab in front of you. - **File → Import UsingScript**, and choose the ''.js''. - 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: * **Numeric variables** honour the **Deadband** — a change smaller than the deadband is not a change. ''-1'' means no deadband. * **Setting LimitMin/LimitMax takes the change event away.** A variable with limits becomes 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); } * GEM ids are reached by name: ''VID.'', ''CEID.'', ''ALID.''. Case does not matter. * Other beans are reached with the arrow: ''/Devices/Group/Bean->getIntValue()''. * ''output = ...'' in a tag script replaces the value on its way through — useful for scaling a raw reading. * A script-scope variable does **not** survive to the next execution, so an edge has to be read 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: * **PassThroughTool** — the variables every GEM tool has, plus an MQTT ''SideChannel'' device with 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).