Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
bc74969
Add S7Comm plugin documentation and implementation plan
thiagoralves Jan 12, 2026
1a86e53
Add S7Comm plugin Phase 1 implementation with Snap7 sources
thiagoralves Jan 13, 2026
a577b51
Add native plugin build system to install.sh
thiagoralves Jan 13, 2026
e9cc729
Implement Phase 2: JSON configuration system for S7Comm plugin
thiagoralves Jan 13, 2026
d5819c1
Implement Phase 3/4: Double-buffering for S7Comm plugin
thiagoralves Jan 13, 2026
563bcf8
Optimize: Skip buffer sync when no S7 clients connected
thiagoralves Jan 13, 2026
5401b53
Add JSON schema reference and update S7Comm documentation
thiagoralves Jan 13, 2026
6b63362
Fix Srv_GetStatus call and C++-only compiler flags
thiagoralves Jan 13, 2026
6c5f204
feat: Register S7Comm plugin in plugins configuration
thiagoralves Jan 13, 2026
5567362
fix: Correct S7Comm plugin library path
thiagoralves Jan 13, 2026
4e0151e
fix: Remove faulty client count optimization in S7Comm sync
thiagoralves Jan 13, 2026
faa3766
fix(s7comm): Correct buffer sync strategy for bidirectional S7 commun…
thiagoralves Jan 13, 2026
63cbfb3
docs: Add journal buffer architecture specification
thiagoralves Jan 13, 2026
8edf9c0
Merge pull request #72 from Autonomy-Logic/feature/s7comm-plugin
thiagoralves Jan 13, 2026
c5502d8
Add journal buffer system for race-condition-free plugin writes (Phas…
thiagoralves Jan 13, 2026
ed6957b
Integrate journal buffer into PLC cycle (Phase 2)
thiagoralves Jan 13, 2026
d89506e
Merge pull request #73 from Autonomy-Logic/docs/journal-buffer-archit…
thiagoralves Jan 13, 2026
cf7b381
Expose journal write functions to plugins (Phase 3)
thiagoralves Jan 13, 2026
c83f701
Optimize Modbus Slave mutex usage for journal writes (Phase 5)
thiagoralves Jan 13, 2026
4854ca8
Add S7Comm plugin with journal-based writes (Phase 4)
thiagoralves Jan 13, 2026
9eb708f
Merge development into feature/journal-buffer
thiagoralves Jan 13, 2026
ac99834
Fix S7 area code comparison bug in RWArea callback
thiagoralves Jan 14, 2026
937c1db
Add debug logging to S7Comm RWArea callback
thiagoralves Jan 14, 2026
1c73290
Remove apt cache cleanup from install script
thiagoralves Jan 14, 2026
95a9b82
Move apt cache cleanup to Dockerfiles
thiagoralves Jan 14, 2026
3f08119
Remove debug logging from S7Comm plugin
thiagoralves Jan 14, 2026
ce8bfde
Merge pull request #74 from Autonomy-Logic/feature/journal-buffer
thiagoralves Jan 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .DS_Store
Binary file not shown.
110 changes: 110 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Build Commands

```bash
# Full installation (installs deps, creates venv, compiles runtime)
sudo ./install.sh

# Manual build (C/C++ runtime core)
mkdir -p build && cd build && cmake .. && make -j$(nproc)

# Start the runtime (requires root for real-time scheduling)
sudo ./start_openplc.sh

# Run web server only (for development)
source venvs/runtime/bin/activate
sudo python3 -m webserver.app

# Run PLC runtime only
sudo ./build/plc_main --print-logs
```

## Testing

```bash
# Setup test environment and run tests
sudo bash scripts/setup-tests-env.sh
pytest tests/

# Or use the test script
bash scripts/run-pytest.sh
```

## Linting and Formatting

Pre-commit hooks handle formatting. Install with:
```bash
pip install pre-commit
pre-commit install
pre-commit run --all-files
```

- **C/C++**: Clang-Format (LLVM style, 4-space indent, 100 char limit)
- **Python**: Black + isort + Ruff (100 char line length)

## Architecture Overview

OpenPLC Runtime v4 is a **dual-process industrial PLC runtime**:

### Process 1: REST API Server (Python/Flask)
- **Location**: `webserver/`
- **Port**: 8443 (HTTPS with self-signed TLS)
- **Purpose**: REST API for OpenPLC Editor, WebSocket debug interface, compilation orchestration
- **Entry point**: `webserver/app.py`

### Process 2: PLC Runtime Core (C/C++)
- **Location**: `core/src/plc_app/`
- **Executable**: `build/plc_main`
- **Purpose**: Real-time PLC execution with SCHED_FIFO priority
- **Entry point**: `core/src/plc_app/plc_main.c`

### Inter-Process Communication
- **Command socket**: `/run/runtime/plc_runtime.socket` (text protocol for start/stop/status)
- **Log socket**: `/run/runtime/log_runtime.socket` (real-time log streaming)
- **Client**: `webserver/unixclient.py`
- **Server**: `core/src/plc_app/unix_socket.c`

### PLC State Machine
```
EMPTY -> INIT -> RUNNING <-> STOPPED -> ERROR
```
State management: `core/src/plc_app/plc_state_manager.c`

### Plugin System
- **Config**: `plugins.conf`
- **Types**: Python (type=0) and Native C/C++ (type=1)
- **Driver code**: `core/src/drivers/`
- **Plugin examples**: `core/src/drivers/plugins/python/` and `core/src/drivers/plugins/native/`

### Key Subsystems
- **Scan cycle manager**: `core/src/plc_app/scan_cycle_manager.c` - deterministic timing
- **Debug handler**: `core/src/plc_app/debug_handler.c` - WebSocket debug protocol
- **Watchdog**: `core/src/plc_app/utils/watchdog.c` - health monitoring
- **Image tables**: `core/src/plc_app/image_tables.c` - I/O buffer management

## Code Style

- **C/C++**: 4-space indent, no tabs, `snake_case` functions, `snake_case_t` types, `UPPER_CASE` macros
- **Python**: PEP 8, type hints, 100 char lines
- **No emojis** anywhere in code, comments, or documentation (project standard)

## Key Directories

- `webserver/` - Flask REST API and WebSocket debug interface
- `core/src/plc_app/` - C/C++ real-time PLC runtime
- `core/src/drivers/` - Plugin driver system
- `core/generated/` - Generated PLC code from uploaded programs
- `scripts/` - Build, compile, and management scripts
- `build/` - CMake output (`plc_main` executable, `libplc_*.so` libraries)
- `venvs/` - Python virtual environments (runtime + per-plugin)
- `docs/` - Detailed documentation

## Compilation Flow

1. OpenPLC Editor uploads `program.zip` to `/api/upload-file`
2. Runtime validates, extracts to `core/generated/`
3. `scripts/compile.sh` compiles to `build/libplc_*.so`
4. Runtime loads shared library dynamically via `plcapp_manager.c`
3 changes: 3 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ RUN rm -rf build/ venvs/ .venv/ 2>/dev/null || true
# Run installation script
RUN ./install.sh

# Clean up apt cache to reduce image size (Docker-specific optimization)
RUN rm -rf /var/lib/apt/lists/*

# Expose webserver port
EXPOSE 8443

Expand Down
3 changes: 3 additions & 0 deletions Dockerfile.dev
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ RUN rm -rf build/ venvs/ 2>/dev/null || true
RUN chmod +x install.sh scripts/* start_openplc.sh
RUN ./install.sh

# Clean up apt cache to reduce image size (Docker-specific optimization)
RUN rm -rf /var/lib/apt/lists/*

EXPOSE 8443

CMD ["bash", "./scripts/run-pytest.sh"]
Binary file added core/.DS_Store
Binary file not shown.
Binary file added core/src/.DS_Store
Binary file not shown.
1 change: 1 addition & 0 deletions core/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ add_executable(plc_main
${CMAKE_SOURCE_DIR}/core/src/plc_app/utils/utils.c
${CMAKE_SOURCE_DIR}/core/src/plc_app/utils/watchdog.c
${CMAKE_SOURCE_DIR}/core/src/plc_app/image_tables.c
${CMAKE_SOURCE_DIR}/core/src/plc_app/journal_buffer.c
${CMAKE_SOURCE_DIR}/core/src/plc_app/plc_state_manager.c
${CMAKE_SOURCE_DIR}/core/src/plc_app/plcapp_manager.c
${CMAKE_SOURCE_DIR}/core/src/plc_app/scan_cycle_manager.c
Expand Down
Binary file added core/src/drivers/.DS_Store
Binary file not shown.
38 changes: 38 additions & 0 deletions core/src/drivers/plugin_driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#endif

#include "../plc_app/image_tables.h"
#include "../plc_app/journal_buffer.h"
#include "../plc_app/utils/log.h"
#include "plugin_config.h"
#include "plugin_driver.h"
Expand Down Expand Up @@ -105,6 +106,36 @@ int plugin_mutex_give(pthread_mutex_t *mutex)
return pthread_mutex_unlock(mutex);
}

// Journal write wrapper functions for plugins
// These match the function pointer signatures in plugin_types.h and delegate
// to the journal_buffer.h API with proper type casting.

static int plugin_journal_write_bool(int type, int index, int bit, int value)
{
return journal_write_bool((journal_buffer_type_t)type, (uint16_t)index, (uint8_t)bit,
value != 0);
}

static int plugin_journal_write_byte(int type, int index, int value)
{
return journal_write_byte((journal_buffer_type_t)type, (uint16_t)index, (uint8_t)value);
}

static int plugin_journal_write_int(int type, int index, int value)
{
return journal_write_int((journal_buffer_type_t)type, (uint16_t)index, (uint16_t)value);
}

static int plugin_journal_write_dint(int type, int index, unsigned int value)
{
return journal_write_dint((journal_buffer_type_t)type, (uint16_t)index, (uint32_t)value);
}

static int plugin_journal_write_lint(int type, int index, unsigned long long value)
{
return journal_write_lint((journal_buffer_type_t)type, (uint16_t)index, (uint64_t)value);
}

// Python capsule destructor for runtime args
// Breakpoint here to debug capsule issues
static void plugin_runtime_args_capsule_destructor(PyObject *capsule)
Expand Down Expand Up @@ -670,6 +701,13 @@ void *generate_structured_args_with_driver(plugin_type_t type, plugin_driver_t *
args->log_warn = log_warn;
args->log_error = log_error;

// Initialize journal write functions for race-condition-free buffer writes
args->journal_write_bool = plugin_journal_write_bool;
args->journal_write_byte = plugin_journal_write_byte;
args->journal_write_int = plugin_journal_write_int;
args->journal_write_dint = plugin_journal_write_dint;
args->journal_write_lint = plugin_journal_write_lint;

// printf("[PLUGIN]: Runtime args initialized:\n");
// printf("[PLUGIN]: buffer_size = %d\n", args->buffer_size);
// printf("[PLUGIN]: bits_per_buffer = %d\n", args->bits_per_buffer);
Expand Down
27 changes: 27 additions & 0 deletions core/src/drivers/plugin_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,26 @@ typedef void (*plugin_log_debug_func_t)(const char *fmt, ...);
typedef void (*plugin_log_warn_func_t)(const char *fmt, ...);
typedef void (*plugin_log_error_func_t)(const char *fmt, ...);

/**
* @brief Journal write function pointer types
*
* These function pointers allow plugins to write to I/O buffers through
* the journal buffer system, ensuring race-condition-free writes.
* All writes are applied atomically at the start of the next PLC scan cycle.
*
* Buffer type values (matching journal_buffer_type_t):
* 0=BOOL_INPUT, 1=BOOL_OUTPUT, 2=BOOL_MEMORY
* 3=BYTE_INPUT, 4=BYTE_OUTPUT
* 5=INT_INPUT, 6=INT_OUTPUT, 7=INT_MEMORY
* 8=DINT_INPUT, 9=DINT_OUTPUT, 10=DINT_MEMORY
* 11=LINT_INPUT, 12=LINT_OUTPUT, 13=LINT_MEMORY
*/
typedef int (*plugin_journal_write_bool_func_t)(int type, int index, int bit, int value);
typedef int (*plugin_journal_write_byte_func_t)(int type, int index, int value);
typedef int (*plugin_journal_write_int_func_t)(int type, int index, int value);
typedef int (*plugin_journal_write_dint_func_t)(int type, int index, unsigned int value);
typedef int (*plugin_journal_write_lint_func_t)(int type, int index, unsigned long long value);

/**
* @brief Runtime buffer access structure for plugins
*
Expand Down Expand Up @@ -79,6 +99,13 @@ typedef struct
plugin_log_debug_func_t log_debug;
plugin_log_warn_func_t log_warn;
plugin_log_error_func_t log_error;

/* Journal write functions - race-condition-free buffer writes */
plugin_journal_write_bool_func_t journal_write_bool;
plugin_journal_write_byte_func_t journal_write_byte;
plugin_journal_write_int_func_t journal_write_int;
plugin_journal_write_dint_func_t journal_write_dint;
plugin_journal_write_lint_func_t journal_write_lint;
} plugin_runtime_args_t;

#endif /* PLUGIN_TYPES_H */
Binary file added core/src/drivers/plugins/.DS_Store
Binary file not shown.
Binary file added core/src/drivers/plugins/native/.DS_Store
Binary file not shown.
Binary file not shown.
151 changes: 151 additions & 0 deletions core/src/drivers/plugins/native/s7comm/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
# CMakeLists.txt for S7Comm Plugin
# Builds a self-contained S7Comm plugin with Snap7 library included

cmake_minimum_required(VERSION 3.10)
project(s7comm_plugin CXX C)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)

# Determine OpenPLC root directory for finding common headers
# When building standalone: calculate from plugin location
# When building from main project: pass -DOPENPLC_ROOT=<path>
if(NOT DEFINED OPENPLC_ROOT)
get_filename_component(OPENPLC_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../" ABSOLUTE)
endif()

message(STATUS "S7Comm Plugin - OpenPLC root: ${OPENPLC_ROOT}")

# =============================================================================
# Snap7 Source Files
# =============================================================================

set(SNAP7_CORE_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/snap7/core/s7_server.cpp
${CMAKE_CURRENT_SOURCE_DIR}/snap7/core/s7_isotcp.cpp
${CMAKE_CURRENT_SOURCE_DIR}/snap7/core/s7_peer.cpp
${CMAKE_CURRENT_SOURCE_DIR}/snap7/core/s7_text.cpp
${CMAKE_CURRENT_SOURCE_DIR}/snap7/core/s7_client.cpp
${CMAKE_CURRENT_SOURCE_DIR}/snap7/core/s7_micro_client.cpp
${CMAKE_CURRENT_SOURCE_DIR}/snap7/core/s7_partner.cpp
)

set(SNAP7_SYS_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/snap7/sys/snap_msgsock.cpp
${CMAKE_CURRENT_SOURCE_DIR}/snap7/sys/snap_tcpsrvr.cpp
${CMAKE_CURRENT_SOURCE_DIR}/snap7/sys/snap_threads.cpp
${CMAKE_CURRENT_SOURCE_DIR}/snap7/sys/snap_sysutils.cpp
)

set(SNAP7_LIB_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/snap7/lib/snap7_libmain.cpp
)

# =============================================================================
# cJSON Library (embedded for JSON configuration parsing)
# =============================================================================

set(CJSON_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/cjson/cJSON.c
)

# =============================================================================
# Plugin Source Files
# =============================================================================

set(PLUGIN_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/s7comm_plugin.cpp
${CMAKE_CURRENT_SOURCE_DIR}/s7comm_config.c
${OPENPLC_ROOT}/core/src/drivers/plugins/native/plugin_logger.c
)

# =============================================================================
# Include Directories
# =============================================================================

include_directories(
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/snap7/core
${CMAKE_CURRENT_SOURCE_DIR}/snap7/sys
${CMAKE_CURRENT_SOURCE_DIR}/snap7/lib
${CMAKE_CURRENT_SOURCE_DIR}/cjson
${OPENPLC_ROOT}/core/src/drivers
${OPENPLC_ROOT}/core/src/drivers/plugins/native
${OPENPLC_ROOT}/core/src/lib
)

# =============================================================================
# Create Shared Library
# =============================================================================

add_library(s7comm_plugin SHARED
${SNAP7_CORE_SOURCES}
${SNAP7_SYS_SOURCES}
${SNAP7_LIB_SOURCES}
${CJSON_SOURCES}
${PLUGIN_SOURCES}
)

# =============================================================================
# Compiler Definitions
# =============================================================================

target_compile_definitions(s7comm_plugin PRIVATE
# Snap7 definitions
$<$<NOT:$<PLATFORM_ID:Windows>>:OS_UNIX>
)

# =============================================================================
# Compiler Options
# =============================================================================

target_compile_options(s7comm_plugin PRIVATE
-fPIC
# Suppress Snap7 warnings (it's third-party code)
-Wno-unused-parameter
-Wno-sign-compare
-Wno-deprecated-declarations
# C++-only warning flags (only apply to C++ files)
$<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CXX_COMPILER_ID:GNU>>:-Wno-class-memaccess>
$<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CXX_COMPILER_ID:Clang>>:-Wno-macro-redefined>
)

# =============================================================================
# Link Libraries
# =============================================================================

target_link_libraries(s7comm_plugin PRIVATE
pthread
${CMAKE_DL_LIBS}
)

# Platform-specific libraries
if(UNIX AND NOT APPLE)
# Linux requires librt for clock functions
target_link_libraries(s7comm_plugin PRIVATE rt)
endif()

# =============================================================================
# Output Settings
# =============================================================================

set_target_properties(s7comm_plugin PROPERTIES
LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/plugins
PREFIX "lib"
OUTPUT_NAME "s7comm_plugin"
)

# On Linux, use .so extension
if(UNIX)
set_target_properties(s7comm_plugin PROPERTIES SUFFIX ".so")
endif()

# =============================================================================
# Install Target
# =============================================================================

install(TARGETS s7comm_plugin
LIBRARY DESTINATION lib/openplc/plugins
RUNTIME DESTINATION lib/openplc/plugins
)
Loading