Ignition TCP polling follows two different paths. A read begins at a Gateway-side scheduler, crosses the device instance and TCP client, reaches the custom server as a Get request, and returns through the parser and data-exposure layer. A write begins at an OPC client or Ignition tag, passes through the device write handler, and reaches the server as a Set command. When writes continue but polling stops, follow the read path and locate the exact hop that no longer advances.
Where does an Ignition TCP request travel?
For a custom OPC UA device, the periodic read path is:
Gateway scheduler → device instance → TCP client → custom TCP server → response parser → OPC nodes → OPC UA subscription → Ignition tags
The write path runs in the opposite direction:
OPC Quick Client or Ignition tag → OPC node write handler → TCP client → Set command → custom TCP server
These paths share the socket and device state but do not necessarily share the scheduled task. Successful writes therefore prove that the device instance and at least part of the TCP path remain operational; they do not prove that the poller is still scheduled, running, receiving a complete response, or publishing values.
Keep the namespaces distinct. An OPC driver creates OPC nodes. A user or configuration process creates Ignition tags that subscribe to those nodes. A Managed Tag Provider exposes actual Ignition tags directly and omits the OPC subscription hop. Confusing nodes with tags makes lifecycle and load problems difficult to locate.
Which integration approach fits the Get/Set protocol?
| Approach | Data exposure | Best fit | Primary limitation |
|---|---|---|---|
| Standard Ignition TCP driver plus scripting | Script reads the Message Tag and writes the Writable Tag | Simple framing and a small amount of receive/send logic | The standard driver has limited capabilities and is aimed mainly at periodic data from devices such as barcode scanners or scales, not at implementing an arbitrary request/response protocol |
| Custom OPC UA device driver | Driver creates OPC nodes; Ignition tags subscribe to them | A custom Get/Set protocol that should appear as an OPC device | Requires correct device lifecycle, scheduler isolation, node management, parsing, and subscription handling |
| Managed Tag Provider module | Module exposes Ignition tags directly | The module owns the tag model and OPC interoperability is unnecessary | Couples the integration directly to Ignition tags rather than an OPC namespace |
| Module that reaches into the existing TCP driver | Depends on another module's internal behavior | No clear advantage for this case | More complicated than either scripting around the public tags or owning the complete TCP implementation |
Use the standard TCP driver and Gateway-side scripting when its Message Tag, Writable Tag, and framing behavior completely represent the protocol. Use a custom OPC UA device when the server exposes many addressable values, Get operations must be polled, Set operations must be translated, and OPC clients should browse or subscribe to the values. Choose a Managed Tag Provider only when the desired public interface is explicitly an Ignition tag provider rather than an OPC device.
For this custom server, the OPC UA device approach is the strongest match: retain the custom TCP client, expose each remote value as an OPC node, and let configured Ignition tags subscribe. Do not make the driver create ordinary Ignition tags as a substitute for its OPC nodes.
Why can polling stop as the node count increases?
The observed transition is from successful testing with 20 values to polling failure somewhere around 100 or 200, while OPC Quick Client writes still reach the TCP server. Node count is a load trigger, not a root-cause diagnosis. Inspect the scheduler callback, blocking network operation, parser, and publication loop separately.
| Observation | Path still working | Likely failure point | Decisive check |
|---|---|---|---|
| Writes work but remote changes never appear | OPC write handler and outbound Set path | Poll callback, Get exchange, parser, or value publication | Log entry and counter at every read-path boundary |
| Last poll starts but never completes | Scheduler invoked the callback | Blocking connect, write, socket read, or parser loop | Compare poll-start with send, receive, parse, and poll-finish markers |
| Poll finishes until a larger response arrives | Scheduling and smaller exchanges | Framing, buffer handling, response validation, or an exception during parsing | Record response byte count and the last successfully parsed item |
| OPC nodes update but Ignition tags do not | TCP polling and node publication | Missing subscriptions, tag configuration, or quality propagation | Read the node in OPC Quick Client, then compare the subscribed tag |
| No callback entry appears after a failure | Independent write handling may still work | Escaped task exception, scheduler registration, or executor starvation | Inspect Gateway-side logs and capture the callback's final invocation |
Treat an uncaught exception as the first suspect. Recurring executors commonly stop future invocations when the task body lets an exception escape. A larger node list can expose an index error, malformed item, null value, concurrent modification, or publication failure that never occurs with 20 values.
A blocked poll is the second suspect. If the callback performs a socket read without a finite completion rule, one incomplete frame can hold the execution thread indefinitely. TCP is a byte stream: one read is not guaranteed to equal one application message. The parser must accumulate bytes until the protocol's delimiter or length rule declares a complete response.
What should the polling architecture isolate?
Keep scheduling, transport, protocol parsing, and OPC publication as separate stages. The scheduled callback should orchestrate one bounded cycle rather than contain an open-ended receive loop. Use one owner for mutable socket state, or serialize access when the poller and write handler share the connection. Otherwise a Set response can be mistaken for a Get response, or two callers can consume bytes from the same stream.
Maintain observable state for at least the last poll start, last poll completion, current connection state, last complete response, last parse result, and last publication result. These values may be logs, internal diagnostics, or exposed status nodes; the important property is that they identify the stalled hop without requiring a debugger.
The installation operates without internet access, so the deployed module and runtime protocol must remain locally complete. TCP connectivity to the local server is independent of internet connectivity. Keep any build-time dependency acquisition outside the operational data path, and test the installed artifact in the same disconnected conditions.
How should the periodic task be registered and protected?
The startup code registers this::poller with a value of 2000:
try {
deviceContext.getGatewayContext().getExecutionManager().register(
getClass().getName(), TASK_NAME, this::poller, 2000);
logger.info("TCP module started.");
} catch (Exception e) {
logger.error("Error starting up TCP poller.", e);
}
The outer try catches registration failures only. It does not catch a later exception thrown inside poller. Put a top-level exception boundary inside the callback, log the complete failure there, and allow the invocation to return normally. Also prevent concurrent polls if one cycle can outlast the scheduling interval. The exact time unit and repeat semantics of 2000 must come from the execution-manager API used by the installed Ignition SDK; verify that contract rather than inferring it from the literal.
Registration belongs to each created device instance, not merely to module startup. Use a stable, instance-specific task identity so multiple configured devices do not replace or collide with each other's work. Pair registration with lifecycle cleanup so disabling or deleting a device stops its task and closes its socket. Confirm that startup has access to a fully initialized node model and TCP client before the first poll executes.
How do you implement the recommended custom device?
- Define the remote model. Retrieve the server's list of values and map each remote identity to one OPC node. Preserve a stable mapping across polls so updates target existing nodes rather than recreating the namespace.
- Separate discovery from polling. Build or refresh the node list in a controlled operation. Let routine polling read values for the established model. If the remote list changes, reconcile additions and removals without modifying a collection while another thread iterates over it.
- Bound every exchange. Send one valid Get request, accumulate the corresponding response using the protocol's actual framing rule, reject incomplete or malformed frames, and terminate the cycle on success or transport failure. Read timeout values must come from the server and site requirements rather than an invented default.
- Parse before publishing. Decode the response into a temporary result set. Validate identities, types, and completeness before changing OPC node values. One malformed item should be logged with its remote identity and should not silently terminate future polling.
- Publish OPC nodes. Update values and quality through the device's OPC model. Let users create Ignition tags that subscribe to those nodes. If direct creation of Ignition tags is a hard requirement, move that responsibility to a Managed Tag Provider design.
- Translate writes independently. Convert an OPC node write into the matching Set command, validate the server reply, and report success or failure through the write result. Serialize the transaction with polling when both operations use one TCP stream.
- Instrument the boundaries. Use the module's logger for server-side diagnostics. Add poll-start, request-sent, response-complete, parse-complete, publish-complete, and poll-finish markers. Include counts and elapsed stages, but never log protected payload data in a high-security deployment.
- Protect the recurring callback. Catch and log failures at the callback boundary, close or reset a failed connection deliberately, and leave the scheduler able to invoke the next cycle.
How is the complete path verified?
- Start with the physical and transport layer. Confirm the socket connects to the intended local endpoint and remains usable across repeated Get and Set transactions.
- Run at the 20-value test size and confirm that every poll produces all boundary markers in order.
- Increase the remote list through the range where failure was observed, around 100 to 200 values. Record the last completed boundary, response size, parsed count, published count, and any exception.
- Change one value at the TCP server. Confirm receipt of a complete Get response, successful parsing, an updated OPC node in OPC Quick Client, and then an updated subscribed Ignition tag.
- Write one value through OPC Quick Client. Confirm that the corresponding Set command reaches the server and that its result is returned to the client.
- Inject a malformed or incomplete response using an approved test endpoint. Confirm that the cycle reports a diagnostic, exits cleanly, and a later valid response is polled successfully.
- Disable and re-enable the device. Confirm that the old polling task and socket stop, one new task starts, and no duplicate Get requests appear.
FAQ
What happens if the Ignition poller throws an exception?
A recurring task may stop receiving future invocations when an exception escapes. Catch failures at the top of poller, log the full exception in the Gateway-side logs, return from the callback, and confirm that the next invocation starts.
What happens if the TCP socket read never completes?
The callback remains blocked, so no later values are parsed or published even though another write path may still operate. Apply the protocol's framing rule and a site-approved finite timeout, then log whether the failure occurred before a complete frame arrived.
What happens if the driver creates OPC nodes but no Ignition tags subscribe?
The nodes can remain visible and readable in OPC Quick Client, but ordinary Ignition tags will not appear automatically. Create tags that subscribe to those nodes, or use a Managed Tag Provider when the module must expose actual Ignition tags directly.
What happens if writes work but periodic reads stop?
The successful Set path does not validate the scheduled Get path. Trace one server-side value change through poll-start, request-sent, response-complete, parse-complete, OPC-node update, and subscribed-tag update; the first missing marker identifies the failed hop.