---
title: Packaging Hall
slug: solutions/packaging-hall
docTags: 
createdAt: 2026-10-01T23:17:36.653Z
---

A soft drink packaging hall with three identical 500 mL PET lines at 400 bottles a minute, all running from one syrup room, one compressor set and one CIP skid. 18 devices and 293 tags over Modbus TCP.

| Operator view      | port **4021**                  |
| ------------------ | ------------------------------ |
| Protocol server    | port **5031**                  |
| Devices and tags   | 18 devices, 293 tags           |
| Litmus Edge driver | Modbus TCP                     |
| Image              | `food-beverage-multiline-demo` |

## Overview

A single line cannot show what goes wrong when three lines share one set of utilities. Each line here is filler, capper, labeler, checkweigher and case packer, and every line runs the same SKU at the same nominal rate. The three shared assets are their own ISA-95 area, separate from the lines:

| Shared asset                     | What it serves                                                                 |
| -------------------------------- | ------------------------------------------------------------------------------ |
| Syrup room and product ring main | one transfer pump feeding all three fillers                                    |
| Compressed air plant             | three compressors and one receiver, shared by all three lines and the CIP skid |
| CIP skid                         | one skid, three lines, each due for a clean on its own clock                   |

Three of the five scenarios start in a shared asset, so they arrive on every line at once and cannot be traced from any single line's data. The other two are confined to one line, with two healthy lines beside it for comparison.

## Scenarios

**Shared-utility contention.** The product header pressure depends on how many lines are drawing, and no line publishes it. The fillers need 2.60 bar. With a worn pump impeller the header holds 2.80 bar at two lines and falls to about 2.18 bar at three, so the fault costs nothing until the third line starts. In six minutes that is 166 rejected bottles against 17 on a healthy pump. Each filler then lengthens its fill time to hold its mean, and when the sag clears, or a line is simply stopped, that trim is left standing: giveaway rises on every line, including the two that were never affected, a minute after the cause has gone.

**Loose caps on every line at once.** The compressed air plant carries three lines and the CIP skid with a small margin. Trip one compressor and two lines still fit. Run three and the header falls below 6.20 barg, every pneumatic capping head delivers under its setpoint, and 37 percent of caps go out below the 1.35 Nm seal minimum on all three lines, with 7 rejects. A loose cap weighs the same as a good one, so it passes the checkweigher. The only tag that reports it is the capper's `LowTorqueCount`, and the cause is on a different device.

**CIP scheduling and verification.** One skid serves three lines, so a clean is a resource a line waits for, and a wait past the clean window is a compliance event rather than a rate loss. When the caustic wash runs cold, the cycle completes normally and fails verification, and the line rejoins the queue behind whoever is already waiting. The overdue time grows with each failed cycle until the line misses its window, without any schedule causing it.

**Which line to walk to first.** With one SKU and one nominal rate, the difference between the three lines is the plant rather than the product. The operator view ranks them worst first by OEE and names the dominant loss. Labeler wear on line 3 widens the OEE spread from about 1.5 to about 10.3 points, and the ranking says "line 3, availability" rather than only "line 3".

## Tag names

Every device carries the same PackML block: `UnitMode`, `State`, `StopReasonCode`, `EquipmentBlocked`, `EquipmentStarved`, `ProdProcessedCount`, `ProdDefectiveCount`, `CurMachSpeed`, `DesMachSpeed` and `AccStoppedTime`. Each machine adds its own tags, for example `FillPressure` and `FillTimeMs` on the filler, `TorqueApplied` and `AirPressure` on the capper, `MeanNetWeight` and `RejectCount` on the checkweigher.

Device names are the line and the machine: `PL2_Capper` is the capper on line 2, and `PU1_SyrupRoom`, `PU1_AirHeader` and `PU1_CipSkid` are the shared assets. The shared assets carry the plant numbers no line publishes, such as `LinesDrawing` and `HeaderPressure` on the syrup room, `TotalDemand` and `CompressorsRunning` on the air plant, and `ServingLine` and `QueueDepth` on the CIP skid.

## What it creates on Litmus Edge

| Component    | Created                                       |
| ------------ | --------------------------------------------- |
| DeviceHub    | 18 devices on the Modbus TCP driver, 293 tags |
| Digital Twin | 9 models, 21 instances                        |
| Analytics    | 5 groups, 23 processors                       |

Five machine models are instantiated once per line, the three shared assets have one model each, and a `PackagingLine` model has one instance per line whose attributes come from five different devices. A fourth line would add six instances and no new models.

| Analytics group    | Computes                                                                                               |
| ------------------ | ------------------------------------------------------------------------------------------------------ |
| Utility Contention | product header margin against lines drawing, and air header margin against demand and running capacity |
| Cap Integrity      | applied cap torque against the seal minimum, per line                                                  |
| Plant Giveaway     | mean net weight over the declared weight, per line                                                     |
| CIP Verification   | caustic wash temperature against its limit, and which line the skid was cleaning                       |
| Line Availability  | filler downtime by line                                                                                |

Cap Integrity, Plant Giveaway and Line Availability each run one processor chain across all three lines, scoped by the tag name, so they do not grow when a line is added.

## Requirements

- Litmus Edge 4.0.x, reachable over HTTPS from the host running the solution
- An API token for that Edge, or a username and password, or a client id and secret
- Docker, and Docker Compose for the compose deployment
- A network path in both directions: the solution reaches the Edge's API, and the Edge reaches this host on port 5031

## Deploy

The solution ships two ways. Use the registry if the host can reach Google Artifact Registry, and the downloaded archive if it cannot.

### Option 1: pull from the registry

Requires a read-only registry credential, supplied by Litmus separately.

```bash
cat key.json | docker login -u _json_key --password-stdin https://us-docker.pkg.dev
```

Save this as `docker-compose.yml`:

```yaml
services:
  packaging-hall:
    image: us-docker.pkg.dev/litmus-customer-facing/litmus-solutions/food-beverage-multiline-demo:0.5.0
    container_name: food-beverage-multiline-demo
    restart: unless-stopped
    ports:
      - target: 4021
        published: 4021
        protocol: tcp
      - target: 5031
        published: 5031
        protocol: tcp
    environment:
      # Fill these in.
      EDGE_URL: "${EDGE_URL:-}"
      EDGE_API_TOKEN: "${EDGE_API_TOKEN:-}"
      SIM_HOST: "${SIM_HOST:-auto}"
      APPLY_ON_START: "${APPLY_ON_START:-1}"
      EDGE_VERIFY_TLS: "${EDGE_VERIFY_TLS:-0}"
      HTTP_PORT: "4021"
      MODBUS_PORT: "5031"
      AUTOSTART: "1"
      TICK_SECONDS: "0.2"
      SEED: "7"
      LOG_LEVEL: "INFO"
```

Put the three values in a `.env` file beside it:

```javascript
EDGE_URL=https://10.0.0.5
EDGE_API_TOKEN=your-token
SIM_HOST=10.0.0.9
```

```bash
docker compose up -d
```

### Option 2: load the downloaded archive

Download `food-beverage-multiline-demo-0.5.0-amd64.tar.gz` from the solution's page on portal.litmus.io. The archive is the container image, not a source bundle, and needs no registry access.

```bash
docker load -i food-beverage-multiline-demo-0.5.0-amd64.tar.gz
```

`docker load` prints the image it added:

```javascript
Loaded image: us-docker.pkg.dev/litmus-customer-facing/litmus-solutions/food-beverage-multiline-demo:0.5.0
```

That reference is the one the compose file above already names, so the same `docker-compose.yml` and `.env` now work offline with no `docker login`:

```bash
docker compose up -d
```

Open `http://localhost:4021`. Within about a minute the Edge is configured and the page fills in.

**The page stays on its waiting panel until the Edge carries this solution's devices.** That is deliberate: the simulation does not tick and no value is shown until Litmus Edge is verified to hold the devices and poll them, so nothing on screen can be mistaken for data that never made the trip. With `APPLY_ON_START` at its default of `1` the solution configures the Edge itself on startup, which takes 30 to 60 seconds. If you set it to `0`, or the apply failed, press **Apply to Edge** in the page header.

## Configuration

| Variable          | Default         | Purpose                                                                                                              |
| ----------------- | --------------- | -------------------------------------------------------------------------------------------------------------------- |
| `EDGE_URL`        | the Docker host | Litmus Edge to configure                                                                                             |
| `EDGE_API_TOKEN`  | unset           | token for that Edge. Alternatively `EDGE_USERNAME` and `EDGE_PASSWORD`, or `EDGE_CLIENT_ID` and `EDGE_CLIENT_SECRET` |
| `SIM_HOST`        | `auto`          | address the Edge polls to reach this solution                                                                        |
| `APPLY_ON_START`  | `1`             | configure the Edge on startup                                                                                        |
| `EDGE_VERIFY_TLS` | `0`             | `1` verifies the Edge's certificate                                                                                  |
| `AUTOSTART`       | `1`             | start the lines running                                                                                              |
| `TICK_SECONDS`    | `0.2`           | simulation step                                                                                                      |
| `SEED`            | `7`             | makes a run repeatable                                                                                               |

An incorrect `SIM_HOST` fails quietly: the devices are created, they report as online, and no tag ever reads a value, because the address written into the template is one the Edge cannot reach. The solution refuses to apply a template whose address is obviously unreachable and reports why.

## Operator view

The header shows the worst line with its dominant loss, the product ring main pressure against its specification, the compressed air header against demand and running capacity, giveaway per day, the share of caps under torque, and what the CIP skid is doing and how many lines are waiting for it.

Below that are the three lines and the shared utilities, with the product and air mains drawn from the utilities to every line. Below them, the lines are ranked worst first. Each line can be started and stopped on its own, because how many lines run changes what each line sees.

Below the lines are the faults. Three are marked as shared utilities, affecting all three lines:

- product pump impeller worn (all three lines)
- one air compressor trips (all three lines)
- caustic wash runs cold (all three lines)
- PL3 labeler micro-stops (one line)
- PL2 capper jams (one line)

Each fault has a guide naming the DataHub topics, the Analytics group and the Digital Twin topic to watch while it runs.

## Apply and remove

The header carries both controls. **Apply to Edge** pushes the devices, twins and analytics, and greys out once the solution is on the Edge. **Remove from Edge** previews first, listing what would be removed and from which Edge, then offers to remove it.

The solution removes only what it created, and records which Edge its apply landed on, so repointing at a different Edge cannot leave a solution stranded on the first one.

## Reports

**Preview report** and **Export report** produce one self-contained HTML file that opens offline, emails and prints. It carries a summary, the lines ranked by OEE with the dominant loss for each, the shared utilities with header pressure, giveaway by line and the weight distribution, a downtime Pareto, every CIP cycle with its result and how far past its clean window it ran, the faults injected, and the Litmus Edge configuration that produced it.

## Support

[support@litmus.io](mailto\:support@litmus.io)
