# Ignition + Docker Development Framework ## What This Is Reusable scaffold for Ignition + Docker projects with Claude Code. The PLC I/O Testing Platform is the first project built on this framework. ## Stack - Ignition 8.3 (Git-native project storage, Perspective, WebDev, OPC-UA) - Rockwell Allen-Bradley ControlLogix/CompactLogix, Studio 5000, PlantPAx - Docker Compose on Ubuntu 24 (Ignition, PostgreSQL, Traefik, Modbus sims) - Gitea at 192.168.3.59:3000 for source control - Jython 2.7 inside Ignition; Python 3.10+ for external tooling - Claude Code on the Linux host (VS Code Remote to Docker host) ## API Key / Secrets Policy **Source of truth**: `~/.config/ignition-dev/secrets.env` (chmod 600, never committed) **Template**: `.env.example` in repo root — update this when adding new secrets ### Rules - All secrets via environment variables — no hardcoded values in any file - Docker Compose references host env vars: `VAR=${VAR}` pattern - Python scripts use `python-dotenv` + `os.environ["KEY"]` (loud failure on missing) - `.env` at project root is a symlink to secrets file — listed in .gitignore - CI secrets live in Gitea repository secrets settings ### Required Variables See `.env.example` for full list. ## Language Rules ### Jython 2.7 (Inside Ignition) All scripts in `ignition/project/` and gateway scripting run Jython 2.7. Hard constraints — violating these causes runtime errors: - NO f-strings → use `"text {}".format(val)` or `"text %s" % val` - NO walrus operator `:=` - NO `pathlib` → use `os.path` - NO type hints → no `def foo(x: int) -> str:` - NO `dataclasses`, `enum.auto()`, `asyncio` - NO dictionary unpacking `{**d1, **d2}` → use `d1.update(d2)` - NO `yield from` → use explicit loops - Imports: `system.*` for Ignition API, standard Java/Jython libs only ### Python 3.10+ (External Tooling) All scripts outside Ignition (test runners, utilities, CI/CD) use Python 3.10+. Comment the target environment at the top of every file: ```python # env: python3.10+ (external tooling — NOT Ignition) ``` ## Safety Rules (Non-Negotiable) 1. **Validate JSON** before writing to any Ignition resource directory. 2. **Back up files** before overwriting: `cp file file.bak` 3. **Sorted-key JSON** for all JSON files (Git-friendly diffs). 4. **Never restart Ignition gateway** without explicit user confirmation. 5. **Never `docker compose down`** without confirmation (destroys unnamed volumes). 6. **Every change needs a verification step** — scenario run, curl, tag read. ## Canonical Patterns (Do Not Change Without Discussion) ### UDT / Linking Pattern Linking/Link sub-UDT with `LinkPath` + `LinkData` for PLC signal binding. Decouples simulation from PLC I/O structure. All device test tags follow this. ### JSON Test Scenarios Five required sections: - `identity` — scenario ID, version, description - `device` — device type, UDT path, tag references - `initial_state` — tag values to set before test begins - `sequence` — ordered steps with `delay_ms` between actions - `expected_outcomes` — pass/fail criteria with `tolerance` See `testing/CLAUDE.md` for full schema. ### WebDev API All external I/O goes through WebDev endpoints — never direct tag file edits at runtime: - `tagWrite` — write values to tags - `tagRead` — read tag values - `tagBrowse` — browse tag tree - `health` — gateway and connection status See `webdev/CLAUDE.md` for endpoint contracts. ### Ignition as I/O Orchestrator Ignition writes to Modbus containers and PLC I/O tags. PLC runs real logic. Test runner talks to Ignition only. Never bypass Ignition to write directly to Modbus or PLC. ## Directory Layout ``` project-root/ ├── CLAUDE.md ← you are here ├── docker/ │ ├── CLAUDE.md ← Docker Compose patterns │ ├── docker-compose.yml │ ├── docker-compose.override.yml │ └── config/ │ ├── ignition/ │ ├── postgres/ │ └── traefik/ ├── ignition/ │ ├── CLAUDE.md ← Ignition project structure │ └── project/ │ ├── com.inductiveautomation.perspective/ │ ├── com.inductiveautomation.webdev/ │ └── ignition/ │ ├── named-query/ │ ├── script-python/ │ └── global-props/ ├── testing/ │ ├── CLAUDE.md ← test scenario schema & runner │ ├── scenarios/ │ ├── runner/ │ └── results/ ├── webdev/ │ ├── CLAUDE.md ← WebDev API contract │ └── endpoints/ ├── tools/ │ └── (Python 3.10+ external utilities) └── docs/ └── (project documentation) ``` ## Module CLAUDE.md Files Each subdirectory has its own CLAUDE.md with domain-specific rules: - `docker/CLAUDE.md` — container orchestration, volumes, networking - `ignition/CLAUDE.md` — project structure, UDTs, scripting, resources - `testing/CLAUDE.md` — scenario schema, test runner, results format - `webdev/CLAUDE.md` — API endpoint contracts, request/response formats