# spolinovskiy/industrial-protocols-labs architecture

> Architecture diagram of the GitHub repository spolinovskiy/industrial-protocols-labs, generated by GitDiagram from its file tree, README and a sample of source files (last updated 2026-08-05). It is an AI-made overview: check details against the source.

- Interactive diagram: https://gitdiagram.com/spolinovskiy/industrial-protocols-labs
- Repository: https://github.com/spolinovskiy/industrial-protocols-labs

## Overview

#### Purpose and Entry Points
This repository is a unified Docker-based industrial-control-system (ICS) protocol testbed. It runs one selected protocol lab at a time, exposes a consistent FUXA HMI, and supports bounded packet capture/diagnostics. The primary operator interface is `labctl` (`start`, `switch`, `stop`); Docker Compose can also start a profile directly through `platform/docker-compose.platform.yml`. The supported architecture is `platform/` plus `protocols/`; `legacy/` preserves superseded per-protocol stacks.

#### Platform Orchestration
The central Compose definition provisions shared platform services and conditionally enables protocol profiles: `platform/docker-compose.platform.yml`. A selected profile starts its protocol server alongside the common gateway, HMI, diagnostics, and reset services. `labctl` is the control-plane boundary for selecting/stopping the active protocol; the optional switcher API/UI invokes that same command path through `scripts/lab_switcher_server.py` and `platform/lab_switcher/index.html`.

#### Protocol Simulation Layer
Each protocol is independently containerized and follows a server/client pattern around a common canonical tag model (digital/analog I/O, timer, counter). Representative implementations are:
- Modbus: `protocols/modbus/server/modbus_server.py`, `protocols/modbus/client/modbus_client.py`
- OPC UA: `protocols/opcua/server/opcua_server.py`, `protocols/opcua/client/opcua_client.py`
- BACnet: `protocols/bacnet/server/bacnet_server.py`, `protocols/bacnet/client/bacnet_client.py`
- MQTT: `protocols/mqtt/broker/mosquitto.conf`, `protocols/mqtt/device-bridge/device_bridge.py`
- S7: `protocols/s7/plc-sim/s7_server.py`, `protocols/s7/client/s7_client.py`

CIP, DNP3, and IEC-104 additionally include gateway adapters where needed: `protocols/cip/gateway/app.py`, `protocols/dnp3/gateway/app.py`, `protocols/iec104/gateway/app.py`. Protocol services are Python-based Docker workloads; MQTT uses a Mosquitto broker.

#### HMI, Access, and Routing Boundary
FUXA provides the common HMI, with protocol-specific project blueprints in `platform/fuxa/seeds/modbus.project.json`, `platform/fuxa/seeds/opcua.project.json`, and related seed files. Nginx is the external boundary separating guest view-only and admin/diagnostic access, including basic-auth credentials: `platform/nginx/nginx.conf`, `platform/nginx/auth/guest.htpasswd`, `platform/nginx/auth/admin.htpasswd`. The gateway routes HMI and diagnostics while protocol services remain on the internal Docker network.

#### Diagnostics and Lifecycle Management
The diagnostics container supplies a constrained terminal menu and protocol-aware packet capture scripts: `platform/diagnostics/entrypoint.sh`, `platform/diagnostics/menu.sh`, `platform/diagnostics/capture/common.sh`. Per-protocol captures bind to standard protocol ports and are retention-limited. The reset controller enforces TTL/idle reset policy, stops the active server, cleans captures, and attempts FUXA seed import: `platform/reset/reset.sh`, `platform/reset/policy.yaml`, `platform/reset/Dockerfile`.

#### Optional Web Frontend
`frontend/` is a separate TypeScript/React application rather than the core lab runtime. Its browser entry is `frontend/client/src/main.tsx`, composed by `frontend/client/src/App.tsx`; the Labs and protocol-testing screens are `frontend/client/src/pages/labs.tsx` and `frontend/client/src/pages/protocol-test.tsx`. Its Node server can proxy or call the switcher backend via `frontend/server/index.ts`, `frontend/server/routes.ts`, and `frontend/server/lib/lab-client.ts`. Authentication integration is Replit-specific: `frontend/server/replit_integrations/auth/replitAuth.ts`.

#### External Dependencies and Operational Boundaries
Material runtimes are Docker/Compose, Python protocol simulators, Nginx, FUXA, Mosquitto, and the optional Node/Vite React frontend. The platform has no indicated cloud control plane; external exposure should terminate TLS at Nginx or an upstream reverse proxy. Protocol state, FUXA state, captures, and reset behavior are operationally isolated from the optional frontend and from archived `legacy/` deployments.

Main entry points: [`platform/docker-compose.platform.yml`](https://github.com/spolinovskiy/industrial-protocols-labs/blob/main/platform/docker-compose.platform.yml) (Platform Compose), [`protocols/modbus/server/modbus_server.py`](https://github.com/spolinovskiy/industrial-protocols-labs/blob/main/protocols/modbus/server/modbus_server.py) (Protocol servers), [`platform/reset/reset.sh`](https://github.com/spolinovskiy/industrial-protocols-labs/blob/main/platform/reset/reset.sh) (Reset controller), [`platform/diagnostics/entrypoint.sh`](https://github.com/spolinovskiy/industrial-protocols-labs/blob/main/platform/diagnostics/entrypoint.sh) (Diagnostics)

## Diagram (Mermaid)

```mermaid
flowchart TD

subgraph group_control["Control Plane"]
  node_operator(("Operator<br/>user"))
  node_labctl{{"labctl<br/>control CLI"}}
  node_switcher_api["Switcher API/UI<br/>Python API + UI"]
end

subgraph group_platform["Shared Platform"]
  node_compose{{"Platform Compose<br/>Docker Compose"}}
  node_nginx["Nginx gateway<br/>access boundary<br/>[nginx.conf]"]
  node_fuxa["FUXA HMI<br/>HMI runtime"]
  node_fuxa_seeds["HMI project seeds<br/>FUXA blueprints"]
  node_diagnostics["Diagnostics<br/>capture container<br/>[entrypoint.sh]"]
  node_reset["Reset controller<br/>lifecycle container<br/>[reset.sh]"]
  node_protocol_network["Internal protocol network<br/>Docker network"]
end

subgraph group_protocols["Protocol Labs"]
  node_tag_model["Canonical tag model<br/>shared I/O model"]
  node_field_protocols["Protocol servers<br/>Python simulators<br/>[modbus_server.py]"]
  node_protocol_clients["Protocol clients<br/>Python clients<br/>[modbus_client.py]"]
  node_mqtt["MQTT lab<br/>Mosquitto + bridge<br/>[mosquitto.conf]"]
  node_gateway_adapters["Protocol gateway adapters<br/>Python adapters<br/>[app.py]"]
end

subgraph group_frontend["Optional Frontend"]
  node_react_app["React browser app<br/>TypeScript frontend<br/>[main.tsx]"]
  node_node_server["Node frontend server<br/>Node backend<br/>[index.ts]"]
  node_replit_auth["Replit auth<br/>authentication integration<br/>[replitAuth.ts]"]
end

node_operator -->|"start / switch / stop"| node_labctl
node_operator -->|"optional web control"| node_switcher_api
node_switcher_api -->|"invokes command path"| node_labctl
node_labctl -->|"selects profile"| node_compose
node_compose -->|"starts"| node_nginx
node_compose -->|"starts"| node_fuxa
node_compose -->|"starts"| node_diagnostics
node_compose -->|"starts"| node_reset
node_compose -->|"starts selected profile"| node_field_protocols
node_compose -.->|"starts MQTT profile"| node_mqtt
node_nginx -->|"routes HMI access"| node_fuxa
node_nginx -->|"routes admin access"| node_diagnostics
node_fuxa -->|"reads/writes tags"| node_protocol_network
node_protocol_network -->|"internal connectivity"| node_field_protocols
node_protocol_network -->|"internal connectivity"| node_mqtt
node_field_protocols -->|"implements"| node_tag_model
node_protocol_clients -->|"tests protocol exchange"| node_field_protocols
node_mqtt -->|"bridges device tags"| node_tag_model
node_gateway_adapters -.->|"adapts selected protocols"| node_field_protocols
node_diagnostics -->|"captures traffic"| node_protocol_network
node_reset -->|"stops active server"| node_field_protocols
node_reset -->|"cleans captures"| node_diagnostics
node_reset -->|"imports project"| node_fuxa_seeds
node_fuxa_seeds -->|"seed configuration"| node_fuxa
node_react_app -->|"browser requests"| node_node_server
node_node_server -.->|"proxies control calls"| node_switcher_api
node_node_server -.->|"authenticates users"| node_replit_auth

click node_switcher_api "https://github.com/spolinovskiy/industrial-protocols-labs/blob/main/scripts/lab_switcher_server.py"
click node_compose "https://github.com/spolinovskiy/industrial-protocols-labs/blob/main/platform/docker-compose.platform.yml"
click node_nginx "https://github.com/spolinovskiy/industrial-protocols-labs/blob/main/platform/nginx/nginx.conf"
click node_fuxa_seeds "https://github.com/spolinovskiy/industrial-protocols-labs/blob/main/platform/fuxa/seeds/modbus.project.json"
click node_diagnostics "https://github.com/spolinovskiy/industrial-protocols-labs/blob/main/platform/diagnostics/entrypoint.sh"
click node_reset "https://github.com/spolinovskiy/industrial-protocols-labs/blob/main/platform/reset/reset.sh"
click node_field_protocols "https://github.com/spolinovskiy/industrial-protocols-labs/blob/main/protocols/modbus/server/modbus_server.py"
click node_protocol_clients "https://github.com/spolinovskiy/industrial-protocols-labs/blob/main/protocols/modbus/client/modbus_client.py"
click node_mqtt "https://github.com/spolinovskiy/industrial-protocols-labs/blob/main/protocols/mqtt/broker/mosquitto.conf"
click node_gateway_adapters "https://github.com/spolinovskiy/industrial-protocols-labs/blob/main/protocols/cip/gateway/app.py"
click node_react_app "https://github.com/spolinovskiy/industrial-protocols-labs/blob/main/frontend/client/src/main.tsx"
click node_node_server "https://github.com/spolinovskiy/industrial-protocols-labs/blob/main/frontend/server/index.ts"
click node_replit_auth "https://github.com/spolinovskiy/industrial-protocols-labs/blob/main/frontend/server/replit_integrations/auth/replitAuth.ts"

classDef toneNeutral fill:#f8fafc,stroke:#334155,stroke-width:1.5px,color:#0f172a
classDef toneBlue fill:#dbeafe,stroke:#2563eb,stroke-width:1.5px,color:#172554
classDef toneAmber fill:#fef3c7,stroke:#d97706,stroke-width:1.5px,color:#78350f
classDef toneMint fill:#dcfce7,stroke:#16a34a,stroke-width:1.5px,color:#14532d
classDef toneRose fill:#ffe4e6,stroke:#e11d48,stroke-width:1.5px,color:#881337
classDef toneIndigo fill:#e0e7ff,stroke:#4f46e5,stroke-width:1.5px,color:#312e81
classDef toneTeal fill:#ccfbf1,stroke:#0f766e,stroke-width:1.5px,color:#134e4a
class node_operator,node_labctl,node_switcher_api toneBlue
class node_compose,node_nginx,node_fuxa,node_fuxa_seeds,node_diagnostics,node_reset,node_protocol_network toneAmber
class node_tag_model,node_field_protocols,node_protocol_clients,node_mqtt,node_gateway_adapters toneMint
class node_react_app,node_node_server,node_replit_auth toneRose
```

## Components

### Control Plane

- **Operator**: user
- **labctl**: control CLI · Selects, switches, and stops the active lab.
- [Switcher API/UI](https://github.com/spolinovskiy/industrial-protocols-labs/blob/main/scripts/lab_switcher_server.py): Python API + UI · `scripts/lab_switcher_server.py`

### Shared Platform

- [Platform Compose](https://github.com/spolinovskiy/industrial-protocols-labs/blob/main/platform/docker-compose.platform.yml): Docker Compose · Starts shared services and the selected protocol profile. · `platform/docker-compose.platform.yml`
- [Nginx gateway](https://github.com/spolinovskiy/industrial-protocols-labs/blob/main/platform/nginx/nginx.conf): access boundary · Separates guest and admin/diagnostic access from internal services. · `platform/nginx/nginx.conf`
- **FUXA HMI**: HMI runtime
- [HMI project seeds](https://github.com/spolinovskiy/industrial-protocols-labs/blob/main/platform/fuxa/seeds/modbus.project.json): FUXA blueprints · Protocol-specific HMI projects are imported on reset. · `platform/fuxa/seeds/modbus.project.json`
- [Diagnostics](https://github.com/spolinovskiy/industrial-protocols-labs/blob/main/platform/diagnostics/entrypoint.sh): capture container · Provides bounded, protocol-aware packet capture. · `platform/diagnostics/entrypoint.sh`
- [Reset controller](https://github.com/spolinovskiy/industrial-protocols-labs/blob/main/platform/reset/reset.sh): lifecycle container · Enforces TTL and idle reset policy. · `platform/reset/reset.sh`
- **Internal protocol network**: Docker network · Keeps protocol services behind the gateway.

### Protocol Labs

- **Canonical tag model**: shared I/O model · Digital and analog I/O, timer, and counter semantics.
- [Protocol servers](https://github.com/spolinovskiy/industrial-protocols-labs/blob/main/protocols/modbus/server/modbus_server.py): Python simulators · Profile-selected Modbus, OPC UA, BACnet, S7, and related servers. · `protocols/modbus/server/modbus_server.py`
- [Protocol clients](https://github.com/spolinovskiy/industrial-protocols-labs/blob/main/protocols/modbus/client/modbus_client.py): Python clients · Exercise representative server implementations. · `protocols/modbus/client/modbus_client.py`
- [MQTT lab](https://github.com/spolinovskiy/industrial-protocols-labs/blob/main/protocols/mqtt/broker/mosquitto.conf): Mosquitto + bridge · `protocols/mqtt/broker/mosquitto.conf`
- [Protocol gateway adapters](https://github.com/spolinovskiy/industrial-protocols-labs/blob/main/protocols/cip/gateway/app.py): Python adapters · Used by CIP, DNP3, and IEC-104 where required. · `protocols/cip/gateway/app.py`

### Optional Frontend

- [React browser app](https://github.com/spolinovskiy/industrial-protocols-labs/blob/main/frontend/client/src/main.tsx): TypeScript frontend · Separate from the core lab runtime. · `frontend/client/src/main.tsx`
- [Node frontend server](https://github.com/spolinovskiy/industrial-protocols-labs/blob/main/frontend/server/index.ts): Node backend · Can proxy or call the lab switcher. · `frontend/server/index.ts`
- [Replit auth](https://github.com/spolinovskiy/industrial-protocols-labs/blob/main/frontend/server/replit_integrations/auth/replitAuth.ts): authentication integration · `frontend/server/replit_integrations/auth/replitAuth.ts`

## Connections

- Operator → labctl: start / switch / stop
- Operator → Switcher API/UI: optional web control
- Switcher API/UI → labctl: invokes command path
- labctl → Platform Compose: selects profile
- Platform Compose → Nginx gateway: starts
- Platform Compose → FUXA HMI: starts
- Platform Compose → Diagnostics: starts
- Platform Compose → Reset controller: starts
- Platform Compose → Protocol servers: starts selected profile
- Platform Compose → MQTT lab: starts MQTT profile
- Nginx gateway → FUXA HMI: routes HMI access
- Nginx gateway → Diagnostics: routes admin access
- FUXA HMI → Internal protocol network: reads/writes tags
- Internal protocol network → Protocol servers: internal connectivity
- Internal protocol network → MQTT lab: internal connectivity
- Protocol servers → Canonical tag model: implements
- Protocol clients → Protocol servers: tests protocol exchange
- MQTT lab → Canonical tag model: bridges device tags
- Protocol gateway adapters → Protocol servers: adapts selected protocols
- Diagnostics → Internal protocol network: captures traffic
- Reset controller → Protocol servers: stops active server
- Reset controller → Diagnostics: cleans captures
- Reset controller → HMI project seeds: imports project
- HMI project seeds → FUXA HMI: seed configuration
- React browser app → Node frontend server: browser requests
- Node frontend server → Switcher API/UI: proxies control calls
- Node frontend server → Replit auth: authenticates users
