feat: Add S7Comm server plugin for Siemens S7 protocol communication#72
Conversation
- Add comprehensive implementation plan covering: - Analysis of OpenPLC v3 S7Comm/Snap7 implementation - Snap7 library API documentation - JSON configuration schema design - Plugin structure and data structures - 7-phase implementation roadmap - Testing strategy - Add user guide with: - Configuration reference - Example configurations - S7 address mapping guide - Client connection examples - Troubleshooting guide - Add default s7comm_config.json with standard mappings Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Phase 1 implementation of S7Comm plugin for OpenPLC Runtime v4: - Include Snap7 library source code (v1.4.3) for self-contained build - Add plugin source files (s7comm_plugin.cpp/h) - Add CMakeLists.txt for standalone plugin compilation - Implement plugin lifecycle: init, start_loop, stop_loop, cleanup - Implement cycle hooks for buffer synchronization - Register S7 system areas (PE, PA, MK) and data blocks (DB1, DB2, DB10, DB20, DB100, DB200) - Handle big-endian conversion for S7 protocol compatibility - Add event logging for client connections Data block mapping (Phase 1 - hardcoded): - DB1: bool_input (%IX) - DB2: bool_output (%QX) - DB10: int_input (%IW) - DB20: int_output (%QW) - DB100: int_memory (%MW) - DB200: dint_memory (%MD) Note: JSON configuration support will be added in Phase 2. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add build_native_plugins() function that automatically scans for native plugins with CMakeLists.txt and builds them during installation. This enables Docker images to include pre-built native plugins like s7comm. The function: - Scans core/src/drivers/plugins/native/ for CMakeLists.txt files - Creates isolated build directories for each plugin - Passes OPENPLC_ROOT to cmake for proper header resolution - Copies built .so files to build/plugins/ - Provides summary of successful/failed builds Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add full JSON configuration support using embedded cJSON library: Configuration features: - Server settings (port, max_clients, timeouts, PDU size) - PLC identity (name, module_type, serial_number, etc.) - Dynamic data block allocation with configurable mappings - System areas (PE, PA, MK) with configurable sizes - Logging options (connections, data_access, errors) Implementation changes: - Add cJSON library (v1.7.18, MIT license) for JSON parsing - Add s7comm_config.h with configuration structures - Add s7comm_config.c with parser and validation - Refactor s7comm_plugin.cpp to use configuration: - Dynamic memory allocation for data blocks - All server parameters from config - Support for all buffer types (bool, int, dint, lint) - 64-bit (LINT) support with swap64 endianness conversion - Update CMakeLists.txt to include new source files The plugin now reads s7comm_config.json at startup and dynamically allocates and registers S7 data areas based on configuration. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Refactor buffer synchronization to use double-buffering pattern: - Add shadow buffers for all S7 areas (system areas + data blocks) - Add S7 mutex (pthread_mutex_t) to protect S7 buffers during sync - Implement three-step sync in cycle_end: 1. Lock mutex, copy S7 -> shadow (capture client writes) 2. Sync shadow <-> OpenPLC (slow part, no S7 mutex held) 3. Lock mutex, copy shadow -> S7 (publish new values) - Remove sync from cycle_start (now no-op) This allows S7 clients to read/write asynchronously from the main PLC cycle with minimal mutex contention. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Use Srv_GetStatus to check client count at the start of cycle_end. If no clients are connected, return early without any mutex operations or buffer copies, reducing overhead when S7 server is idle. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add JSON_SCHEMA.md with complete configuration reference for Editor - Document all fields, types, constraints, and default values - Add Editor UI recommendations and validation rules - Update USER_GUIDE.md with double-buffering architecture section - Add data flow diagram and related documentation links Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix Srv_GetStatus: use references (int&) not pointers (int*) - Fix CMakeLists.txt: apply -Wno-class-memaccess only to C++ files using COMPILE_LANGUAGE generator expression Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add S7Comm native plugin entry to plugins.conf and plugins_default.conf so the runtime will recognize and load the S7Comm server when a config file is provided by the editor. Plugin configuration format: - name: s7comm - path: ./core/src/drivers/plugins/native/s7comm/libs7comm_plugin.so - enabled: 0 (disabled by default, enabled when config file is present) - type: 1 (native plugin) - config_path: ./core/src/drivers/plugins/native/s7comm/s7comm_config.json Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Update path to match where the installer places the built library: core/src/drivers/plugins/native/s7comm/build/plugins/libs7comm_plugin.so Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The Srv_GetStatus client count check was incorrectly returning 0 even when clients were connected, causing buffer sync to be skipped. Remove this optimization to ensure buffer sync always happens. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…ication Change the sync strategy to copy ENTIRE buffers in both directions: - cycle_start: Copy ENTIRE S7 buffer -> OpenPLC buffer (all types) - cycle_end: Copy ENTIRE OpenPLC buffer -> S7 buffer (all types) This allows S7 clients to write to any location (inputs, outputs, memory). Values actively driven by the PLC program or I/O drivers will naturally overwrite S7 writes during the scan cycle, but S7 writes to other locations (e.g., outputs used only as contacts) will persist. Fixes issue where PLC outputs were being overwritten with zeros from the S7 buffer. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR adds native S7Comm server functionality to OpenPLC Runtime, enabling Siemens S7 protocol communication with compatible HMI and SCADA systems. The implementation integrates the Snap7 library and provides bidirectional synchronization between S7 data areas and OpenPLC I/O buffers through a plugin architecture.
Changes:
- Added complete S7Comm plugin implementation with embedded Snap7 library
- Integrated native plugin build system into install.sh
- Registered s7comm plugin in default configuration files
Reviewed changes
Copilot reviewed 42 out of 44 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| plugins_default.conf | Added s7comm plugin registration entry |
| plugins.conf | Added s7comm plugin registration entry |
| install.sh | Added build_native_plugins function to compile CMake-based plugins |
| core/src/drivers/plugins/native/s7comm/snap7/sys/*.h | Snap7 threading and platform abstraction headers |
| core/src/drivers/plugins/native/s7comm/snap7/sys/*.cpp | Snap7 threading, utilities, socket, and TCP server implementations |
| core/src/drivers/plugins/native/s7comm/snap7/lib/*.h | Snap7 library main interface headers |
| core/src/drivers/plugins/native/s7comm/snap7/lib/*.cpp | Snap7 library main implementation |
| core/src/drivers/plugins/native/s7comm/snap7/core/*.h | S7 protocol type definitions and class interfaces |
| core/src/drivers/plugins/native/s7comm/snap7/core/*.cpp | S7 protocol implementations |
| core/src/drivers/plugins/native/s7comm/s7comm_plugin.h | Plugin interface header with lifecycle function declarations |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| TSnapThread::TSnapThread() | ||
| { | ||
| Started = false; | ||
| Closed=false; |
There was a problem hiding this comment.
Inconsistent spacing around assignment operator. Should be 'Closed = false;' to match the style on line 74.
| Closed=false; | |
| Closed = false; |
| #else | ||
| struct timespec ts; | ||
| ts.tv_sec = (time_t)(Delay_ms / 1000); | ||
| ts.tv_nsec =(long)((Delay_ms - ts.tv_sec) * 1000000); |
There was a problem hiding this comment.
Missing space after assignment operator. Should be 'ts.tv_nsec = (long)' for consistency.
| ts.tv_nsec =(long)((Delay_ms - ts.tv_sec) * 1000000); | |
| ts.tv_nsec = (long)((Delay_ms - ts.tv_sec) * 1000000); |
| case evcClientTerminated : strcat(S,"Client terminated");break; | ||
| case evcClientsDropped: | ||
| strcat(S, IntToString(Event.EvtParam1, Buf)); | ||
| strcat(S, " clients have been dropped bacause unresponsive"); |
There was a problem hiding this comment.
Corrected spelling of 'bacause' to 'because'.
| strcat(S, " clients have been dropped bacause unresponsive"); | |
| strcat(S, " clients have been dropped because unresponsive"); |
Summary
This PR adds a native S7Comm server plugin that enables Siemens S7 protocol communication with OpenPLC. It allows S7-compatible HMIs, SCADA systems, and other clients to read and write OpenPLC I/O buffers using the industry-standard S7 protocol.
Key Features
cycle_start: Copy S7 buffer → OpenPLC (S7 client writes visible to PLC)cycle_end: Copy OpenPLC buffer → S7 (PLC outputs visible to S7 clients)Files Added
core/src/drivers/plugins/native/s7comm/- Complete plugin implementations7comm_plugin.cpp/h- Main plugin with buffer sync logics7comm_config.c/h- JSON configuration parsersnap7/- Snap7 library sources (embedded for portability)cjson/- cJSON library for configuration parsingdocs/- Implementation plan, JSON schema, user guideinstall.sh- Build system for native pluginsplugins_default.conf- Default plugin registrationConfiguration Example
{ "server": { "enabled": true, "port": 102, "max_clients": 32 }, "data_blocks": [ { "db_number": 1, "size_bytes": 128, "mapping": { "type": "bool_output", "start_buffer": 0 } } ] }Testing
Test Plan
Related
🤖 Generated with Claude Code