S7-1200 T_Send to Zebra ZT230: Fix Status 7004 and Send ZPL Data
An S7-1200 controlling a Zebra ZT230 industrial printer over Ethernet is one of the most common print-and-apply setups on packaging skids, palletizing cells, and compliance labeling stations. The most frequently reported failure mode is the T_Send instruction sitting at status 7004 forever: the connection is up, the operator LCD on the printer shows Ready, but no bytes ever leave the CPU and no label ever prints. This reference walks through the exact cause of that symptom, the protocol realities of the ZT230, the TIA Portal configuration, and tested ZPL payloads that print the first time.
Problem Summary
The reported fault, generalized from the field, is:
- S7-1200 CPU (firmware V4.0 and later; typical build in 2024-2025 deployments is V4.4 or V4.5; the S7-1200 G2 family on V5.x behaves identically for Open User Communication).
- Zebra ZT230 industrial printer with the internal 10/100 Ethernet option installed (Zebra part family ZT23042-Dxxxxx with the Internal Ethernet print server).
- TSEND_C or legacy TSEND/TRECV Open User Communication configured in TIA Portal.
- STATUS output of TSEND_C reads
W#16#7004(decimal 28676) every scan. - No head movement on the ZT230, no flash on the Ethernet link LED, no label feed.
Status 7004 is not an error - it is a "no work to do" indication. The block has opened a TCP socket to the printer and is idle. The print job is never actually initiated because the engineer's program never arms a transmission job, never calculates a valid length, or never places a valid ZPL string into the send buffer. The printer is patiently waiting on port 9100; the PLC just never spoke.
Why Status 7004 Occurs
TSEND/TSEND_C reports a two-dimensional status: connection state and active-job state. Status 7004 sits in the "connection ready, no job in flight" row of that matrix. It is the steady-state value of the STATUS output the entire time the TCP socket is open and no REQ rising edge has been processed.
| STATUS (hex) | STATUS (dec) | Meaning |
|---|---|---|
| 7000 | 28672 | Block not active / no job pending |
| 7001 | 28673 | First call: connection being initiated |
| 7002 | 28674 | Connection establishment in progress |
| 7003 | 28675 | Connection setup complete, no send in progress |
| 7004 | 28676 | Connection established, no data being sent (steady state) |
| 7005 | 28677 | Data transmission in progress |
| 7006 | 28678 | Data transmission complete, connection stays open |
| 7007 | 28679 | Data transmission complete, connection being closed |
| 80A1 | 32929 | Local connection endpoint already in use |
| 80A2 | 32930 | Remote endpoint not reachable / no route |
| 80A3 | 32931 | Connection refused by partner (wrong port or no listener) |
| 80A4 | 32932 | Partner reset the connection |
| 80A7 | 32935 | TCP timeout, no ACK from partner |
If STATUS were sitting at 7002, the connection would be the problem. If STATUS is at 80A3, port 9100 is wrong or the ZT230 firewall is rejecting the socket. Status 7004, however, is the "waiting for work" state - the PLC has nothing wrong with it, it just has nothing to send.
Zebra ZT230 Communication Architecture
The ZT230 is a thermal-transfer industrial printer designed for compliance labeling, not a PROFINET device. It does not speak PROFINET. It does not have a GSD file. It does not appear in a PROFINET topology scan. The "PROFINET" reference in the field report almost always means "the S7-1200 is using its PROFINET port to send raw TCP/IP frames," not "the printer is on PROFINET." The CPU's PROFINET interface is fully capable of standard TCP/IP via the Open User Communication instructions; that is what TSEND_C uses regardless of the I/O name on the front of the module.
| Interface | Available | Protocol | Notes |
|---|---|---|---|
| USB | Standard | Vendor driver / ZPL over USB | Driver v10 per Zebra support page |
| RS-232 Serial | Standard | ZPL or EPL on serial port | Default 9600-8-N-1, XON/XOFF |
| Internal 10/100 Ethernet | Option | Raw ZPL on TCP 9100, HTTP 80, HTTPS 443, Telnet 23 | Default IP via DHCP or front panel |
| 802.11b/g Wi-Fi | Option | Same as wired Ethernet | Wireless option card |
The raw ZPL socket is on TCP port 9100. This is the de-facto industry port for unidirectional ASCII label submission to Zebra printers and is documented in Zebra's ZPL II Programming Guide. The ZT230's internal print server also exposes:
- Port 9100 - raw ZPL stream (primary print port).
- Port 9101 to 9109 - additional raw streams mapped per queue in the print server web UI.
- Port 80 / 443 - web administration UI for status, IP, and queue configuration.
- Port 23 - Telnet configuration (often disabled by default for security).
ZPL mode versus EPL mode is selected on the ZT230 via the front panel under Menu → Settings → Print Mode. The default is Auto, which auto-detects based on incoming data, but the recommended setting for explicit control from a PLC is ZPL - if the printer auto-detects wrong, your labels print as garbage or not at all.
Configuring the ZT230 for ZPL Mode
Before commissioning the S7-1200 side, set the printer to ZPL mode and verify the Ethernet port:
- Power on the ZT230 and wait for the operator LCD to display Ready.
- Press the right arrow until Menu is highlighted, then press the check mark.
- Navigate to Settings → Print Mode and set to ZPL (not Auto, not EPL).
- Navigate to Communications → Network → Wired → IP Protocol; set to Permanent for a fixed address, or DHCP if your PLC network has a DHCP server and you can resolve the lease.
- Set IP Address, Subnet Mask, and Gateway to a free address in the same subnet as the S7-1200 PROFINET port (commonly 192.168.0.x/24 or 192.168.1.x/24). Avoid the PLC's own IP and any HMI IP.
- Save and reboot the printer. The IP persists across reboots but the printer must reload its print server after a network change.
- From a laptop on the same subnet, browse to
http://<printer-IP>. The Zebra web admin should load and confirm the firmware build, the print queue state, and the wired MAC address. - From the laptop, open a raw TCP socket to port 9100 (PuTTY raw mode, telnet, or
ncon Linux). Type^XA^FDTEST^FS^XZand press Enter. A test label should print. This proves the network path and ZPL parser are good before the PLC enters the picture.
The ZT230 firmware version is visible in the printer's About menu and on the web admin home page. Current long-running firmware branches in 2024-2025 deployments are V72.20.x and later (the "V" suffix indicates a ZPL-aware build). Downgrade or upgrade as required from the ZT230 Support & Downloads page. Always use the Zebra Windows Printer Driver v10 (also from that page) on engineering laptops for offline label design.
TIA Portal T_Send Configuration
TSEND_C is the recommended Open User Communication block for new S7-1200 projects. It combines TSEND and the connection-establishment handshake into a single instance and provides cleaner error handling. The legacy TSEND/TRECV pair is still valid for symmetric or split projects. Both blocks share the same status code table above.
Connection DB
When you drop a TSEND_C instance, TIA Portal auto-generates an instance DB and the associated connection data block. Open the connection DB and configure the following parameters:
| Parameter | Value | Notes |
|---|---|---|
| Connection type | TCP | Not UDP, not ISO-on-TCP |
| Active connection establishment | Active | PLC initiates; printer is passive listener on 9100 |
| Partner IP address | 192.168.0.50 (example) | ZT230 Ethernet IP |
| Partner port | 9100 | Zebra raw ZPL port |
| Local port | 2000 (example) | Any free port 2000..49151 |
| Connection name | ZEBRA_ZT230 | Free-form, max 16 chars |
| Connection ID | 1 (example) | Unique within the project |
Download the hardware configuration. If the STATUS output is now 7004 after the first cycle, the TCP handshake succeeded. If STATUS is 80A3, the printer is not listening on 9100 - re-check the ZT230 network configuration and confirm no IP allow-list is enabled in the web admin under Security → IP Filtering.
TSEND_C Interface Wiring
| Input/Output | Type | Source / Sink |
|---|---|---|
| REQ | BOOL (in) | Rising edge from a print-trigger condition |
| CONT | BOOL (in) | TRUE - keep connection open between jobs |
| LEN | UINT (in) | Byte count of the ZPL string - this is the most-misconfigured parameter |
| DATA | VARIANT (in) | Pointer to a STRING or ARRAY OF BYTE in a global DB |
| COM_RST | BOOL (in) | Rising edge tears down and reopens the connection |
| DONE | BOOL (out) | One-shot TRUE on successful send |
| BUSY | BOOL (out) | TRUE while transmission in progress |
| ERROR | BOOL (out) | TRUE if STATUS contains an error code |
| STATUS | WORD (out) | Returns the codes in the table above |
Building the ZPL Command String
ZPL is a flat ASCII language. Every label job is bracketed by ^XA (Start of Label) and ^XZ (End of Label). Between them, every printable element - text, barcode, graphic line, RFID write - is a separate ^X-prefixed command. The ZT230 firmware's ZPL II interpreter accepts the full ZPL II command set documented in Zebra's ZPL II Programming Guide.
Minimum Static Label
^XA
^FO50,50^A0N,50,50^FDHello World^FS
^XZ
Breaking this down:
-
^XA- begin label format -
^FO50,50- field origin at x=50, y=50 dots. The ZT230 203 dpi head is 8 dots/mm, so 50 dots is about 6.25 mm from the top-left of the label. -
^A0N,50,50- select scalable font 0, normal orientation, height 50, width 50 (in dots). -
^FDHello World- the field data to print. -
^FS- field separator (end of the ^FD field). -
^XZ- end of label format and trigger print.
This 38-byte ZPL string is exactly the example given in the source thread. It is a valid ZPL job; sending it to a ZT230 in ZPL mode on port 9100 will feed one label and print "Hello World" in the upper-left corner.
Including a Code 128 Barcode
^XA
^FO50,50^BCN,80,Y,N,N^FD12345678^FS
^FO50,200^A0N,40,40^FDPart: 12345678^FS
^XZ
-
^BC- Code 128 barcode, normal orientation, height 80 dots, print human-readable text below the bars (Y), no check digit (N), no UCC case code (N). -
^FD12345678- the encoded value.
Including a QR Code
^XA
^FO50,50^BQN,2,5^FDQA,https://example.com/line-7^FS
^FO50,400^A0N,30,30^FDScan for line status^FS
^XZ
^BQN,2,5 selects QR Code model 2 with magnification 5. The QA prefix in the ^FD selects error correction level "M" (other options are QH = L, QL = M, QM = Q, QH is also valid for level L - consult the ZPL II guide for current nomenclature).
Setting Label Dimensions and Print Speed
^XA
^PW812 ; print width 812 dots (4 inches at 203 dpi)
^LL1218 ; label length 1218 dots (6 inches at 203 dpi)
^LH0,0 ; label home top-left
^PR4 ; print speed 4 ips
^FO50,50^A0N,50,50^FDHello World^FS
^XZ
The ^PW, ^LL, ^LH, and ^PR commands must precede the first ^FO command. ZPL processes commands left-to-right and top-to-bottom within a format. If you set ^PR to 6 ips but your label has dense barcode content, you may get voids; back off to 4 ips for safety.
PLC Program: Sending ZPL from the S7-1200
Data Block Layout
Create a global DB "ZPL_Data" with the following tags. The S7-1200 STRING[200] holds 200 characters of payload plus the standard STRING header, plenty for most labels.
DATA_BLOCK "ZPL_Data"
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
STRUCT
PrintString : STRING[200]; // holds the ZPL payload
PrintLength : UINT; // byte count to send (LEN to TSEND_C)
Trigger : BOOL; // rising edge starts the send
Done : BOOL; // echoes TSEND_C.DONE
Busy : BOOL; // echoes TSEND_C.BUSY
Error : BOOL; // echoes TSEND_C.ERROR
Status : WORD; // echoes TSEND_C.STATUS
END_STRUCT;
END_DATA_BLOCK
Size the STRING for the longest label you will ever send. A short 2-line label is 35 bytes; a complex 1D + barcode + 5 text fields label can exceed 180 bytes. Leave 30% headroom. If you need more, go to STRING[512].
Populating the String in SCL
"ZPL_Data".PrintString :=
'$'^XA'^FO50,50^A0N,50,50^FDHello World^FS'^XZ$';
"ZPL_Data".PrintLength :=
UINT#38; // exact byte count of the literal above, no header overhead
The '$' literal in SCL is the ASCII 02 STX character. While not strictly required by the ZT230, many engineers prepend it as a "wake the printer up" byte, especially after long idle periods. The terminator CR or LF after ^XZ is not required but harmless. If you wrap with CONCAT in SCL, use the byte length returned from the standard LEN function on a STRING - it returns the actual used character count, not the declared max.
Trigger Logic in SCL
// One-shot pulse on rising edge of print request
IF "PrintRequest" AND NOT "PrintRequestOld" THEN
"ZPL_Data".Trigger := TRUE;
ELSE
"ZPL_Data".Trigger := FALSE;
END_IF;
"PrintRequestOld" := "PrintRequest";
// Hand the trigger to TSEND_C
TSEND_C_DB_1(REQ := "ZPL_Data".Trigger,
CONT := TRUE,
LEN := "ZPL_Data".PrintLength,
DATA := "ZPL_Data".PrintString,
COM_RST := FALSE,
DONE => "ZPL_Data".Done,
BUSY => "ZPL_Data".Busy,
ERROR => "ZPL_Data".Error,
STATUS=> "ZPL_Data".Status);
Once STATUS returns 7006 and DONE pulses TRUE, the buffer is flushed and the printer has the bytes. The ZT230 will print almost immediately - ZPL print jobs are processed as soon as the ^XZ terminator arrives in the receive buffer. The total latency from REQ rising edge to label feed is typically 80-150 ms on a ZT230 over a clean gigabit link.
Variable Substitution
To merge PLC variables into a ZPL string at runtime, use the CONCAT function and recompute the length on every change:
// Build a dynamic label
VAR
PartNo : STRING[12];
Qty : INT;
Label : STRING[200];
END_VAR
PartNo := 'ABC-12345';
Qty := 100;
Label := CONCAT(IN1 := '$^XA^FO50,50^A0N,50,50^FDPart: ',
IN2 := PartNo);
Label := CONCAT(IN1 := Label,
IN2 := '^FS^FO50,150^A0N,40,40^FDQty: ');
Label := CONCAT(IN1 := Label,
IN2 := INT_TO_STRING(Qty));
Label := CONCAT(IN1 := Label,
IN2 := '^FS^XZ$');
"ZPL_Data".PrintString := Label;
"ZPL_Data".PrintLength := LEN(Label);
"ZPL_Data".Trigger := TRUE;
Re-compute PrintLength every time the string is rebuilt. Hard-coding PrintLength to the length of a static template will clip dynamic content. Note that LEN(Label) on an S7-1200 STRING returns the used character count excluding the implicit null terminator, which is the correct value for LEN.
Re-arming without Double-Triggering
TSEND_C only fires on a rising edge of REQ. If PrintRequest stays TRUE across multiple scans, the block will fire once and then sit idle. To re-arm after a successful send, wait for DONE to pulse and a debounce time, then allow the next rising edge. The pattern below is robust against sticky inputs:
IF "PrintRequest" AND NOT "PrintRequestOld"
AND NOT "ZPL_Data".Busy
AND NOT "ZPL_Data".Trigger THEN
"ZPL_Data".Trigger := TRUE;
"PrintRequestOld" := TRUE;
END_IF;
IF "ZPL_Data".Done THEN
"PrintRequestOld" := FALSE; // allow re-trigger once printRequest clears
END_IF;
IF NOT "PrintRequest" THEN
"PrintRequestOld" := FALSE;
END_IF;
IF NOT "ZPL_Data".Trigger AND NOT "ZPL_Data".Busy THEN
// ready for next cycle
END_IF;
Verification Procedure
Use this checklist to prove the path end-to-end before declaring the job done:
-
PC to printer test (bypasses PLC): From a Windows command prompt, run
telnet 192.168.0.50 9100or use PuTTY in raw mode on port 9100. Type^XA^FDPLC_TEST^FS^XZand press Enter. The ZT230 should print "PLC_TEST" on one label. If this fails, the printer or network is the problem - the PLC is innocent. - Online watch on TSEND_C STATUS: In TIA Portal, go Online → Monitor All. STATUS should be 7004 at idle. Trigger the print. STATUS should move 7001 → 7002 → 7004 → 7005 → 7006. If it never leaves 7004, the REQ is not pulsing or LEN is zero.
-
Online watch on LEN and DATA: Confirm the PrintLength DB tag has the expected non-zero value at the moment REQ rises. Confirm the first bytes of PrintString match the ZPL header
^XA. The TIA Portal DB watch shows the STRING as a CHAR array on the right pane. - Watch the ZT230 LCD: During a print, the ZT230 will show "Printing" or a paper-feeding icon. The Ethernet activity LED on the printer's internal print server will flash during the TCP send. The internal print server's web admin will show the active queue draining.
-
Wireshark capture (optional but definitive): Mirror the PLC PROFINET port on the managed switch. Filter for
tcp.port == 9100. You should see a TCP PSH/ACK from the PLC's local port to the printer's port 9100 with the full ZPL payload as the data segment. If you see the SYN/SYN-ACK/ACK handshake but no PSH, the PLC never sent a job - back to step 2. -
Print count verification: Add a
~HSHost Status return and use TRECV to read the ZT230's print count. Cross-check with physical labels fed.
Common Faults and Diagnostics
| Symptom | Likely Cause | Fix |
|---|---|---|
| STATUS 7004 forever, no print | LEN = 0, or REQ never pulses, or DATA pointer is null | Set PrintLength to a non-zero UINT, edge-detect the trigger, confirm DB is not optimized-away for the VARIANT pointer if using absolute addressing |
| STATUS 80A3 at first call | Printer not listening on 9100, IP filter blocking the PLC, wrong IP | Ping the printer. Disable any IP allow-list. Telnet to 9100 from a laptop |
| STATUS 80A1 at first call | Local port already in use on the CPU | Change the local port. The S7-1200 reserves the lower range for system services |
| STATUS 80A2 at first call | No route to the printer subnet | Check PLC IP, subnet mask, gateway. Confirm the PROFINET port is connected to the right physical switch port |
| STATUS 7005 forever, never reaches 7006 | Printer closes the connection mid-payload, or buffer overrun | Reduce payload size, disable any print server "auto-disconnect" timer, confirm ZT230 firmware is current |
| Label prints gibberish | Printer is in EPL mode, receiving ZPL commands it cannot interpret | Set Print Mode = ZPL on the front panel. Power-cycle the printer |
| Label prints but text is misaligned | Print width (^PW) or label length (^LL) does not match loaded media | Set ^PW and ^LL to the physical label dimensions in dots, calibrate media with the CALIBRATE button on the front panel |
| First job prints, second job hangs | REQ is staying TRUE; TSEND_C only fires on rising edge | Edge-detect the trigger with a flip-flop or one-shot as shown in the SCL example |
| One label prints per cycle, but cycle is short | Multiple jobs triggered before first ^XZ is processed | Insert a 200 ms debounce; only re-arm when BUSY falls and DONE has pulsed |
| Print is double-exposed / ghosted | Head pressure or darkness wrong, or label gap sensor mis-calibrated | Run the CALIBRATE routine, set darkness via ~SD, inspect printhead for damage |
| STATUS 80A4 mid-job | Printer TCP receive buffer overflow | Reduce LEN per job to under 4 KB, increase socket idle timeout in print server admin |
Advanced: Multiple Labels in One Job
To print a fixed quantity of the same label, use the ^PQ (Print Quantity) command inside the format. This avoids 10 separate TSEND_C jobs and 10 TCP transactions.
^XA
^FO50,50^A0N,50,50^FDHello World^FS
^PQ10
^XZ
This prints 10 identical labels from a single send. The ZT230 will not reply until all 10 are fed. If you need to count the printed quantity, the ZT230 supports a ~HS (Host Status) return string if you also wire a TRECV block on the same connection. By default the raw ZPL socket on 9100 is unidirectional; for a return channel, enable bidirectional mode in the print server's web UI and use port 9101, or open a second TSEND_C/TRECV pair on a different port for the return stream.
Advanced: Numbered Fields and ^FH Substitution
For labels that share a template but vary data per instance, use the ^FN (Field Number) command. The ^FH command enables the _ (underscore) as the hex delimiter for special characters in the field data.
^XA
^FO50,50^A0N,50,50^FN1^FDDefault Part^FS
^FO50,150^A0N,40,40^FN2^FDDefault Qty^FS
^XZ
To overwrite field 1 with a new value, send a follow-up ^FN1^FDNew Value^FS^XZ. The ZT230 keeps the field table across jobs within a single TCP session. This is the standard pattern for continuous-feed pallet labels where the printer is fed a stream of unique labels without re-issuing the full template.
ZPL Command Quick Reference
| Command | Function | Example |
|---|---|---|
| ^XA | Start of label format | ^XA |
| ^XZ | End of label format (triggers print) | ^XZ |
| ^FOx,y | Field origin | ^FO100,200 |
| ^FD | Field data (printable text) | ^FDHello^FS |
| ^FS | Field separator | ^FS |
| ^A0 | Select scalable font 0 | ^A0N,50,50 |
| ^BC | Code 128 barcode | ^BCN,80,Y,N,N |
| ^BQ | QR code | ^BQN,2,5 |
| ^PW | Print width in dots | ^PW812 |
| ^LL | Label length in dots | ^LL1218 |
| ^PR | Print speed (ips) | ^PR4 |
| ^PQ | Print quantity | ^PQ10 |
| ^GB | Graphic box | ^GB200,100,3 |
| ^BY | Bar code default parameters | ^BY2,2,100 |
| ^FN | Numbered field for re-issue | ^FN1^FD^FS |
| ^FH | Field hex enable | ^FH_ |
| ~SD | Set darkness (print energy) | ~SD20 |
| ~HS | Host Status return | ~HS |
| ^SZ | Set ZPL/EPL mode | ^SZZPL |
Performance, Sizing, and Network Notes
A typical ZT230 can sustain 4-6 inches per second (ips) at 203 dpi. The S7-1200 PROFINET interface runs at 100 Mbit/s full-duplex; the TCP overhead of a 200-byte ZPL job is negligible (~0.02 ms at the wire). End-to-end latency from REQ to first dot on the label is dominated by the ZT230's internal print server, not the network.
For a multi-printer cell, give each ZT230 a unique static IP and unique local port on the S7-1200. Sharing one connection across multiple printers is not supported by the ZT230 print server - each printer is a separate socket endpoint. The S7-1200 can maintain 8 simultaneous Open User Communication connections per firmware revision; check the CPU's connection resource budget in TIA Portal under Device view → Properties → Communication → Connection resources.
For an HMI-driven print system, expose ZPL_Data.PrintString and ZPL_Data.PrintLength as HMI tags on a WinCC Unified or Comfort Panel. An HMI text field bound to PrintString lets operators compose ad-hoc ZPL payloads, and a button wired to PrintRequest triggers the send. Always wrap the HMI input in a PLC-side whitelist validator that checks for the presence of ^XA and ^XZ before allowing TSEND_C to fire.
Safety and Operational Notes
^PQ jobs. If the PLC pushes more than 8 KB in a single TSEND_C job, the printer's TCP window will close and STATUS will return 80A4. Chunk large jobs across multiple sends.~HS and formatting wrappers. For raw ZPL testing, always use telnet, PuTTY raw, or nc, never the Zebra driver.Why does my S7-1200 T_Send show status 7004 but nothing prints?
Status 7004 (decimal 28676) means the TCP connection is open to the Zebra ZT230 and the PLC has nothing to send. The two most common causes are LEN set to 0 in the TSEND_C block and the REQ input never producing a rising edge. Populate LEN with the exact byte count of your ZPL string and edge-trigger REQ to a one-shot. The printer will then receive the ^XA...^XZ job and print immediately.
Does the Zebra ZT230 support PROFINET?
No. The ZT230 is not a PROFINET device and has no GSD file. The standard 10/100 Ethernet option on the ZT230 speaks raw ZPL on TCP port 9100, plus HTTP/HTTPS/Telnet for administration. The PLC's PROFINET port is used as a normal TCP/IP socket to send ASCII ZPL frames to the printer.
What TCP port does the Zebra ZT230 listen on for ZPL?
Port 9100 is the primary raw ZPL stream and the de-facto industry standard for Zebra printers. Additional streams can be enabled on 9101-9109 via the print server's web UI. The web admin runs on 80/443 and should be left enabled for diagnostics but not used for print data.
How do I switch the ZT230 from EPL to ZPL mode?
On the operator LCD, navigate to Menu → Settings → Print Mode and select ZPL. If you are already in EPL mode and need to switch without a keypad, send the ZPL command ^SZZPL from a host that the printer will accept. Verify the mode is ZPL before commissioning the PLC, otherwise EPL firmware will treat ^XA as garbage and print blank labels.
What is the minimum working ZPL string to test the S7-1200 to ZT230 link?
^XA^FO50,50^A0N,50,50^FDHello World^FS^XZ. That 38-byte payload covers the full ZPL job structure: ^XA start, ^FO field origin, ^A0 scalable font, ^FD field data, ^FS field separator, ^XZ end and print trigger. Send it from a PC with telnet to port 9100 first; once it prints, replicate the same byte sequence from the PLC with LEN = 38.