# Getting started — three things you might need to do

Short answers. Detail is in `DEPLOY.md`, `VERSIONS.md` and `README.md`.

Before anything else, know the two hard facts about this setup:

1. **The ST → C++ compiler (STruC++) ships only inside the OpenPLC Editor GUI.**
   There is no CLI, no library, no container image of it. Any change to the PLC
   *logic* goes through the Editor. Nothing else will do it.
2. **The running PLC's image was made with `docker commit`** and holds the compiled
   program in its writable layer. It is not in a registry and cannot be re-pulled.

---

## 1. I need to modify the PLC program

**What you need:** OpenPLC Editor **v4.2.11**, on a machine with a graphical
desktop that can reach `10.0.0.17:8443` (LAN or WireGuard VPN).

> ⚠️ **The Editor is not currently installed anywhere.** It lived on the retired
> `dev-ubuntu` host. Installing it is step one — see `DEPLOY.md` §0.

**Then:**

```bash
# 1. edit the ST - this is the only place you edit
vi src/30_prog_control.st

# 2. rebuild. This also regenerates register-map.csv
python build.py --mode sim

# 3. generate the Editor project
python gen_project.py --mode sim --out editor-project
```

Copy `editor-project/project.json` and `editor-project/pous/` into the Editor
project folder — **with the Editor closed** — then open it, Build, and Start PLC.
`DEPLOY.md` §B is the full procedure; every warning in it was earned.

**Then verify:**

```bash
python ../05-tests/verify_modbus.py --host 10.0.0.17 --port 502 --unit 1
```

### Three things to get right

- **Never hand-edit `register-map.csv`.** It is generated. Change the `REGISTERS`
  table in `build.py` and rebuild. It is the contract with CI Server — if it moves,
  the SCADA point list in `04-scada/modbus/` must be regenerated too.
- **Never edit the Editor project directly.** `gen_project.py` overwrites it. Fixes
  go into `src/`.
- **Never edit `as-built/`.** That is a snapshot of what is deployed, not source.

### If you add or move a register

The whole chain has to be re-run, in order:

```
src/*.st  →  build.py  →  register-map.csv
                              ↓
                        04-scada/modbus/gen_scada_points.py
                              ↓
                        gen_ciserver_qli.py  →  re-import into CI Server
```

Skip a step and the PLC and SCADA disagree silently — the reads still succeed,
they are just wrong.

---

## 2. I need to create a new PLC container from scratch

For a **new demo**, not for recovering this one.

**What you need:** a Docker host, and the OpenPLC Editor to compile a program.

```yaml
# ~/myplc-compose.yml
services:
  myplc-runtime:
    image: ghcr.io/autonomy-logic/openplc-runtime:latest
    container_name: myplc-runtime
    restart: unless-stopped
    cap_add: [SYS_NICE, SYS_RESOURCE]     # required - the runtime needs
                                          # real-time scheduling or it breaks
    ports:
      - "<host-lan-ip>:502:502"           # Modbus TCP, for SCADA
      - "<host-lan-ip>:8443:8443"         # REST API, for the Editor
    volumes:
      - myplc-data:/var/run/runtime
    logging:
      driver: json-file
      options: { max-size: "10m", max-file: "3" }
volumes:
  myplc-data:
```

```bash
docker compose -f ~/myplc-compose.yml up -d
```

**Bind the ports to the host's LAN address, never `0.0.0.0`.** Modbus has no
authentication or encryption, and 8443 is the control channel with only JWT in
front of it. On a host with a public IP, the bind address is the security control.

### Then, in the Editor — the part that is not obvious

Three steps that are configured through GUI dialogs and are not generated. The
reference copies of what they produce are in `editor-devices/`.

1. **Device → Configuration** — device `OpenPLC Runtime V4`, IP = the host's LAN
   address, port `8443`, user `admin`.
2. **Device → Servers** — add a Modbus TCP server, **Enable Server** on,
   `networkInterface` `0.0.0.0`, port `502`.
   > **This is the step everyone misses.** It is what makes the Editor ship
   > `conf/modbus_slave.json`, which enables the runtime's `modbus_slave` plugin.
   > Without it the plugin stays off and **nothing ever binds port 502**, however
   > the container is configured. `0.0.0.0` here is correct — it is inside the
   > container; Docker's publish is what restricts exposure.
3. **Do not touch Device → Remote Devices.** That is the Modbus *master* — it makes
   the PLC poll someone else, and its IO groups claim `%IW` addresses. Your SCADA
   needs no entry in the PLC project at all; it is a client and simply connects.

Then Build → the Editor uploads → **Start PLC**. Port 502 only answers while a
program is running.

### What to copy from this project

The method transfers even when the plant does not:

| Take | Why |
|---|---|
| `build.py`'s `REGISTERS` table + `modbus_address()` | Generating the register map from one table is what stops the PLC and SCADA drifting. Change the tags, keep the machinery. |
| `gen_project.py` | The Editor has no flat-`.st` import, and it will not let you type located addresses by hand. This writes them into `project.json`. |
| The `IO_MUX` pattern | One POU owns every located variable; control logic reads globals only. It is what lets the same control code run against a simulation or a real field. |
| `05-tests/verify_modbus.py` | Proves the map against the live PLC before you touch SCADA. |

---

## 3. I need to move this PLC to a different host

The compiled program is in the container's **writable layer**, so `docker commit`,
never `docker pull`. Full procedure with the real checksums from the 2026-08-19
move: `../02-environment/MIGRATION.md`.

---

## Where to look when something is wrong

| Symptom | Look at |
|---|---|
| Port 502 refuses connections | `README.md` — the two conditions. A program must be **running**, and the Editor project must define a Modbus **Server**. |
| SCADA reads plausible but wrong values | `%MW` starts at **HR 1024**, not 0. `register-map.csv` has resolved addresses. |
| The alarm word goes negative | `%QW17` must be read as **unsigned** 16-bit. Bit 15 does not fit a signed INT. |
| Every measurement reads 0 | You are polling `%IW`/`%IX` (FC04/FC02). In the simulation build nothing writes those. Live values are in the `%QW` block. |
| Editor build fails on `VAR_EXTERNAL … has no matching VAR_GLOBAL` | The Editor was open while files were copied in and wrote its cached `project.json` back. `DEPLOY.md` §B2. |
| Console says "Compile only mode — skipping upload" | Compile-only is on; nothing was uploaded. `DEPLOY.md` §B4. |
| Setpoints reverted to unfamiliar values | The runtime restarted and `IO_MUX` re-seeded the defaults. Live tuning is not stored in this repo. `README.md`. |
