Automation / Serialization
Laser Etcher Modernization
I replaced a deprecated, network-dependent etching package with an in-house Ignition system that marks glass from the production schedule.
01
Problem
PPS, a deprecated Java-based etching package, depended on the network. When PPS or the network went down, lasers stopped etching to schedule: during one outage about 60,000 units shipped unetched, Venice went months unable to etch, and Stayton's etcher was beyond repair. Stayton was the only site without etching on a standard production line. The permanent etch identifies who made a unit, what it is, and where it was built, and traces back to its order and line. When labels are missing, it is the last ID left; it also supports warranty and customer care, IG certification, and brand.
02
Approach
I built a full-stack Python/Ignition replacement in house, deployed and supported on a local Ignition server. Titan ERP exports the production schedule as CSV to a network path; Ignition watches for new files and loads them into SQL. A Perspective HMI presents the batch. As each glass lite rides the float table, the DL05 PLC detects its position and triggers the Domino laser with the current record's data; the exit sensor advances the record. The gateway owns the Dynamark TCP conversation and waits for each response before sending the next command, preventing replies from getting scrambled. Etching runs locally without an internet dependency.
Operator HMI and diagnostics
03
Architecture
Titan ERP schedules arrive as CSV and load into SQL. Perspective presents the batch; the DL05 detects glass position and advances records, while the local Ignition gateway serializes Dynamark commands to the Domino laser and waits for each response.
04
Implementation
I built the Ignition and Python software; controls and mechanical partners handled the laser and float-table integration. Dynamark is a text protocol over TCP port 20000, terminated by CR/LF; replies are OK, RESULT, or ERROR n. On startup, the gateway loads the label and subscribes to SETMSG 1–5, 12, and 26. Its state machine covers DISCONNECTED, INITIALIZING, IDLE/READY, TX IN FLIGHT, WAITING MSG 26, PRINTING (MSG 2 to MSG 3), NOT READY (MSG 4/12), and FAULT (ERROR, TCP loss, or MSG 5). The CommandQueue, EtcherBusy, EtcherFaulted, and isConnected tags support this flow. A diagnostics view shows the state diagram and live etcherAI_P.writeToEtcher log for multi-session debugging. The operator HMI includes schedule selection, Start/Stop, Cycle-Start/Send-to-Print, Mark-Done and Mark-Reject, Show Rework, a live etch preview with record values, and paging through batches of about 5,000 records (about 500 pages). I added Sequence Search after operators requested it on the floor. Etch content is standardized, including the JW prefix and AAMA certification information, so it can replace the separate AAMA Gold sticker.
TCP_WRITE_TAG = "[provider]PLACEHOLDER/Etcher/Write"
COMMAND_QUEUE_TAG = "[provider]PLACEHOLDER/Etcher/CommandQueue"
ETCHER_BUSY_TAG = "[provider]PLACEHOLDER/Etcher/EtcherBusy"
ETCHER_FAULTED_TAG = "[provider]PLACEHOLDER/Etcher/EtcherFaulted"
def writeToEtcher(command):
system.tag.writeBlocking([TCP_WRITE_TAG], [command + "\r\n"])
def setMsgsTrue(message_types):
return ["SETMSG %d 1" % message_type for message_type in message_types]
def startUp():
commands = ['LOADPROJECT "<label-name>"']
commands.extend(setMsgsTrue([1, 2, 3, 4, 5, 12, 26]))
system.tag.writeBlocking([COMMAND_QUEUE_TAG], [commands])
sendNextCommand()
def sendNextCommand():
busy = system.tag.readBlocking([ETCHER_BUSY_TAG])[0].value
if busy:
return
queue = system.tag.readBlocking([COMMAND_QUEUE_TAG])[0].value or []
if not queue:
return
command = queue.pop(0)
system.tag.writeBlocking(
[COMMAND_QUEUE_TAG, ETCHER_BUSY_TAG], [queue, True])
writeToEtcher(command)
def onResponse(response):
response = response.strip()
if response.startswith("ERROR") or response == "MSG 5":
system.tag.writeBlocking(
[ETCHER_BUSY_TAG, ETCHER_FAULTED_TAG], [False, True])
return
if response == "OK" or response in ("MSG 3", "MSG 4", "MSG 12"):
system.tag.writeBlocking([ETCHER_BUSY_TAG], [False])
sendNextCommand()
05
Results
Conservative. Excludes label elimination and service-trip savings, and scales with each line deployed.
06
Reflection
What I’d do next
Next, I would complete the rollout pattern with a dedicated Ignition server per glass plant, so no site depends on another site's connection, and parameterize site and line IDs so I can deploy the project to the remaining lines without hand-copying tags.