Industry 4.0 · SAP DMC · OPC-UA · Smart Factory Labels

MES Integration:
SAP DMC + OPC-UA
Label Triggers

How to trigger enterprise label printing from production events in SAP Digital Manufacturing Cloud (DMC), OPC-UA machine signals, and MES production order completions — without a human touching a keyboard.

SAP DMC OPC-UA IDoc LABELS01 S/4HANA Cloud REST API ZPL Print
01Integration Architecture
Three trigger paths — all terminate at the same ZPL print output
🏭
PLC / Machine
OPC-UA signal
OPC-UA
🔗
OPC-UA Connector
node-opcua / SAP Edge
REST POST
☁️
SAP DMC
Production Order
Event / IDoc
⚙️
SAP S/4HANA
LABELS01 IDoc
TCP/IP
🖨️
Zebra Printer
ZPL label
🔴
Path A — OPC-UA Direct
Machine PLC fires OPC-UA event on production completion. Node.js OPC-UA listener intercepts, calls api.labelnex.in/print directly. Bypasses SAP entirely. Fastest path — sub-second latency.
☁️
Path B — SAP DMC Event
SAP DMC production order completion fires a business event. BTP Integration Suite routes event to S/4HANA. S/4HANA creates LABELS01 IDoc → BarTender/NiceLabel → Zebra. Full SAP governance.
📋
Path C — API Trigger
MES system (non-SAP) calls api.labelnex.in REST endpoint with material + quantity + lot. API fetches label data from SAP OData, constructs ZPL, sends TCP to printer. Middleware pattern.
02Path A — OPC-UA Connector (Node.js)
Listen for machine signals, trigger label print on production events
OPC-UA Listener — Setup
# Install OPC-UA client library npm install node-opcua axios # File: opcua-label-trigger.js
// opcua-label-trigger.js — runs on factory edge device const { OPCUAClient, AttributeIds, DataType } = require("node-opcua"); const axios = require("axios"); const OPCUA_ENDPOINT = "opc.tcp://192.168.1.50:4840"; // PLC IP const LABEL_API = "https://api.labelnex.in/print"; const PRINTER_IP = "192.168.1.100"; // OPC-UA Node IDs for production signals const NODES = { productionComplete: "ns=2;s=ProductionLine1.OrderComplete", materialNumber: "ns=2;s=ProductionLine1.MaterialNumber", lotNumber: "ns=2;s=ProductionLine1.LotNumber", quantity: "ns=2;s=ProductionLine1.QuantityProduced", }; async function startListener() { const client = OPCUAClient.create({ endpointMustExist: false }); const session = await client.connect(OPCUA_ENDPOINT) .then(() => client.createSession()); console.log("✓ OPC-UA connected to", OPCUA_ENDPOINT); // Subscribe to production complete signal const subscription = await session.createSubscription2({ requestedPublishingInterval: 500, // check every 500ms requestedMaxKeepAliveCount: 20, maxNotificationsPerPublish: 10, publishingEnabled: true, }); subscription.monitor( { nodeId: NODES.productionComplete, attributeId: AttributeIds.Value }, { samplingInterval: 500, discardOldest: true, queueSize: 1 }, async (dataValue) => { if (dataValue.value?.value === true) { await triggerLabel(session); } } ); } async function triggerLabel(session) { // Read material, lot, quantity from OPC-UA nodes const [matNode, lotNode, qtyNode] = await session.readVariableValue([ NODES.materialNumber, NODES.lotNumber, NODES.quantity ]); const payload = { printerIp: PRINTER_IP, printerPort: 9100, materialNumber: matNode.value.value, lotNumber: lotNode.value.value, quantity: qtyNode.value.value, trigger: "OPC_UA_PRODUCTION_COMPLETE", timestamp: new Date().toISOString(), }; console.log("Label trigger:", payload); try { const res = await axios.post(LABEL_API, payload); console.log("✓ Label printed:", res.data); } catch(e) { console.error("✗ Print failed:", e.message); // TODO: queue for retry, alert ops } } startListener().catch(console.error);
Deploy on: Raspberry Pi 4, factory edge PC, or SAP Edge Services Node — any device on the same network as the OPC-UA server (PLC). Run with pm2 start opcua-label-trigger.js for auto-restart.
api.labelnex.in — /print endpoint (OPC-UA handler)
// routes/print.js — add to your Fastify API fastify.post('/print', async (req, reply) => { const { printerIp, printerPort=9100, materialNumber, lotNumber, quantity, zpl, copies=1, trigger } = req.body; // If ZPL not provided, build from SAP OData let printZpl = zpl; if (!printZpl && materialNumber) { const sapData = await fetchSapMaterialData(materialNumber, lotNumber); printZpl = buildZpl(sapData, quantity); } if (!printZpl) return reply.code(400).send({ error: 'No ZPL or material data' }); // Send TCP to Zebra printer await tcpPrint(printerIp, printerPort, printZpl, copies); // Log to DB await db.query( 'INSERT INTO print_log(material,lot,printer,copies,trigger,zpl) VALUES($1,$2,$3,$4,$5,$6)', [materialNumber, lotNumber, printerIp, copies, trigger||'MANUAL', printZpl] ); return { ok: true, printed: copies, material: materialNumber }; }); async function tcpPrint(ip, port, zpl, copies) { const net = require('net'); return new Promise((resolve, reject) => { const sock = net.createConnection({ host: ip, port }, () => { sock.write(zpl.repeat(copies)); sock.end(); resolve(); }); sock.on('error', reject); sock.setTimeout(5000, () => { sock.destroy(); reject(new Error('Printer timeout')); }); }); }
03Path B — SAP Digital Manufacturing Cloud (DMC)
Production order completion event → BTP → S/4HANA → LABELS01 IDoc
SAP DMC Business Event Configuration
Configuration StepWhereValue
Enable Business EventsDMC → Admin → Business EventsON — POST_COMPLETE event type
Event TypeBusinessEvent.POST_COMPLETEFires when production order operation is confirmed
Event DestinationDMC → Integration → DestinationsBTP EventMesh or SAP Integration Suite webhook
Payload FieldsmaterialNumber, quantity, batch, plant, workcenterAll available in event payload — no custom code
BTP Integration FlowBTP Integration Suite → iFlowsHTTP POST → transform → SAP S/4HANA BAPI call
S/4HANA Entry PointBAPI_GOODSMVT_CREATE or custom RFCCreates goods movement → triggers NACE output → LABELS01 IDoc
Key: SAP DMC is the cloud MES for S/4HANA RISE. If client is on RISE/Cloud (not on-premise ECC), this is the correct integration path. The IDoc trigger still fires from S/4HANA — DMC just initiates the production confirmation.
BTP Integration Suite iFlow — DMC Event → S/4HANA Label
// BTP Integration Suite Groovy script — transform DMC event to S/4HANA call // Used in iFlow: "DMC Production Complete → Label Trigger" import com.sap.gateway.ip.core.customdev.util.Message def Message processData(Message message) { def body = message.getBody(String) def json = new groovy.json.JsonSlurper().parseText(body) // Extract DMC event fields def material = json.materialNumber // e.g. "CHEM-001-GHS" def batch = json.batch // lot number def plant = json.plant // e.g. "1000" def qty = json.quantity // Build S/4HANA OData request body for label trigger def s4Payload = """ { "Material": "${material}", "Batch": "${batch}", "Plant": "${plant}", "Quantity": "${qty}", "LabelTriggerSource": "SAP_DMC" }""" message.setBody(s4Payload) message.setHeader("Content-Type", "application/json") message.setHeader("sap-client", "100") return message }
04Path C — REST API Trigger (Non-SAP MES)
Any MES, WMS, or production system can trigger labels via api.labelnex.in
API Specification
POST https://api.labelnex.in/v1/label/trigger Authorization: Bearer {JWT_TOKEN} Content-Type: application/json { "material": "CHEM-001-GHS", // SAP material number "batch": "LOT2026-001", // lot / batch "plant": "1000", // SAP plant "quantity": 100, // units to label "printerGroup": "LINE1_PRINTERS", // printer routing group "template": "GHS_LABEL_EU", // optional override "source": "CUSTOM_MES" // audit trail } // Response: { "jobId": "LN-JOB-20260601-0042", "printed": 100, "printer": "192.168.1.100", "template": "GHS_LABEL_EU", "status": "QUEUED" // or PRINTED / FAILED }
SAP data fetch: When the API receives a material + batch, it calls SAP S/4HANA OData endpoint /sap/opu/odata/sap/API_MATERIAL_DOCUMENT_SRV to get current material master and classification data. The ZPL is built dynamically — no static templates needed for standard label types.
05Production Scenarios & ZPL Generation
How label content is determined at print time
// GHS label ZPL builder — called by api.labelnex.in async function buildGhsZpl(materialNumber, batchNumber) { // 1. Fetch GHS classification from SAP EHS GLM const ghsData = await fetchSapGhsData(materialNumber); // Returns: { signalWord, hazardClasses, hPhrases, pPhrases, pictograms, ufI } const { signalWord, hPhrases, pPhrases, pictograms } = ghsData; return `^XA ^PW812 ^LL1218 ^CF0,28 ^FO30,30^FD${materialNumber}^FS ^FO30,75^A0N,22,22^FD${ghsData.productName}^FS ^FO30,110^A0N,36,36^FDBold: ${signalWord.toUpperCase()}^FS ^FO30,165^A0N,20,20^FD${hPhrases.slice(0,3).join(' | ')}^FS ^FO30,200^A0N,20,20^FD${pPhrases.slice(0,2).join(' | ')}^FS ^FO30,270^A0N,20,20^FDBatch: ${batchNumber}^FS ^FO30,310^BY2^BCN,60,Y,N,N ^FD>;>801${ghsData.gtin}^FS ^XZ`; }
// EU MDR UDI label — Datamatrix + linear fallback function buildUdiZpl(gtin, lot, expiry, serial) { // expiry must be YYMMDD format for AI(17) const exp = formatExpiry(expiry); // DDMMYYYY → YYMMDD const dmData = `]d101${gtin}10${lot}17${exp}21${serial}`; return `^XA ^PW608 ^LL406 ^FO20,20 ^BXN,4,200,0,0,1 ^FD${dmData}^FS ^FO170,20^A0N,22,22^FDUDI Label - EU MDR^FS ^FO170,52^A0N,20,20^FDGTIN: (01)${gtin}^FS ^FO170,80^A0N,20,20^FDLot: (10)${lot}^FS ^FO170,108^A0N,20,20^FDExp: (17)${exp}^FS ^FO170,136^A0N,20,20^FDSer: (21)${serial}^FS ^XZ`; }
// GS1-128 shipping label with SSCC function buildShippingZpl(sscc, gtin, lot, expiry, qty) { return `^XA ^PW812 ^LL1218 ^FO30,20^A0N,40,40^FDShipping Label^FS ^FO30,80^BY3^BCN,120,Y,N,N ^FD>;>800${sscc}^FS ^FO30,240^A0N,26,26^FD(00)${sscc}^FS ^FO30,290^BY2^BCN,80,Y,N,N ^FD>;>801${gtin}>810${lot}>817${expiry}>837${qty}^FS ^FO30,410^A0N,22,22^FD(01)${gtin} (10)${lot}^FS ^FO30,445^A0N,22,22^FD(17)${expiry} (37)${qty}^FS ^XZ`; }
06Deployment Checklist
ComponentWhereStatus Verification
OPC-UA ListenerEdge device / Hetznerpm2 status opcua-trigger → online
api.labelnex.in /printHetzner CX21curl https://api.labelnex.in/health{"status":"ok"}
SAP SM59 RFCSAP S/4HANASM59 → test connection → green
WE20 Partner ProfileSAP S/4HANAWE20 → partner visible → output type LABELS01
NACE Output TypeSAP S/4HANANACE → find output type → active on goods movement
Zebra PrinterFactory floorping 192.168.1.100nc -z 192.168.1.100 9100
BTP iFlow (DMC path)SAP BTPBTP Integration Monitor → iFlow status → Running