Skip to main content

Bridging to DCS, MES & ERP: DeltaV, Siemens, SAP

πŸ“ Where we are: Part IV Β· Meeting Reality β€” having bridged the validated historian, we now wire our open-source stack to the three systems it will never replace: the DCS that runs the plant, the MES that executes the recipe, and the ERP that owns the materials and orders.

The simple version

Picture a hospital. The operating room (the DCS) controls the patient minute by minute; the surgical workflow board (the MES) says which procedure happens in which room and signs that each step was done; the hospital's billing and supply office (the ERP, i.e. SAP) owns the inventory, the bed assignments, and the paperwork. You would not let a clever new app run the anesthesia, sign the surgical record, or issue the supply orders β€” those are life-and-records-critical and already accountable. What a new app can do is listen (read what the OR is doing), mirror the workflow board so analytics can see it, and exchange notes with the supply office through the official mail slot. This chapter builds those three listening posts and mail slots β€” and is blunt about the one thing open source genuinely cannot give you here: a credible GxP MES.

What this chapter covers​

For seventeen chapters we built a complete open-source platform and, last chapter, taught it to live politely beside a commercial historian. The historian was the friendly commercial system: it speaks open protocols and trades in time-series we already understand. This chapter walks into harder country, where the honest-hybrid boundary is drawn not by taste but by necessity:

  • Reading DeltaV and Siemens control data into the OSS stack over OPC UA, without touching the validated control loop.
  • The blunt verdict that there is no credible open-source GxP MES, and what that means for your architecture.
  • Exchanging materials, lots, and work orders with SAP/ERP using B2MML/ISA-95 messages over IDoc and OData.
  • Why every one of these exchanges must be idempotent and reconcilable, and how the ISA-88/95 model we already built in PostgreSQL is the landing pad for all of it.

The thread tying it together is a single sentence you can take into any design review: the validated DCS, MES, and ERP remain the systems of record; the open-source layer mirrors them, it does not replace them [12].

Three systems, three different doors​

The historian had two doors. DCS, MES, and ERP each have their own, and they are not interchangeable. The trap is to treat them as one "integration" problem. They are three problems with three contracts, three trust levels, and three honest verdicts.

Each commercial system exposes a standard door; the OSS layer reads through it and lands the result in the ISA-88/95 model. Notice every arrow points into the mirror β€” the OSS layer is read-mostly here by design.

The Purdue/ISA-95 levels make the direction obvious. The DCS sits at Levels 1-2, the MES at Level 3, the ERP at Level 4 [1]. Data, and trust, flow down from those validated systems into our analytics mirror β€” almost never the other way. That single design rule keeps us out of trouble.

DeltaV and Siemens: read the control layer over OPC UA​

A distributed control system (DCS) is the validated brain of a bioreactor suite β€” it holds the loops that keep BR101 at 37 Β°C and pH 7.0. You do not reimplement those loops in open source, and you do not write setpoints from a Python script into a GMP control system. What you do is read.

Both major vendors hand you a clean read path. Emerson's DeltaV natively exposes runtime parameters, alarms and events, and historical data over an OPC UA server, so any standard OPC UA client can browse the DeltaV address space and subscribe to values without altering the control system [5]. Siemens SIMATIC controllers act as OPC UA servers in exactly the same way, offering a manufacturer- and platform-independent, secured channel up to higher layers [6]. OPC UA itself is the standard that makes this portable: a platform-independent, service-oriented, secure architecture standardized as IEC 62541 [4].

Because our bioreactor already speaks OPC UA β€” Chapter 5 built the opcua-collector against the opcua-server β€” a DCS bridge costs us almost nothing on the client side. The open-source SDKs are mature: node-opcua (MIT) can browse, read, write, and subscribe to a DCS/PLC OPC UA server [8], and the Python asyncua library we already use does the same. The bridge is the same shape as every capture chapter: subscribe to a node, receive value + quality + timestamp, write a row.

Since neither a DeltaV nor a SIMATIC server runs on a laptop, the companion repo follows the same honest-hybrid pattern it uses for PI and SAP: a small dcs-mock (an asyncua server exposing DeltaV/PCS7-style nodes, behind the commercial profile) is on the build roadmap so the bridge can be developed and contract-tested with no licensed hardware in reach. As of this writing the repo ships the pi-web-api-stub under the commercial profile but not yet the dcs-mock, so treat the DCS node addresses below as the target contract you pin and build against β€” the production swap is a server URL and a certificate, not a code change. A DeltaV value, read over OPC UA, would arrive in exactly the shape our historian already stores (illustrative OPC UA read result, the contract a real DeltaV Edge server honors):

{ "NodeId": "ns=3;s=BR101/PID-101/PV", "DisplayName": "BR101.Temp.PV",
"Value": 37.02, "StatusCode": "Good", "SourceTimestamp": "2026-01-12T08:30:00Z",
"Unit": "degC" }

That maps, one-to-one, onto the ts.sensor_reading row contract you saw the loader use last chapter (ts, tag, value, unit, quality, batch_id). StatusCode "Good" becomes OPC quality 192; the DCS tag BR101.Temp.PV is already a name our Chapter 4 tag dictionary recognizes. The DCS bridge is, deliberately, the least exciting code in the book β€” and that is the point.

The bridge code already exists β€” as a working template. You do not have to imagine the DCS bridge: it is a near-clone of the PI bridge we shipped and tested last chapter. That bridge lives in examples/chapters/17-bridge-pi-historian/pi_bridge.py, and it is exercised by examples/tests/test_bridges.py against the pi-web-api-stub. Its two load-bearing functions are the exact shape a DCS bridge needs β€” map a vendor's quality flag to an OPC quality code, then emit the canonical row tuple:

# examples/chapters/17-bridge-pi-historian/pi_bridge.py
def quality_code(item: dict) -> int:
"""PI Good/Questionable/Substituted -> OPC UA quality code."""
if item.get("Questionable"):
return 64 # Uncertain
return 192 if item.get("Good", True) else 0 # Good / Bad


def to_sensor_rows(items: list[dict], tag: str, batch_id: str) -> list[tuple]:
"""Map PI recorded values to ts.sensor_reading rows (ts, tag, value, unit, quality, batch_id)."""
return [(it["Timestamp"], tag, it["Value"], it.get("UnitsAbbreviation"),
quality_code(it), batch_id) for it in items]

The DCS bridge clones this exactly: where pi_bridge.read_recorded() does client.get(".../streams/{wid}/recorded") against PI Web API, the DCS version subscribes to an OPC UA node with asyncua and receives value + StatusCode + SourceTimestamp; quality_code() maps the DCS "Good" to 192 instead of PI's flag, and to_sensor_rows() is reused verbatim. The honest status: the dedicated opcua_dcs_bridge.py and its dcs-mock land in the repo only when the commercial profile gains a dcs-mock server (the pi-web-api-stub is already there; see below). Until then, pi_bridge.py is the committed, tested artifact that defines the bridge's shape β€” the DCS port is a different transport call wrapped around the same quality_code / to_sensor_rows core.

When OPC UA is not on offer. Older Siemens skids sometimes expose only the native S7 protocol, with no OPC UA server in front. Here the open-source answer is Apache PLC4X (Apache 2.0), a vendor-neutral library whose S7 (Step7) driver reads and writes Siemens S7 PLCs directly [7]. It speaks dozens of legacy protocols, so it is the bridge of last resort for the brownfield skids OPC UA forgot. Chapter 9 used the same read-a-tag-write-a-row pattern for legacy Modbus skids (with pymodbus, in examples/chapters/09-legacy-skids-modbus-s7/modbus_reader.py); the DCS case applies that pattern through PLC4X instead, pointed at a control system rather than a standalone skid.

One honesty note carried over from the connectivity chapters: a read-only OPC UA channel up to a monitoring layer is precisely the NAMUR Open Architecture idea β€” a second, read-mostly data path for analytics that leaves the validated core untouched. It is the architecturally sanctioned way to get DCS data without re-validating the DCS.

The MES: the honest verdict is "no credible OSS GxP option"​

Now the hard sentence. A Manufacturing Execution System (MES) at ISA-95 Level 3 is what turns a master recipe into an executed, electronically signed batch record: it dispenses materials against a work order, enforces the order of operations, captures operator e-signatures, and produces the reviewed electronic batch record (EBR) that a quality unit releases against. It is the most heavily validated, most Part-11-saturated system on the floor.

There is no open-source product that credibly fills this slot for GMP biomanufacturing. This is not an oversight in the book's tool survey; it is the survey's finding. You can assemble pieces β€” a workflow engine here, an eLN there, our own ISA-88/95 model in PostgreSQL β€” but none of them carries the validated, vendor-accountable, Part-11-complete execution-and-e-signature package a commercial MES (or a validated paper-on-glass system) provides. GAMP 5's second edition is explicit that open source can be used in GxP, but only inside a validated lifecycle with risk-based, critical-thinking assurance proportionate to use [12] β€” and assembling a homemade MES and validating it to that bar is a multi-year program no analytics team should pretend to win on the side.

So the architecture follows the verdict. The commercial MES (or paper-on-glass) stays the system of record for execution. Our open-source layer does two legitimate things instead:

  1. It mirrors the MES's structural output β€” the recipe, the operations and phases, the batch and its actual phase windows β€” into the relational model we already built, so analytics and dashboards have context.
  2. It never originates the execution record. No e-signature, no material disposition, no release decision lives in the OSS layer.

The good news is that the mirror is already built. The MES's world is ISA-88/95, and we modeled ISA-88/95 in PostgreSQL back in Chapter 3. Here is the actual backbone, from examples/platform/db/10-isa88-95.sql β€” the equipment hierarchy and the procedural model an MES exports map straight onto these tables:

-- 10-isa88-95.sql β€” the relational backbone (Chapter 3).
CREATE TABLE s88.unit ( -- the equipment a phase runs on
unit_id text PRIMARY KEY, -- e.g. BR101
area_id text NOT NULL REFERENCES s88.area,
name text NOT NULL,
unit_type text NOT NULL, -- bioreactor | chromatography | tff | fill_line ...
vendor text,
model text
);

CREATE TABLE s88.operation ( -- an ordered step of the recipe
operation_id text PRIMARY KEY,
recipe_id text NOT NULL REFERENCES s88.recipe,
seq_no int NOT NULL,
name text NOT NULL, -- Inoculation | Fed-batch | Harvest | ProteinA ...
unit_type text NOT NULL
);

CREATE TABLE s88.phase ( -- the smallest procedural element
phase_id text PRIMARY KEY,
operation_id text NOT NULL REFERENCES s88.operation,
seq_no int NOT NULL,
name text NOT NULL
);

And the batch β€” the run a work order produces β€” with the genealogy that lets us trace a finished lot back to its seed train, exactly as an MES would record it (also from examples/platform/db/10-isa88-95.sql):

CREATE TABLE s88.batch (
batch_id text PRIMARY KEY,
product_id text NOT NULL,
recipe_id text NOT NULL REFERENCES s88.recipe,
unit_id text NOT NULL REFERENCES s88.unit,
lot text,
status text NOT NULL DEFAULT 'in_progress', -- in_progress | complete | released | rejected
start_ts timestamptz NOT NULL,
end_ts timestamptz
);

-- lot genealogy: directed edges child -> parent (seed -> bioreactor -> pool -> DS -> DP)
CREATE TABLE s88.genealogy (
batch_id text REFERENCES s88.batch,
child text NOT NULL,
child_type text NOT NULL,
parent text NOT NULL,
parent_type text NOT NULL,
PRIMARY KEY (child, parent)
);

This is the receiving end of every MES and ERP message in the chapter. When SAP sends a production order, it becomes a row in s88.batch. When the MES reports the actual phase windows, they land in s88.batch_phase. When the ERP reconciles which component lots fed which product lot, those edges land in s88.genealogy. Our seed data already populates this for the running case β€” the ACME Biologics enterprise, the Newark DE Plant site, BR101 (a Sartorius Biostat STR 50), the CHO-MAB-001 fed-batch recipe, and six campaign batches including the deliberately rejected BATCH-2026-004. The mirror is real and queryable today; what it must not do is become the place the batch is executed and signed.

Three lanes feeding one open-source mirror. The OT lane reads DeltaV and Siemens over OPC UA into TimescaleDB; the MES Level-3 lane exports batch structure as B2MML into a PostgreSQL ISA-88/95 model, with a red dashed barrier marking no e-signature and no release in open source; the ERP Level-4 lane exchanges SAP materials and orders via IDoc and OData. Every arrow points into the mirror.

The honest-hybrid boundary at Levels 1-4: open source reads the DCS over OPC UA, mirrors MES batch structure as B2MML, and exchanges ERP messages β€” but the validated systems keep execution, signatures, and disposition. The OSS layer is the mirror, never the original. Original diagram by the authors, created with AI assistance.

SAP/ERP: exchange materials, lots, and work orders with B2MML​

The ERP β€” in biopharma, overwhelmingly SAP S/4HANA β€” owns the business truth: which materials exist, which lots are released or quarantined, and which work order authorizes which batch. The OSS layer needs that context to make a sensor reading meaningful, and occasionally needs to report a result back. The standard, vendor-neutral way to do this is ISA-95 / IEC 62264 object models serialized as B2MML (Business To Manufacturing Markup Language), an XML implementation of ISA-95 that any company may use royalty-free with attribution to MESA [1][2][3]. The literature backs the pattern: peer-reviewed ERP↔MES integrations use ISA-95 object models implemented as B2MML XML transaction messages [11].

SAP itself offers two concrete doors. The classic one is the IDoc (Intermediate Document), SAP's structured message for exchanging materials, lots, and order data, which integration layers convert to and from B2MML [10]. The modern one is OData: SAP S/4HANA exposes production orders and related material data through documented OData APIs β€” for example API_PRODUCTION_ORDER_2_SRV β€” that an OSS client consumes to mirror ERP state [9]. As with PI and the DCS, the companion repo's plan is a sap-mock (a FastAPI service offering an OData endpoint plus an IDoc XML drop folder, behind the commercial profile) so the exchange is built and contract-tested with no SAP license; it is on the roadmap, not yet shipped, so the snippets here are the target contract.

A production order, pulled from the OData door, would look like this (illustrative SAP OData JSON matching the documented API_PRODUCTION_ORDER_2_SRV shape):

{
"d": {
"ManufacturingOrder": "1000004711",
"Material": "MAB-001",
"ProductionPlant": "NEWARK",
"MfgOrderPlannedTotalQty": "1",
"ProductionUnit": "EA",
"MfgOrderScheduledStartDate": "2026-01-05T00:00:00Z",
"OrderIsReleased": true
}
}

The bridge's job is to land that in our model without ever pretending to be SAP. The mapping is small and explicit β€” Material β†’ s88.batch.product_id, ProductionPlant β†’ the site, the order ID kept as a reference so the mirror can be reconciled back to SAP:

-- Mirror a released SAP production order into the ISA-88/95 batch table.
-- SAP stays the system of record; this row is a faithful copy keyed to the order.
INSERT INTO s88.batch (batch_id, product_id, recipe_id, unit_id, lot, status, start_ts)
VALUES ('BATCH-2026-007', 'MAB-001', 'CHO-MAB-001', 'BR101', 'L26007',
'in_progress', '2026-01-05T00:00:00Z')
ON CONFLICT (batch_id) DO UPDATE
SET status = EXCLUDED.status,
product_id = EXCLUDED.product_id; -- idempotent: re-applying the same order is a no-op

The same B2MML envelope carries material-lot information the other direction. ISA-95's MaterialLot and MaterialDefinition objects map onto our s88.genealogy edges, so when the ERP confirms that capture pool PApool-007 came from bioreactor batch BATCH-2026-007, the mirror records the lineage exactly as the seed data already does for the golden batch:

batch_id,child,child_type,parent,parent_type
BATCH-2026-001,SEED-001,seed_train,WCB-CHO-001,wcb
BATCH-2026-001,BATCH-2026-001,bioreactor,SEED-001,seed_train
BATCH-2026-001,PApool-001,capture_pool,BATCH-2026-001,bioreactor
BATCH-2026-001,DS-001,drug_substance,PApool-001,capture_pool
BATCH-2026-001,DP-001,drug_product,DS-001,drug_substance

That is real data from examples/datasets/lot_genealogy.csv β€” the full seed-train β†’ bioreactor β†’ capture-pool β†’ drug-substance β†’ drug-product chain for one lot, the exact lineage an ERP-driven genealogy exchange would reconstruct.

Idempotency and reconciliation: the rule that keeps a mirror honest​

Every exchange in this chapter is a copy, and copies drift. The discipline that keeps a mirror trustworthy is the same one the historian chapter introduced: make every write idempotent, and reconcile rather than overwrite.

Idempotent means re-applying the same message changes nothing. SAP redelivers IDocs; OData polls overlap; a DCS subscription replays after a reconnect. If "production order 1000004711 is released" arrives three times, you must end with one batch row, not three. The ON CONFLICT (batch_id) DO UPDATE above is precisely that guarantee β€” the order is keyed, and re-applying it is a no-op. (Note the asymmetry with time-series: the historian's hypertable carries no unique key on (tag, ts), so a DCS backfill earns idempotency by delete-window-then-insert, exactly as Chapter 17 described, while the relational batch/genealogy tables earn it through primary keys and ON CONFLICT.)

Reconciliation means you periodically ask both systems the same question and assert they agree. Does every released SAP order have exactly one mirrored batch? Does every s88.genealogy edge trace to an ERP-confirmed material lot? A divergence is logged as a data-integrity event, never silently fixed β€” because a silent fix in a mirror is how a shadow record is born, and a shadow record that disagrees with the validated system of record is exactly what an inspector hunts for. The rule, stated once: the OSS layer must sync faithfully and never become a parallel record of authority.

Why it matters​

Get these three bridges wrong and you fail in one of two recognizable ways. Either you over-reach β€” you let the open-source layer write setpoints to the DCS, sign batch records, or dispose material β€” and you inherit a validation and Part-11 burden the OSS tools cannot carry, for no patient benefit and considerable regulatory peril. Or you under-engineer the exchange β€” non-idempotent writes, no reconciliation β€” and your mirror quietly drifts from SAP and the MES until a dashboard contradicts the official record during a release review.

Get them right and the division of labor is clean and liberating. The DCS keeps controlling the process; the MES keeps executing and signing the batch; SAP keeps owning materials and orders. The open-source layer reads all three through standard doors, lands everything in one ISA-88/95 model, and becomes the fast, cheap, unconstrained place to do contextualization, SPC, and soft-sensing on top of trustworthy context. Each commercial system stays the system of record for what it is accountable for [12]; the standards β€” OPC UA for control [4], B2MML/ISA-95 for business exchange [1] β€” are the seams that make the mirror faithful.

In the real world​

Walk into an approved-product mAb plant and this is the topology you find: a DeltaV or PCS7 DCS running the suites, a commercial MES (Werum PAS-X, KΓΆrber, Tulip-on-glass, or similar) holding the electronic batch record, and SAP at the top owning everything financial and material. Integration teams spend their careers on exactly the doors this chapter describes β€” OPC UA off the DCS, B2MML/IDoc/OData to and from SAP β€” and they spend it precisely because nobody is allowed to replace the validated systems underneath.

The honest OSS-vs-commercial verdict for this layer is the bluntest in the book. For the DCS read path, open source is excellent: node-opcua [8], asyncua, and Apache PLC4X [7] give you everything you need to mirror control data, and the read-only NOA pattern means you do it without re-validating anything. For ERP exchange, open source is fully capable: B2MML is a free, royalty-free schema [3], and a Python OData/IDoc client is a weekend, not a year. But for the MES slot, there is simply no credible open-source GxP product, and pretending otherwise would be the most dangerous overstatement this book could make. Pure open source gets you roughly 80% of the platform; the MES is one of the places the last GxP mile is not hybrid but firmly, honestly commercial.

NIIMBL β€” the U.S. public-private Institute for the advancement of biopharmaceutical manufacturing β€” funds the interoperability and standards work that makes these doors real, and its SABRE facility (a pilot-scale cGMP β€” current Good Manufacturing Practice β€” facility at the University of Delaware, which broke ground in April 2024 and remains under construction as of mid-2026) is the kind of site where an open-source analytics layer would sit beside validated DCS, MES, and ERP, reading through standard doors rather than supplanting them. The intensified/continuous variant of our process β€” perfusion with multi-column capture β€” only multiplies the tags and the orders flowing across these seams, which makes a disciplined, idempotent, reconcilable mirror more valuable, not less.

Key terms​

  • DCS (Distributed Control System) β€” the validated control layer (Emerson DeltaV, Siemens PCS7/SIMATIC) that runs the process loops at ISA-95 Levels 1-2; the OSS layer reads it over OPC UA [5][6], never writes setpoints to it.
  • MES (Manufacturing Execution System) β€” the Level-3 system that executes the recipe, captures e-signatures, and produces the electronic batch record; there is no credible open-source GxP option, so it stays commercial [12].
  • ERP (Enterprise Resource Planning) β€” the Level-4 business system (SAP S/4HANA) owning materials, lots, and work orders, exchanged via IDoc and OData [9][10].
  • OPC UA β€” IEC 62541; the platform-independent, secure protocol over which DCS/PLC control data is read into the OSS stack [4].
  • Apache PLC4X β€” vendor-neutral open-source library (Apache 2.0) that reads Siemens S7 and many legacy PLC protocols when no OPC UA server is on offer [7].
  • node-opcua β€” MIT-licensed Node.js OPC UA SDK used to browse/read/subscribe to DCS OPC UA servers [8].
  • B2MML β€” Business To Manufacturing Markup Language; the royalty-free XML implementation of ISA-95 used for materials/lots/work-order exchange [2][3].
  • ISA-95 / IEC 62264 β€” the standard models and terminology for enterprise-to-control integration that B2MML serializes and our PostgreSQL model implements [1][11].
  • IDoc / OData β€” SAP's classic message format and modern REST API for exchanging order and material data [9][10].
  • System of record β€” the authoritative, validated source for a kind of data; here, the DCS for control, the MES for execution, SAP for materials. The OSS layer is a mirror, never the record [12].
  • Idempotent β€” a write safe to repeat; relational mirrors earn it with primary keys and ON CONFLICT, time-series with delete-window-then-insert.
  • Shadow record β€” an uncontrolled parallel copy that diverges from the validated system of record; the failure mode reconciliation exists to prevent.
  • NAMUR Open Architecture (NOA) β€” a sanctioned read-mostly second channel that feeds analytics without altering the validated control core.

Where this leads​

Control and business data now mirror faithfully into our stack, but one system of record still stands outside: the laboratory. Release testing β€” the assays that decide whether a lot ships β€” often lives in a commercial LIMS, and the result it signs is the one that matters most. The next chapter, Bridging to Commercial & Open-Source LIMS, builds the sample-and-certificate-of-analysis exchange to that world, with the honest note that LabKey's Part 11 features are paywalled and that SENAITE and openBIS fit the QC and process-development slots respectively β€” the same mirror-not-replace discipline, applied where the stakes are highest.