Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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.
Loading