Development Guide
December 22, 2025 · View on GitHub
Overview
This guide covers setting up a development environment for OpenPLC Runtime v4, understanding the codebase structure, and contributing to the project.
Development Environment Setup
Prerequisites
Required:
- Linux (Ubuntu 20.04+, Debian 11+, Fedora, CentOS, RHEL)
- GCC/G++ compiler
- CMake 3.10+
- Python 3.8+
- Git
Optional:
- Docker (for containerized development)
- VSCode or other IDE
- GDB (for debugging)
Clone Repository
git clone https://github.com/Autonomy-Logic/openplc-runtime.git
cd openplc-runtime
git checkout development
Install Dependencies
Run the installation script:
sudo ./install.sh
This will:
- Detect your Linux distribution
- Install system dependencies (gcc, cmake, python3, etc.)
- Create Python virtual environment at
venvs/runtime/ - Install Python dependencies from
requirements.txt - Compile the PLC runtime core (
build/plc_main)
Manual Dependency Installation
If you prefer manual installation:
Ubuntu/Debian:
sudo apt-get update
sudo apt-get install -y build-essential gcc make cmake \
python3-dev python3-pip python3-venv pkg-config
Fedora/RHEL/CentOS:
sudo dnf install -y gcc gcc-c++ make cmake \
python3 python3-devel python3-pip python3-venv
Python Environment:
python3 -m venv venvs/runtime
source venvs/runtime/bin/activate
pip install --upgrade pip setuptools wheel
pip install -r requirements.txt
pip install -e .
Compile Runtime:
mkdir -p build
cd build
cmake ..
make -j$(nproc)
cd ..
Project Structure
Directory Layout
openplc-runtime/
├── webserver/ # Flask web application (Python)
│ ├── app.py # Main application entry
│ ├── restapi.py # REST API blueprint
│ ├── debug_websocket.py # WebSocket debug interface
│ ├── unixclient.py # Unix socket client
│ ├── plcapp_management.py # Build orchestration
│ ├── runtimemanager.py # Runtime process control
│ ├── credentials.py # TLS certificate generation
│ ├── config.py # Configuration management
│ └── logger/ # Logging subsystem
├── core/
│ ├── src/
│ │ ├── plc_app/ # PLC runtime source (C/C++)
│ │ │ ├── plc_main.c # Main entry point
│ │ │ ├── plc_state_manager.c/h # State management
│ │ │ ├── unix_socket.c/h # IPC server
│ │ │ ├── debug_handler.c/h # Debug protocol
│ │ │ ├── plcapp_manager.c/h # Program loading
│ │ │ ├── scan_cycle_manager.c/h # Scan cycle execution
│ │ │ └── utils/ # Utilities (log, watchdog, timing)
│ │ └── drivers/ # Plugin driver system
│ │ ├── plugin_driver.c/h # Plugin management
│ │ ├── plugin_config.c/h # Configuration parsing
│ │ └── plugins/ # Plugin implementations
│ └── generated/ # Generated PLC code (runtime)
├── scripts/ # Build and management scripts
│ ├── compile.sh # Compile PLC program
│ ├── compile-clean.sh # Clean and rename library
│ ├── manage_plugin_venvs.sh # Plugin venv management
│ ├── build-docker-image.sh # Production Docker build
│ ├── build-docker-image-dev.sh # Development Docker build
│ ├── run-image.sh # Run production container
│ └── run-image-dev.sh # Run development container
├── build/ # Compilation output
│ ├── plc_main # Compiled runtime executable
│ └── libplc_*.so # Compiled PLC program libraries
├── venvs/ # Python virtual environments
│ ├── runtime/ # Web server venv
│ └── {plugin_name}/ # Per-plugin venvs
├── docs/ # Documentation
├── tests/ # Test suite
├── .github/workflows/ # CI/CD pipelines
├── CMakeLists.txt # CMake build configuration
├── Dockerfile # Production container definition
├── requirements.txt # Python dependencies
├── install.sh # Installation script
└── start_openplc.sh # Startup script
Key Files
Build Configuration:
CMakeLists.txt- CMake configuration for C/C++ compilationrequirements.txt- Python dependenciessetup.py- Python package configuration
Entry Points:
webserver/app.py- Web server main entrycore/src/plc_app/plc_main.c- PLC runtime main entrystart_openplc.sh- Startup script
Configuration:
plugins.conf- Plugin configuration/var/run/runtime/.env- Runtime environment variables
Running for Development
Start Runtime
sudo ./start_openplc.sh
This will:
- Check installation status
- Setup plugin virtual environments
- Activate runtime virtual environment
- Start the web server (which manages the PLC runtime)
Note: The runtime will listen on https://localhost:8443 for connections from the OpenPLC Editor. Do not open this in a browser - there is no web interface. Connect from the OpenPLC Editor desktop application.
Run Web Server Only
For web server development without the full startup script:
source venvs/runtime/bin/activate
sudo python3 -m webserver.app
Run PLC Runtime Only
For PLC runtime development:
sudo ./build/plc_main
Options:
--print-logs- Print logs to stdout in addition to socket
Development Mode
For faster iteration during development:
-
Disable real-time scheduling (edit
core/src/plc_app/utils/utils.c):// Comment out set_realtime_priority() call -
Enable debug symbols (edit
scripts/compile.sh):FLAGS="-g -O0 -fPIC" # Debug symbols, no optimization -
Increase log verbosity (edit
core/src/plc_app/plc_main.c):log_set_level(LOG_LEVEL_DEBUG);
Code Style and Standards
C/C++ Code
Style Guidelines:
- Follow existing code style
- Use 4-space indentation
- No tabs
- Function names:
snake_case - Type names:
snake_case_tsuffix - Macro names:
UPPER_CASE - No emojis anywhere in code or comments
Best Practices:
- Check return values
- Free allocated memory
- Close file descriptors
- Use const where appropriate
- Document complex logic
- Avoid global variables when possible
Example:
int initialize_system(void)
{
int result = 0;
// Initialize subsystem
if (subsystem_init() != 0)
{
log_error("Failed to initialize subsystem");
return -1;
}
log_info("System initialized successfully");
return 0;
}
Python Code
Style Guidelines:
- Follow PEP 8
- Use 4-space indentation
- Type hints where appropriate
- Docstrings for public functions
- No emojis anywhere in code or comments
Best Practices:
- Use context managers for resources
- Handle exceptions appropriately
- Use logging instead of print
- Validate inputs
- Document complex logic
Example:
def process_upload(file_path: str) -> tuple[bool, str]:
"""
Process uploaded PLC program file.
Args:
file_path: Path to uploaded ZIP file
Returns:
Tuple of (success, error_message)
"""
try:
safe, valid_files = analyze_zip(file_path)
if not safe:
return False, "ZIP validation failed"
safe_extract(file_path, "core/generated", valid_files)
return True, ""
except Exception as e:
logger.error(f"Upload processing failed: {e}")
return False, str(e)
Building and Testing
Build Runtime Core
cd build
cmake ..
make -j$(nproc)
Clean Build
rm -rf build/
mkdir build
cd build
cmake ..
make -j$(nproc)
Run Tests
# Setup test environment
sudo bash scripts/setup-tests-env.sh
# Run tests (if test suite exists)
pytest tests/
Development Container
Build and run development container:
bash scripts/build-docker-image-dev.sh
bash scripts/run-image-dev.sh
Debugging
GDB Debugging
Debug the PLC runtime:
sudo gdb ./build/plc_main
(gdb) run --print-logs
(gdb) break plc_main.c:68
(gdb) continue
(gdb) backtrace
Python Debugging
Debug the web server:
source venvs/runtime/bin/activate
sudo python3 -m pdb -m webserver.app
Log Analysis
View runtime logs:
curl -k https://localhost:8443/api/runtime-logs | jq
View compilation logs:
curl -k https://localhost:8443/api/compilation-status | jq
Network Debugging
Monitor API calls:
# Terminal 1: Start runtime
sudo ./start_openplc.sh
# Terminal 2: Monitor traffic
sudo tcpdump -i lo -A 'port 8443'
Pre-commit Hooks
Setup
Install pre-commit:
pip install pre-commit
pre-commit install
Run Manually
pre-commit run --all-files
Skip Hooks
For quick fixes (use sparingly):
git commit --no-verify -m "Quick fix"
Contributing
Workflow
-
Fork the repository on GitHub
-
Clone your fork:
git clone https://github.com/YOUR_USERNAME/openplc-runtime.git cd openplc-runtime git remote add upstream https://github.com/Autonomy-Logic/openplc-runtime.git -
Create a feature branch:
git checkout development git pull upstream development git checkout -b feature/my-feature -
Make changes and commit:
git add . git commit -m "Add my feature" -
Push to your fork:
git push origin feature/my-feature -
Create Pull Request on GitHub
Commit Messages
Follow conventional commit format:
type(scope): subject
body
footer
Types:
feat- New featurefix- Bug fixdocs- Documentation changesstyle- Code style changes (formatting)refactor- Code refactoringtest- Test changeschore- Build/tooling changes
Example:
feat(webserver): add rate limiting to API endpoints
Implement rate limiting using Flask-Limiter to prevent
abuse of API endpoints. Limits are configurable via
environment variables.
Closes #123
Pull Request Guidelines
Before submitting:
- Ensure all tests pass
- Update documentation
- Follow code style guidelines
- Add tests for new features
- Rebase on latest development branch
PR Description should include:
- Summary of changes
- Motivation and context
- Testing performed
- Screenshots (if UI changes)
- Related issues
Architecture Deep Dive
Inter-Process Communication
The web server and PLC runtime communicate via Unix domain sockets:
Command Socket: /run/runtime/plc_runtime.socket
- Synchronous request-response
- Text-based protocol
- Commands: start, stop, status, ping, load, unload
Log Socket: /run/runtime/log_runtime.socket
- Asynchronous log streaming
- Binary protocol with structured messages
- Buffered when socket unavailable
State Management
PLC lifecycle states:
EMPTY → INIT → RUNNING ⟷ STOPPED → ERROR
State transitions are validated and logged. See core/src/plc_app/plc_state_manager.c.
Plugin System
Plugins extend I/O capabilities. Both Python and native C/C++ plugins are supported:
- Configuration:
plugins.confdefines enabled plugins (type 0=Python, 1=Native) - Loading:
plugin_driver_load_config()parses configuration and loads symbols - Initialization:
plugin_driver_init()calls each plugin'sinit()function - Execution:
plugin_driver_start()calls each plugin'sstart_loop()function - Cleanup:
plugin_driver_destroy()callscleanup()and releases resources
See core/src/drivers/README.md for detailed plugin development documentation.
Debug Protocol
WebSocket-based debug interface:
- Authentication: JWT token required
- Commands: Hex-encoded binary protocol
- Function Codes: 0x41-0x45 for different operations
- Responses: Hex-encoded with 0x7E success indicator
See Debug Protocol for details.
Performance Profiling
CPU Profiling
Profile the PLC runtime:
sudo perf record -g ./build/plc_main
sudo perf report
Memory Profiling
Check for memory leaks:
valgrind --leak-check=full --show-leak-kinds=all ./build/plc_main
Timing Analysis
Runtime tracks detailed timing statistics:
- Scan time (PLC logic execution)
- Cycle time (total including sleep)
- Cycle latency (deviation from target)
- Overruns (cycles exceeding target)
View stats in runtime logs every 5 seconds.
Documentation
Building Documentation
Documentation is written in Markdown and located in docs/.
Adding Documentation
When adding features:
- Update relevant docs in
docs/ - Update README.md if user-facing
- Add inline code comments
- Update API documentation if endpoints changed
Release Process
Version Numbering
Follow Semantic Versioning (SemVer):
- MAJOR.MINOR.PATCH
- Example: 4.0.1
Creating a Release
- Update version numbers
- Update CHANGELOG.md
- Tag release:
git tag -a v4.0.1 -m "Release v4.0.1" git push origin v4.0.1 - GitHub Actions builds and publishes Docker images
Troubleshooting Development Issues
Build Errors
CMake cache issues:
rm -rf build/
mkdir build && cd build && cmake .. && make
Missing dependencies:
sudo ./install.sh
Runtime Crashes
Enable core dumps:
ulimit -c unlimited
sudo ./build/plc_main
# After crash:
gdb ./build/plc_main core
Python Import Errors
Reinstall in development mode:
source venvs/runtime/bin/activate
pip install -e .
Related Documentation
- Editor Integration - How OpenPLC Editor connects to runtime
- Architecture - System architecture
- API Reference - REST API documentation
- Compilation Flow - Build pipeline
- Troubleshooting - Common issues
- Security - Security considerations