---
title: Grid substation
slug: solutions/grid-substation
docTags: 
createdAt: 2026-08-17T14:41:02.637Z
---

A 110/20 kV distribution substation with two transformers and six feeders, on IEC 60870-5-104. Nine devices and 69 points.

| Operator view      | port **4016**          |
| ------------------ | ---------------------- |
| Protocol server    | port **5026**          |
| Devices and tags   | 9 devices, 69 points   |
| Litmus Edge driver | IEC 60870-5-104        |
| Image              | `grid-substation-demo` |

## Scenarios

- **Thermal margin as a forecast.** A transformer's hot spot is not a threshold to alarm on but a curve to project. The useful question is how long the current loading can continue, not whether it is over a limit right now.
- **The tap changer wear counter.** Operations accumulate towards maintenance. A counter that adds up over months is worth more than an alarm that fires after the fact.
- **A recloser sequence, end to end, on the wire.** A feeder fault produces a burst of protection events in under a second. The demonstration is what an Edge does with a burst: capture it at the resolution it happened rather than at the rate someone chose to poll.

## Overview

Most of the solutions here poll steadily. A substation does not. The data that matters arrives in bursts around an event, and the value of an Edge is that it is close enough to catch the whole sequence.

IEC 60870-5-104 is report by exception for exactly this reason, and the solution uses it that way.

## What it creates on Litmus Edge

Nine DeviceHub devices covering the two transformers, six feeders and the station itself, with 69 points over IEC 60870-5-104. A Digital Twin model and instance for each transformer and feeder. Analytics groups behind the thermal margin, tap changer wear and event figures.

## 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 the protocol port

## 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:
  grid-substation:
    image: us-docker.pkg.dev/litmus-customer-facing/litmus-solutions/grid-substation-demo:0.5.0
    container_name: grid-substation-demo
    restart: unless-stopped
    ports:
      - target: 4016
        published: 4016
        protocol: tcp
      - target: 5026
        published: 5026
        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: "4016"
      IEC104_PORT: "5026"
      AUTOSTART: "1"
      TIME_SCALE: "120"
      SEED: "7"
      LOG_LEVEL: "INFO"
```

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

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

Open `http://localhost:4016`.

**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.

### Option 2: load the downloaded archive

Download `grid-substation-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 grid-substation-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/grid-substation-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
```

To run it without Compose, pass the same three values on the command line:

```bash
docker run -d \
  --name grid-substation-demo \
  --restart unless-stopped \
  -p 4016:4016 \
  -p 5026:5026 \
  -e EDGE_URL=https://10.0.0.5 \
  -e EDGE_API_TOKEN=your-token \
  -e SIM_HOST=10.0.0.9 \
  us-docker.pkg.dev/litmus-customer-facing/litmus-solutions/grid-substation-demo:0.5.0
```

## Configuration

| Variable          | Default         | What it does                                                                |
| ----------------- | --------------- | --------------------------------------------------------------------------- |
| `EDGE_URL`        | the Docker host | Litmus Edge to configure                                                    |
| `EDGE_API_TOKEN`  | unset           | token for that Edge. Or a username and password, or a client id and 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` checks the Edge's certificate                                           |
| `TIME_SCALE`      | `120`           | the station clock runs 120x, so a daily load curve passes in twelve minutes |
| `SEED`            | `7`             | makes a run repeatable                                                      |

## Operator view

The station single line: the 110 kV bus, both transformers with their loading, hot spot temperature and tap position, the 20 kV bus, and the six feeders with their currents.

Above it, station load, worst transformer, bus voltage and frequency, feeders open, and ambient temperature, since the thermal margin depends on it.

Every value on the page is read back from Litmus Edge rather than from the simulator, and the page says how many reads it has made and how old the newest is.

Faults include a feeder fault with a recloser sequence, a transformer cooling fan failure, tap changer hunting and a load surge.

## Apply and remove

```bash
docker compose down
```

Use Clean up in the [Solutions provisioner](#) to remove what it created on the Edge.

## Support

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