Skip to content
Of Ash and Fire Logo

MES Integration With ERP and PLCs in Running Legacy Plants

MES integration with ERP and PLCs in legacy plants: ISA-95 levels, validated B2MML messages, OPC UA, Modbus and EtherNet/IP maps, and a line-by-line rollout.

TL;DR

Key Takeaways

  • ISA-95 places the MES at Level 3 between ERP at Level 4 and the control systems below it, and the standard mainly governs the Level 3 to Level 4 interface.
  • B2MML version 7 has no separate ProductionSchedule schema: a released production order travels as a ProcessOperationsSchedule message with OperationsType set to Production, and the BOM goes as an OperationsMaterialBill.
  • Use the B2MML ApplicationArea BODID as an idempotency key so a retried confirmation never posts the same goods issue to ERP twice.
  • Modbus function codes 03 and 04 read 1 to 125 registers per request, and register 40101 in a device manual is PDU address 100 on the wire.
  • A low-risk MES rollout moves from read-only shadow mode to a parallel run to a per-line cutover at a planned changeover, with PLC writes last and behind a PLC-side enable bit.
·15 min read
manufacturingmes integrationisa-95opc ualegacy integrationreverse engineeringindustrial iot

Written by Daniel Ashcraft — 12+ years building industrial software including SCADA modernization, IoT data pipelines, and WebGL 3D visualization for manufacturing and energy companies.

MES integration with ERP follows the ISA-95 split: ERP sends released production orders, material definitions, routings and BOMs to the MES, and the MES returns confirmations, consumption and scrap, often as B2MML messages. The MES reads PLCs over OPC UA, Modbus TCP, EtherNet/IP or a serial gateway, starting read-only so production keeps running.

Done well, the integration replaces end-of-shift paperwork with confirmations posted from machine counts and gives you lot genealogy you can query. This post covers the integration patterns; whether to modernize at all is covered in our risk-aware guide to legacy modernization for manufacturers.

What do the ISA-95 levels mean for MES, ERP and PLC integration?

ISA-95 splits a plant into five functional levels and puts the MES at Level 3, between ERP at Level 4 and the control systems below it. According to ISA's summary of the standard, "The ISA-95 standard primarily deals with the interface between levels 3 and 4," which is why most of its object models describe what ERP and MES exchange.

ISA's series runs to eight parts, the earlier ones also published internationally as IEC 62264. Parts 2 and 4 define the objects (materials, equipment, personnel, schedules, performance) and Part 5 the business-to-manufacturing transactions.

ISA-95 levelFunctionTypical systems in a legacy plantWhat crosses the boundary above it
Level 4Business planning and logisticsSAP ECC or S/4HANA, Epicor, JobBOSS, Infor, an AS/400 package(top of the stack)
Level 3Manufacturing operations managementMES, historian, quality system, WMSOrders, material definitions, routings and BOMs down; performance, consumption, scrap and genealogy up
Level 2Monitoring and supervisory controlSCADA, HMI, batch servers, OPC servers such as KepwareTag values, alarms, counts, job status
Level 1Sensing and manipulating the processPLCs, I/O, sensors, drivesI/O values, registers, tags
Level 0The physical processMotors, valves, presses, moldsSignals

The levels describe functions rather than products, so plant diagrams disagree on whether a given PLC sits at Level 1 or Level 2. The integration rule: ERP does not talk to a PLC directly, and each record has one authoritative owner. ERP owns the item master and the MES may keep a read-only copy. When two systems can edit the same record, nobody can say which copy is right.

How should ERP send work orders and BOMs to the MES?

Send released production orders, material definitions, routings and BOMs from ERP to the MES as ISA-95 objects, and send confirmations back as performance records. ISA-95 defines the objects but no wire format. B2MML, the XML schema set that implements ISA-95, defines a message for each one, which makes it a sensible default when you write the contract yourself.

B2MML is a set of XSD schemas published by MESA International. The schema files are marked Version 0701. A B2MML-JSON schema ships too, but the README says "There has been no checking of this file," so treat XML as the reference. MESA ceased operations on 30 June 2026, with ISA taking over its educational programs and content. The schemas stay on GitHub (last commit July 2023), so pin the exact commit in your interface agreement.

Every B2MML message is a verb plus a noun. A Process message asks the receiver to act and can request an Acknowledge. A Get asks for data and expects a Show. A Sync publishes a change with no reply. Versions 5 and 6 had separate ProductionSchedule and ProductionPerformance schemas; version 7 drops them, and a production order is now an OperationsSchedule with OperationsType set to Production.

ERP objectISA-95 / B2MML V07 objectMessageDirectionNotes
Released production orderOperationsSchedule / OperationsRequestProcessOperationsSchedule, answered by AcknowledgeOperationsScheduleERP to MESSend on release. Edits use ChangeOperationsSchedule, withdrawals CancelOperationsSchedule.
Material masterMaterialDefinitionSyncMaterialDefinitionERP to MESERP is authoritative. MES keeps a read-only copy.
BOM (components and quantities)OperationsMaterialBill with OperationsMaterialBillItemSyncOperationsMaterialBillERP to MESItems carry quantity and effectivity dates. Send the version the order was released with.
RoutingOperationsDefinition (references the bill by BillOfMaterialsID)SyncOperationsDefinitionERP to MESVersion it, and define which revision a running order uses after an engineering change.
Material lot or batchMaterialLotSyncMaterialLotBothDecide once which side creates lot numbers.
Confirmations, goods issue, scrapOperationsPerformance / SegmentResponse with MaterialActualProcessOperationsPerformance or SyncOperationsPerformanceMES to ERPAggregate per operation or shift. If ERP backflushes, agree whether MES sends actual consumption.

Here is a released order for one shift on a molding line as a ProcessOperationsSchedule message. We validated it with xmllint --schema AllSchemas.xsd against the B2MML repository at commit 65cc665.

<ProcessOperationsSchedule xmlns="http://www.mesa.org/xml/B2MML" releaseID="7.0">
  <ApplicationArea>
    <CreationDateTime>2026-10-26T05:40:00-05:00</CreationDateTime>
  </ApplicationArea>
  <DataArea>
    <Process/>
    <OperationsSchedule>
      <ID>SCHED-2026-10-26-L3</ID>
      <OperationsType>Production</OperationsType>
      <OperationsRequest>
        <ID>1000457</ID>
        <StartTime>2026-10-26T06:00:00-05:00</StartTime>
        <EndTime>2026-10-26T14:00:00-05:00</EndTime>
        <HierarchyScope>
          <EquipmentID>PLANT1.MOLDING.LINE3</EquipmentID>
          <EquipmentLevel>ProductionLine</EquipmentLevel>
        </HierarchyScope>
        <OperationsType>Production</OperationsType>
        <SegmentRequirement>
          <ID>0010</ID>
          <ProcessSegmentID>MOLD-PA66</ProcessSegmentID>
          <OperationsDefinitionID>BRKT-4410-R03</OperationsDefinitionID>
          <OperationsSegmentID>0010-MOLD</OperationsSegmentID>
          <MaterialRequirement>
            <ID>OUT-1</ID>
            <MaterialDefinitionID>BRKT-4410</MaterialDefinitionID>
            <MaterialUse>Produced</MaterialUse>
            <Quantity><QuantityString>12000</QuantityString><UnitOfMeasure>EA</UnitOfMeasure></Quantity>
          </MaterialRequirement>
          <MaterialRequirement>
            <ID>IN-1</ID>
            <MaterialDefinitionID>RESIN-PA66-GF30</MaterialDefinitionID>
            <MaterialUse>Consumed</MaterialUse>
            <Quantity><QuantityString>310</QuantityString><UnitOfMeasure>KG</UnitOfMeasure></Quantity>
          </MaterialRequirement>
        </SegmentRequirement>
      </OperationsRequest>
    </OperationsSchedule>
  </DataArea>
</ProcessOperationsSchedule>

Element order matters: OperationsType inside an OperationsRequest must come after HierarchyScope, and a segment requirement must carry ProcessSegmentID, OperationsDefinitionID and OperationsSegmentID. In the current OperationsPerformance schema the top-level end time element is spelled Endtime, while OperationsSchedule uses EndTime. Validate every outbound message against the exact XSD version both sides agreed on. On the undocumented ISP CRM API we reverse engineered, schema validation caught three vendor-side changes before they reached production.

The integration layer translates from whatever the ERP speaks. On SAP ECC, SAP's MES integration documentation sends released production orders as IDoc LOIPRO03 through the data replication framework and posts production confirmations back through a BAPI. If you are on ECC and facing the 2027 maintenance deadline, writing the MES contract in B2MML instead of raw IDocs lets it survive the ERP change. On S/4HANA, the exchange can use OData APIs such as API_PRODUCTION_ORDER_2_SRV for orders and API_PROD_ORDER_CONFIRMATION_2_SRV for confirmations (see this connectivity vendor's S/4HANA guide). For ERPs with no outbound messaging, the translator polls the order tables.

How do you keep ERP and MES postings from duplicating or going missing?

Give every message a unique ID and make the receiver ignore IDs it has already processed. Queue outbound messages durably so an ERP outage only delays postings, and count a confirmation as posted when ERP returns a business acknowledgment with a document number.

B2MML has a slot for this: the ApplicationArea carries an optional BODID. Use it as the idempotency key, because the case to design for is a confirmation ERP posted whose response never came back; a blind retry posts the goods issue twice. You also need:

  • A per-order message log: what was sent, the acknowledgment, and the ERP document number it produced.
  • A dead-letter queue for messages ERP rejects (closed period, missing material, locked order), cleared daily by a named person.
  • In-sequence processing per order, so a confirmation never posts before its release.
  • A defined message and test case for changes and cancellations after release, partial completions, rework and reversals.
  • A daily report comparing MES totals with ERP postings per order, with PLC, MES and ERP clocks synced to one time source so shift boundaries line up.
  • Agreed state mapping. ERP may close an order on quantity while the MES still holds parts for quality disposition, so define which MES state triggers the final confirmation.

How does an MES connect to PLCs in a legacy plant?

Use whatever protocol each controller already speaks: OPC UA where it exists, EtherNet/IP for Rockwell, S7 or OPC UA for Siemens, Modbus TCP for many drives, meters and skids, and a driver or gateway for serial-only PLCs. Many plants put an OPC server at Level 2 so the MES reads one OPC UA address space.

Fill in this map per line during discovery (ports from the IANA registry).

Controller familyProtocolPortHow data is addressedKeeping MES access read-onlyWhat bites you
OPC servers (Kepware, Ignition) and embedded UA serversOPC UA binary, opc.tcp://TCP 4840 registered; many servers use another portBrowsable nodes with types and engineering unitsA dedicated MES user with read-only roles on the serverCertificate trust setup, and security mode left at None
Rockwell ControlLogix, CompactLogixEtherNet/IP (CIP)TCP 44818 messaging, UDP 2222 I/OSymbolic tag names, UDTs, arraysRead-only access at the OPC server, or External Access on telemetry-only tagsExternal Access applies to every external client, including HMIs
Siemens S7-300/400/1200/1500S7comm over ISO-on-TCP, or the S7-1500's built-in OPC UA serverTCP 102 (S7), 4840 (UA)DB number plus byte offset (classic S7), or symbols (UA and symbolic drivers)Read-only items in the OPC server or collector, plus firewall rulesClassic absolute access to S7-1200/1500 needs PUT/GET enabled and non-optimized DBs
Modicon, drives, meters, many OEM skidsModbus TCPTCP 502 (802 for Modbus/TCP Security)Register numbers, zero-based on the wireCollector issues only function codes 01 to 04; a function-code-aware gateway or firewall blocks writesOff-by-one addresses and multi-register word order
SLC 500, PLC-5, MicroLogix on serialDF1 (RS-232/485), DH+None (serial)Data files such as N7:0, F8:3Read-only items in a DF1 driver such as Kepware's, or a DF1/DH+ gatewayIf the HMI owns the only serial port, the gateway becomes a single point of failure

Every extra client adds communication load on the controller, read-only or not. Baseline each controller's comms load and connection count, start the collector slow on a few tags, and raise the rate in steps while watching scan time, against stop criteria the controls engineer sets.

Modernizing Manufacturing Systems?

Get a free architecture review for your industrial software. IoT integration and legacy migration expertise.

Download: Manufacturing Legacy Modernization Checklist

Zero-downtime migration playbook for SCADA, MES & ERP systems. Includes system assessment, risk matrix, ROI calculator, and phased cutover plan.

No spam. We respect your privacy.

On OPC UA, the specification defines three security modes: None, Sign and SignAndEncrypt. Move servers left at None to Sign or SignAndEncrypt before go-live. For pushing job orders to machines, the OPC Foundation's ISA-95 Job Control companion specification (OPC 10031-4) defines methods such as StoreAndStart, Pause and Abort; version 2.00 (2024) is incompatible with 1.0, so ask which one your machine builder implemented. For the wider protocol choice, see our comparison of OPC UA and MQTT on the factory floor.

On Rockwell, Studio 5000 gives every tag an External Access setting of Read/Write, Read Only or None, and newer versions add a separate OPC UA Access property. External Access covers all external applications, so Read Only also stops a PanelView from writing the tag; lock down only tags nothing external writes. For a custom collector, libplctag reads ControlLogix tags and PLC-5, SLC 500 and MicroLogix data.

On Siemens, the route depends on the driver. Classic absolute-address reads from an S7-1200 or S7-1500 need "Permit access with PUT/GET communication from remote partner" ticked in the CPU protection settings and, on the S7-1500, data blocks with standard (non-optimized) access, as this integration vendor's S7 setup guide documents. Siemens describes the S7-1500's built-in OPC UA server as offering symbolic access to PLC data. Either route is a PLC project change that goes through the controls engineer.

On Modbus, the Modbus Application Protocol Specification V1.1b3 makes discrete inputs and input registers read-only and uses 16-bit registers. Function codes 03 and 04 read 1 to 125 registers per request. The spec numbers registers from 1 in the data model but addresses them from 0 in the PDU, so "register 40101" in a manual is address 100 on the wire with function code 03. A 32-bit value takes two registers and a 64-bit value four, in an order the device documentation defines.

How do you reverse engineer an undocumented PLC data map without touching production?

Capture the traffic that already flows between the PLC and its HMI or SCADA, decode it, and build a candidate tag map from what the existing systems read. Confirm each tag with slow, read-only polling while a controls tech watches the HMI. Nothing writes to the controller during discovery.

On a line with no usable documentation, we work in this order:

  1. Get the PLC program if one exists. Even an uncommented .ACD or .RSS file gives you names; check its revision against the running controller.
  2. Mirror the HMI's switch port to a capture laptop. Per the Wireshark capture setup guide, use a SPAN port on a managed switch or a network tap, and make the monitoring port at least as fast as the mirrored one or you lose packets. Inserting a tap breaks the link while you cable it, so do it during a planned stop.
  3. Capture across a full cycle, a changeover and at least one fault. Wireshark ships dissectors for Modbus/TCP, EtherNet/IP and S7comm, so requests show up as register ranges, tag names or DB offsets.
  4. Build the candidate map: address, type guess, observed range and HMI label. Counters climb, states sit in small integer sets, temperatures drift.
  5. Confirm candidates with read-only polling and mark each tag confirmed, suspected or unknown. Watch for HMI-side scaling: a temperature showing 123.4 on screen may be the integer 1234 in the register.
  6. Commit the confirmed map to version control with the PLC program revision it matches, and re-verify it after every program download.

The capture only shows what something already asks for; data no HMI reads has to come from the program file or the OEM. OPC UA traffic with SignAndEncrypt cannot be decoded from a capture, and serial DF1 links need a serial port monitor or tap instead of a SPAN port.

This snippet decodes two captured Modbus holding registers as an IEEE 754 float in both word orders:

import struct

def regs_to_float(w0, w1, word_swap=False):
    hi, lo = (w1, w0) if word_swap else (w0, w1)
    return struct.unpack(">f", struct.pack(">HH", hi, lo))[0]

# FC03 response for registers 40101-40102 (PDU address 100, count 2)
regs = [0x42F6, 0xE979]
print(regs_to_float(*regs))                   # 123.45600128173828 (HMI shows 123.5 deg C)
print(regs_to_float(*regs, word_swap=True))   # -1.8833671448184637e+25

The wrong order is not always this obvious, and the value may be a scaled integer, so check several known operating values before trusting a decode.

On OEM equipment with its own software license, check those terms before decoding anything beyond the plant's own PLC program. This is not legal advice; review your license agreement and MSA with counsel. Our post on whether it is legal to reverse engineer software covers the general rules.

Where should the integration layer sit on the network?

Put the MES and its translators on the plant side, put anything the business network touches in a DMZ, and allow no direct connection from the corporate network to a PLC. NIST's OT security guide, SP 800-82 Rev. 3, recommends a DMZ to "prevent direct traffic between the corporate and OT networks" and says communications between the enterprise level and the operations management level "are required to go through services within the DMZ."

ERP-to-MES traffic lands on a broker in the DMZ and the MES pulls from it; PLC traffic stays on the plant network. ANSI/ISA-62443-3-2 groups assets into zones and defines a conduit as a "logical grouping of communication channels that share common security requirements connecting two or more zones." The firewall rules should be exactly the conduit list. Plain Modbus TCP has no authentication (the Modbus Security protocol adds TLS on port 802), and a port rule cannot tell a read from a write, so blocking Modbus writes takes a device that inspects function codes.

What does a low-risk MES rollout sequence look like?

Roll out in read-only stages and cut over one line at a time: shadow mode, a parallel run against the current paperwork, then cutover at a planned changeover with a written rollback. Writes to PLCs come last, if at all. Any step that needs a stop goes into an existing changeover or maintenance window.

StageWhat runsExit criteriaRollback
1. DiscoveryCapture, program review, integration map, conduits, baseline controller loadEvery tag the MES needs is confirmed or explicitly out of scopeNothing to roll back
2. Shadow modeMES reads PLCs read-only and receives ERP orders; nobody acts on its outputCounts match PLC and HMI totals for two weeks, within a tolerance written down beforehand (exact match per shift is a fair target for discrete parts); controller load within stop criteriaTurn the collector off
3. Parallel runOperators use the MES and the old method side by side on one lineDaily reconciliation of good parts, scrap and consumption shows no unexplained gapsOperators drop the MES screen; the old method never stopped
4. Line cutoverMES posts to ERP for that line; the old paper record continues as a fallback for the first weeksOne full production week with clean ERP postings and no manual correctionsStop posting at a shift boundary, hold the queue, reconcile ERP postings by message ID, resume from the fallback record
5. Repeat per lineSame steps, usually shorter each timeSameSame, per line
6. Writes (optional)MES sends recipes or job orders via OPC UA Job Control or a handshake tag setControls team signs off the handshake, tested on a non-production controllerPLC ignores MES writes when a local "MES enable" bit is off

Cut over at a changeover or shift boundary, and let the order already running finish on the old method so no order is split. The line's production supervisor reviews and signs off the stage 3 reconciliation every morning. The "MES enable" bit belongs to the controls engineer, so the plant can disconnect the MES from a machine without touching the MES.

A plant historian gives shadow mode weeks of stored counter values to compare against; see our post on extracting data from Wonderware Historian and OSIsoft PI.

Which integration pattern should a small or mid-size plant pick?

Use a vendor connector when one exists and you can inspect its output, and a small ERP-to-B2MML translator when there is none. Add an OPC server at Level 2 once you have two controller families, and a message broker or iPaaS once several systems share the same messages. Avoid a mesh of scripts writing to each other's tables; our guide to legacy system integration makes the case for one documented contract per boundary.

To start, inventory every PLC in scope with model, firmware and program revision, every place counts or material usage get written down today, and the ERP transactions the plant posts. If you want a second set of eyes on the resulting integration map and conduit list, our industrial IoT development team can review them; send them over.

Download: Manufacturing Legacy Modernization Checklist

Zero-downtime migration playbook for SCADA, MES & ERP systems. Includes system assessment, risk matrix, ROI calculator, and phased cutover plan.

No spam. We respect your privacy.

Daniel Ashcraft

Industrial Software

Founder & Lead Engineer at Of Ash and Fire. Building custom software for healthcare, education, and manufacturing.

Founder & Lead Developer at Of Ash and Fire · Test Double alumni · Former President, Techlahoma Foundation

12+ years building industrial software including SCADA modernization, IoT data pipelines, and WebGL 3D visualization for manufacturing and energy companies.

Frequently Asked Questions

What is the standard for MES integration with ERP?+
ISA-95, also published internationally as IEC 62264, defines the objects that cross the boundary between ERP at Level 4 and the MES at Level 3, such as schedules, material definitions and performance records. It does not mandate a wire format. B2MML is the XML schema set that implements those objects, and messages like ProcessOperationsSchedule are a common choice when you write the contract yourself.
Which protocol should an MES use to read PLC data?+
Use what the controller already speaks. Rockwell Logix controllers use EtherNet/IP on TCP 44818, Siemens S7 controllers use S7comm on TCP 102 or the S7-1500's built-in OPC UA server, and many drives, meters and OEM skids use Modbus TCP on port 502. Older serial PLCs need a DF1 driver or a gateway. Many plants put an OPC server in front of all of them.
Can you connect an MES to PLCs without stopping production?+
Usually, for reading. Mirror a switch port, poll read-only starting slow while watching controller scan time, and run the MES in shadow mode. Some steps are PLC or network changes that plants schedule into a planned stop, such as inserting a network tap or enabling PUT/GET on an S7-1200 or S7-1500. Fit those into existing changeovers or maintenance windows.
What data should flow from the MES back to the ERP?+
Send confirmations of good quantity, scrap, material consumption and lot genealogy, usually aggregated per operation or per shift. In B2MML that is an OperationsPerformance message. On SAP ECC, production confirmations go back through a BAPI; on S/4HANA, through the production order confirmation OData API. Give each message a unique ID so retries cannot post twice.
How do you find the tag map for an undocumented PLC?+
Start with the PLC program file if it exists. Then capture traffic between the PLC and its HMI through a SPAN port, decode it with Wireshark's Modbus/TCP, EtherNet/IP or S7comm dissectors, and list every address the HMI reads. Confirm each tag with read-only polling, watch for HMI-side scaling, and version the map against the PLC program revision.

Modernizing Legacy Manufacturing Systems?

We migrate SCADA, MES, and ERP systems without production downtime. Phased rollouts, data pipeline engineering, and real-time dashboards.