Skip to main content

ControlForge Configuration Reference

James M. Belcher Founder, JMB Technical Services LLC June 2026 | ControlForge v1.0.1064


1. Configuration Files

ControlForge uses YAML configuration files. There are two ways to provide configuration:

MethodFlagDescription
YAML config--config config.yamlFull configuration file with all settings
Project filecontrolforge project.goplcPositional argument — loads programs, tasks, and config from a .goplc JSON file

When both are provided, the .goplc project file takes precedence for programs and tasks. The YAML config provides runtime settings, protocol configuration, and service configuration.

Minimal Config

project:
auto_start: true

runtime:
log_level: info

tasks:
- name: MainTask
type: periodic
priority: 1
scan_time_ms: 50

This is all you need. Everything else has sensible defaults.


2. Runtime

runtime:
log_level: info # off, error, warn, info, debug, trace
scan_time_ms: 50 # Legacy: single-task scan time (use tasks[] instead)
st_files: # Legacy: ST source files (use project file instead)
- programs/main.st
libraries: # Library files to load at startup
- oscat # Short name: loads lib/oscat/LIB_Oscat.st
- /path/to/custom.st # Full path also works
FieldTypeDefaultDescription
log_levelstringinfoMinimum log level
scan_time_msint50Legacy single-task scan time
st_files[]string[]ST source files to load
libraries[]string[]Library files or names to load

3. Tasks

Tasks are the execution containers. Each task runs one or more programs in a periodic scan loop.

tasks:
- name: MainTask
type: periodic
priority: 1
scan_time_ms: 50
programs:
- POU_Control
- POU_Comms
watchdog_ms: 200
watchdog_fault: true
watchdog_halt: false
cpu_affinity: -1

- name: SlowTask
type: periodic
priority: 10
scan_time_ms: 1000
programs:
- POU_Logging
FieldTypeDefaultDescription
namestringrequiredUnique task name
typestringperiodicExecution type (periodic)
priorityint11 (highest) to 100 (lowest)
scan_time_msint50Scan interval in milliseconds (1-60000)
scan_time_usint0Scan interval in microseconds (overrides ms if set)
programs[]string[]Ordered list of programs to execute per scan
watchdog_msintscan_time_ms * 2Maximum allowed scan duration
watchdog_faultboolfalseSet fault flag on watchdog timeout
watchdog_haltboolfalseStop all tasks on watchdog timeout (safety shutdown)
cpu_affinityint0Pin to CPU core (-1 or 0 = no pinning)

4. Project

project:
path: projects/my-project.goplc # Auto-load a project file
auto_start: true # Start runtime after loading
FieldTypeDefaultDescription
pathstring""Path to .goplc project file
auto_startboolfalseAuto-deploy and start after loading

5. Paths

Override default directory locations. Defaults are relative to the working directory or --data-dir flag.

paths:
projects: /opt/controlforge/projects # .goplc project files
st_code: /opt/controlforge/st_code # .st source files
lib: /opt/controlforge/lib # Libraries (OSCAT, etc.)
data: /opt/controlforge/data # Persistence, logs, snapshots

6. API

api:
port: 8082 # HTTP API port
socket: /var/run/controlforge/plc01.sock # Unix socket (optional, for cluster)
broadcast_interval: 100 # WebSocket push interval (ms)
mdns_name: goplc-plant1 # mDNS service name
mdns_role: gateway # Fleet role: gateway, aggregator, simulator, standalone
mdns_tier: 1 # Fleet tier: 1=device, 2=family, 3=site
mdns_family: crac # Fleet family tag
FieldTypeDefaultDescription
portint8080API listen port
socketstring""Unix domain socket path (cluster member comms)
broadcast_intervalint100WebSocket variable broadcast interval (ms)
mdns_namestringautomDNS instance name (default: hostname-port)
mdns_rolestring""Fleet discovery role
mdns_tierint0Fleet tier level
mdns_familystring""Fleet family grouping

Port flag: The API port can also be set via command line: controlforge --api-port 8082. The flag overrides the config file.

6.1 Authentication

api:
auth:
enabled: true
jwt_secret: "" # Auto-generated if empty
token_expiry_hours: 24
trust_proxy: false # Skip auth for reverse-proxied requests
ctrlx_auth: false # Forward to ctrlX identity manager
ctrlx_url: https://localhost
users:
- username: admin
password_hash: "$2a$10$..." # bcrypt hash

6.2 MQTT Publishing

Automatically publish PLC variables to an MQTT broker:

api:
mqtt:
enabled: true
broker: tcp://10.0.0.144:1883
client_id: goplc-runtime
username: ""
password: ""
topic_prefix: controlforge/vars
qos: 0
retained: false
publish_prefixes: # Only publish vars with these prefixes
- DL_
- MB_
publish_stats: false
stats_interval: 1000
subscribe_topics: # Subscribe to external topics
- controlforge/plc2/#
var_prefix: MQTT_ # Prefix for subscribed variables

6.3 Cluster Members

api:
cluster:
members:
- name: plc01
socket: /tmp/goplc-plc01.sock
- name: plc02
url: http://10.0.0.51:8082

7. Protocols

Configure protocol servers and clients that start automatically with the runtime. These are in addition to protocols created dynamically from ST code.

protocols:
# Modbus TCP server
modbus:
enabled: true
port: 502

# Modbus TCP client
modbus_master:
enabled: false
host: 10.0.0.50
port: 502
poll_rate_ms: 100

# Multiple Modbus servers (stress testing / gateway)
modbus_servers:
- name: srv1
port: 5020
unit_id: 1

# Multiple Modbus clients
modbus_clients:
- name: vfd1
host: 10.0.0.50
port: 502
unit_id: 1
poll_rate_ms: 100

# S7 server
s7:
enabled: false
port: 102
io_registers: 15

# S7 client
s7_client:
enabled: false
host: 10.0.0.60
port: 102
rack: 0
slot: 1
poll_rate_ms: 100
timeout_ms: 5000

# OPC UA server
opcua:
enabled: true
port: 4840
io_registers: 15

# OPC UA client
opcua_client:
enabled: false
endpoint: opc.tcp://10.0.0.70:4840
policy: None
mode: None
poll_rate_ms: 1000

# FINS (Omron)
fins:
enabled: false
host: 10.0.0.34
port: 9600
dest_node: 34
src_node: 196
poll_rate_ms: 100

# FINS server
fins_server:
enabled: false
port: 9600
node: 1

# EtherNet/IP adapter (server)
enip:
enabled: false
port: 44818
tags: []

# EtherNet/IP scanner (client)
enip_scanner:
enabled: false
host: 10.0.0.80
port: 44818
slot: 0
poll_rate_ms: 100
tags: []

# DNP3 outstation (server)
dnp3:
enabled: false
port: 20000
io_registers: 16

# DNP3 master (client)
dnp3_master:
enabled: false
host: 10.0.0.90
port: 20000
local_address: 1
remote_address: 10
poll_rate_ms: 1000

# IEC 60870-5-104 server
iec104:
enabled: false
port: 2404
common_addr: 1
io_registers: 16

# IEC 104 client
iec104_client:
enabled: false
host: 10.0.0.100
port: 2404
common_addr: 1

# BACnet/IP server
bacnet:
enabled: false
port: 47808
device_id: 1234
device_name: ControlForge
io_registers: 15

# BACnet client
bacnet_client:
enabled: false
host: 10.0.0.110
port: 47808
poll_rate_ms: 5000

# SNMP agent
snmp_agent:
enabled: false
port: 161
community: public

# SNMP client
snmp_client:
enabled: false
host: 10.0.0.120
port: 161
community: public
version: v2c
poll_rate_ms: 5000

# MQTT broker (embedded)
mqtt:
enabled: false
port: 1883
ws_port: 9001

# MQTT client
mqtt_client:
enabled: false
broker: tcp://10.0.0.144:1883
client_id: goplc-mqtt

8. I/O Mapping

Map PLC variables to IEC 61131-3 addresses for automatic protocol synchronization:

io_mapping:
discrete_inputs: # %IX addresses
- name: limit_switch_1
address: "%IX0.0"
type: BOOL

coils: # %QX addresses
- name: motor_start
address: "%QX0.0"
type: BOOL

input_registers: # %IW addresses
- name: temperature_raw
address: "%IW0"
type: INT
scale: 0.1
offset: 0.0
units: degF

holding_registers: # %QW addresses
- name: speed_setpoint
address: "%QW0"
type: INT

memory_words: # %MW addresses
- name: batch_count
address: "%MW0"
type: INT

io_scan_rate_ms: 10 # I/O sync frequency (default: 10ms = 100Hz)

9. DataLayer

Inter-PLC variable sharing for clusters:

datalayer:
enabled: true
node_id: pump-ctrl
type: direct # direct, memory, shm, tcp
address: ":4222" # TCP mode only
is_server: false # TCP mode: server or client
publish_vars: true
publish_prefixes:
- DL_
- MB_
publish_stats: false
stats_interval_ms: 1000
subscribe_paths:
- boss
- valve-ctrl
FieldTypeDefaultDescription
enabledboolfalseEnable DataLayer
node_idstring""Unique node identifier
typestringdirectTransport: direct, memory, shm, tcp
addressstring""TCP address (server: :port, client: host:port)
publish_prefixes[]string[]Variable prefixes to publish
subscribe_paths[]string[]Node IDs to subscribe to

10. AI Assistant

ai:
enabled: true
name: Fred
provider: claude # claude, openai, ollama
api_key_env: ANTHROPIC_API_KEY
model: claude-opus-4-8
endpoint: "" # Required for ollama (e.g. http://localhost:11434)
timeout_seconds: 30
max_tokens: 8192
temperature: 0.3

See the AI Assistant Guide for full documentation.


11. Node-RED

nodered:
enabled: true
port: 1880
user_dir: data/nodered
flow_file: flows.json
binary_path: "" # Auto-detect if empty
auto_start: true
restart_on_crash: true
max_restarts: 5
restart_backoff_ms: 2000
credential_secret: ""
extra_modules:
- node-red-contrib-influxdb
- node-red-contrib-modbus

See the Node-RED Guide for full documentation.


12. Real-Time Performance

For deterministic scan times on dedicated hardware:

realtime:
enabled: true
mode: baremetal # container or baremetal
lock_memory: true # Prevent page faults (mlockall)
lock_os_thread: true # Pin scan goroutine to OS thread
cpu_affinity: [2, 3] # Dedicated CPU cores
gomaxprocs: 2 # Match CPU affinity count
gc_percent: 500 # Reduce GC frequency (0=off, 100=default)
priority: 80 # SCHED_FIFO priority (baremetal only, 1-99)

13. Debug Logging

debug:
enabled: true
level: info # Global level: off, error, warn, info, debug, trace
modules: # Per-module overrides
modbus: debug
s7: trace
runtime: warn
file: /var/log/controlforge/debug.log
syslog: 10.0.0.144:514
buffer_size: 1000 # In-memory ring buffer for API access

14. Fleet Management

fleet:
enabled: true
auto_discover: true # mDNS discovery on startup
collect_snapshots: true # Auto-collect snapshots on hash change
node_id: CRAC-GW-1 # Stable human-readable ID
registry_file: data/fleet-registry.json
username: "" # Credentials for authenticated fleet nodes
password: ""

15. License

license:
demo_hours: 2 # Demo duration (default: 2 hours per session)
internal_dir: "" # Override license storage path

16. Security

allow_exec: false # Allow EXEC/EXEC_ASYNC/ENV_SET from ST code
# Default false — must be explicitly enabled

Warning: Enabling allow_exec lets ST programs run arbitrary shell commands. Only enable on trusted, isolated systems.


17. ctrlX Data Layer Bridge

For Bosch Rexroth ctrlX CORE integration:

ctrlx_datalayer:
enabled: true
base_url: https://localhost
username: boschrexroth
password: boschrexroth
publish_prefix: goplc-runtime
publish_vars:
- temperature
- pressure
subscribe_vars:
- plc/app/variables/speed_setpoint
sync_interval_ms: 100
insecure_tls: true # Skip TLS verification (localhost)

18. FUXA Web SCADA

fuxa:
enabled: false
url: http://fuxa:1881 # Docker container URL

19. Complete Example

A production-ready configuration for a water treatment gateway:

project:
auto_start: true

runtime:
log_level: info

tasks:
- name: ControlTask
type: periodic
priority: 1
scan_time_ms: 50
programs: [POU_PumpControl, POU_ValveControl]
watchdog_ms: 200
watchdog_fault: true

- name: CommsTask
type: periodic
priority: 5
scan_time_ms: 100
programs: [POU_ModbusComms, POU_MQTTPublish]

- name: LogTask
type: periodic
priority: 10
scan_time_ms: 1000
programs: [POU_DataLogger]

protocols:
modbus:
enabled: true
port: 502

nodered:
enabled: true
extra_modules:
- node-red-contrib-influxdb

ai:
enabled: true
provider: claude
api_key_env: ANTHROPIC_API_KEY

debug:
enabled: true
level: info
file: /var/log/controlforge/runtime.log

ControlForge v1.0.1064 | YAML Configuration Reference

© 2026 JMB Technical Services LLC. All rights reserved. Back to All Guides