From 63cbfb3c7f9f4774352791d829a014038633b5e8 Mon Sep 17 00:00:00 2001 From: Thiago Alves Date: Tue, 13 Jan 2026 10:58:21 -0500 Subject: [PATCH 01/11] docs: Add journal buffer architecture specification This document describes a proposed journaling system for plugin writes to OpenPLC image tables. Key features: - All plugin writes go to a journal buffer instead of direct buffer access - Journal is applied atomically at cycle_start with "last writer wins" - Solves race conditions between asynchronous plugins (Python) and synchronous plugins (native with cycle hooks) - Eliminates the "zero vs uninitialized" ambiguity - No priority configuration needed - temporal ordering is deterministic The document includes: - Detailed architecture diagrams and data flows - API specifications for both C and Python - Thread safety model and mutex ordering - Integration points with existing runtime - Implementation phases with effort estimates - Comparison with alternative approaches Co-Authored-By: Claude Opus 4.5 --- docs/JOURNAL_BUFFER_ARCHITECTURE.md | 596 ++++++++++++++++++++++++++++ 1 file changed, 596 insertions(+) create mode 100644 docs/JOURNAL_BUFFER_ARCHITECTURE.md diff --git a/docs/JOURNAL_BUFFER_ARCHITECTURE.md b/docs/JOURNAL_BUFFER_ARCHITECTURE.md new file mode 100644 index 00000000..9f02af85 --- /dev/null +++ b/docs/JOURNAL_BUFFER_ARCHITECTURE.md @@ -0,0 +1,596 @@ +# Journal Buffer Architecture + +## Overview + +This document describes the architecture for a journaled buffer system to handle plugin writes to OpenPLC image tables. The journal buffer provides a race-condition-free mechanism for multiple plugins (both native and Python) to write to shared I/O buffers with deterministic conflict resolution. + +## Problem Statement + +### Current Architecture Issues + +The current plugin architecture has several limitations when multiple plugins need to write to the same image table locations: + +1. **Race Conditions**: Plugins run in separate threads and can write to buffers at arbitrary times, causing race conditions. + +2. **Zero vs. Uninitialized Ambiguity**: When a plugin copies its buffer to the core image tables, there's no way to distinguish between "intentionally wrote zero" and "never touched this location." + +3. **Asynchronous Plugin Timing**: Python plugins run in their own threads without `cycle_start`/`cycle_end` hooks, so their writes can occur at any point relative to the PLC scan cycle. + +4. **No Conflict Resolution**: When two plugins write to the same location, the outcome depends on thread timing, leading to unpredictable behavior. + +### Example Scenario + +Consider a system with both S7Comm (native) and Modbus Master (Python) plugins: + +``` +Timeline: +|---- PLC Scan Cycle N ----|--- Sleep ---|---- PLC Scan Cycle N+1 ----| + cycle_start cycle_end cycle_start cycle_end + │ │ │ │ + S7→OpenPLC OpenPLC→S7 S7→OpenPLC OpenPLC→S7 + ↑ + Overwrites! + │ + Modbus Master writes + new input value here +``` + +1. **cycle_end N**: OpenPLC inputs (value X) → S7 buffer +2. **Sleep period**: Modbus Master reads remote I/O, writes new value Y to OpenPLC inputs +3. **cycle_start N+1**: S7 buffer (still has old value X) → OpenPLC inputs (overwrites Y with X!) + +## Proposed Solution: Journal Buffer + +### Core Concept + +Instead of plugins writing directly to image tables, all writes go through a journal buffer. The journal is applied atomically at the start of each PLC scan cycle, with "last writer wins" semantics based on write sequence. + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ PLC Scan Cycle │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────┐ ┌─────────────┐ │ +│ │ JOURNAL │ ──── Apply ────────► │ CORE │ │ +│ │ BUFFER │ (cycle_start) │ IMAGE │ │ +│ │ │ │ TABLES │ │ +│ │ seq=1: ... │ │ │ │ +│ │ seq=2: ... │ │ bool_input │ │ +│ │ seq=3: ... │ │ bool_output │ │ +│ │ seq=4: ... │ │ int_input │ │ +│ └──────▲──────┘ │ ... │ │ +│ │ └──────┬──────┘ │ +│ │ │ │ +│ WRITES READS │ +│ (journal) (direct) │ +│ │ │ │ +│ ┌──────┴────────────────────────────────────────▼──────┐ │ +│ │ │ │ +│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐│ │ +│ │ │Plugin A │ │Plugin B │ │Plugin C │ │Plugin D ││ │ +│ │ │(S7Comm) │ │(Modbus) │ │(Python) │ │(Native) ││ │ +│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘│ │ +│ │ │ │ +│ └───────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### Key Design Principles + +1. **Writes Go to Journal**: All plugin writes are recorded in a journal buffer with a sequence number. + +2. **Reads Are Direct**: Plugins read directly from image tables, getting the most recent applied values. + +3. **Atomic Application**: The entire journal is applied at `cycle_start`, before plugin hooks run. + +4. **Last Writer Wins**: Entries are applied in sequence order. If multiple plugins write to the same location, the one with the highest sequence number wins. + +5. **Sequence Reset Per Cycle**: The sequence counter resets to zero when the journal is cleared after application. + +## Detailed Architecture + +### Journal Entry Structure + +```c +typedef struct { + uint32_t sequence; /* Auto-increment, determines apply order */ + uint8_t buffer_type; /* journal_buffer_type_t enum */ + uint8_t bit_index; /* For bool types: 0-7, for others: 0xFF */ + uint16_t index; /* Buffer array index */ + uint64_t value; /* Value to write (sized for largest type) */ +} journal_entry_t; +``` + +**Size**: 16 bytes per entry (with padding for alignment, may be 20 bytes) + +### Buffer Type Enumeration + +```c +typedef enum { + JOURNAL_BOOL_INPUT = 0, + JOURNAL_BOOL_OUTPUT, + JOURNAL_BOOL_MEMORY, + JOURNAL_BYTE_INPUT, + JOURNAL_BYTE_OUTPUT, + JOURNAL_INT_INPUT, + JOURNAL_INT_OUTPUT, + JOURNAL_INT_MEMORY, + JOURNAL_DINT_INPUT, + JOURNAL_DINT_OUTPUT, + JOURNAL_DINT_MEMORY, + JOURNAL_LINT_INPUT, + JOURNAL_LINT_OUTPUT, + JOURNAL_LINT_MEMORY, + JOURNAL_TYPE_COUNT +} journal_buffer_type_t; +``` + +### Static Journal Buffer + +```c +#define JOURNAL_MAX_ENTRIES 1024 + +static journal_entry_t g_entries[JOURNAL_MAX_ENTRIES]; +static size_t g_count = 0; +static uint32_t g_next_sequence = 0; +static pthread_mutex_t g_journal_mutex = PTHREAD_MUTEX_INITIALIZER; +``` + +**Memory Usage**: ~20KB for 1024 entries (fixed, no dynamic allocation) + +### Thread Safety Model + +Two mutexes are involved: + +1. **Journal Mutex** (`g_journal_mutex`): Protects journal buffer state (entries, count, sequence) +2. **Image Table Mutex** (`image_mutex`): Protects core image tables (existing runtime mutex) + +**Lock Ordering** (to prevent deadlock): Always acquire `image_mutex` before `journal_mutex` when both are needed. + +### Write Flow + +``` +Plugin calls journal_write_*() + │ + ▼ +┌─────────────────────────┐ +│ Acquire journal_mutex │ +└───────────┬─────────────┘ + │ + ▼ + ┌───────────────┐ + │ Journal Full? │ + └───────┬───────┘ + │ + Yes │ No + ┌───────┴───────┐ + │ │ + ▼ │ +┌─────────────┐ │ +│ Emergency │ │ +│ Flush │ │ +│ (see below) │ │ +└──────┬──────┘ │ + │ │ + └─────┬──────┘ + │ + ▼ +┌─────────────────────────┐ +│ Add entry with │ +│ next sequence number │ +└───────────┬─────────────┘ + │ + ▼ +┌─────────────────────────┐ +│ Release journal_mutex │ +└─────────────────────────┘ +``` + +### Apply Flow (at cycle_start) + +``` +PLC Cycle Manager calls journal_apply_and_clear() +(image_mutex already held) + │ + ▼ +┌─────────────────────────┐ +│ Acquire journal_mutex │ +└───────────┬─────────────┘ + │ + ▼ +┌─────────────────────────┐ +│ For each entry in order:│ +│ Apply to image table │ +└───────────┬─────────────┘ + │ + ▼ +┌─────────────────────────┐ +│ Reset count = 0 │ +│ Reset sequence = 0 │ +└───────────┬─────────────┘ + │ + ▼ +┌─────────────────────────┐ +│ Release journal_mutex │ +└─────────────────────────┘ +``` + +### Emergency Flush + +When the journal buffer is full and a new write is attempted: + +``` +(Already holding journal_mutex) + │ + ▼ +┌─────────────────────────┐ +│ Release journal_mutex │ ← Prevent deadlock +└───────────┬─────────────┘ + │ + ▼ +┌─────────────────────────┐ +│ Acquire image_mutex │ ← Lock ordering +└───────────┬─────────────┘ + │ + ▼ +┌─────────────────────────┐ +│ Acquire journal_mutex │ +└───────────┬─────────────┘ + │ + ▼ +┌─────────────────────────┐ +│ Apply all entries │ +│ Clear journal │ +└───────────┬─────────────┘ + │ + ▼ +┌─────────────────────────┐ +│ Release image_mutex │ +└───────────┬─────────────┘ + │ + ▼ +(Continue with write, still holding journal_mutex) +``` + +## Public API + +### C API (for native plugins) + +```c +/* Initialize the journal buffer system */ +int journal_init(const journal_buffer_ptrs_t* buffer_ptrs); + +/* Cleanup journal buffer resources */ +void journal_cleanup(void); + +/* Write functions */ +int journal_write_bool(journal_buffer_type_t type, uint16_t index, + uint8_t bit, bool value); +int journal_write_byte(journal_buffer_type_t type, uint16_t index, + uint8_t value); +int journal_write_int(journal_buffer_type_t type, uint16_t index, + uint16_t value); +int journal_write_dint(journal_buffer_type_t type, uint16_t index, + uint32_t value); +int journal_write_lint(journal_buffer_type_t type, uint16_t index, + uint64_t value); + +/* Apply pending writes (called at cycle_start, image_mutex must be held) */ +void journal_apply_and_clear(void); + +/* Diagnostics */ +size_t journal_pending_count(void); +bool journal_is_initialized(void); +``` + +### Python API (for Python plugins) + +The existing `SafeBufferAccess` class will be extended to route writes through the journal: + +```python +class SafeBufferAccess: + def write_bool(self, buf_type: int, index: int, bit: int, value: bool) -> bool: + """Write boolean value to journal buffer.""" + return self._journal_write_bool(buf_type, index, bit, value) + + def write_int(self, buf_type: int, index: int, value: int) -> bool: + """Write 16-bit integer value to journal buffer.""" + return self._journal_write_int(buf_type, index, value) + + # ... similar for other types + + def read_bool(self, buf_type: int, index: int, bit: int) -> Optional[bool]: + """Read boolean value directly from image table (unchanged).""" + return self._direct_read_bool(buf_type, index, bit) +``` + +## Integration Points + +### 1. PLC State Manager Integration + +In `plc_state_manager.c`, add journal application at cycle start: + +```c +void plc_cycle_start(void) { + /* Acquire image table mutex */ + pthread_mutex_lock(&image_mutex); + + /* Apply journal entries FIRST, before any plugin hooks */ + journal_apply_and_clear(); + + /* Call plugin cycle_start hooks */ + plugin_driver_cycle_start(); + + /* ... rest of cycle start logic */ +} +``` + +### 2. Runtime Initialization + +In runtime initialization, set up journal buffer pointers: + +```c +int runtime_init(void) { + /* ... existing init code ... */ + + /* Initialize journal buffer */ + journal_buffer_ptrs_t ptrs = { + .bool_input = bool_input, + .bool_output = bool_output, + .bool_memory = bool_memory, + .byte_input = byte_input, + .byte_output = byte_output, + .int_input = int_input, + .int_output = int_output, + .int_memory = int_memory, + .dint_input = dint_input, + .dint_output = dint_output, + .dint_memory = dint_memory, + .lint_input = lint_input, + .lint_output = lint_output, + .lint_memory = lint_memory, + .buffer_size = BUFFER_SIZE, + .image_mutex = &bufferLock /* existing buffer mutex */ + }; + + if (journal_init(&ptrs) != 0) { + log_error("Failed to initialize journal buffer"); + return -1; + } + + /* ... rest of init ... */ +} +``` + +### 3. Native Plugin Migration + +Native plugins using direct buffer access need to migrate to journal writes: + +**Before (direct write):** +```c +void sync_to_openplc(void) { + IEC_BOOL** arr = runtime_args->bool_output; + if (arr[index][bit] != NULL) { + *arr[index][bit] = value; /* Direct write */ + } +} +``` + +**After (journal write):** +```c +void sync_to_openplc(void) { + journal_write_bool(JOURNAL_BOOL_OUTPUT, index, bit, value); +} +``` + +### 4. Python Plugin Migration + +Python plugins using `SafeBufferAccess` will automatically use the journal when the `SafeBufferAccess` implementation is updated. No changes required in plugin code. + +## Conflict Resolution Examples + +### Example 1: Two Plugins Write Same Location + +``` +Time 0ms: Plugin A writes %QX0.0 = TRUE → seq=1 +Time 5ms: Plugin B writes %QX0.0 = FALSE → seq=2 +Time 10ms: cycle_start - apply journal + +Apply order: + 1. seq=1: %QX0.0 = TRUE + 2. seq=2: %QX0.0 = FALSE ← This wins (last writer) + +Final value: %QX0.0 = FALSE +``` + +### Example 2: Modbus and S7 Conflict + +``` +Time 0ms: S7 client writes %IX0.0 = TRUE → seq=1 +Time 50ms: Modbus Master reads remote I/O, writes %IX0.0 = FALSE → seq=2 +Time 100ms: cycle_start - apply journal + +Result: %IX0.0 = FALSE (Modbus Master wins because it wrote later) +``` + +### Example 3: Emergency Flush + +``` +Time 0ms: Writes 1-1023 accumulate in journal +Time 50ms: Write 1024 triggers emergency flush + - Apply seq 1-1023 to image tables + - Clear journal + - Add write 1024 as seq=0 +Time 100ms: Writes 1-500 accumulate +Time 200ms: cycle_start - apply journal (seq 0-500) +``` + +## Performance Considerations + +### Memory Usage + +- **Journal Buffer**: 1024 entries × ~20 bytes = ~20KB (static allocation) +- **No per-plugin buffers needed**: Single shared journal +- **Total overhead**: Minimal compared to current architecture + +### Latency + +- **Write latency**: Acquiring journal mutex (~microseconds) +- **Apply latency**: Iterating 1024 entries maximum (~milliseconds) +- **Read latency**: Unchanged (direct buffer access) + +### Throughput + +- **Maximum writes per cycle**: 1024 (configurable via `JOURNAL_MAX_ENTRIES`) +- **Emergency flush**: Handles overflow gracefully without data loss + +## Implementation Phases + +### Phase 1: Core Journal Buffer (C Implementation) + +**Files to create:** +- `core/src/plc_app/journal_buffer.h` - Public API header +- `core/src/plc_app/journal_buffer.c` - Implementation + +**Tasks:** +1. Implement journal entry structure and static buffer +2. Implement write functions with mutex protection +3. Implement `journal_apply_and_clear()` function +4. Implement emergency flush logic +5. Add unit tests for journal operations + +**Estimated effort**: 2-3 days + +### Phase 2: Runtime Integration + +**Files to modify:** +- `core/src/plc_app/plc_state_manager.c` - Add journal apply at cycle_start +- `core/src/plc_app/main.c` or equivalent - Initialize journal at startup + +**Tasks:** +1. Initialize journal buffer during runtime startup +2. Call `journal_apply_and_clear()` at cycle_start +3. Cleanup journal buffer at runtime shutdown +4. Integration testing with PLC scan cycle + +**Estimated effort**: 1-2 days + +### Phase 3: Python API Extension + +**Files to modify:** +- `core/src/drivers/plugins/python/shared/safe_buffer_access.py` - Route writes to journal +- `core/src/drivers/plugins/python/shared/__init__.py` - Export new functions + +**Tasks:** +1. Add ctypes bindings for journal write functions +2. Modify `SafeBufferAccess` write methods to use journal +3. Keep read methods unchanged (direct buffer access) +4. Update documentation + +**Estimated effort**: 1-2 days + +### Phase 4: Native Plugin Migration + +**Files to modify:** +- `core/src/drivers/plugins/native/s7comm/s7comm_plugin.cpp` - Use journal writes +- Any other native plugins with direct buffer writes + +**Tasks:** +1. Replace direct buffer writes with `journal_write_*()` calls +2. Remove plugin-specific double-buffering logic (no longer needed) +3. Simplify `cycle_start`/`cycle_end` hooks +4. Integration testing + +**Estimated effort**: 1-2 days per plugin + +### Phase 5: Python Plugin Migration + +**Files to modify:** +- `core/src/drivers/plugins/python/modbus_master/modbus_master_plugin.py` +- `core/src/drivers/plugins/python/modbus_slave/simple_modbus.py` +- Any other Python plugins + +**Tasks:** +1. Verify plugins work with new `SafeBufferAccess` (should be automatic) +2. Integration testing +3. Performance testing + +**Estimated effort**: 1-2 days + +### Phase 6: Testing and Documentation + +**Tasks:** +1. End-to-end testing with multiple plugins +2. Stress testing (high write volume, emergency flush scenarios) +3. Performance benchmarking +4. Update plugin development documentation +5. Update architecture documentation + +**Estimated effort**: 2-3 days + +## Total Implementation Estimate + +| Phase | Effort | +|-------|--------| +| Phase 1: Core Journal Buffer | 2-3 days | +| Phase 2: Runtime Integration | 1-2 days | +| Phase 3: Python API Extension | 1-2 days | +| Phase 4: Native Plugin Migration | 1-2 days per plugin | +| Phase 5: Python Plugin Migration | 1-2 days | +| Phase 6: Testing and Documentation | 2-3 days | +| **Total** | **~10-15 days** | + +## Future Enhancements (Out of Scope) + +The following features are explicitly NOT part of this design but could be added later: + +1. **Write Coalescing**: Merge multiple writes to the same location within a cycle +2. **Read-After-Write Consistency**: Allow plugins to read their own pending writes +3. **Journal Persistence**: Save journal to disk for crash recovery +4. **Journal Replay**: Replay journal for debugging/simulation +5. **Per-Plugin Statistics**: Track write counts per plugin for diagnostics + +## Appendix A: Comparison with Alternative Approaches + +### Alternative 1: Per-Plugin Shadow Buffers + +Each plugin gets its own copy of image tables. Merge at cycle boundaries. + +**Pros**: Complete isolation +**Cons**: High memory usage (N copies), complex merge logic, priority configuration needed + +### Alternative 2: Timestamp-Based Conflict Resolution + +Each write includes a timestamp. Latest timestamp wins. + +**Pros**: Most recent data always wins +**Cons**: Clock synchronization issues, storage overhead, complexity + +### Alternative 3: Priority-Based System + +Each plugin has a priority. Higher priority wins conflicts. + +**Pros**: Deterministic, configurable +**Cons**: Requires priority configuration, may not reflect actual "freshness" of data + +### Why Journal Buffer? + +The journal buffer approach was chosen because: + +1. **No configuration needed**: "Last writer wins" is intuitive +2. **Memory efficient**: Single shared buffer +3. **Simple API**: Just replace write calls +4. **Deterministic**: Same sequence = same result +5. **Debuggable**: Can log/inspect journal contents + +## Appendix B: Glossary + +| Term | Definition | +|------|------------| +| **Image Table** | Core OpenPLC buffer arrays (bool_input, int_output, etc.) | +| **Journal Entry** | Single write record with sequence, type, index, and value | +| **Journal Buffer** | Static array holding pending write entries | +| **Sequence Number** | Auto-incrementing counter determining apply order | +| **Emergency Flush** | Immediate journal application when buffer is full | +| **Apply** | Process of writing journal entries to image tables | +| **Cycle Start** | Beginning of PLC scan cycle, when journal is applied | From c5502d864fed8caa37044d5d08f91e9d10d6fd9e Mon Sep 17 00:00:00 2001 From: Thiago Alves Date: Tue, 13 Jan 2026 15:23:35 -0500 Subject: [PATCH 02/11] Add journal buffer system for race-condition-free plugin writes (Phase 1) Implement core journal buffer infrastructure: - journal_buffer.h: Public API with write functions for all buffer types - journal_buffer.c: Implementation with mutex-protected journal entries Key features: - Static buffer with 1024 max entries (~20KB memory) - Sequence-numbered entries for last-writer-wins conflict resolution - Thread-safe write functions for bool, byte, int, dint, lint types - journal_apply_and_clear() for atomic application at cycle_start - Emergency flush when buffer is full (respects lock ordering) - Diagnostic functions for monitoring pending entries This addresses race conditions between plugins writing to shared image tables by routing all writes through a journal that is applied atomically at the start of each PLC scan cycle. Co-Authored-By: Claude Opus 4.5 --- core/src/CMakeLists.txt | 1 + core/src/plc_app/journal_buffer.c | 506 ++++++++++++++++++++++++++++++ core/src/plc_app/journal_buffer.h | 234 ++++++++++++++ 3 files changed, 741 insertions(+) create mode 100644 core/src/plc_app/journal_buffer.c create mode 100644 core/src/plc_app/journal_buffer.h diff --git a/core/src/CMakeLists.txt b/core/src/CMakeLists.txt index ba00b5ba..7cd2d893 100644 --- a/core/src/CMakeLists.txt +++ b/core/src/CMakeLists.txt @@ -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 diff --git a/core/src/plc_app/journal_buffer.c b/core/src/plc_app/journal_buffer.c new file mode 100644 index 00000000..7358f75e --- /dev/null +++ b/core/src/plc_app/journal_buffer.c @@ -0,0 +1,506 @@ +/** + * @file journal_buffer.c + * @brief Journal Buffer Implementation for Race-Condition-Free Plugin Writes + * + * This implementation provides: + * - Static journal buffer with configurable max entries + * - Thread-safe write operations with mutex protection + * - Atomic application of journal entries at cycle start + * - Emergency flush when buffer is full + * - Last-writer-wins conflict resolution via sequence numbers + */ + +#include "journal_buffer.h" +#include +#include + +/* + * ============================================================================= + * Static State + * ============================================================================= + */ + +/* Journal entries buffer */ +static journal_entry_t g_entries[JOURNAL_MAX_ENTRIES]; + +/* Current number of entries in journal */ +static size_t g_count = 0; + +/* Next sequence number to assign (auto-increment) */ +static uint32_t g_next_sequence = 0; + +/* Journal mutex - protects g_entries, g_count, g_next_sequence */ +static pthread_mutex_t g_journal_mutex = PTHREAD_MUTEX_INITIALIZER; + +/* Buffer pointers for applying entries */ +static journal_buffer_ptrs_t g_buffer_ptrs; + +/* Initialization flag */ +static bool g_initialized = false; + +/* + * ============================================================================= + * Forward Declarations + * ============================================================================= + */ +static void apply_entry(const journal_entry_t *entry); +static void emergency_flush_locked(void); + +/* + * ============================================================================= + * Initialization and Cleanup + * ============================================================================= + */ + +int journal_init(const journal_buffer_ptrs_t *buffer_ptrs) +{ + if (buffer_ptrs == NULL) { + fprintf(stderr, "[JOURNAL] Error: buffer_ptrs is NULL\n"); + return -1; + } + + if (buffer_ptrs->image_mutex == NULL) { + fprintf(stderr, "[JOURNAL] Error: image_mutex is NULL\n"); + return -1; + } + + pthread_mutex_lock(&g_journal_mutex); + + /* Copy buffer pointers */ + memcpy(&g_buffer_ptrs, buffer_ptrs, sizeof(journal_buffer_ptrs_t)); + + /* Reset journal state */ + g_count = 0; + g_next_sequence = 0; + memset(g_entries, 0, sizeof(g_entries)); + + g_initialized = true; + + pthread_mutex_unlock(&g_journal_mutex); + + return 0; +} + +void journal_cleanup(void) +{ + pthread_mutex_lock(&g_journal_mutex); + + g_initialized = false; + g_count = 0; + g_next_sequence = 0; + memset(&g_buffer_ptrs, 0, sizeof(g_buffer_ptrs)); + + pthread_mutex_unlock(&g_journal_mutex); +} + +bool journal_is_initialized(void) +{ + bool result; + pthread_mutex_lock(&g_journal_mutex); + result = g_initialized; + pthread_mutex_unlock(&g_journal_mutex); + return result; +} + +/* + * ============================================================================= + * Write Functions + * ============================================================================= + */ + +/** + * @brief Internal function to add an entry to the journal + * + * Must be called with g_journal_mutex held. + * Handles emergency flush if buffer is full. + * + * @return Pointer to the new entry, or NULL on failure + */ +static journal_entry_t *add_entry_locked(void) +{ + /* Check if buffer is full */ + if (g_count >= JOURNAL_MAX_ENTRIES) { + /* Emergency flush: apply all entries and clear */ + emergency_flush_locked(); + } + + /* Add new entry */ + journal_entry_t *entry = &g_entries[g_count]; + entry->sequence = g_next_sequence++; + g_count++; + + return entry; +} + +int journal_write_bool(journal_buffer_type_t type, uint16_t index, + uint8_t bit, bool value) +{ + if (!g_initialized) { + return -1; + } + + /* Validate type */ + if (type != JOURNAL_BOOL_INPUT && + type != JOURNAL_BOOL_OUTPUT && + type != JOURNAL_BOOL_MEMORY) { + return -1; + } + + /* Validate bit index */ + if (bit > 7) { + return -1; + } + + pthread_mutex_lock(&g_journal_mutex); + + journal_entry_t *entry = add_entry_locked(); + if (entry == NULL) { + pthread_mutex_unlock(&g_journal_mutex); + return -1; + } + + entry->buffer_type = (uint8_t)type; + entry->index = index; + entry->bit_index = bit; + entry->value = value ? 1 : 0; + + pthread_mutex_unlock(&g_journal_mutex); + return 0; +} + +int journal_write_byte(journal_buffer_type_t type, uint16_t index, + uint8_t value) +{ + if (!g_initialized) { + return -1; + } + + /* Validate type */ + if (type != JOURNAL_BYTE_INPUT && type != JOURNAL_BYTE_OUTPUT) { + return -1; + } + + pthread_mutex_lock(&g_journal_mutex); + + journal_entry_t *entry = add_entry_locked(); + if (entry == NULL) { + pthread_mutex_unlock(&g_journal_mutex); + return -1; + } + + entry->buffer_type = (uint8_t)type; + entry->index = index; + entry->bit_index = 0xFF; /* Not applicable for non-bool types */ + entry->value = value; + + pthread_mutex_unlock(&g_journal_mutex); + return 0; +} + +int journal_write_int(journal_buffer_type_t type, uint16_t index, + uint16_t value) +{ + if (!g_initialized) { + return -1; + } + + /* Validate type */ + if (type != JOURNAL_INT_INPUT && + type != JOURNAL_INT_OUTPUT && + type != JOURNAL_INT_MEMORY) { + return -1; + } + + pthread_mutex_lock(&g_journal_mutex); + + journal_entry_t *entry = add_entry_locked(); + if (entry == NULL) { + pthread_mutex_unlock(&g_journal_mutex); + return -1; + } + + entry->buffer_type = (uint8_t)type; + entry->index = index; + entry->bit_index = 0xFF; + entry->value = value; + + pthread_mutex_unlock(&g_journal_mutex); + return 0; +} + +int journal_write_dint(journal_buffer_type_t type, uint16_t index, + uint32_t value) +{ + if (!g_initialized) { + return -1; + } + + /* Validate type */ + if (type != JOURNAL_DINT_INPUT && + type != JOURNAL_DINT_OUTPUT && + type != JOURNAL_DINT_MEMORY) { + return -1; + } + + pthread_mutex_lock(&g_journal_mutex); + + journal_entry_t *entry = add_entry_locked(); + if (entry == NULL) { + pthread_mutex_unlock(&g_journal_mutex); + return -1; + } + + entry->buffer_type = (uint8_t)type; + entry->index = index; + entry->bit_index = 0xFF; + entry->value = value; + + pthread_mutex_unlock(&g_journal_mutex); + return 0; +} + +int journal_write_lint(journal_buffer_type_t type, uint16_t index, + uint64_t value) +{ + if (!g_initialized) { + return -1; + } + + /* Validate type */ + if (type != JOURNAL_LINT_INPUT && + type != JOURNAL_LINT_OUTPUT && + type != JOURNAL_LINT_MEMORY) { + return -1; + } + + pthread_mutex_lock(&g_journal_mutex); + + journal_entry_t *entry = add_entry_locked(); + if (entry == NULL) { + pthread_mutex_unlock(&g_journal_mutex); + return -1; + } + + entry->buffer_type = (uint8_t)type; + entry->index = index; + entry->bit_index = 0xFF; + entry->value = value; + + pthread_mutex_unlock(&g_journal_mutex); + return 0; +} + +/* + * ============================================================================= + * Apply and Clear + * ============================================================================= + */ + +/** + * @brief Apply a single journal entry to the image tables + * + * @param entry The entry to apply + */ +static void apply_entry(const journal_entry_t *entry) +{ + uint16_t idx = entry->index; + + /* Bounds check */ + if (idx >= (uint16_t)g_buffer_ptrs.buffer_size) { + return; + } + + switch ((journal_buffer_type_t)entry->buffer_type) { + case JOURNAL_BOOL_INPUT: { + IEC_BOOL *ptr = g_buffer_ptrs.bool_input[idx][entry->bit_index]; + if (ptr != NULL) { + *ptr = (IEC_BOOL)(entry->value & 1); + } + break; + } + case JOURNAL_BOOL_OUTPUT: { + IEC_BOOL *ptr = g_buffer_ptrs.bool_output[idx][entry->bit_index]; + if (ptr != NULL) { + *ptr = (IEC_BOOL)(entry->value & 1); + } + break; + } + case JOURNAL_BOOL_MEMORY: { + IEC_BOOL *ptr = g_buffer_ptrs.bool_memory[idx][entry->bit_index]; + if (ptr != NULL) { + *ptr = (IEC_BOOL)(entry->value & 1); + } + break; + } + case JOURNAL_BYTE_INPUT: { + IEC_BYTE *ptr = g_buffer_ptrs.byte_input[idx]; + if (ptr != NULL) { + *ptr = (IEC_BYTE)(entry->value & 0xFF); + } + break; + } + case JOURNAL_BYTE_OUTPUT: { + IEC_BYTE *ptr = g_buffer_ptrs.byte_output[idx]; + if (ptr != NULL) { + *ptr = (IEC_BYTE)(entry->value & 0xFF); + } + break; + } + case JOURNAL_INT_INPUT: { + IEC_UINT *ptr = g_buffer_ptrs.int_input[idx]; + if (ptr != NULL) { + *ptr = (IEC_UINT)(entry->value & 0xFFFF); + } + break; + } + case JOURNAL_INT_OUTPUT: { + IEC_UINT *ptr = g_buffer_ptrs.int_output[idx]; + if (ptr != NULL) { + *ptr = (IEC_UINT)(entry->value & 0xFFFF); + } + break; + } + case JOURNAL_INT_MEMORY: { + IEC_UINT *ptr = g_buffer_ptrs.int_memory[idx]; + if (ptr != NULL) { + *ptr = (IEC_UINT)(entry->value & 0xFFFF); + } + break; + } + case JOURNAL_DINT_INPUT: { + IEC_UDINT *ptr = g_buffer_ptrs.dint_input[idx]; + if (ptr != NULL) { + *ptr = (IEC_UDINT)(entry->value & 0xFFFFFFFF); + } + break; + } + case JOURNAL_DINT_OUTPUT: { + IEC_UDINT *ptr = g_buffer_ptrs.dint_output[idx]; + if (ptr != NULL) { + *ptr = (IEC_UDINT)(entry->value & 0xFFFFFFFF); + } + break; + } + case JOURNAL_DINT_MEMORY: { + IEC_UDINT *ptr = g_buffer_ptrs.dint_memory[idx]; + if (ptr != NULL) { + *ptr = (IEC_UDINT)(entry->value & 0xFFFFFFFF); + } + break; + } + case JOURNAL_LINT_INPUT: { + IEC_ULINT *ptr = g_buffer_ptrs.lint_input[idx]; + if (ptr != NULL) { + *ptr = (IEC_ULINT)entry->value; + } + break; + } + case JOURNAL_LINT_OUTPUT: { + IEC_ULINT *ptr = g_buffer_ptrs.lint_output[idx]; + if (ptr != NULL) { + *ptr = (IEC_ULINT)entry->value; + } + break; + } + case JOURNAL_LINT_MEMORY: { + IEC_ULINT *ptr = g_buffer_ptrs.lint_memory[idx]; + if (ptr != NULL) { + *ptr = (IEC_ULINT)entry->value; + } + break; + } + default: + /* Unknown type - ignore */ + break; + } +} + +void journal_apply_and_clear(void) +{ + if (!g_initialized) { + return; + } + + pthread_mutex_lock(&g_journal_mutex); + + /* Apply all entries in sequence order (they're already in order) */ + for (size_t i = 0; i < g_count; i++) { + apply_entry(&g_entries[i]); + } + + /* Clear journal */ + g_count = 0; + g_next_sequence = 0; + + pthread_mutex_unlock(&g_journal_mutex); +} + +/* + * ============================================================================= + * Emergency Flush + * ============================================================================= + */ + +/** + * @brief Emergency flush - apply all entries when buffer is full + * + * Called when a new write is attempted but the journal buffer is full. + * Must be called with g_journal_mutex already held. + * + * Lock ordering to prevent deadlock: + * 1. Release journal mutex + * 2. Acquire image mutex + * 3. Re-acquire journal mutex + * 4. Apply entries + * 5. Release image mutex + * (Continue holding journal mutex for the pending write) + */ +static void emergency_flush_locked(void) +{ + /* Release journal mutex to respect lock ordering */ + pthread_mutex_unlock(&g_journal_mutex); + + /* Acquire image mutex first (lock ordering) */ + pthread_mutex_lock(g_buffer_ptrs.image_mutex); + + /* Re-acquire journal mutex */ + pthread_mutex_lock(&g_journal_mutex); + + /* Apply all entries */ + for (size_t i = 0; i < g_count; i++) { + apply_entry(&g_entries[i]); + } + + /* Clear journal */ + g_count = 0; + g_next_sequence = 0; + + /* Release image mutex (keep journal mutex for the pending write) */ + pthread_mutex_unlock(g_buffer_ptrs.image_mutex); + + /* Note: We return with g_journal_mutex still held */ +} + +/* + * ============================================================================= + * Diagnostics + * ============================================================================= + */ + +size_t journal_pending_count(void) +{ + size_t count; + pthread_mutex_lock(&g_journal_mutex); + count = g_count; + pthread_mutex_unlock(&g_journal_mutex); + return count; +} + +uint32_t journal_get_sequence(void) +{ + uint32_t seq; + pthread_mutex_lock(&g_journal_mutex); + seq = g_next_sequence; + pthread_mutex_unlock(&g_journal_mutex); + return seq; +} diff --git a/core/src/plc_app/journal_buffer.h b/core/src/plc_app/journal_buffer.h new file mode 100644 index 00000000..b819eeb1 --- /dev/null +++ b/core/src/plc_app/journal_buffer.h @@ -0,0 +1,234 @@ +/** + * @file journal_buffer.h + * @brief Journal Buffer System for Race-Condition-Free Plugin Writes + * + * This module provides a journaled write mechanism for plugins to write to + * OpenPLC image tables without race conditions. All plugin writes go through + * the journal buffer, which is applied atomically at the start of each PLC + * scan cycle. + * + * Key design principles: + * - Writes go to journal: All plugin writes are recorded with a sequence number + * - Reads are direct: Plugins read directly from image tables + * - Atomic application: Journal is applied at cycle_start before plugin hooks + * - Last writer wins: Entries applied in sequence order + * + * Usage: + * // In plugin code, instead of: + * // *bool_output[5][3] = true; + * // Use: + * journal_write_bool(JOURNAL_BOOL_OUTPUT, 5, 3, true); + * + * @note Thread-safe. Uses internal mutex for journal operations. + */ + +#ifndef JOURNAL_BUFFER_H +#define JOURNAL_BUFFER_H + +#include +#include +#include +#include +#include "../lib/iec_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Maximum number of journal entries per cycle + * + * If this limit is reached, an emergency flush is triggered to apply + * pending entries and make room for new ones. + */ +#define JOURNAL_MAX_ENTRIES 1024 + +/** + * @brief Buffer type enumeration for journal entries + * + * Matches the OpenPLC image table types. + */ +typedef enum { + JOURNAL_BOOL_INPUT = 0, + JOURNAL_BOOL_OUTPUT, + JOURNAL_BOOL_MEMORY, + JOURNAL_BYTE_INPUT, + JOURNAL_BYTE_OUTPUT, + JOURNAL_INT_INPUT, + JOURNAL_INT_OUTPUT, + JOURNAL_INT_MEMORY, + JOURNAL_DINT_INPUT, + JOURNAL_DINT_OUTPUT, + JOURNAL_DINT_MEMORY, + JOURNAL_LINT_INPUT, + JOURNAL_LINT_OUTPUT, + JOURNAL_LINT_MEMORY, + JOURNAL_TYPE_COUNT +} journal_buffer_type_t; + +/** + * @brief Journal entry structure + * + * Each entry represents a single write operation to be applied. + * Entries are applied in sequence order during journal_apply_and_clear(). + */ +typedef struct { + uint32_t sequence; /**< Auto-increment, determines apply order */ + uint8_t buffer_type; /**< journal_buffer_type_t enum */ + uint8_t bit_index; /**< For bool types: 0-7, for others: 0xFF */ + uint16_t index; /**< Buffer array index */ + uint64_t value; /**< Value to write (sized for largest type) */ +} journal_entry_t; + +/** + * @brief Buffer pointers structure for journal initialization + * + * Contains pointers to all image table arrays and the image table mutex. + */ +typedef struct { + /* Boolean buffers (pointer to 2D array) */ + IEC_BOOL *(*bool_input)[8]; + IEC_BOOL *(*bool_output)[8]; + IEC_BOOL *(*bool_memory)[8]; + + /* Byte buffers */ + IEC_BYTE **byte_input; + IEC_BYTE **byte_output; + + /* Integer buffers (16-bit) */ + IEC_UINT **int_input; + IEC_UINT **int_output; + IEC_UINT **int_memory; + + /* Double integer buffers (32-bit) */ + IEC_UDINT **dint_input; + IEC_UDINT **dint_output; + IEC_UDINT **dint_memory; + + /* Long integer buffers (64-bit) */ + IEC_ULINT **lint_input; + IEC_ULINT **lint_output; + IEC_ULINT **lint_memory; + + /* Buffer size (number of elements in each array) */ + int buffer_size; + + /* Image table mutex (for emergency flush and apply operations) */ + pthread_mutex_t *image_mutex; +} journal_buffer_ptrs_t; + +/** + * @brief Initialize the journal buffer system + * + * Must be called during runtime initialization, after image tables are set up. + * + * @param buffer_ptrs Pointer to structure containing image table pointers + * @return 0 on success, -1 on failure + */ +int journal_init(const journal_buffer_ptrs_t *buffer_ptrs); + +/** + * @brief Cleanup journal buffer resources + * + * Should be called during runtime shutdown. + */ +void journal_cleanup(void); + +/** + * @brief Check if journal buffer is initialized + * + * @return true if initialized and ready for use + */ +bool journal_is_initialized(void); + +/** + * @brief Write a boolean value to the journal + * + * @param type Buffer type (JOURNAL_BOOL_INPUT, JOURNAL_BOOL_OUTPUT, JOURNAL_BOOL_MEMORY) + * @param index Buffer array index (0 to buffer_size-1) + * @param bit Bit index within the byte (0-7) + * @param value Boolean value to write + * @return 0 on success, -1 on failure + */ +int journal_write_bool(journal_buffer_type_t type, uint16_t index, + uint8_t bit, bool value); + +/** + * @brief Write a byte value to the journal + * + * @param type Buffer type (JOURNAL_BYTE_INPUT, JOURNAL_BYTE_OUTPUT) + * @param index Buffer array index (0 to buffer_size-1) + * @param value Byte value to write + * @return 0 on success, -1 on failure + */ +int journal_write_byte(journal_buffer_type_t type, uint16_t index, + uint8_t value); + +/** + * @brief Write a 16-bit integer value to the journal + * + * @param type Buffer type (JOURNAL_INT_INPUT, JOURNAL_INT_OUTPUT, JOURNAL_INT_MEMORY) + * @param index Buffer array index (0 to buffer_size-1) + * @param value 16-bit value to write + * @return 0 on success, -1 on failure + */ +int journal_write_int(journal_buffer_type_t type, uint16_t index, + uint16_t value); + +/** + * @brief Write a 32-bit integer value to the journal + * + * @param type Buffer type (JOURNAL_DINT_INPUT, JOURNAL_DINT_OUTPUT, JOURNAL_DINT_MEMORY) + * @param index Buffer array index (0 to buffer_size-1) + * @param value 32-bit value to write + * @return 0 on success, -1 on failure + */ +int journal_write_dint(journal_buffer_type_t type, uint16_t index, + uint32_t value); + +/** + * @brief Write a 64-bit integer value to the journal + * + * @param type Buffer type (JOURNAL_LINT_INPUT, JOURNAL_LINT_OUTPUT, JOURNAL_LINT_MEMORY) + * @param index Buffer array index (0 to buffer_size-1) + * @param value 64-bit value to write + * @return 0 on success, -1 on failure + */ +int journal_write_lint(journal_buffer_type_t type, uint16_t index, + uint64_t value); + +/** + * @brief Apply all pending journal entries to image tables and clear the journal + * + * This function should be called at the start of each PLC scan cycle, + * BEFORE plugin cycle_start hooks are called. + * + * @note The image table mutex (image_mutex) MUST be held by the caller. + * This function acquires the journal mutex internally. + */ +void journal_apply_and_clear(void); + +/** + * @brief Get the number of pending journal entries + * + * Useful for diagnostics and monitoring. + * + * @return Number of entries waiting to be applied + */ +size_t journal_pending_count(void); + +/** + * @brief Get the current sequence number + * + * Useful for diagnostics. The sequence number increments with each write + * and resets to 0 when the journal is cleared. + * + * @return Current sequence number + */ +uint32_t journal_get_sequence(void); + +#ifdef __cplusplus +} +#endif + +#endif /* JOURNAL_BUFFER_H */ From ed6957b12a5d8f69219cf0832b34d85af20aaa0a Mon Sep 17 00:00:00 2001 From: Thiago Alves Date: Tue, 13 Jan 2026 15:29:36 -0500 Subject: [PATCH 03/11] Integrate journal buffer into PLC cycle (Phase 2) - Initialize journal buffer in plc_cycle_thread after image tables setup - Call journal_apply_and_clear() at start of each PLC cycle (after acquiring buffer_mutex, before plugin cycle_start hooks) - Cleanup journal buffer in unload_plc_program before clearing image tables This ensures all plugin writes from the journal are atomically applied at the start of each scan cycle, before plugins and the PLC program run. Co-Authored-By: Claude Opus 4.5 --- core/src/plc_app/plc_state_manager.c | 34 ++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/core/src/plc_app/plc_state_manager.c b/core/src/plc_app/plc_state_manager.c index c9f84be7..678eb586 100644 --- a/core/src/plc_app/plc_state_manager.c +++ b/core/src/plc_app/plc_state_manager.c @@ -3,6 +3,7 @@ #include "../drivers/plugin_driver.h" #include "image_tables.h" +#include "journal_buffer.h" #include "plc_state_manager.h" #include "scan_cycle_manager.h" #include "utils/log.h" @@ -36,6 +37,31 @@ void *plc_cycle_thread(void *arg) image_tables_fill_null_pointers(); plugin_mutex_give(&plugin_driver->buffer_mutex); + // Initialize journal buffer for race-condition-free plugin writes + journal_buffer_ptrs_t journal_ptrs = { + .bool_input = bool_input, + .bool_output = bool_output, + .bool_memory = bool_memory, + .byte_input = byte_input, + .byte_output = byte_output, + .int_input = int_input, + .int_output = int_output, + .int_memory = int_memory, + .dint_input = dint_input, + .dint_output = dint_output, + .dint_memory = dint_memory, + .lint_input = lint_input, + .lint_output = lint_output, + .lint_memory = lint_memory, + .buffer_size = BUFFER_SIZE, + .image_mutex = &plugin_driver->buffer_mutex + }; + if (journal_init(&journal_ptrs) != 0) { + log_error("Failed to initialize journal buffer"); + } else { + log_info("Journal buffer initialized"); + } + log_info("Starting main loop"); pthread_mutex_lock(&state_mutex); @@ -53,6 +79,10 @@ void *plc_cycle_thread(void *arg) scan_cycle_time_start(); plugin_mutex_take(&plugin_driver->buffer_mutex); + // Apply pending journal entries before plugin hooks run + // This ensures all plugin writes from the previous cycle are visible + journal_apply_and_clear(); + // Call cycle_start for all active native plugins that registered the hook plugin_driver_cycle_start(plugin_driver); @@ -160,6 +190,10 @@ int unload_plc_program(PluginManager *pm) // Wait for the PLC thread to finish pthread_join(plc_thread, NULL); + // Cleanup journal buffer before clearing image tables + journal_cleanup(); + log_info("Journal buffer cleaned up"); + // Clear temporary pointers from image tables before unloading // This ensures clean state for the next program load plugin_mutex_take(&plugin_driver->buffer_mutex); From cf7b381812bc0c6883a083ff11d8d5879681e72d Mon Sep 17 00:00:00 2001 From: Thiago Alves Date: Tue, 13 Jan 2026 15:43:05 -0500 Subject: [PATCH 04/11] Expose journal write functions to plugins (Phase 3) Add journal buffer API for Python plugins: - Add journal write wrapper functions in plugin_driver.c - Set journal function pointers in plugin_runtime_args_t - Add ctypes bindings in PluginRuntimeArgs Python class - Modify GenericBufferAccessor to use journal writes - Keep read operations unchanged (direct buffer access) All Python plugin writes now go through the journal system for race-condition-free buffer access. Co-Authored-By: Claude Opus 4.5 --- core/src/drivers/plugin_driver.c | 38 ++++++++ core/src/drivers/plugin_types.h | 27 ++++++ .../plugins/python/shared/buffer_accessor.py | 96 ++++++++++++++----- .../python/shared/plugin_runtime_args.py | 7 ++ 4 files changed, 146 insertions(+), 22 deletions(-) diff --git a/core/src/drivers/plugin_driver.c b/core/src/drivers/plugin_driver.c index eb7ce1d3..5bc20ae3 100644 --- a/core/src/drivers/plugin_driver.c +++ b/core/src/drivers/plugin_driver.c @@ -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" @@ -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) @@ -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); diff --git a/core/src/drivers/plugin_types.h b/core/src/drivers/plugin_types.h index 0c8d9aca..ea5933ac 100644 --- a/core/src/drivers/plugin_types.h +++ b/core/src/drivers/plugin_types.h @@ -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 * @@ -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 */ diff --git a/core/src/drivers/plugins/python/shared/buffer_accessor.py b/core/src/drivers/plugins/python/shared/buffer_accessor.py index 685c87d8..58222e7d 100644 --- a/core/src/drivers/plugins/python/shared/buffer_accessor.py +++ b/core/src/drivers/plugins/python/shared/buffer_accessor.py @@ -33,8 +33,29 @@ class GenericBufferAccessor(IBufferAccessor): This class encapsulates the complex ctypes buffer access logic and provides a clean, type-agnostic interface for buffer operations. It eliminates the massive code duplication that existed in the original SafeBufferAccess class. + + Write operations use the journal buffer system for race-condition-free writes. + Read operations access buffers directly (reads are always safe). """ + # Journal buffer type mapping (matches journal_buffer_type_t enum) + JOURNAL_TYPE_MAP = { + "bool_input": 0, # JOURNAL_BOOL_INPUT + "bool_output": 1, # JOURNAL_BOOL_OUTPUT + "bool_memory": 2, # JOURNAL_BOOL_MEMORY + "byte_input": 3, # JOURNAL_BYTE_INPUT + "byte_output": 4, # JOURNAL_BYTE_OUTPUT + "int_input": 5, # JOURNAL_INT_INPUT + "int_output": 6, # JOURNAL_INT_OUTPUT + "int_memory": 7, # JOURNAL_INT_MEMORY + "dint_input": 8, # JOURNAL_DINT_INPUT + "dint_output": 9, # JOURNAL_DINT_OUTPUT + "dint_memory": 10, # JOURNAL_DINT_MEMORY + "lint_input": 11, # JOURNAL_LINT_INPUT + "lint_output": 12, # JOURNAL_LINT_OUTPUT + "lint_memory": 13, # JOURNAL_LINT_MEMORY + } + def __init__(self, runtime_args, validator: BufferValidator, mutex_manager: MutexManager): """ Initialize the generic buffer accessor. @@ -97,12 +118,16 @@ def write_buffer( """ Generic buffer write operation. + Writes go through the journal buffer system for race-condition-free operation. + The journal is internally thread-safe, so the thread_safe parameter is kept + for backward compatibility but writes no longer require explicit mutex. + Args: buffer_type: Buffer type name (e.g., 'bool_output', 'int_output') buffer_idx: Buffer index value: Value to write bit_idx: Bit index (required for boolean operations) - thread_safe: Whether to use mutex protection + thread_safe: Kept for backward compatibility (journal is always thread-safe) Returns: Tuple[bool, str]: (success, error_message) @@ -117,18 +142,11 @@ def write_buffer( # Get buffer type info buffer_type_obj, direction = self.buffer_types.get_buffer_info(buffer_type) - # Define the write operation - def do_write(): - return self._perform_write( - buffer_type, buffer_type_obj, direction, buffer_idx, value, bit_idx - ) - - # Execute with or without mutex - if thread_safe: - result = self.mutex.with_mutex(do_write) - return result if isinstance(result, tuple) else (result, "Success") - else: - return do_write() + # Journal writes are thread-safe internally, no mutex needed + # The thread_safe parameter is ignored but kept for backward compatibility + return self._perform_write( + buffer_type, buffer_type_obj, direction, buffer_idx, value, bit_idx + ) def get_buffer_pointer(self, buffer_type: str) -> Optional[ctypes.POINTER]: """ @@ -215,27 +233,61 @@ def _perform_write( ) -> Tuple[bool, str]: """ Internal method to perform the actual buffer write operation. + + Uses the journal buffer system for race-condition-free writes. + All writes go through the journal and are applied atomically at the + start of the next PLC scan cycle. """ try: - # Get the appropriate buffer pointer - buffer_ptr = self.get_buffer_pointer(buffer_type) - if buffer_ptr is None or buffer_ptr.contents is None: - return False, f"Buffer pointer not available for {buffer_type}" + # Get journal buffer type + journal_type = self.JOURNAL_TYPE_MAP.get(buffer_type) + if journal_type is None: + return False, f"Unknown buffer type: {buffer_type}" # Handle boolean operations (require bit indexing) if buffer_type_obj.name == "bool": if bit_idx is None: return False, "Bit index required for boolean operations" - # Set the specific bit within the buffer - buffer_ptr[buffer_idx][bit_idx].contents.value = 1 if value else 0 + # Write through journal + result = self.args.journal_write_bool( + journal_type, buffer_idx, bit_idx, 1 if value else 0 + ) + if result != 0: + return False, f"Journal write failed with code {result}" return True, "Success" - # Handle other buffer types (direct value assignment) - else: - buffer_ptr[buffer_idx].contents.value = value + # Handle byte operations + elif buffer_type_obj.name == "byte": + result = self.args.journal_write_byte(journal_type, buffer_idx, int(value) & 0xFF) + if result != 0: + return False, f"Journal write failed with code {result}" return True, "Success" + # Handle int operations (16-bit) + elif buffer_type_obj.name == "int": + result = self.args.journal_write_int(journal_type, buffer_idx, int(value) & 0xFFFF) + if result != 0: + return False, f"Journal write failed with code {result}" + return True, "Success" + + # Handle dint operations (32-bit) + elif buffer_type_obj.name == "dint": + result = self.args.journal_write_dint(journal_type, buffer_idx, int(value) & 0xFFFFFFFF) + if result != 0: + return False, f"Journal write failed with code {result}" + return True, "Success" + + # Handle lint operations (64-bit) + elif buffer_type_obj.name == "lint": + result = self.args.journal_write_lint(journal_type, buffer_idx, int(value)) + if result != 0: + return False, f"Journal write failed with code {result}" + return True, "Success" + + else: + return False, f"Unsupported buffer type: {buffer_type_obj.name}" + except (AttributeError, TypeError, ValueError, OSError, MemoryError) as e: return False, f"Buffer write error: {e}" diff --git a/core/src/drivers/plugins/python/shared/plugin_runtime_args.py b/core/src/drivers/plugins/python/shared/plugin_runtime_args.py index 13c44257..a091e055 100644 --- a/core/src/drivers/plugins/python/shared/plugin_runtime_args.py +++ b/core/src/drivers/plugins/python/shared/plugin_runtime_args.py @@ -50,6 +50,13 @@ class PluginRuntimeArgs(ctypes.Structure): ("log_debug", ctypes.CFUNCTYPE(None, ctypes.c_char_p)), ("log_warn", ctypes.CFUNCTYPE(None, ctypes.c_char_p)), ("log_error", ctypes.CFUNCTYPE(None, ctypes.c_char_p)), + # Journal write function pointers for race-condition-free buffer writes + # int (*func)(int type, int index, int bit/value, int value) + ("journal_write_bool", ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int)), + ("journal_write_byte", ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int)), + ("journal_write_int", ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int)), + ("journal_write_dint", ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_uint)), + ("journal_write_lint", ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_ulonglong)), ] def validate_pointers(self): From c83f7017cd63163fc8336a6add06ccd49f9e2f0a Mon Sep 17 00:00:00 2001 From: Thiago Alves Date: Tue, 13 Jan 2026 15:52:20 -0500 Subject: [PATCH 05/11] Optimize Modbus Slave mutex usage for journal writes (Phase 5) - Remove unnecessary mutex from simple setValues methods since journal writes are internally thread-safe - Keep mutex for OpenPLCSegmentedHoldingRegistersDataBlock.setValues because it requires read-modify-write for partial DINT/LINT updates - Add documentation explaining journal-based thread safety - Update batch_processor.py with thread safety notes Affected setValues methods: - OpenPLCCoilsDataBlock: mutex removed (simple bool writes) - OpenPLCHoldingRegistersDataBlock: mutex removed (simple int writes) - OpenPLCSegmentedCoilsDataBlock: mutex removed (simple bool writes) - OpenPLCSegmentedHoldingRegistersDataBlock: mutex kept (RMW needed) Co-Authored-By: Claude Opus 4.5 --- .../python/modbus_slave/simple_modbus.py | 102 +++++++++--------- .../plugins/python/shared/batch_processor.py | 6 ++ 2 files changed, 60 insertions(+), 48 deletions(-) diff --git a/core/src/drivers/plugins/python/modbus_slave/simple_modbus.py b/core/src/drivers/plugins/python/modbus_slave/simple_modbus.py index b467c953..6c6c6f12 100644 --- a/core/src/drivers/plugins/python/modbus_slave/simple_modbus.py +++ b/core/src/drivers/plugins/python/modbus_slave/simple_modbus.py @@ -119,7 +119,11 @@ def getValues(self, address, count=1): return values def setValues(self, address, values): - """Set coil values to OpenPLC bool_output using SafeBufferAccess""" + """Set coil values to OpenPLC bool_output using SafeBufferAccess. + + Note: Writes go through the journal buffer system which is internally + thread-safe, so no mutex is needed for write operations. + """ address = address - 1 # Modbus addresses are 0-based if not self.safe_buffer_access.is_valid: @@ -129,9 +133,7 @@ def setValues(self, address, values): ) return - # Ensure thread-safe access - self.safe_buffer_access.acquire_mutex() - + # Journal writes are thread-safe, no mutex needed for i, value in enumerate(values): coil_addr = address + i @@ -141,15 +143,12 @@ def setValues(self, address, values): bit_idx = coil_addr % MAX_BITS # bit within buffer _, error_msg = self.safe_buffer_access.write_bool_output( - buffer_idx, bit_idx, bool(value), thread_safe=False + buffer_idx, bit_idx, bool(value) ) if error_msg != "Success": if logger: logger.error(f"Error setting coil {coil_addr}: {error_msg}") - # Release mutex after access - self.safe_buffer_access.release_mutex() - class OpenPLCDiscreteInputsDataBlock(ModbusSparseDataBlock): """Custom Modbus discrete inputs data block that mirrors OpenPLC bool_input.""" @@ -330,7 +329,11 @@ def getValues(self, address, count=1): return values def setValues(self, address, values): - """Set holding register values to OpenPLC int_output using SafeBufferAccess""" + """Set holding register values to OpenPLC int_output using SafeBufferAccess. + + Note: Writes go through the journal buffer system which is internally + thread-safe, so no mutex is needed for write operations. + """ address = address - 1 # Modbus addresses are 0-based if not self.safe_buffer_access.is_valid: @@ -340,23 +343,16 @@ def setValues(self, address, values): ) return - # Ensure buffer mutex - self.safe_buffer_access.acquire_mutex() - + # Journal writes are thread-safe, no mutex needed for i, value in enumerate(values): reg_addr = address + i if reg_addr < self.num_registers: - _, error_msg = self.safe_buffer_access.write_int_output( - reg_addr, value, thread_safe=False - ) + _, error_msg = self.safe_buffer_access.write_int_output(reg_addr, value) if error_msg != "Success": if logger: logger.error(f"Error setting holding register {reg_addr}: {error_msg}") - # Release mutex after access - self.safe_buffer_access.release_mutex() - class OpenPLCSegmentedCoilsDataBlock(ModbusSparseDataBlock): """ @@ -459,7 +455,11 @@ def getValues(self, address, count=1): self.safe_buffer_access.release_mutex() def setValues(self, address, values): - """Set coil values to appropriate OpenPLC buffer based on address segmentation""" + """Set coil values to appropriate OpenPLC buffer based on address segmentation. + + Note: Writes go through the journal buffer system which is internally + thread-safe, so no mutex is needed for write operations. + """ address = address - 1 # Modbus addresses are 1-based if not self.safe_buffer_access.is_valid: @@ -469,29 +469,26 @@ def setValues(self, address, values): ) return - self.safe_buffer_access.acquire_mutex() - try: - for i, value in enumerate(values): - coil_addr = address + i - segment, buffer_idx, bit_idx = self._get_segment_info(coil_addr) + # Journal writes are thread-safe, no mutex needed + for i, value in enumerate(values): + coil_addr = address + i + segment, buffer_idx, bit_idx = self._get_segment_info(coil_addr) - if segment == "qx": - _, error_msg = self.safe_buffer_access.write_bool_output( - buffer_idx, bit_idx, bool(value), thread_safe=False - ) - if error_msg != "Success": - if logger: - logger.error(f"Error setting coil %QX{coil_addr}: {error_msg}") - elif segment == "mx": - mx_addr = coil_addr - self.qx_bits - _, error_msg = self.safe_buffer_access.write_bool_memory( - buffer_idx, bit_idx, bool(value), thread_safe=False - ) - if error_msg != "Success": - if logger: - logger.error(f"Error setting coil %MX{mx_addr}: {error_msg}") - finally: - self.safe_buffer_access.release_mutex() + if segment == "qx": + _, error_msg = self.safe_buffer_access.write_bool_output( + buffer_idx, bit_idx, bool(value) + ) + if error_msg != "Success": + if logger: + logger.error(f"Error setting coil %QX{coil_addr}: {error_msg}") + elif segment == "mx": + mx_addr = coil_addr - self.qx_bits + _, error_msg = self.safe_buffer_access.write_bool_memory( + buffer_idx, bit_idx, bool(value) + ) + if error_msg != "Success": + if logger: + logger.error(f"Error setting coil %MX{mx_addr}: {error_msg}") class OpenPLCSegmentedHoldingRegistersDataBlock(ModbusSparseDataBlock): @@ -704,7 +701,13 @@ def getValues(self, address, count=1): self.safe_buffer_access.release_mutex() def setValues(self, address, values): - """Set holding register values to appropriate OpenPLC buffer based on address segmentation""" + """Set holding register values to appropriate OpenPLC buffer based on address segmentation. + + Note: While journal writes are thread-safe, this method still uses mutex because + partial DINT/LINT updates require read-modify-write (RMW) operations. The mutex + ensures consistency between the read and subsequent write for multi-word values. + Simple QW/MW writes don't strictly need the mutex, but we keep it for RMW safety. + """ address = address - 1 # Modbus addresses are 1-based if not self.safe_buffer_access.is_valid: @@ -714,18 +717,20 @@ def setValues(self, address, values): ) return + # Mutex needed for read-modify-write consistency on partial DINT/LINT updates self.safe_buffer_access.acquire_mutex() try: # For multi-word values, we need to handle partial writes carefully # Build a map of pending multi-word updates - pending_dint = {} # value_idx -> {word_offset: value} - pending_lint = {} # value_idx -> {word_offset: value} + pending_dint = {} # value_idx -> [words] + pending_lint = {} # value_idx -> [words] for i, value in enumerate(values): reg_addr = address + i segment, value_idx, word_offset = self._get_segment_info(reg_addr) if segment == "qw": + # Simple 16-bit write - journal handles thread safety _, error_msg = self.safe_buffer_access.write_int_output( value_idx, value & 0xFFFF, thread_safe=False ) @@ -734,6 +739,7 @@ def setValues(self, address, values): logger.error(f"Error setting %QW{value_idx}: {error_msg}") elif segment == "mw": + # Simple 16-bit write - journal handles thread safety _, error_msg = self.safe_buffer_access.write_int_memory( value_idx, value & 0xFFFF, thread_safe=False ) @@ -742,7 +748,7 @@ def setValues(self, address, values): logger.error(f"Error setting %MW{value_idx}: {error_msg}") elif segment == "md": - # Collect words for this DINT + # Partial 32-bit write - need RMW for consistency if value_idx not in pending_dint: # Read current value to preserve unchanged words current, _ = self.safe_buffer_access.read_dint_memory( @@ -754,7 +760,7 @@ def setValues(self, address, values): pending_dint[value_idx][word_offset] = value & 0xFFFF elif segment == "ml": - # Collect words for this LINT + # Partial 64-bit write - need RMW for consistency if value_idx not in pending_lint: # Read current value to preserve unchanged words current, _ = self.safe_buffer_access.read_lint_memory( @@ -765,7 +771,7 @@ def setValues(self, address, values): ) pending_lint[value_idx][word_offset] = value & 0xFFFF - # Write pending DINT values + # Write pending DINT values (RMW complete, write to journal) for value_idx, words in pending_dint.items(): dint_value = self._combine_words_to_dint(words) _, error_msg = self.safe_buffer_access.write_dint_memory( @@ -775,7 +781,7 @@ def setValues(self, address, values): if logger: logger.error(f"Error setting %MD{value_idx}: {error_msg}") - # Write pending LINT values + # Write pending LINT values (RMW complete, write to journal) for value_idx, words in pending_lint.items(): lint_value = self._combine_words_to_lint(words) _, error_msg = self.safe_buffer_access.write_lint_memory( diff --git a/core/src/drivers/plugins/python/shared/batch_processor.py b/core/src/drivers/plugins/python/shared/batch_processor.py index 332acfc7..0f0d09de 100644 --- a/core/src/drivers/plugins/python/shared/batch_processor.py +++ b/core/src/drivers/plugins/python/shared/batch_processor.py @@ -3,6 +3,12 @@ This module handles batch operations for optimized buffer access. It processes multiple read/write operations with a single mutex acquisition. + +Note on thread safety: +- Read operations require mutex to ensure consistent reads from image tables +- Write operations go through the journal buffer system which is internally + thread-safe. The mutex is still acquired for batch writes to maintain API + consistency and to support mixed read/write operations atomically. """ from typing import List, Tuple, Dict, Any From 4854ca87097e81ccad5f9e9e674e845becae0b5e Mon Sep 17 00:00:00 2001 From: Thiago Alves Date: Tue, 13 Jan 2026 16:43:04 -0500 Subject: [PATCH 06/11] Add S7Comm plugin with journal-based writes (Phase 4) Implement S7Comm plugin for Siemens S7 communication using Snap7 library. Uses journal buffer system for race-condition-free writes to OpenPLC. Key features: - On-demand data sync via Snap7 RWArea callback (no cycle hooks needed) - S7 client reads: acquire OpenPLC mutex, copy fresh data, release mutex - S7 client writes: use journal_write functions (thread-safe, no mutex) - Support for PE/PA/MK system areas and configurable data blocks - JSON-based configuration for flexible I/O mapping - Big-endian to little-endian conversion for S7 protocol The S7 server thread runs independently without requiring cycle_start/ cycle_end hooks for data synchronization, similar to Python Modbus plugins. Co-Authored-By: Claude Opus 4.5 --- .../plugins/native/s7comm/CMakeLists.txt | 151 + .../plugins/native/s7comm/cjson/cJSON.c | 3143 ++++++++++++++++ .../plugins/native/s7comm/cjson/cJSON.h | 300 ++ .../native/s7comm/docs/IMPLEMENTATION_PLAN.md | 1385 +++++++ .../plugins/native/s7comm/docs/JSON_SCHEMA.md | 460 +++ .../plugins/native/s7comm/docs/USER_GUIDE.md | 554 +++ .../plugins/native/s7comm/s7comm_config.c | 477 +++ .../plugins/native/s7comm/s7comm_config.h | 177 + .../plugins/native/s7comm/s7comm_config.json | 133 + .../plugins/native/s7comm/s7comm_plugin.cpp | 1098 ++++++ .../plugins/native/s7comm/s7comm_plugin.h | 74 + .../native/s7comm/snap7/core/s7_client.cpp | 503 +++ .../native/s7comm/snap7/core/s7_client.h | 104 + .../native/s7comm/snap7/core/s7_firmware.h | 1256 +++++++ .../native/s7comm/snap7/core/s7_isotcp.cpp | 541 +++ .../native/s7comm/snap7/core/s7_isotcp.h | 271 ++ .../s7comm/snap7/core/s7_micro_client.cpp | 3328 +++++++++++++++++ .../s7comm/snap7/core/s7_micro_client.h | 364 ++ .../native/s7comm/snap7/core/s7_partner.cpp | 1178 ++++++ .../native/s7comm/snap7/core/s7_partner.h | 284 ++ .../native/s7comm/snap7/core/s7_peer.cpp | 122 + .../native/s7comm/snap7/core/s7_peer.h | 58 + .../native/s7comm/snap7/core/s7_server.cpp | 2158 +++++++++++ .../native/s7comm/snap7/core/s7_server.h | 261 ++ .../native/s7comm/snap7/core/s7_text.cpp | 788 ++++ .../native/s7comm/snap7/core/s7_text.h | 49 + .../native/s7comm/snap7/core/s7_types.h | 1066 ++++++ .../native/s7comm/snap7/lib/snap7_libmain.cpp | 1196 ++++++ .../native/s7comm/snap7/lib/snap7_libmain.h | 201 + .../native/s7comm/snap7/sys/snap_msgsock.cpp | 923 +++++ .../native/s7comm/snap7/sys/snap_msgsock.h | 339 ++ .../native/s7comm/snap7/sys/snap_platform.h | 130 + .../native/s7comm/snap7/sys/snap_sysutils.cpp | 73 + .../native/s7comm/snap7/sys/snap_sysutils.h | 39 + .../native/s7comm/snap7/sys/snap_tcpsrvr.cpp | 487 +++ .../native/s7comm/snap7/sys/snap_tcpsrvr.h | 247 ++ .../native/s7comm/snap7/sys/snap_threads.cpp | 162 + .../native/s7comm/snap7/sys/snap_threads.h | 45 + .../native/s7comm/snap7/sys/sol_threads.h | 208 ++ .../native/s7comm/snap7/sys/unix_threads.h | 228 ++ .../native/s7comm/snap7/sys/win_threads.h | 159 + 41 files changed, 24720 insertions(+) create mode 100644 core/src/drivers/plugins/native/s7comm/CMakeLists.txt create mode 100644 core/src/drivers/plugins/native/s7comm/cjson/cJSON.c create mode 100644 core/src/drivers/plugins/native/s7comm/cjson/cJSON.h create mode 100644 core/src/drivers/plugins/native/s7comm/docs/IMPLEMENTATION_PLAN.md create mode 100644 core/src/drivers/plugins/native/s7comm/docs/JSON_SCHEMA.md create mode 100644 core/src/drivers/plugins/native/s7comm/docs/USER_GUIDE.md create mode 100644 core/src/drivers/plugins/native/s7comm/s7comm_config.c create mode 100644 core/src/drivers/plugins/native/s7comm/s7comm_config.h create mode 100644 core/src/drivers/plugins/native/s7comm/s7comm_config.json create mode 100644 core/src/drivers/plugins/native/s7comm/s7comm_plugin.cpp create mode 100644 core/src/drivers/plugins/native/s7comm/s7comm_plugin.h create mode 100644 core/src/drivers/plugins/native/s7comm/snap7/core/s7_client.cpp create mode 100644 core/src/drivers/plugins/native/s7comm/snap7/core/s7_client.h create mode 100644 core/src/drivers/plugins/native/s7comm/snap7/core/s7_firmware.h create mode 100644 core/src/drivers/plugins/native/s7comm/snap7/core/s7_isotcp.cpp create mode 100644 core/src/drivers/plugins/native/s7comm/snap7/core/s7_isotcp.h create mode 100644 core/src/drivers/plugins/native/s7comm/snap7/core/s7_micro_client.cpp create mode 100644 core/src/drivers/plugins/native/s7comm/snap7/core/s7_micro_client.h create mode 100644 core/src/drivers/plugins/native/s7comm/snap7/core/s7_partner.cpp create mode 100644 core/src/drivers/plugins/native/s7comm/snap7/core/s7_partner.h create mode 100644 core/src/drivers/plugins/native/s7comm/snap7/core/s7_peer.cpp create mode 100644 core/src/drivers/plugins/native/s7comm/snap7/core/s7_peer.h create mode 100644 core/src/drivers/plugins/native/s7comm/snap7/core/s7_server.cpp create mode 100644 core/src/drivers/plugins/native/s7comm/snap7/core/s7_server.h create mode 100644 core/src/drivers/plugins/native/s7comm/snap7/core/s7_text.cpp create mode 100644 core/src/drivers/plugins/native/s7comm/snap7/core/s7_text.h create mode 100644 core/src/drivers/plugins/native/s7comm/snap7/core/s7_types.h create mode 100644 core/src/drivers/plugins/native/s7comm/snap7/lib/snap7_libmain.cpp create mode 100644 core/src/drivers/plugins/native/s7comm/snap7/lib/snap7_libmain.h create mode 100644 core/src/drivers/plugins/native/s7comm/snap7/sys/snap_msgsock.cpp create mode 100644 core/src/drivers/plugins/native/s7comm/snap7/sys/snap_msgsock.h create mode 100644 core/src/drivers/plugins/native/s7comm/snap7/sys/snap_platform.h create mode 100644 core/src/drivers/plugins/native/s7comm/snap7/sys/snap_sysutils.cpp create mode 100644 core/src/drivers/plugins/native/s7comm/snap7/sys/snap_sysutils.h create mode 100644 core/src/drivers/plugins/native/s7comm/snap7/sys/snap_tcpsrvr.cpp create mode 100644 core/src/drivers/plugins/native/s7comm/snap7/sys/snap_tcpsrvr.h create mode 100644 core/src/drivers/plugins/native/s7comm/snap7/sys/snap_threads.cpp create mode 100644 core/src/drivers/plugins/native/s7comm/snap7/sys/snap_threads.h create mode 100644 core/src/drivers/plugins/native/s7comm/snap7/sys/sol_threads.h create mode 100644 core/src/drivers/plugins/native/s7comm/snap7/sys/unix_threads.h create mode 100644 core/src/drivers/plugins/native/s7comm/snap7/sys/win_threads.h diff --git a/core/src/drivers/plugins/native/s7comm/CMakeLists.txt b/core/src/drivers/plugins/native/s7comm/CMakeLists.txt new file mode 100644 index 00000000..be15fd78 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/CMakeLists.txt @@ -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= +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 + $<$>: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) + $<$,$>:-Wno-class-memaccess> + $<$,$>:-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 +) diff --git a/core/src/drivers/plugins/native/s7comm/cjson/cJSON.c b/core/src/drivers/plugins/native/s7comm/cjson/cJSON.c new file mode 100644 index 00000000..61483d90 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/cjson/cJSON.c @@ -0,0 +1,3143 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +/* cJSON */ +/* JSON parser in C. */ + +/* disable warnings about old C89 functions in MSVC */ +#if !defined(_CRT_SECURE_NO_DEPRECATE) && defined(_MSC_VER) +#define _CRT_SECURE_NO_DEPRECATE +#endif + +#ifdef __GNUC__ +#pragma GCC visibility push(default) +#endif +#if defined(_MSC_VER) +#pragma warning (push) +/* disable warning about single line comments in system headers */ +#pragma warning (disable : 4001) +#endif + +#include +#include +#include +#include +#include +#include +#include + +#ifdef ENABLE_LOCALES +#include +#endif + +#if defined(_MSC_VER) +#pragma warning (pop) +#endif +#ifdef __GNUC__ +#pragma GCC visibility pop +#endif + +#include "cJSON.h" + +/* define our own boolean type */ +#ifdef true +#undef true +#endif +#define true ((cJSON_bool)1) + +#ifdef false +#undef false +#endif +#define false ((cJSON_bool)0) + +/* define isnan and isinf for ANSI C, if in C99 or above, isnan and isinf has been defined in math.h */ +#ifndef isinf +#define isinf(d) (isnan((d - d)) && !isnan(d)) +#endif +#ifndef isnan +#define isnan(d) (d != d) +#endif + +#ifndef NAN +#ifdef _WIN32 +#define NAN sqrt(-1.0) +#else +#define NAN 0.0/0.0 +#endif +#endif + +typedef struct { + const unsigned char *json; + size_t position; +} error; +static error global_error = { NULL, 0 }; + +CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void) +{ + return (const char*) (global_error.json + global_error.position); +} + +CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item) +{ + if (!cJSON_IsString(item)) + { + return NULL; + } + + return item->valuestring; +} + +CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item) +{ + if (!cJSON_IsNumber(item)) + { + return (double) NAN; + } + + return item->valuedouble; +} + +/* This is a safeguard to prevent copy-pasters from using incompatible C and header files */ +#if (CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 7) || (CJSON_VERSION_PATCH != 18) + #error cJSON.h and cJSON.c have different versions. Make sure that both have the same. +#endif + +CJSON_PUBLIC(const char*) cJSON_Version(void) +{ + static char version[15]; + sprintf(version, "%i.%i.%i", CJSON_VERSION_MAJOR, CJSON_VERSION_MINOR, CJSON_VERSION_PATCH); + + return version; +} + +/* Case insensitive string comparison, doesn't consider two NULL pointers equal though */ +static int case_insensitive_strcmp(const unsigned char *string1, const unsigned char *string2) +{ + if ((string1 == NULL) || (string2 == NULL)) + { + return 1; + } + + if (string1 == string2) + { + return 0; + } + + for(; tolower(*string1) == tolower(*string2); (void)string1++, string2++) + { + if (*string1 == '\0') + { + return 0; + } + } + + return tolower(*string1) - tolower(*string2); +} + +typedef struct internal_hooks +{ + void *(CJSON_CDECL *allocate)(size_t size); + void (CJSON_CDECL *deallocate)(void *pointer); + void *(CJSON_CDECL *reallocate)(void *pointer, size_t size); +} internal_hooks; + +#if defined(_MSC_VER) +/* work around MSVC error C2322: '...' address of dllimport '...' is not static */ +static void * CJSON_CDECL internal_malloc(size_t size) +{ + return malloc(size); +} +static void CJSON_CDECL internal_free(void *pointer) +{ + free(pointer); +} +static void * CJSON_CDECL internal_realloc(void *pointer, size_t size) +{ + return realloc(pointer, size); +} +#else +#define internal_malloc malloc +#define internal_free free +#define internal_realloc realloc +#endif + +/* strlen of character literals resolved at compile time */ +#define static_strlen(string_literal) (sizeof(string_literal) - sizeof("")) + +static internal_hooks global_hooks = { internal_malloc, internal_free, internal_realloc }; + +static unsigned char* cJSON_strdup(const unsigned char* string, const internal_hooks * const hooks) +{ + size_t length = 0; + unsigned char *copy = NULL; + + if (string == NULL) + { + return NULL; + } + + length = strlen((const char*)string) + sizeof(""); + copy = (unsigned char*)hooks->allocate(length); + if (copy == NULL) + { + return NULL; + } + memcpy(copy, string, length); + + return copy; +} + +CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks) +{ + if (hooks == NULL) + { + /* Reset hooks */ + global_hooks.allocate = malloc; + global_hooks.deallocate = free; + global_hooks.reallocate = realloc; + return; + } + + global_hooks.allocate = malloc; + if (hooks->malloc_fn != NULL) + { + global_hooks.allocate = hooks->malloc_fn; + } + + global_hooks.deallocate = free; + if (hooks->free_fn != NULL) + { + global_hooks.deallocate = hooks->free_fn; + } + + /* use realloc only if both free and malloc are used */ + global_hooks.reallocate = NULL; + if ((global_hooks.allocate == malloc) && (global_hooks.deallocate == free)) + { + global_hooks.reallocate = realloc; + } +} + +/* Internal constructor. */ +static cJSON *cJSON_New_Item(const internal_hooks * const hooks) +{ + cJSON* node = (cJSON*)hooks->allocate(sizeof(cJSON)); + if (node) + { + memset(node, '\0', sizeof(cJSON)); + } + + return node; +} + +/* Delete a cJSON structure. */ +CJSON_PUBLIC(void) cJSON_Delete(cJSON *item) +{ + cJSON *next = NULL; + while (item != NULL) + { + next = item->next; + if (!(item->type & cJSON_IsReference) && (item->child != NULL)) + { + cJSON_Delete(item->child); + } + if (!(item->type & cJSON_IsReference) && (item->valuestring != NULL)) + { + global_hooks.deallocate(item->valuestring); + item->valuestring = NULL; + } + if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) + { + global_hooks.deallocate(item->string); + item->string = NULL; + } + global_hooks.deallocate(item); + item = next; + } +} + +/* get the decimal point character of the current locale */ +static unsigned char get_decimal_point(void) +{ +#ifdef ENABLE_LOCALES + struct lconv *lconv = localeconv(); + return (unsigned char) lconv->decimal_point[0]; +#else + return '.'; +#endif +} + +typedef struct +{ + const unsigned char *content; + size_t length; + size_t offset; + size_t depth; /* How deeply nested (in arrays/objects) is the input at the current offset. */ + internal_hooks hooks; +} parse_buffer; + +/* check if the given size is left to read in a given parse buffer (starting with 1) */ +#define can_read(buffer, size) ((buffer != NULL) && (((buffer)->offset + size) <= (buffer)->length)) +/* check if the buffer can be accessed at the given index (starting with 0) */ +#define can_access_at_index(buffer, index) ((buffer != NULL) && (((buffer)->offset + index) < (buffer)->length)) +#define cannot_access_at_index(buffer, index) (!can_access_at_index(buffer, index)) +/* get a pointer to the buffer at the position */ +#define buffer_at_offset(buffer) ((buffer)->content + (buffer)->offset) + +/* Parse the input text to generate a number, and populate the result into item. */ +static cJSON_bool parse_number(cJSON * const item, parse_buffer * const input_buffer) +{ + double number = 0; + unsigned char *after_end = NULL; + unsigned char number_c_string[64]; + unsigned char decimal_point = get_decimal_point(); + size_t i = 0; + + if ((input_buffer == NULL) || (input_buffer->content == NULL)) + { + return false; + } + + /* copy the number into a temporary buffer and replace '.' with the decimal point + * of the current locale (for strtod) + * This also takes care of '\0' not necessarily being available for marking the end of the input */ + for (i = 0; (i < (sizeof(number_c_string) - 1)) && can_access_at_index(input_buffer, i); i++) + { + switch (buffer_at_offset(input_buffer)[i]) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '+': + case '-': + case 'e': + case 'E': + number_c_string[i] = buffer_at_offset(input_buffer)[i]; + break; + + case '.': + number_c_string[i] = decimal_point; + break; + + default: + goto loop_end; + } + } +loop_end: + number_c_string[i] = '\0'; + + number = strtod((const char*)number_c_string, (char**)&after_end); + if (number_c_string == after_end) + { + return false; /* parse_error */ + } + + item->valuedouble = number; + + /* use saturation in case of overflow */ + if (number >= INT_MAX) + { + item->valueint = INT_MAX; + } + else if (number <= (double)INT_MIN) + { + item->valueint = INT_MIN; + } + else + { + item->valueint = (int)number; + } + + item->type = cJSON_Number; + + input_buffer->offset += (size_t)(after_end - number_c_string); + return true; +} + +/* don't ask me, but the original cJSON_SetNumberValue returns an integer or double */ +CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number) +{ + if (number >= INT_MAX) + { + object->valueint = INT_MAX; + } + else if (number <= (double)INT_MIN) + { + object->valueint = INT_MIN; + } + else + { + object->valueint = (int)number; + } + + return object->valuedouble = number; +} + +/* Note: when passing a NULL valuestring, cJSON_SetValuestring treats this as an error and return NULL */ +CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring) +{ + char *copy = NULL; + /* if object's type is not cJSON_String or is cJSON_IsReference, it should not set valuestring */ + if ((object == NULL) || !(object->type & cJSON_String) || (object->type & cJSON_IsReference)) + { + return NULL; + } + /* return NULL if the object is corrupted or valuestring is NULL */ + if (object->valuestring == NULL || valuestring == NULL) + { + return NULL; + } + if (strlen(valuestring) <= strlen(object->valuestring)) + { + strcpy(object->valuestring, valuestring); + return object->valuestring; + } + copy = (char*) cJSON_strdup((const unsigned char*)valuestring, &global_hooks); + if (copy == NULL) + { + return NULL; + } + if (object->valuestring != NULL) + { + cJSON_free(object->valuestring); + } + object->valuestring = copy; + + return copy; +} + +typedef struct +{ + unsigned char *buffer; + size_t length; + size_t offset; + size_t depth; /* current nesting depth (for formatted printing) */ + cJSON_bool noalloc; + cJSON_bool format; /* is this print a formatted print */ + internal_hooks hooks; +} printbuffer; + +/* realloc printbuffer if necessary to have at least "needed" bytes more */ +static unsigned char* ensure(printbuffer * const p, size_t needed) +{ + unsigned char *newbuffer = NULL; + size_t newsize = 0; + + if ((p == NULL) || (p->buffer == NULL)) + { + return NULL; + } + + if ((p->length > 0) && (p->offset >= p->length)) + { + /* make sure that offset is valid */ + return NULL; + } + + if (needed > INT_MAX) + { + /* sizes bigger than INT_MAX are currently not supported */ + return NULL; + } + + needed += p->offset + 1; + if (needed <= p->length) + { + return p->buffer + p->offset; + } + + if (p->noalloc) { + return NULL; + } + + /* calculate new buffer size */ + if (needed > (INT_MAX / 2)) + { + /* overflow of int, use INT_MAX if possible */ + if (needed <= INT_MAX) + { + newsize = INT_MAX; + } + else + { + return NULL; + } + } + else + { + newsize = needed * 2; + } + + if (p->hooks.reallocate != NULL) + { + /* reallocate with realloc if available */ + newbuffer = (unsigned char*)p->hooks.reallocate(p->buffer, newsize); + if (newbuffer == NULL) + { + p->hooks.deallocate(p->buffer); + p->length = 0; + p->buffer = NULL; + + return NULL; + } + } + else + { + /* otherwise reallocate manually */ + newbuffer = (unsigned char*)p->hooks.allocate(newsize); + if (!newbuffer) + { + p->hooks.deallocate(p->buffer); + p->length = 0; + p->buffer = NULL; + + return NULL; + } + + memcpy(newbuffer, p->buffer, p->offset + 1); + p->hooks.deallocate(p->buffer); + } + p->length = newsize; + p->buffer = newbuffer; + + return newbuffer + p->offset; +} + +/* calculate the new length of the string in a printbuffer and update the offset */ +static void update_offset(printbuffer * const buffer) +{ + const unsigned char *buffer_pointer = NULL; + if ((buffer == NULL) || (buffer->buffer == NULL)) + { + return; + } + buffer_pointer = buffer->buffer + buffer->offset; + + buffer->offset += strlen((const char*)buffer_pointer); +} + +/* securely comparison of floating-point variables */ +static cJSON_bool compare_double(double a, double b) +{ + double maxVal = fabs(a) > fabs(b) ? fabs(a) : fabs(b); + return (fabs(a - b) <= maxVal * DBL_EPSILON); +} + +/* Render the number nicely from the given item into a string. */ +static cJSON_bool print_number(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + double d = item->valuedouble; + int length = 0; + size_t i = 0; + unsigned char number_buffer[26] = {0}; /* temporary buffer to print the number into */ + unsigned char decimal_point = get_decimal_point(); + double test = 0.0; + + if (output_buffer == NULL) + { + return false; + } + + /* This checks for NaN and Infinity */ + if (isnan(d) || isinf(d)) + { + length = sprintf((char*)number_buffer, "null"); + } + else if(d == (double)item->valueint) + { + length = sprintf((char*)number_buffer, "%d", item->valueint); + } + else + { + /* Try 15 decimal places of precision to avoid nonsignificant nonzero digits */ + length = sprintf((char*)number_buffer, "%1.15g", d); + + /* Check whether the original double can be recovered */ + if ((sscanf((char*)number_buffer, "%lg", &test) != 1) || !compare_double((double)test, d)) + { + /* If not, print with 17 decimal places of precision */ + length = sprintf((char*)number_buffer, "%1.17g", d); + } + } + + /* sprintf failed or buffer overrun occurred */ + if ((length < 0) || (length > (int)(sizeof(number_buffer) - 1))) + { + return false; + } + + /* reserve appropriate space in the output */ + output_pointer = ensure(output_buffer, (size_t)length + sizeof("")); + if (output_pointer == NULL) + { + return false; + } + + /* copy the printed number to the output and replace locale + * dependent decimal point with '.' */ + for (i = 0; i < ((size_t)length); i++) + { + if (number_buffer[i] == decimal_point) + { + output_pointer[i] = '.'; + continue; + } + + output_pointer[i] = number_buffer[i]; + } + output_pointer[i] = '\0'; + + output_buffer->offset += (size_t)length; + + return true; +} + +/* parse 4 digit hexadecimal number */ +static unsigned parse_hex4(const unsigned char * const input) +{ + unsigned int h = 0; + size_t i = 0; + + for (i = 0; i < 4; i++) + { + /* parse digit */ + if ((input[i] >= '0') && (input[i] <= '9')) + { + h += (unsigned int) input[i] - '0'; + } + else if ((input[i] >= 'A') && (input[i] <= 'F')) + { + h += (unsigned int) 10 + input[i] - 'A'; + } + else if ((input[i] >= 'a') && (input[i] <= 'f')) + { + h += (unsigned int) 10 + input[i] - 'a'; + } + else /* invalid */ + { + return 0; + } + + if (i < 3) + { + /* shift left to make place for the next nibble */ + h = h << 4; + } + } + + return h; +} + +/* converts a UTF-16 literal to UTF-8 + * A literal can be one or two sequences of the form \uXXXX */ +static unsigned char utf16_literal_to_utf8(const unsigned char * const input_pointer, const unsigned char * const input_end, unsigned char **output_pointer) +{ + long unsigned int codepoint = 0; + unsigned int first_code = 0; + const unsigned char *first_sequence = input_pointer; + unsigned char utf8_length = 0; + unsigned char utf8_position = 0; + unsigned char sequence_length = 0; + unsigned char first_byte_mark = 0; + + if ((input_end - first_sequence) < 6) + { + /* input ends unexpectedly */ + goto fail; + } + + /* get the first utf16 sequence */ + first_code = parse_hex4(first_sequence + 2); + + /* check that the code is valid */ + if (((first_code >= 0xDC00) && (first_code <= 0xDFFF))) + { + goto fail; + } + + /* UTF16 surrogate pair */ + if ((first_code >= 0xD800) && (first_code <= 0xDBFF)) + { + const unsigned char *second_sequence = first_sequence + 6; + unsigned int second_code = 0; + sequence_length = 12; /* \uXXXX\uXXXX */ + + if ((input_end - second_sequence) < 6) + { + /* input ends unexpectedly */ + goto fail; + } + + if ((second_sequence[0] != '\\') || (second_sequence[1] != 'u')) + { + /* missing second half of the surrogate pair */ + goto fail; + } + + /* get the second utf16 sequence */ + second_code = parse_hex4(second_sequence + 2); + /* check that the code is valid */ + if ((second_code < 0xDC00) || (second_code > 0xDFFF)) + { + /* invalid second half of the surrogate pair */ + goto fail; + } + + + /* calculate the unicode codepoint from the surrogate pair */ + codepoint = 0x10000 + (((first_code & 0x3FF) << 10) | (second_code & 0x3FF)); + } + else + { + sequence_length = 6; /* \uXXXX */ + codepoint = first_code; + } + + /* encode as UTF-8 + * takes at maximum 4 bytes to encode: + * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ + if (codepoint < 0x80) + { + /* normal ascii, encoding 0xxxxxxx */ + utf8_length = 1; + } + else if (codepoint < 0x800) + { + /* two bytes, encoding 110xxxxx 10xxxxxx */ + utf8_length = 2; + first_byte_mark = 0xC0; /* 11000000 */ + } + else if (codepoint < 0x10000) + { + /* three bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx */ + utf8_length = 3; + first_byte_mark = 0xE0; /* 11100000 */ + } + else if (codepoint <= 0x10FFFF) + { + /* four bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx */ + utf8_length = 4; + first_byte_mark = 0xF0; /* 11110000 */ + } + else + { + /* invalid unicode codepoint */ + goto fail; + } + + /* encode as utf8 */ + for (utf8_position = (unsigned char)(utf8_length - 1); utf8_position > 0; utf8_position--) + { + /* 10xxxxxx */ + (*output_pointer)[utf8_position] = (unsigned char)((codepoint | 0x80) & 0xBF); + codepoint >>= 6; + } + /* encode first byte */ + if (utf8_length > 1) + { + (*output_pointer)[0] = (unsigned char)((codepoint | first_byte_mark) & 0xFF); + } + else + { + (*output_pointer)[0] = (unsigned char)(codepoint & 0x7F); + } + + *output_pointer += utf8_length; + + return sequence_length; + +fail: + return 0; +} + +/* Parse the input text into an unescaped cinput, and populate item. */ +static cJSON_bool parse_string(cJSON * const item, parse_buffer * const input_buffer) +{ + const unsigned char *input_pointer = buffer_at_offset(input_buffer) + 1; + const unsigned char *input_end = buffer_at_offset(input_buffer) + 1; + unsigned char *output_pointer = NULL; + unsigned char *output = NULL; + + /* not a string */ + if (buffer_at_offset(input_buffer)[0] != '\"') + { + goto fail; + } + + { + /* calculate approximate size of the output (overestimate) */ + size_t allocation_length = 0; + size_t skipped_bytes = 0; + while (((size_t)(input_end - input_buffer->content) < input_buffer->length) && (*input_end != '\"')) + { + /* is escape sequence */ + if (input_end[0] == '\\') + { + if ((size_t)(input_end + 1 - input_buffer->content) >= input_buffer->length) + { + /* prevent buffer overflow when last input character is a backslash */ + goto fail; + } + skipped_bytes++; + input_end++; + } + input_end++; + } + if (((size_t)(input_end - input_buffer->content) >= input_buffer->length) || (*input_end != '\"')) + { + goto fail; /* string ended unexpectedly */ + } + + /* This is at most how much we need for the output */ + allocation_length = (size_t) (input_end - buffer_at_offset(input_buffer)) - skipped_bytes; + output = (unsigned char*)input_buffer->hooks.allocate(allocation_length + sizeof("")); + if (output == NULL) + { + goto fail; /* allocation failure */ + } + } + + output_pointer = output; + /* loop through the string literal */ + while (input_pointer < input_end) + { + if (*input_pointer != '\\') + { + *output_pointer++ = *input_pointer++; + } + /* escape sequence */ + else + { + unsigned char sequence_length = 2; + if ((input_end - input_pointer) < 1) + { + goto fail; + } + + switch (input_pointer[1]) + { + case 'b': + *output_pointer++ = '\b'; + break; + case 'f': + *output_pointer++ = '\f'; + break; + case 'n': + *output_pointer++ = '\n'; + break; + case 'r': + *output_pointer++ = '\r'; + break; + case 't': + *output_pointer++ = '\t'; + break; + case '\"': + case '\\': + case '/': + *output_pointer++ = input_pointer[1]; + break; + + /* UTF-16 literal */ + case 'u': + sequence_length = utf16_literal_to_utf8(input_pointer, input_end, &output_pointer); + if (sequence_length == 0) + { + /* failed to convert UTF16-literal to UTF-8 */ + goto fail; + } + break; + + default: + goto fail; + } + input_pointer += sequence_length; + } + } + + /* zero terminate the output */ + *output_pointer = '\0'; + + item->type = cJSON_String; + item->valuestring = (char*)output; + + input_buffer->offset = (size_t) (input_end - input_buffer->content); + input_buffer->offset++; + + return true; + +fail: + if (output != NULL) + { + input_buffer->hooks.deallocate(output); + output = NULL; + } + + if (input_pointer != NULL) + { + input_buffer->offset = (size_t)(input_pointer - input_buffer->content); + } + + return false; +} + +/* Render the cstring provided to an escaped version that can be printed. */ +static cJSON_bool print_string_ptr(const unsigned char * const input, printbuffer * const output_buffer) +{ + const unsigned char *input_pointer = NULL; + unsigned char *output = NULL; + unsigned char *output_pointer = NULL; + size_t output_length = 0; + /* numbers of additional characters needed for escaping */ + size_t escape_characters = 0; + + if (output_buffer == NULL) + { + return false; + } + + /* empty string */ + if (input == NULL) + { + output = ensure(output_buffer, sizeof("\"\"")); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "\"\""); + + return true; + } + + /* set "flag" to 1 if something needs to be escaped */ + for (input_pointer = input; *input_pointer; input_pointer++) + { + switch (*input_pointer) + { + case '\"': + case '\\': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + /* one character escape sequence */ + escape_characters++; + break; + default: + if (*input_pointer < 32) + { + /* UTF-16 escape sequence uXXXX */ + escape_characters += 5; + } + break; + } + } + output_length = (size_t)(input_pointer - input) + escape_characters; + + output = ensure(output_buffer, output_length + sizeof("\"\"")); + if (output == NULL) + { + return false; + } + + /* no characters have to be escaped */ + if (escape_characters == 0) + { + output[0] = '\"'; + memcpy(output + 1, input, output_length); + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; + } + + output[0] = '\"'; + output_pointer = output + 1; + /* copy the string */ + for (input_pointer = input; *input_pointer != '\0'; (void)input_pointer++, output_pointer++) + { + if ((*input_pointer > 31) && (*input_pointer != '\"') && (*input_pointer != '\\')) + { + /* normal character, copy */ + *output_pointer = *input_pointer; + } + else + { + /* character needs to be escaped */ + *output_pointer++ = '\\'; + switch (*input_pointer) + { + case '\\': + *output_pointer = '\\'; + break; + case '\"': + *output_pointer = '\"'; + break; + case '\b': + *output_pointer = 'b'; + break; + case '\f': + *output_pointer = 'f'; + break; + case '\n': + *output_pointer = 'n'; + break; + case '\r': + *output_pointer = 'r'; + break; + case '\t': + *output_pointer = 't'; + break; + default: + /* escape and print as unicode codepoint */ + sprintf((char*)output_pointer, "u%04x", *input_pointer); + output_pointer += 4; + break; + } + } + } + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; +} + +/* Invoke print_string_ptr (which is useful) on an item. */ +static cJSON_bool print_string(const cJSON * const item, printbuffer * const p) +{ + return print_string_ptr((unsigned char*)item->valuestring, p); +} + +/* Predeclare these prototypes. */ +static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer); +static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer); +static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer); + +/* Utility to jump whitespace and cr/lf */ +static parse_buffer *buffer_skip_whitespace(parse_buffer * const buffer) +{ + if ((buffer == NULL) || (buffer->content == NULL)) + { + return NULL; + } + + if (cannot_access_at_index(buffer, 0)) + { + return buffer; + } + + while (can_access_at_index(buffer, 0) && (buffer_at_offset(buffer)[0] <= 32)) + { + buffer->offset++; + } + + if (buffer->offset == buffer->length) + { + buffer->offset--; + } + + return buffer; +} + +/* skip the UTF-8 BOM (byte order mark) if it is at the beginning of a buffer */ +static parse_buffer *skip_utf8_bom(parse_buffer * const buffer) +{ + if ((buffer == NULL) || (buffer->content == NULL) || (buffer->offset != 0)) + { + return NULL; + } + + if (can_access_at_index(buffer, 4) && (strncmp((const char*)buffer_at_offset(buffer), "\xEF\xBB\xBF", 3) == 0)) + { + buffer->offset += 3; + } + + return buffer; +} + +CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated) +{ + size_t buffer_length; + + if (NULL == value) + { + return NULL; + } + + /* Adding null character size due to require_null_terminated. */ + buffer_length = strlen(value) + sizeof(""); + + return cJSON_ParseWithLengthOpts(value, buffer_length, return_parse_end, require_null_terminated); +} + +/* Parse an object - create a new root, and populate. */ +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated) +{ + parse_buffer buffer = { 0, 0, 0, 0, { 0, 0, 0 } }; + cJSON *item = NULL; + + /* reset error position */ + global_error.json = NULL; + global_error.position = 0; + + if (value == NULL || 0 == buffer_length) + { + goto fail; + } + + buffer.content = (const unsigned char*)value; + buffer.length = buffer_length; + buffer.offset = 0; + buffer.hooks = global_hooks; + + item = cJSON_New_Item(&global_hooks); + if (item == NULL) /* memory fail */ + { + goto fail; + } + + if (!parse_value(item, buffer_skip_whitespace(skip_utf8_bom(&buffer)))) + { + /* parse failure. ep is set. */ + goto fail; + } + + /* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */ + if (require_null_terminated) + { + buffer_skip_whitespace(&buffer); + if ((buffer.offset >= buffer.length) || buffer_at_offset(&buffer)[0] != '\0') + { + goto fail; + } + } + if (return_parse_end) + { + *return_parse_end = (const char*)buffer_at_offset(&buffer); + } + + return item; + +fail: + if (item != NULL) + { + cJSON_Delete(item); + } + + if (value != NULL) + { + error local_error; + local_error.json = (const unsigned char*)value; + local_error.position = 0; + + if (buffer.offset < buffer.length) + { + local_error.position = buffer.offset; + } + else if (buffer.length > 0) + { + local_error.position = buffer.length - 1; + } + + if (return_parse_end != NULL) + { + *return_parse_end = (const char*)local_error.json + local_error.position; + } + + global_error = local_error; + } + + return NULL; +} + +/* Default options for cJSON_Parse */ +CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value) +{ + return cJSON_ParseWithOpts(value, 0, 0); +} + +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length) +{ + return cJSON_ParseWithLengthOpts(value, buffer_length, 0, 0); +} + +#define cjson_min(a, b) (((a) < (b)) ? (a) : (b)) + +static unsigned char *print(const cJSON * const item, cJSON_bool format, const internal_hooks * const hooks) +{ + static const size_t default_buffer_size = 256; + printbuffer buffer[1]; + unsigned char *printed = NULL; + + memset(buffer, 0, sizeof(buffer)); + + /* create buffer */ + buffer->buffer = (unsigned char*) hooks->allocate(default_buffer_size); + buffer->length = default_buffer_size; + buffer->format = format; + buffer->hooks = *hooks; + if (buffer->buffer == NULL) + { + goto fail; + } + + /* print the value */ + if (!print_value(item, buffer)) + { + goto fail; + } + update_offset(buffer); + + /* check if reallocate is available */ + if (hooks->reallocate != NULL) + { + printed = (unsigned char*) hooks->reallocate(buffer->buffer, buffer->offset + 1); + if (printed == NULL) { + goto fail; + } + buffer->buffer = NULL; + } + else /* otherwise copy the JSON over to a new buffer */ + { + printed = (unsigned char*) hooks->allocate(buffer->offset + 1); + if (printed == NULL) + { + goto fail; + } + memcpy(printed, buffer->buffer, cjson_min(buffer->length, buffer->offset + 1)); + printed[buffer->offset] = '\0'; /* just to be sure */ + + /* free the buffer */ + hooks->deallocate(buffer->buffer); + buffer->buffer = NULL; + } + + return printed; + +fail: + if (buffer->buffer != NULL) + { + hooks->deallocate(buffer->buffer); + buffer->buffer = NULL; + } + + if (printed != NULL) + { + hooks->deallocate(printed); + printed = NULL; + } + + return NULL; +} + +/* Render a cJSON item/entity/structure to text. */ +CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item) +{ + return (char*)print(item, true, &global_hooks); +} + +CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item) +{ + return (char*)print(item, false, &global_hooks); +} + +CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt) +{ + printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; + + if (prebuffer < 0) + { + return NULL; + } + + p.buffer = (unsigned char*)global_hooks.allocate((size_t)prebuffer); + if (!p.buffer) + { + return NULL; + } + + p.length = (size_t)prebuffer; + p.offset = 0; + p.noalloc = false; + p.format = fmt; + p.hooks = global_hooks; + + if (!print_value(item, &p)) + { + global_hooks.deallocate(p.buffer); + p.buffer = NULL; + return NULL; + } + + return (char*)p.buffer; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format) +{ + printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; + + if ((length < 0) || (buffer == NULL)) + { + return false; + } + + p.buffer = (unsigned char*)buffer; + p.length = (size_t)length; + p.offset = 0; + p.noalloc = true; + p.format = format; + p.hooks = global_hooks; + + return print_value(item, &p); +} + +/* Parser core - when encountering text, process appropriately. */ +static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer) +{ + if ((input_buffer == NULL) || (input_buffer->content == NULL)) + { + return false; /* no input */ + } + + /* parse the different types of values */ + /* null */ + if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "null", 4) == 0)) + { + item->type = cJSON_NULL; + input_buffer->offset += 4; + return true; + } + /* false */ + if (can_read(input_buffer, 5) && (strncmp((const char*)buffer_at_offset(input_buffer), "false", 5) == 0)) + { + item->type = cJSON_False; + input_buffer->offset += 5; + return true; + } + /* true */ + if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "true", 4) == 0)) + { + item->type = cJSON_True; + item->valueint = 1; + input_buffer->offset += 4; + return true; + } + /* string */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '\"')) + { + return parse_string(item, input_buffer); + } + /* number */ + if (can_access_at_index(input_buffer, 0) && ((buffer_at_offset(input_buffer)[0] == '-') || ((buffer_at_offset(input_buffer)[0] >= '0') && (buffer_at_offset(input_buffer)[0] <= '9')))) + { + return parse_number(item, input_buffer); + } + /* array */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '[')) + { + return parse_array(item, input_buffer); + } + /* object */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '{')) + { + return parse_object(item, input_buffer); + } + + return false; +} + +/* Render a value to text. */ +static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output = NULL; + + if ((item == NULL) || (output_buffer == NULL)) + { + return false; + } + + switch ((item->type) & 0xFF) + { + case cJSON_NULL: + output = ensure(output_buffer, 5); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "null"); + return true; + + case cJSON_False: + output = ensure(output_buffer, 6); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "false"); + return true; + + case cJSON_True: + output = ensure(output_buffer, 5); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "true"); + return true; + + case cJSON_Number: + return print_number(item, output_buffer); + + case cJSON_Raw: + { + size_t raw_length = 0; + if (item->valuestring == NULL) + { + return false; + } + + raw_length = strlen(item->valuestring) + sizeof(""); + output = ensure(output_buffer, raw_length); + if (output == NULL) + { + return false; + } + memcpy(output, item->valuestring, raw_length); + return true; + } + + case cJSON_String: + return print_string(item, output_buffer); + + case cJSON_Array: + return print_array(item, output_buffer); + + case cJSON_Object: + return print_object(item, output_buffer); + + default: + return false; + } +} + +/* Build an array from input text. */ +static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer) +{ + cJSON *head = NULL; /* head of the linked list */ + cJSON *current_item = NULL; + + if (input_buffer->depth >= CJSON_NESTING_LIMIT) + { + return false; /* to deeply nested */ + } + input_buffer->depth++; + + if (buffer_at_offset(input_buffer)[0] != '[') + { + /* not an array */ + goto fail; + } + + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ']')) + { + /* empty array */ + goto success; + } + + /* check if we skipped to the end of the buffer */ + if (cannot_access_at_index(input_buffer, 0)) + { + input_buffer->offset--; + goto fail; + } + + /* step back to character in front of the first element */ + input_buffer->offset--; + /* loop through the comma separated array elements */ + do + { + /* allocate next item */ + cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); + if (new_item == NULL) + { + goto fail; /* allocation failure */ + } + + /* attach next item to list */ + if (head == NULL) + { + /* start the linked list */ + current_item = head = new_item; + } + else + { + /* add to the end and advance */ + current_item->next = new_item; + new_item->prev = current_item; + current_item = new_item; + } + + /* parse next value */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_value(current_item, input_buffer)) + { + goto fail; /* failed to parse value */ + } + buffer_skip_whitespace(input_buffer); + } + while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); + + if (cannot_access_at_index(input_buffer, 0) || buffer_at_offset(input_buffer)[0] != ']') + { + goto fail; /* expected end of array */ + } + +success: + input_buffer->depth--; + + if (head != NULL) { + head->prev = current_item; + } + + item->type = cJSON_Array; + item->child = head; + + input_buffer->offset++; + + return true; + +fail: + if (head != NULL) + { + cJSON_Delete(head); + } + + return false; +} + +/* Render an array to text */ +static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + size_t length = 0; + cJSON *current_element = item->child; + + if (output_buffer == NULL) + { + return false; + } + + /* Compose the output array. */ + /* opening square bracket */ + output_pointer = ensure(output_buffer, 1); + if (output_pointer == NULL) + { + return false; + } + + *output_pointer = '['; + output_buffer->offset++; + output_buffer->depth++; + + while (current_element != NULL) + { + if (!print_value(current_element, output_buffer)) + { + return false; + } + update_offset(output_buffer); + if (current_element->next) + { + length = (size_t) (output_buffer->format ? 2 : 1); + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ','; + if(output_buffer->format) + { + *output_pointer++ = ' '; + } + *output_pointer = '\0'; + output_buffer->offset += length; + } + current_element = current_element->next; + } + + output_pointer = ensure(output_buffer, 2); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ']'; + *output_pointer = '\0'; + output_buffer->depth--; + + return true; +} + +/* Build an object from the text. */ +static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer) +{ + cJSON *head = NULL; /* linked list head */ + cJSON *current_item = NULL; + + if (input_buffer->depth >= CJSON_NESTING_LIMIT) + { + return false; /* to deeply nested */ + } + input_buffer->depth++; + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '{')) + { + goto fail; /* not an object */ + } + + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '}')) + { + goto success; /* empty object */ + } + + /* check if we skipped to the end of the buffer */ + if (cannot_access_at_index(input_buffer, 0)) + { + input_buffer->offset--; + goto fail; + } + + /* step back to character in front of the first element */ + input_buffer->offset--; + /* loop through the comma separated array elements */ + do + { + /* allocate next item */ + cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); + if (new_item == NULL) + { + goto fail; /* allocation failure */ + } + + /* attach next item to list */ + if (head == NULL) + { + /* start the linked list */ + current_item = head = new_item; + } + else + { + /* add to the end and advance */ + current_item->next = new_item; + new_item->prev = current_item; + current_item = new_item; + } + + if (cannot_access_at_index(input_buffer, 1)) + { + goto fail; /* nothing comes after the comma */ + } + + /* parse the name of the child */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_string(current_item, input_buffer)) + { + goto fail; /* failed to parse name */ + } + buffer_skip_whitespace(input_buffer); + + /* swap valuestring and string, because we parsed the name */ + current_item->string = current_item->valuestring; + current_item->valuestring = NULL; + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != ':')) + { + goto fail; /* invalid object */ + } + + /* parse the value */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_value(current_item, input_buffer)) + { + goto fail; /* failed to parse value */ + } + buffer_skip_whitespace(input_buffer); + } + while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '}')) + { + goto fail; /* expected end of object */ + } + +success: + input_buffer->depth--; + + if (head != NULL) { + head->prev = current_item; + } + + item->type = cJSON_Object; + item->child = head; + + input_buffer->offset++; + return true; + +fail: + if (head != NULL) + { + cJSON_Delete(head); + } + + return false; +} + +/* Render an object to text. */ +static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + size_t length = 0; + cJSON *current_item = item->child; + + if (output_buffer == NULL) + { + return false; + } + + /* Compose the output: */ + length = (size_t) (output_buffer->format ? 2 : 1); /* fmt: {\n */ + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + + *output_pointer++ = '{'; + output_buffer->depth++; + if (output_buffer->format) + { + *output_pointer++ = '\n'; + } + output_buffer->offset += length; + + while (current_item) + { + if (output_buffer->format) + { + size_t i; + output_pointer = ensure(output_buffer, output_buffer->depth); + if (output_pointer == NULL) + { + return false; + } + for (i = 0; i < output_buffer->depth; i++) + { + *output_pointer++ = '\t'; + } + output_buffer->offset += output_buffer->depth; + } + + /* print key */ + if (!print_string_ptr((unsigned char*)current_item->string, output_buffer)) + { + return false; + } + update_offset(output_buffer); + + length = (size_t) (output_buffer->format ? 2 : 1); + output_pointer = ensure(output_buffer, length); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ':'; + if (output_buffer->format) + { + *output_pointer++ = '\t'; + } + output_buffer->offset += length; + + /* print value */ + if (!print_value(current_item, output_buffer)) + { + return false; + } + update_offset(output_buffer); + + /* print comma if not last */ + length = ((size_t)(output_buffer->format ? 1 : 0) + (size_t)(current_item->next ? 1 : 0)); + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + if (current_item->next) + { + *output_pointer++ = ','; + } + + if (output_buffer->format) + { + *output_pointer++ = '\n'; + } + *output_pointer = '\0'; + output_buffer->offset += length; + + current_item = current_item->next; + } + + output_pointer = ensure(output_buffer, output_buffer->format ? (output_buffer->depth + 1) : 2); + if (output_pointer == NULL) + { + return false; + } + if (output_buffer->format) + { + size_t i; + for (i = 0; i < (output_buffer->depth - 1); i++) + { + *output_pointer++ = '\t'; + } + } + *output_pointer++ = '}'; + *output_pointer = '\0'; + output_buffer->depth--; + + return true; +} + +/* Get Array size/item / object item. */ +CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array) +{ + cJSON *child = NULL; + size_t size = 0; + + if (array == NULL) + { + return 0; + } + + child = array->child; + + while(child != NULL) + { + size++; + child = child->next; + } + + /* FIXME: Can overflow here. Cannot be fixed without breaking the API */ + + return (int)size; +} + +static cJSON* get_array_item(const cJSON *array, size_t index) +{ + cJSON *current_child = NULL; + + if (array == NULL) + { + return NULL; + } + + current_child = array->child; + while ((current_child != NULL) && (index > 0)) + { + index--; + current_child = current_child->next; + } + + return current_child; +} + +CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index) +{ + if (index < 0) + { + return NULL; + } + + return get_array_item(array, (size_t)index); +} + +static cJSON *get_object_item(const cJSON * const object, const char * const name, const cJSON_bool case_sensitive) +{ + cJSON *current_element = NULL; + + if ((object == NULL) || (name == NULL)) + { + return NULL; + } + + current_element = object->child; + if (case_sensitive) + { + while ((current_element != NULL) && (current_element->string != NULL) && (strcmp(name, current_element->string) != 0)) + { + current_element = current_element->next; + } + } + else + { + while ((current_element != NULL) && (case_insensitive_strcmp((const unsigned char*)name, (const unsigned char*)(current_element->string)) != 0)) + { + current_element = current_element->next; + } + } + + if ((current_element == NULL) || (current_element->string == NULL)) { + return NULL; + } + + return current_element; +} + +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string) +{ + return get_object_item(object, string, false); +} + +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string) +{ + return get_object_item(object, string, true); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string) +{ + return cJSON_GetObjectItem(object, string) ? 1 : 0; +} + +/* Utility for array list handling. */ +static void suffix_object(cJSON *prev, cJSON *item) +{ + prev->next = item; + item->prev = prev; +} + +/* Utility for handling references. */ +static cJSON *create_reference(const cJSON *item, const internal_hooks * const hooks) +{ + cJSON *reference = NULL; + if (item == NULL) + { + return NULL; + } + + reference = cJSON_New_Item(hooks); + if (reference == NULL) + { + return NULL; + } + + memcpy(reference, item, sizeof(cJSON)); + reference->string = NULL; + reference->type |= cJSON_IsReference; + reference->next = reference->prev = NULL; + return reference; +} + +static cJSON_bool add_item_to_array(cJSON *array, cJSON *item) +{ + cJSON *child = NULL; + + if ((item == NULL) || (array == NULL) || (array == item)) + { + return false; + } + + child = array->child; + /* + * To find the last item in array quickly, we use prev in array + */ + if (child == NULL) + { + /* list is empty, start new one */ + array->child = item; + item->prev = item; + item->next = NULL; + } + else + { + /* append to the end */ + if (child->prev) + { + suffix_object(child->prev, item); + array->child->prev = item; + } + } + + return true; +} + +/* Add item to array/object. */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item) +{ + return add_item_to_array(array, item); +} + +#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) + #pragma GCC diagnostic push +#endif +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif +/* helper function to cast away const */ +static void* cast_away_const(const void* string) +{ + return (void*)string; +} +#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) + #pragma GCC diagnostic pop +#endif + + +static cJSON_bool add_item_to_object(cJSON * const object, const char * const string, cJSON * const item, const internal_hooks * const hooks, const cJSON_bool constant_key) +{ + char *new_key = NULL; + int new_type = cJSON_Invalid; + + if ((object == NULL) || (string == NULL) || (item == NULL) || (object == item)) + { + return false; + } + + if (constant_key) + { + new_key = (char*)cast_away_const(string); + new_type = item->type | cJSON_StringIsConst; + } + else + { + new_key = (char*)cJSON_strdup((const unsigned char*)string, hooks); + if (new_key == NULL) + { + return false; + } + + new_type = item->type & ~cJSON_StringIsConst; + } + + if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) + { + hooks->deallocate(item->string); + } + + item->string = new_key; + item->type = new_type; + + return add_item_to_array(object, item); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item) +{ + return add_item_to_object(object, string, item, &global_hooks, false); +} + +/* Add an item to an object with constant string as key */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item) +{ + return add_item_to_object(object, string, item, &global_hooks, true); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item) +{ + if (array == NULL) + { + return false; + } + + return add_item_to_array(array, create_reference(item, &global_hooks)); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item) +{ + if ((object == NULL) || (string == NULL)) + { + return false; + } + + return add_item_to_object(object, string, create_reference(item, &global_hooks), &global_hooks, false); +} + +CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name) +{ + cJSON *null = cJSON_CreateNull(); + if (add_item_to_object(object, name, null, &global_hooks, false)) + { + return null; + } + + cJSON_Delete(null); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name) +{ + cJSON *true_item = cJSON_CreateTrue(); + if (add_item_to_object(object, name, true_item, &global_hooks, false)) + { + return true_item; + } + + cJSON_Delete(true_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name) +{ + cJSON *false_item = cJSON_CreateFalse(); + if (add_item_to_object(object, name, false_item, &global_hooks, false)) + { + return false_item; + } + + cJSON_Delete(false_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean) +{ + cJSON *bool_item = cJSON_CreateBool(boolean); + if (add_item_to_object(object, name, bool_item, &global_hooks, false)) + { + return bool_item; + } + + cJSON_Delete(bool_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number) +{ + cJSON *number_item = cJSON_CreateNumber(number); + if (add_item_to_object(object, name, number_item, &global_hooks, false)) + { + return number_item; + } + + cJSON_Delete(number_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string) +{ + cJSON *string_item = cJSON_CreateString(string); + if (add_item_to_object(object, name, string_item, &global_hooks, false)) + { + return string_item; + } + + cJSON_Delete(string_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw) +{ + cJSON *raw_item = cJSON_CreateRaw(raw); + if (add_item_to_object(object, name, raw_item, &global_hooks, false)) + { + return raw_item; + } + + cJSON_Delete(raw_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name) +{ + cJSON *object_item = cJSON_CreateObject(); + if (add_item_to_object(object, name, object_item, &global_hooks, false)) + { + return object_item; + } + + cJSON_Delete(object_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name) +{ + cJSON *array = cJSON_CreateArray(); + if (add_item_to_object(object, name, array, &global_hooks, false)) + { + return array; + } + + cJSON_Delete(array); + return NULL; +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item) +{ + if ((parent == NULL) || (item == NULL)) + { + return NULL; + } + + if (item != parent->child) + { + /* not the first element */ + item->prev->next = item->next; + } + if (item->next != NULL) + { + /* not the last element */ + item->next->prev = item->prev; + } + + if (item == parent->child) + { + /* first element */ + parent->child = item->next; + } + else if (item->next == NULL) + { + /* last element */ + parent->child->prev = item->prev; + } + + /* make sure the detached item doesn't point anywhere anymore */ + item->prev = NULL; + item->next = NULL; + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which) +{ + if (which < 0) + { + return NULL; + } + + return cJSON_DetachItemViaPointer(array, get_array_item(array, (size_t)which)); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which) +{ + cJSON_Delete(cJSON_DetachItemFromArray(array, which)); +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string) +{ + cJSON *to_detach = cJSON_GetObjectItem(object, string); + + return cJSON_DetachItemViaPointer(object, to_detach); +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string) +{ + cJSON *to_detach = cJSON_GetObjectItemCaseSensitive(object, string); + + return cJSON_DetachItemViaPointer(object, to_detach); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string) +{ + cJSON_Delete(cJSON_DetachItemFromObject(object, string)); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string) +{ + cJSON_Delete(cJSON_DetachItemFromObjectCaseSensitive(object, string)); +} + +/* Replace array/object items with new ones. */ +CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem) +{ + cJSON *after_inserted = NULL; + + if (which < 0 || newitem == NULL) + { + return false; + } + + after_inserted = get_array_item(array, (size_t)which); + if (after_inserted == NULL) + { + return add_item_to_array(array, newitem); + } + + if (after_inserted != array->child && after_inserted->prev == NULL) { + /* return false if after_inserted is a corrupted array item */ + return false; + } + + newitem->next = after_inserted; + newitem->prev = after_inserted->prev; + after_inserted->prev = newitem; + if (after_inserted == array->child) + { + array->child = newitem; + } + else + { + newitem->prev->next = newitem; + } + return true; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement) +{ + if ((parent == NULL) || (parent->child == NULL) || (replacement == NULL) || (item == NULL)) + { + return false; + } + + if (replacement == item) + { + return true; + } + + replacement->next = item->next; + replacement->prev = item->prev; + + if (replacement->next != NULL) + { + replacement->next->prev = replacement; + } + if (parent->child == item) + { + if (parent->child->prev == parent->child) + { + replacement->prev = replacement; + } + parent->child = replacement; + } + else + { /* + * To find the last item in array quickly, we use prev in array. + * We can't modify the last item's next pointer where this item was the parent's child + */ + if (replacement->prev != NULL) + { + replacement->prev->next = replacement; + } + if (replacement->next == NULL) + { + parent->child->prev = replacement; + } + } + + item->next = NULL; + item->prev = NULL; + cJSON_Delete(item); + + return true; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem) +{ + if (which < 0) + { + return false; + } + + return cJSON_ReplaceItemViaPointer(array, get_array_item(array, (size_t)which), newitem); +} + +static cJSON_bool replace_item_in_object(cJSON *object, const char *string, cJSON *replacement, cJSON_bool case_sensitive) +{ + if ((replacement == NULL) || (string == NULL)) + { + return false; + } + + /* replace the name in the replacement */ + if (!(replacement->type & cJSON_StringIsConst) && (replacement->string != NULL)) + { + cJSON_free(replacement->string); + } + replacement->string = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); + if (replacement->string == NULL) + { + return false; + } + + replacement->type &= ~cJSON_StringIsConst; + + return cJSON_ReplaceItemViaPointer(object, get_object_item(object, string, case_sensitive), replacement); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem) +{ + return replace_item_in_object(object, string, newitem, false); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object, const char *string, cJSON *newitem) +{ + return replace_item_in_object(object, string, newitem, true); +} + +/* Create basic types: */ +CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_NULL; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_True; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_False; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = boolean ? cJSON_True : cJSON_False; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_Number; + item->valuedouble = num; + + /* use saturation in case of overflow */ + if (num >= INT_MAX) + { + item->valueint = INT_MAX; + } + else if (num <= (double)INT_MIN) + { + item->valueint = INT_MIN; + } + else + { + item->valueint = (int)num; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_String; + item->valuestring = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); + if(!item->valuestring) + { + cJSON_Delete(item); + return NULL; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) + { + item->type = cJSON_String | cJSON_IsReference; + item->valuestring = (char*)cast_away_const(string); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) { + item->type = cJSON_Object | cJSON_IsReference; + item->child = (cJSON*)cast_away_const(child); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child) { + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) { + item->type = cJSON_Array | cJSON_IsReference; + item->child = (cJSON*)cast_away_const(child); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_Raw; + item->valuestring = (char*)cJSON_strdup((const unsigned char*)raw, &global_hooks); + if(!item->valuestring) + { + cJSON_Delete(item); + return NULL; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type=cJSON_Array; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item) + { + item->type = cJSON_Object; + } + + return item; +} + +/* Create Arrays: */ +CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber(numbers[i]); + if (!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber((double)numbers[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber(numbers[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (strings == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for (i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateString(strings[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p,n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +/* Duplication */ +CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse) +{ + cJSON *newitem = NULL; + cJSON *child = NULL; + cJSON *next = NULL; + cJSON *newchild = NULL; + + /* Bail on bad ptr */ + if (!item) + { + goto fail; + } + /* Create new item */ + newitem = cJSON_New_Item(&global_hooks); + if (!newitem) + { + goto fail; + } + /* Copy over all vars */ + newitem->type = item->type & (~cJSON_IsReference); + newitem->valueint = item->valueint; + newitem->valuedouble = item->valuedouble; + if (item->valuestring) + { + newitem->valuestring = (char*)cJSON_strdup((unsigned char*)item->valuestring, &global_hooks); + if (!newitem->valuestring) + { + goto fail; + } + } + if (item->string) + { + newitem->string = (item->type&cJSON_StringIsConst) ? item->string : (char*)cJSON_strdup((unsigned char*)item->string, &global_hooks); + if (!newitem->string) + { + goto fail; + } + } + /* If non-recursive, then we're done! */ + if (!recurse) + { + return newitem; + } + /* Walk the ->next chain for the child. */ + child = item->child; + while (child != NULL) + { + newchild = cJSON_Duplicate(child, true); /* Duplicate (with recurse) each item in the ->next chain */ + if (!newchild) + { + goto fail; + } + if (next != NULL) + { + /* If newitem->child already set, then crosswire ->prev and ->next and move on */ + next->next = newchild; + newchild->prev = next; + next = newchild; + } + else + { + /* Set newitem->child and move to it */ + newitem->child = newchild; + next = newchild; + } + child = child->next; + } + if (newitem && newitem->child) + { + newitem->child->prev = newchild; + } + + return newitem; + +fail: + if (newitem != NULL) + { + cJSON_Delete(newitem); + } + + return NULL; +} + +static void skip_oneline_comment(char **input) +{ + *input += static_strlen("//"); + + for (; (*input)[0] != '\0'; ++(*input)) + { + if ((*input)[0] == '\n') { + *input += static_strlen("\n"); + return; + } + } +} + +static void skip_multiline_comment(char **input) +{ + *input += static_strlen("/*"); + + for (; (*input)[0] != '\0'; ++(*input)) + { + if (((*input)[0] == '*') && ((*input)[1] == '/')) + { + *input += static_strlen("*/"); + return; + } + } +} + +static void minify_string(char **input, char **output) { + (*output)[0] = (*input)[0]; + *input += static_strlen("\""); + *output += static_strlen("\""); + + + for (; (*input)[0] != '\0'; (void)++(*input), ++(*output)) { + (*output)[0] = (*input)[0]; + + if ((*input)[0] == '\"') { + (*output)[0] = '\"'; + *input += static_strlen("\""); + *output += static_strlen("\""); + return; + } else if (((*input)[0] == '\\') && ((*input)[1] == '\"')) { + (*output)[1] = (*input)[1]; + *input += static_strlen("\""); + *output += static_strlen("\""); + } + } +} + +CJSON_PUBLIC(void) cJSON_Minify(char *json) +{ + char *into = json; + + if (json == NULL) + { + return; + } + + while (json[0] != '\0') + { + switch (json[0]) + { + case ' ': + case '\t': + case '\r': + case '\n': + json++; + break; + + case '/': + if (json[1] == '/') + { + skip_oneline_comment(&json); + } + else if (json[1] == '*') + { + skip_multiline_comment(&json); + } else { + json++; + } + break; + + case '\"': + minify_string(&json, (char**)&into); + break; + + default: + into[0] = json[0]; + json++; + into++; + } + } + + /* and null-terminate. */ + *into = '\0'; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Invalid; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_False; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xff) == cJSON_True; +} + + +CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & (cJSON_True | cJSON_False)) != 0; +} +CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_NULL; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Number; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_String; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Array; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Object; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Raw; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive) +{ + if ((a == NULL) || (b == NULL) || ((a->type & 0xFF) != (b->type & 0xFF))) + { + return false; + } + + /* check if type is valid */ + switch (a->type & 0xFF) + { + case cJSON_False: + case cJSON_True: + case cJSON_NULL: + case cJSON_Number: + case cJSON_String: + case cJSON_Raw: + case cJSON_Array: + case cJSON_Object: + break; + + default: + return false; + } + + /* identical objects are equal */ + if (a == b) + { + return true; + } + + switch (a->type & 0xFF) + { + /* in these cases and equal type is enough */ + case cJSON_False: + case cJSON_True: + case cJSON_NULL: + return true; + + case cJSON_Number: + if (compare_double(a->valuedouble, b->valuedouble)) + { + return true; + } + return false; + + case cJSON_String: + case cJSON_Raw: + if ((a->valuestring == NULL) || (b->valuestring == NULL)) + { + return false; + } + if (strcmp(a->valuestring, b->valuestring) == 0) + { + return true; + } + + return false; + + case cJSON_Array: + { + cJSON *a_element = a->child; + cJSON *b_element = b->child; + + for (; (a_element != NULL) && (b_element != NULL);) + { + if (!cJSON_Compare(a_element, b_element, case_sensitive)) + { + return false; + } + + a_element = a_element->next; + b_element = b_element->next; + } + + /* one of the arrays is longer than the other */ + if (a_element != b_element) { + return false; + } + + return true; + } + + case cJSON_Object: + { + cJSON *a_element = NULL; + cJSON *b_element = NULL; + cJSON_ArrayForEach(a_element, a) + { + /* TODO This has O(n^2) runtime, which is horrible! */ + b_element = get_object_item(b, a_element->string, case_sensitive); + if (b_element == NULL) + { + return false; + } + + if (!cJSON_Compare(a_element, b_element, case_sensitive)) + { + return false; + } + } + + /* doing this twice, once on a and b to prevent true comparison if a subset of b + * TODO: Do this the proper way, this is just a fix for now */ + cJSON_ArrayForEach(b_element, b) + { + a_element = get_object_item(a, b_element->string, case_sensitive); + if (a_element == NULL) + { + return false; + } + + if (!cJSON_Compare(b_element, a_element, case_sensitive)) + { + return false; + } + } + + return true; + } + + default: + return false; + } +} + +CJSON_PUBLIC(void *) cJSON_malloc(size_t size) +{ + return global_hooks.allocate(size); +} + +CJSON_PUBLIC(void) cJSON_free(void *object) +{ + global_hooks.deallocate(object); + object = NULL; +} diff --git a/core/src/drivers/plugins/native/s7comm/cjson/cJSON.h b/core/src/drivers/plugins/native/s7comm/cjson/cJSON.h new file mode 100644 index 00000000..88cf0bcf --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/cjson/cJSON.h @@ -0,0 +1,300 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#ifndef cJSON__h +#define cJSON__h + +#ifdef __cplusplus +extern "C" +{ +#endif + +#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32)) +#define __WINDOWS__ +#endif + +#ifdef __WINDOWS__ + +/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 3 define options: + +CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols +CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default) +CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol + +For *nix builds that support visibility attribute, you can define similar behavior by + +setting default visibility to hidden by adding +-fvisibility=hidden (for gcc) +or +-xldscope=hidden (for sun cc) +to CFLAGS + +then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does + +*/ + +#define CJSON_CDECL __cdecl +#define CJSON_STDCALL __stdcall + +/* export symbols by default, this is necessary for copy pasting the C and header file */ +#if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS) +#define CJSON_EXPORT_SYMBOLS +#endif + +#if defined(CJSON_HIDE_SYMBOLS) +#define CJSON_PUBLIC(type) type CJSON_STDCALL +#elif defined(CJSON_EXPORT_SYMBOLS) +#define CJSON_PUBLIC(type) __declspec(dllexport) type CJSON_STDCALL +#elif defined(CJSON_IMPORT_SYMBOLS) +#define CJSON_PUBLIC(type) __declspec(dllimport) type CJSON_STDCALL +#endif +#else /* !__WINDOWS__ */ +#define CJSON_CDECL +#define CJSON_STDCALL + +#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY) +#define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type +#else +#define CJSON_PUBLIC(type) type +#endif +#endif + +/* project version */ +#define CJSON_VERSION_MAJOR 1 +#define CJSON_VERSION_MINOR 7 +#define CJSON_VERSION_PATCH 18 + +#include + +/* cJSON Types: */ +#define cJSON_Invalid (0) +#define cJSON_False (1 << 0) +#define cJSON_True (1 << 1) +#define cJSON_NULL (1 << 2) +#define cJSON_Number (1 << 3) +#define cJSON_String (1 << 4) +#define cJSON_Array (1 << 5) +#define cJSON_Object (1 << 6) +#define cJSON_Raw (1 << 7) /* raw json */ + +#define cJSON_IsReference 256 +#define cJSON_StringIsConst 512 + +/* The cJSON structure: */ +typedef struct cJSON +{ + /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */ + struct cJSON *next; + struct cJSON *prev; + /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ + struct cJSON *child; + + /* The type of the item, as above. */ + int type; + + /* The item's string, if type==cJSON_String and type == cJSON_Raw */ + char *valuestring; + /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */ + int valueint; + /* The item's number, if type==cJSON_Number */ + double valuedouble; + + /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ + char *string; +} cJSON; + +typedef struct cJSON_Hooks +{ + /* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */ + void *(CJSON_CDECL *malloc_fn)(size_t sz); + void (CJSON_CDECL *free_fn)(void *ptr); +} cJSON_Hooks; + +typedef int cJSON_bool; + +/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them. + * This is to prevent stack overflows. */ +#ifndef CJSON_NESTING_LIMIT +#define CJSON_NESTING_LIMIT 1000 +#endif + +/* returns the version of cJSON as a string */ +CJSON_PUBLIC(const char*) cJSON_Version(void); + +/* Supply malloc, realloc and free functions to cJSON */ +CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks); + +/* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */ +/* Supply a block of JSON, and this returns a cJSON object you can interrogate. */ +CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value); +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length); +/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */ +/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */ +CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated); +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated); + +/* Render a cJSON entity to text for transfer/storage. */ +CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item); +/* Render a cJSON entity to text for transfer/storage without any formatting. */ +CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item); +/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */ +CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt); +/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */ +/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */ +CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format); +/* Delete a cJSON entity and all subentities. */ +CJSON_PUBLIC(void) cJSON_Delete(cJSON *item); + +/* Returns the number of items in an array (or object). */ +CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array); +/* Retrieve item number "index" from array "array". Returns NULL if unsuccessful. */ +CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index); +/* Get item "string" from object. Case insensitive. */ +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string); +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string); +CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string); +/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */ +CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void); + +/* Check item type and return its value */ +CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item); +CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item); + +/* These functions check the type of an item */ +CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item); + +/* These calls create a cJSON item of the appropriate type. */ +CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean); +CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num); +CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string); +/* raw json */ +CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw); +CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void); + +/* Create a string where valuestring references a string so + * it will not be freed by cJSON_Delete */ +CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string); +/* Create an object/array that only references it's elements so + * they will not be freed by cJSON_Delete */ +CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child); +CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child); + +/* These utilities create an Array of count items. + * The parameter count cannot be greater than the number of elements in the number array, otherwise array access will be out of bounds.*/ +CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count); + +/* Append item to the specified array/object. */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item); +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item); +/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object. + * WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before + * writing to `item->string` */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item); +/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item); +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item); + +/* Remove/Detach items from Arrays/Objects. */ +CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which); +CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string); +CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string); +CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string); + +/* Update array items. */ +CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */ +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement); +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem); +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem); +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem); + +/* Duplicate a cJSON item */ +CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse); +/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will + * need to be released. With recurse!=0, it will duplicate any children connected to the item. + * The item->next and ->prev pointers are always zero on return from Duplicate. */ +/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal. + * case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */ +CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive); + +/* Minify a strings, remove blank characters(such as ' ', '\t', '\r', '\n') from strings. + * The input pointer json cannot point to a read-only address area, such as a string constant, + * but should point to a readable and writable address area. */ +CJSON_PUBLIC(void) cJSON_Minify(char *json); + +/* Helper functions for creating and adding items to an object at the same time. + * They return the added item or NULL on failure. */ +CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean); +CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number); +CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string); +CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw); +CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name); + +/* When assigning an integer value, it needs to be propagated to valuedouble too. */ +#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number)) +/* helper for the cJSON_SetNumberValue macro */ +CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number); +#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number)) +/* Change the valuestring of a cJSON_String object, only takes effect when type of object is cJSON_String */ +CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring); + +/* If the object is not a boolean type this does nothing and returns cJSON_Invalid else it returns the new type*/ +#define cJSON_SetBoolValue(object, boolValue) ( \ + (object != NULL && ((object)->type & (cJSON_False|cJSON_True))) ? \ + (object)->type=((object)->type &(~(cJSON_False|cJSON_True)))|((boolValue)?cJSON_True:cJSON_False) : \ + cJSON_Invalid\ +) + +/* Macro for iterating over an array or object */ +#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next) + +/* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */ +CJSON_PUBLIC(void *) cJSON_malloc(size_t size); +CJSON_PUBLIC(void) cJSON_free(void *object); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/core/src/drivers/plugins/native/s7comm/docs/IMPLEMENTATION_PLAN.md b/core/src/drivers/plugins/native/s7comm/docs/IMPLEMENTATION_PLAN.md new file mode 100644 index 00000000..f7574db9 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/docs/IMPLEMENTATION_PLAN.md @@ -0,0 +1,1385 @@ +# S7Comm Plugin Implementation Plan for OpenPLC Runtime v4 + +## Table of Contents + +1. [Executive Summary](#executive-summary) +2. [Background and Motivation](#background-and-motivation) +3. [Analysis of Existing Implementation](#analysis-of-existing-implementation) + - [OpenPLC v3 S7Comm Architecture](#openplc-v3-s7comm-architecture) + - [Snap7 Library API](#snap7-library-api) + - [OpenPLC Runtime v4 Plugin Architecture](#openplc-runtime-v4-plugin-architecture) +4. [Design Decisions](#design-decisions) +5. [JSON Configuration Schema](#json-configuration-schema) +6. [Plugin Structure](#plugin-structure) +7. [Implementation Details](#implementation-details) +8. [Phased Implementation Plan](#phased-implementation-plan) +9. [Testing Strategy](#testing-strategy) +10. [References](#references) + +--- + +## Executive Summary + +This document outlines the plan to port S7Comm functionality from OpenPLC Runtime v3 to the new v4 plugin architecture. The implementation leverages the Snap7 open-source library to provide Siemens S7 protocol server capabilities, allowing OpenPLC to communicate with S7-compatible HMIs, SCADA systems, and other industrial equipment. + +Key improvements over v3: +- Full JSON-based configuration +- Flexible data block mapping to any OpenPLC buffer +- Native C/C++ plugin for optimal performance +- Thread-safe design with PLC cycle synchronization +- Configurable PLC identity for S7 client compatibility +- Comprehensive logging and diagnostics + +--- + +## Background and Motivation + +### What is S7Comm? + +S7Comm (S7 Communication) is the proprietary protocol used by Siemens S7 PLCs (S7-300, S7-400, S7-1200, S7-1500). It runs over ISO-on-TCP (RFC 1006) on port 102 and is widely supported by: + +- Siemens WinCC and TIA Portal +- Many third-party HMI/SCADA systems +- Industrial data acquisition tools +- OPC servers + +### Why Add S7Comm to OpenPLC v4? + +1. **Industry Compatibility**: Many existing installations use S7Comm-compatible equipment +2. **User Request**: Feature was present in v3 and users expect it in v4 +3. **Protocol Maturity**: S7Comm is well-documented and stable +4. **Snap7 Library**: High-quality open-source implementation available + +### Snap7 Library + +Snap7 is an open-source, multi-platform Ethernet communication suite for interfacing with Siemens S7 PLCs. It provides: + +- S7 Server (emulate a PLC) +- S7 Client (connect to PLCs) +- S7 Partner (peer-to-peer communication) + +Repository: https://github.com/davenardella/snap7 + +--- + +## Analysis of Existing Implementation + +### OpenPLC v3 S7Comm Architecture + +The v3 implementation is located at `OpenPLC_v3/utils/snap7_src/wrapper/`: + +#### Key Files + +| File | Purpose | +|------|---------| +| `oplc_snap7.cpp` | Main implementation - server lifecycle, callbacks, buffer mapping | +| `oplc_snap7.h` | Header with S7 protocol constants and function declarations | + +#### Data Area Mapping (Hardcoded in v3) + +| S7 Area | DB Number | OpenPLC Buffer | Description | +|---------|-----------|----------------|-------------| +| PE (0x81) | N/A | `bool_input[][]` | Process inputs (digital) | +| PA (0x82) | N/A | `bool_output[][]` | Process outputs (digital) | +| MK (0x83) | N/A | Internal `MK[]` | Marker/flag memory | +| DB | 2 | `int_input[]` | Input words (%IW) | +| DB | 102 | `int_output[]` | Output words (%QW) | +| DB | 1002 | `int_memory[]` | Memory words (%MW) | +| DB | 1004 | `dint_memory[]` | Memory double-words (%MD) | + +#### Server Lifecycle (v3) + +```c +// Initialization +void initializeSnap7() { + Server = new TS7Server; + Server->SetEventsMask(0x3ff); + Server->SetEventsCallback(EventCallBack, NULL); + Server->SetRWAreaCallback(RWAreaCallBack, NULL); +} + +// Start server +void startSnap7() { + Server->StartTo("0.0.0.0"); // Port 102 +} + +// Stop server +void stopSnap7() { + Server->Stop(); +} + +// Cleanup +void finalizeSnap7() { + Server->Stop(); + delete Server; +} +``` + +#### RW Callback Pattern (v3) + +```c +int S7API RWAreaCallBack(void* usrPtr, int Sender, int Operation, + PS7Tag PTag, void* pUsrData) { + pthread_mutex_lock(&bufferLock); // Thread safety + + switch (PTag->Area) { + case S7AreaPE: // Process inputs + case S7AreaPA: // Process outputs + // Handle bit/byte access + break; + case S7AreaMK: // Markers + // Handle marker access + break; + case S7AreaDB: // Data blocks + switch (PTag->DBNumber) { + case 2: // int_input + case 102: // int_output + case 1002: // int_memory + case 1004: // dint_memory + } + break; + } + + pthread_mutex_unlock(&bufferLock); + return result; +} +``` + +#### Limitations of v3 Implementation + +1. **Hardcoded DB Numbers**: Cannot change DB2, DB102, DB1002, DB1004 +2. **Fixed Buffer Sizes**: Limited to BUFFER_SIZE constant +3. **No Configuration File**: All settings compiled in +4. **Tight Coupling**: Integrated directly into main runtime +5. **Limited Logging**: Minimal diagnostic output +6. **No PLC Identity Config**: Fixed SZL responses + +--- + +### Snap7 Library API + +#### Server Creation and Lifecycle + +```c +// C API +S7Object Srv_Create(); +void Srv_Destroy(S7Object *Server); +int Srv_Start(S7Object Server); +int Srv_StartTo(S7Object Server, const char *Address); +int Srv_Stop(S7Object Server); + +// C++ API +class TS7Server { +public: + TS7Server(); + ~TS7Server(); + int Start(); + int StartTo(const char *Address); + int Stop(); +}; +``` + +#### Configuration Parameters + +| Parameter ID | Constant | Type | Default | Description | +|--------------|----------|------|---------|-------------| +| 1 | `p_u16_LocalPort` | uint16 | 102 | TCP listening port | +| 3 | `p_i32_PingTimeout` | int32 | 10000 | Keep-alive timeout (ms) | +| 4 | `p_i32_SendTimeout` | int32 | 3000 | Socket send timeout (ms) | +| 5 | `p_i32_RecvTimeout` | int32 | 3000 | Socket receive timeout (ms) | +| 6 | `p_i32_WorkInterval` | int32 | 100 | Worker thread interval (ms) | +| 10 | `p_i32_PDURequest` | int32 | 480 | PDU size (240-960) | +| 11 | `p_i32_MaxClients` | int32 | 32 | Max concurrent connections | + +```c +int Srv_GetParam(S7Object Server, int ParamNumber, void *pValue); +int Srv_SetParam(S7Object Server, int ParamNumber, void *pValue); +``` + +#### Data Area Registration + +```c +// Area codes +const int srvAreaPE = 0; // Process inputs (I) +const int srvAreaPA = 1; // Process outputs (Q) +const int srvAreaMK = 2; // Markers (M) +const int srvAreaCT = 3; // Counters (C) +const int srvAreaTM = 4; // Timers (T) +const int srvAreaDB = 5; // Data blocks (DB) + +// Registration functions +int Srv_RegisterArea(S7Object Server, int AreaCode, word Index, + void *pUsrData, int Size); +int Srv_UnregisterArea(S7Object Server, int AreaCode, word Index); + +// Thread-safe access +int Srv_LockArea(S7Object Server, int AreaCode, word Index); +int Srv_UnlockArea(S7Object Server, int AreaCode, word Index); +``` + +#### Callback Mechanisms + +```c +// Tag structure for RW operations +typedef struct { + int Area; // Area code (S7AreaXX) + int DBNumber; // DB number (for S7AreaDB) + int Start; // Starting byte offset + int Size; // Data size in bytes + int WordLen; // Word length code +} TS7Tag, *PS7Tag; + +// Word length codes +const int S7WLBit = 0x01; // 1 bit +const int S7WLByte = 0x02; // 8 bits +const int S7WLChar = 0x03; // 8 bits +const int S7WLWord = 0x04; // 16 bits +const int S7WLDWord = 0x06; // 32 bits +const int S7WLInt = 0x05; // 16 bits signed +const int S7WLDInt = 0x07; // 32 bits signed +const int S7WLReal = 0x08; // 32 bits float + +// Callback types +typedef int (S7API *pfn_RWAreaCallBack)(void *usrPtr, int Sender, + int Operation, PS7Tag PTag, + void *pUsrData); +typedef void (S7API *pfn_SrvCallBack)(void *usrPtr, PSrvEvent PEvent, + int Size); + +// Operation codes +const int OperationRead = 0; +const int OperationWrite = 1; + +// Set callbacks +int Srv_SetRWAreaCallback(S7Object Server, pfn_RWAreaCallBack pCallback, + void *usrPtr); +int Srv_SetEventsCallback(S7Object Server, pfn_SrvCallBack pCallback, + void *usrPtr); +``` + +#### Event Codes + +```c +// Server events +const longword evcServerStarted = 0x00000001; +const longword evcServerStopped = 0x00000002; +const longword evcListenerCannotStart = 0x00000004; +const longword evcClientAdded = 0x00000008; +const longword evcClientRejected = 0x00000010; +const longword evcClientNoRoom = 0x00000020; +const longword evcClientException = 0x00000040; +const longword evcClientDisconnected = 0x00000080; + +// Protocol events +const longword evcPDUincoming = 0x00010000; +const longword evcDataRead = 0x00020000; +const longword evcDataWrite = 0x00040000; +const longword evcNegotiatePDU = 0x00080000; +const longword evcReadSZL = 0x00100000; +const longword evcClock = 0x00200000; +const longword evcUpload = 0x00400000; +const longword evcDownload = 0x00800000; +``` + +--- + +### OpenPLC Runtime v4 Plugin Architecture + +#### Plugin Configuration File (`plugins.conf`) + +``` +name,path,enabled,type,config_path,venv_path +``` + +| Field | Description | +|-------|-------------| +| `name` | Unique plugin identifier (max 64 chars) | +| `path` | Path to `.py` or `.so` file | +| `enabled` | 1=enabled, 0=disabled | +| `type` | 0=Python, 1=Native C/C++ | +| `config_path` | Path to JSON config file | +| `venv_path` | Python venv path (Python plugins only) | + +#### Plugin Lifecycle Functions + +| Function | Required | Description | +|----------|----------|-------------| +| `init(void *args)` | Yes | Initialize plugin, copy runtime args | +| `start_loop()` | Yes | Start background operations | +| `stop_loop()` | Yes | Stop background operations | +| `cleanup()` | No | Release resources | +| `cycle_start()` | No | Called at PLC scan cycle start (native only) | +| `cycle_end()` | No | Called at PLC scan cycle end (native only) | + +#### Runtime Arguments Structure + +```c +typedef struct { + // Boolean buffers [1024][8] + IEC_BOOL *(*bool_input)[8]; + IEC_BOOL *(*bool_output)[8]; + IEC_BOOL *(*bool_memory)[8]; + + // Byte buffers [1024] + IEC_BYTE **byte_input; + IEC_BYTE **byte_output; + + // Integer buffers [1024] + IEC_UINT **int_input; + IEC_UINT **int_output; + IEC_UINT **int_memory; + + // Double integer buffers [1024] + IEC_UDINT **dint_input; + IEC_UDINT **dint_output; + IEC_UDINT **dint_memory; + + // Long integer buffers [1024] + IEC_ULINT **lint_input; + IEC_ULINT **lint_output; + IEC_ULINT **lint_memory; + + // Synchronization + int (*mutex_take)(pthread_mutex_t *mutex); + int (*mutex_give)(pthread_mutex_t *mutex); + pthread_mutex_t *buffer_mutex; + + // Logging + void (*log_info)(const char *format, ...); + void (*log_debug)(const char *format, ...); + void (*log_warn)(const char *format, ...); + void (*log_error)(const char *format, ...); + + // Configuration + const char *config_file_path; + + // Buffer dimensions + int buffer_size; // 1024 + int bits_per_buffer; // 8 +} plugin_runtime_args_t; +``` + +#### Buffer Access Pattern + +```c +// Thread-safe buffer access +args->mutex_take(args->buffer_mutex); + +// Read from bool_input[buffer_idx][bit_idx] +IEC_BOOL *ptr = args->bool_input[buffer_idx][bit_idx]; +if (ptr != NULL) { + value = *ptr; +} + +// Write to int_output[buffer_idx] +IEC_UINT *ptr = args->int_output[buffer_idx]; +if (ptr != NULL) { + *ptr = value; +} + +args->mutex_give(args->buffer_mutex); +``` + +--- + +## Design Decisions + +### 1. Native C/C++ Plugin (Not Python) + +**Rationale:** +- Snap7 is a C/C++ library with complex threading +- Low-latency buffer access required for industrial protocols +- No GIL contention with PLC cycle +- Direct memory operations for endianness conversion + +### 2. Callback-Based Data Access with Cycle Hooks + +**Approach:** +- Use Snap7 RW callback for immediate data access +- Implement `cycle_start()` and `cycle_end()` for optional buffering +- Mutex protection during callback execution + +**Benefits:** +- Real-time data access for S7 clients +- Option to batch updates with PLC cycle +- Flexible synchronization strategy + +### 3. Configurable Data Block Mapping + +**Approach:** +- JSON configuration defines DB numbers and buffer mappings +- Each DB maps to a specific OpenPLC buffer type +- Support for custom start offsets + +**Benefits:** +- Users can match existing HMI configurations +- No recompilation needed to change mappings +- Multiple configurations for different deployments + +### 4. Event-Driven Logging + +**Approach:** +- Configurable event mask for Snap7 +- Log levels controlled via JSON config +- Integration with OpenPLC logging system + +**Benefits:** +- Minimal performance impact when disabled +- Detailed diagnostics when needed +- Consistent log format with rest of runtime + +--- + +## JSON Configuration Schema + +### Complete Configuration Example + +```json +{ + "server": { + "enabled": true, + "bind_address": "0.0.0.0", + "port": 102, + "max_clients": 32, + "work_interval_ms": 100, + "send_timeout_ms": 3000, + "recv_timeout_ms": 3000, + "ping_timeout_ms": 10000, + "pdu_size": 480 + }, + "plc_identity": { + "name": "OpenPLC Runtime", + "module_type": "CPU 315-2 PN/DP", + "serial_number": "S C-XXXXXXXXX", + "copyright": "OpenPLC Project", + "module_name": "OpenPLC" + }, + "data_blocks": [ + { + "db_number": 1, + "description": "Digital Inputs (%IX)", + "size_bytes": 128, + "mapping": { + "type": "bool_input", + "start_buffer": 0, + "bit_addressing": true + } + }, + { + "db_number": 2, + "description": "Digital Outputs (%QX)", + "size_bytes": 128, + "mapping": { + "type": "bool_output", + "start_buffer": 0, + "bit_addressing": true + } + }, + { + "db_number": 10, + "description": "Analog Inputs (%IW)", + "size_bytes": 2048, + "mapping": { + "type": "int_input", + "start_buffer": 0, + "bit_addressing": false + } + }, + { + "db_number": 20, + "description": "Analog Outputs (%QW)", + "size_bytes": 2048, + "mapping": { + "type": "int_output", + "start_buffer": 0, + "bit_addressing": false + } + }, + { + "db_number": 100, + "description": "Memory Words (%MW)", + "size_bytes": 2048, + "mapping": { + "type": "int_memory", + "start_buffer": 0, + "bit_addressing": false + } + }, + { + "db_number": 200, + "description": "Memory DWords (%MD)", + "size_bytes": 4096, + "mapping": { + "type": "dint_memory", + "start_buffer": 0, + "bit_addressing": false + } + } + ], + "system_areas": { + "pe_area": { + "enabled": true, + "size_bytes": 128, + "mapping": { + "type": "bool_input", + "start_buffer": 0 + } + }, + "pa_area": { + "enabled": true, + "size_bytes": 128, + "mapping": { + "type": "bool_output", + "start_buffer": 0 + } + }, + "mk_area": { + "enabled": true, + "size_bytes": 256, + "mapping": { + "type": "bool_memory", + "start_buffer": 0 + } + } + }, + "logging": { + "log_connections": true, + "log_data_access": false, + "log_errors": true + } +} +``` + +### Configuration Field Reference + +#### Server Section + +| Field | Type | Default | Range | Description | +|-------|------|---------|-------|-------------| +| `enabled` | bool | true | - | Enable/disable the S7 server | +| `bind_address` | string | "0.0.0.0" | Valid IP | Network interface to bind | +| `port` | int | 102 | 1-65535 | S7Comm TCP port | +| `max_clients` | int | 32 | 1-1024 | Maximum simultaneous connections | +| `work_interval_ms` | int | 100 | 1-10000 | Worker thread polling interval | +| `send_timeout_ms` | int | 3000 | 100-60000 | Socket send timeout | +| `recv_timeout_ms` | int | 3000 | 100-60000 | Socket receive timeout | +| `ping_timeout_ms` | int | 10000 | 1000-300000 | Keep-alive timeout | +| `pdu_size` | int | 480 | 240-960 | Maximum PDU size | + +#### PLC Identity Section + +These values are returned in S7 SZL (System State List) queries: + +| Field | S7 SZL | Description | +|-------|--------|-------------| +| `name` | 0x001C Index 1 | PLC name (max 32 chars) | +| `module_type` | 0x001C Index 2 | CPU type string | +| `serial_number` | 0x001C Index 3 | Serial number | +| `copyright` | 0x001C Index 4 | Copyright string | +| `module_name` | 0x001C Index 5 | Module name | + +#### Data Blocks Section + +| Field | Type | Description | +|-------|------|-------------| +| `db_number` | int | S7 DB number (1-65535) | +| `description` | string | Human-readable description | +| `size_bytes` | int | DB size in bytes | +| `mapping.type` | enum | OpenPLC buffer type (see below) | +| `mapping.start_buffer` | int | Starting buffer index | +| `mapping.bit_addressing` | bool | Enable bit-level access | + +#### Supported Buffer Types + +| Type | IEC Type | Element Size | Max Elements | +|------|----------|--------------|--------------| +| `bool_input` | BOOL | 1 bit | 8192 (1024 * 8) | +| `bool_output` | BOOL | 1 bit | 8192 | +| `bool_memory` | BOOL | 1 bit | 8192 | +| `byte_input` | BYTE | 1 byte | 1024 | +| `byte_output` | BYTE | 1 byte | 1024 | +| `int_input` | UINT/INT | 2 bytes | 1024 | +| `int_output` | UINT/INT | 2 bytes | 1024 | +| `int_memory` | UINT/INT | 2 bytes | 1024 | +| `dint_input` | UDINT/DINT | 4 bytes | 1024 | +| `dint_output` | UDINT/DINT | 4 bytes | 1024 | +| `dint_memory` | UDINT/DINT | 4 bytes | 1024 | +| `lint_input` | ULINT/LINT | 8 bytes | 1024 | +| `lint_output` | ULINT/LINT | 8 bytes | 1024 | +| `lint_memory` | ULINT/LINT | 8 bytes | 1024 | + +#### System Areas Section + +| Area | S7 Code | Description | +|------|---------|-------------| +| `pe_area` | 0x81 | Process inputs (I area) | +| `pa_area` | 0x82 | Process outputs (Q area) | +| `mk_area` | 0x83 | Markers (M area) | + +#### Logging Section + +| Field | Type | Default | Description | +|-------|------|---------|-------------| +| `log_connections` | bool | true | Log client connect/disconnect | +| `log_data_access` | bool | false | Log read/write operations | +| `log_errors` | bool | true | Log errors and warnings | + +--- + +## Plugin Structure + +### Directory Layout + +``` +core/src/drivers/plugins/native/s7comm/ +├── CMakeLists.txt # Build configuration +├── s7comm_plugin.c # Main plugin implementation +├── s7comm_plugin.h # Plugin header +├── s7comm_config.c # JSON configuration parser +├── s7comm_config.h # Configuration structures +├── s7comm_buffer_mapping.c # OpenPLC buffer mapping logic +├── s7comm_buffer_mapping.h # Buffer mapping header +├── s7comm_callbacks.c # Snap7 RW and event callbacks +├── s7comm_callbacks.h # Callback function declarations +├── s7comm_config.json # Default configuration file +├── docs/ +│ ├── IMPLEMENTATION_PLAN.md # This document +│ └── USER_GUIDE.md # User documentation +├── snap7/ # Snap7 library +│ ├── snap7.h # Snap7 header +│ ├── snap7.cpp # Snap7 C++ wrapper +│ └── src/ # Snap7 source files +│ ├── core/ +│ │ ├── s7_server.h +│ │ ├── s7_server.cpp +│ │ ├── s7_types.h +│ │ └── s7_isotcp.h/cpp +│ └── sys/ +│ ├── snap_tcpsrvr.h/cpp +│ └── snap_threads.h +└── tests/ + ├── test_config.c # Configuration parsing tests + ├── test_buffer_mapping.c # Buffer mapping tests + └── test_integration.py # Integration tests with python-snap7 +``` + +### Core Data Structures + +```c +// s7comm_config.h + +#ifndef S7COMM_CONFIG_H +#define S7COMM_CONFIG_H + +#include +#include +#include + +#define MAX_DATA_BLOCKS 64 +#define MAX_STRING_LEN 64 +#define MAX_DESCRIPTION_LEN 128 + +// Buffer type enumeration +typedef enum { + BUFFER_TYPE_NONE = 0, + BUFFER_TYPE_BOOL_INPUT, + BUFFER_TYPE_BOOL_OUTPUT, + BUFFER_TYPE_BOOL_MEMORY, + BUFFER_TYPE_BYTE_INPUT, + BUFFER_TYPE_BYTE_OUTPUT, + BUFFER_TYPE_INT_INPUT, + BUFFER_TYPE_INT_OUTPUT, + BUFFER_TYPE_INT_MEMORY, + BUFFER_TYPE_DINT_INPUT, + BUFFER_TYPE_DINT_OUTPUT, + BUFFER_TYPE_DINT_MEMORY, + BUFFER_TYPE_LINT_INPUT, + BUFFER_TYPE_LINT_OUTPUT, + BUFFER_TYPE_LINT_MEMORY +} buffer_type_t; + +// Buffer mapping configuration +typedef struct { + buffer_type_t type; + int start_buffer; + bool bit_addressing; +} buffer_mapping_t; + +// Data block configuration +typedef struct { + int db_number; + char description[MAX_DESCRIPTION_LEN]; + int size_bytes; + buffer_mapping_t mapping; + uint8_t *data_buffer; // Allocated S7 data buffer + pthread_mutex_t *lock; // Per-DB lock for thread safety +} data_block_config_t; + +// System area configuration +typedef struct { + bool enabled; + int size_bytes; + buffer_mapping_t mapping; + uint8_t *data_buffer; +} system_area_config_t; + +// PLC identity configuration +typedef struct { + char name[MAX_STRING_LEN]; + char module_type[MAX_STRING_LEN]; + char serial_number[MAX_STRING_LEN]; + char copyright[MAX_STRING_LEN]; + char module_name[MAX_STRING_LEN]; +} plc_identity_t; + +// Logging configuration +typedef struct { + bool log_connections; + bool log_data_access; + bool log_errors; +} logging_config_t; + +// Complete S7Comm configuration +typedef struct { + // Server settings + bool enabled; + char bind_address[MAX_STRING_LEN]; + uint16_t port; + int max_clients; + int work_interval_ms; + int send_timeout_ms; + int recv_timeout_ms; + int ping_timeout_ms; + int pdu_size; + + // PLC identity + plc_identity_t identity; + + // Data blocks + int num_data_blocks; + data_block_config_t data_blocks[MAX_DATA_BLOCKS]; + + // System areas + system_area_config_t pe_area; + system_area_config_t pa_area; + system_area_config_t mk_area; + + // Logging + logging_config_t logging; +} s7comm_config_t; + +// Configuration functions +int s7comm_parse_config(const char *config_path, s7comm_config_t *config); +int s7comm_validate_config(const s7comm_config_t *config); +void s7comm_free_config(s7comm_config_t *config); +void s7comm_print_config(const s7comm_config_t *config); + +#endif // S7COMM_CONFIG_H +``` + +### Plugin Interface + +```c +// s7comm_plugin.h + +#ifndef S7COMM_PLUGIN_H +#define S7COMM_PLUGIN_H + +#include "plugin_types.h" + +// Required plugin lifecycle functions +int init(void *args); +void start_loop(void); +void stop_loop(void); +void cleanup(void); + +// Optional cycle hooks (native plugins only) +void cycle_start(void); +void cycle_end(void); + +#endif // S7COMM_PLUGIN_H +``` + +--- + +## Implementation Details + +### Endianness Handling + +S7Comm uses big-endian (network byte order). OpenPLC uses native endianness. + +```c +// s7comm_buffer_mapping.c + +#include + +// Convert host to S7 (big-endian) +static inline uint16_t host_to_s7_16(uint16_t val) { + return htobe16(val); +} + +static inline uint32_t host_to_s7_32(uint32_t val) { + return htobe32(val); +} + +static inline uint64_t host_to_s7_64(uint64_t val) { + return htobe64(val); +} + +// Convert S7 to host +static inline uint16_t s7_to_host_16(uint16_t val) { + return be16toh(val); +} + +static inline uint32_t s7_to_host_32(uint32_t val) { + return be32toh(val); +} + +static inline uint64_t s7_to_host_64(uint64_t val) { + return be64toh(val); +} +``` + +### Bit Addressing + +S7 uses byte.bit addressing (e.g., DBX10.3 = byte 10, bit 3): + +```c +// Convert S7 bit address to OpenPLC buffer indices +static void s7_bit_to_openplc(int s7_bit_address, int start_buffer, + int *buffer_idx, int *bit_idx) { + int s7_byte = s7_bit_address / 8; + int s7_bit = s7_bit_address % 8; + + *buffer_idx = start_buffer + s7_byte; + *bit_idx = s7_bit; +} + +// Read single bit from OpenPLC buffer +static int read_bool_bit(plugin_runtime_args_t *args, buffer_type_t type, + int buffer_idx, int bit_idx, uint8_t *value) { + IEC_BOOL *(*buffer)[8] = NULL; + + switch (type) { + case BUFFER_TYPE_BOOL_INPUT: + buffer = args->bool_input; + break; + case BUFFER_TYPE_BOOL_OUTPUT: + buffer = args->bool_output; + break; + case BUFFER_TYPE_BOOL_MEMORY: + buffer = args->bool_memory; + break; + default: + return -1; + } + + if (buffer_idx >= args->buffer_size || bit_idx >= args->bits_per_buffer) { + return -1; + } + + IEC_BOOL *ptr = buffer[buffer_idx][bit_idx]; + *value = (ptr != NULL) ? *ptr : 0; + return 0; +} +``` + +### Thread Safety Strategy + +```c +// s7comm_callbacks.c + +int S7API s7comm_rw_callback(void *usrPtr, int Sender, int Operation, + PS7Tag PTag, void *pUsrData) { + s7comm_context_t *ctx = (s7comm_context_t *)usrPtr; + plugin_runtime_args_t *args = ctx->runtime_args; + + // Acquire OpenPLC buffer mutex + args->mutex_take(args->buffer_mutex); + + int result = 0; + bool is_read = (Operation == OperationRead); + + switch (PTag->Area) { + case S7AreaPE: + result = handle_pe_access(ctx, PTag, pUsrData, is_read); + break; + case S7AreaPA: + result = handle_pa_access(ctx, PTag, pUsrData, is_read); + break; + case S7AreaMK: + result = handle_mk_access(ctx, PTag, pUsrData, is_read); + break; + case S7AreaDB: + result = handle_db_access(ctx, PTag, pUsrData, is_read); + break; + default: + result = errSrvUnknownArea; + break; + } + + // Release mutex + args->mutex_give(args->buffer_mutex); + + return result; +} +``` + +### DB Access Handler + +```c +static int handle_db_access(s7comm_context_t *ctx, PS7Tag PTag, + void *pUsrData, bool is_read) { + // Find matching DB configuration + data_block_config_t *db = find_db_config(ctx->config, PTag->DBNumber); + if (db == NULL) { + return errSrvUnknownArea; + } + + // Validate access bounds + if (PTag->Start + PTag->Size > db->size_bytes) { + return errSrvInvalidParams; + } + + // Handle based on word length + switch (PTag->WordLen) { + case S7WLBit: + return handle_bit_access(ctx, db, PTag, pUsrData, is_read); + case S7WLByte: + case S7WLChar: + return handle_byte_access(ctx, db, PTag, pUsrData, is_read); + case S7WLWord: + case S7WLInt: + return handle_word_access(ctx, db, PTag, pUsrData, is_read); + case S7WLDWord: + case S7WLDInt: + case S7WLReal: + return handle_dword_access(ctx, db, PTag, pUsrData, is_read); + default: + return errSrvInvalidParams; + } +} +``` + +### Configuration Validation + +```c +// s7comm_config.c + +int s7comm_validate_config(const s7comm_config_t *config) { + // Validate port + if (config->port == 0) { + return -1; + } + + // Validate timeouts + if (config->send_timeout_ms < 100 || config->recv_timeout_ms < 100) { + return -1; + } + + // Validate PDU size + if (config->pdu_size < 240 || config->pdu_size > 960) { + return -1; + } + + // Check for duplicate DB numbers + for (int i = 0; i < config->num_data_blocks; i++) { + for (int j = i + 1; j < config->num_data_blocks; j++) { + if (config->data_blocks[i].db_number == + config->data_blocks[j].db_number) { + return -1; + } + } + } + + // Validate buffer mappings don't exceed limits + for (int i = 0; i < config->num_data_blocks; i++) { + const data_block_config_t *db = &config->data_blocks[i]; + int max_offset = get_buffer_max_offset(db->mapping.type); + + int required = db->mapping.start_buffer + + (db->size_bytes / get_element_size(db->mapping.type)); + + if (required > max_offset) { + return -1; + } + } + + return 0; +} +``` + +--- + +## Phased Implementation Plan + +### Phase 1: Foundation (1-2 weeks) + +**Objectives:** +- Project structure setup +- Snap7 library integration +- Basic server lifecycle + +**Tasks:** +1. Create plugin directory structure +2. Copy/integrate Snap7 source files +3. Create CMakeLists.txt +4. Implement plugin lifecycle stubs (`init`, `start_loop`, `stop_loop`, `cleanup`) +5. Hardcode minimal configuration for testing +6. Test server starts and accepts connections +7. Add entry to `plugins.conf` + +**Deliverables:** +- Compilable `libs7comm.so` plugin +- Server accepts connections on port 102 +- Basic logging output + +**Verification:** +```bash +# Build plugin +cd build && cmake .. && make s7comm_plugin + +# Test with python-snap7 +python3 -c " +import snap7 +client = snap7.client.Client() +client.connect('127.0.0.1', 0, 0) +print('Connected:', client.get_connected()) +client.disconnect() +" +``` + +### Phase 2: Configuration System (1 week) + +**Objectives:** +- JSON configuration parsing +- All parameters configurable + +**Tasks:** +1. Add cJSON library (or similar) for JSON parsing +2. Implement `s7comm_parse_config()` function +3. Create default `s7comm_config.json` +4. Implement configuration validation +5. Apply server parameters from config +6. Add PLC identity configuration + +**Deliverables:** +- Plugin fully configurable via JSON +- Configuration validation with clear error messages +- Default configuration file + +**Verification:** +- Modify port in config, verify server binds to new port +- Invalid config rejected with clear error message + +### Phase 3: Buffer Mapping (1-2 weeks) + +**Objectives:** +- Dynamic buffer allocation +- Complete S7 area mapping + +**Tasks:** +1. Implement buffer allocation based on config +2. Register S7 areas (PE, PA, MK, DBs) with Snap7 +3. Implement RW callback handler +4. Implement PE/PA area handlers (bool_input/bool_output) +5. Implement MK area handler (bool_memory) +6. Implement DB handlers for all buffer types +7. Add endianness conversion +8. Support bit-level addressing + +**Deliverables:** +- All configured areas accessible via S7 +- Correct data mapping to OpenPLC buffers +- Proper byte order handling + +**Verification:** +```python +# Test DB read/write +import snap7 +client = snap7.client.Client() +client.connect('127.0.0.1', 0, 0) + +# Write to DB10 (int_input mapping) +data = bytearray([0x12, 0x34]) +client.db_write(10, 0, data) + +# Read back +result = client.db_read(10, 0, 2) +assert result == data +``` + +### Phase 4: Thread Safety (1 week) + +**Objectives:** +- Ensure thread-safe buffer access +- Implement cycle hooks + +**Tasks:** +1. Integrate with plugin mutex (buffer_mutex) +2. Test concurrent access from multiple clients +3. Implement `cycle_start()` hook +4. Implement `cycle_end()` hook +5. Add stress testing +6. Profile and optimize hot paths + +**Deliverables:** +- No race conditions under load +- Deterministic buffer updates +- Performance metrics + +**Verification:** +- Run multiple S7 clients simultaneously +- Verify PLC cycle timing unaffected +- Memory sanitizer clean run + +### Phase 5: Logging and Diagnostics (0.5 weeks) + +**Objectives:** +- Comprehensive logging +- Event handling + +**Tasks:** +1. Implement Snap7 event callback +2. Add connection logging (configurable) +3. Add data access logging (configurable) +4. Add error logging with context +5. Implement server status reporting + +**Deliverables:** +- Configurable logging output +- Clear diagnostic messages +- Status queryable via logs + +### Phase 6: Testing and Documentation (1 week) + +**Objectives:** +- Comprehensive test coverage +- User documentation + +**Tasks:** +1. Unit tests for configuration parsing +2. Unit tests for buffer mapping +3. Integration tests with python-snap7 +4. Performance benchmarks +5. Write USER_GUIDE.md +6. Add configuration examples +7. Update main plugin README + +**Deliverables:** +- Test suite with >80% coverage +- Complete user documentation +- Example configurations + +### Phase 7: Advanced Features (Optional, Future) + +**Potential enhancements:** +1. SZL data responses (system queries) +2. Password protection for areas +3. Block upload/download simulation +4. Counter/Timer area support +5. WebSocket monitoring interface +6. Multiple server instances +7. Connection rate limiting + +--- + +## Testing Strategy + +### Unit Tests + +```c +// test_config.c + +void test_parse_valid_config(void) { + s7comm_config_t config; + int result = s7comm_parse_config("test_config.json", &config); + + assert(result == 0); + assert(config.port == 102); + assert(config.num_data_blocks == 6); + assert(config.data_blocks[0].db_number == 1); +} + +void test_parse_invalid_config(void) { + s7comm_config_t config; + int result = s7comm_parse_config("invalid_config.json", &config); + + assert(result != 0); +} + +void test_validate_duplicate_db(void) { + s7comm_config_t config = {0}; + config.num_data_blocks = 2; + config.data_blocks[0].db_number = 1; + config.data_blocks[1].db_number = 1; // Duplicate + + int result = s7comm_validate_config(&config); + assert(result != 0); +} +``` + +### Integration Tests + +```python +# test_integration.py + +import snap7 +import pytest + +@pytest.fixture +def s7_client(): + client = snap7.client.Client() + client.connect('127.0.0.1', 0, 0, 102) + yield client + client.disconnect() + +def test_read_db(s7_client): + """Test reading from configured DB""" + data = s7_client.db_read(10, 0, 10) + assert len(data) == 10 + +def test_write_db(s7_client): + """Test writing to configured DB""" + data = bytearray([0x01, 0x02, 0x03, 0x04]) + s7_client.db_write(20, 0, data) + result = s7_client.db_read(20, 0, 4) + assert result == data + +def test_read_inputs(s7_client): + """Test reading process inputs (PE area)""" + data = s7_client.read_area(snap7.types.Areas.PE, 0, 0, 10) + assert len(data) == 10 + +def test_write_outputs(s7_client): + """Test writing process outputs (PA area)""" + data = bytearray([0xFF, 0x00]) + s7_client.write_area(snap7.types.Areas.PA, 0, 0, data) + +def test_concurrent_access(s7_client): + """Test multiple concurrent clients""" + import threading + + def client_task(): + c = snap7.client.Client() + c.connect('127.0.0.1', 0, 0, 102) + for _ in range(100): + c.db_read(10, 0, 10) + c.disconnect() + + threads = [threading.Thread(target=client_task) for _ in range(10)] + for t in threads: + t.start() + for t in threads: + t.join() +``` + +### Performance Benchmarks + +```python +# benchmark.py + +import snap7 +import time + +def benchmark_read(client, iterations=10000): + start = time.time() + for _ in range(iterations): + client.db_read(10, 0, 100) + elapsed = time.time() - start + print(f"Read: {iterations/elapsed:.0f} ops/sec") + +def benchmark_write(client, iterations=10000): + data = bytearray(100) + start = time.time() + for _ in range(iterations): + client.db_write(20, 0, data) + elapsed = time.time() - start + print(f"Write: {iterations/elapsed:.0f} ops/sec") +``` + +--- + +## References + +### External Documentation + +1. **Snap7 Project**: https://github.com/davenardella/snap7 +2. **Snap7 Documentation**: http://snap7.sourceforge.net/ +3. **S7Comm Protocol Analysis**: https://github.com/orange-cyberdefense/S7Comm-Wireshark-Dissector +4. **RFC 1006 (ISO-on-TCP)**: https://tools.ietf.org/html/rfc1006 + +### Internal Documentation + +1. **OpenPLC v4 Plugin Guide**: `core/src/drivers/README.md` +2. **Plugin Types Header**: `core/src/drivers/plugin_types.h` +3. **Image Tables**: `core/src/plc_app/image_tables.h` +4. **Architecture Overview**: `docs/ARCHITECTURE.md` + +### Related Code + +1. **OpenPLC v3 S7Comm**: `OpenPLC_v3/utils/snap7_src/wrapper/oplc_snap7.cpp` +2. **Modbus Slave Plugin**: `core/src/drivers/plugins/python/modbus_slave/simple_modbus.py` +3. **Native Plugin Example**: `core/src/drivers/plugins/native/examples/test_plugin.c` + +--- + +## Appendix A: S7 Address Reference + +### S7 Area Codes + +| Area | Code | Description | Access | +|------|------|-------------|--------| +| PE | 0x81 | Process inputs | Read | +| PA | 0x82 | Process outputs | Read/Write | +| MK | 0x83 | Markers/Flags | Read/Write | +| DB | 0x84 | Data blocks | Read/Write | +| CT | 0x1C | Counters | Read/Write | +| TM | 0x1D | Timers | Read/Write | + +### S7 Word Length Codes + +| Code | Name | Size | Description | +|------|------|------|-------------| +| 0x01 | Bit | 1 bit | Single bit | +| 0x02 | Byte | 1 byte | 8 bits | +| 0x03 | Char | 1 byte | ASCII character | +| 0x04 | Word | 2 bytes | 16-bit unsigned | +| 0x05 | Int | 2 bytes | 16-bit signed | +| 0x06 | DWord | 4 bytes | 32-bit unsigned | +| 0x07 | DInt | 4 bytes | 32-bit signed | +| 0x08 | Real | 4 bytes | IEEE 754 float | + +### S7 Address Examples + +| S7 Address | Area | DB | Offset | Bit | Description | +|------------|------|-----|--------|-----|-------------| +| I0.0 | PE | - | 0 | 0 | Input byte 0, bit 0 | +| Q10.5 | PA | - | 10 | 5 | Output byte 10, bit 5 | +| M100.0 | MK | - | 100 | 0 | Marker byte 100, bit 0 | +| DB1.DBX0.0 | DB | 1 | 0 | 0 | DB1, byte 0, bit 0 | +| DB10.DBW0 | DB | 10 | 0 | - | DB10, word at byte 0 | +| DB10.DBD4 | DB | 10 | 4 | - | DB10, dword at byte 4 | + +--- + +## Appendix B: Error Codes + +### Snap7 Server Errors + +| Code | Name | Description | +|------|------|-------------| +| 0x00100000 | errSrvCannotStart | Server failed to start | +| 0x00200000 | errSrvDBNullPointer | NULL pointer for DB registration | +| 0x00300000 | errSrvAreaAlreadyExists | Area already registered | +| 0x00400000 | errSrvUnknownArea | Unknown area requested | +| 0x00500000 | errSrvInvalidParams | Invalid parameters | +| 0x00600000 | errSrvTooManyDB | Too many DBs registered | +| 0x00700000 | errSrvInvalidParamNumber | Invalid parameter number | +| 0x00800000 | errSrvCannotChangeParam | Cannot change parameter | + +--- + +*Document Version: 1.0* +*Last Updated: 2026-01-12* +*Author: OpenPLC Development Team* diff --git a/core/src/drivers/plugins/native/s7comm/docs/JSON_SCHEMA.md b/core/src/drivers/plugins/native/s7comm/docs/JSON_SCHEMA.md new file mode 100644 index 00000000..e90aa28f --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/docs/JSON_SCHEMA.md @@ -0,0 +1,460 @@ +# S7Comm Plugin JSON Configuration Schema + +This document defines the JSON configuration schema for the S7Comm plugin. +It is intended for OpenPLC Editor developers implementing the configuration UI. + +## JSON Structure Overview + +```json +{ + "server": { ... }, + "plc_identity": { ... }, + "data_blocks": [ ... ], + "system_areas": { ... }, + "logging": { ... } +} +``` + +All sections are optional. Missing sections use default values. + +## Complete Schema Reference + +### Root Object + +| Field | Type | Required | Default | Description | +|-------|------|----------|---------|-------------| +| `server` | object | No | See below | Server configuration | +| `plc_identity` | object | No | See below | PLC identity for S7 clients | +| `data_blocks` | array | No | `[]` | Data block mappings | +| `system_areas` | object | No | All disabled | S7 system area mappings | +| `logging` | object | No | See below | Logging configuration | + +--- + +### server Object + +```json +{ + "server": { + "enabled": true, + "bind_address": "0.0.0.0", + "port": 102, + "max_clients": 32, + "work_interval_ms": 100, + "send_timeout_ms": 3000, + "recv_timeout_ms": 3000, + "ping_timeout_ms": 10000, + "pdu_size": 480 + } +} +``` + +| Field | Type | Required | Default | Constraints | Description | +|-------|------|----------|---------|-------------|-------------| +| `enabled` | boolean | No | `true` | - | Enable/disable the S7 server | +| `bind_address` | string | No | `"0.0.0.0"` | Valid IP or `"0.0.0.0"` | Network interface to bind | +| `port` | integer | No | `102` | 1-65535 | TCP port (102 = standard S7) | +| `max_clients` | integer | No | `32` | 1-128 | Max simultaneous connections | +| `work_interval_ms` | integer | No | `100` | 10-1000 | Internal polling interval (ms) | +| `send_timeout_ms` | integer | No | `3000` | 100-30000 | Socket send timeout (ms) | +| `recv_timeout_ms` | integer | No | `3000` | 100-30000 | Socket receive timeout (ms) | +| `ping_timeout_ms` | integer | No | `10000` | 1000-60000 | Keep-alive timeout (ms) | +| `pdu_size` | integer | No | `480` | 240-960 | Maximum PDU size | + +**Notes:** +- Port 102 requires root/administrator privileges +- Use port > 1024 to avoid privilege requirements + +--- + +### plc_identity Object + +```json +{ + "plc_identity": { + "name": "OpenPLC Runtime", + "module_type": "CPU 315-2 PN/DP", + "serial_number": "S C-XXXXXXXXX", + "copyright": "OpenPLC Project", + "module_name": "OpenPLC" + } +} +``` + +| Field | Type | Required | Default | Constraints | Description | +|-------|------|----------|---------|-------------|-------------| +| `name` | string | No | `"OpenPLC Runtime"` | Max 64 chars | PLC name | +| `module_type` | string | No | `"CPU 315-2 PN/DP"` | Max 64 chars | CPU type string | +| `serial_number` | string | No | `"S C-XXXXXXXXX"` | Max 64 chars | Serial number | +| `copyright` | string | No | `"OpenPLC Project"` | Max 64 chars | Copyright string | +| `module_name` | string | No | `"OpenPLC"` | Max 64 chars | Module name | + +**Notes:** +- These values are returned in S7 SZL (System Status List) queries +- `module_type` should match a real Siemens CPU for best HMI compatibility + +--- + +### data_blocks Array + +```json +{ + "data_blocks": [ + { + "db_number": 1, + "description": "Digital Inputs", + "size_bytes": 128, + "mapping": { + "type": "bool_input", + "start_buffer": 0, + "bit_addressing": true + } + } + ] +} +``` + +Each data block object: + +| Field | Type | Required | Default | Constraints | Description | +|-------|------|----------|---------|-------------|-------------| +| `db_number` | integer | **Yes** | - | 1-65535 | S7 DB number | +| `description` | string | No | `""` | Max 128 chars | Human-readable description | +| `size_bytes` | integer | **Yes** | - | 1-65536 | DB size in bytes | +| `mapping` | object | **Yes** | - | See below | Buffer mapping configuration | + +**mapping Object:** + +| Field | Type | Required | Default | Constraints | Description | +|-------|------|----------|---------|-------------|-------------| +| `type` | string | **Yes** | - | See valid types | OpenPLC buffer type | +| `start_buffer` | integer | No | `0` | 0-1023 | Starting index in OpenPLC buffer | +| `bit_addressing` | boolean | No | `false` | - | Enable bit-level access (BOOL types) | + +**Valid `type` values:** + +| Type | IEC Address | Element Size | Direction | Description | +|------|-------------|--------------|-----------|-------------| +| `"bool_input"` | %IX | 1 bit | Read | Digital inputs | +| `"bool_output"` | %QX | 1 bit | Read/Write | Digital outputs | +| `"bool_memory"` | %MX | 1 bit | Read/Write | Internal markers | +| `"byte_input"` | %IB | 1 byte | Read | Byte inputs | +| `"byte_output"` | %QB | 1 byte | Read/Write | Byte outputs | +| `"int_input"` | %IW | 2 bytes | Read | Word inputs (UINT) | +| `"int_output"` | %QW | 2 bytes | Read/Write | Word outputs (UINT) | +| `"int_memory"` | %MW | 2 bytes | Read/Write | Word memory (UINT) | +| `"dint_input"` | %ID | 4 bytes | Read | Double word inputs (UDINT) | +| `"dint_output"` | %QD | 4 bytes | Read/Write | Double word outputs (UDINT) | +| `"dint_memory"` | %MD | 4 bytes | Read/Write | Double word memory (UDINT) | +| `"lint_input"` | %IL | 8 bytes | Read | Long word inputs (ULINT) | +| `"lint_output"` | %QL | 8 bytes | Read/Write | Long word outputs (ULINT) | +| `"lint_memory"` | %ML | 8 bytes | Read/Write | Long word memory (ULINT) | + +**Notes:** +- Maximum 64 data blocks supported +- Input types are read-only from S7 client perspective +- Output and memory types are read/write + +--- + +### system_areas Object + +```json +{ + "system_areas": { + "pe_area": { + "enabled": true, + "size_bytes": 128, + "mapping": { + "type": "bool_input", + "start_buffer": 0 + } + }, + "pa_area": { + "enabled": true, + "size_bytes": 128, + "mapping": { + "type": "bool_output", + "start_buffer": 0 + } + }, + "mk_area": { + "enabled": true, + "size_bytes": 256, + "mapping": { + "type": "bool_memory", + "start_buffer": 0 + } + } + } +} +``` + +Each system area (`pe_area`, `pa_area`, `mk_area`): + +| Field | Type | Required | Default | Constraints | Description | +|-------|------|----------|---------|-------------|-------------| +| `enabled` | boolean | No | `false` | - | Enable this area | +| `size_bytes` | integer | No | `0` | 1-65536 | Area size in bytes | +| `mapping` | object | No | - | See above | Buffer mapping (same as data_blocks) | + +**Area Descriptions:** + +| Area | S7 Area Code | S7 Address | Typical Use | +|------|--------------|------------|-------------| +| `pe_area` | PE (Process Inputs) | I (e.g., I0.0) | Digital/analog inputs | +| `pa_area` | PA (Process Outputs) | Q (e.g., Q0.0) | Digital/analog outputs | +| `mk_area` | MK (Markers) | M (e.g., M0.0) | Internal flags/memory | + +--- + +### logging Object + +```json +{ + "logging": { + "log_connections": true, + "log_data_access": false, + "log_errors": true + } +} +``` + +| Field | Type | Required | Default | Description | +|-------|------|----------|---------|-------------| +| `log_connections` | boolean | No | `true` | Log client connect/disconnect | +| `log_data_access` | boolean | No | `false` | Log read/write operations (verbose) | +| `log_errors` | boolean | No | `true` | Log errors and warnings | + +**Notes:** +- `log_data_access` generates high log volume - use for debugging only +- All logs go through the centralized logging system (visible in Editor) + +--- + +## Complete Example Configurations + +### Minimal Configuration + +```json +{ + "server": { + "enabled": true, + "port": 102 + } +} +``` + +### Digital I/O Only + +```json +{ + "server": { + "enabled": true, + "port": 102 + }, + "data_blocks": [ + { + "db_number": 1, + "description": "Digital Inputs", + "size_bytes": 128, + "mapping": { + "type": "bool_input", + "start_buffer": 0, + "bit_addressing": true + } + }, + { + "db_number": 2, + "description": "Digital Outputs", + "size_bytes": 128, + "mapping": { + "type": "bool_output", + "start_buffer": 0, + "bit_addressing": true + } + } + ] +} +``` + +### Mixed I/O with System Areas + +```json +{ + "server": { + "enabled": true, + "bind_address": "0.0.0.0", + "port": 102, + "max_clients": 16 + }, + "plc_identity": { + "name": "Production PLC", + "module_type": "CPU 315-2 PN/DP" + }, + "data_blocks": [ + { + "db_number": 10, + "description": "Analog Inputs", + "size_bytes": 256, + "mapping": { + "type": "int_input", + "start_buffer": 0 + } + }, + { + "db_number": 20, + "description": "Analog Outputs", + "size_bytes": 256, + "mapping": { + "type": "int_output", + "start_buffer": 0 + } + }, + { + "db_number": 100, + "description": "Setpoints", + "size_bytes": 512, + "mapping": { + "type": "int_memory", + "start_buffer": 0 + } + } + ], + "system_areas": { + "pe_area": { + "enabled": true, + "size_bytes": 128, + "mapping": { + "type": "bool_input", + "start_buffer": 0 + } + }, + "pa_area": { + "enabled": true, + "size_bytes": 128, + "mapping": { + "type": "bool_output", + "start_buffer": 0 + } + }, + "mk_area": { + "enabled": true, + "size_bytes": 256, + "mapping": { + "type": "bool_memory", + "start_buffer": 0 + } + } + }, + "logging": { + "log_connections": true, + "log_data_access": false, + "log_errors": true + } +} +``` + +### Non-Privileged Port + +For running without root privileges: + +```json +{ + "server": { + "enabled": true, + "port": 1102, + "max_clients": 8 + }, + "data_blocks": [ + { + "db_number": 1, + "size_bytes": 1024, + "mapping": { + "type": "int_memory", + "start_buffer": 0 + } + } + ] +} +``` + +--- + +## Editor UI Recommendations + +### Required Fields +- `server.enabled` - Toggle to enable/disable plugin +- `server.port` - Port number input (validate 1-65535) +- At least one data_block or system_area for meaningful operation + +### Suggested UI Layout + +1. **Server Settings Panel** + - Enable/Disable toggle + - Port number (default 102, warn if < 1024) + - Max clients (slider 1-128) + - Advanced: timeouts, PDU size (collapsible) + +2. **PLC Identity Panel** (collapsible, optional) + - Text fields for identity strings + - Module type dropdown with common CPU types + +3. **Data Blocks Panel** + - Add/Remove data block buttons + - For each block: + - DB Number (required, unique) + - Description (optional) + - Size in bytes (required) + - Mapping type dropdown + - Start buffer index + - Bit addressing checkbox (only for bool types) + +4. **System Areas Panel** (collapsible) + - PE Area (Inputs) - enable + size + mapping + - PA Area (Outputs) - enable + size + mapping + - MK Area (Markers) - enable + size + mapping + +5. **Logging Panel** (collapsible) + - Checkboxes for each log type + +### Validation Rules + +1. **DB Numbers**: Must be unique, 1-65535 +2. **Sizes**: Must be > 0, <= 65536 +3. **Port**: Warn if 102 (requires root), error if out of range +4. **Start Buffer**: Validate against buffer_size (typically 1024) +5. **Type Consistency**: Warn if mixing input types with write-heavy use cases + +--- + +## Runtime Behavior + +### Double-Buffering Architecture + +The plugin uses double-buffering for thread safety: +- **S7 Buffer**: What Snap7 clients read/write (asynchronous access) +- **Shadow Buffer**: Used for synchronization with OpenPLC + +Sync occurs at the end of each PLC scan cycle: +1. If no clients connected: skip sync entirely (optimization) +2. Copy S7 buffers -> shadow buffers (brief mutex lock) +3. Sync shadow <-> OpenPLC buffers +4. Copy shadow buffers -> S7 buffers (brief mutex lock) + +### Data Direction + +| Buffer Type | S7 Client Can Read | S7 Client Can Write | +|-------------|-------------------|---------------------| +| `*_input` | Yes | No (ignored) | +| `*_output` | Yes | Yes | +| `*_memory` | Yes | Yes | + +### Byte Order + +S7 protocol uses **big-endian** (network byte order). The plugin automatically converts between big-endian (S7) and little-endian (OpenPLC/x86). + +--- + +*Schema Version: 1.0* +*Last Updated: 2026-01-12* diff --git a/core/src/drivers/plugins/native/s7comm/docs/USER_GUIDE.md b/core/src/drivers/plugins/native/s7comm/docs/USER_GUIDE.md new file mode 100644 index 00000000..05e08cf7 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/docs/USER_GUIDE.md @@ -0,0 +1,554 @@ +# S7Comm Plugin User Guide + +## Overview + +The S7Comm plugin enables OpenPLC Runtime v4 to communicate using the Siemens S7 protocol. This allows S7-compatible HMIs, SCADA systems, and other industrial equipment to read and write data from/to the OpenPLC runtime. + +## Features + +- Full S7Comm protocol support via Snap7 library +- Configurable data block mappings +- Support for all OpenPLC buffer types (BOOL, BYTE, UINT, UDINT, ULINT) +- Multiple concurrent client connections +- Configurable PLC identity for S7 client compatibility +- Double-buffered, thread-safe operation (S7 clients run asynchronously from PLC cycle) +- Automatic optimization: no overhead when no clients are connected +- Centralized logging integrated with OpenPLC Editor + +## Quick Start + +### 1. Enable the Plugin + +Add or uncomment the S7Comm entry in `plugins.conf`: + +``` +s7comm,./core/src/drivers/plugins/native/s7comm/libs7comm.so,1,1,./core/src/drivers/plugins/native/s7comm/s7comm_config.json, +``` + +### 2. Configure the Plugin + +Edit `s7comm_config.json` to match your requirements: + +```json +{ + "server": { + "enabled": true, + "port": 102 + }, + "data_blocks": [ + { + "db_number": 1, + "size_bytes": 128, + "mapping": { + "type": "bool_input", + "start_buffer": 0 + } + } + ] +} +``` + +### 3. Start the Runtime + +```bash +sudo ./start_openplc.sh +``` + +### 4. Connect with S7 Client + +The plugin listens on port 102 (default S7 port). Connect using any S7-compatible client. + +## Configuration Reference + +### Server Settings + +```json +{ + "server": { + "enabled": true, + "bind_address": "0.0.0.0", + "port": 102, + "max_clients": 32, + "work_interval_ms": 100, + "send_timeout_ms": 3000, + "recv_timeout_ms": 3000, + "ping_timeout_ms": 10000, + "pdu_size": 480 + } +} +``` + +| Setting | Default | Description | +|---------|---------|-------------| +| `enabled` | true | Enable/disable the S7 server | +| `bind_address` | "0.0.0.0" | Network interface to bind (0.0.0.0 = all) | +| `port` | 102 | TCP port (102 is standard S7 port) | +| `max_clients` | 32 | Maximum simultaneous connections | +| `work_interval_ms` | 100 | Internal polling interval | +| `send_timeout_ms` | 3000 | Socket send timeout | +| `recv_timeout_ms` | 3000 | Socket receive timeout | +| `ping_timeout_ms` | 10000 | Keep-alive timeout | +| `pdu_size` | 480 | Maximum PDU size (240-960) | + +**Note:** Port 102 requires root privileges on Linux. Use `sudo` or configure capabilities. + +### PLC Identity + +Configure how the PLC identifies itself to S7 clients: + +```json +{ + "plc_identity": { + "name": "OpenPLC Runtime", + "module_type": "CPU 315-2 PN/DP", + "serial_number": "S C-XXXXXXXXX", + "copyright": "OpenPLC Project", + "module_name": "OpenPLC" + } +} +``` + +These values are returned when clients query the PLC's system state list (SZL). + +### Data Block Mappings + +Data blocks map S7 DB areas to OpenPLC memory buffers: + +```json +{ + "data_blocks": [ + { + "db_number": 10, + "description": "Analog Inputs", + "size_bytes": 2048, + "mapping": { + "type": "int_input", + "start_buffer": 0, + "bit_addressing": false + } + } + ] +} +``` + +| Field | Description | +|-------|-------------| +| `db_number` | S7 DB number (1-65535) | +| `description` | Human-readable description (optional) | +| `size_bytes` | Size of the data block in bytes | +| `mapping.type` | OpenPLC buffer type to map to | +| `mapping.start_buffer` | Starting index in OpenPLC buffer | +| `mapping.bit_addressing` | Enable bit-level access (for BOOL types) | + +### Supported Buffer Types + +| Type | IEC Address | Element Size | Max Count | +|------|-------------|--------------|-----------| +| `bool_input` | %IX | 1 bit | 8192 | +| `bool_output` | %QX | 1 bit | 8192 | +| `bool_memory` | %MX | 1 bit | 8192 | +| `byte_input` | %IB | 1 byte | 1024 | +| `byte_output` | %QB | 1 byte | 1024 | +| `int_input` | %IW | 2 bytes | 1024 | +| `int_output` | %QW | 2 bytes | 1024 | +| `int_memory` | %MW | 2 bytes | 1024 | +| `dint_input` | %ID | 4 bytes | 1024 | +| `dint_output` | %QD | 4 bytes | 1024 | +| `dint_memory` | %MD | 4 bytes | 1024 | +| `lint_input` | %IL | 8 bytes | 1024 | +| `lint_output` | %QL | 8 bytes | 1024 | +| `lint_memory` | %ML | 8 bytes | 1024 | + +### System Areas + +Configure S7 system areas (PE, PA, MK): + +```json +{ + "system_areas": { + "pe_area": { + "enabled": true, + "size_bytes": 128, + "mapping": { + "type": "bool_input", + "start_buffer": 0 + } + }, + "pa_area": { + "enabled": true, + "size_bytes": 128, + "mapping": { + "type": "bool_output", + "start_buffer": 0 + } + }, + "mk_area": { + "enabled": true, + "size_bytes": 256, + "mapping": { + "type": "bool_memory", + "start_buffer": 0 + } + } + } +} +``` + +| Area | S7 Address | Description | +|------|------------|-------------| +| `pe_area` | I (Inputs) | Process inputs | +| `pa_area` | Q (Outputs) | Process outputs | +| `mk_area` | M (Markers) | Internal flags/markers | + +### Logging + +Configure logging verbosity: + +```json +{ + "logging": { + "log_connections": true, + "log_data_access": false, + "log_errors": true + } +} +``` + +| Setting | Description | +|---------|-------------| +| `log_connections` | Log client connect/disconnect events | +| `log_data_access` | Log read/write operations (verbose) | +| `log_errors` | Log errors and warnings | + +## Example Configurations + +### Minimal Configuration + +Basic setup with just digital I/O: + +```json +{ + "server": { + "enabled": true, + "port": 102 + }, + "data_blocks": [ + { + "db_number": 1, + "size_bytes": 128, + "mapping": { "type": "bool_input", "start_buffer": 0 } + }, + { + "db_number": 2, + "size_bytes": 128, + "mapping": { "type": "bool_output", "start_buffer": 0 } + } + ] +} +``` + +### Full Industrial Setup + +Complete configuration with analog and digital I/O: + +```json +{ + "server": { + "enabled": true, + "bind_address": "0.0.0.0", + "port": 102, + "max_clients": 16, + "pdu_size": 480 + }, + "plc_identity": { + "name": "Production Line PLC", + "module_type": "CPU 315-2 PN/DP" + }, + "data_blocks": [ + { + "db_number": 1, + "description": "Digital Inputs", + "size_bytes": 128, + "mapping": { "type": "bool_input", "start_buffer": 0, "bit_addressing": true } + }, + { + "db_number": 2, + "description": "Digital Outputs", + "size_bytes": 128, + "mapping": { "type": "bool_output", "start_buffer": 0, "bit_addressing": true } + }, + { + "db_number": 10, + "description": "Analog Inputs (4-20mA)", + "size_bytes": 256, + "mapping": { "type": "int_input", "start_buffer": 0 } + }, + { + "db_number": 20, + "description": "Analog Outputs", + "size_bytes": 256, + "mapping": { "type": "int_output", "start_buffer": 0 } + }, + { + "db_number": 100, + "description": "Setpoints", + "size_bytes": 512, + "mapping": { "type": "int_memory", "start_buffer": 0 } + }, + { + "db_number": 200, + "description": "Counters/Timers", + "size_bytes": 1024, + "mapping": { "type": "dint_memory", "start_buffer": 0 } + } + ], + "system_areas": { + "pe_area": { "enabled": true, "size_bytes": 128, "mapping": { "type": "bool_input", "start_buffer": 0 } }, + "pa_area": { "enabled": true, "size_bytes": 128, "mapping": { "type": "bool_output", "start_buffer": 0 } }, + "mk_area": { "enabled": true, "size_bytes": 256, "mapping": { "type": "bool_memory", "start_buffer": 0 } } + }, + "logging": { + "log_connections": true, + "log_data_access": false, + "log_errors": true + } +} +``` + +### WinCC-Compatible Configuration + +Configuration matching typical WinCC tag addressing: + +```json +{ + "server": { + "enabled": true, + "port": 102 + }, + "plc_identity": { + "name": "OpenPLC", + "module_type": "CPU 315-2 PN/DP" + }, + "data_blocks": [ + { + "db_number": 1, + "description": "Process Data", + "size_bytes": 4096, + "mapping": { "type": "int_memory", "start_buffer": 0 } + } + ], + "system_areas": { + "pe_area": { "enabled": true, "size_bytes": 256, "mapping": { "type": "bool_input", "start_buffer": 0 } }, + "pa_area": { "enabled": true, "size_bytes": 256, "mapping": { "type": "bool_output", "start_buffer": 0 } }, + "mk_area": { "enabled": true, "size_bytes": 256, "mapping": { "type": "bool_memory", "start_buffer": 0 } } + } +} +``` + +## S7 Address Mapping + +### Understanding S7 Addresses + +| S7 Address | Area | Description | OpenPLC Equivalent | +|------------|------|-------------|-------------------| +| I0.0 | PE | Input byte 0, bit 0 | %IX0.0 | +| Q10.5 | PA | Output byte 10, bit 5 | %QX10.5 | +| M100.0 | MK | Marker byte 100, bit 0 | %MX100.0 | +| DB1.DBX0.0 | DB | DB1, byte 0, bit 0 | Configured mapping | +| DB10.DBW0 | DB | DB10, word at byte 0 | Configured mapping | +| DB10.DBD4 | DB | DB10, dword at byte 4 | Configured mapping | + +### Address Calculation + +For data blocks with word mappings: + +``` +OpenPLC Buffer Index = start_buffer + (S7_byte_offset / element_size) + +Example: +- DB10 mapped to int_input, start_buffer = 0 +- S7 access: DB10.DBW10 (word at byte 10) +- OpenPLC: int_input[0 + (10/2)] = int_input[5] +``` + +For data blocks with bit mappings: + +``` +OpenPLC Buffer Index = start_buffer + S7_byte_offset +OpenPLC Bit Index = S7_bit_offset + +Example: +- DB1 mapped to bool_output, start_buffer = 0 +- S7 access: DB1.DBX5.3 (byte 5, bit 3) +- OpenPLC: bool_output[0 + 5][3] = bool_output[5][3] +``` + +## Connecting S7 Clients + +### Connection Parameters + +| Parameter | Value | +|-----------|-------| +| IP Address | OpenPLC host IP | +| Rack | 0 | +| Slot | 0 (or 1, 2) | +| Port | 102 (default) | + +### Python (python-snap7) + +```python +import snap7 + +client = snap7.client.Client() +client.connect('192.168.1.100', 0, 0) # IP, rack, slot + +# Read DB10, 10 bytes starting at byte 0 +data = client.db_read(10, 0, 10) + +# Write to DB20 +client.db_write(20, 0, bytearray([0x01, 0x02, 0x03])) + +# Read inputs (PE area) +inputs = client.read_area(snap7.types.Areas.PE, 0, 0, 10) + +# Write outputs (PA area) +client.write_area(snap7.types.Areas.PA, 0, 0, bytearray([0xFF])) + +client.disconnect() +``` + +### Node.js (node-snap7) + +```javascript +const snap7 = require('node-snap7'); + +const client = new snap7.S7Client(); +client.ConnectTo('192.168.1.100', 0, 0, (err) => { + if (err) throw err; + + // Read DB10 + client.DBRead(10, 0, 10, (err, data) => { + console.log(data); + }); + + // Write to DB20 + client.DBWrite(20, 0, Buffer.from([0x01, 0x02]), (err) => { + if (!err) console.log('Written'); + }); +}); +``` + +### WinCC / TIA Portal + +1. Add a new connection to the PLC +2. Set connection type to "S7 connection" +3. Enter OpenPLC IP address +4. Set Rack=0, Slot=0 +5. Configure tags using DB addresses matching your configuration + +## Troubleshooting + +### Server Won't Start + +**Symptom:** Plugin fails to initialize + +**Solutions:** +1. Check port availability: `netstat -an | grep 102` +2. Verify root privileges for port 102 +3. Check configuration file syntax +4. Review logs for specific errors + +### Connection Refused + +**Symptom:** Clients cannot connect + +**Solutions:** +1. Verify server is running (check logs) +2. Check firewall rules for port 102 +3. Verify bind_address allows external connections +4. Check max_clients limit + +### Data Not Updating + +**Symptom:** Values don't change in HMI + +**Solutions:** +1. Verify DB number matches configuration +2. Check address offsets are within size_bytes +3. Verify PLC program is writing to correct addresses +4. Enable log_data_access for debugging + +### Wrong Values + +**Symptom:** Data appears corrupted + +**Solutions:** +1. Check byte order (S7 uses big-endian) +2. Verify data type matches (Word vs DWord) +3. Check buffer mapping configuration +4. Verify bit_addressing setting for BOOL types + +### Performance Issues + +**Symptom:** Slow response times + +**Solutions:** +1. Reduce log_data_access (very verbose) +2. Increase work_interval_ms +3. Reduce number of concurrent clients +4. Check network latency + +## Limitations + +1. **Counter/Timer Areas**: CT and TM areas are not currently supported +2. **Block Upload/Download**: Block transfer operations are not supported +3. **Password Protection**: S7 password protection is not implemented +4. **Multiple Instances**: Only one S7 server instance per runtime + +## Security Considerations + +1. **No Authentication**: S7Comm has no built-in authentication +2. **Network Segmentation**: Isolate S7 traffic on industrial network +3. **Firewall Rules**: Restrict access to port 102 +4. **Read-Only Mode**: Consider mapping sensitive areas as read-only + +## Architecture + +### Double-Buffering + +The plugin uses a double-buffering architecture for thread safety: + +``` +S7 Clients <---> [S7 Buffer] <---> [Shadow Buffer] <---> OpenPLC Buffers + ^ ^ + | | + Snap7 server PLC cycle sync + (asynchronous) (at cycle_end) +``` + +- **S7 Buffer**: Registered with Snap7, accessed asynchronously by S7 clients +- **Shadow Buffer**: Used for synchronization with OpenPLC at cycle end +- **Sync**: Happens only at cycle_end with brief mutex locks (minimal contention) +- **Optimization**: If no clients are connected, sync is skipped entirely + +### Data Flow + +1. S7 clients read/write to S7 buffers at any time (lock-free) +2. At end of PLC scan cycle: + - S7 buffer -> Shadow buffer (capture client writes) + - Shadow buffer <-> OpenPLC buffers (apply writes, get new values) + - Shadow buffer -> S7 buffer (publish to clients) + +## Related Documentation + +- [JSON Configuration Schema](JSON_SCHEMA.md) - Detailed schema reference for Editor developers + +## Support + +For issues and feature requests: +- GitHub Issues: https://github.com/Autonomy-Logic/openplc-runtime/issues + +--- + +*Document Version: 1.1* +*Last Updated: 2026-01-12* diff --git a/core/src/drivers/plugins/native/s7comm/s7comm_config.c b/core/src/drivers/plugins/native/s7comm/s7comm_config.c new file mode 100644 index 00000000..5b57feae --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/s7comm_config.c @@ -0,0 +1,477 @@ +/** + * @file s7comm_config.c + * @brief S7Comm Plugin Configuration Parser Implementation + * + * Parses JSON configuration files using cJSON library. + */ + +#include "s7comm_config.h" +#include "cJSON.h" + +#include +#include +#include + +/* Error codes */ +#define S7COMM_CONFIG_OK 0 +#define S7COMM_CONFIG_ERR_FILE -1 +#define S7COMM_CONFIG_ERR_PARSE -2 +#define S7COMM_CONFIG_ERR_MEMORY -3 +#define S7COMM_CONFIG_ERR_INVALID -4 +#define S7COMM_CONFIG_ERR_MISSING -5 + +/* Buffer type string mappings */ +static const struct { + const char *name; + s7comm_buffer_type_t type; +} buffer_type_map[] = { + {"bool_input", BUFFER_TYPE_BOOL_INPUT}, + {"bool_output", BUFFER_TYPE_BOOL_OUTPUT}, + {"bool_memory", BUFFER_TYPE_BOOL_MEMORY}, + {"byte_input", BUFFER_TYPE_BYTE_INPUT}, + {"byte_output", BUFFER_TYPE_BYTE_OUTPUT}, + {"int_input", BUFFER_TYPE_INT_INPUT}, + {"int_output", BUFFER_TYPE_INT_OUTPUT}, + {"int_memory", BUFFER_TYPE_INT_MEMORY}, + {"dint_input", BUFFER_TYPE_DINT_INPUT}, + {"dint_output", BUFFER_TYPE_DINT_OUTPUT}, + {"dint_memory", BUFFER_TYPE_DINT_MEMORY}, + {"lint_input", BUFFER_TYPE_LINT_INPUT}, + {"lint_output", BUFFER_TYPE_LINT_OUTPUT}, + {"lint_memory", BUFFER_TYPE_LINT_MEMORY}, + {NULL, BUFFER_TYPE_NONE} +}; + +/** + * @brief Read entire file into a string + */ +static char *read_file(const char *path) +{ + FILE *fp = fopen(path, "rb"); + if (fp == NULL) { + return NULL; + } + + fseek(fp, 0, SEEK_END); + long size = ftell(fp); + fseek(fp, 0, SEEK_SET); + + if (size <= 0 || size > 1024 * 1024) { /* Max 1MB config file */ + fclose(fp); + return NULL; + } + + char *buffer = (char *)malloc(size + 1); + if (buffer == NULL) { + fclose(fp); + return NULL; + } + + size_t read_size = fread(buffer, 1, size, fp); + fclose(fp); + + if ((long)read_size != size) { + free(buffer); + return NULL; + } + + buffer[size] = '\0'; + return buffer; +} + +/** + * @brief Parse buffer type from string + */ +static s7comm_buffer_type_t parse_buffer_type(const char *type_str) +{ + if (type_str == NULL) { + return BUFFER_TYPE_NONE; + } + + for (int i = 0; buffer_type_map[i].name != NULL; i++) { + if (strcmp(type_str, buffer_type_map[i].name) == 0) { + return buffer_type_map[i].type; + } + } + + return BUFFER_TYPE_NONE; +} + +/** + * @brief Safely copy string with length limit + */ +static void safe_strcpy(char *dest, const char *src, size_t max_len) +{ + if (src == NULL) { + dest[0] = '\0'; + return; + } + strncpy(dest, src, max_len - 1); + dest[max_len - 1] = '\0'; +} + +/** + * @brief Get string value from JSON object + */ +static const char *get_string(const cJSON *obj, const char *key, const char *default_val) +{ + const cJSON *item = cJSON_GetObjectItemCaseSensitive(obj, key); + if (cJSON_IsString(item) && item->valuestring != NULL) { + return item->valuestring; + } + return default_val; +} + +/** + * @brief Get integer value from JSON object + */ +static int get_int(const cJSON *obj, const char *key, int default_val) +{ + const cJSON *item = cJSON_GetObjectItemCaseSensitive(obj, key); + if (cJSON_IsNumber(item)) { + return item->valueint; + } + return default_val; +} + +/** + * @brief Get boolean value from JSON object + */ +static bool get_bool(const cJSON *obj, const char *key, bool default_val) +{ + const cJSON *item = cJSON_GetObjectItemCaseSensitive(obj, key); + if (cJSON_IsBool(item)) { + return cJSON_IsTrue(item); + } + return default_val; +} + +/** + * @brief Parse buffer mapping from JSON object + */ +static void parse_buffer_mapping(const cJSON *obj, s7comm_buffer_mapping_t *mapping) +{ + if (obj == NULL || mapping == NULL) { + return; + } + + const char *type_str = get_string(obj, "type", NULL); + mapping->type = parse_buffer_type(type_str); + mapping->start_buffer = get_int(obj, "start_buffer", 0); + mapping->bit_addressing = get_bool(obj, "bit_addressing", false); +} + +/** + * @brief Parse server section from JSON + */ +static void parse_server_section(const cJSON *server, s7comm_config_t *config) +{ + if (server == NULL) { + return; + } + + config->enabled = get_bool(server, "enabled", true); + safe_strcpy(config->bind_address, get_string(server, "bind_address", "0.0.0.0"), + S7COMM_MAX_STRING_LEN); + config->port = (uint16_t)get_int(server, "port", S7COMM_DEFAULT_PORT); + config->max_clients = get_int(server, "max_clients", S7COMM_DEFAULT_MAX_CLIENTS); + config->work_interval_ms = get_int(server, "work_interval_ms", S7COMM_DEFAULT_WORK_INTERVAL); + config->send_timeout_ms = get_int(server, "send_timeout_ms", S7COMM_DEFAULT_SEND_TIMEOUT); + config->recv_timeout_ms = get_int(server, "recv_timeout_ms", S7COMM_DEFAULT_RECV_TIMEOUT); + config->ping_timeout_ms = get_int(server, "ping_timeout_ms", S7COMM_DEFAULT_PING_TIMEOUT); + config->pdu_size = get_int(server, "pdu_size", S7COMM_DEFAULT_PDU_SIZE); +} + +/** + * @brief Parse PLC identity section from JSON + */ +static void parse_identity_section(const cJSON *identity, s7comm_plc_identity_t *id) +{ + if (identity == NULL) { + return; + } + + safe_strcpy(id->name, get_string(identity, "name", "OpenPLC Runtime"), + S7COMM_MAX_STRING_LEN); + safe_strcpy(id->module_type, get_string(identity, "module_type", "CPU 315-2 PN/DP"), + S7COMM_MAX_STRING_LEN); + safe_strcpy(id->serial_number, get_string(identity, "serial_number", "S C-XXXXXXXXX"), + S7COMM_MAX_STRING_LEN); + safe_strcpy(id->copyright, get_string(identity, "copyright", "OpenPLC Project"), + S7COMM_MAX_STRING_LEN); + safe_strcpy(id->module_name, get_string(identity, "module_name", "OpenPLC"), + S7COMM_MAX_STRING_LEN); +} + +/** + * @brief Parse a single data block from JSON + */ +static int parse_data_block(const cJSON *db_json, s7comm_data_block_t *db) +{ + if (db_json == NULL || db == NULL) { + return S7COMM_CONFIG_ERR_INVALID; + } + + db->db_number = get_int(db_json, "db_number", 0); + if (db->db_number <= 0 || db->db_number > 65535) { + return S7COMM_CONFIG_ERR_INVALID; + } + + safe_strcpy(db->description, get_string(db_json, "description", ""), + S7COMM_MAX_DESCRIPTION_LEN); + db->size_bytes = get_int(db_json, "size_bytes", 0); + if (db->size_bytes <= 0) { + return S7COMM_CONFIG_ERR_INVALID; + } + + const cJSON *mapping = cJSON_GetObjectItemCaseSensitive(db_json, "mapping"); + if (mapping != NULL) { + parse_buffer_mapping(mapping, &db->mapping); + } + + return S7COMM_CONFIG_OK; +} + +/** + * @brief Parse data_blocks array from JSON + */ +static int parse_data_blocks_section(const cJSON *data_blocks, s7comm_config_t *config) +{ + if (data_blocks == NULL || !cJSON_IsArray(data_blocks)) { + config->num_data_blocks = 0; + return S7COMM_CONFIG_OK; + } + + int count = cJSON_GetArraySize(data_blocks); + if (count > S7COMM_MAX_DATA_BLOCKS) { + count = S7COMM_MAX_DATA_BLOCKS; + } + + config->num_data_blocks = 0; + const cJSON *db_json; + cJSON_ArrayForEach(db_json, data_blocks) { + if (config->num_data_blocks >= S7COMM_MAX_DATA_BLOCKS) { + break; + } + + int result = parse_data_block(db_json, &config->data_blocks[config->num_data_blocks]); + if (result == S7COMM_CONFIG_OK) { + config->num_data_blocks++; + } + } + + return S7COMM_CONFIG_OK; +} + +/** + * @brief Parse a system area (PE, PA, MK) from JSON + */ +static void parse_system_area(const cJSON *area_json, s7comm_system_area_t *area) +{ + if (area_json == NULL) { + area->enabled = false; + return; + } + + area->enabled = get_bool(area_json, "enabled", false); + area->size_bytes = get_int(area_json, "size_bytes", 128); + + const cJSON *mapping = cJSON_GetObjectItemCaseSensitive(area_json, "mapping"); + if (mapping != NULL) { + parse_buffer_mapping(mapping, &area->mapping); + } +} + +/** + * @brief Parse system_areas section from JSON + */ +static void parse_system_areas_section(const cJSON *system_areas, s7comm_config_t *config) +{ + if (system_areas == NULL) { + return; + } + + parse_system_area(cJSON_GetObjectItemCaseSensitive(system_areas, "pe_area"), + &config->pe_area); + parse_system_area(cJSON_GetObjectItemCaseSensitive(system_areas, "pa_area"), + &config->pa_area); + parse_system_area(cJSON_GetObjectItemCaseSensitive(system_areas, "mk_area"), + &config->mk_area); +} + +/** + * @brief Parse logging section from JSON + */ +static void parse_logging_section(const cJSON *logging, s7comm_logging_t *log_config) +{ + if (logging == NULL) { + return; + } + + log_config->log_connections = get_bool(logging, "log_connections", true); + log_config->log_data_access = get_bool(logging, "log_data_access", false); + log_config->log_errors = get_bool(logging, "log_errors", true); +} + +void s7comm_config_init_defaults(s7comm_config_t *config) +{ + if (config == NULL) { + return; + } + + memset(config, 0, sizeof(s7comm_config_t)); + + /* Server defaults */ + config->enabled = true; + safe_strcpy(config->bind_address, "0.0.0.0", S7COMM_MAX_STRING_LEN); + config->port = S7COMM_DEFAULT_PORT; + config->max_clients = S7COMM_DEFAULT_MAX_CLIENTS; + config->work_interval_ms = S7COMM_DEFAULT_WORK_INTERVAL; + config->send_timeout_ms = S7COMM_DEFAULT_SEND_TIMEOUT; + config->recv_timeout_ms = S7COMM_DEFAULT_RECV_TIMEOUT; + config->ping_timeout_ms = S7COMM_DEFAULT_PING_TIMEOUT; + config->pdu_size = S7COMM_DEFAULT_PDU_SIZE; + + /* Identity defaults */ + safe_strcpy(config->identity.name, "OpenPLC Runtime", S7COMM_MAX_STRING_LEN); + safe_strcpy(config->identity.module_type, "CPU 315-2 PN/DP", S7COMM_MAX_STRING_LEN); + safe_strcpy(config->identity.serial_number, "S C-XXXXXXXXX", S7COMM_MAX_STRING_LEN); + safe_strcpy(config->identity.copyright, "OpenPLC Project", S7COMM_MAX_STRING_LEN); + safe_strcpy(config->identity.module_name, "OpenPLC", S7COMM_MAX_STRING_LEN); + + /* Logging defaults */ + config->logging.log_connections = true; + config->logging.log_data_access = false; + config->logging.log_errors = true; +} + +int s7comm_config_parse(const char *config_path, s7comm_config_t *config) +{ + if (config_path == NULL || config == NULL) { + return S7COMM_CONFIG_ERR_INVALID; + } + + /* Initialize with defaults */ + s7comm_config_init_defaults(config); + + /* Read file contents */ + char *json_str = read_file(config_path); + if (json_str == NULL) { + return S7COMM_CONFIG_ERR_FILE; + } + + /* Parse JSON */ + cJSON *root = cJSON_Parse(json_str); + free(json_str); + + if (root == NULL) { + return S7COMM_CONFIG_ERR_PARSE; + } + + /* Parse each section */ + parse_server_section(cJSON_GetObjectItemCaseSensitive(root, "server"), config); + parse_identity_section(cJSON_GetObjectItemCaseSensitive(root, "plc_identity"), &config->identity); + parse_data_blocks_section(cJSON_GetObjectItemCaseSensitive(root, "data_blocks"), config); + parse_system_areas_section(cJSON_GetObjectItemCaseSensitive(root, "system_areas"), config); + parse_logging_section(cJSON_GetObjectItemCaseSensitive(root, "logging"), &config->logging); + + cJSON_Delete(root); + + /* Validate the parsed configuration */ + return s7comm_config_validate(config); +} + +int s7comm_config_validate(const s7comm_config_t *config) +{ + if (config == NULL) { + return S7COMM_CONFIG_ERR_INVALID; + } + + /* Validate port */ + if (config->port == 0) { + return S7COMM_CONFIG_ERR_INVALID; + } + + /* Validate timeouts */ + if (config->send_timeout_ms < 100 || config->recv_timeout_ms < 100) { + return S7COMM_CONFIG_ERR_INVALID; + } + + /* Validate PDU size (S7 spec: 240-960) */ + if (config->pdu_size < 240 || config->pdu_size > 960) { + return S7COMM_CONFIG_ERR_INVALID; + } + + /* Validate max_clients */ + if (config->max_clients < 1 || config->max_clients > 1024) { + return S7COMM_CONFIG_ERR_INVALID; + } + + /* Check for duplicate DB numbers */ + for (int i = 0; i < config->num_data_blocks; i++) { + for (int j = i + 1; j < config->num_data_blocks; j++) { + if (config->data_blocks[i].db_number == config->data_blocks[j].db_number) { + return S7COMM_CONFIG_ERR_INVALID; + } + } + } + + /* Validate data block mappings */ + for (int i = 0; i < config->num_data_blocks; i++) { + const s7comm_data_block_t *db = &config->data_blocks[i]; + + if (db->size_bytes <= 0 || db->size_bytes > 65535) { + return S7COMM_CONFIG_ERR_INVALID; + } + + if (db->mapping.type == BUFFER_TYPE_NONE) { + return S7COMM_CONFIG_ERR_INVALID; + } + + if (db->mapping.start_buffer < 0) { + return S7COMM_CONFIG_ERR_INVALID; + } + } + + return S7COMM_CONFIG_OK; +} + +const char *s7comm_buffer_type_name(s7comm_buffer_type_t type) +{ + for (int i = 0; buffer_type_map[i].name != NULL; i++) { + if (buffer_type_map[i].type == type) { + return buffer_type_map[i].name; + } + } + return "none"; +} + +int s7comm_buffer_type_size(s7comm_buffer_type_t type) +{ + switch (type) { + case BUFFER_TYPE_BOOL_INPUT: + case BUFFER_TYPE_BOOL_OUTPUT: + case BUFFER_TYPE_BOOL_MEMORY: + case BUFFER_TYPE_BYTE_INPUT: + case BUFFER_TYPE_BYTE_OUTPUT: + return 1; + + case BUFFER_TYPE_INT_INPUT: + case BUFFER_TYPE_INT_OUTPUT: + case BUFFER_TYPE_INT_MEMORY: + return 2; + + case BUFFER_TYPE_DINT_INPUT: + case BUFFER_TYPE_DINT_OUTPUT: + case BUFFER_TYPE_DINT_MEMORY: + return 4; + + case BUFFER_TYPE_LINT_INPUT: + case BUFFER_TYPE_LINT_OUTPUT: + case BUFFER_TYPE_LINT_MEMORY: + return 8; + + default: + return 0; + } +} diff --git a/core/src/drivers/plugins/native/s7comm/s7comm_config.h b/core/src/drivers/plugins/native/s7comm/s7comm_config.h new file mode 100644 index 00000000..66d5b1a3 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/s7comm_config.h @@ -0,0 +1,177 @@ +/** + * @file s7comm_config.h + * @brief S7Comm Plugin Configuration Structures and Parser + * + * Defines data structures for JSON configuration and functions + * for parsing, validating, and managing configuration. + */ + +#ifndef S7COMM_CONFIG_H +#define S7COMM_CONFIG_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Configuration limits */ +#define S7COMM_MAX_DATA_BLOCKS 64 +#define S7COMM_MAX_STRING_LEN 64 +#define S7COMM_MAX_DESCRIPTION_LEN 128 + +/* Default values */ +#define S7COMM_DEFAULT_PORT 102 +#define S7COMM_DEFAULT_MAX_CLIENTS 32 +#define S7COMM_DEFAULT_WORK_INTERVAL 100 +#define S7COMM_DEFAULT_SEND_TIMEOUT 3000 +#define S7COMM_DEFAULT_RECV_TIMEOUT 3000 +#define S7COMM_DEFAULT_PING_TIMEOUT 10000 +#define S7COMM_DEFAULT_PDU_SIZE 480 + +/** + * @brief Buffer type enumeration for mapping S7 areas to OpenPLC buffers + */ +typedef enum { + BUFFER_TYPE_NONE = 0, + BUFFER_TYPE_BOOL_INPUT, + BUFFER_TYPE_BOOL_OUTPUT, + BUFFER_TYPE_BOOL_MEMORY, + BUFFER_TYPE_BYTE_INPUT, + BUFFER_TYPE_BYTE_OUTPUT, + BUFFER_TYPE_INT_INPUT, + BUFFER_TYPE_INT_OUTPUT, + BUFFER_TYPE_INT_MEMORY, + BUFFER_TYPE_DINT_INPUT, + BUFFER_TYPE_DINT_OUTPUT, + BUFFER_TYPE_DINT_MEMORY, + BUFFER_TYPE_LINT_INPUT, + BUFFER_TYPE_LINT_OUTPUT, + BUFFER_TYPE_LINT_MEMORY +} s7comm_buffer_type_t; + +/** + * @brief Buffer mapping configuration + */ +typedef struct { + s7comm_buffer_type_t type; /* OpenPLC buffer type */ + int start_buffer; /* Starting buffer index */ + bool bit_addressing; /* Enable bit-level access */ +} s7comm_buffer_mapping_t; + +/** + * @brief Data block configuration + */ +typedef struct { + int db_number; /* S7 DB number (1-65535) */ + char description[S7COMM_MAX_DESCRIPTION_LEN]; /* Human-readable description */ + int size_bytes; /* DB size in bytes */ + s7comm_buffer_mapping_t mapping; /* Buffer mapping */ +} s7comm_data_block_t; + +/** + * @brief System area configuration (PE, PA, MK) + */ +typedef struct { + bool enabled; /* Enable this area */ + int size_bytes; /* Area size in bytes */ + s7comm_buffer_mapping_t mapping; /* Buffer mapping */ +} s7comm_system_area_t; + +/** + * @brief PLC identity configuration for SZL responses + */ +typedef struct { + char name[S7COMM_MAX_STRING_LEN]; /* PLC name */ + char module_type[S7COMM_MAX_STRING_LEN]; /* CPU type string */ + char serial_number[S7COMM_MAX_STRING_LEN]; /* Serial number */ + char copyright[S7COMM_MAX_STRING_LEN]; /* Copyright string */ + char module_name[S7COMM_MAX_STRING_LEN]; /* Module name */ +} s7comm_plc_identity_t; + +/** + * @brief Logging configuration + */ +typedef struct { + bool log_connections; /* Log client connect/disconnect */ + bool log_data_access; /* Log read/write operations */ + bool log_errors; /* Log errors and warnings */ +} s7comm_logging_t; + +/** + * @brief Complete S7Comm configuration + */ +typedef struct { + /* Server settings */ + bool enabled; /* Enable/disable the S7 server */ + char bind_address[S7COMM_MAX_STRING_LEN]; /* Network interface to bind */ + uint16_t port; /* S7Comm TCP port */ + int max_clients; /* Maximum simultaneous connections */ + int work_interval_ms; /* Worker thread polling interval */ + int send_timeout_ms; /* Socket send timeout */ + int recv_timeout_ms; /* Socket receive timeout */ + int ping_timeout_ms; /* Keep-alive timeout */ + int pdu_size; /* Maximum PDU size */ + + /* PLC identity */ + s7comm_plc_identity_t identity; + + /* Data blocks */ + int num_data_blocks; + s7comm_data_block_t data_blocks[S7COMM_MAX_DATA_BLOCKS]; + + /* System areas */ + s7comm_system_area_t pe_area; /* Process inputs (I area) */ + s7comm_system_area_t pa_area; /* Process outputs (Q area) */ + s7comm_system_area_t mk_area; /* Markers (M area) */ + + /* Logging */ + s7comm_logging_t logging; +} s7comm_config_t; + +/** + * @brief Parse configuration from JSON file + * + * @param config_path Path to the JSON configuration file + * @param config Pointer to configuration structure to populate + * @return 0 on success, negative error code on failure + */ +int s7comm_config_parse(const char *config_path, s7comm_config_t *config); + +/** + * @brief Validate configuration values + * + * @param config Pointer to configuration structure to validate + * @return 0 if valid, negative error code indicating the issue + */ +int s7comm_config_validate(const s7comm_config_t *config); + +/** + * @brief Initialize configuration with default values + * + * @param config Pointer to configuration structure to initialize + */ +void s7comm_config_init_defaults(s7comm_config_t *config); + +/** + * @brief Get human-readable name for a buffer type + * + * @param type Buffer type enumeration value + * @return String name of the buffer type + */ +const char *s7comm_buffer_type_name(s7comm_buffer_type_t type); + +/** + * @brief Get element size in bytes for a buffer type + * + * @param type Buffer type enumeration value + * @return Size in bytes (1, 2, 4, or 8), or 0 for invalid type + */ +int s7comm_buffer_type_size(s7comm_buffer_type_t type); + +#ifdef __cplusplus +} +#endif + +#endif /* S7COMM_CONFIG_H */ diff --git a/core/src/drivers/plugins/native/s7comm/s7comm_config.json b/core/src/drivers/plugins/native/s7comm/s7comm_config.json new file mode 100644 index 00000000..faac7d8e --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/s7comm_config.json @@ -0,0 +1,133 @@ +{ + "server": { + "enabled": true, + "bind_address": "0.0.0.0", + "port": 102, + "max_clients": 32, + "work_interval_ms": 100, + "send_timeout_ms": 3000, + "recv_timeout_ms": 3000, + "ping_timeout_ms": 10000, + "pdu_size": 480 + }, + "plc_identity": { + "name": "OpenPLC Runtime", + "module_type": "CPU 315-2 PN/DP", + "serial_number": "S C-OPENPLC01", + "copyright": "OpenPLC Project", + "module_name": "OpenPLC" + }, + "data_blocks": [ + { + "db_number": 1, + "description": "Digital Inputs (%IX)", + "size_bytes": 128, + "mapping": { + "type": "bool_input", + "start_buffer": 0, + "bit_addressing": true + } + }, + { + "db_number": 2, + "description": "Digital Outputs (%QX)", + "size_bytes": 128, + "mapping": { + "type": "bool_output", + "start_buffer": 0, + "bit_addressing": true + } + }, + { + "db_number": 3, + "description": "Digital Memory (%MX)", + "size_bytes": 128, + "mapping": { + "type": "bool_memory", + "start_buffer": 0, + "bit_addressing": true + } + }, + { + "db_number": 10, + "description": "Analog Inputs (%IW)", + "size_bytes": 2048, + "mapping": { + "type": "int_input", + "start_buffer": 0, + "bit_addressing": false + } + }, + { + "db_number": 20, + "description": "Analog Outputs (%QW)", + "size_bytes": 2048, + "mapping": { + "type": "int_output", + "start_buffer": 0, + "bit_addressing": false + } + }, + { + "db_number": 100, + "description": "Memory Words (%MW)", + "size_bytes": 2048, + "mapping": { + "type": "int_memory", + "start_buffer": 0, + "bit_addressing": false + } + }, + { + "db_number": 200, + "description": "Memory DWords (%MD)", + "size_bytes": 4096, + "mapping": { + "type": "dint_memory", + "start_buffer": 0, + "bit_addressing": false + } + }, + { + "db_number": 300, + "description": "Memory LWords (%ML)", + "size_bytes": 8192, + "mapping": { + "type": "lint_memory", + "start_buffer": 0, + "bit_addressing": false + } + } + ], + "system_areas": { + "pe_area": { + "enabled": true, + "size_bytes": 128, + "mapping": { + "type": "bool_input", + "start_buffer": 0 + } + }, + "pa_area": { + "enabled": true, + "size_bytes": 128, + "mapping": { + "type": "bool_output", + "start_buffer": 0 + } + }, + "mk_area": { + "enabled": true, + "size_bytes": 256, + "mapping": { + "type": "bool_memory", + "start_buffer": 0 + } + } + }, + "logging": { + "log_connections": true, + "log_data_access": false, + "log_errors": true + } +} diff --git a/core/src/drivers/plugins/native/s7comm/s7comm_plugin.cpp b/core/src/drivers/plugins/native/s7comm/s7comm_plugin.cpp new file mode 100644 index 00000000..1eac758c --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/s7comm_plugin.cpp @@ -0,0 +1,1098 @@ +/** + * @file s7comm_plugin.cpp + * @brief S7Comm Plugin Implementation for OpenPLC Runtime v4 + * + * This plugin implements a Siemens S7 communication server using the Snap7 library. + * It allows S7-compatible HMIs and SCADA systems to read/write OpenPLC I/O buffers. + * + * Buffer Sync Strategy (using journal buffer system): + * - S7 buffers: What Snap7 clients read/write (managed by Snap7 server) + * - Journal writes: Race-condition-free writes to OpenPLC buffers + * + * Data flow (on-demand via Snap7 RWArea callback): + * - S7 client READ: Callback acquires OpenPLC mutex, copies fresh data to S7 buffer + * - S7 client WRITE: Callback uses journal writes (thread-safe, no mutex needed) + * + * This approach allows the S7 server thread to run independently without + * requiring cycle_start/cycle_end hooks for data synchronization. + */ + +#include +#include +#include +#include + +/* Snap7 includes */ +#include "snap7_libmain.h" +#include "s7_types.h" +#include "s7_server.h" /* For OperationRead, OperationWrite, TS7Tag */ + +/* Plugin includes */ +extern "C" { +#include "plugin_logger.h" +#include "plugin_types.h" +#include "s7comm_plugin.h" +#include "s7comm_config.h" +} + +/* + * ============================================================================= + * Constants + * ============================================================================= + */ +#define S7COMM_MAX_DB_SIZE 65536 /* Maximum size for a single DB buffer */ + +/* + * ============================================================================= + * Data Block Runtime Structure + * ============================================================================= + */ +typedef struct { + int db_number; /* S7 DB number */ + s7comm_buffer_type_t type; /* Mapping type */ + int start_buffer; /* Starting buffer index */ + int size_bytes; /* Size in bytes */ + bool bit_addressing; /* Bit-level access enabled */ + uint8_t *s7_buffer; /* S7 buffer (registered with Snap7) */ +} s7comm_db_runtime_t; + +/* + * ============================================================================= + * System Area Runtime Structure + * ============================================================================= + */ +typedef struct { + bool enabled; + int size_bytes; + s7comm_buffer_type_t type; + int start_buffer; + uint8_t *s7_buffer; /* S7 buffer (registered with Snap7) */ +} s7comm_area_runtime_t; + +/* + * ============================================================================= + * Plugin State + * ============================================================================= + */ +static plugin_logger_t g_logger; +static plugin_runtime_args_t g_runtime_args; +static s7comm_config_t g_config; +static bool g_initialized = false; +static bool g_running = false; +static bool g_config_loaded = false; + +/* Snap7 server handle (S7Object is uintptr_t, use 0 for null) */ +static S7Object g_server = 0; + +/* No S7 buffer mutex needed - reads use OpenPLC mutex, writes use journal */ + +/* Runtime data blocks (dynamically allocated based on config) */ +static s7comm_db_runtime_t g_db_runtime[S7COMM_MAX_DATA_BLOCKS]; +static int g_num_db_runtime = 0; + +/* System area runtime */ +static s7comm_area_runtime_t g_pe_runtime; +static s7comm_area_runtime_t g_pa_runtime; +static s7comm_area_runtime_t g_mk_runtime; + +/* + * ============================================================================= + * Forward Declarations + * ============================================================================= + */ +static void s7comm_event_callback(void *usrPtr, PSrvEvent PEvent, int Size); +static int s7comm_rw_area_callback(void *usrPtr, int Sender, int Operation, PS7Tag PTag, void *pUsrData); +static int allocate_buffers(void); +static void free_buffers(void); +static int register_all_areas(void); +static void read_openplc_to_buffer(uint8_t *dest, int size, s7comm_buffer_type_t type, int start_buffer); +static void write_buffer_to_openplc_journal(uint8_t *src, int size, s7comm_buffer_type_t type, int start_buffer); +static s7comm_db_runtime_t* find_db_runtime(int db_number); +static s7comm_area_runtime_t* find_area_runtime(int area); +static int get_type_size(s7comm_buffer_type_t type); + +/* + * ============================================================================= + * Endianness Conversion Helpers + * S7 protocol uses big-endian (network byte order) + * ============================================================================= + */ +static inline uint16_t swap16(uint16_t val) +{ + return ((val & 0xFF00) >> 8) | ((val & 0x00FF) << 8); +} + +static inline uint32_t swap32(uint32_t val) +{ + return ((val & 0xFF000000) >> 24) | + ((val & 0x00FF0000) >> 8) | + ((val & 0x0000FF00) << 8) | + ((val & 0x000000FF) << 24); +} + +static inline uint64_t swap64(uint64_t val) +{ + return ((val & 0xFF00000000000000ULL) >> 56) | + ((val & 0x00FF000000000000ULL) >> 40) | + ((val & 0x0000FF0000000000ULL) >> 24) | + ((val & 0x000000FF00000000ULL) >> 8) | + ((val & 0x00000000FF000000ULL) << 8) | + ((val & 0x0000000000FF0000ULL) << 24) | + ((val & 0x000000000000FF00ULL) << 40) | + ((val & 0x00000000000000FFULL) << 56); +} + +/* + * ============================================================================= + * Memory Management + * ============================================================================= + */ + +/** + * @brief Allocate a system area buffer + */ +static int allocate_area(s7comm_area_runtime_t *area, const s7comm_system_area_t *config) +{ + memset(area, 0, sizeof(s7comm_area_runtime_t)); + + if (!config->enabled || config->size_bytes <= 0) { + area->enabled = false; + return 0; + } + + area->enabled = true; + area->size_bytes = config->size_bytes; + area->type = config->mapping.type; + area->start_buffer = config->mapping.start_buffer; + + /* Allocate S7 buffer (what Snap7 clients see) */ + area->s7_buffer = (uint8_t *)calloc(1, config->size_bytes); + if (area->s7_buffer == NULL) { + return -1; + } + + return 0; +} + +/** + * @brief Free a system area's buffer + */ +static void free_area(s7comm_area_runtime_t *area) +{ + if (area->s7_buffer != NULL) { + free(area->s7_buffer); + area->s7_buffer = NULL; + } + area->enabled = false; +} + +/** + * @brief Allocate all S7 buffers based on configuration + */ +static int allocate_buffers(void) +{ + g_num_db_runtime = 0; + + /* Allocate system areas */ + if (allocate_area(&g_pe_runtime, &g_config.pe_area) != 0) { + plugin_logger_error(&g_logger, "Failed to allocate PE area buffer"); + return -1; + } + + if (allocate_area(&g_pa_runtime, &g_config.pa_area) != 0) { + plugin_logger_error(&g_logger, "Failed to allocate PA area buffer"); + return -1; + } + + if (allocate_area(&g_mk_runtime, &g_config.mk_area) != 0) { + plugin_logger_error(&g_logger, "Failed to allocate MK area buffer"); + return -1; + } + + /* Allocate data blocks */ + for (int i = 0; i < g_config.num_data_blocks; i++) { + const s7comm_data_block_t *db_cfg = &g_config.data_blocks[i]; + + if (db_cfg->size_bytes <= 0 || db_cfg->size_bytes > S7COMM_MAX_DB_SIZE) { + plugin_logger_warn(&g_logger, "DB%d: invalid size %d, skipping", + db_cfg->db_number, db_cfg->size_bytes); + continue; + } + + s7comm_db_runtime_t *db_rt = &g_db_runtime[g_num_db_runtime]; + db_rt->db_number = db_cfg->db_number; + db_rt->type = db_cfg->mapping.type; + db_rt->start_buffer = db_cfg->mapping.start_buffer; + db_rt->size_bytes = db_cfg->size_bytes; + db_rt->bit_addressing = db_cfg->mapping.bit_addressing; + + /* Allocate S7 buffer */ + db_rt->s7_buffer = (uint8_t *)calloc(1, db_cfg->size_bytes); + if (db_rt->s7_buffer == NULL) { + plugin_logger_error(&g_logger, "Failed to allocate DB%d S7 buffer", db_cfg->db_number); + return -1; + } + + g_num_db_runtime++; + plugin_logger_debug(&g_logger, "Allocated DB%d: %d bytes, type=%s", + db_cfg->db_number, db_cfg->size_bytes, + s7comm_buffer_type_name(db_cfg->mapping.type)); + } + + return 0; +} + +/** + * @brief Free all allocated buffers + */ +static void free_buffers(void) +{ + /* Free system areas */ + free_area(&g_pe_runtime); + free_area(&g_pa_runtime); + free_area(&g_mk_runtime); + + /* Free data blocks */ + for (int i = 0; i < g_num_db_runtime; i++) { + if (g_db_runtime[i].s7_buffer != NULL) { + free(g_db_runtime[i].s7_buffer); + g_db_runtime[i].s7_buffer = NULL; + } + } + g_num_db_runtime = 0; +} + +/** + * @brief Register all S7 areas with the Snap7 server + */ +static int register_all_areas(void) +{ + int result; + + /* Register system areas (using S7 buffers, not shadow) */ + if (g_pe_runtime.enabled && g_pe_runtime.s7_buffer != NULL) { + result = Srv_RegisterArea(g_server, srvAreaPE, 0, g_pe_runtime.s7_buffer, g_pe_runtime.size_bytes); + if (result != 0) { + plugin_logger_warn(&g_logger, "Failed to register PE area: 0x%08X", result); + } else { + plugin_logger_debug(&g_logger, "Registered PE area: %d bytes", g_pe_runtime.size_bytes); + } + } + + if (g_pa_runtime.enabled && g_pa_runtime.s7_buffer != NULL) { + result = Srv_RegisterArea(g_server, srvAreaPA, 0, g_pa_runtime.s7_buffer, g_pa_runtime.size_bytes); + if (result != 0) { + plugin_logger_warn(&g_logger, "Failed to register PA area: 0x%08X", result); + } else { + plugin_logger_debug(&g_logger, "Registered PA area: %d bytes", g_pa_runtime.size_bytes); + } + } + + if (g_mk_runtime.enabled && g_mk_runtime.s7_buffer != NULL) { + result = Srv_RegisterArea(g_server, srvAreaMK, 0, g_mk_runtime.s7_buffer, g_mk_runtime.size_bytes); + if (result != 0) { + plugin_logger_warn(&g_logger, "Failed to register MK area: 0x%08X", result); + } else { + plugin_logger_debug(&g_logger, "Registered MK area: %d bytes", g_mk_runtime.size_bytes); + } + } + + /* Register data blocks (using S7 buffers) */ + for (int i = 0; i < g_num_db_runtime; i++) { + s7comm_db_runtime_t *db = &g_db_runtime[i]; + result = Srv_RegisterArea(g_server, srvAreaDB, db->db_number, db->s7_buffer, db->size_bytes); + if (result != 0) { + plugin_logger_warn(&g_logger, "Failed to register DB%d: 0x%08X", db->db_number, result); + } else { + plugin_logger_debug(&g_logger, "Registered DB%d: %d bytes", db->db_number, db->size_bytes); + } + } + + return 0; +} + +/* + * ============================================================================= + * Plugin Lifecycle Functions + * ============================================================================= + */ + +/** + * @brief Initialize the S7Comm plugin + */ +extern "C" int init(void *args) +{ + /* Initialize logger first (before we have runtime_args) */ + plugin_logger_init(&g_logger, "S7COMM", NULL); + plugin_logger_info(&g_logger, "Initializing S7Comm plugin (journal-buffered)..."); + + if (!args) { + plugin_logger_error(&g_logger, "init args is NULL"); + return -1; + } + + /* Copy runtime args (critical - pointer is freed after init returns) */ + memcpy(&g_runtime_args, args, sizeof(plugin_runtime_args_t)); + + /* Re-initialize logger with runtime_args for central logging */ + plugin_logger_init(&g_logger, "S7COMM", args); + + plugin_logger_info(&g_logger, "Buffer size: %d", g_runtime_args.buffer_size); + + /* Parse configuration file */ + const char *config_path = g_runtime_args.plugin_specific_config_file_path; + if (config_path == NULL || config_path[0] == '\0') { + plugin_logger_warn(&g_logger, "No config file specified, using defaults"); + s7comm_config_init_defaults(&g_config); + } else { + plugin_logger_info(&g_logger, "Loading config: %s", config_path); + int result = s7comm_config_parse(config_path, &g_config); + if (result != 0) { + plugin_logger_error(&g_logger, "Failed to parse config file (error %d)", result); + plugin_logger_warn(&g_logger, "Using default configuration"); + s7comm_config_init_defaults(&g_config); + } else { + plugin_logger_info(&g_logger, "Configuration loaded successfully"); + g_config_loaded = true; + } + } + + /* Check if server is enabled */ + if (!g_config.enabled) { + plugin_logger_info(&g_logger, "S7Comm server is disabled in configuration"); + g_initialized = true; + return 0; + } + + /* Log configuration summary */ + plugin_logger_info(&g_logger, "Server config: port=%d, max_clients=%d, pdu_size=%d", + g_config.port, g_config.max_clients, g_config.pdu_size); + plugin_logger_info(&g_logger, "PLC identity: %s (%s)", g_config.identity.name, g_config.identity.module_type); + plugin_logger_info(&g_logger, "Data blocks configured: %d", g_config.num_data_blocks); + + /* Allocate S7 buffers */ + if (allocate_buffers() != 0) { + plugin_logger_error(&g_logger, "Failed to allocate buffers"); + free_buffers(); + return -1; + } + + /* Create Snap7 server */ + g_server = Srv_Create(); + if (g_server == 0) { + plugin_logger_error(&g_logger, "Failed to create Snap7 server"); + free_buffers(); + return -1; + } + + /* Configure server parameters from config */ + uint16_t port = g_config.port; + int max_clients = g_config.max_clients; + int work_interval = g_config.work_interval_ms; + int send_timeout = g_config.send_timeout_ms; + int recv_timeout = g_config.recv_timeout_ms; + int ping_timeout = g_config.ping_timeout_ms; + int pdu_size = g_config.pdu_size; + + Srv_SetParam(g_server, p_u16_LocalPort, &port); + Srv_SetParam(g_server, p_i32_MaxClients, &max_clients); + Srv_SetParam(g_server, p_i32_WorkInterval, &work_interval); + Srv_SetParam(g_server, p_i32_SendTimeout, &send_timeout); + Srv_SetParam(g_server, p_i32_RecvTimeout, &recv_timeout); + Srv_SetParam(g_server, p_i32_PingTimeout, &ping_timeout); + Srv_SetParam(g_server, p_i32_PDURequest, &pdu_size); + + /* Set event mask based on logging configuration */ + longword event_mask = 0; + if (g_config.logging.log_connections) { + event_mask |= evcServerStarted | evcServerStopped | + evcClientAdded | evcClientDisconnected | evcClientRejected; + } + if (g_config.logging.log_errors) { + event_mask |= evcListenerCannotStart | evcClientException; + } + if (g_config.logging.log_data_access) { + event_mask |= evcDataRead | evcDataWrite; + } + Srv_SetMask(g_server, mkEvent, event_mask); + + /* Set event callback for logging */ + Srv_SetEventsCallback(g_server, s7comm_event_callback, NULL); + + /* Set RWArea callback for on-demand data synchronization */ + Srv_SetRWAreaCallback(g_server, s7comm_rw_area_callback, NULL); + + /* Register all S7 areas with the server */ + register_all_areas(); + + g_initialized = true; + plugin_logger_info(&g_logger, "S7Comm plugin initialized successfully (journal-buffered mode)"); + + /* Log registered areas summary */ + if (g_pe_runtime.enabled) { + plugin_logger_info(&g_logger, "PE area: %d bytes -> %s[%d]", + g_pe_runtime.size_bytes, + s7comm_buffer_type_name(g_pe_runtime.type), + g_pe_runtime.start_buffer); + } + if (g_pa_runtime.enabled) { + plugin_logger_info(&g_logger, "PA area: %d bytes -> %s[%d]", + g_pa_runtime.size_bytes, + s7comm_buffer_type_name(g_pa_runtime.type), + g_pa_runtime.start_buffer); + } + if (g_mk_runtime.enabled) { + plugin_logger_info(&g_logger, "MK area: %d bytes -> %s[%d]", + g_mk_runtime.size_bytes, + s7comm_buffer_type_name(g_mk_runtime.type), + g_mk_runtime.start_buffer); + } + for (int i = 0; i < g_num_db_runtime; i++) { + plugin_logger_info(&g_logger, "DB%d: %d bytes -> %s[%d]", + g_db_runtime[i].db_number, + g_db_runtime[i].size_bytes, + s7comm_buffer_type_name(g_db_runtime[i].type), + g_db_runtime[i].start_buffer); + } + + return 0; +} + +/** + * @brief Start the S7 server + */ +extern "C" void start_loop(void) +{ + if (!g_initialized) { + plugin_logger_error(&g_logger, "Cannot start - plugin not initialized"); + return; + } + + if (!g_config.enabled) { + plugin_logger_info(&g_logger, "S7 server disabled in configuration"); + return; + } + + if (g_running) { + plugin_logger_warn(&g_logger, "Server already running"); + return; + } + + plugin_logger_info(&g_logger, "Starting S7 server on %s:%d...", + g_config.bind_address, g_config.port); + + /* Start the server */ + int result; + if (strcmp(g_config.bind_address, "0.0.0.0") == 0) { + result = Srv_Start(g_server); + } else { + result = Srv_StartTo(g_server, g_config.bind_address); + } + + if (result != 0) { + plugin_logger_error(&g_logger, "Failed to start S7 server: 0x%08X", result); + if (g_config.port < 1024) { + plugin_logger_error(&g_logger, "Note: Port %d requires root privileges on Linux", g_config.port); + } + return; + } + + g_running = true; + plugin_logger_info(&g_logger, "S7 server started successfully"); +} + +/** + * @brief Stop the S7 server + */ +extern "C" void stop_loop(void) +{ + if (!g_running) { + plugin_logger_debug(&g_logger, "Server already stopped"); + return; + } + + plugin_logger_info(&g_logger, "Stopping S7 server..."); + + Srv_Stop(g_server); + g_running = false; + + plugin_logger_info(&g_logger, "S7 server stopped"); +} + +/** + * @brief Cleanup plugin resources + */ +extern "C" void cleanup(void) +{ + plugin_logger_info(&g_logger, "Cleaning up S7Comm plugin..."); + + if (g_running) { + stop_loop(); + } + + if (g_server != 0) { + Srv_Destroy(g_server); + g_server = 0; + } + + free_buffers(); + + g_initialized = false; + g_config_loaded = false; + plugin_logger_info(&g_logger, "S7Comm plugin cleanup complete"); +} + +/** + * @brief Called at the start of each PLC scan cycle + * + * With the journal-based approach, data synchronization happens on-demand + * via the RWArea callback. No action needed here. + */ +extern "C" void cycle_start(void) +{ + /* Data sync is handled on-demand via RWArea callback */ +} + +/** + * @brief Called at the end of each PLC scan cycle + * + * With the journal-based approach, data synchronization happens on-demand + * via the RWArea callback. No action needed here. + */ +extern "C" void cycle_end(void) +{ + /* Data sync is handled on-demand via RWArea callback */ +} + +/* + * ============================================================================= + * Snap7 Callbacks + * ============================================================================= + */ + +/** + * @brief Snap7 event callback for logging connections and errors + */ +static void s7comm_event_callback(void *usrPtr, PSrvEvent PEvent, int Size) +{ + (void)usrPtr; + (void)Size; + + switch (PEvent->EvtCode) { + case evcServerStarted: + plugin_logger_info(&g_logger, "S7 server started"); + break; + case evcServerStopped: + plugin_logger_info(&g_logger, "S7 server stopped"); + break; + case evcClientAdded: + if (g_config.logging.log_connections) { + plugin_logger_info(&g_logger, "Client connected (ID: %d)", PEvent->EvtSender); + } + break; + case evcClientDisconnected: + if (g_config.logging.log_connections) { + plugin_logger_info(&g_logger, "Client disconnected (ID: %d)", PEvent->EvtSender); + } + break; + case evcClientRejected: + plugin_logger_warn(&g_logger, "Client rejected (ID: %d)", PEvent->EvtSender); + break; + case evcListenerCannotStart: + plugin_logger_error(&g_logger, "Listener cannot start - port may be in use or requires root"); + break; + case evcClientException: + if (g_config.logging.log_errors) { + plugin_logger_warn(&g_logger, "Client exception (ID: %d)", PEvent->EvtSender); + } + break; + case evcDataRead: + if (g_config.logging.log_data_access) { + plugin_logger_debug(&g_logger, "Data read by client %d", PEvent->EvtSender); + } + break; + case evcDataWrite: + if (g_config.logging.log_data_access) { + plugin_logger_debug(&g_logger, "Data write by client %d", PEvent->EvtSender); + } + break; + default: + /* Ignore other events */ + break; + } +} + +/* + * ============================================================================= + * On-Demand Data Synchronization (via RWArea Callback) + * + * S7 client READs: Acquire OpenPLC mutex, copy to S7 buffer, release mutex + * S7 client WRITEs: Use journal writes (thread-safe, no mutex needed) + * ============================================================================= + */ + +/** + * @brief Map s7comm buffer type to journal buffer type + * + * Journal buffer types (from plugin_types.h): + * 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 + */ +static int map_to_journal_type(s7comm_buffer_type_t type) +{ + switch (type) { + case BUFFER_TYPE_BOOL_INPUT: return 0; + case BUFFER_TYPE_BOOL_OUTPUT: return 1; + case BUFFER_TYPE_BOOL_MEMORY: return 2; + case BUFFER_TYPE_INT_INPUT: return 5; + case BUFFER_TYPE_INT_OUTPUT: return 6; + case BUFFER_TYPE_INT_MEMORY: return 7; + case BUFFER_TYPE_DINT_INPUT: return 8; + case BUFFER_TYPE_DINT_OUTPUT: return 9; + case BUFFER_TYPE_DINT_MEMORY: return 10; + case BUFFER_TYPE_LINT_INPUT: return 11; + case BUFFER_TYPE_LINT_OUTPUT: return 12; + case BUFFER_TYPE_LINT_MEMORY: return 13; + default: return -1; + } +} + +/** + * @brief Find DB runtime structure by DB number + */ +static s7comm_db_runtime_t* find_db_runtime(int db_number) +{ + for (int i = 0; i < g_num_db_runtime; i++) { + if (g_db_runtime[i].db_number == db_number) { + return &g_db_runtime[i]; + } + } + return NULL; +} + +/** + * @brief Find system area runtime structure by S7 area code + */ +static s7comm_area_runtime_t* find_area_runtime(int area) +{ + switch (area) { + case srvAreaPE: + return g_pe_runtime.enabled ? &g_pe_runtime : NULL; + case srvAreaPA: + return g_pa_runtime.enabled ? &g_pa_runtime : NULL; + case srvAreaMK: + return g_mk_runtime.enabled ? &g_mk_runtime : NULL; + default: + return NULL; + } +} + +/* + * ============================================================================= + * Read Functions: OpenPLC -> S7 Buffer (for S7 client READs) + * These functions copy data from OpenPLC image tables to the S7 buffer. + * Called with OpenPLC mutex held. + * ============================================================================= + */ + +/** + * @brief Read OpenPLC bool buffer to destination (mutex must be held) + */ +static void read_openplc_bool_to_buffer(uint8_t *dest, int size, s7comm_buffer_type_t type, int start_buffer) +{ + IEC_BOOL *(*buffer)[8] = NULL; + + switch (type) { + case BUFFER_TYPE_BOOL_INPUT: + buffer = g_runtime_args.bool_input; + break; + case BUFFER_TYPE_BOOL_OUTPUT: + buffer = g_runtime_args.bool_output; + break; + case BUFFER_TYPE_BOOL_MEMORY: + buffer = g_runtime_args.bool_memory; + break; + default: + return; + } + + int max_bytes = g_runtime_args.buffer_size - start_buffer; + if (max_bytes > size) max_bytes = size; + + for (int byte_idx = 0; byte_idx < max_bytes; byte_idx++) { + uint8_t byte_val = 0; + int plc_idx = start_buffer + byte_idx; + for (int bit_idx = 0; bit_idx < 8; bit_idx++) { + IEC_BOOL *ptr = buffer[plc_idx][bit_idx]; + if (ptr != NULL && *ptr) { + byte_val |= (1 << bit_idx); + } + } + dest[byte_idx] = byte_val; + } +} + +/** + * @brief Read OpenPLC int buffer to destination with endian conversion (mutex must be held) + */ +static void read_openplc_int_to_buffer(uint8_t *dest, int size, s7comm_buffer_type_t type, int start_buffer) +{ + IEC_UINT **buffer = NULL; + + switch (type) { + case BUFFER_TYPE_INT_INPUT: + buffer = g_runtime_args.int_input; + break; + case BUFFER_TYPE_INT_OUTPUT: + buffer = g_runtime_args.int_output; + break; + case BUFFER_TYPE_INT_MEMORY: + buffer = g_runtime_args.int_memory; + break; + default: + return; + } + + uint16_t *s7_words = (uint16_t *)dest; + int num_words = size / 2; + int max_words = g_runtime_args.buffer_size - start_buffer; + if (max_words > num_words) max_words = num_words; + + for (int i = 0; i < max_words; i++) { + IEC_UINT *ptr = buffer[start_buffer + i]; + if (ptr != NULL) { + s7_words[i] = swap16(*ptr); + } + } +} + +/** + * @brief Read OpenPLC dint buffer to destination with endian conversion (mutex must be held) + */ +static void read_openplc_dint_to_buffer(uint8_t *dest, int size, s7comm_buffer_type_t type, int start_buffer) +{ + IEC_UDINT **buffer = NULL; + + switch (type) { + case BUFFER_TYPE_DINT_INPUT: + buffer = g_runtime_args.dint_input; + break; + case BUFFER_TYPE_DINT_OUTPUT: + buffer = g_runtime_args.dint_output; + break; + case BUFFER_TYPE_DINT_MEMORY: + buffer = g_runtime_args.dint_memory; + break; + default: + return; + } + + uint32_t *s7_dwords = (uint32_t *)dest; + int num_dwords = size / 4; + int max_dwords = g_runtime_args.buffer_size - start_buffer; + if (max_dwords > num_dwords) max_dwords = num_dwords; + + for (int i = 0; i < max_dwords; i++) { + IEC_UDINT *ptr = buffer[start_buffer + i]; + if (ptr != NULL) { + s7_dwords[i] = swap32(*ptr); + } + } +} + +/** + * @brief Read OpenPLC lint buffer to destination with endian conversion (mutex must be held) + */ +static void read_openplc_lint_to_buffer(uint8_t *dest, int size, s7comm_buffer_type_t type, int start_buffer) +{ + IEC_ULINT **buffer = NULL; + + switch (type) { + case BUFFER_TYPE_LINT_INPUT: + buffer = g_runtime_args.lint_input; + break; + case BUFFER_TYPE_LINT_OUTPUT: + buffer = g_runtime_args.lint_output; + break; + case BUFFER_TYPE_LINT_MEMORY: + buffer = g_runtime_args.lint_memory; + break; + default: + return; + } + + uint64_t *s7_lwords = (uint64_t *)dest; + int num_lwords = size / 8; + int max_lwords = g_runtime_args.buffer_size - start_buffer; + if (max_lwords > num_lwords) max_lwords = num_lwords; + + for (int i = 0; i < max_lwords; i++) { + IEC_ULINT *ptr = buffer[start_buffer + i]; + if (ptr != NULL) { + s7_lwords[i] = swap64(*ptr); + } + } +} + +/** + * @brief Dispatch read from OpenPLC to buffer based on buffer type + */ +static void read_openplc_to_buffer(uint8_t *dest, int size, s7comm_buffer_type_t type, int start_buffer) +{ + switch (type) { + case BUFFER_TYPE_BOOL_INPUT: + case BUFFER_TYPE_BOOL_OUTPUT: + case BUFFER_TYPE_BOOL_MEMORY: + read_openplc_bool_to_buffer(dest, size, type, start_buffer); + break; + + case BUFFER_TYPE_INT_INPUT: + case BUFFER_TYPE_INT_OUTPUT: + case BUFFER_TYPE_INT_MEMORY: + read_openplc_int_to_buffer(dest, size, type, start_buffer); + break; + + case BUFFER_TYPE_DINT_INPUT: + case BUFFER_TYPE_DINT_OUTPUT: + case BUFFER_TYPE_DINT_MEMORY: + read_openplc_dint_to_buffer(dest, size, type, start_buffer); + break; + + case BUFFER_TYPE_LINT_INPUT: + case BUFFER_TYPE_LINT_OUTPUT: + case BUFFER_TYPE_LINT_MEMORY: + read_openplc_lint_to_buffer(dest, size, type, start_buffer); + break; + + default: + break; + } +} + +/* + * ============================================================================= + * Write Functions: S7 Buffer -> OpenPLC via Journal (for S7 client WRITEs) + * These functions write data from S7 buffer to OpenPLC via journal. + * No mutex needed - journal writes are thread-safe. + * ============================================================================= + */ + +/** + * @brief Write bool buffer to OpenPLC via journal + */ +static void write_bool_to_openplc_journal(uint8_t *src, int size, s7comm_buffer_type_t type, int start_buffer) +{ + int journal_type = map_to_journal_type(type); + if (journal_type < 0) return; + + int max_bytes = g_runtime_args.buffer_size - start_buffer; + if (max_bytes > size) max_bytes = size; + + for (int byte_idx = 0; byte_idx < max_bytes; byte_idx++) { + uint8_t byte_val = src[byte_idx]; + int plc_idx = start_buffer + byte_idx; + for (int bit_idx = 0; bit_idx < 8; bit_idx++) { + int bit_val = (byte_val >> bit_idx) & 0x01; + g_runtime_args.journal_write_bool(journal_type, plc_idx, bit_idx, bit_val); + } + } +} + +/** + * @brief Write int buffer to OpenPLC via journal with endian conversion + */ +static void write_int_to_openplc_journal(uint8_t *src, int size, s7comm_buffer_type_t type, int start_buffer) +{ + int journal_type = map_to_journal_type(type); + if (journal_type < 0) return; + + uint16_t *s7_words = (uint16_t *)src; + int num_words = size / 2; + int max_words = g_runtime_args.buffer_size - start_buffer; + if (max_words > num_words) max_words = num_words; + + for (int i = 0; i < max_words; i++) { + uint16_t value = swap16(s7_words[i]); + g_runtime_args.journal_write_int(journal_type, start_buffer + i, value); + } +} + +/** + * @brief Write dint buffer to OpenPLC via journal with endian conversion + */ +static void write_dint_to_openplc_journal(uint8_t *src, int size, s7comm_buffer_type_t type, int start_buffer) +{ + int journal_type = map_to_journal_type(type); + if (journal_type < 0) return; + + uint32_t *s7_dwords = (uint32_t *)src; + int num_dwords = size / 4; + int max_dwords = g_runtime_args.buffer_size - start_buffer; + if (max_dwords > num_dwords) max_dwords = num_dwords; + + for (int i = 0; i < max_dwords; i++) { + uint32_t value = swap32(s7_dwords[i]); + g_runtime_args.journal_write_dint(journal_type, start_buffer + i, value); + } +} + +/** + * @brief Write lint buffer to OpenPLC via journal with endian conversion + */ +static void write_lint_to_openplc_journal(uint8_t *src, int size, s7comm_buffer_type_t type, int start_buffer) +{ + int journal_type = map_to_journal_type(type); + if (journal_type < 0) return; + + uint64_t *s7_lwords = (uint64_t *)src; + int num_lwords = size / 8; + int max_lwords = g_runtime_args.buffer_size - start_buffer; + if (max_lwords > num_lwords) max_lwords = num_lwords; + + for (int i = 0; i < max_lwords; i++) { + uint64_t value = swap64(s7_lwords[i]); + g_runtime_args.journal_write_lint(journal_type, start_buffer + i, value); + } +} + +/** + * @brief Dispatch write from buffer to OpenPLC journal based on buffer type + */ +static void write_buffer_to_openplc_journal(uint8_t *src, int size, s7comm_buffer_type_t type, int start_buffer) +{ + switch (type) { + case BUFFER_TYPE_BOOL_INPUT: + case BUFFER_TYPE_BOOL_OUTPUT: + case BUFFER_TYPE_BOOL_MEMORY: + write_bool_to_openplc_journal(src, size, type, start_buffer); + break; + + case BUFFER_TYPE_INT_INPUT: + case BUFFER_TYPE_INT_OUTPUT: + case BUFFER_TYPE_INT_MEMORY: + write_int_to_openplc_journal(src, size, type, start_buffer); + break; + + case BUFFER_TYPE_DINT_INPUT: + case BUFFER_TYPE_DINT_OUTPUT: + case BUFFER_TYPE_DINT_MEMORY: + write_dint_to_openplc_journal(src, size, type, start_buffer); + break; + + case BUFFER_TYPE_LINT_INPUT: + case BUFFER_TYPE_LINT_OUTPUT: + case BUFFER_TYPE_LINT_MEMORY: + write_lint_to_openplc_journal(src, size, type, start_buffer); + break; + + default: + break; + } +} + +/* + * ============================================================================= + * Snap7 RWArea Callback - On-Demand Data Synchronization + * ============================================================================= + */ + +/** + * @brief Snap7 RWArea callback for on-demand data synchronization + * + * Called by Snap7 when an S7 client reads or writes data. + * - On READ: Acquire OpenPLC mutex, copy fresh data to S7 buffer, release mutex + * - On WRITE: Use journal writes (thread-safe, no mutex needed) + * + * @param usrPtr User pointer (unused) + * @param Sender Client identifier + * @param Operation OperationRead or OperationWrite + * @param PTag S7 tag with Area, DBNumber, Start, Size + * @param pUsrData Pointer to data buffer + * @return 0 to accept operation, non-zero to reject + */ +static int s7comm_rw_area_callback(void *usrPtr, int Sender, int Operation, PS7Tag PTag, void *pUsrData) +{ + (void)usrPtr; + (void)Sender; + + if (pUsrData == NULL || PTag == NULL) { + return -1; + } + + s7comm_buffer_type_t type; + int start_buffer; + int size = PTag->Size; + + /* Determine mapping based on S7 area */ + if (PTag->Area == srvAreaDB) { + /* Data block - look up configuration */ + s7comm_db_runtime_t *db = find_db_runtime(PTag->DBNumber); + if (db == NULL) { + /* DB not configured - let Snap7 handle with its registered buffer */ + return 0; + } + type = db->type; + start_buffer = db->start_buffer + (PTag->Start / get_type_size(type)); + } else { + /* System area (PE, PA, MK) */ + s7comm_area_runtime_t *area = find_area_runtime(PTag->Area); + if (area == NULL) { + /* Area not configured - let Snap7 handle with its registered buffer */ + return 0; + } + type = area->type; + start_buffer = area->start_buffer + (PTag->Start / get_type_size(type)); + } + + if (Operation == OperationRead) { + /* + * S7 client is READing - provide fresh data from OpenPLC + * Acquire mutex, copy data, release mutex + */ + g_runtime_args.mutex_take(g_runtime_args.buffer_mutex); + read_openplc_to_buffer((uint8_t *)pUsrData, size, type, start_buffer); + g_runtime_args.mutex_give(g_runtime_args.buffer_mutex); + } else if (Operation == OperationWrite) { + /* + * S7 client is WRITing - journal the changes + * Journal writes are thread-safe, no mutex needed + */ + write_buffer_to_openplc_journal((uint8_t *)pUsrData, size, type, start_buffer); + } + + return 0; /* Accept operation */ +} + +/** + * @brief Get size of a single element for a buffer type + */ +static int get_type_size(s7comm_buffer_type_t type) +{ + switch (type) { + case BUFFER_TYPE_BOOL_INPUT: + case BUFFER_TYPE_BOOL_OUTPUT: + case BUFFER_TYPE_BOOL_MEMORY: + return 1; /* 1 byte per bool group */ + + case BUFFER_TYPE_INT_INPUT: + case BUFFER_TYPE_INT_OUTPUT: + case BUFFER_TYPE_INT_MEMORY: + return 2; /* 2 bytes per INT */ + + case BUFFER_TYPE_DINT_INPUT: + case BUFFER_TYPE_DINT_OUTPUT: + case BUFFER_TYPE_DINT_MEMORY: + return 4; /* 4 bytes per DINT */ + + case BUFFER_TYPE_LINT_INPUT: + case BUFFER_TYPE_LINT_OUTPUT: + case BUFFER_TYPE_LINT_MEMORY: + return 8; /* 8 bytes per LINT */ + + default: + return 1; + } +} diff --git a/core/src/drivers/plugins/native/s7comm/s7comm_plugin.h b/core/src/drivers/plugins/native/s7comm/s7comm_plugin.h new file mode 100644 index 00000000..f86c93a7 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/s7comm_plugin.h @@ -0,0 +1,74 @@ +/** + * @file s7comm_plugin.h + * @brief S7Comm Plugin for OpenPLC Runtime v4 + * + * This plugin implements a Siemens S7 communication server using the Snap7 library, + * allowing S7-compatible HMIs and SCADA systems to communicate with OpenPLC. + * + * The plugin is self-contained with Snap7 sources compiled directly into the + * shared library, requiring no external dependencies. + */ + +#ifndef S7COMM_PLUGIN_H +#define S7COMM_PLUGIN_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Initialize the S7Comm plugin + * + * Called when the plugin is loaded. Initializes the Snap7 server and + * registers data areas for S7 client access. + * + * @param args Pointer to plugin_runtime_args_t containing runtime buffers, + * mutex functions, and logging function pointers + * @return 0 on success, -1 on failure + */ +int init(void *args); + +/** + * @brief Start the S7 server + * + * Starts the Snap7 server and begins accepting client connections + * on the configured port (default: 102). + */ +void start_loop(void); + +/** + * @brief Stop the S7 server + * + * Stops the Snap7 server and disconnects all clients gracefully. + */ +void stop_loop(void); + +/** + * @brief Cleanup plugin resources + * + * Releases all resources allocated by the plugin including + * the Snap7 server instance and data buffers. + */ +void cleanup(void); + +/** + * @brief Called at the start of each PLC scan cycle + * + * Synchronizes OpenPLC input buffers to S7 data areas. + * Called with buffer mutex already held. + */ +void cycle_start(void); + +/** + * @brief Called at the end of each PLC scan cycle + * + * Synchronizes S7 data areas to OpenPLC output buffers. + * Called with buffer mutex already held. + */ +void cycle_end(void); + +#ifdef __cplusplus +} +#endif + +#endif /* S7COMM_PLUGIN_H */ diff --git a/core/src/drivers/plugins/native/s7comm/snap7/core/s7_client.cpp b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_client.cpp new file mode 100644 index 00000000..af115b96 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_client.cpp @@ -0,0 +1,503 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 Davide Nardella | +| All rights reserved. | +|==============================================================================| +| SNAP7 is free software: you can redistribute it and/or modify | +| it under the terms of the Lesser GNU General Public License as published by | +| the Free Software Foundation, either version 3 of the License, or | +| (at your option) any later version. | +| | +| It means that you can distribute your commercial software linked with | +| SNAP7 without the requirement to distribute the source code of your | +| application and without the requirement that your application be itself | +| distributed under LGPL. | +| | +| SNAP7 is distributed in the hope that it will be useful, | +| but WITHOUT ANY WARRANTY; without even the implied warranty of | +| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | +| Lesser GNU General Public License for more details. | +| | +| You should have received a copy of the GNU General Public License and a | +| copy of Lesser GNU General Public License along with Snap7. | +| If not, see http://www.gnu.org/licenses/ | +|=============================================================================*/ +#include "s7_client.h" + +//--------------------------------------------------------------------------- +TSnap7Client::TSnap7Client() +{ + FThread = 0; + CliCompletion = 0; + EvtJob = NULL; + EvtComplete = NULL; + FThread=NULL; + ThreadCreated = false; +} +//--------------------------------------------------------------------------- +TSnap7Client::~TSnap7Client() +{ + Destroying=true; + Disconnect(); + CliCompletion=NULL; + if (ThreadCreated) + { + CloseThread(); + delete EvtComplete; + delete EvtJob; + ThreadCreated=false; + } +} +//--------------------------------------------------------------------------- +void TSnap7Client::CloseThread() +{ + int Timeout; + + if (FThread) + { + FThread->Terminate(); + if (Job.Pending) + Timeout=3000; + else + Timeout=1000; + EvtJob->Set(); + if (FThread->WaitFor(Timeout)!=WAIT_OBJECT_0) + FThread->Kill(); + try { + delete FThread; + } + catch (...){ + } + + FThread=0; + } +} +//--------------------------------------------------------------------------- +void TSnap7Client::OpenThread() +{ + FThread = new TClientThread(this); + FThread->Start(); +} +//--------------------------------------------------------------------------- +int TSnap7Client::Reset(bool DoReconnect) +{ + bool WasConnected = Connected; + if (ThreadCreated) + { + CloseThread(); + Disconnect(); + OpenThread(); + } + else + Disconnect(); + + if (DoReconnect || WasConnected) + return Connect(); + else + return 0; +} +//--------------------------------------------------------------------------- +void TSnap7Client::DoCompletion() +{ + if ((CliCompletion!=NULL) && !Destroying) + { + try{ + CliCompletion(FUsrPtr, Job.Op, Job.Result); + }catch (...) + { + } + } +} +//--------------------------------------------------------------------------- +int TSnap7Client::SetAsCallback(pfn_CliCompletion pCompletion, void * usrPtr) +{ + CliCompletion=pCompletion; + FUsrPtr=usrPtr; + return 0; +} +//--------------------------------------------------------------------------- +int TSnap7Client::GetParam(int ParamNumber, void * pValue) +{ + // Actually there are no specific client params, maybe in future... + return TSnap7MicroClient::GetParam(ParamNumber, pValue); +} +//--------------------------------------------------------------------------- +int TSnap7Client::SetParam(int ParamNumber, void * pValue) +{ + // Actually there are no specific client params, maybe in future... + return TSnap7MicroClient::SetParam(ParamNumber, pValue); +} +//--------------------------------------------------------------------------- +bool TSnap7Client::CheckAsCompletion(int &opResult) +{ + if (!Job.Pending) + opResult=Job.Result; + else + if (!Destroying) + opResult=errCliJobPending; // don't set LastError here + else + { + opResult=errCliDestroying; + return true; + } + + return !Job.Pending; +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsReadArea(int Area, int DBNumber, int Start, int Amount, int WordLen, void * pUsrData) +{ + if (!Job.Pending) + { + Job.Pending = true; + Job.Op = s7opReadArea; + Job.Area = Area; + Job.Number = DBNumber; + Job.Start = Start; + Job.Amount = Amount; + Job.WordLen = WordLen; + Job.pData = pUsrData; + JobStart = SysGetTick(); + StartAsyncJob(); + return 0; + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsWriteArea(int Area, int DBNumber, int Start, int Amount, int WordLen, void * pUsrData) +{ + int ByteSize, TotalSize; + + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opWriteArea; + Job.Area =Area; + Job.Number =DBNumber; + Job.Start =Start; + // Performs some check first to copy the data + ByteSize=DataSizeByte(WordLen); + TotalSize=ByteSize*Amount; // Total size in bytes + if (ByteSize==0) + return SetError(errCliInvalidWordLen); + if ((TotalSize < 1) || (TotalSize > int(sizeof(opData)))) + return SetError(errCliInvalidParams); + Job.Amount =Amount; + Job.WordLen =WordLen; + // Doublebuffering + memcpy(&opData, pUsrData, TotalSize); + Job.pData =&opData; + JobStart =SysGetTick(); + StartAsyncJob(); + return 0; + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsListBlocksOfType(int BlockType, PS7BlocksOfType pUsrData, int & ItemsCount) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opListBlocksOfType; + Job.Area =BlockType; + Job.pData =pUsrData; + Job.pAmount =&ItemsCount; + JobStart =SysGetTick(); + StartAsyncJob(); + return 0; + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsReadSZL(int ID, int Index, PS7SZL pUsrData, int & Size) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opReadSZL; + Job.ID =ID; + Job.Index =Index; + Job.pData =pUsrData; + Job.pAmount =&Size; + Job.Amount =Size; + Job.IParam =1; // Data has to be copied into user buffer + JobStart =SysGetTick(); + StartAsyncJob(); + return 0; + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsReadSZLList(PS7SZLList pUsrData, int &ItemsCount) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opReadSzlList; + Job.pData =pUsrData; + Job.pAmount =&ItemsCount; + Job.Amount =ItemsCount; + JobStart =SysGetTick(); + StartAsyncJob(); + return 0; + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsUpload(int BlockType, int BlockNum, void * pUsrData, int & Size) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opUpload; + Job.Area =BlockType; + Job.pData =pUsrData; + Job.pAmount =&Size; + Job.Amount =Size; + Job.Number =BlockNum; + Job.IParam =0; // not full upload, only data + JobStart =SysGetTick(); + StartAsyncJob(); + return 0; + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsFullUpload(int BlockType, int BlockNum, void * pUsrData, int & Size) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opUpload; + Job.Area =BlockType; + Job.pData =pUsrData; + Job.pAmount =&Size; + Job.Amount =Size; + Job.Number =BlockNum; + Job.IParam =1; // full upload + JobStart =SysGetTick(); + StartAsyncJob(); + return 0; + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsDownload(int BlockNum, void * pUsrData, int Size) +{ + if (!Job.Pending) + { + // Checks the size : here we only need a size>0 to avoid problems during + // doublebuffering, the real test of the block size will be done in + // Checkblock. + if (Size<1) + return SetError(errCliInvalidBlockSize); + Job.Pending =true; + Job.Op =s7opDownload; + // Doublebuffering + memcpy(&opData, pUsrData, Size); + Job.Number =BlockNum; + Job.Amount =Size; + JobStart =SysGetTick(); + StartAsyncJob(); + return 0; + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsCopyRamToRom(int Timeout) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opCopyRamToRom; + if (Timeout>0) + { + Job.IParam =Timeout; + JobStart =SysGetTick(); + StartAsyncJob(); + return 0; + } + else + return SetError(errCliInvalidParams); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsCompress(int Timeout) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opCompress; + if (Timeout>0) + { + Job.IParam =Timeout; + JobStart =SysGetTick(); + StartAsyncJob(); + return 0; + } + else + return SetError(errCliInvalidParams); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsDBRead(int DBNumber, int Start, int Size, void * pUsrData) +{ + return AsReadArea(S7AreaDB, DBNumber, Start, Size, S7WLByte, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsDBWrite(int DBNumber, int Start, int Size, void * pUsrData) +{ + return AsWriteArea(S7AreaDB, DBNumber, Start, Size, S7WLByte, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsMBRead(int Start, int Size, void * pUsrData) +{ + return AsReadArea(S7AreaMK, 0, Start, Size, S7WLByte, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsMBWrite(int Start, int Size, void * pUsrData) +{ + return AsWriteArea(S7AreaMK, 0, Start, Size, S7WLByte, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsEBRead(int Start, int Size, void * pUsrData) +{ + return AsReadArea(S7AreaPE, 0, Start, Size, S7WLByte, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsEBWrite(int Start, int Size, void * pUsrData) +{ + return AsWriteArea(S7AreaPE, 0, Start, Size, S7WLByte, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsABRead(int Start, int Size, void * pUsrData) +{ + return AsReadArea(S7AreaPA, 0, Start, Size, S7WLByte, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsABWrite(int Start, int Size, void * pUsrData) +{ + return AsWriteArea(S7AreaPA, 0, Start, Size, S7WLByte, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsTMRead(int Start, int Amount, void * pUsrData) +{ + return AsReadArea(S7AreaTM, 0, Start, Amount, S7WLTimer, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsTMWrite(int Start, int Amount, void * pUsrData) +{ + return AsWriteArea(S7AreaTM, 0, Start, Amount, S7WLTimer, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsCTRead(int Start, int Amount, void * pUsrData) +{ + return AsReadArea(S7AreaCT, 0, Start, Amount, S7WLCounter, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsCTWrite(int Start, int Amount, void * pUsrData) +{ + return AsWriteArea(S7AreaCT, 0, Start, Amount, S7WLCounter, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsDBGet(int DBNumber, void * pUsrData, int &Size) +{ + if (!Job.Pending) + { + if (Size<=0) + return SetError(errCliInvalidBlockSize); + Job.Pending =true; + Job.Op =s7opDBGet; + Job.Number =DBNumber; + Job.pData =pUsrData; + Job.pAmount =&Size; + Job.Amount =Size; + JobStart =SysGetTick(); + StartAsyncJob(); + return 0; + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7Client::AsDBFill(int DBNumber, int FillChar) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opDBFill; + Job.Number =DBNumber; + Job.IParam =FillChar; + JobStart =SysGetTick(); + StartAsyncJob(); + return 0; + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +void TSnap7Client::StartAsyncJob() +{ + ClrError(); + if (!ThreadCreated) + { + EvtJob = new TSnapEvent(false); + EvtComplete = new TSnapEvent(false); + OpenThread(); + ThreadCreated=true; + } + EvtComplete->Reset(); // reset if previously was not called WaitAsCompletion + EvtJob->Set(); +} +//--------------------------------------------------------------------------- +int TSnap7Client::WaitAsCompletion(unsigned long Timeout) +{ + if (Job.Pending) + { + if (ThreadCreated) + { + if (EvtComplete->WaitFor(Timeout)==WAIT_OBJECT_0) + return Job.Result; + else + { + if (Destroying) + return errCliDestroying; + else + return SetError(errCliJobTimeout); + } + } + else + return SetError(errCliJobTimeout); + } + else + return Job.Result; +} +//--------------------------------------------------------------------------- +void TClientThread::Execute() +{ + while (!Terminated) + { + FClient->EvtJob->WaitForever(); + if (!Terminated) + { + FClient->PerformOperation(); + FClient->EvtComplete->Set(); + // Notify the caller the end of job (if callback is set) + FClient->DoCompletion(); + } + }; +} + diff --git a/core/src/drivers/plugins/native/s7comm/snap7/core/s7_client.h b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_client.h new file mode 100644 index 00000000..560949e2 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_client.h @@ -0,0 +1,104 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 Davide Nardella | +| All rights reserved. | +|==============================================================================| +| SNAP7 is free software: you can redistribute it and/or modify | +| it under the terms of the Lesser GNU General Public License as published by | +| the Free Software Foundation, either version 3 of the License, or | +| (at your option) any later version. | +| | +| It means that you can distribute your commercial software linked with | +| SNAP7 without the requirement to distribute the source code of your | +| application and without the requirement that your application be itself | +| distributed under LGPL. | +| | +| SNAP7 is distributed in the hope that it will be useful, | +| but WITHOUT ANY WARRANTY; without even the implied warranty of | +| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | +| Lesser GNU General Public License for more details. | +| | +| You should have received a copy of the GNU General Public License and a | +| copy of Lesser GNU General Public License along with Snap7. | +| If not, see http://www.gnu.org/licenses/ | +|=============================================================================*/ +#ifndef s7_client_h +#define s7_client_h +//--------------------------------------------------------------------------- +#include "snap_threads.h" +#include "s7_micro_client.h" +//--------------------------------------------------------------------------- + +extern "C" { +typedef void (S7API *pfn_CliCompletion) (void * usrPtr, int opCode, int opResult); +} +class TSnap7Client; + +class TClientThread: public TSnapThread +{ +private: + TSnap7Client * FClient; +public: + TClientThread(TSnap7Client *Client) + { + FClient = Client; + } + void Execute(); +}; +//--------------------------------------------------------------------------- +class TSnap7Client: public TSnap7MicroClient +{ +private: + TClientThread *FThread; + bool ThreadCreated; + void CloseThread(); + void OpenThread(); + void StartAsyncJob(); +protected: + PSnapEvent EvtJob; + PSnapEvent EvtComplete; + pfn_CliCompletion CliCompletion; + void *FUsrPtr; + void DoCompletion(); +public: + friend class TClientThread; + TSnap7Client(); + ~TSnap7Client(); + int Reset(bool DoReconnect); + int SetAsCallback(pfn_CliCompletion pCompletion, void * usrPtr); + int GetParam(int ParamNumber, void *pValue); + int SetParam(int ParamNumber, void *pValue); + // Async functions + bool CheckAsCompletion( int & opResult); + int WaitAsCompletion(unsigned long Timeout); + int AsReadArea(int Area, int DBNumber, int Start, int Amount, int WordLen, void * pUsrData); + int AsWriteArea(int Area, int DBNumber, int Start, int Amount, int WordLen, void * pUsrData); + int AsListBlocksOfType(int BlockType, PS7BlocksOfType pUsrData, int & ItemsCount); + int AsReadSZL(int ID, int Index, PS7SZL pUsrData, int & Size); + int AsReadSZLList(PS7SZLList pUsrData, int &ItemsCount); + int AsUpload(int BlockType, int BlockNum, void * pUsrData, int & Size); + int AsFullUpload(int BlockType, int BlockNum, void * pUsrData, int & Size); + int AsDownload(int BlockNum, void * pUsrData, int Size); + int AsCopyRamToRom(int Timeout); + int AsCompress(int Timeout); + int AsDBRead(int DBNumber, int Start, int Size, void * pUsrData); + int AsDBWrite(int DBNumber, int Start, int Size, void * pUsrData); + int AsMBRead(int Start, int Size, void * pUsrData); + int AsMBWrite(int Start, int Size, void * pUsrData); + int AsEBRead(int Start, int Size, void * pUsrData); + int AsEBWrite(int Start, int Size, void * pUsrData); + int AsABRead(int Start, int Size, void * pUsrData); + int AsABWrite(int Start, int Size, void * pUsrData); + int AsTMRead(int Start, int Amount, void * pUsrData); + int AsTMWrite(int Start, int Amount, void * pUsrData); + int AsCTRead(int Start, int Amount, void * pUsrData); + int AsCTWrite(int Start, int Amount, void * pUsrData); + int AsDBGet(int DBNumber, void * pUsrData, int & Size); + int AsDBFill(int DBNumber, int FillChar); +}; + +typedef TSnap7Client *PSnap7Client; + +//--------------------------------------------------------------------------- +#endif // s7_client_h diff --git a/core/src/drivers/plugins/native/s7comm/snap7/core/s7_firmware.h b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_firmware.h new file mode 100644 index 00000000..28102a13 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_firmware.h @@ -0,0 +1,1256 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 Davide Nardella | +| All rights reserved. | +|==============================================================================| +| SNAP7 is free software: you can redistribute it and/or modify | +| it under the terms of the Lesser GNU General Public License as published by | +| the Free Software Foundation, either version 3 of the License, or | +| (at your option) any later version. | +| | +| It means that you can distribute your commercial software linked with | +| SNAP7 without the requirement to distribute the source code of your | +| application and without the requirement that your application be itself | +| distributed under LGPL. | +| | +| SNAP7 is distributed in the hope that it will be useful, | +| but WITHOUT ANY WARRANTY; without even the implied warranty of | +| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | +| Lesser GNU General Public License for more details. | +| | +| You should have received a copy of the GNU General Public License and a | +| copy of Lesser GNU General Public License along with Snap7. | +| If not, see http://www.gnu.org/licenses/ | +|=============================================================================*/ +#ifndef s7_firmware_h +#define s7_firmware_h +//--------------------------------------------------------------------------- + +#include "snap_platform.h" + +//****************************************************************************** +// CPU DATABANK +//****************************************************************************** + + byte SZLNotAvail[4] = { + 0x0A,0x00,0x00,0x00 + }; + + byte SZLSysState[6] = { + 0xFF,0x09,0x00,0x02,0x02,0x00 + }; + + byte SZL_ID_0000_IDX_XXXX[236] = { + 0xFF,0x09,0x00,0xE8,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x70,0x00,0x00,0x0F,0x00,0x00,0x02,0x00, + 0x11,0x01,0x11,0x0F,0x11,0x00,0x12,0x01,0x12,0x0F,0x12,0x00,0x13,0x01,0x13,0x00,0x14,0x0F,0x14, + 0x00,0x15,0x01,0x15,0x00,0x17,0x01,0x17,0x0F,0x17,0x00,0x18,0x01,0x18,0x0F,0x18,0x00,0x19,0x0F, + 0x19,0x00,0x1A,0x0F,0x1A,0x00,0x1B,0x0F,0x1B,0x00,0x1C,0x01,0x1C,0x0F,0x1C,0x00,0x21,0x0A,0x21, + 0x0F,0x21,0x02,0x22,0x00,0x23,0x0F,0x23,0x00,0x24,0x01,0x24,0x04,0x24,0x05,0x24,0x00,0x25,0x01, + 0x25,0x02,0x25,0x0F,0x25,0x01,0x31,0x01,0x32,0x02,0x32,0x00,0x36,0x01,0x36,0x0F,0x36,0x00,0x37, + 0x01,0x37,0x0F,0x37,0x00,0x38,0x01,0x38,0x02,0x38,0x0F,0x38,0x01,0x39,0x00,0x3A,0x0F,0x3A,0x00, + 0x74,0x01,0x74,0x0F,0x74,0x05,0x91,0x0A,0x91,0x0C,0x91,0x0D,0x91,0x00,0x92,0x02,0x92,0x06,0x92, + 0x0F,0x92,0x00,0x94,0x01,0x94,0x02,0x94,0x06,0x94,0x07,0x94,0x0F,0x94,0x00,0x95,0x01,0x95,0x0F, + 0x95,0x06,0x96,0x0C,0x96,0x0C,0x97,0x0D,0x97,0x01,0x9A,0x02,0x9A,0x0F,0x9A,0x0C,0x9B,0x00,0x9C, + 0x01,0x9C,0x02,0x9C,0x03,0x9C,0x0F,0x9C,0x00,0xA0,0x01,0xA0,0x0F,0xA0,0x00,0xB1,0x00,0xB2,0x00, + 0xB3,0x00,0xB4,0x01,0xB5,0x02,0xB5,0x03,0xB5,0x04,0xB5,0x05,0xB5,0x06,0xB5,0x07,0xB5,0x08,0xB5, + 0x01,0xB6,0x02,0xB6,0x03,0xB6,0x04,0xB6 + }; + + byte SZL_ID_0F00_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x00,0x00,0x00,0x00,0x02,0x00,0x70 + }; + + byte SZL_ID_0002_IDX_XXXX[458] = { // <--Wrapped to 458 bytes + 0xFF,0x09,0x01,0xC6,0x00,0x02,0x00,0x00,0x00,0x08,0x00,0x71,0x01,0xAC,0x00,0x01,0x00,0x28,0x00, + 0x1C,0x01,0xAC,0x24,0x00,0x00,0x24,0x00,0x00,0x01,0xAC,0x23,0x00,0x00,0x06,0x04,0xB0,0x01,0xAC, + 0x22,0x00,0x00,0x08,0x00,0x01,0x01,0xAC,0x31,0x00,0x04,0x00,0x00,0x01,0x01,0xAC,0x12,0xFF,0x00, + 0x08,0x00,0x01,0x01,0xAC,0x12,0x31,0x00,0x08,0x00,0x01,0x01,0xAD,0x00,0x00,0x80,0x00,0x00,0x01, + 0x01,0xAD,0x01,0x00,0x80,0x00,0x00,0x01,0x01,0xAD,0x02,0x00,0x80,0x00,0x00,0x01,0x01,0xAD,0x03, + 0x00,0x80,0x00,0x00,0x01,0x01,0xAD,0x04,0x00,0x80,0x00,0x00,0x01,0x01,0xAD,0x05,0x00,0x80,0x00, + 0x00,0x01,0x01,0xAD,0x06,0x00,0x80,0x00,0x00,0x01,0x01,0xAD,0x07,0x00,0x80,0x00,0x00,0x01,0x01, + 0xAD,0x00,0x01,0x80,0x00,0x00,0x01,0x01,0xAD,0x01,0x01,0x80,0x00,0x00,0x01,0x01,0xAD,0x02,0x01, + 0x80,0x00,0x00,0x01,0x01,0xAD,0x03,0x01,0x80,0x00,0x00,0x01,0x01,0xAD,0x04,0x01,0x80,0x00,0x00, + 0x01,0x01,0xAD,0x05,0x01,0x80,0x00,0x00,0x01,0x01,0xAD,0x06,0x01,0x80,0x00,0x00,0x01,0x01,0xAD, + 0x07,0x01,0x80,0x00,0x00,0x01,0x01,0xAD,0x00,0x03,0x80,0x00,0x00,0x01,0x01,0xAD,0x01,0x03,0x80, + 0x00,0x00,0x01,0x01,0xAD,0x02,0x03,0x80,0x00,0x00,0x01,0x01,0xAD,0x03,0x03,0x80,0x00,0x00,0x01, + 0x01,0xAD,0x04,0x03,0x80,0x00,0x00,0x01,0x01,0xAD,0x05,0x03,0x80,0x00,0x00,0x01,0x01,0xAD,0x06, + 0x03,0x80,0x00,0x00,0x01,0x01,0xAD,0x07,0x03,0x80,0x00,0x00,0x01,0x01,0xAD,0x00,0x04,0x80,0x00, + 0x00,0x01,0x01,0xAD,0x01,0x04,0x80,0x00,0x00,0x01,0x01,0xAD,0x02,0x04,0x80,0x00,0x00,0x01,0x01, + 0xAD,0x03,0x04,0x80,0x00,0x00,0x01,0x01,0xAD,0x04,0x04,0x80,0x00,0x00,0x01,0x01,0xAD,0x05,0x04, + 0x80,0x00,0x00,0x01,0x01,0xAD,0x06,0x04,0x80,0x00,0x00,0x01,0x01,0xAD,0x07,0x04,0x80,0x00,0x00, + 0x01,0x01,0xAD,0x00,0x05,0x80,0x00,0x00,0x01,0x01,0xAD,0x01,0x05,0x80,0x00,0x00,0x01,0x01,0xAD, + 0x02,0x05,0x80,0x00,0x00,0x01,0x01,0xAD,0x03,0x05,0x80,0x00,0x00,0x01,0x01,0xAD,0x04,0x05,0x80, + 0x00,0x00,0x01,0x01,0xAD,0x05,0x05,0x80,0x00,0x00,0x01,0x01,0xAD,0x06,0x05,0x80,0x00,0x00,0x01, + 0x01,0xAD,0x07,0x05,0x80,0x00,0x00,0x01,0x01,0xAD,0x00,0x06,0x80,0x00,0x00,0x01,0x01,0xAD,0x01, + 0x06,0x80,0x00,0x00,0x01,0x01,0xAD,0x02,0x06,0x80,0x00,0x00,0x01,0x01,0xAD,0x03,0x06,0x80,0x00, + 0x00,0x01,0x01,0xAD,0x04,0x06,0x80,0x00,0x00,0x01,0x01,0xAD,0x05,0x06,0x80,0x00,0x00,0x01,0x01, + 0xAD,0x06,0x06,0x80,0x00,0x00,0x01,0x01,0xAD,0x07,0x06,0x80,0x00,0x00,0x01,0x01,0xAD,0x00,0x07, + 0x80,0x00 + }; + + byte SZL_ID_0011_IDX_XXXX[124] = { + 0xFF,0x09,0x00,0x78,0x00,0x11,0x00,0x00,0x00,0x1C,0x00,0x04,0x00,0x01,0x36,0x45,0x53,0x37,0x20, + 0x33,0x31,0x35,0x2D,0x32,0x45,0x48,0x31,0x34,0x2D,0x30,0x41,0x42,0x30,0x20,0x00,0xC0,0x00,0x04, + 0x00,0x01,0x00,0x06,0x36,0x45,0x53,0x37,0x20,0x33,0x31,0x35,0x2D,0x32,0x45,0x48,0x31,0x34,0x2D, + 0x30,0x41,0x42,0x30,0x20,0x00,0xC0,0x00,0x04,0x00,0x01,0x00,0x07,0x20,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x00,0xC0,0x56,0x03,0x02, + 0x06,0x00,0x81,0x42,0x6F,0x6F,0x74,0x20,0x4C,0x6F,0x61,0x64,0x65,0x72,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x00,0x00,0x41,0x20,0x09,0x09 + }; + + byte SZL_ID_0012_IDX_XXXX[58] = { + 0xFF,0x09,0x00,0x36,0x00,0x12,0x00,0x00,0x00,0x02,0x00,0x17,0x00,0x01,0x01,0x01,0x01,0x04,0x03, + 0x02,0x03,0x03,0x03,0x04,0x03,0x06,0x03,0x07,0x03,0x08,0x03,0x09,0x03,0x0A,0x03,0x0B,0x03,0x0C, + 0x03,0x0D,0x03,0x0E,0x03,0x0F,0x03,0x10,0x03,0x11,0x03,0x12,0x03,0x13,0x03,0x14,0x03,0x15,0x03, + 0x17 + }; + + byte SZL_ID_0013_IDX_XXXX[192] = { + 0xFF,0x09,0x00,0xBC,0x00,0x13,0x00,0x00,0x00,0x24,0x00,0x05,0x00,0x01,0x00,0x01,0x00,0x06,0x00, + 0x00,0x00,0x11,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x00,0x00,0x26,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x01,0x00,0x00,0x00,0x00,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x02,0x00,0x08,0x00,0x00,0x00,0x02,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x00, + 0x0C,0x06,0x00,0x00,0x00,0x00,0x00,0x04,0x00,0x02,0x00,0x80,0x00,0x00,0x00,0x02,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x05,0x00,0x02,0x00,0x00,0x0C,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x00,0x20,0x00,0x00, + 0x00,0x00 + }; + + byte SZL_ID_0014_IDX_XXXX[84] = { + 0xFF,0x09,0x00,0x50,0x00,0x14,0x00,0x00,0x00,0x08,0x00,0x09,0x00,0x01,0x00,0x01,0x08,0x00,0x00, + 0x00,0x00,0x02,0x00,0x01,0x08,0x00,0x00,0x00,0x00,0x03,0x00,0x01,0x40,0x00,0x00,0x80,0x00,0x04, + 0x00,0x01,0x01,0x00,0x00,0x00,0x00,0x05,0x00,0x01,0x01,0x00,0x00,0x08,0x00,0x06,0x00,0x01,0x08, + 0x00,0x00,0x00,0x00,0x07,0x00,0x01,0x80,0x00,0x00,0x00,0x00,0x08,0x00,0x01,0x08,0x00,0x00,0x10, + 0x00,0x09,0x00,0x01,0x00,0x20,0x00,0x00 + }; + + byte SZL_ID_0015_IDX_XXXX[62] = { + 0xFF,0x09,0x00,0x3A,0x00,0x15,0x00,0x00,0x00,0x0A,0x00,0x05,0x08,0x00,0x00,0x16,0x03,0xD1,0x00, + 0x00,0xFF,0xFE,0x0A,0x00,0x3E,0x81,0x03,0xD1,0x00,0x00,0xFF,0xFE,0x0B,0x00,0x04,0x22,0x03,0xD1, + 0x00,0x00,0xFF,0xFE,0x0C,0x00,0x1F,0x40,0x03,0xD1,0x00,0x00,0xFF,0xFE,0x0E,0x00,0x1F,0x40,0x03, + 0xD1,0x00,0x00,0xFF,0xFE + }; + + byte SZL_ID_0F14_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x14,0x00,0x00,0x00,0x08,0x00,0x09 + }; + + byte SZL_ID_0019_IDX_XXXX[40] = { + 0xFF,0x09,0x00,0x24,0x00,0x19,0x00,0x00,0x00,0x04,0x00,0x07,0x00,0x01,0x00,0x00,0x00,0x04,0x01, + 0x00,0x00,0x05,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x0B,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x15, + 0x00,0x00 + }; + + byte SZL_ID_0F19_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x19,0x00,0x00,0x00,0x04,0x00,0x07 + }; + + byte SZL_ID_001C_IDX_XXXX[352] = { + 0xFF,0x09,0x01,0x5C,0x00,0x1C,0x00,0x00,0x00,0x22,0x00,0x0A,0x00,0x01,0x53,0x4E,0x41,0x50,0x37, + 0x2D,0x53,0x45,0x52,0x56,0x45,0x52,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x43,0x50,0x55,0x20,0x33,0x31,0x35,0x2D,0x32, + 0x20,0x50,0x4E,0x2F,0x44,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x04,0x4F,0x72,0x69,0x67,0x69,0x6E,0x61,0x6C,0x20,0x53,0x69,0x65,0x6D,0x65,0x6E,0x73,0x20, + 0x45,0x71,0x75,0x69,0x70,0x6D,0x65,0x6E,0x74,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0x53,0x20, + 0x43,0x2D,0x43,0x32,0x55,0x52,0x32,0x38,0x39,0x32,0x32,0x30,0x31,0x32,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x43,0x50,0x55,0x20,0x33,0x31, + 0x35,0x2D,0x32,0x20,0x50,0x4E,0x2F,0x44,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x4D,0x4D,0x43,0x20,0x32,0x36,0x37,0x46,0x46,0x31, + 0x31,0x46,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x09,0x00,0x2A,0xF6,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x0A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0B,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0F1C_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x1C,0x00,0x00,0x00,0x22,0x00,0x0A + }; + + byte SZL_ID_0036_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x00,0x36,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0F36_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x36,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0025_IDX_XXXX[16] = { + 0xFF,0x09,0x00,0x0C,0x00,0x25,0x00,0x00,0x00,0x04,0x00,0x01,0x01,0x0C,0x3D,0x00 + }; + + byte SZL_ID_0F25_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x25,0x00,0x00,0x00,0x04,0x00,0x01 + }; + + byte SZL_ID_0037_IDX_XXXX[60] = { + 0xFF,0x09,0x00,0x38,0x00,0x37,0x00,0x00,0x00,0x30,0x00,0x01,0x07,0xFE,0xC0,0xA8,0x01,0x0A,0xFF, + 0xFF,0xFF,0x00,0xC0,0xA8,0x01,0x0A,0x00,0x1B,0x1B,0x1D,0x1A,0x2D,0x01,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x8F,0x88,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00 + }; + + byte SZL_ID_0F37_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x37,0x00,0x00,0x00,0x30,0x00,0x01 + }; + + byte SZL_ID_0074_IDX_XXXX[40] = { + 0xFF,0x09,0x00,0x24,0x00,0x74,0x00,0x00,0x00,0x04,0x00,0x07,0x00,0x01,0x00,0x00,0x00,0x04,0x01, + 0x00,0x00,0x05,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x0B,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x15, + 0x00,0x00 + }; + + byte SZL_ID_0F74_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x74,0x00,0x00,0x00,0x04,0x00,0x07 + }; + + byte SZL_ID_0591_IDX_XXXX[76] = { + 0xFF,0x09,0x00,0x48,0x05,0x91,0x00,0x00,0x00,0x10,0x00,0x04,0x00,0x00,0x02,0x01,0x07,0xFF,0xC4, + 0xC0,0xC4,0xC0,0x00,0x00,0xB4,0x02,0x00,0x11,0x00,0x00,0x02,0x02,0x07,0xFE,0xA7,0xC4,0xA7,0xC4, + 0x00,0x00,0xB4,0x02,0x00,0x11,0x00,0x00,0x02,0x03,0x07,0xFD,0x97,0xC5,0x97,0xC5,0x00,0x00,0xB4, + 0x02,0x00,0x11,0x00,0x00,0x02,0x04,0x07,0xFC,0x97,0xC5,0x97,0xC5,0x00,0x00,0xB4,0x02,0x00,0x11 + }; + + byte SZL_ID_0A91_IDX_XXXX[44] = { + 0xFF,0x09,0x00,0x28,0x0A,0x91,0x00,0x00,0x00,0x10,0x00,0x02,0x01,0x00,0x02,0x01,0x07,0xFF,0xC4, + 0xC0,0xC4,0xC0,0x00,0x01,0xFE,0x02,0x00,0x11,0x80,0x00,0x00,0x00,0x07,0xFB,0xA7,0xC4,0xA7,0xC4, + 0x00,0x01,0xFE,0x02,0x00,0x11 + }; + + byte SZL_ID_0F92_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x92,0x00,0x00,0x00,0x10,0x00,0x01 + }; + + byte SZL_ID_0294_IDX_XXXX[270] = { + 0xFF,0x09,0x01,0x0A,0x02,0x94,0x00,0x00,0x01,0x02,0x00,0x01,0x00,0x00,0x03,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0794_IDX_XXXX[270] = { + 0xFF,0x09,0x01,0x0A,0x07,0x94,0x00,0x00,0x01,0x02,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0F94_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x94,0x00,0x00,0x01,0x02,0x00,0x01 + }; + + byte SZL_ID_0095_IDX_XXXX[52] = { + 0xFF,0x09,0x00,0x30,0x00,0x95,0x00,0x00,0x00,0x28,0x00,0x01,0x64,0x00,0x02,0x02,0x07,0xFE,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0F95_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x95,0x00,0x00,0x00,0x28,0x00,0x01 + }; + + byte SZL_ID_00A0_IDX_XXXX[212] = { + 0xFF,0x09,0x00,0xD0,0x00,0xA0,0x00,0x00,0x00,0x14,0x00,0x0A,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86,0x13,0x81,0xFE,0x64,0xC7,0x72, + 0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x76,0x43,0x01,0xFF,0x46,0xC7, + 0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x29,0x66,0x43,0x04,0xFF,0x84, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x52,0x13,0x90,0x16,0x43,0x02,0xFF, + 0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x56,0x13,0x81, + 0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x46,0x43, + 0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x79,0x46, + 0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x51,0x19,0x48, + 0x26,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50,0x45, + 0x09,0x96,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50, + 0x45,0x09,0x86 + }; + + byte SZL_ID_0FA0_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0xA0,0x00,0x00,0x00,0x14,0x01,0xF4 + }; + + byte SZL_ID_0017_IDX_XXXX[458] = { // <--Wrapped to 458 bytes + 0xFF,0x09,0x01,0xC6,0x00,0x17,0x00,0x00,0x00,0x04,0x00,0x73,0x00,0x00,0x00,0x01,0x00,0x01,0x00, + 0x03,0x00,0x02,0x00,0x02,0x00,0x03,0x00,0x01,0x00,0x04,0x00,0x01,0x00,0x07,0x00,0x01,0x00,0x16, + 0x00,0x01,0x00,0x64,0x00,0x01,0x00,0x65,0x00,0x01,0x00,0x66,0x00,0x01,0x00,0x67,0x00,0x01,0x00, + 0x7A,0x00,0x01,0x00,0xC8,0x00,0x01,0x00,0xD2,0x00,0x01,0x02,0xBC,0x00,0x01,0x02,0xBD,0x00,0x01, + 0x02,0xBE,0x00,0x01,0x02,0xBF,0x00,0x01,0x02,0xC0,0x00,0x01,0x02,0xC1,0x00,0x01,0x02,0xC2,0x00, + 0x01,0x02,0xC3,0x00,0x01,0x02,0xC4,0x00,0x01,0x02,0xC5,0x00,0x01,0x02,0xC6,0x00,0x01,0x02,0xC7, + 0x00,0x01,0x02,0xC8,0x00,0x01,0x02,0xC9,0x00,0x01,0x02,0xCA,0x00,0x01,0x02,0xCB,0x00,0x01,0x02, + 0xCC,0x00,0x01,0x02,0xCD,0x00,0x01,0x02,0xCE,0x00,0x01,0x02,0xCF,0x00,0x01,0x02,0xD0,0x00,0x01, + 0x02,0xD1,0x00,0x01,0x02,0xD2,0x00,0x01,0x02,0xD3,0x00,0x01,0x02,0xD4,0x00,0x01,0x02,0xD5,0x00, + 0x01,0x02,0xD6,0x00,0x01,0x02,0xD7,0x00,0x01,0x02,0xD8,0x00,0x01,0x02,0xD9,0x00,0x01,0x02,0xDA, + 0x00,0x01,0x02,0xDB,0x00,0x01,0x02,0xDC,0x00,0x01,0x02,0xDD,0x00,0x01,0x02,0xDE,0x00,0x01,0x02, + 0xDF,0x00,0x01,0x02,0xE0,0x00,0x01,0x02,0xE1,0x00,0x01,0x02,0xE2,0x00,0x01,0x02,0xE3,0x00,0x01, + 0x02,0xE4,0x00,0x01,0x02,0xE5,0x00,0x01,0x02,0xE6,0x00,0x01,0x02,0xE7,0x00,0x01,0x02,0xE8,0x00, + 0x01,0x02,0xE9,0x00,0x01,0x02,0xEA,0x00,0x01,0x02,0xEB,0x00,0x01,0x02,0xEC,0x00,0x01,0x02,0xED, + 0x00,0x01,0x02,0xEE,0x00,0x01,0x02,0xEF,0x00,0x01,0x02,0xF0,0x00,0x01,0x02,0xF1,0x00,0x01,0x02, + 0xF2,0x00,0x01,0x02,0xF3,0x00,0x01,0x02,0xF4,0x00,0x01,0x02,0xF5,0x00,0x01,0x02,0xF6,0x00,0x01, + 0x02,0xF7,0x00,0x01,0x02,0xF8,0x00,0x01,0x02,0xF9,0x00,0x01,0x02,0xFA,0x00,0x01,0x02,0xFB,0x00, + 0x01,0x02,0xFC,0x00,0x01,0x02,0xFD,0x00,0x01,0x02,0xFE,0x00,0x01,0x02,0xFF,0x00,0x01,0x03,0x00, + 0x00,0x01,0x03,0x01,0x00,0x01,0x03,0x02,0x00,0x01,0x03,0x03,0x00,0x01,0x03,0x04,0x00,0x01,0x03, + 0x05,0x00,0x01,0x03,0x06,0x00,0x01,0x03,0x07,0x00,0x01,0x03,0x08,0x00,0x01,0x03,0x09,0x00,0x01, + 0x03,0x0A,0x00,0x01,0x03,0x0B,0x00,0x01,0x03,0x0C,0x00,0x01,0x03,0x0D,0x00,0x01,0x03,0x0E,0x00, + 0x01,0x03,0x0F,0x00,0x01,0x03,0x10,0x00,0x01,0x03,0x11,0x00,0x01,0x03,0x12,0x00,0x01,0x03,0x13, + 0x00,0x01,0x03,0x14,0x00,0x01,0x03,0x15,0x00,0x01,0x03,0x16,0x00,0x01,0x03,0x17,0x00,0x01,0x03, + 0x18,0x00,0x01,0x03,0x19,0x00,0x01,0x03,0x1A,0x00,0x01,0x03,0x1B,0x00,0x01,0x03,0x1C,0x00,0x01, + 0x03,0x1D + }; + + byte SZL_ID_0F17_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x17,0x00,0x00,0x00,0x04,0x00,0x73 + }; + + byte SZL_ID_0018_IDX_XXXX[28] = { + 0xFF,0x09,0x00,0x18,0x00,0x18,0x00,0x00,0x00,0x04,0x00,0x04,0x00,0x00,0x00,0x08,0x00,0x01,0x00, + 0x08,0x00,0x02,0x00,0x08,0x00,0x03,0x00,0x08 + }; + + byte SZL_ID_0F18_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x18,0x00,0x00,0x00,0x04,0x00,0x04 + }; + + byte SZL_ID_001A_IDX_XXXX[48] = { + 0xFF,0x09,0x00,0x2C,0x00,0x1A,0x00,0x00,0x00,0x0C,0x00,0x03,0x09,0x01,0x01,0x05,0x00,0x08,0x00, + 0x00,0x00,0x80,0x00,0x00,0x12,0x01,0x00,0x02,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x18,0x01, + 0x00,0x02,0x00,0x02,0x00,0x00,0x00,0x02,0x00,0x00 + }; + + byte SZL_ID_0F1A_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x1A,0x00,0x00,0x00,0x0C,0x00,0x03 + }; + + byte SZL_ID_001B_IDX_XXXX[132] = { + 0xFF,0x09,0x00,0x80,0x00,0x1B,0x00,0x00,0x00,0x14,0x00,0x06,0x09,0x00,0x00,0x00,0x00,0x02,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0C,0x06,0x00,0x00,0x00,0x00,0x09,0x00,0x00,0x70,0x00,0x02, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0xF3,0xFA,0x00,0x00,0x00,0x00,0x12,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x26,0x00,0x00,0x00,0x00,0x12,0x00,0x00,0x70, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0xFF,0xDA,0x00,0x00,0x00,0x00,0x18,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x00, + 0x00,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0F1B_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x1B,0x00,0x00,0x00,0x14,0x00,0x06 + }; + + byte SZL_ID_0021_IDX_XXXX[100] = { + 0xFF,0x09,0x00,0x60,0x00,0x21,0x00,0x00,0x00,0x04,0x00,0x16,0x01,0x01,0x01,0x01,0x01,0x11,0xFE, + 0x0A,0x01,0x21,0xFE,0x14,0x01,0x22,0xFE,0x15,0x01,0x33,0xFE,0x20,0x01,0x34,0xFE,0x21,0x01,0x35, + 0xFE,0x22,0x01,0x36,0xFE,0x23,0x01,0x41,0xFE,0x28,0x01,0x55,0xFE,0x37,0x01,0x56,0xFE,0x38,0x01, + 0x57,0xFE,0x39,0x01,0x64,0xFE,0x3D,0x00,0x01,0xFE,0x50,0x00,0x42,0xFE,0x52,0x00,0x61,0xFE,0x53, + 0x00,0xA1,0xFE,0x55,0x00,0xC1,0xFE,0x56,0x00,0xD2,0xFE,0x57,0x00,0x81,0xFE,0x64,0x00,0x21,0xFE, + 0x79,0x00,0x42,0xFE,0x7A + }; + + byte SZL_ID_0A21_IDX_XXXX[16] = { + 0xFF,0x09,0x00,0x0C,0x0A,0x21,0x00,0x00,0x00,0x04,0x00,0x01,0x01,0x01,0x01,0x01 + }; + + byte SZL_ID_0F21_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x21,0x00,0x00,0x00,0x04,0x00,0x16 + }; + + byte SZL_ID_0023_IDX_XXXX[228] = { + 0xFF,0x09,0x00,0xE0,0x00,0x23,0x00,0x00,0x00,0x12,0x00,0x0C,0x1A,0x00,0x00,0x00,0x10,0xBE,0x00, + 0x00,0x08,0x00,0x0B,0xC0,0xFC,0x01,0xFF,0xFF,0xFF,0xF3,0x19,0x00,0x00,0x00,0x10,0x01,0x00,0x00, + 0x08,0x00,0x0B,0xC0,0xFC,0x01,0xFF,0xFF,0xFF,0xF3,0x10,0x00,0x00,0x00,0x10,0x26,0x00,0x00,0x08, + 0x00,0x0B,0xC0,0xFC,0x01,0xFF,0xFF,0xFF,0xF3,0x0C,0x00,0x00,0x00,0x10,0x01,0x00,0x00,0x08,0x00, + 0x0B,0xC0,0xFC,0x01,0xFF,0xFF,0xFF,0xF3,0x0B,0x00,0x00,0x00,0x10,0x01,0x00,0x00,0x08,0x00,0x0B, + 0xC0,0xFC,0x01,0xFF,0xFF,0xFF,0xF3,0x0A,0x00,0x00,0x00,0x10,0x01,0x00,0x00,0x08,0x00,0x0B,0xC0, + 0xFC,0x01,0xFF,0xFF,0xFF,0xF3,0x09,0x00,0x00,0x00,0x10,0x01,0x00,0x00,0x08,0x00,0x0B,0xC0,0xFC, + 0x01,0xFF,0xFF,0xFF,0xF3,0x04,0x00,0x00,0x00,0x10,0x01,0x00,0x00,0x08,0x00,0x0B,0xC0,0xFC,0x01, + 0xFF,0xFF,0xFF,0xF3,0x03,0x00,0x00,0x00,0x10,0x01,0x00,0x00,0x08,0x00,0x0B,0xC0,0xFC,0x01,0xFF, + 0xFF,0xFF,0xF3,0x02,0x00,0x00,0x00,0x10,0x16,0x00,0x00,0x08,0x00,0x0B,0xC0,0xFC,0x01,0xFF,0xFF, + 0xFF,0xF3,0x01,0x00,0x00,0x00,0x10,0x01,0x00,0x00,0x08,0x00,0x0B,0xC0,0xFC,0x01,0xFF,0xFF,0xFF, + 0xF3,0x1B,0x00,0x00,0x00,0x10,0x01,0x00,0x00,0x08,0x00,0x0B,0xC0,0xFC,0x01,0xFF,0xFF,0xFF,0xF3 + }; + + byte SZL_ID_0F23_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x23,0x00,0x00,0x00,0x12,0x00,0x0C + }; + + byte SZL_ID_0024_IDX_XXXX[92] = { + 0xFF,0x09,0x00,0x58,0x00,0x24,0x00,0x00,0x00,0x14,0x00,0x04,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86,0x43,0x01,0xFF,0x46,0xC7,0x72, + 0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x29,0x66,0x43,0x04,0xFF,0x84,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x52,0x13,0x90,0x16,0x43,0x02,0xFF,0x68, + 0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x56 + }; + + byte SZL_ID_0124_IDX_XXXX[32] = { + 0xFF,0x09,0x00,0x1C,0x01,0x24,0x00,0x00,0x00,0x14,0x00,0x01,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86 + }; + + byte SZL_ID_0424_IDX_XXXX[32] = { + 0xFF,0x09,0x00,0x1C,0x04,0x24,0x00,0x00,0x00,0x14,0x00,0x01,0x51,0x44,0xFF, + 0x08, // <-- CPU Status + 0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x05,0x02,0x01,0x55,0x90,0x67 + }; + + byte SZL_ID_0038_IDX_XXXX[78] = { + 0xFF,0x09,0x00,0x4A,0x00,0x38,0x00,0x00,0x00,0x42,0x00,0x01,0x00,0x01,0x07,0xFE,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xAB,0xE3,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0xEE,0x49,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00 + }; + + byte SZL_ID_0F38_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x38,0x00,0x00,0x00,0x42,0x00,0x01 + }; + + byte SZL_ID_003A_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x00,0x3A,0x00,0x00,0x00,0x94,0x00,0x00 + }; + + byte SZL_ID_0F3A_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x3A,0x00,0x00,0x00,0x94,0x00,0x08 + }; + + byte SZL_ID_0F9A_IDX_XXXX[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x9A,0x00,0x00,0x01,0x1C,0x00,0x01 + }; + + byte SZL_ID_0D91_IDX_0000[28] = { + 0xFF,0x09,0x00,0x18,0x0D,0x91,0x00,0x00,0x00,0x10,0x00,0x01,0x00,0x00,0x02,0x00,0x7F,0xFF,0x00, + 0xC0,0x00,0xC0,0x00,0x00,0xB4,0x02,0x00,0x11 + }; + + byte SZL_ID_0092_IDX_0000[28] = { + 0xFF,0x09,0x00,0x18,0x00,0x92,0x00,0x00,0x00,0x10,0x00,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0292_IDX_0000[28] = { + 0xFF,0x09,0x00,0x18,0x02,0x92,0x00,0x00,0x00,0x10,0x00,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0692_IDX_0000[28] = { + 0xFF,0x09,0x00,0x18,0x06,0x92,0x00,0x00,0x00,0x10,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0094_IDX_0000[270] = { + 0xFF,0x09,0x01,0x0A,0x00,0x94,0x00,0x00,0x01,0x02,0x00,0x01,0x00,0x00,0x03,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0D97_IDX_0000[60] = { + 0xFF,0x09,0x00,0x38,0x0D,0x97,0x00,0x00,0x00,0x30,0x00,0x01,0x7F,0xFF,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x02,0x00,0x04,0x00,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x11,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00 + }; + + byte SZL_ID_0111_IDX_0001[40] = { + 0xFF,0x09,0x00,0x24,0x01,0x11,0x00,0x01,0x00,0x1C,0x00,0x01,0x00,0x01,0x36,0x45,0x53,0x37,0x20, + 0x33,0x31,0x35,0x2D,0x32,0x45,0x48,0x31,0x34,0x2D,0x30,0x41,0x42,0x30,0x20,0x00,0xC0,0x00,0x04, + 0x00,0x01 + }; + + byte SZL_ID_0111_IDX_0006[40] = { + 0xFF,0x09,0x00,0x24,0x01,0x11,0x00,0x06,0x00,0x1C,0x00,0x01,0x00,0x06,0x36,0x45,0x53,0x37,0x20, + 0x33,0x31,0x35,0x2D,0x32,0x45,0x48,0x31,0x34,0x2D,0x30,0x41,0x42,0x30,0x20,0x00,0xC0,0x00,0x04, + 0x00,0x01 + }; + + byte SZL_ID_0111_IDX_0007[40] = { + 0xFF,0x09,0x00,0x24,0x01,0x11,0x00,0x07,0x00,0x1C,0x00,0x01,0x00,0x07,0x20,0x20,0x20,0x20,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x00,0xC0,0x56,0x03, + 0x02,0x06 + }; + + byte SZL_ID_0F11_IDX_0001[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x11,0x00,0x00,0x00,0x1C,0x00,0x04 + }; + + byte SZL_ID_0F11_IDX_0006[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x11,0x00,0x00,0x00,0x1C,0x00,0x04 + }; + + byte SZL_ID_0F11_IDX_0007[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x11,0x00,0x00,0x00,0x1C,0x00,0x04 + }; + + byte SZL_ID_0112_IDX_0000[14] = { + 0xFF,0x09,0x00,0x0A,0x01,0x12,0x00,0x00,0x00,0x02,0x00,0x01,0x00,0x01 + }; + + byte SZL_ID_0112_IDX_0100[16] = { + 0xFF,0x09,0x00,0x0C,0x01,0x12,0x01,0x00,0x00,0x02,0x00,0x02,0x01,0x01,0x01,0x04 + }; + + byte SZL_ID_0112_IDX_0200[12] = { + 0xFF,0x09,0x00,0x08,0x01,0x12,0x02,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0112_IDX_0400[12] = { + 0xFF,0x09,0x00,0x08,0x01,0x12,0x04,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0F12_IDX_0000[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x12,0x00,0x00,0x00,0x02,0x00,0x17 + }; + + byte SZL_ID_0F12_IDX_0100[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x12,0x00,0x00,0x00,0x02,0x00,0x17 + }; + + byte SZL_ID_0F12_IDX_0200[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x12,0x00,0x00,0x00,0x02,0x00,0x17 + }; + + byte SZL_ID_0F12_IDX_0400[12] = { + 0xFF,0x09,0x00,0x08,0x0F,0x12,0x00,0x00,0x00,0x02,0x00,0x17 + }; + + byte SZL_ID_0113_IDX_0001[48] = { + 0xFF,0x09,0x00,0x2C,0x01,0x13,0x00,0x01,0x00,0x24,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x06,0x00, + 0x00,0x00,0x11,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x00,0x00,0x26,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0115_IDX_0800[22] = { + 0xFF,0x09,0x00,0x12,0x01,0x15,0x08,0x00,0x00,0x0A,0x00,0x01,0x08,0x00,0x00,0x16,0x03,0xD1,0x00, + 0x00,0xFF,0xFE + }; + + byte SZL_ID_011C_IDX_0001[46] = { + 0xFF,0x09,0x00,0x2A,0x01,0x1C,0x00,0x01,0x00,0x22,0x00,0x01,0x00,0x01,0x53,0x4D,0x41,0x52,0x54, + 0x37,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_011C_IDX_0002[46] = { + 0xFF,0x09,0x00,0x2A,0x01,0x1C,0x00,0x02,0x00,0x22,0x00,0x01,0x00,0x02,0x43,0x50,0x55,0x20,0x33, + 0x31,0x35,0x2D,0x32,0x20,0x50,0x4E,0x2F,0x44,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_011C_IDX_0003[46] = { + 0xFF,0x09,0x00,0x2A,0x01,0x1C,0x00,0x03,0x00,0x22,0x00,0x01,0x00,0x03,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_011C_IDX_0004[46] = { + 0xFF,0x09,0x00,0x2A,0x01,0x1C,0x00,0x04,0x00,0x22,0x00,0x01,0x00,0x04,0x4F,0x72,0x69,0x67,0x69, + 0x6E,0x61,0x6C,0x20,0x53,0x69,0x65,0x6D,0x65,0x6E,0x73,0x20,0x45,0x71,0x75,0x69,0x70,0x6D,0x65, + 0x6E,0x74,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_011C_IDX_0005[46] = { + 0xFF,0x09,0x00,0x2A,0x01,0x1C,0x00,0x05,0x00,0x22,0x00,0x01,0x00,0x05,0x53,0x20,0x43,0x2D,0x43, + 0x32,0x55,0x52,0x32,0x38,0x39,0x32,0x32,0x30,0x31,0x32,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_011C_IDX_0007[46] = { + 0xFF,0x09,0x00,0x2A,0x01,0x1C,0x00,0x07,0x00,0x22,0x00,0x01,0x00,0x07,0x43,0x50,0x55,0x20,0x33, + 0x31,0x35,0x2D,0x32,0x20,0x50,0x4E,0x2F,0x44,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_011C_IDX_0008[46] = { + 0xFF,0x09,0x00,0x2A,0x01,0x1C,0x00,0x08,0x00,0x22,0x00,0x01,0x00,0x08,0x4D,0x4D,0x43,0x20,0x32, + 0x36,0x37,0x46,0x46,0x31,0x31,0x46,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_011C_IDX_0009[46] = { + 0xFF,0x09,0x00,0x2A,0x01,0x1C,0x00,0x09,0x00,0x22,0x00,0x01,0x00,0x09,0x00,0x2A,0xF6,0x00,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_011C_IDX_000A[46] = { + 0xFF,0x09,0x00,0x2A,0x01,0x1C,0x00,0x0A,0x00,0x22,0x00,0x01,0x00,0x0A,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_011C_IDX_000B[46] = { + 0xFF,0x09,0x00,0x2A,0x01,0x1C,0x00,0x0B,0x00,0x22,0x00,0x01,0x00,0x0B,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0222_IDX_0001[40] = { + 0xFF,0x09,0x00,0x24,0x02,0x22,0x00,0x01,0x00,0x1C,0x00,0x01,0x11,0x03,0x01,0x01,0xC8,0x58,0x00, + 0x00,0x00,0x00,0x00,0x01,0x94,0x02,0x05,0x02,0x01,0x56,0x64,0x77,0x00,0x10,0x00,0x08,0x00,0x00, + 0x00,0x00 + }; + + byte SZL_ID_0222_IDX_000A[40] = { + 0xFF,0x09,0x00,0x24,0x02,0x22,0x00,0x0A,0x00,0x1C,0x00,0x01,0x10,0x11,0x02,0x0A,0x00,0x50,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00 + }; + + byte SZL_ID_0222_IDX_0014[40] = { + 0xFF,0x09,0x00,0x24,0x02,0x22,0x00,0x14,0x00,0x1C,0x00,0x01,0x10,0x21,0x03,0x14,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00 + }; + + byte SZL_ID_0222_IDX_0028[40] = { + 0xFF,0x09,0x00,0x24,0x02,0x22,0x00,0x28,0x00,0x1C,0x00,0x01,0x10,0x41,0x10,0x28,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00 + }; + + byte SZL_ID_0222_IDX_0050[40] = { + 0xFF,0x09,0x00,0x24,0x02,0x22,0x00,0x50,0x00,0x1C,0x00,0x01,0x35,0x01,0xFE,0x50,0xC8,0x58,0x00, + 0x00,0x00,0x00,0x00,0x96,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x00,0x00, + 0x00,0x00 + }; + + byte SZL_ID_0222_IDX_0064[40] = { + 0xFF,0x09,0x00,0x24,0x02,0x22,0x00,0x64,0x00,0x1C,0x00,0x01,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43, + 0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x76,0x00,0x00,0x00,0x08,0x00,0x00, + 0x00,0x00 + }; + + byte SZL_ID_0125_IDX_0000[16] = { + 0xFF,0x09,0x00,0x0C,0x01,0x25,0x00,0x00,0x00,0x04,0x00,0x01,0x00,0x03,0x01,0x00 + }; + + byte SZL_ID_0125_IDX_0001[16] = { + 0xFF,0x09,0x00,0x0C,0x01,0x25,0x00,0x01,0x00,0x04,0x00,0x01,0x01,0x0C,0x3D,0x00 + }; + + byte SZL_ID_0225_IDX_0001[16] = { + 0xFF,0x09,0x00,0x0C,0x02,0x25,0x00,0x01,0x00,0x04,0x00,0x01,0x00,0x03,0x01,0x00 + }; + + byte SZL_ID_0132_IDX_0001[52] = { + 0xFF,0x09,0x00,0x30,0x01,0x32,0x00,0x01,0x00,0x28,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0E,0x00,0x00,0x00,0x14,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03 + }; + + byte SZL_ID_0132_IDX_0002[52] = { + 0xFF,0x09,0x00,0x30,0x01,0x32,0x00,0x02,0x00,0x28,0x00,0x01,0x00,0x02,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x0E,0x00,0x00,0x00,0x00,0x06,0x01,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0132_IDX_0003[52] = { + 0xFF,0x09,0x00,0x30,0x01,0x32,0x00,0x03,0x00,0x28,0x00,0x01,0x00,0x03,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0132_IDX_0004[52] = { + 0xFF,0x09,0x00,0x30,0x01,0x32,0x00,0x04,0x00,0x28,0x00,0x01,0x00,0x04,0x00,0x01,0x00,0x00,0x00, + 0x01,0x00,0x02,0x00,0x00,0x00,0x00,0x56,0x56,0x10,0x01,0x33,0x7B,0x02,0x00,0x75,0xF4,0x02,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0132_IDX_0005[52] = { + 0xFF,0x09,0x00,0x30,0x01,0x32,0x00,0x05,0x00,0x28,0x00,0x01,0x00,0x05,0x00,0x00,0x00,0x01,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0132_IDX_0006[52] = { + 0xFF,0x09,0x00,0x30,0x01,0x32,0x00,0x06,0x00,0x28,0x00,0x01,0x00,0x06,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0132_IDX_0007[52] = { + 0xFF,0x09,0x00,0x30,0x01,0x32,0x00,0x07,0x00,0x28,0x00,0x01,0x00,0x07,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0132_IDX_0008[52] = { + 0xFF,0x09,0x00,0x30,0x01,0x32,0x00,0x08,0x00,0x28,0x00,0x01,0x00,0x08,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x05,0x02, + 0x01,0x56,0x94,0x57,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0132_IDX_0009[52] = { + 0xFF,0x09,0x00,0x30,0x01,0x32,0x00,0x09,0x00,0x28,0x00,0x01,0x00,0x09,0x00,0x02,0xDC,0x6C,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0132_IDX_000A[52] = { + 0xFF,0x09,0x00,0x30,0x01,0x32,0x00,0x0A,0x00,0x28,0x00,0x01,0x00,0x0A,0x00,0x02,0xDC,0x6C,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0132_IDX_000B[52] = { + 0xFF,0x09,0x00,0x30,0x01,0x32,0x00,0x0B,0x00,0x28,0x00,0x01,0x00,0x0B,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0132_IDX_000C[52] = { + 0xFF,0x09,0x00,0x30,0x01,0x32,0x00,0x0C,0x00,0x28,0x00,0x01,0x00,0x0C,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0232_IDX_0001[52] = { + 0xFF,0x09,0x00,0x30,0x02,0x32,0x00,0x01,0x00,0x28,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00, + 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0E,0x00,0x00,0x00,0x14,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03 + }; + + byte SZL_ID_0232_IDX_0004[52] = { + 0xFF,0x09,0x00,0x30,0x02,0x32,0x00,0x04,0x00,0x28,0x00,0x01,0x00,0x04,0x00,0x01,0x00,0x00,0x00, + 0x01,0x00,0x02,0x00,0x00,0x00,0x00,0x56,0x56,0x10,0x01,0x33,0x7B,0x02,0x00,0x75,0xF4,0x02,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0137_IDX_07FE[60] = { + 0xFF,0x09,0x00,0x38,0x01,0x37,0x07,0xFE,0x00,0x30,0x00,0x01,0x07,0xFE,0xC0,0xA8,0x01,0x0A,0xFF, + 0xFF,0xFF,0x00,0xC0,0xA8,0x01,0x0A,0x00,0x1B,0x1B,0x1D,0x1A,0x2D,0x01,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x8F,0x88,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00 + }; + + byte SZL_ID_0174_IDX_0001[16] = { + 0xFF,0x09,0x00,0x0C,0x01,0x74,0x00,0x01,0x00,0x04,0x00,0x01,0x00,0x01,0x00,0x00 + }; + + byte SZL_ID_0174_IDX_0004[16] = { + 0xFF,0x09,0x00,0x0C,0x01,0x74,0x00,0x04,0x00,0x04,0x00,0x01,0x00,0x04,0x01,0x00 + }; + + byte SZL_ID_0174_IDX_0005[16] = { + 0xFF,0x09,0x00,0x0C,0x01,0x74,0x00,0x05,0x00,0x04,0x00,0x01,0x00,0x05,0x00,0x00 + }; + + byte SZL_ID_0174_IDX_0006[16] = { + 0xFF,0x09,0x00,0x0C,0x01,0x74,0x00,0x06,0x00,0x04,0x00,0x01,0x00,0x06,0x00,0x00 + }; + + byte SZL_ID_0174_IDX_000B[16] = { + 0xFF,0x09,0x00,0x0C,0x01,0x74,0x00,0x0B,0x00,0x04,0x00,0x01,0x00,0x0B,0x00,0x00 + }; + + byte SZL_ID_0174_IDX_000C[16] = { + 0xFF,0x09,0x00,0x0C,0x01,0x74,0x00,0x0C,0x00,0x04,0x00,0x01,0x00,0x0C,0x00,0x00 + }; + + byte SZL_ID_0194_IDX_0064[270] = { + 0xFF,0x09,0x01,0x0A,0x01,0x94,0x00,0x64,0x01,0x02,0x00,0x01,0x00,0x64,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0694_IDX_0064[270] = { + 0xFF,0x09,0x01,0x0A,0x06,0x94,0x00,0x64,0x01,0x02,0x00,0x01,0x00,0x64,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_01A0_IDX_0000[12] = { + 0xFF,0x09,0x00,0x08,0x01,0xA0,0x00,0x00,0x00,0x14,0x00,0x00 + }; + + byte SZL_ID_01A0_IDX_0001[32] = { + 0xFF,0x09,0x00,0x1C,0x01,0xA0,0x00,0x00,0x00,0x14,0x00,0x01,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86 + }; + + byte SZL_ID_01A0_IDX_0002[52] = { + 0xFF,0x09,0x00,0x30,0x01,0xA0,0x00,0x00,0x00,0x14,0x00,0x02,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86,0x13,0x81,0xFE,0x64,0xC7,0x72, + 0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x76 + }; + + byte SZL_ID_01A0_IDX_0003[72] = { + 0xFF,0x09,0x00,0x44,0x01,0xA0,0x00,0x00,0x00,0x14,0x00,0x03,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86,0x13,0x81,0xFE,0x64,0xC7,0x72, + 0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x76,0x43,0x01,0xFF,0x46,0xC7, + 0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x29,0x66 + }; + + byte SZL_ID_01A0_IDX_0004[92] = { + 0xFF,0x09,0x00,0x58,0x01,0xA0,0x00,0x00,0x00,0x14,0x00,0x04,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86,0x13,0x81,0xFE,0x64,0xC7,0x72, + 0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x76,0x43,0x01,0xFF,0x46,0xC7, + 0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x29,0x66,0x43,0x04,0xFF,0x84, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x52,0x13,0x90,0x16 + }; + + byte SZL_ID_01A0_IDX_0005[112] = { + 0xFF,0x09,0x00,0x6C,0x01,0xA0,0x00,0x00,0x00,0x14,0x00,0x05,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86,0x13,0x81,0xFE,0x64,0xC7,0x72, + 0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x76,0x43,0x01,0xFF,0x46,0xC7, + 0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x29,0x66,0x43,0x04,0xFF,0x84, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x52,0x13,0x90,0x16,0x43,0x02,0xFF, + 0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x56 + }; + + byte SZL_ID_01A0_IDX_0006[132] = { + 0xFF,0x09,0x00,0x80,0x01,0xA0,0x00,0x00,0x00,0x14,0x00,0x06,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86,0x13,0x81,0xFE,0x64,0xC7,0x72, + 0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x76,0x43,0x01,0xFF,0x46,0xC7, + 0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x29,0x66,0x43,0x04,0xFF,0x84, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x52,0x13,0x90,0x16,0x43,0x02,0xFF, + 0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x56,0x13,0x81, + 0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x46 + }; + + byte SZL_ID_01A0_IDX_0007[152] = { + 0xFF,0x09,0x00,0x94,0x01,0xA0,0x00,0x00,0x00,0x14,0x00,0x07,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86,0x13,0x81,0xFE,0x64,0xC7,0x72, + 0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x76,0x43,0x01,0xFF,0x46,0xC7, + 0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x29,0x66,0x43,0x04,0xFF,0x84, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x52,0x13,0x90,0x16,0x43,0x02,0xFF, + 0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x56,0x13,0x81, + 0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x46,0x43, + 0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x79,0x46 + }; + + byte SZL_ID_01A0_IDX_0008[172] = { + 0xFF,0x09,0x00,0xA8,0x01,0xA0,0x00,0x00,0x00,0x14,0x00,0x08,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86,0x13,0x81,0xFE,0x64,0xC7,0x72, + 0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x76,0x43,0x01,0xFF,0x46,0xC7, + 0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x29,0x66,0x43,0x04,0xFF,0x84, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x52,0x13,0x90,0x16,0x43,0x02,0xFF, + 0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x56,0x13,0x81, + 0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x46,0x43, + 0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x79,0x46, + 0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x51,0x19,0x48, + 0x26 + }; + + byte SZL_ID_01A0_IDX_0009[192] = { + 0xFF,0x09,0x00,0xBC,0x01,0xA0,0x00,0x00,0x00,0x14,0x00,0x09,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86,0x13,0x81,0xFE,0x64,0xC7,0x72, + 0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x76,0x43,0x01,0xFF,0x46,0xC7, + 0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x29,0x66,0x43,0x04,0xFF,0x84, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x52,0x13,0x90,0x16,0x43,0x02,0xFF, + 0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x56,0x13,0x81, + 0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x46,0x43, + 0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x79,0x46, + 0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x51,0x19,0x48, + 0x26,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50,0x45, + 0x09,0x96 + }; + + byte SZL_ID_01A0_IDX_000A[212] = { + 0xFF,0x09,0x00,0xD0,0x01,0xA0,0x00,0x00,0x00,0x14,0x00,0x0A,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86,0x13,0x81,0xFE,0x64,0xC7,0x72, + 0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x76,0x43,0x01,0xFF,0x46,0xC7, + 0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x29,0x66,0x43,0x04,0xFF,0x84, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x52,0x13,0x90,0x16,0x43,0x02,0xFF, + 0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x56,0x13,0x81, + 0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x46,0x43, + 0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x79,0x46, + 0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x51,0x19,0x48, + 0x26,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50,0x45, + 0x09,0x96,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50, + 0x45,0x09,0x86 + }; + + byte SZL_ID_01A0_IDX_000B[232] = { + 0xFF,0x09,0x00,0xE4,0x01,0xA0,0x00,0x00,0x00,0x14,0x00,0x0B,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86,0x13,0x81,0xFE,0x64,0xC7,0x72, + 0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x76,0x43,0x01,0xFF,0x46,0xC7, + 0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x29,0x66,0x43,0x04,0xFF,0x84, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x52,0x13,0x90,0x16,0x43,0x02,0xFF, + 0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x56,0x13,0x81, + 0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x46,0x43, + 0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x79,0x46, + 0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x51,0x19,0x48, + 0x26,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50,0x45, + 0x09,0x96,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50, + 0x45,0x09,0x86,0x43,0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23, + 0x50,0x45,0x00,0x96 + }; + + byte SZL_ID_01A0_IDX_000C[252] = { + 0xFF,0x09,0x00,0xF8,0x01,0xA0,0x00,0x00,0x00,0x14,0x00,0x0C,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86,0x13,0x81,0xFE,0x64,0xC7,0x72, + 0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x76,0x43,0x01,0xFF,0x46,0xC7, + 0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x29,0x66,0x43,0x04,0xFF,0x84, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x52,0x13,0x90,0x16,0x43,0x02,0xFF, + 0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x56,0x13,0x81, + 0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x46,0x43, + 0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x79,0x46, + 0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x51,0x19,0x48, + 0x26,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50,0x45, + 0x09,0x96,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50, + 0x45,0x09,0x86,0x43,0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23, + 0x50,0x45,0x00,0x96,0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04, + 0x23,0x50,0x42,0x54,0x66 + }; + + byte SZL_ID_01A0_IDX_000D[272] = { + 0xFF,0x09,0x01,0x0C,0x01,0xA0,0x00,0x00,0x00,0x14,0x00,0x0D,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86,0x13,0x81,0xFE,0x64,0xC7,0x72, + 0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x76,0x43,0x01,0xFF,0x46,0xC7, + 0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x29,0x66,0x43,0x04,0xFF,0x84, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x52,0x13,0x90,0x16,0x43,0x02,0xFF, + 0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x56,0x13,0x81, + 0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x46,0x43, + 0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x79,0x46, + 0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x51,0x19,0x48, + 0x26,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50,0x45, + 0x09,0x96,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50, + 0x45,0x09,0x86,0x43,0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23, + 0x50,0x45,0x00,0x96,0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04, + 0x23,0x50,0x42,0x54,0x66,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02, + 0x04,0x23,0x50,0x32,0x40,0x46 + }; + + byte SZL_ID_01A0_IDX_000E[292] = { + 0xFF,0x09,0x01,0x20,0x01,0xA0,0x00,0x00,0x00,0x14,0x00,0x0E,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86,0x13,0x81,0xFE,0x64,0xC7,0x72, + 0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x76,0x43,0x01,0xFF,0x46,0xC7, + 0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x29,0x66,0x43,0x04,0xFF,0x84, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x52,0x13,0x90,0x16,0x43,0x02,0xFF, + 0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x56,0x13,0x81, + 0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x46,0x43, + 0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x79,0x46, + 0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x51,0x19,0x48, + 0x26,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50,0x45, + 0x09,0x96,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50, + 0x45,0x09,0x86,0x43,0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23, + 0x50,0x45,0x00,0x96,0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04, + 0x23,0x50,0x42,0x54,0x66,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02, + 0x04,0x23,0x50,0x32,0x40,0x46,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94, + 0x02,0x04,0x23,0x50,0x32,0x40,0x36 + }; + + byte SZL_ID_01A0_IDX_000F[312] = { + 0xFF,0x09,0x01,0x34,0x01,0xA0,0x00,0x00,0x00,0x14,0x00,0x0F,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86,0x13,0x81,0xFE,0x64,0xC7,0x72, + 0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x76,0x43,0x01,0xFF,0x46,0xC7, + 0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x29,0x66,0x43,0x04,0xFF,0x84, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x52,0x13,0x90,0x16,0x43,0x02,0xFF, + 0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x56,0x13,0x81, + 0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x46,0x43, + 0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x79,0x46, + 0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x51,0x19,0x48, + 0x26,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50,0x45, + 0x09,0x96,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50, + 0x45,0x09,0x86,0x43,0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23, + 0x50,0x45,0x00,0x96,0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04, + 0x23,0x50,0x42,0x54,0x66,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02, + 0x04,0x23,0x50,0x32,0x40,0x46,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94, + 0x02,0x04,0x23,0x50,0x32,0x40,0x36,0x43,0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14, + 0x94,0x02,0x04,0x23,0x50,0x32,0x31,0x46 + }; + + byte SZL_ID_01A0_IDX_0010[332] = { + 0xFF,0x09,0x01,0x48,0x01,0xA0,0x00,0x00,0x00,0x14,0x00,0x10,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86,0x13,0x81,0xFE,0x64,0xC7,0x72, + 0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x76,0x43,0x01,0xFF,0x46,0xC7, + 0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x29,0x66,0x43,0x04,0xFF,0x84, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x52,0x13,0x90,0x16,0x43,0x02,0xFF, + 0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x56,0x13,0x81, + 0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x46,0x43, + 0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x79,0x46, + 0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x51,0x19,0x48, + 0x26,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50,0x45, + 0x09,0x96,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50, + 0x45,0x09,0x86,0x43,0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23, + 0x50,0x45,0x00,0x96,0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04, + 0x23,0x50,0x42,0x54,0x66,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02, + 0x04,0x23,0x50,0x32,0x40,0x46,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94, + 0x02,0x04,0x23,0x50,0x32,0x40,0x36,0x43,0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14, + 0x94,0x02,0x04,0x23,0x50,0x32,0x31,0x46,0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x94,0x02,0x04,0x23,0x50,0x29,0x92,0x26 + }; + + byte SZL_ID_01A0_IDX_0011[352] = { + 0xFF,0x09,0x01,0x5C,0x01,0xA0,0x00,0x00,0x00,0x14,0x00,0x11,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86,0x13,0x81,0xFE,0x64,0xC7,0x72, + 0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x76,0x43,0x01,0xFF,0x46,0xC7, + 0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x29,0x66,0x43,0x04,0xFF,0x84, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x52,0x13,0x90,0x16,0x43,0x02,0xFF, + 0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x56,0x13,0x81, + 0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x46,0x43, + 0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x79,0x46, + 0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x51,0x19,0x48, + 0x26,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50,0x45, + 0x09,0x96,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50, + 0x45,0x09,0x86,0x43,0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23, + 0x50,0x45,0x00,0x96,0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04, + 0x23,0x50,0x42,0x54,0x66,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02, + 0x04,0x23,0x50,0x32,0x40,0x46,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94, + 0x02,0x04,0x23,0x50,0x32,0x40,0x36,0x43,0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14, + 0x94,0x02,0x04,0x23,0x50,0x32,0x31,0x46,0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x94,0x02,0x04,0x23,0x50,0x29,0x92,0x26,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14, + 0x77,0x14,0x94,0x02,0x04,0x23,0x49,0x22,0x26,0x66 + }; + + byte SZL_ID_01A0_IDX_0012[372] = { + 0xFF,0x09,0x01,0x70,0x01,0xA0,0x00,0x00,0x00,0x14,0x00,0x12,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86,0x13,0x81,0xFE,0x64,0xC7,0x72, + 0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x76,0x43,0x01,0xFF,0x46,0xC7, + 0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x29,0x66,0x43,0x04,0xFF,0x84, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x52,0x13,0x90,0x16,0x43,0x02,0xFF, + 0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x56,0x13,0x81, + 0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x46,0x43, + 0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x79,0x46, + 0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x51,0x19,0x48, + 0x26,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50,0x45, + 0x09,0x96,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50, + 0x45,0x09,0x86,0x43,0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23, + 0x50,0x45,0x00,0x96,0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04, + 0x23,0x50,0x42,0x54,0x66,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02, + 0x04,0x23,0x50,0x32,0x40,0x46,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94, + 0x02,0x04,0x23,0x50,0x32,0x40,0x36,0x43,0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14, + 0x94,0x02,0x04,0x23,0x50,0x32,0x31,0x46,0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x94,0x02,0x04,0x23,0x50,0x29,0x92,0x26,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14, + 0x77,0x14,0x94,0x02,0x04,0x23,0x49,0x22,0x26,0x66,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08, + 0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x49,0x22,0x26,0x56 + }; + + byte SZL_ID_01A0_IDX_0013[392] = { + 0xFF,0x09,0x01,0x84,0x01,0xA0,0x00,0x00,0x00,0x14,0x00,0x13,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86,0x13,0x81,0xFE,0x64,0xC7,0x72, + 0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x76,0x43,0x01,0xFF,0x46,0xC7, + 0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x29,0x66,0x43,0x04,0xFF,0x84, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x52,0x13,0x90,0x16,0x43,0x02,0xFF, + 0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x56,0x13,0x81, + 0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x46,0x43, + 0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x79,0x46, + 0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x51,0x19,0x48, + 0x26,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50,0x45, + 0x09,0x96,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50, + 0x45,0x09,0x86,0x43,0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23, + 0x50,0x45,0x00,0x96,0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04, + 0x23,0x50,0x42,0x54,0x66,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02, + 0x04,0x23,0x50,0x32,0x40,0x46,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94, + 0x02,0x04,0x23,0x50,0x32,0x40,0x36,0x43,0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14, + 0x94,0x02,0x04,0x23,0x50,0x32,0x31,0x46,0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x94,0x02,0x04,0x23,0x50,0x29,0x92,0x26,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14, + 0x77,0x14,0x94,0x02,0x04,0x23,0x49,0x22,0x26,0x66,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08, + 0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x49,0x22,0x26,0x56,0x43,0x01,0xFF,0x46,0xC7,0x72,0x43,0x04, + 0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x49,0x22,0x17,0x66 + }; + + byte SZL_ID_01A0_IDX_0014[412] = { + 0xFF,0x09,0x01,0x98,0x01,0xA0,0x00,0x00,0x00,0x14,0x00,0x14,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86,0x13,0x81,0xFE,0x64,0xC7,0x72, + 0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x76,0x43,0x01,0xFF,0x46,0xC7, + 0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x29,0x66,0x43,0x04,0xFF,0x84, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x52,0x13,0x90,0x16,0x43,0x02,0xFF, + 0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x56,0x13,0x81, + 0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x46,0x43, + 0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x79,0x46, + 0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x51,0x19,0x48, + 0x26,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50,0x45, + 0x09,0x96,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50, + 0x45,0x09,0x86,0x43,0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23, + 0x50,0x45,0x00,0x96,0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04, + 0x23,0x50,0x42,0x54,0x66,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02, + 0x04,0x23,0x50,0x32,0x40,0x46,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94, + 0x02,0x04,0x23,0x50,0x32,0x40,0x36,0x43,0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14, + 0x94,0x02,0x04,0x23,0x50,0x32,0x31,0x46,0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x94,0x02,0x04,0x23,0x50,0x29,0x92,0x26,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14, + 0x77,0x14,0x94,0x02,0x04,0x23,0x49,0x22,0x26,0x66,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08, + 0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x49,0x22,0x26,0x56,0x43,0x01,0xFF,0x46,0xC7,0x72,0x43,0x04, + 0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x49,0x22,0x17,0x66,0x43,0x04,0xFF,0x84,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x49,0x19,0x75,0x46 + }; + + byte SZL_ID_01A0_IDX_0015[432] = { + 0xFF,0x09,0x01,0xAC,0x01,0xA0,0x00,0x00,0x00,0x14,0x00,0x15,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00, + 0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x86,0x13,0x81,0xFE,0x64,0xC7,0x72, + 0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x38,0x76,0x43,0x01,0xFF,0x46,0xC7, + 0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x52,0x16,0x29,0x66,0x43,0x04,0xFF,0x84, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x52,0x13,0x90,0x16,0x43,0x02,0xFF, + 0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x56,0x13,0x81, + 0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x88,0x46,0x43, + 0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x51,0x21,0x79,0x46, + 0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x51,0x19,0x48, + 0x26,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50,0x45, + 0x09,0x96,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x50, + 0x45,0x09,0x86,0x43,0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23, + 0x50,0x45,0x00,0x96,0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04, + 0x23,0x50,0x42,0x54,0x66,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02, + 0x04,0x23,0x50,0x32,0x40,0x46,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14,0x94, + 0x02,0x04,0x23,0x50,0x32,0x40,0x36,0x43,0x01,0xFF,0x46,0xC7,0x72,0x43,0x04,0x08,0x14,0x77,0x14, + 0x94,0x02,0x04,0x23,0x50,0x32,0x31,0x46,0x43,0x04,0xFF,0x84,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x94,0x02,0x04,0x23,0x50,0x29,0x92,0x26,0x43,0x02,0xFF,0x68,0xC7,0x00,0x00,0x00,0x08,0x14, + 0x77,0x14,0x94,0x02,0x04,0x23,0x49,0x22,0x26,0x66,0x13,0x81,0xFE,0x64,0xC7,0x72,0x43,0x04,0x08, + 0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x49,0x22,0x26,0x56,0x43,0x01,0xFF,0x46,0xC7,0x72,0x43,0x04, + 0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x49,0x22,0x17,0x66,0x43,0x04,0xFF,0x84,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x94,0x02,0x04,0x23,0x49,0x19,0x75,0x46,0x43,0x02,0xFF,0x68,0xC7,0x00, + 0x00,0x00,0x08,0x14,0x77,0x14,0x94,0x02,0x04,0x23,0x48,0x32,0x21,0x16 + }; + + byte SZL_ID_0117_IDX_0000[16] = { + 0xFF,0x09,0x00,0x0C,0x01,0x17,0x00,0x00,0x00,0x04,0x00,0x01,0x00,0x00,0x00,0x01 + }; + + byte SZL_ID_0117_IDX_0001[16] = { + 0xFF,0x09,0x00,0x0C,0x01,0x17,0x00,0x01,0x00,0x04,0x00,0x01,0x00,0x01,0x00,0x03 + }; + + byte SZL_ID_0117_IDX_0002[16] = { + 0xFF,0x09,0x00,0x0C,0x01,0x17,0x00,0x02,0x00,0x04,0x00,0x01,0x00,0x02,0x00,0x02 + }; + + byte SZL_ID_0117_IDX_0003[16] = { + 0xFF,0x09,0x00,0x0C,0x01,0x17,0x00,0x03,0x00,0x04,0x00,0x01,0x00,0x03,0x00,0x01 + }; + + byte SZL_ID_0117_IDX_0004[16] = { + 0xFF,0x09,0x00,0x0C,0x01,0x17,0x00,0x04,0x00,0x04,0x00,0x01,0x00,0x04,0x00,0x01 + }; + + byte SZL_ID_0118_IDX_0000[16] = { + 0xFF,0x09,0x00,0x0C,0x01,0x18,0x00,0x00,0x00,0x04,0x00,0x01,0x00,0x00,0x00,0x08 + }; + + byte SZL_ID_0118_IDX_0001[16] = { + 0xFF,0x09,0x00,0x0C,0x01,0x18,0x00,0x01,0x00,0x04,0x00,0x01,0x00,0x01,0x00,0x08 + }; + + byte SZL_ID_0118_IDX_0002[16] = { + 0xFF,0x09,0x00,0x0C,0x01,0x18,0x00,0x02,0x00,0x04,0x00,0x01,0x00,0x02,0x00,0x08 + }; + + byte SZL_ID_0118_IDX_0003[16] = { + 0xFF,0x09,0x00,0x0C,0x01,0x18,0x00,0x03,0x00,0x04,0x00,0x01,0x00,0x03,0x00,0x08 + }; + + byte SZL_ID_0131_IDX_0001[52] = { + 0xFF,0x09,0x00,0x30,0x01,0x31,0x00,0x01,0x00,0x28,0x00,0x01,0x00,0x01, + 0x08,0x00, // PDU SIZE : We expose 2048 00F0 + 0x04,0x00, // Max Commections : We expose 1024 0010 + 0x00,0xB7,0x1B,0x00,0x00,0x02,0xDC,0x6C,0x05,0xF5,0xE1,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0131_IDX_0002[52] = { + 0xFF,0x09,0x00,0x30,0x01,0x31,0x00,0x02,0x00,0x28,0x00,0x01,0x00,0x02,0xBE,0xFD,0x4F,0x00,0x00, + 0x00,0x00,0x00,0x08,0x00,0x00,0x00,0x3C,0x01,0x08,0x00,0x00,0x00,0x7D,0x00,0x00,0x05,0x03,0x0F, + 0x00,0x00,0x08,0x00,0x00,0x0C,0x00,0x0A,0x00,0x00,0x00,0x01,0x00,0x00 + }; + + byte SZL_ID_0131_IDX_0003[52] = { + 0xFF,0x09,0x00,0x30,0x01,0x31,0x00,0x03,0x00,0x28,0x00,0x01,0x00,0x03,0x7F,0xFC,0x83,0x01, + 0x00,0xF0, // Max size of consistently readable data (will be set = PDU size) + 0x00,0x10,0x00,0x01,0x02,0x09,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0131_IDX_0004[52] = { + 0xFF,0x09,0x00,0x30,0x01,0x31,0x00,0x04,0x00,0x28,0x00,0x01,0x00,0x04,0xFE,0x01,0x62,0x41,0x63, + 0x00,0x1E,0x00,0x10,0x10,0x10,0x04,0x02,0x00,0x00,0x02,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0131_IDX_0005[52] = { + 0xFF,0x09,0x00,0x30,0x01,0x31,0x00,0x05,0x00,0x28,0x00,0x01,0x00,0x05,0x3E,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x10,0x01,0xF4,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0131_IDX_0006[52] = { + 0xFF,0x09,0x00,0x30,0x01,0x31,0x00,0x06,0x00,0x28,0x00,0x01,0x00,0x06,0xF3,0x00,0x00,0xF8,0x01, + 0xF0,0xFF,0x4E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x20, + 0x01,0x00,0x00,0x0E,0x00,0x4C,0xFF,0xFF,0x05,0xC0,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0131_IDX_0007[52] = { + 0xFF,0x09,0x00,0x30,0x01,0x31,0x00,0x07,0x00,0x28,0x00,0x01,0x00,0x07,0x01,0x00,0x3F,0x00,0x20, + 0x01,0x01,0x00,0x00,0x00,0x01,0x08,0x01,0x08,0x01,0x08,0x20,0x08,0x02,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0131_IDX_0008[52] = { + 0xFF,0x09,0x00,0x30,0x01,0x31,0x00,0x08,0x00,0x28,0x00,0x01,0x00,0x08,0x70,0x03,0x70,0x02,0x70, + 0x02,0x40,0x32,0x40,0x32,0x40,0x32,0x40,0x64,0x40,0x32,0x40,0x32,0x40,0x64,0x40,0x64,0x40,0x64, + 0x40,0x64,0x40,0x64,0x40,0x64,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0131_IDX_0009[52] = { + 0xFF,0x09,0x00,0x30,0x01,0x31,0x00,0x09,0x00,0x28,0x00,0x01,0x00,0x09,0x04,0x06,0x01,0x00,0x01, + 0xF7,0x01,0xF7,0x00,0x01,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + }; + + byte SZL_ID_0C91_IDX_07FE[28] = { + 0xFF,0x09,0x00,0x18,0x0C,0x91,0x07,0xFE,0x00,0x10,0x00,0x01,0x00,0x00,0x02,0x02,0x07,0xFE,0xA7, + 0xC4,0xA7,0xC4,0x00,0x00,0xB4,0x02,0x00,0x11 + }; + + +#endif + diff --git a/core/src/drivers/plugins/native/s7comm/snap7/core/s7_isotcp.cpp b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_isotcp.cpp new file mode 100644 index 00000000..70b6af45 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_isotcp.cpp @@ -0,0 +1,541 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 Davide Nardella | +| All rights reserved. | +|==============================================================================| +| SNAP7 is free software: you can redistribute it and/or modify | +| it under the terms of the Lesser GNU General Public License as published by | +| the Free Software Foundation, either version 3 of the License, or | +| (at your option) any later version. | +| | +| It means that you can distribute your commercial software linked with | +| SNAP7 without the requirement to distribute the source code of your | +| application and without the requirement that your application be itself | +| distributed under LGPL. | +| | +| SNAP7 is distributed in the hope that it will be useful, | +| but WITHOUT ANY WARRANTY; without even the implied warranty of | +| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | +| Lesser GNU General Public License for more details. | +| | +| You should have received a copy of the GNU General Public License and a | +| copy of Lesser GNU General Public License along with Snap7. | +| If not, see http://www.gnu.org/licenses/ | +|=============================================================================*/ +#include "s7_isotcp.h" +//--------------------------------------------------------------------------- +TIsoTcpSocket::TIsoTcpSocket() +{ + RecvTimeout = 3000; // Some old equipments are a bit slow to answer.... + RemotePort = isoTcpPort; + // These fields should be $0000 and in any case RFC says that they are not considered. + // But some equipment...need a non zero value for the source reference. + DstRef = 0x0000; + SrcRef = 0x0100; + // PDU size requested + IsoPDUSize =1024; + IsoMaxFragments=MaxIsoFragments; + LastIsoError=0; +} +//--------------------------------------------------------------------------- +TIsoTcpSocket::~TIsoTcpSocket() +{ +} +//--------------------------------------------------------------------------- +int TIsoTcpSocket::CheckPDU(void *pPDU, u_char PduTypeExpected) +{ + PIsoHeaderInfo Info; + int Size; + ClrIsoError(); + if (pPDU!=0) + { + Info = PIsoHeaderInfo(pPDU); + Size = PDUSize(pPDU); + // Performs check + if (( Size<7 ) || ( Size>IsoPayload_Size ) || // Checks RFC 1006 header length + ( Info->HLengthPDUType!=PduTypeExpected)) // Checks PDU Type + return SetIsoError(errIsoInvalidPDU); + else + return noError; + } + else + return SetIsoError(errIsoNullPointer); +} +//--------------------------------------------------------------------------- +int TIsoTcpSocket::SetIsoError(int Error) +{ + LastIsoError = Error | LastTcpError; + return LastIsoError; +} +//--------------------------------------------------------------------------- +void TIsoTcpSocket::ClrIsoError() +{ + LastIsoError=0; + LastTcpError=0; +} +//--------------------------------------------------------------------------- +int TIsoTcpSocket::BuildControlPDU() +{ + int ParLen, IsoLen; + + ClrIsoError(); + FControlPDU.COTP.Params.PduSizeCode=0xC0; // code that identifies TPDU size + FControlPDU.COTP.Params.PduSizeLen =0x01; // 1 byte this field + switch(IsoPDUSize) + { + case 128: + FControlPDU.COTP.Params.PduSizeVal =0x07; + break; + case 256: + FControlPDU.COTP.Params.PduSizeVal =0x08; + break; + case 512: + FControlPDU.COTP.Params.PduSizeVal =0x09; + break; + case 1024: + FControlPDU.COTP.Params.PduSizeVal =0x0A; + break; + case 2048: + FControlPDU.COTP.Params.PduSizeVal =0x0B; + break; + case 4096: + FControlPDU.COTP.Params.PduSizeVal =0x0C; + break; + case 8192: + FControlPDU.COTP.Params.PduSizeVal =0x0D; + break; + default: + FControlPDU.COTP.Params.PduSizeVal =0x0B; // Our Default + }; + // Build TSAPs + FControlPDU.COTP.Params.TSAP[0]=0xC1; // code that identifies source TSAP + FControlPDU.COTP.Params.TSAP[1]=2; // source TSAP Len + FControlPDU.COTP.Params.TSAP[2]=(SrcTSap>>8) & 0xFF; // HI part + FControlPDU.COTP.Params.TSAP[3]=SrcTSap & 0xFF; // LO part + + FControlPDU.COTP.Params.TSAP[4]=0xC2; // code that identifies dest TSAP + FControlPDU.COTP.Params.TSAP[5]=2; // dest TSAP Len + FControlPDU.COTP.Params.TSAP[6]=(DstTSap>>8) & 0xFF; // HI part + FControlPDU.COTP.Params.TSAP[7]=DstTSap & 0xFF; // LO part + + // Params length + ParLen=11; // 2 Src TSAP (Code+field Len) + + // 2 Src TSAP len + + // 2 Dst TSAP (Code+field Len) + + // 2 Src TSAP len + + // 3 PDU size (Code+field Len+Val) = 11 + // Telegram length + IsoLen=sizeof(TTPKT)+ // TPKT Header + 7 + // COTP Header Size without params + ParLen; // COTP params + + FControlPDU.TPKT.Version =isoTcpVersion; + FControlPDU.TPKT.Reserved =0; + FControlPDU.TPKT.HI_Lenght=0; // Connection Telegram size cannot exced 255 bytes, so + // this field is always 0 + FControlPDU.TPKT.LO_Lenght=IsoLen; + + FControlPDU.COTP.HLength =ParLen + 6; // <-- 6 = 7 - 1 (COTP Header size - 1) + FControlPDU.COTP.PDUType =pdu_type_CR; // Connection Request + FControlPDU.COTP.DstRef =DstRef; // Destination reference + FControlPDU.COTP.SrcRef =SrcRef; // Source reference + FControlPDU.COTP.CO_R =0x00; // Class + Option : RFC0983 states that it must be always 0x40 + // but for some equipment (S7) must be 0 in disaccord of specifications !!! + return noError; +} +//--------------------------------------------------------------------------- +int TIsoTcpSocket::PDUSize(void *pPDU) +{ + return PIsoHeaderInfo(pPDU)->TPKT.HI_Lenght*256+PIsoHeaderInfo( pPDU )->TPKT.LO_Lenght; +} +//--------------------------------------------------------------------------- +void TIsoTcpSocket::IsoParsePDU(TIsoControlPDU pdu) +{ +// Currently we accept a connection with any kind of src/dst tsap +// Override to implement special filters. +} +//--------------------------------------------------------------------------- +int TIsoTcpSocket::IsoConfirmConnection(u_char PDUType) +{ + PIsoControlPDU CPDU = PIsoControlPDU(&PDU); + u_short TempRef; + + ClrIsoError(); + PDU.COTP.PDUType=PDUType; + // Exchange SrcRef<->DstRef, not strictly needed by COTP 8073 but S7PLC as client needs it. + TempRef=CPDU->COTP.DstRef; + CPDU->COTP.DstRef=CPDU->COTP.SrcRef; + CPDU->COTP.SrcRef=0x0100;//TempRef; + + return SendPacket(&PDU,PDUSize(&PDU)); +} +//--------------------------------------------------------------------------- +void TIsoTcpSocket::FragmentSkipped(int Size) +{ +// override for log purpose +} +//--------------------------------------------------------------------------- +int TIsoTcpSocket::isoConnect() +{ + pbyte TmpControlPDU; + PIsoControlPDU ControlPDU; + u_int Length; + int Result; + + // Build the default connection telegram + BuildControlPDU(); + ControlPDU =&FControlPDU; + + // Checks the format + Result =CheckPDU(ControlPDU, pdu_type_CR); + if (Result!=0) + return Result; + + Result =SckConnect(); + if (Result==noError) + { + // Calcs the length + Length =PDUSize(ControlPDU); + // Send connection telegram + SendPacket(ControlPDU, Length); + if (LastTcpError==0) + { + TmpControlPDU = pbyte(ControlPDU); + // Receives TPKT header (4 bytes) + RecvPacket(TmpControlPDU, sizeof(TTPKT)); + if (LastTcpError==0) + { + // Calc the packet length + Length =PDUSize(TmpControlPDU); + // Check if it fits in the buffer and if it's greater then TTPKT size + if ((Length<=sizeof(TIsoControlPDU)) && (Length>sizeof(TTPKT))) + { + // Points to COTP + TmpControlPDU+=sizeof(TTPKT); + Length -= sizeof(TTPKT); + // Receives remainin bytes 4 bytes after + RecvPacket(TmpControlPDU, Length); + if (LastTcpError==0) + { + // Finally checks the Connection Confirm telegram + Result =CheckPDU(ControlPDU, pdu_type_CC); + if (Result!=0) + LastIsoError=Result; + } + else + Result =SetIsoError(errIsoRecvPacket); + } + else + Result =SetIsoError(errIsoInvalidPDU); + } + else + Result =SetIsoError(errIsoRecvPacket); + // Flush buffer + if (Result!=0) + Purge(); + } + else + Result =SetIsoError(errIsoSendPacket); + + if (Result!=0) + SckDisconnect(); + } + return Result; +} +//--------------------------------------------------------------------------- +int TIsoTcpSocket::isoSendBuffer(void *Data, int Size) +{ + int Result; + u_int IsoSize; + + ClrIsoError(); + // Total Size = Size + Header Size + IsoSize =Size+DataHeaderSize; + // Checks the length + if ((IsoSize>0) && (IsoSize<=IsoFrameSize)) + { + // Builds the header + Result =0; + // TPKT + PDU.TPKT.Version = isoTcpVersion; + PDU.TPKT.Reserved = 0; + PDU.TPKT.HI_Lenght= (u_short(IsoSize)>> 8) & 0xFF; + PDU.TPKT.LO_Lenght= u_short(IsoSize) & 0xFF; + // COPT + PDU.COTP.HLength =sizeof(TCOTP_DT)-1; + PDU.COTP.PDUType =pdu_type_DT; + PDU.COTP.EoT_Num =pdu_EoT; + // Fill payload + if (Data!=0) // Data=null ==> use internal buffer PDU.Payload + memcpy(&PDU.Payload, Data, Size); + // Send over TCP/IP + SendPacket(&PDU, IsoSize); + + if (LastTcpError!=0) + Result =SetIsoError(errIsoSendPacket); + } + else + Result =SetIsoError(errIsoInvalidDataSize ); + return Result; +} +//--------------------------------------------------------------------------- +int TIsoTcpSocket::isoRecvBuffer(void *Data, int & Size) +{ + int Result; + + ClrIsoError(); + Size =0; + Result =isoRecvPDU(&PDU); + if (Result==0) + { + Size =PDUSize( &PDU )-DataHeaderSize; + if (Data!=0) // Data=NULL ==> a child will consume directly PDY.Payload + memcpy(Data, &PDU.Payload, Size); + } + return Result; +} +//--------------------------------------------------------------------------- +int TIsoTcpSocket::isoExchangeBuffer(void *Data, int &Size) +{ + int Result; + + ClrIsoError(); + Result =isoSendBuffer(Data, Size); + if (Result==0) + Result =isoRecvBuffer(Data, Size); + return Result; +} +//--------------------------------------------------------------------------- +bool TIsoTcpSocket::IsoPDUReady() +{ + ClrIsoError(); + return PacketReady(sizeof(TCOTP_DT)); +} +//--------------------------------------------------------------------------- +int TIsoTcpSocket::isoDisconnect(bool OnlyTCP) +{ + int Result; + + ClrIsoError(); + if (Connected) + Purge(); // Flush pending + LastIsoError=0; + // OnlyTCP true -> Disconnect Request telegram is not required : only TCP disconnection + if (!OnlyTCP) + { + // if we are connected -> we have a valid connection telegram + if (Connected) + FControlPDU.COTP.PDUType =pdu_type_DR; + // Checks the format + Result =CheckPDU(&FControlPDU, pdu_type_DR); + if (Result!=0) + return Result; + // Sends Disconnect request + SendPacket(&FControlPDU, PDUSize(&FControlPDU)); + if (LastTcpError!=0) + { + Result =SetIsoError(errIsoSendPacket); + return Result; + } + } + // TCP disconnect + SckDisconnect(); + if (LastTcpError!=0) + Result =SetIsoError(errIsoDisconnect); + else + Result =0; + + return Result; +} +//--------------------------------------------------------------------------- +int TIsoTcpSocket::isoSendPDU(PIsoDataPDU Data) +{ + int Result; + + ClrIsoError(); + Result=CheckPDU(Data,pdu_type_DT); + if (Result==0) + { + SendPacket(Data,PDUSize(Data)); + if (LastTcpError!=0) + Result=SetIsoError(errIsoSendPacket); + } + return Result; +} +//------------------------------------------------------------------------------ +int TIsoTcpSocket::isoRecvFragment(void *From, int Max, int &Size, bool &EoT) +{ + int DataLength; + + Size =0; + EoT =false; + byte PDUType; + ClrIsoError(); + // header is received always from beginning + RecvPacket(&PDU, DataHeaderSize); // TPKT + COPT_DT + if (LastTcpError==0) + { + PDUType=PDU.COTP.PDUType; + switch (PDUType) + { + case pdu_type_CR: + case pdu_type_DR: + EoT=true; + break; + case pdu_type_DT: + EoT = (PDU.COTP.EoT_Num & 0x80) == 0x80; // EoT flag + break; + default: + return SetIsoError(errIsoInvalidPDU); + } + + DataLength = PDUSize(&PDU) - DataHeaderSize; + if (CheckPDU(&PDU, PDUType)!=0) + return LastIsoError; + // Checks for data presence + if (DataLength>0) // payload present + { + // Check if the data fits in the buffer + if(DataLength<=Max) + { + RecvPacket(From, DataLength); + if (LastTcpError!=0) + return SetIsoError(errIsoRecvPacket); + else + Size =DataLength; + } + else + return SetIsoError(errIsoPduOverflow); + } + } + else + return SetIsoError(errIsoRecvPacket); + + return LastIsoError; +} +//--------------------------------------------------------------------------- +// Fragments Recv schema +//------------------------------------------------------------------------------ +// +// packet 1 packet 2 packet 3 +// +--------+------------+ +--------+------------+ +--------+------------+ +// | HEADER | FRAGMENT 1 | | HEADER | FRAGMENT 2 | | HEADER | FRAGMENT 3 | +// +--------+------------+ +--------+------------+ +--------+------------+ +// | | | +// | +-----------+ | +// | | | +// | | +------------------------+ +// | | | (Packet 3 has EoT Flag set) +// V V V +// +--------+------------+------------+------------+ +// | HEADER | FRAGMENT 1 : FRAGMENT 2 : FRAGMENT 3 | +// +--------+------------+------------+------------+ +// ^ +// | +// +-- A new header is built with updated info +// +//------------------------------------------------------------------------------ +int TIsoTcpSocket::isoRecvPDU(PIsoDataPDU Data) +{ + int Result; + int Size; + pbyte pData; + int max; + int Offset; + int Received; + int NumParts; + bool Complete; + + NumParts =1; + Offset =0; + Complete =false; + ClrIsoError(); + pData = pbyte(&PDU.Payload); + do { + pData=pData+Offset; + max =IsoPayload_Size-Offset; // Maximum packet allowed + if (max>0) + { + Result =isoRecvFragment(pData, max, Received, Complete); + if((Result==0) && !Complete) + { + ++NumParts; + Offset += Received; + if (NumParts>IsoMaxFragments) + Result =SetIsoError(errIsoTooManyFragments); + } + } + else + Result =SetIsoError(errIsoTooManyFragments); + } while ((!Complete) && (Result==0)); + + + if (Result==0) + { + // Add to offset the header size + Size =Offset+Received+DataHeaderSize; + // Adjust header + PDU.TPKT.HI_Lenght =(u_short(Size)>>8) & 0xFF; + PDU.TPKT.LO_Lenght =u_short(Size) & 0xFF; + // Copies data if target is not the local PDU + if (Data!=&PDU) + memcpy(Data, &PDU, Size); + } + else + if (LastTcpError!=WSAECONNRESET) + Purge(); + return Result; +} +//--------------------------------------------------------------------------- +int TIsoTcpSocket::isoExchangePDU(PIsoDataPDU Data) +{ + int Result; + ClrIsoError(); + Result=isoSendPDU(Data); + if (Result==0) + Result=isoRecvPDU(Data); + return Result; +} +//--------------------------------------------------------------------------- +void TIsoTcpSocket::IsoPeek(void *pPDU, TPDUKind &PduKind) +{ + PIsoHeaderInfo Info; + u_int IsoLen; + + Info=PIsoHeaderInfo(pPDU); + IsoLen=PDUSize(Info); + + // Check for empty fragment : size of PDU = size of header and nothing else + if (IsoLen==DataHeaderSize ) + { + // We don't need to check the EoT flag since the PDU is empty.... + PduKind=pkEmptyFragment; + return; + }; + // Check for invalid packet : size of PDU < size of header + if (IsoLenDataHeaderSize : check the PDUType + switch (Info->PDUType) + { + case pdu_type_CR: + PduKind=pkConnectionRequest; + break; + case pdu_type_DR: + PduKind=pkDisconnectRequest; + break; + case pdu_type_DT: + PduKind=pkValidData; + break; + default: + PduKind=pkUnrecognizedType; + }; +} + + + diff --git a/core/src/drivers/plugins/native/s7comm/snap7/core/s7_isotcp.h b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_isotcp.h new file mode 100644 index 00000000..86f2a9a6 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_isotcp.h @@ -0,0 +1,271 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 Davide Nardella | +| All rights reserved. | +|==============================================================================| +| SNAP7 is free software: you can redistribute it and/or modify | +| it under the terms of the Lesser GNU General Public License as published by | +| the Free Software Foundation, either version 3 of the License, or | +| (at your option) any later version. | +| | +| It means that you can distribute your commercial software linked with | +| SNAP7 without the requirement to distribute the source code of your | +| application and without the requirement that your application be itself | +| distributed under LGPL. | +| | +| SNAP7 is distributed in the hope that it will be useful, | +| but WITHOUT ANY WARRANTY; without even the implied warranty of | +| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | +| Lesser GNU General Public License for more details. | +| | +| You should have received a copy of the GNU General Public License and a | +| copy of Lesser GNU General Public License along with Snap7. | +| If not, see http://www.gnu.org/licenses/ | +|=============================================================================*/ +#ifndef s7_isotcp_h +#define s7_isotcp_h +//--------------------------------------------------------------------------- +#include "snap_msgsock.h" +//--------------------------------------------------------------------------- +#pragma pack(1) + +#define isoTcpVersion 3 // RFC 1006 +#define isoTcpPort 102 // RFC 1006 +#define isoInvalidHandle 0 +#define MaxTSAPLength 16 // Max Lenght for Src and Dst TSAP +#define MaxIsoFragments 64 // Max fragments +#define IsoPayload_Size 4096 // Iso telegram Buffer size + +#define noError 0 + +const longword errIsoMask = 0x000F0000; +const longword errIsoBase = 0x0000FFFF; + +const longword errIsoConnect = 0x00010000; // Connection error +const longword errIsoDisconnect = 0x00020000; // Disconnect error +const longword errIsoInvalidPDU = 0x00030000; // Bad format +const longword errIsoInvalidDataSize = 0x00040000; // Bad Datasize passed to send/recv : buffer is invalid +const longword errIsoNullPointer = 0x00050000; // Null passed as pointer +const longword errIsoShortPacket = 0x00060000; // A short packet received +const longword errIsoTooManyFragments = 0x00070000; // Too many packets without EoT flag +const longword errIsoPduOverflow = 0x00080000; // The sum of fragments data exceded maximum packet size +const longword errIsoSendPacket = 0x00090000; // An error occurred during send +const longword errIsoRecvPacket = 0x000A0000; // An error occurred during recv +const longword errIsoInvalidParams = 0x000B0000; // Invalid TSAP params +const longword errIsoResvd_1 = 0x000C0000; // Unassigned +const longword errIsoResvd_2 = 0x000D0000; // Unassigned +const longword errIsoResvd_3 = 0x000E0000; // Unassigned +const longword errIsoResvd_4 = 0x000F0000; // Unassigned + +const longword ISO_OPT_TCP_NODELAY = 0x00000001; // Disable Nagle algorithm +const longword ISO_OPT_INSIDE_MTU = 0x00000002; // Max packet size < MTU ethernet card + +// TPKT Header - ISO on TCP - RFC 1006 (4 bytes) +typedef struct{ + u_char Version; // Always 3 for RFC 1006 + u_char Reserved; // 0 + u_char HI_Lenght; // High part of packet lenght (entire frame, payload and TPDU included) + u_char LO_Lenght; // Low part of packet lenght (entire frame, payload and TPDU included) +} TTPKT; // Packet length : min 7 max 65535 + +typedef struct { + u_char PduSizeCode; + u_char PduSizeLen; + u_char PduSizeVal; + u_char TSAP[245]; // We don't know in advance these fields.... +} TCOPT_Params ; + +// PDU Type constants - ISO 8073, not all are mentioned in RFC 1006 +// For our purposes we use only those labeled with ** +// These constants contains 4 low bit order 0 (credit nibble) +// +// $10 ED : Expedited Data +// $20 EA : Expedited Data Ack +// $40 UD : CLTP UD +// $50 RJ : Reject +// $70 AK : Ack data +// ** $80 DR : Disconnect request (note : S7 doesn't use it) +// ** $C0 DC : Disconnect confirm (note : S7 doesn't use it) +// ** $D0 CC : Connection confirm +// ** $E0 CR : Connection request +// ** $F0 DT : Data +// + +// COTP Header for CONNECTION REQUEST/CONFIRM - DISCONNECT REQUEST/CONFIRM +typedef struct { + u_char HLength; // Header length : initialized to 6 (length without params - 1) + // descending classes that add values in params field must update it. + u_char PDUType; // 0xE0 Connection request + // 0xD0 Connection confirm + // 0x80 Disconnect request + // 0xDC Disconnect confirm + u_short DstRef; // Destination reference : Always 0x0000 + u_short SrcRef; // Source reference : Always 0x0000 + u_char CO_R; // If the telegram is used for Connection request/Confirm, + // the meaning of this field is CLASS+OPTION : + // Class (High 4 bits) + Option (Low 4 bits) + // Class : Always 4 (0100) but is ignored in input (RFC States this) + // Option : Always 0, also this in ignored. + // If the telegram is used for Disconnect request, + // the meaning of this field is REASON : + // 1 Congestion at TSAP + // 2 Session entity not attached to TSAP + // 3 Address unknown (at TCP connect time) + // 128+0 Normal disconnect initiated by the session + // entity. + // 128+1 Remote transport entity congestion at connect + // request time + // 128+3 Connection negotiation failed + // 128+5 Protocol Error + // 128+8 Connection request refused on this network + // connection + // Parameter data : depending on the protocol implementation. + // ISO 8073 define several type of parameters, but RFC 1006 recognizes only + // TSAP related parameters and PDU size. See RFC 0983 for more details. + TCOPT_Params Params; + /* Other params not used here, list only for completeness + ACK_TIME = 0x85, 1000 0101 Acknowledge Time + RES_ERROR = 0x86, 1000 0110 Residual Error Rate + PRIORITY = 0x87, 1000 0111 Priority + TRANSIT_DEL = 0x88, 1000 1000 Transit Delay + THROUGHPUT = 0x89, 1000 1001 Throughput + SEQ_NR = 0x8A, 1000 1010 Subsequence Number (in AK) + REASSIGNMENT = 0x8B, 1000 1011 Reassignment Time + FLOW_CNTL = 0x8C, 1000 1100 Flow Control Confirmation (in AK) + TPDU_SIZE = 0xC0, 1100 0000 TPDU Size + SRC_TSAP = 0xC1, 1100 0001 TSAP-ID / calling TSAP ( in CR/CC ) + DST_TSAP = 0xC2, 1100 0010 TSAP-ID / called TSAP + CHECKSUM = 0xC3, 1100 0011 Checksum + VERSION_NR = 0xC4, 1100 0100 Version Number + PROTECTION = 0xC5, 1100 0101 Protection Parameters (user defined) + OPT_SEL = 0xC6, 1100 0110 Additional Option Selection + PROTO_CLASS = 0xC7, 1100 0111 Alternative Protocol Classes + PREF_MAX_TPDU_SIZE = 0xF0, 1111 0000 + INACTIVITY_TIMER = 0xF2, 1111 0010 + ADDICC = 0xe0 1110 0000 Additional Information on Connection Clearing + */ +} TCOTP_CO ; +typedef TCOTP_CO *PCOTP_CO; + +// COTP Header for DATA EXCHANGE +typedef struct { + u_char HLength; // Header length : 3 for this header + u_char PDUType; // 0xF0 for this header + u_char EoT_Num; // EOT (bit 7) + PDU Number (bits 0..6) + // EOT = 1 -> End of Trasmission Packet (This packet is complete) + // PDU Number : Always 0 +} TCOTP_DT; +typedef TCOTP_DT *PCOTP_DT; + +// Info part of a PDU, only common parts. We use it to check the consistence +// of a telegram regardless of it's nature (control or data). +typedef struct { + TTPKT TPKT; // TPKT Header + // Common part of any COTP + u_char HLength; // Header length : 3 for this header + u_char PDUType; // Pdu type +} TIsoHeaderInfo ; +typedef TIsoHeaderInfo *PIsoHeaderInfo; + +// PDU Type consts (Code + Credit) +const byte pdu_type_CR = 0xE0; // Connection request +const byte pdu_type_CC = 0xD0; // Connection confirm +const byte pdu_type_DR = 0x80; // Disconnect request +const byte pdu_type_DC = 0xC0; // Disconnect confirm +const byte pdu_type_DT = 0xF0; // Data transfer + +const byte pdu_EoT = 0x80; // End of Trasmission Packet (This packet is complete) + +const longword DataHeaderSize = sizeof(TTPKT)+sizeof(TCOTP_DT); +const longword IsoFrameSize = IsoPayload_Size+DataHeaderSize; + +typedef struct { + TTPKT TPKT; // TPKT Header + TCOTP_CO COTP; // COPT Header for CONNECTION stuffs +} TIsoControlPDU; +typedef TIsoControlPDU *PIsoControlPDU; + +typedef u_char TIsoPayload[IsoPayload_Size]; + +typedef struct { + TTPKT TPKT; // TPKT Header + TCOTP_DT COTP; // COPT Header for DATA EXCHANGE + TIsoPayload Payload; // Payload +} TIsoDataPDU ; + +typedef TIsoDataPDU *PIsoDataPDU; +typedef TIsoPayload *PIsoPayload; + +typedef enum { + pkConnectionRequest, + pkDisconnectRequest, + pkEmptyFragment, + pkInvalidPDU, + pkUnrecognizedType, + pkValidData +} TPDUKind ; + +#pragma pack() + +void ErrIsoText(int Error, char *Msg, int len); + +class TIsoTcpSocket : public TMsgSocket +{ +private: + + TIsoControlPDU FControlPDU; + int IsoMaxFragments; // max fragments allowed for an ISO telegram + // Checks the PDU format + int CheckPDU(void *pPDU, u_char PduTypeExpected); + // Receives the next fragment + int isoRecvFragment(void *From, int Max, int &Size, bool &EoT); +protected: + TIsoDataPDU PDU; + int SetIsoError(int Error); + // Builds the control PDU starting from address properties + virtual int BuildControlPDU(); + // Calcs the PDU size + int PDUSize(void *pPDU); + // Parses the connection request PDU to extract TSAP and PDU size info + virtual void IsoParsePDU(TIsoControlPDU PDU); + // Confirms the connection, override this method for special pourpose + // By default it checks the PDU format and resend it changing the pdu type + int IsoConfirmConnection(u_char PDUType); + void ClrIsoError(); + virtual void FragmentSkipped(int Size); +public: + word SrcTSap; // Source TSAP + word DstTSap; // Destination TSAP + word SrcRef; // Source Reference + word DstRef; // Destination Reference + int IsoPDUSize; + int LastIsoError; + //-------------------------------------------------------------------------- + TIsoTcpSocket(); + ~TIsoTcpSocket(); + // HIGH Level functions (work on payload hiding the underlying protocol) + // Connects with a peer, the connection PDU is automatically built starting from address scheme (see below) + int isoConnect(); + // Disconnects from a peer, if OnlyTCP = true, only a TCP disconnect is performed, + // otherwise a disconnect PDU is built and send. + int isoDisconnect(bool OnlyTCP); + // Sends a buffer, a valid header is created + int isoSendBuffer(void *Data, int Size); + // Receives a buffer + int isoRecvBuffer(void *Data, int & Size); + // Exchange cycle send->receive + int isoExchangeBuffer(void *Data, int & Size); + // A PDU is ready (at least its header) to be read + bool IsoPDUReady(); + // Same as isoSendBuffer, but the entire PDU has to be provided (in any case a check is performed) + int isoSendPDU(PIsoDataPDU Data); + // Same as isoRecvBuffer, but it returns the entire PDU, automatically enques the fragments + int isoRecvPDU(PIsoDataPDU Data); + // Same as isoExchangeBuffer, but the entire PDU has to be provided (in any case a check is performed) + int isoExchangePDU(PIsoDataPDU Data); + // Peeks an header info to know which kind of telegram is incoming + void IsoPeek(void *pPDU, TPDUKind &PduKind); +}; + +#endif // s7_isotcp_h diff --git a/core/src/drivers/plugins/native/s7comm/snap7/core/s7_micro_client.cpp b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_micro_client.cpp new file mode 100644 index 00000000..b5c727c3 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_micro_client.cpp @@ -0,0 +1,3328 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 Davide Nardella | +| All rights reserved. | +|==============================================================================| +| SNAP7 is free software: you can redistribute it and/or modify | +| it under the terms of the Lesser GNU General Public License as published by | +| the Free Software Foundation, either version 3 of the License, or | +| (at your option) any later version. | +| | +| It means that you can distribute your commercial software linked with | +| SNAP7 without the requirement to distribute the source code of your | +| application and without the requirement that your application be itself | +| distributed under LGPL. | +| | +| SNAP7 is distributed in the hope that it will be useful, | +| but WITHOUT ANY WARRANTY; without even the implied warranty of | +| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | +| Lesser GNU General Public License for more details. | +| | +| You should have received a copy of the GNU General Public License and a | +| copy of Lesser GNU General Public License along with Snap7. | +| If not, see http://www.gnu.org/licenses/ | +|=============================================================================*/ +#include "s7_micro_client.h" +//--------------------------------------------------------------------------- + +TSnap7MicroClient::TSnap7MicroClient() +{ + SrcRef =0x0100; // RFC0983 states that SrcRef and DetRef should be 0 + // and, in any case, they are ignored. + // S7 instead requires a number != 0 + // Libnodave uses 0x0100 + // S7Manager uses 0x0D00 + // TIA Portal V12 uses 0x1D00 + // WinCC uses 0x0300 + // Seems that every non zero value is good enough... + DstRef =0x0000; + SrcTSap =0x0100; + DstTSap =0x0000; // It's filled by connection functions + ConnectionType = CONNTYPE_PG; // Default connection type + memset(&Job,0,sizeof(TSnap7Job)); +} +//--------------------------------------------------------------------------- +TSnap7MicroClient::~TSnap7MicroClient() +{ + Destroying = true; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opReadArea() +{ + PReqFunReadParams ReqParams; + PResFunReadParams ResParams; + PS7ResHeader23 Answer; + PResFunReadItem ResData; + word RPSize; // ReqParams size + int WordSize; + uintptr_t Offset; + pbyte Target; + int Address; + int IsoSize; + int Start; + int MaxElements; // Max elements that we can transfer in a PDU + word NumElements; // Num of elements that we are asking for this telegram + int TotElements; // Total elements requested + int Size; + int Result; + + WordSize=DataSizeByte(Job.WordLen); // The size in bytes of an element that we are asking for + if (WordSize==0) + return errCliInvalidWordLen; + // First check : params bounds + if ((Job.Number<0) || (Job.Number>65535) || (Job.Start<0) || (Job.Amount<1)) + return errCliInvalidParams; + // Second check : transport size + if ((Job.WordLen==S7WLBit) && (Job.Amount>1)) + return errCliInvalidTransportSize; + // Request Params size + RPSize =sizeof(TReqFunReadItem)+2; // 1 item + FunRead + ItemsCount + // Setup pointers (note : PDUH_out and PDU.Payload are the same pointer) + ReqParams =PReqFunReadParams(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + Answer =PS7ResHeader23(&PDU.Payload); + ResParams =PResFunReadParams(pbyte(Answer)+ResHeaderSize23); + ResData =PResFunReadItem(pbyte(ResParams)+sizeof(TResFunReadParams)); + // Each packet cannot exceed the PDU length (in bytes) negotiated, and moreover + // we must ensure to transfer a "finite" number of item per PDU + MaxElements=(PDULength-sizeof(TS7ResHeader23)-sizeof(TResFunReadParams)-4) / WordSize; + TotElements=Job.Amount; + Start =Job.Start; + Offset =0; + Result =0; + while ((TotElements>0) && (Result==0)) + { + NumElements=TotElements; + if (NumElements>MaxElements) + NumElements=MaxElements; + + Target=pbyte(Job.pData)+Offset; + //----------------------------------------------- Read next slice----- + PDUH_out->P = 0x32; // Always 0x32 + PDUH_out->PDUType = PduType_request; // 0x01 + PDUH_out->AB_EX = 0x0000; // Always 0x0000 + PDUH_out->Sequence = GetNextWord(); // AutoInc + PDUH_out->ParLen = SwapWord(RPSize); // 14 bytes params + PDUH_out->DataLen = 0x0000; // No data + + ReqParams->FunRead = pduFuncRead; // 0x04 + ReqParams->ItemsCount = 1; + ReqParams->Items[0].ItemHead[0] = 0x12; + ReqParams->Items[0].ItemHead[1] = 0x0A; + ReqParams->Items[0].ItemHead[2] = 0x10; + ReqParams->Items[0].TransportSize = Job.WordLen; + ReqParams->Items[0].Length = SwapWord(NumElements); + ReqParams->Items[0].Area = Job.Area; + if (Job.Area==S7AreaDB) + ReqParams->Items[0].DBNumber = SwapWord(Job.Number); + else + ReqParams->Items[0].DBNumber = 0x0000; + // Adjusts the offset + if ((Job.WordLen==S7WLBit) || (Job.WordLen==S7WLCounter) || (Job.WordLen==S7WLTimer)) + Address = Start; + else + Address = Start*8; + + ReqParams->Items[0].Address[2] = Address & 0x000000FF; + Address = Address >> 8; + ReqParams->Items[0].Address[1] = Address & 0x000000FF; + Address = Address >> 8; + ReqParams->Items[0].Address[0] = Address & 0x000000FF; + + IsoSize = sizeof(TS7ReqHeader)+RPSize; + Result = isoExchangeBuffer(0,IsoSize); + // Get Data + if (Result==0) // 1St level Iso + { + Size = 0; + // Item level error + if (ResData->ReturnCode==0xFF) // <-- 0xFF means Result OK + { + // Calcs data size in bytes + Size = SwapWord(ResData->DataLength); + // Adjust Size in accord of TransportSize + if ((ResData->TransportSize != TS_ResOctet) && (ResData->TransportSize != TS_ResReal) && (ResData->TransportSize != TS_ResBit)) + Size = Size >> 3; + memcpy(Target, &ResData->Data[0], Size); + } + else + Result = CpuError(ResData->ReturnCode); + Offset+=Size; + }; + //-------------------------------------------------------------------- + TotElements -= NumElements; + Start += NumElements*WordSize; + } + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opWriteArea() +{ + PReqFunWriteParams ReqParams; + PReqFunWriteDataItem ReqData; // only 1 item for WriteArea Function + PResFunWrite ResParams; + PS7ResHeader23 Answer; + word RPSize; // ReqParams size + word RHSize; // Request headers size + bool First = true; + pbyte Source; + pbyte Target; + int Address; + int IsoSize; + int WordSize; + word Size; + uintptr_t Offset = 0; + int Start; // where we are starting from for this telegram + int MaxElements; // Max elements that we can transfer in a PDU + word NumElements; // Num of elements that we are asking for this telegram + int TotElements; // Total elements requested + int Result = 0; + + WordSize=DataSizeByte(Job.WordLen); // The size in bytes of an element that we are pushing + if (WordSize==0) + return errCliInvalidWordLen; + // First check : params bounds + if ((Job.Number<0) || (Job.Number>65535) || (Job.Start<0) || (Job.Amount<1)) + return errCliInvalidParams; + // Second check : transport size + if ((Job.WordLen==S7WLBit) && (Job.Amount>1)) + return errCliInvalidTransportSize; + + RHSize =sizeof(TS7ReqHeader)+ // Request header + 2+ // FunWrite+ItemCount (of TReqFunWriteParams) + sizeof(TReqFunWriteItem)+// 1 item reference + 4; // ReturnCode+TransportSize+DataLength + RPSize =sizeof(TReqFunWriteItem)+2; + + // Setup pointers (note : PDUH_out and PDU.Payload are the same pointer) + ReqParams=PReqFunWriteParams(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + ReqData =PReqFunWriteDataItem(pbyte(ReqParams)+sizeof(TReqFunWriteItem)+2); // 2 = FunWrite+ItemsCount + Target =pbyte(ReqData)+4; // 4 = ReturnCode+TransportSize+DataLength + Answer =PS7ResHeader23(&PDU.Payload); + ResParams=PResFunWrite(pbyte(Answer)+ResHeaderSize23); + + // Each packet cannot exceed the PDU length (in bytes) negotiated, and moreover + // we must ensure to transfer a "finite" number of item per PDU + MaxElements=(PDULength-RHSize) / WordSize; + TotElements=Job.Amount; + Start =Job.Start; + while ((TotElements>0) && (Result==0)) + { + NumElements=TotElements; + if (NumElements>MaxElements) + NumElements=MaxElements; + Source=pbyte(Job.pData)+Offset; + + Size=NumElements * WordSize; + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_request; // 0x01 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen =SwapWord(RPSize); // 14 bytes params + PDUH_out->DataLen =SwapWord(Size+4); + + ReqParams->FunWrite=pduFuncWrite; // 0x05 + ReqParams->ItemsCount=1; + ReqParams->Items[0].ItemHead[0]=0x12; + ReqParams->Items[0].ItemHead[1]=0x0A; + ReqParams->Items[0].ItemHead[2]=0x10; + ReqParams->Items[0].TransportSize=Job.WordLen; + ReqParams->Items[0].Length=SwapWord(NumElements); + ReqParams->Items[0].Area=Job.Area; + if (Job.Area==S7AreaDB) + ReqParams->Items[0].DBNumber=SwapWord(Job.Number); + else + ReqParams->Items[0].DBNumber=0x0000; + + // Adjusts the offset + if ((Job.WordLen==S7WLBit) || (Job.WordLen==S7WLCounter) || (Job.WordLen==S7WLTimer)) + Address=Start; + else + Address=Start*8; + + ReqParams->Items[0].Address[2]=Address & 0x000000FF; + Address=Address >> 8; + ReqParams->Items[0].Address[1]=Address & 0x000000FF; + Address=Address >> 8; + ReqParams->Items[0].Address[0]=Address & 0x000000FF; + + ReqData->ReturnCode=0x00; + + switch(Job.WordLen) + { + case S7WLBit: + ReqData->TransportSize=TS_ResBit; + break; + case S7WLInt: + case S7WLDInt: + ReqData->TransportSize=TS_ResInt; + break; + case S7WLReal: + ReqData->TransportSize=TS_ResReal; + break; + case S7WLChar : + case S7WLCounter: + case S7WLTimer: + ReqData->TransportSize=TS_ResOctet; + break; + default: + ReqData->TransportSize=TS_ResByte; + break; + }; + + if ((ReqData->TransportSize!=TS_ResOctet) && (ReqData->TransportSize!=TS_ResReal) && (ReqData->TransportSize!=TS_ResBit)) + ReqData->DataLength=SwapWord(Size*8); + else + ReqData->DataLength=SwapWord(Size); + + memcpy(Target, Source, Size); + IsoSize=RHSize + Size; + Result=isoExchangeBuffer(0,IsoSize); + + if (Result==0) // 1St check : Iso result + { + Result=CpuError(SwapWord(Answer->Error)); // 2nd level global error + if (Result==0) + { // 2th check : item error + if (ResParams->Data[0] == 0xFF) // <-- 0xFF means Result OK + Result=0; + else + // Now we check the error : if it's the first part we report the cpu error + // otherwise we warn that the function failed but some data were written + if (First) + Result=CpuError(ResParams->Data[0]); + else + Result=errCliPartialDataWritten; + }; + Offset+=Size; + }; + First=false; + + TotElements-=NumElements; + Start+=(NumElements*WordSize); + } + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opReadMultiVars() +{ + PS7DataItem Item; + PReqFunReadParams ReqParams; + PS7ResHeader23 Answer; + PResFunReadParams ResParams; + TResFunReadData ResData; + + word RPSize; // ReqParams size + uintptr_t Offset =0 ; + word Slice; + longword Address; + int IsoSize; + pbyte P; + int ItemsCount, c, Result; + + Item = PS7DataItem(Job.pData); + ItemsCount = Job.Amount; + + // Some useful initial check to detail the errors (Since S7 CPU always answers + // with $05 if (something is wrong in params) + if (ItemsCount>MaxVars) + return errCliTooManyItems; + + // Adjusts Word Length in case of timers and counters and clears results + for (c = 0; c < ItemsCount; c++) + { + Item->Result=0; + if (Item->Area==S7AreaCT) + Item->WordLen=S7WLCounter; + if (Item->Area==S7AreaTM) + Item->WordLen=S7WLTimer; + Item++; + }; + + // Let's build the PDU + RPSize = word(2 + ItemsCount * sizeof(TReqFunReadItem)); + ReqParams = PReqFunReadParams(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + Answer = PS7ResHeader23(&PDU.Payload); + ResParams = PResFunReadParams(pbyte(Answer)+ResHeaderSize23); + // Fill Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_request; // 0x01 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen=SwapWord(RPSize); // Request params size + PDUH_out->DataLen=0x0000; // No data in output + + // Fill Params + ReqParams->FunRead=pduFuncRead; // 0x04 + ReqParams->ItemsCount=ItemsCount; + + Item = PS7DataItem(Job.pData); + for (c = 0; c < ItemsCount; c++) + { + ReqParams->Items[c].ItemHead[0]=0x12; + ReqParams->Items[c].ItemHead[1]=0x0A; + ReqParams->Items[c].ItemHead[2]=0x10; + + ReqParams->Items[c].TransportSize=Item->WordLen; + ReqParams->Items[c].Length=SwapWord(Item->Amount); + ReqParams->Items[c].Area=Item->Area; + // Automatically drops DBNumber if (Area is not DB + if (Item->Area==S7AreaDB) + ReqParams->Items[c].DBNumber=SwapWord(Item->DBNumber); + else + ReqParams->Items[c].DBNumber=0x0000; + // Adjusts the offset + if ((Item->WordLen==S7WLBit) || (Item->WordLen==S7WLCounter) || (Item->WordLen==S7WLTimer)) + Address=Item->Start; + else + Address=Item->Start*8; + // Builds the offset + ReqParams->Items[c].Address[2]=Address & 0x000000FF; + Address=Address >> 8; + ReqParams->Items[c].Address[1]=Address & 0x000000FF; + Address=Address >> 8; + ReqParams->Items[c].Address[0]=Address & 0x000000FF; + Item++; + }; + + IsoSize=RPSize+sizeof(TS7ReqHeader); + if (IsoSize>PDULength) + return errCliSizeOverPDU; + Result=isoExchangeBuffer(0,IsoSize); + + if (Result!=0) + return Result; + + // Function level error + if (Answer->Error!=0) + return CpuError(SwapWord(Answer->Error)); + + if (ResParams->ItemCount!=ItemsCount) + return errCliInvalidPlcAnswer; + + P=pbyte(ResParams)+sizeof(TResFunReadParams); + Item = PS7DataItem(Job.pData); + for (c = 0; c < ItemsCount; c++) + { + ResData[c] =PResFunReadItem(pbyte(P)+Offset); + Slice=0; + // Item level error + if (ResData[c]->ReturnCode==0xFF) // <-- 0xFF means Result OK + { + // Calcs data size in bytes + Slice=SwapWord(ResData[c]->DataLength); + // Adjust Size in accord of TransportSize + if ((ResData[c]->TransportSize != TS_ResOctet) && (ResData[c]->TransportSize != TS_ResReal) && (ResData[c]->TransportSize != TS_ResBit)) + Slice=Slice >> 3; + + memcpy(Item->pdata, ResData[c]->Data, Slice); + Item->Result=0; + } + else + Item->Result=CpuError(ResData[c]->ReturnCode); + + if ((Slice % 2)!=0) + Slice++; // Skip fill byte for Odd frame + + Offset+=(4+Slice); + Item++; + }; + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opWriteMultiVars() +{ + PS7DataItem Item; + PReqFunWriteParams ReqParams; + PResFunWrite ResParams; + TReqFunWriteData ReqData; + PS7ResHeader23 Answer; + pbyte P; + uintptr_t Offset; + longword Address; + int ItemsCount, c, IsoSize; + word RPSize; // ReqParams size + word Size; // Write data size + int WordSize, Result; + + Item = PS7DataItem(Job.pData); + ItemsCount = Job.Amount; + + // Some useful initial check to detail the errors (Since S7 CPU always answers + // with $05 if (something is wrong in params) + if (ItemsCount>MaxVars) + return errCliTooManyItems; + + // Adjusts Word Length in case of timers and counters and clears results + for (c = 0; c < ItemsCount; c++) + { + Item->Result=0; + if (Item->Area==S7AreaCT) + Item->WordLen=S7WLCounter; + if (Item->Area==S7AreaTM) + Item->WordLen=S7WLTimer; + Item++; + }; + + // Let's build the PDU : setup pointers + RPSize = word(2 + ItemsCount * sizeof(TReqFunWriteItem)); + ReqParams = PReqFunWriteParams(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + Answer = PS7ResHeader23(&PDU.Payload); + ResParams = PResFunWrite(pbyte(Answer)+ResHeaderSize23); + P=pbyte(ReqParams)+RPSize; + + // Fill Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_request; // 0x01 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen=SwapWord(RPSize); // Request params size + + // Fill Params + ReqParams->FunWrite=pduFuncWrite; // 0x05 + ReqParams->ItemsCount=ItemsCount; + + Offset=0; + Item = PS7DataItem(Job.pData); + for (c = 0; c < ItemsCount; c++) + { + // Items Params + ReqParams->Items[c].ItemHead[0]=0x12; + ReqParams->Items[c].ItemHead[1]=0x0A; + ReqParams->Items[c].ItemHead[2]=0x10; + + ReqParams->Items[c].TransportSize=Item->WordLen; + ReqParams->Items[c].Length=SwapWord(Item->Amount); + ReqParams->Items[c].Area=Item->Area; + + if (Item->Area==S7AreaDB) + ReqParams->Items[c].DBNumber=SwapWord(Item->DBNumber); + else + ReqParams->Items[c].DBNumber=0x0000; + + // Adjusts the offset + if ((Item->WordLen==S7WLBit) || (Item->WordLen==S7WLCounter) || (Item->WordLen==S7WLTimer)) + Address=Item->Start; + else + Address=Item->Start*8; + // Builds the offset + ReqParams->Items[c].Address[2]=Address & 0x000000FF; + Address=Address >> 8; + ReqParams->Items[c].Address[1]=Address & 0x000000FF; + Address=Address >> 8; + ReqParams->Items[c].Address[0]=Address & 0x000000FF; + + // Items Data + ReqData[c]=PReqFunWriteDataItem(pbyte(P)+Offset); + ReqData[c]->ReturnCode=0x00; + + switch (Item->WordLen) + { + case S7WLBit : + ReqData[c]->TransportSize=TS_ResBit; + break; + case S7WLInt : + case S7WLDInt : + ReqData[c]->TransportSize=TS_ResInt; + break; + case S7WLReal : + ReqData[c]->TransportSize=TS_ResReal; + break; + case S7WLChar : + case S7WLCounter : + case S7WLTimer : ReqData[c]->TransportSize=TS_ResOctet; + break; + default : + ReqData[c]->TransportSize=TS_ResByte; // byte/word/dword etc. + break; + }; + + WordSize=DataSizeByte(Item->WordLen); + Size=Item->Amount * WordSize; + + if ((ReqData[c]->TransportSize!=TS_ResOctet) && (ReqData[c]->TransportSize!=TS_ResReal) && (ReqData[c]->TransportSize!=TS_ResBit)) + ReqData[c]->DataLength=SwapWord(Size*8); + else + ReqData[c]->DataLength=SwapWord(Size); + + memcpy(ReqData[c]->Data, Item->pdata, Size); + + if ((Size % 2) != 0 && (ItemsCount - c != 1)) + Size++; // Skip fill byte for Odd frame (except for the last one) + + Offset+=(4+Size); // next item + Item++; + }; + + PDUH_out->DataLen=SwapWord(word(Offset)); + + IsoSize=RPSize+sizeof(TS7ReqHeader)+int(Offset); + if (IsoSize>PDULength) + return errCliSizeOverPDU; + Result=isoExchangeBuffer(0,IsoSize); + + if (Result!=0) + return Result; + + // Function level error + if (Answer->Error!=0) + return CpuError(SwapWord(Answer->Error)); + + if (ResParams->ItemCount!=ItemsCount) + return errCliInvalidPlcAnswer; + + Item = PS7DataItem(Job.pData); + for (c = 0; c < ItemsCount; c++) + { + // Item level error + if (ResParams->Data[c]==0xFF) // <-- 0xFF means Result OK + Item->Result=0; + else + Item->Result=CpuError(ResParams->Data[c]); + Item++; + }; + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opListBlocks() +{ + PReqFunGetBlockInfo ReqParams; + PReqDataFunBlocks ReqData; + PResFunGetBlockInfo ResParams; + PDataFunListAll ResData; + PS7ResHeader17 Answer; + PS7BlocksList List; + int IsoSize, Result; + + // Setup pointers (note : PDUH_out and PDU.Payload are the same pointer) + ReqParams=PReqFunGetBlockInfo(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + ReqData =PReqDataFunBlocks(pbyte(ReqParams)+sizeof(TReqFunGetBlockInfo)); + Answer =PS7ResHeader17(&PDU.Payload); + ResParams=PResFunGetBlockInfo(pbyte(Answer)+ResHeaderSize17); + ResData =PDataFunListAll(pbyte(ResParams)+sizeof(TResFunGetBlockInfo)); + List =PS7BlocksList(Job.pData); + // Fill Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_userdata; // 0x07 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen=SwapWord(sizeof(TReqFunGetBlockInfo)); // 8 bytes params + PDUH_out->DataLen=SwapWord(sizeof(TReqDataFunBlocks)); // 4 bytes data + // Fill params (mostly constants) + ReqParams->Head[0]=0x00; + ReqParams->Head[1]=0x01; + ReqParams->Head[2]=0x12; + ReqParams->Plen =0x04; + ReqParams->Uk =0x11; + ReqParams->Tg =grBlocksInfo; + ReqParams->SubFun =SFun_ListAll; + ReqParams->Seq =0x00; + // Fill data + ReqData[0] =0x0A; + ReqData[1] =0x00; + ReqData[2] =0x00; + ReqData[3] =0x00; + + IsoSize=sizeof(TS7ReqHeader)+sizeof(TReqFunGetBlockInfo)+sizeof(TReqDataFunBlocks); + Result=isoExchangeBuffer(0,IsoSize); + // Get Data + if (Result==0) + { + if (ResParams->ErrNo==0) + { + if (SwapWord(ResData->Length)!=28) + return errCliInvalidPlcAnswer; + + for (int c = 0; c < 7; c++) + { + switch (ResData->Blocks[c].BType) + { + case Block_OB: + List->OBCount=SwapWord(ResData->Blocks[c].BCount); + break; + case Block_DB: + List->DBCount=SwapWord(ResData->Blocks[c].BCount); + break; + case Block_SDB: + List->SDBCount=SwapWord(ResData->Blocks[c].BCount); + break; + case Block_FC: + List->FCCount=SwapWord(ResData->Blocks[c].BCount); + break; + case Block_SFC: + List->SFCCount=SwapWord(ResData->Blocks[c].BCount); + break; + case Block_FB: + List->FBCount=SwapWord(ResData->Blocks[c].BCount); + break; + case Block_SFB: + List->SFBCount=SwapWord(ResData->Blocks[c].BCount); + break; + } + } + } + else + Result=CpuError(SwapWord(ResParams->ErrNo)); + } + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opListBlocksOfType() +{ + PReqFunGetBlockInfo ReqParams; + PReqDataBlockOfType ReqData; + + PS7ResHeader17 Answer; + PResFunGetBlockInfo ResParams; + PDataFunGetBot ResData; + longword *PadData; + word *List; + bool First; + bool Done = false; + byte BlockType, In_Seq; + int Count, Last, IsoSize, Result; + int c, CThis; + word DataLength; + bool RoomError = false; + + BlockType=Job.Area; + List=(word*)(&opData); + // Setup pointers (note : PDUH_out and PDU.Payload are the same pointer) + ReqParams=PReqFunGetBlockInfo(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + Answer =PS7ResHeader17(&PDU.Payload); + ResParams=PResFunGetBlockInfo(pbyte(Answer)+ResHeaderSize17); + ResData =PDataFunGetBot(pbyte(ResParams)+sizeof(TResFunGetBlockInfo)); + // Get Data + First =true; + In_Seq=0x00; // first group sequence, next will come from PLC + Count =0; + Last =0; + do + { + //<--------------------------------------------------------- Get next slice + // Fill Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_userdata; // 0x07 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + if (First) + { + PDUH_out->ParLen=SwapWord(8); // 8 bytes params + PDUH_out->DataLen=SwapWord(6); // 6 bytes data + DataLength=14; + } + else + { + PDUH_out->ParLen=SwapWord(12); // 12 bytes params + PDUH_out->DataLen=SwapWord(4); // 4 bytes data + DataLength=16; + } + + // Fill params (mostly constants) + ReqParams->Head[0]=0x00; + ReqParams->Head[1]=0x01; + ReqParams->Head[2]=0x12; + + if (First) + ReqParams->Plen =0x04; + else + ReqParams->Plen =0x08; + + if (First) + ReqParams->Uk = 0x11; + else + ReqParams->Uk = 0x12; + + ReqParams->Tg =grBlocksInfo; + ReqParams->SubFun =SFun_ListBoT; + ReqParams->Seq =In_Seq; + + // Fill data + if (First) + { + // overlap resvd and error to avoid another struct... + ReqData =PReqDataBlockOfType(pbyte(ReqParams)+sizeof(TReqFunGetBlockInfo)); + ReqData->RetVal =0xFF; + ReqData->TSize =TS_ResOctet; + ReqData->Length =SwapWord(0x0002); + ReqData->Zero =0x30; // zero ascii '0' + ReqData->BlkType =BlockType; + } + else + { + PadData =(longword*)(pbyte(ReqParams)+sizeof(TReqFunGetBlockInfo)); + ReqData =PReqDataBlockOfType(pbyte(ReqParams)+sizeof(TReqFunGetBlockInfo)+4); + *PadData =0x00000000; + ReqData->RetVal =0x0A; + ReqData->TSize =0x00; + ReqData->Length =0x0000; + ReqData->Zero =0x00; + ReqData->BlkType =0x00; + }; + + IsoSize=sizeof(TS7ReqHeader)+DataLength; + Result=isoExchangeBuffer(0,IsoSize); + + if (Result==0) + { + if (ResParams->ErrNo==0) + { + if (ResData->RetVal==0xFF) + { + Done=((ResParams->Rsvd & 0xFF00) == 0); // Low order byte = 0x00 => the sequence is done + In_Seq=ResParams->Seq; // every next telegram must have this number + CThis=((SwapWord(ResData->DataLen) - 4 ) / 4) + 1; // Partial counter + for (c=0; c < CThis+1; c++) + { + *List=SwapWord(ResData->Items[c].BlockNum); + Last++; + List++; + if (Last==0x8000) + { + Done=true; + break; + }; + }; + Count+=CThis; // Total counter + List--; + } + else + Result=errCliItemNotAvailable; + } + else + Result=errCliItemNotAvailable; + }; + First=false; + //---------------------------------------------------------> Get next slice + } + while ((!Done) && (Result==0)); + + *Job.pAmount=0; + if (Result==0) + { + if (Count>Job.Amount) + { + Count=Job.Amount; + RoomError=true; + } + memcpy(Job.pData, &opData, Count*2); + *Job.pAmount=Count; + + if (RoomError) // Result==0 -> override if romerror + Result=errCliPartialDataRead; + }; + + return Result; +} +//--------------------------------------------------------------------------- +void TSnap7MicroClient::FillTime(word SiemensTime, char *PTime) +{ + // SiemensTime -> number of seconds after 1/1/1984 + // This is not S7 date and time but is used only internally for block info + time_t TheDate = (SiemensTime * 86400)+ DeltaSecs; + struct tm * timeinfo = localtime (&TheDate); + if (timeinfo!=NULL) { + strftime(PTime,11,"%Y/%m/%d",timeinfo); + } + else + *PTime='\0'; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opAgBlockInfo() +{ + PS7BlockInfo BlockInfo; + PReqFunGetBlockInfo ReqParams; + PReqDataBlockInfo ReqData; + PS7ResHeader17 Answer; + PResFunGetBlockInfo ResParams; + PResDataBlockInfo ResData; + byte BlockType; + int BlockNum, IsoSize, Result; + + BlockType=Job.Area; + BlockNum =Job.Number; + BlockInfo=PS7BlockInfo(Job.pData); + memset(BlockInfo,0,sizeof(TS7BlockInfo)); + // Setup pointers (note : PDUH_out and PDU.Payload are the same pointer) + ReqParams=PReqFunGetBlockInfo(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + ReqData =PReqDataBlockInfo(pbyte(ReqParams)+sizeof(TReqFunGetBlockInfo)); + Answer =PS7ResHeader17(&PDU.Payload); + ResParams=PResFunGetBlockInfo(pbyte(Answer)+ResHeaderSize17); + ResData =PResDataBlockInfo(pbyte(ResParams)+sizeof(TResFunGetBlockInfo)); + // Fill Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_userdata; // 0x07 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen=SwapWord(sizeof(TReqFunGetBlockInfo)); // 8 bytes params + PDUH_out->DataLen=SwapWord(sizeof(TReqDataBlockInfo)); // 4 bytes data + // Fill params (mostly constants) + ReqParams->Head[0]=0x00; + ReqParams->Head[1]=0x01; + ReqParams->Head[2]=0x12; + ReqParams->Plen =0x04; + ReqParams->Uk =0x11; + ReqParams->Tg =grBlocksInfo; + ReqParams->SubFun =SFun_BlkInfo; + ReqParams->Seq =0x00; + // Fill data + ReqData->RetVal =0xFF; + ReqData->TSize =TS_ResOctet; + ReqData->DataLen =SwapWord(0x0008); + ReqData->BlkPrfx =0x30; + ReqData->BlkType =BlockType; + ReqData->A =0x41; + + ReqData->AsciiBlk[0]=(BlockNum / 10000)+0x30; + BlockNum=BlockNum % 10000; + ReqData->AsciiBlk[1]=(BlockNum / 1000)+0x30; + BlockNum=BlockNum % 1000; + ReqData->AsciiBlk[2]=(BlockNum / 100)+0x30; + BlockNum=BlockNum % 100; + ReqData->AsciiBlk[3]=(BlockNum / 10)+0x30; + BlockNum=BlockNum % 10; + ReqData->AsciiBlk[4]=(BlockNum / 1)+0x30; + + IsoSize=sizeof(TS7ReqHeader)+sizeof(TReqFunGetBlockInfo)+sizeof(TReqDataBlockInfo); + Result=isoExchangeBuffer(0,IsoSize); + + // Get Data + if (Result==0) + { + if (ResParams->ErrNo==0) + { + if (SwapWord(ResData->Length)<40) // 78 + return errCliInvalidPlcAnswer; + if (ResData->RetVal==0xFF) // <-- 0xFF means Result OK + { + //<----------------------------------------------Fill block info + BlockInfo->BlkType=ResData->SubBlkType; + BlockInfo->BlkNumber=SwapWord(ResData->BlkNumber); + BlockInfo->BlkLang=ResData->BlkLang; + BlockInfo->BlkFlags=ResData->BlkFlags; + BlockInfo->MC7Size=SwapWord(ResData->MC7Len); + BlockInfo->LoadSize=SwapDWord(ResData->LenLoadMem); + BlockInfo->LocalData=SwapWord(ResData->LocDataLen); + BlockInfo->SBBLength=SwapWord(ResData->SbbLen); + BlockInfo->CheckSum=SwapWord(ResData->BlkChksum); + BlockInfo->Version=ResData->Version; + memcpy(BlockInfo->Author, ResData->Author, 8); + memcpy(BlockInfo->Family,ResData->Family,8); + memcpy(BlockInfo->Header,ResData->Header,8); + FillTime(SwapWord(ResData->CodeTime_dy),BlockInfo->CodeDate); + FillTime(SwapWord(ResData->IntfTime_dy),BlockInfo->IntfDate); + //---------------------------------------------->Fill block info + } + else + Result=CpuError(ResData->RetVal); + } + else + Result=CpuError(SwapWord(ResParams->ErrNo)); + }; + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opDBGet() +{ + TS7BlockInfo BI; + void * usrPData; + int * usrPSize; + int Result, Room; + bool RoomError = false; + + // Stores user pointer + usrPData=Job.pData; + usrPSize=Job.pAmount; + Room =Job.Amount; + + // 1 Pass : Get block info + Job.Area=Block_DB; + Job.pData=&BI; + Result=opAgBlockInfo(); + + // 2 Pass : Read the whole (MC7Size bytes) DB. + if (Result==0) + { + // Check user space + if (BI.MC7Size>Room) + { + Job.Amount=Room; + RoomError=true; + } + else + Job.Amount =BI.MC7Size; + // The data is read even if the buffer is small (the error is reported). + // Imagine that we want to read only a small amount of data at the + // beginning of a DB regardless it's size.... + Job.Area =S7AreaDB; + Job.WordLen=S7WLByte; + Job.Start =0; + Job.pData =usrPData; + Result =opReadArea(); + if (Result==0) + *usrPSize=Job.Amount; + } + + if ((Result==0) && RoomError) + return errCliBufferTooSmall; + else + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opDBFill() +{ + TS7BlockInfo BI; + int Result; + // new op : get block info + Job.Op =s7opAgBlockInfo; + Job.Area =Block_DB; + Job.pData=&BI; + Result =opAgBlockInfo(); + // Restore original op + Job.Op =s7opDBFill; + // Fill internal buffer then write it + if (Result==0) + { + Job.Amount =BI.MC7Size; + Job.Area =S7AreaDB; + Job.WordLen=S7WLByte; + Job.Start =0; + memset(&opData, byte(Job.IParam), Job.Amount); + Job.pData =&opData; + Result =opWriteArea(); + } + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opUpload() +{ + PS7ResHeader23 Answer; + int IsoSize; + byte Upload_ID = 0; // not strictly needed, only to avoid warning + byte BlockType; + int BlockNum, BlockLength, Result; + bool Done, Full; // if full==true, the data will be compatible to full download function + uintptr_t Offset; + bool RoomError = false; + + BlockType=Job.Area; + BlockNum =Job.Number; + Full =Job.IParam==1; + // Setup Answer (is the same for all Upload pdus) + Answer= PS7ResHeader23(&PDU.Payload); + // Init sequence + Done =false; + Offset=0; + //<-------------------------------------------------------------StartUpload + PReqFunStartUploadParams ReqParams; + PResFunStartUploadParams ResParams; + // Setup pointers (note : PDUH_out and PDU.Payload are the same pointer) + ReqParams=PReqFunStartUploadParams(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + ResParams=PResFunStartUploadParams(pbyte(Answer)+ResHeaderSize23); + // Init Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_request; // 0x01 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen=SwapWord(sizeof(TReqFunStartUploadParams));// params size + PDUH_out->DataLen=0x0000; // No data + // Init Params + ReqParams->FunSUpld=pduStartUpload; + ReqParams->Uk6[0]=0x00; + ReqParams->Uk6[1]=0x00; + ReqParams->Uk6[2]=0x00; + ReqParams->Uk6[3]=0x00; + ReqParams->Uk6[4]=0x00; + ReqParams->Uk6[5]=0x00; + ReqParams->Upload_ID=Upload_ID; // At begining is 0) we will put upload id incoming from plc + ReqParams->Len_1 =0x09; // 9 bytes from here + ReqParams->Prefix=0x5F; + ReqParams->BlkPrfx=0x30; // '0' + ReqParams->BlkType=BlockType; + // Block number + ReqParams->AsciiBlk[0]=(BlockNum / 10000)+0x30; + BlockNum=BlockNum % 10000; + ReqParams->AsciiBlk[1]=(BlockNum / 1000)+0x30; + BlockNum=BlockNum % 1000; + ReqParams->AsciiBlk[2]=(BlockNum / 100)+0x30; + BlockNum=BlockNum % 100; + ReqParams->AsciiBlk[3]=(BlockNum / 10)+0x30; + BlockNum=BlockNum % 10; + ReqParams->AsciiBlk[4]=(BlockNum / 1)+0x30; + ReqParams->A=0x41; // 'A' + + IsoSize=sizeof(TS7ReqHeader)+sizeof(TReqFunStartUploadParams); + Result=isoExchangeBuffer(0,IsoSize); + // Get Upload Infos (only ID now) + if (Result==0) + { + if (Answer->Error==0) + Upload_ID=ResParams->Upload_ID; + else + Result=CpuError(SwapWord(Answer->Error)); + }; + //------------------------------------------------------------->StartUpload + if (Result==0) + { + //<--------------------------------------------------------FirstUpload + PReqFunUploadParams ReqParams; + PResFunUploadParams ResParams; + PResFunUploadDataHeaderFirst ResDataHeader; + pbyte Source; + pbyte Target; + int Size; + + // Setup pointers (note : PDUH_out and PDU.Payload are the same pointer) + ReqParams=PReqFunUploadParams(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + // First upload pdu consists of params, block info header, data. + ResParams=PResFunUploadParams(pbyte(Answer)+ResHeaderSize23); + ResDataHeader=PResFunUploadDataHeaderFirst(pbyte(ResParams)+sizeof(TResFunUploadParams)); + if (Full) + Source=pbyte(ResDataHeader)+4; // skip only the mini header + else + Source=pbyte(ResDataHeader)+sizeof(TResFunUploadDataHeaderFirst); // not full : skip the data header + // Init Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_request; // 0x01 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen=SwapWord(sizeof(TReqFunUploadParams));// params size + PDUH_out->DataLen=0x0000; // No data + // Init Params + ReqParams->FunUpld=pduUpload; + ReqParams->Uk6[0]=0x00; + ReqParams->Uk6[1]=0x00; + ReqParams->Uk6[2]=0x00; + ReqParams->Uk6[3]=0x00; + ReqParams->Uk6[4]=0x00; + ReqParams->Uk6[5]=0x00; + ReqParams->Upload_ID=Upload_ID; // At begining is 0) we will put upload id incoming from plc + + IsoSize=sizeof(TS7ReqHeader)+sizeof(TReqFunUploadParams); + Result=isoExchangeBuffer(0,IsoSize); + // Get Upload Infos (only ID now) + if (Result==0) + { + if (Answer->Error==0) + { + Done=ResParams->EoU==0; + if (Full) + Size=SwapWord(Answer->DataLen)-4; // Full data Size + else + Size=SwapWord(Answer->DataLen)-sizeof(TResFunUploadDataHeaderFirst); // Size of this data slice + + BlockLength=SwapWord(ResDataHeader->MC7Len); // Full block size in byte + Target=pbyte(&opData)+Offset; + memcpy(Target, Source, Size); + Offset+=Size; + } + else + Result=errCliUploadSequenceFailed; + }; + //-------------------------------------------------------->FirstUpload + while (!Done && (Result==0)) + { + //<----------------------------------------------------NextUpload + PReqFunUploadParams ReqParams; + PResFunUploadParams ResParams; + PResFunUploadDataHeaderNext ResDataHeader; + pbyte Source; + pbyte Target; + int Size; + + // Setup pointers (note : PDUH_out and PDU.Payload are the same pointer) + ReqParams=PReqFunUploadParams(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + // Next upload pdu consists of params, small info header, data. + ResParams=PResFunUploadParams(pbyte(Answer)+ResHeaderSize23); + ResDataHeader=PResFunUploadDataHeaderNext(pbyte(ResParams)+sizeof(TResFunUploadParams)); + Source=pbyte(ResDataHeader)+sizeof(TResFunUploadDataHeaderNext); + // Init Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_request; // 0x01 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen=SwapWord(sizeof(TReqFunUploadParams));// params size + PDUH_out->DataLen=0x0000; // No data + // Init Params + ReqParams->FunUpld=pduUpload; + ReqParams->Uk6[0]=0x00; + ReqParams->Uk6[1]=0x00; + ReqParams->Uk6[2]=0x00; + ReqParams->Uk6[3]=0x00; + ReqParams->Uk6[4]=0x00; + ReqParams->Uk6[5]=0x00; + ReqParams->Upload_ID=Upload_ID; // At begining is 0) we will put upload id incoming from plc + + IsoSize=sizeof(TS7ReqHeader)+sizeof(TReqFunUploadParams); + Result=isoExchangeBuffer(0,IsoSize); + // Get Upload Infos (only ID now) + if (Result==0) + { + if (Answer->Error==0) + { + Done=ResParams->EoU==0; + Size=SwapWord(Answer->DataLen)-sizeof(TResFunUploadDataHeaderNext); // Size of this data slice + Target=pbyte(&opData)+Offset; + memcpy(Target, Source, Size); + Offset+=Size; + } + else + Result=errCliUploadSequenceFailed; + }; + //---------------------------------------------------->NextUpload + } + if (Result==0) + { + //<----------------------------------------------------EndUpload; + PReqFunEndUploadParams ReqParams; + PResFunEndUploadParams ResParams; + + // Setup pointers (note : PDUH_out and PDU.Payload are the same pointer) + ReqParams=PReqFunEndUploadParams(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + ResParams=PResFunEndUploadParams(pbyte(Answer)+ResHeaderSize23); + // Init Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_request; // 0x01 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen=SwapWord(sizeof(TReqFunEndUploadParams));// params size + PDUH_out->DataLen=0x0000; // No data + // Init Params + ReqParams->FunEUpld=pduEndUpload; + ReqParams->Uk6[0]=0x00; + ReqParams->Uk6[1]=0x00; + ReqParams->Uk6[2]=0x00; + ReqParams->Uk6[3]=0x00; + ReqParams->Uk6[4]=0x00; + ReqParams->Uk6[5]=0x00; + ReqParams->Upload_ID=Upload_ID; // At begining is 0) we will put upload id incoming from plc + + IsoSize=sizeof(TS7ReqHeader)+sizeof(TReqFunEndUploadParams); + Result=isoExchangeBuffer(0,IsoSize); + + // Get EndUpload Result + if (Result==0) + { + if ((Answer->Error!=0) || (ResParams->FunEUpld!=pduEndUpload)) + Result=errCliUploadSequenceFailed; + }; + //---------------------------------------------------->EndUpload; + } + }; + + *Job.pAmount=0; + if (Result==0) + { + if (Full) + { + opSize=int(Offset); + if (opSize<78) + Result=errCliInvalidDataSizeRecvd; + } + else + { + opSize=BlockLength; + if (opSize<1) + Result=errCliInvalidDataSizeRecvd; + }; + if (Result==0) + { + // Checks user space + if (Job.Amount override if romerror + Result=errCliPartialDataRead; + }; + }; + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opDownload() +{ + PS7CompactBlockInfo Info; + PS7BlockFooter Footer; + int BlockNum, StoreBlockNum, BlockAmount; + int BlockSize, BlockSizeLd; + int BlockType, Remainder; + int Result, IsoSize; + bool Done = false; + uintptr_t Offset; + + BlockAmount=Job.Amount; + BlockNum =Job.Number; + Result=CheckBlock(-1,-1,&opData,BlockAmount); + if (Result==0) + { + Info=PS7CompactBlockInfo(&opData); + // Gets blocktype + BlockType=SubBlockToBlock(Info->SubBlkType); + + if (BlockNum>=0) + Info->BlkNum=SwapWord(BlockNum); // change the number + else + BlockNum=SwapWord(Info->BlkNum); // use the header's number + + BlockSizeLd=BlockAmount; // load mem needed for this block + BlockSize =SwapWord(Info->MC7Len); // net size + Footer=PS7BlockFooter(pbyte(&opData)+BlockSizeLd-sizeof(TS7BlockFooter)); + Footer->Chksum=0x0000; + + Offset=0; + Remainder=BlockAmount; + //<---------------------------------------------- Start Download request + PReqStartDownloadParams ReqParams; + PResStartDownloadParams ResParams; + PS7ResHeader23 Answer; + + // Setup pointers (note : PDUH_out and PDU.Payload are the same pointer) + ReqParams=PReqStartDownloadParams(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + Answer =PS7ResHeader23(&PDU.Payload); + ResParams=PResStartDownloadParams(pbyte(Answer)+ResHeaderSize23); + // Init Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_request; // 0x01 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen=SwapWord(sizeof(TReqStartDownloadParams)); + PDUH_out->DataLen=0x0000; // No data + // Init Params + ReqParams->FunSDwnld = pduReqDownload; + ReqParams->Uk6[0]=0x00; + ReqParams->Uk6[1]=0x01; + ReqParams->Uk6[2]=0x00; + ReqParams->Uk6[3]=0x00; + ReqParams->Uk6[4]=0x00; + ReqParams->Uk6[5]=0x00; + ReqParams->Dwnld_ID=0x00; + ReqParams->Len_1 =0x09; + ReqParams->Prefix=0x5F; + ReqParams->BlkPrfx=0x30; + ReqParams->BlkType=BlockType; + StoreBlockNum=BlockNum; + ReqParams->AsciiBlk[0]=(BlockNum / 10000)+0x30; + BlockNum=BlockNum % 10000; + ReqParams->AsciiBlk[1]=(BlockNum / 1000)+0x30; + BlockNum=BlockNum % 1000; + ReqParams->AsciiBlk[2]=(BlockNum / 100)+0x30; + BlockNum=BlockNum % 100; + ReqParams->AsciiBlk[3]=(BlockNum / 10)+0x30; + BlockNum=BlockNum % 10; + ReqParams->AsciiBlk[4]=(BlockNum / 1)+0x30; + ReqParams->P =0x50; + ReqParams->Len_2=0x0D; + ReqParams->Uk1 =0x31; // '1' + BlockNum=StoreBlockNum; + // Load memory + ReqParams->AsciiLoad[0]=(BlockSizeLd / 100000)+0x30; + BlockSizeLd=BlockSizeLd % 100000; + ReqParams->AsciiLoad[1]=(BlockSizeLd / 10000)+0x30; + BlockSizeLd=BlockSizeLd % 10000; + ReqParams->AsciiLoad[2]=(BlockSizeLd / 1000)+0x30; + BlockSizeLd=BlockSizeLd % 1000; + ReqParams->AsciiLoad[3]=(BlockSizeLd / 100)+0x30; + BlockSizeLd=BlockSizeLd % 100; + ReqParams->AsciiLoad[4]=(BlockSizeLd / 10)+0x30; + BlockSizeLd=BlockSizeLd % 10; + ReqParams->AsciiLoad[5]=(BlockSizeLd / 1)+0x30; + // MC7 memory + ReqParams->AsciiMC7[0]=(BlockSize / 100000)+0x30; + BlockSize=BlockSize % 100000; + ReqParams->AsciiMC7[1]=(BlockSize / 10000)+0x30; + BlockSize=BlockSize % 10000; + ReqParams->AsciiMC7[2]=(BlockSize / 1000)+0x30; + BlockSize=BlockSize % 1000; + ReqParams->AsciiMC7[3]=(BlockSize / 100)+0x30; + BlockSize=BlockSize % 100; + ReqParams->AsciiMC7[4]=(BlockSize / 10)+0x30; + BlockSize=BlockSize % 10; + ReqParams->AsciiMC7[5]=(BlockSize / 1)+0x30; + + IsoSize=sizeof(TS7ReqHeader)+sizeof(TReqStartDownloadParams); + Result=isoExchangeBuffer(0,IsoSize); + + // Get Result + if (Result==0) + { + if (SwapWord(Answer->Error)!=Code7NeedPassword) + { + if ((Answer->Error!=0) || (*ResParams!=pduReqDownload)) + Result=errCliDownloadSequenceFailed; + } + else + Result=errCliNeedPassword; + } + //----------------------------------------------> Start Download request + if (Result==0) + { + do + { + //<-------------------------------- Download sequence (PLC requests) + PReqDownloadParams ReqParams; + PS7ResHeader23 Answer; + PResDownloadParams ResParams; + PResDownloadDataHeader ResData; + int Slice, Size, MaxSlice; + word Sequence; + pbyte Source; + pbyte Target; + + ReqParams=PReqDownloadParams(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + Answer =PS7ResHeader23(&PDU.Payload); + ResParams=PResDownloadParams(pbyte(Answer)+ResHeaderSize23); + ResData =PResDownloadDataHeader(pbyte(ResParams)+sizeof(TResDownloadParams)); + Target =pbyte(ResData)+sizeof(TResDownloadDataHeader); + Source =pbyte(&opData)+Offset; + + Result=isoRecvBuffer(0,Size); + if (Result==0) + { + if ((u_int(Size)>sizeof(TS7ReqHeader)) && (ReqParams->Fun==pduDownload)) + { + Sequence=PDUH_out->Sequence; + // Max data slice that we can fit in this pdu + MaxSlice=PDULength-ResHeaderSize23-sizeof(TResDownloadParams)-sizeof(TResDownloadDataHeader); + Slice=Remainder; + if (Slice>MaxSlice) + Slice=MaxSlice; + Remainder-=Slice; + Offset+=Slice; + Done=Remainder<=0; + // Init Answer + Answer->P=0x32; + Answer->PDUType=PduType_response; + Answer->AB_EX=0x0000; + Answer->Sequence=Sequence; + Answer->ParLen =SwapWord(sizeof(TResDownloadParams)); + Answer->DataLen=SwapWord(word(sizeof(TResDownloadDataHeader))+Slice); + Answer->Error =0x0000; + + // Init Params + ResParams->FunDwnld=pduDownload; + if (Remainder>0) + ResParams->EoS=0x01; + else + ResParams->EoS=0x00; + + // Init Data + ResData->DataLen=SwapWord(Slice); + ResData->FB_00=0xFB00; + memcpy(Target, Source, Slice); + + // Send the slice + IsoSize=ResHeaderSize23+sizeof(TResDownloadParams)+sizeof(TResDownloadDataHeader)+Slice; + Result=isoSendBuffer(0,IsoSize); + } + else + Result=errCliDownloadSequenceFailed; + }; + //--------------------------------> Download sequence (PLC requests) + } + while (!Done && (Result==0)); + + if (Result==0) + { + //<-------------------------------------------Perform Download Ended + PReqDownloadParams ReqParams; + PS7ResHeader23 Answer; + PResEndDownloadParams ResParams; + int Size; + word Sequence; + + ReqParams=PReqDownloadParams(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + Answer =PS7ResHeader23(&PDU.Payload); + ResParams=PResEndDownloadParams(pbyte(Answer)+ResHeaderSize23); + + Result=isoRecvBuffer(0,Size); + if (Result==0) + { + if ((u_int(Size)>sizeof(TS7ReqHeader)) && (ReqParams->Fun==pduDownloadEnded)) + { + Sequence=PDUH_out->Sequence; + // Init Answer + Answer->P=0x32; + Answer->PDUType=PduType_response; + Answer->AB_EX=0x0000; + Answer->Sequence=Sequence; + Answer->ParLen =SwapWord(sizeof(TResEndDownloadParams)); + Answer->DataLen=0x0000; + Answer->Error =0x0000; + + // Init Params + *ResParams=pduDownloadEnded; + IsoSize=ResHeaderSize23+sizeof(TResEndDownloadParams); + Result=isoSendBuffer(0,IsoSize); + } + else + Result=errCliDownloadSequenceFailed; + }; + //------------------------------------------->Perform Download Ended + if (Result==0) + { + //<----------------------------------- Insert block into the unit + PReqControlBlockParams ReqParams; + PS7ResHeader23 Answer; + pbyte ResParams; + + // Setup pointers (note : PDUH_out and PDU.Payload are the same pointer) + ReqParams=PReqControlBlockParams(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + Answer=PS7ResHeader23(&PDU.Payload); + ResParams=pbyte(Answer)+ResHeaderSize23; + // Init Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_request; // 0x01 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen=SwapWord(sizeof(TReqControlBlockParams)); + PDUH_out->DataLen=0x0000; // No data + // Init Params + ReqParams->Fun = pduControl; + ReqParams->Uk7[0]=0x00; + ReqParams->Uk7[1]=0x00; + ReqParams->Uk7[2]=0x00; + ReqParams->Uk7[3]=0x00; + ReqParams->Uk7[4]=0x00; + ReqParams->Uk7[5]=0x00; + ReqParams->Uk7[6]=0xFD; + ReqParams->Len_1 =SwapWord(0x0A); + ReqParams->NumOfBlocks=0x01; + ReqParams->ByteZero =0x00; + ReqParams->AsciiZero =0x30; + ReqParams->BlkType=BlockType; + ReqParams->AsciiBlk[0]=(BlockNum / 10000)+0x30; + BlockNum=BlockNum % 10000; + ReqParams->AsciiBlk[1]=(BlockNum / 1000)+0x30; + BlockNum=BlockNum % 1000; + ReqParams->AsciiBlk[2]=(BlockNum / 100)+0x30; + BlockNum=BlockNum % 100; + ReqParams->AsciiBlk[3]=(BlockNum / 10)+0x30; + BlockNum=BlockNum % 10; + ReqParams->AsciiBlk[4]=(BlockNum / 1)+0x30; + ReqParams->SFun =SFun_Insert; + ReqParams->Len_2=0x05; + ReqParams->Cmd[0]='_'; + ReqParams->Cmd[1]='I'; + ReqParams->Cmd[2]='N'; + ReqParams->Cmd[3]='S'; + ReqParams->Cmd[4]='E'; + + IsoSize=sizeof(TS7ReqHeader)+sizeof(TReqControlBlockParams); + Result=isoExchangeBuffer(0,IsoSize); + + if (Result==0) + { + if ((Answer->Error!=0) || (*ResParams!=pduControl)) + Result=errCliInsertRefused; + }; + //-----------------------------------> Insert block into the unit + } + }; + }; + }; + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opDelete() +{ + PReqControlBlockParams ReqParams; + PS7ResHeader23 Answer; + pbyte ResParams; + int IsoSize, BlockType, BlockNum, Result; + + BlockType=Job.Area; + BlockNum =Job.Number; + // Setup pointers (note : PDUH_out and PDU.Payload are the same pointer) + ReqParams=PReqControlBlockParams(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + Answer =PS7ResHeader23(&PDU.Payload); + ResParams=pbyte(Answer)+ResHeaderSize23; + // Init Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_request; // 0x01 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen=SwapWord(sizeof(TReqControlBlockParams)); + PDUH_out->DataLen=0x0000; // No data + // Init Params + ReqParams->Fun = pduControl; + ReqParams->Uk7[0]=0x00; + ReqParams->Uk7[1]=0x00; + ReqParams->Uk7[2]=0x00; + ReqParams->Uk7[3]=0x00; + ReqParams->Uk7[4]=0x00; + ReqParams->Uk7[5]=0x00; + ReqParams->Uk7[6]=0xFD; + ReqParams->Len_1 =SwapWord(0x0A); + ReqParams->NumOfBlocks=0x01; + ReqParams->ByteZero =0x00; + ReqParams->AsciiZero =0x30; + ReqParams->BlkType=BlockType; + ReqParams->AsciiBlk[0]=(BlockNum / 10000)+0x30; + BlockNum=BlockNum % 10000; + ReqParams->AsciiBlk[1]=(BlockNum / 1000)+0x30; + BlockNum=BlockNum % 1000; + ReqParams->AsciiBlk[2]=(BlockNum / 100)+0x30; + BlockNum=BlockNum % 100; + ReqParams->AsciiBlk[3]=(BlockNum / 10)+0x30; + BlockNum=BlockNum % 10; + ReqParams->AsciiBlk[4]=(BlockNum / 1)+0x30; + ReqParams->SFun =SFun_Delete; + ReqParams->Len_2=0x05; + ReqParams->Cmd[0]='_'; + ReqParams->Cmd[1]='D'; + ReqParams->Cmd[2]='E'; + ReqParams->Cmd[3]='L'; + ReqParams->Cmd[4]='E'; + + IsoSize=sizeof(TS7ReqHeader)+sizeof(TReqControlBlockParams); + Result=isoExchangeBuffer(0,IsoSize); + + if (Result==0) + { + if (SwapWord(Answer->Error)!=Code7NeedPassword) + { + if ((Answer->Error!=0) || (*ResParams!=pduControl)) + Result=errCliDeleteRefused; + } + else + Result=errCliNeedPassword; + } + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opReadSZL() +{ + PS7Answer17 Answer; + PReqFunReadSZLFirst ReqParamsFirst; + PReqFunReadSZLNext ReqParamsNext; + PS7ReqSZLData ReqDataFirst; + PS7ReqSZLData ReqDataNext; + PS7ResParams7 ResParams; + PS7ResSZLDataFirst ResDataFirst; + PS7ResSZLDataNext ResDataNext; + PSZL_HEADER Header; + PS7SZLList Target; + pbyte PDataFirst; + pbyte PDataNext; + word ID, Index; + int IsoSize, DataSize, DataSZL, Result; + bool First, Done; + bool NoRoom = false; + uintptr_t Offset =0; + byte Seq_in =0x00; + + ID=Job.ID; + Index=Job.Index; + opSize=0; + // Setup pointers (note : PDUH_out and PDU.Payload are the same pointer) + ReqParamsFirst=PReqFunReadSZLFirst(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + ReqParamsNext =PReqFunReadSZLNext(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + ReqDataFirst =PS7ReqSZLData(pbyte(ReqParamsFirst)+sizeof(TReqFunReadSZLFirst)); + ReqDataNext =PS7ReqSZLData(pbyte(ReqParamsNext)+sizeof(TReqFunReadSZLNext)); + + Answer =PS7Answer17(&PDU.Payload); + ResParams =PS7ResParams7(pbyte(Answer)+ResHeaderSize17); + ResDataFirst =PS7ResSZLDataFirst(pbyte(ResParams)+sizeof(TS7Params7)); + ResDataNext =PS7ResSZLDataNext(pbyte(ResParams)+sizeof(TS7Params7)); + PDataFirst =pbyte(ResDataFirst)+8; // skip header + PDataNext =pbyte(ResDataNext)+4; // skip header + Header =PSZL_HEADER(&opData); + First=true; + Done =false; + do + { + //<------------------------------------------------------- read slices + if (First) + { + //<-------------------------------------------------- prepare first + DataSize=sizeof(TS7ReqSZLData); + // Fill Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_userdata; // 0x07 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen=SwapWord(sizeof(TReqFunReadSZLFirst)); // 8 bytes params + PDUH_out->DataLen=SwapWord(DataSize); // 8/4 bytes data + // Fill Params + ReqParamsFirst->Head[0]=0x00; + ReqParamsFirst->Head[1]=0x01; + ReqParamsFirst->Head[2]=0x12; + ReqParamsFirst->Plen =0x04; + ReqParamsFirst->Uk =0x11; + ReqParamsFirst->Tg =grSZL; + ReqParamsFirst->SubFun =SFun_ReadSZL; //0x03 + ReqParamsFirst->Seq =Seq_in; + // Fill Data + ReqDataFirst->Ret =0xFF; + ReqDataFirst->TS =TS_ResOctet; + ReqDataFirst->DLen =SwapWord(0x0004); + ReqDataFirst->ID =SwapWord(ID); + ReqDataFirst->Index =SwapWord(Index); + IsoSize=sizeof(TS7ReqHeader)+sizeof(TReqFunReadSZLFirst)+DataSize; + //--------------------------------------------------> prepare first + } + else + { + //<-------------------------------------------------- prepare next + DataSize=sizeof(TS7ReqSZLData)-4; + // Fill Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_userdata; // 0x07 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen=SwapWord(sizeof(TReqFunReadSZLNext)); // 8 bytes params + PDUH_out->DataLen=SwapWord(DataSize);// 8/4 bytes data + // Fill Params + ReqParamsNext->Head[0]=0x00; + ReqParamsNext->Head[1]=0x01; + ReqParamsNext->Head[2]=0x12; + ReqParamsNext->Plen =0x08; + ReqParamsNext->Uk =0x12; + ReqParamsNext->Tg =grSZL; + ReqParamsNext->SubFun =SFun_ReadSZL; + ReqParamsNext->Seq =Seq_in; + ReqParamsNext->Rsvd =0x0000; + ReqParamsNext->ErrNo =0x0000; + // Fill Data + ReqDataNext->Ret =0x0A; + ReqDataNext->TS =0x00; + ReqDataNext->DLen =0x0000; + ReqDataNext->ID =0x0000; + ReqDataNext->Index =0x0000; + IsoSize=sizeof(TS7ReqHeader)+sizeof(TReqFunReadSZLNext)+DataSize; + //--------------------------------------------------> prepare next + } + Result=isoExchangeBuffer(0,IsoSize); + // Get Data + if (Result==0) + { + if (First) + { + //<------------------------------------------ get data first + if (ResParams->Err==0) + { + if (ResDataFirst->Ret==0xFF) // <-- 0xFF means Result OK + { + // Gets Amount of this slice + DataSZL=SwapWord(ResDataFirst->DLen)-4;// Skips extra params (ID, Index ...) + // Gets end of Sequence Flag + Done=(ResParams->resvd & 0xFF00) == 0; // Low order byte = 0x00 => the sequence is done + // Gets Unit's function sequence + Seq_in=ResParams->Seq; + Target=PS7SZLList(pbyte(&opData)+Offset); + memcpy(Target, PDataFirst, DataSZL); + Offset+=DataSZL; + } + else + Result=CpuError(ResDataFirst->Ret); + } + else + Result=CpuError(ResDataFirst->Ret); + //------------------------------------------> get data first + } + else + { + //<------------------------------------------ get data next + if (ResParams->Err==0) + { + if (ResDataNext->Ret==0xFF) // <-- 0xFF means Result OK + { + // Gets Amount of this slice + DataSZL=SwapWord(ResDataNext->DLen); + // Gets end of Sequence Flag + Done=(ResParams->resvd & 0xFF00) == 0; // Low order byte = 0x00 => the sequence is done + // Gets Unit's function sequence + Seq_in=ResParams->Seq; + Target=PS7SZLList(pbyte(&opData)+Offset); + memcpy(Target, PDataNext, DataSZL); + Offset+=DataSZL; + } + else + Result=CpuError(ResDataNext->Ret); + } + else + Result=CpuError(ResDataNext->Ret); + //------------------------------------------> get data next + } + First=false; + } + //-------------------------------------------------------> read slices + } + while ((!Done) && (Result==0)); + + // Check errors and adjust header + if (Result==0) + { + // Adjust big endian header + Header->LENTHDR=SwapWord(Header->LENTHDR); + Header->N_DR =SwapWord(Header->N_DR); + opSize=int(Offset); + + if (Job.IParam==1) // if 1 data has to be copied into user buffer + { + // Check buffer size + if (opSize>Job.Amount) + { + opSize=Job.Amount; + NoRoom=true; + } + memcpy(Job.pData, &opData, opSize); + *Job.pAmount=opSize; + }; + }; + if ((Result==0)&& NoRoom) + Result=errCliBufferTooSmall; + + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opReadSZLList() +{ + PS7SZLList usrSZLList, opDataList; + int ItemsCount, ItemsCount_in, c, Result; + bool NoRoom = false; + + Job.ID =0x0000; + Job.Index =0x0000; + Job.IParam =0; + ItemsCount_in=Job.Amount; // stores the room + Job.Amount =sizeof(opData); // read into the internal buffer + + Result =opReadSZL(); + if (Result==0) + { + opDataList=PS7SZLList(&opData); // Source + usrSZLList=PS7SZLList(Job.pData); // Target + + ItemsCount=(opSize-sizeof(SZL_HEADER)) / 2; + // Check input size + if (ItemsCount>ItemsCount_in) + { + ItemsCount=ItemsCount_in; // Trim itemscount + NoRoom=true; + } + for (c = 0; c < ItemsCount; c++) + usrSZLList->List[c]=SwapWord(opDataList->List[c]); + *Job.pAmount=ItemsCount; + } + else + *Job.pAmount=0; + + if ((Result==0) && NoRoom) + Result=errCliBufferTooSmall; + + return Result; +} +//--------------------------------------------------------------------------- +byte TSnap7MicroClient::BCDtoByte(byte B) +{ + return ((B >> 4) * 10) + (B & 0x0F); +} +//--------------------------------------------------------------------------- +byte TSnap7MicroClient::WordToBCD(word Value) +{ + return ((Value / 10) << 4) | (Value % 10); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opGetDateTime() +{ + PTimeStruct DateTime; + PReqFunDateTime ReqParams; + PReqDataGetDateTime ReqData; + PS7ResParams7 ResParams; + PResDataGetTime ResData; + PS7ResHeader17 Answer; + int IsoSize, Result; + word AYear; + + // Setup pointers (note : PDUH_out and PDU.Payload are the same pointer) + ReqParams=PReqFunDateTime(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + ReqData =PReqDataGetDateTime(pbyte(ReqParams)+sizeof(TReqFunDateTime)); + Answer =PS7ResHeader17(&PDU.Payload); + ResParams=PS7ResParams7(pbyte(Answer)+ResHeaderSize17); + ResData =PResDataGetTime(pbyte(ResParams)+sizeof(TS7Params7)); + DateTime =PTimeStruct(Job.pData); + // Fill Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_userdata; // 0x07 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen=SwapWord(sizeof(TReqFunDateTime)); // 8 bytes params + PDUH_out->DataLen=SwapWord(sizeof(TReqDataGetDateTime)); // 4 bytes data + // Fill params (mostly constants) + ReqParams->Head[0]=0x00; + ReqParams->Head[1]=0x01; + ReqParams->Head[2]=0x12; + ReqParams->Plen =0x04; + ReqParams->Uk =0x11; + ReqParams->Tg =grClock; + ReqParams->SubFun =SFun_ReadClock; + ReqParams->Seq =0x00; + // Fill Data + *ReqData =0x0000000A; + + IsoSize=sizeof(TS7ReqHeader)+sizeof(TReqFunDateTime)+sizeof(TReqDataGetDateTime); + Result=isoExchangeBuffer(0,IsoSize); + + // Get Data + if (Result==0) + { + if (ResParams->Err==0) + { + if (ResData->RetVal==0xFF) // <-- 0xFF means Result OK + { + // Decode Plc Date and Time + AYear=BCDtoByte(ResData->Time[0]); + if (AYear<90) + AYear=AYear+100; + DateTime->tm_year=AYear; + DateTime->tm_mon =BCDtoByte(ResData->Time[1])-1; + DateTime->tm_mday=BCDtoByte(ResData->Time[2]); + DateTime->tm_hour=BCDtoByte(ResData->Time[3]); + DateTime->tm_min =BCDtoByte(ResData->Time[4]); + DateTime->tm_sec =BCDtoByte(ResData->Time[5]); + DateTime->tm_wday=(ResData->Time[7] & 0x0F)-1; + } + else + Result=CpuError(ResData->RetVal); + } + else + Result=CpuError(ResData->RetVal); + } + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opSetDateTime() +{ + PTimeStruct DateTime; + PReqFunDateTime ReqParams; + PReqDataSetTime ReqData; + PS7ResParams7 ResParams; + PS7ResHeader17 Answer; + word AYear; + int IsoSize, Result; + + // Setup pointers (note : PDUH_out and PDU.Payload are the same pointer) + ReqParams=PReqFunDateTime(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + ReqData =PReqDataSetTime(pbyte(ReqParams)+sizeof(TReqFunDateTime)); + Answer =PS7ResHeader17(&PDU.Payload); + ResParams=PS7ResParams7(pbyte(Answer)+ResHeaderSize17); + DateTime =PTimeStruct(Job.pData); + // Fill Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_userdata; // 0x07 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen=SwapWord(sizeof(TReqFunDateTime)); // 8 bytes params + PDUH_out->DataLen=SwapWord(sizeof(TReqDataSetTime)); // 4 bytes data + // Fill params (mostly constants) + ReqParams->Head[0]=0x00; + ReqParams->Head[1]=0x01; + ReqParams->Head[2]=0x12; + ReqParams->Plen =0x04; + ReqParams->Uk =0x11; + ReqParams->Tg =grClock; + ReqParams->SubFun =SFun_SetClock; + ReqParams->Seq =0x00; + // EncodeSiemensDateTime; + if (DateTime->tm_year<100) + AYear=DateTime->tm_year; + else + AYear=DateTime->tm_year-100; + + ReqData->RetVal=0xFF; + ReqData->TSize =TS_ResOctet; + ReqData->Length=SwapWord(0x000A); + ReqData->Rsvd =0x00; + ReqData->HiYear=0x19; // *must* be 19 tough it's not the Hi part of the year... + + ReqData->Time[0]=WordToBCD(AYear); + ReqData->Time[1]=WordToBCD(DateTime->tm_mon+1); + ReqData->Time[2]=WordToBCD(DateTime->tm_mday); + ReqData->Time[3]=WordToBCD(DateTime->tm_hour); + ReqData->Time[4]=WordToBCD(DateTime->tm_min); + ReqData->Time[5]=WordToBCD(DateTime->tm_sec); + ReqData->Time[6]=0; + ReqData->Time[7]=DateTime->tm_wday+1; + + IsoSize=sizeof(TS7ReqHeader)+sizeof(TReqFunDateTime)+sizeof(TReqDataSetTime); + Result=isoExchangeBuffer(0,IsoSize); + + // Get Result + if (Result==0) + { + if (ResParams->Err!=0) + Result=CpuError(SwapWord(ResParams->Err)); + }; + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opGetOrderCode() +{ + PS7OrderCode OC; + int Result; + + Job.ID =0x0011; + Job.Index =0x0000; + Job.IParam =0; + Result =opReadSZL(); + if (Result==0) + { + OC=PS7OrderCode(Job.pData); + memset(OC,0,sizeof(TS7OrderCode)); + memcpy(OC->Code,&opData[6],20); + OC->V1=opData[opSize-3]; + OC->V2=opData[opSize-2]; + OC->V3=opData[opSize-1]; + }; + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opGetCpuInfo() +{ + PS7CpuInfo Info; + int Result; + + // Store Pointer + Info=PS7CpuInfo(Job.pData); + // Clear data in order to have the end of strings (\0) correctly setted + memset(Info, 0, sizeof(TS7CpuInfo)); + + Job.ID =0x001C; + Job.Index =0x0000; + Job.IParam=0; + Result =opReadSZL(); + if (Result==0) + { + memcpy(Info->ModuleTypeName,&opData[176],32); + memcpy(Info->SerialNumber,&opData[142],24); + memcpy(Info->ASName,&opData[6],24); + memcpy(Info->Copyright,&opData[108],26); + memcpy(Info->ModuleName,&opData[40],24); + } + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opGetCpInfo() +{ + PS7CpInfo Info; + int Result; + // Store Pointer + Info=PS7CpInfo(Job.pData); + memset(Info,0,sizeof(TS7CpInfo)); + Job.ID =0x0131; + Job.Index =0x0001; + Job.IParam=0; + Result =opReadSZL(); + if (Result==0) + { + Info->MaxPduLengt=opData[6]*256+opData[7]; + Info->MaxConnections=opData[8]*256+opData[9]; + Info->MaxMpiRate=DWordAt(&opData[10]); + Info->MaxBusRate=DWordAt(&opData[14]); + }; + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opGetPlcStatus() +{ + int *Status; + int Result; + + Status =(int*)Job.pData; + Job.ID =0x0424; + Job.Index =0x0000; + Job.IParam =0; + Result =opReadSZL(); + if (Result==0) + { + switch (opData[7]) + { + case S7CpuStatusUnknown : + case S7CpuStatusRun : + case S7CpuStatusStop : *Status=opData[7]; + break; + default : + // Since RUN status is always $08 for all CPUs and CPs, STOP status + // sometime can be coded as $03 (especially for old cpu...) + *Status=S7CpuStatusStop; + } + } + else + *Status=0; + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opPlcStop() +{ + PReqFunPlcStop ReqParams; + PResFunCtrl ResParams; + PS7ResHeader23 Answer; + int IsoSize, Result; + + char p_program[] = {'P','_','P','R','O','G','R','A','M'}; + + // Setup pointers (note : PDUH_out and PDU.Payload are the same pointer) + ReqParams=PReqFunPlcStop(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + Answer =PS7ResHeader23(&PDU.Payload); + ResParams=PResFunCtrl(pbyte(Answer)+ResHeaderSize23); + // Fill Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_request; // 0x01 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen=SwapWord(sizeof(TReqFunPlcStop)); + PDUH_out->DataLen=0x0000; // No Data + // Fill Params + ReqParams->Fun=pduStop; + memset(ReqParams->Uk_5,0,5); + ReqParams->Len_2=0x09; + memcpy(ReqParams->Cmd,&p_program,9); + + IsoSize=sizeof(TS7ReqHeader)+sizeof(TReqFunPlcStop); + Result=isoExchangeBuffer(0,IsoSize); + + if (Result==0) + { + if (Answer->Error!=0) + { + if (ResParams->ResFun!=pduStop) + Result=errCliCannotStopPLC; + else + if (ResParams->para ==0x07) + Result=errCliAlreadyStop; + else + Result=errCliCannotStopPLC; + }; + }; + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opPlcHotStart() +{ + PReqFunPlcHotStart ReqParams; + PResFunCtrl ResParams; + PS7ResHeader23 Answer; + int IsoSize, Result; + + char p_program[] = {'P','_','P','R','O','G','R','A','M'}; + + // Setup pointers (note : PDUH_out and PDU.Payload are the same pointer) + ReqParams=PReqFunPlcHotStart(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + Answer =PS7ResHeader23(&PDU.Payload); + ResParams=PResFunCtrl(pbyte(Answer)+ResHeaderSize23); + // Fill Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_request; // 0x01 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen=SwapWord(sizeof(TReqFunPlcHotStart)); // 16 bytes params + PDUH_out->DataLen=0x0000; // No Data + // Fill Params + ReqParams->Fun=pduStart; + ReqParams->Uk_7[0]=0x00; + ReqParams->Uk_7[1]=0x00; + ReqParams->Uk_7[2]=0x00; + ReqParams->Uk_7[3]=0x00; + ReqParams->Uk_7[4]=0x00; + ReqParams->Uk_7[5]=0x00; + ReqParams->Uk_7[6]=0xFD; + + ReqParams->Len_1=0x0000; + ReqParams->Len_2=0x09; + memcpy(ReqParams->Cmd,&p_program,9); + + IsoSize=sizeof(TS7ReqHeader)+sizeof(TReqFunPlcHotStart); + Result=isoExchangeBuffer(0,IsoSize); + + if (Result==0) + { + if ((Answer->Error!=0)) + { + if ((ResParams->ResFun!=pduStart)) + Result=errCliCannotStartPLC; + else + { + if (ResParams->para==0x03) + Result=errCliAlreadyRun; + else + if (ResParams->para==0x02) + Result=errCliCannotStartPLC; + else + Result=errCliCannotStartPLC; + } + } + }; + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opPlcColdStart() +{ + PReqFunPlcColdStart ReqParams; + PResFunCtrl ResParams; + PS7ResHeader23 Answer; + int IsoSize, Result; + char p_program[] = {'P','_','P','R','O','G','R','A','M'}; + + // Setup pointers (note : PDUH_out and PDU.Payload are the same pointer) + ReqParams=PReqFunPlcColdStart(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + Answer =PS7ResHeader23(&PDU.Payload); + ResParams=PResFunCtrl(pbyte(Answer)+ResHeaderSize23); + // Fill Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_request; // 0x01 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen=SwapWord(sizeof(TReqFunPlcColdStart)); // 22 bytes params + PDUH_out->DataLen=0x0000; // No Data + // Fill Params + ReqParams->Fun=pduStart; + ReqParams->Uk_7[0]=0x00; + ReqParams->Uk_7[1]=0x00; + ReqParams->Uk_7[2]=0x00; + ReqParams->Uk_7[3]=0x00; + ReqParams->Uk_7[4]=0x00; + ReqParams->Uk_7[5]=0x00; + ReqParams->Uk_7[6]=0xFD; + + ReqParams->Len_1=SwapWord(0x0002); + ReqParams->SFun =SwapWord(0x4320); // Cold start + ReqParams->Len_2=0x09; + memcpy(ReqParams->Cmd,&p_program,9); + + IsoSize=sizeof(TS7ReqHeader)+sizeof(TReqFunPlcColdStart); + Result=isoExchangeBuffer(0,IsoSize); + + if (Result==0) + { + if ((Answer->Error!=0)) + { + if ((ResParams->ResFun!=pduStart)) + Result=errCliCannotStartPLC; + else + { + if (ResParams->para==0x03) + Result=errCliAlreadyRun; + else + if (ResParams->para==0x02) + Result=errCliCannotStartPLC; + else + Result=errCliCannotStartPLC; + } + } + }; + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opCopyRamToRom() +{ + PReqFunCopyRamToRom ReqParams; + PResFunCtrl ResParams; + PS7ResHeader23 Answer; + int IsoSize, CurTimeout, Result; + char _modu[] = {'_','M','O','D','U'}; + + // Setup pointers (note : PDUH_out and PDU.Payload are the same pointer) + ReqParams=PReqFunCopyRamToRom(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + Answer =PS7ResHeader23(&PDU.Payload); + ResParams=PResFunCtrl(pbyte(Answer)+ResHeaderSize23); + // Fill Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_request; // 0x01 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen=SwapWord(sizeof(TReqFunCopyRamToRom)); + PDUH_out->DataLen=0x0000; // No Data + // Fill Params + ReqParams->Fun=pduControl; + ReqParams->Uk_7[0]=0x00; + ReqParams->Uk_7[1]=0x00; + ReqParams->Uk_7[2]=0x00; + ReqParams->Uk_7[3]=0x00; + ReqParams->Uk_7[4]=0x00; + ReqParams->Uk_7[5]=0x00; + ReqParams->Uk_7[6]=0xFD; + + ReqParams->Len_1=SwapWord(0x0002); + ReqParams->SFun =SwapWord(0x4550); + ReqParams->Len_2=0x05; + memcpy(ReqParams->Cmd,&_modu,5); + + IsoSize=sizeof(TS7ReqHeader)+sizeof(TReqFunCopyRamToRom); + // Changes the timeout + CurTimeout=RecvTimeout; + RecvTimeout=Job.IParam; + Result=isoExchangeBuffer(0,IsoSize); + // Restores the timeout + RecvTimeout=CurTimeout; + + if (Result==0) + { + if ((Answer->Error!=0) || (ResParams->ResFun!=pduControl)) + Result=errCliCannotCopyRamToRom; + } + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opCompress() +{ + PReqFunCompress ReqParams; + PResFunCtrl ResParams; + PS7ResHeader23 Answer; + int IsoSize, CurTimeout, Result; + char _garb[] = {'_','G','A','R','B'}; + + // Setup pointers (note : PDUH_out and PDU.Payload are the same pointer) + ReqParams=PReqFunCompress(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + Answer =PS7ResHeader23(&PDU.Payload); + ResParams=PResFunCtrl(pbyte(Answer)+ResHeaderSize23); + // Fill Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_request; // 0x01 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen=SwapWord(sizeof(TReqFunCompress)); + PDUH_out->DataLen=0x0000; // No Data + // Fill Params + ReqParams->Fun=pduControl; + ReqParams->Uk_7[0]=0x00; + ReqParams->Uk_7[1]=0x00; + ReqParams->Uk_7[2]=0x00; + ReqParams->Uk_7[3]=0x00; + ReqParams->Uk_7[4]=0x00; + ReqParams->Uk_7[5]=0x00; + ReqParams->Uk_7[6]=0xFD; + + ReqParams->Len_1=0x0000; + ReqParams->Len_2=0x05; + memcpy(ReqParams->Cmd,&_garb,5); + + IsoSize=sizeof(TS7ReqHeader)+sizeof(TReqFunCompress); + // Changes the timeout + CurTimeout=RecvTimeout; + RecvTimeout=Job.IParam; + Result=isoExchangeBuffer(0,IsoSize); + // Restores the timeout + RecvTimeout=CurTimeout; + + if (Result==0) + { + if (((Answer->Error!=0) || (ResParams->ResFun!=pduControl))) + Result=errCliCannotCompress; + } + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opGetProtection() +{ + PS7Protection Info, usrInfo; + int Result; + + // Store Pointer + usrInfo=PS7Protection(Job.pData); + memset(usrInfo, 0, sizeof(TS7Protection)); + + Job.ID =0x0232; + Job.Index =0x0004; + Job.IParam=0; // No copy in Usr Data pointed by Job.pData + Result =opReadSZL(); + if (Result==0) + { + Info=PS7Protection(pbyte(&opData)+6); + usrInfo->sch_schal=SwapWord(Info->sch_schal); + usrInfo->sch_par =SwapWord(Info->sch_par); + usrInfo->sch_rel =SwapWord(Info->sch_rel); + usrInfo->bart_sch =SwapWord(Info->bart_sch); + usrInfo->anl_sch =SwapWord(Info->anl_sch); + } + return Result; +} +//****************************************************************************** +// NOTE +// PASSWORD HACKING IS VERY FAR FROM THE AIM OF THIS PROJECT +// NEXT FUNCTION ONLY ENCODES THE ASCII PASSWORD TO BE DOWNLOADED IN THE PLC. +// +// MOREOVER **YOU NEED TO KNOW** THE CORRECT PASSWORD TO MEET THE CPU +// SECURITY LEVEL +//****************************************************************************** +int TSnap7MicroClient::opSetPassword() +{ + PReqFunSecurity ReqParams; + PReqDataSecurity ReqData; + PResParamsSecurity ResParams; + PS7ResHeader23 Answer; + int c, IsoSize, Result; + + // Setup pointers (note : PDUH_out and PDU.Payload are the same pointer) + ReqParams=PReqFunSecurity(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + ReqData =PReqDataSecurity(pbyte(ReqParams)+sizeof(TReqFunSecurity)); + Answer =PS7ResHeader23(&PDU.Payload); + ResParams=PResParamsSecurity(pbyte(Answer)+ResHeaderSize17); + // Fill Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_userdata; // 0x07 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen =SwapWord(sizeof(TReqFunSecurity)); + PDUH_out->DataLen=SwapWord(sizeof(TReqDataSecurity)); + // Fill params (mostly constants) + ReqParams->Head[0]=0x00; + ReqParams->Head[1]=0x01; + ReqParams->Head[2]=0x12; + ReqParams->Plen =0x04; + ReqParams->Uk =0x11; + ReqParams->Tg =grSecurity; + ReqParams->SubFun =SFun_EnterPwd; + ReqParams->Seq =0x00; + // Fill Data + ReqData->Ret =0xFF; + ReqData->TS =TS_ResOctet; + ReqData->DLen =SwapWord(0x0008); // 8 bytes data : password + // Encode the password + ReqData->Pwd[0]=opData[0] ^ 0x55; + ReqData->Pwd[1]=opData[1] ^ 0x55; + for (c = 2; c < 8; c++){ + ReqData->Pwd[c]=opData[c] ^ 0x55 ^ ReqData->Pwd[c-2]; + }; + + IsoSize=sizeof(TS7ReqHeader)+sizeof(TReqFunSecurity)+sizeof(TReqDataSecurity); + Result=isoExchangeBuffer(0,IsoSize); + + // Get Return + if (Result==0) + { + if (ResParams->Err!=0) + Result=CpuError(SwapWord(ResParams->Err)); + }; + + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::opClearPassword() +{ + PReqFunSecurity ReqParams; + PReqDataSecurity ReqData; + PResParamsSecurity ResParams; + PS7ResHeader23 Answer; + int IsoSize, Result; + + // Setup pointers (note : PDUH_out and PDU.Payload are the same pointer) + ReqParams=PReqFunSecurity(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + ReqData =PReqDataSecurity(pbyte(ReqParams)+sizeof(TReqFunSecurity)); + Answer =PS7ResHeader23(&PDU.Payload); + ResParams=PResParamsSecurity(pbyte(Answer)+ResHeaderSize17); + // Fill Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_userdata; // 0x07 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // AutoInc + PDUH_out->ParLen =SwapWord(sizeof(TReqFunSecurity)); + PDUH_out->DataLen=SwapWord(0x0004); // We need only 4 bytes + // Fill params (mostly constants) + ReqParams->Head[0]=0x00; + ReqParams->Head[1]=0x01; + ReqParams->Head[2]=0x12; + ReqParams->Plen =0x04; + ReqParams->Uk =0x11; + ReqParams->Tg =grSecurity; + ReqParams->SubFun =SFun_CancelPwd; + ReqParams->Seq =0x00; + // Fill Data + ReqData->Ret =0x0A; + ReqData->TS =0x00; + ReqData->DLen =0x0000; + + IsoSize=sizeof(TS7ReqHeader)+sizeof(TReqFunSecurity)+4; + Result=isoExchangeBuffer(0,IsoSize); + + // Get Return + if (Result==0) + { + if (ResParams->Err!=0) + Result=CpuError(SwapWord(ResParams->Err)); + }; + return Result; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::CpuError(int Error) +{ + switch(Error) + { + case 0 : return 0; + case Code7AddressOutOfRange : return errCliAddressOutOfRange; + case Code7InvalidTransportSize : return errCliInvalidTransportSize; + case Code7WriteDataSizeMismatch : return errCliWriteDataSizeMismatch; + case Code7ResItemNotAvailable : + case Code7ResItemNotAvailable1 : return errCliItemNotAvailable; + case Code7DataOverPDU : return errCliSizeOverPDU; + case Code7InvalidValue : return errCliInvalidValue; + case Code7FunNotAvailable : return errCliFunNotAvailable; + case Code7NeedPassword : return errCliNeedPassword; + case Code7InvalidPassword : return errCliInvalidPassword; + case Code7NoPasswordToSet : + case Code7NoPasswordToClear : return errCliNoPasswordToSetOrClear; + default: + return errCliFunctionRefused; + }; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::DataSizeByte(int WordLength) +{ + switch (WordLength){ + case S7WLBit : return 1; // S7 sends 1 byte per bit + case S7WLByte : return 1; + case S7WLChar : return 1; + case S7WLWord : return 2; + case S7WLDWord : return 4; + case S7WLInt : return 2; + case S7WLDInt : return 4; + case S7WLReal : return 4; + case S7WLCounter : return 2; + case S7WLTimer : return 2; + default : return 0; + } +} +//--------------------------------------------------------------------------- +longword TSnap7MicroClient::DWordAt(void * P) +{ + longword DW; + DW=*(longword*)P; + return SwapDWord(DW); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::CheckBlock(int BlockType, int BlockNum, void * pBlock, int Size) +{ + PS7CompactBlockInfo Info = PS7CompactBlockInfo(pBlock); + + if (BlockType>=0) // if (BlockType<0 the test is skipped + { + if ((BlockType!=Block_OB)&&(BlockType!=Block_DB)&&(BlockType!=Block_FB)&& + (BlockType!=Block_FC)&&(BlockType!=Block_SDB)&&(BlockType!=Block_SFC)&& + (BlockType!=Block_SFB)) + return errCliInvalidBlockType; + } + + if (BlockNum>=0) // if (BlockNum<0 the test is skipped + { + if (BlockNum>0xFFFF) + return errCliInvalidBlockNumber; + }; + + if (SwapDWord(Info->LenLoadMem)!=longword(Size)) + return errCliInvalidBlockSize; + + // Check the presence of the footer + if (SwapWord(Info->MC7Len)+sizeof(TS7CompactBlockInfo)>=u_int(Size)) + return errCliInvalidBlockSize; + + return 0; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::SubBlockToBlock(int SBB) +{ + switch (SBB) + { + case SubBlk_OB : return Block_OB; + case SubBlk_DB : return Block_DB; + case SubBlk_SDB : return Block_SDB; + case SubBlk_FC : return Block_FC; + case SubBlk_SFC : return Block_SFC; + case SubBlk_FB : return Block_FB; + case SubBlk_SFB : return Block_SFB; + default : return 0; + }; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::PerformOperation() +{ + ClrError(); + int Operation=Job.Op; + switch(Operation) + { + case s7opNone: + Job.Result=errCliInvalidParams; + break; + case s7opReadArea: + Job.Result=opReadArea(); + break; + case s7opWriteArea: + Job.Result=opWriteArea(); + break; + case s7opReadMultiVars: + Job.Result=opReadMultiVars(); + break; + case s7opWriteMultiVars: + Job.Result=opWriteMultiVars(); + break; + case s7opDBGet: + Job.Result=opDBGet(); + break; + case s7opDBFill: + Job.Result=opDBFill(); + break; + case s7opUpload: + Job.Result=opUpload(); + break; + case s7opDownload: + Job.Result=opDownload(); + break; + case s7opDelete: + Job.Result=opDelete(); + break; + case s7opListBlocks: + Job.Result=opListBlocks(); + break; + case s7opAgBlockInfo: + Job.Result=opAgBlockInfo(); + break; + case s7opListBlocksOfType: + Job.Result=opListBlocksOfType(); + break; + case s7opReadSzlList: + Job.Result=opReadSZLList(); + break; + case s7opReadSZL: + Job.Result=opReadSZL(); + break; + case s7opGetDateTime: + Job.Result=opGetDateTime(); + break; + case s7opSetDateTime: + Job.Result=opSetDateTime(); + break; + case s7opGetOrderCode: + Job.Result=opGetOrderCode(); + break; + case s7opGetCpuInfo: + Job.Result=opGetCpuInfo(); + break; + case s7opGetCpInfo: + Job.Result=opGetCpInfo(); + break; + case s7opGetPlcStatus: + Job.Result=opGetPlcStatus(); + break; + case s7opPlcHotStart: + Job.Result=opPlcHotStart(); + break; + case s7opPlcColdStart: + Job.Result=opPlcColdStart(); + break; + case s7opCopyRamToRom: + Job.Result=opCopyRamToRom(); + break; + case s7opCompress: + Job.Result=opCompress(); + break; + case s7opPlcStop: + Job.Result=opPlcStop(); + break; + case s7opGetProtection: + Job.Result=opGetProtection(); + break; + case s7opSetPassword: + Job.Result=opSetPassword(); + break; + case s7opClearPassword: + Job.Result=opClearPassword(); + break; + } + Job.Time =SysGetTick()-JobStart; + Job.Pending=false; + return SetError(Job.Result); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::Disconnect() +{ + JobStart=SysGetTick(); + PeerDisconnect(); + Job.Time=SysGetTick()-JobStart; + Job.Pending=false; + return 0; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::Reset(bool DoReconnect) +{ + Job.Pending=false; + if (DoReconnect) { + Disconnect(); + return Connect(); + } + else + return 0; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::Connect() +{ + int Result; + JobStart=SysGetTick(); + Result =PeerConnect(); + Job.Time=SysGetTick()-JobStart; + return Result; +} +//--------------------------------------------------------------------------- +void TSnap7MicroClient::SetConnectionType(word ConnType) +{ + ConnectionType=ConnType; +} +//--------------------------------------------------------------------------- +void TSnap7MicroClient::SetConnectionParams(const char *RemAddress, word LocalTSAP, word RemoteTSAP) +{ + SrcTSap = LocalTSAP; + DstTSap = RemoteTSAP; + strncpy(RemoteAddress, RemAddress, 16); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::ConnectTo(const char *RemAddress, int Rack, int Slot) +{ + word RemoteTSAP = (ConnectionType<<8)+(Rack*0x20)+Slot; + SetConnectionParams(RemAddress, SrcTSap, RemoteTSAP); + return Connect(); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::GetParam(int ParamNumber, void *pValue) +{ + switch (ParamNumber) + { + case p_u16_RemotePort: + *Puint16_t(pValue)=RemotePort; + break; + case p_i32_PingTimeout: + *Pint32_t(pValue)=PingTimeout; + break; + case p_i32_SendTimeout: + *Pint32_t(pValue)=SendTimeout; + break; + case p_i32_RecvTimeout: + *Pint32_t(pValue)=RecvTimeout; + break; + case p_i32_WorkInterval: + *Pint32_t(pValue)=WorkInterval; + break; + case p_u16_SrcRef: + *Puint16_t(pValue)=SrcRef; + break; + case p_u16_DstRef: + *Puint16_t(pValue)=DstRef; + break; + case p_u16_SrcTSap: + *Puint16_t(pValue)=SrcTSap; + break; + case p_i32_PDURequest: + *Pint32_t(pValue)=PDURequest; + break; + default: return errCliInvalidParamNumber; + } + return 0; +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::SetParam(int ParamNumber, void *pValue) +{ + switch (ParamNumber) + { + case p_u16_RemotePort: + if (!Connected) + RemotePort=*Puint16_t(pValue); + else + return errCliCannotChangeParam; + break; + case p_i32_PingTimeout: + PingTimeout=*Pint32_t(pValue); + break; + case p_i32_SendTimeout: + SendTimeout=*Pint32_t(pValue); + break; + case p_i32_RecvTimeout: + RecvTimeout=*Pint32_t(pValue); + break; + case p_i32_WorkInterval: + WorkInterval=*Pint32_t(pValue); + break; + case p_u16_SrcRef: + SrcRef=*Puint16_t(pValue); + break; + case p_u16_DstRef: + DstRef=*Puint16_t(pValue); + break; + case p_u16_SrcTSap: + SrcTSap=*Puint16_t(pValue); + break; + case p_i32_PDURequest: + PDURequest=*Pint32_t(pValue); + break; + default: return errCliInvalidParamNumber; + } + return 0; +} +//--------------------------------------------------------------------------- +// Data I/O functions +int TSnap7MicroClient::ReadArea(int Area, int DBNumber, int Start, int Amount, int WordLen, void * pUsrData) +{ + if (!Job.Pending) + { + Job.Pending = true; + Job.Op = s7opReadArea; + Job.Area = Area; + Job.Number = DBNumber; + Job.Start = Start; + Job.Amount = Amount; + Job.WordLen = WordLen; + Job.pData = pUsrData; + JobStart = SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::WriteArea(int Area, int DBNumber, int Start, int Amount, int WordLen, void * pUsrData) +{ + if (!Job.Pending) + { + Job.Pending = true; + Job.Op = s7opWriteArea; + Job.Area = Area; + Job.Number = DBNumber; + Job.Start = Start; + Job.Amount = Amount; + Job.WordLen = WordLen; + Job.pData = pUsrData; + JobStart = SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::ReadMultiVars(PS7DataItem Item, int ItemsCount) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opReadMultiVars; + Job.Amount =ItemsCount; + Job.pData =Item; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::WriteMultiVars(PS7DataItem Item, int ItemsCount) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opWriteMultiVars; + Job.Amount =ItemsCount; + Job.pData =Item; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::DBRead(int DBNumber, int Start, int Size, void * pUsrData) +{ + return ReadArea(S7AreaDB, DBNumber, Start, Size, S7WLByte, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::DBWrite(int DBNumber, int Start, int Size, void * pUsrData) +{ + return WriteArea(S7AreaDB, DBNumber, Start, Size, S7WLByte, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::MBRead(int Start, int Size, void * pUsrData) +{ + return ReadArea(S7AreaMK, 0, Start, Size, S7WLByte, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::MBWrite(int Start, int Size, void * pUsrData) +{ + return WriteArea(S7AreaMK, 0, Start, Size, S7WLByte, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::EBRead(int Start, int Size, void * pUsrData) +{ + return ReadArea(S7AreaPE, 0, Start, Size, S7WLByte, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::EBWrite(int Start, int Size, void * pUsrData) +{ + return WriteArea(S7AreaPE, 0, Start, Size, S7WLByte, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::ABRead(int Start, int Size, void * pUsrData) +{ + return ReadArea(S7AreaPA, 0, Start, Size, S7WLByte, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::ABWrite(int Start, int Size, void * pUsrData) +{ + return WriteArea(S7AreaPA, 0, Start, Size, S7WLByte, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::TMRead(int Start, int Amount, void * pUsrData) +{ + return ReadArea(S7AreaTM, 0, Start, Amount, S7WLTimer, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::TMWrite(int Start, int Amount, void * pUsrData) +{ + return WriteArea(S7AreaTM, 0, Start, Amount, S7WLTimer, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::CTRead(int Start, int Amount, void * pUsrData) +{ + return ReadArea(S7AreaCT, 0, Start, Amount, S7WLCounter, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::CTWrite(int Start, int Amount, void * pUsrData) +{ + return WriteArea(S7AreaCT, 0, Start, Amount, S7WLCounter, pUsrData); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::ListBlocks(PS7BlocksList pUsrData) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opListBlocks; + Job.pData =pUsrData; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::GetAgBlockInfo(int BlockType, int BlockNum, PS7BlockInfo pUsrData) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opAgBlockInfo; + Job.Area =BlockType; + Job.Number =BlockNum; + Job.pData =pUsrData; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::GetPgBlockInfo(void * pBlock, PS7BlockInfo pUsrData, int Size) +{ + PS7CompactBlockInfo Info; + PS7BlockFooter Footer; + + int Result=CheckBlock(-1,-1,pBlock,Size); + if (Result==0) + { + Info=PS7CompactBlockInfo(pBlock); + pUsrData->BlkType =Info->SubBlkType; + pUsrData->BlkNumber=SwapWord(Info->BlkNum); + pUsrData->BlkLang =Info->BlkLang; + pUsrData->BlkFlags =Info->BlkFlags; + pUsrData->MC7Size =SwapWord(Info->MC7Len); + pUsrData->LoadSize =SwapDWord(Info->LenLoadMem); + pUsrData->LocalData=SwapDWord(Info->LocDataLen); + pUsrData->SBBLength=SwapDWord(Info->SbbLen); + pUsrData->CheckSum =0; // this info is not available + pUsrData->Version =0; // this info is not available + FillTime(SwapWord(Info->CodeTime_dy),pUsrData->CodeDate); + FillTime(SwapWord(Info->IntfTime_dy),pUsrData->IntfDate); + + Footer=PS7BlockFooter(pbyte(Info)+pUsrData->LoadSize-sizeof(TS7BlockFooter)); + + memcpy(pUsrData->Author,Footer->Author,8); + memcpy(pUsrData->Family,Footer->Family,8); + memcpy(pUsrData->Header,Footer->Header,8); + }; + return SetError(Result); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::ListBlocksOfType(int BlockType, TS7BlocksOfType *pUsrData, int &ItemsCount) +{ + if (!Job.Pending) + { + if (ItemsCount<1) + return SetError(errCliInvalidBlockSize); + Job.Pending =true; + Job.Op =s7opListBlocksOfType; + Job.Area =BlockType; + Job.pData =pUsrData; + Job.pAmount =&ItemsCount; + Job.Amount =ItemsCount; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::Upload(int BlockType, int BlockNum, void * pUsrData, int & Size) +{ + if (!Job.Pending) + { + if (Size<=0) + return SetError(errCliInvalidBlockSize); + Job.Pending =true; + Job.Op =s7opUpload; + Job.Area =BlockType; + Job.pData =pUsrData; + Job.pAmount =&Size; + Job.Amount =Size; + Job.Number =BlockNum; + Job.IParam =0; // not full upload, only data + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::FullUpload(int BlockType, int BlockNum, void * pUsrData, int & Size) +{ + if (!Job.Pending) + { + if (Size<=0) + return SetError(errCliInvalidBlockSize); + Job.Pending =true; + Job.Op =s7opUpload; + Job.Area =BlockType; + Job.pData =pUsrData; + Job.pAmount =&Size; + Job.Amount =Size; + Job.Number =BlockNum; + Job.IParam =1; // header + data + footer + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::Download(int BlockNum, void * pUsrData, int Size) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opDownload; + memcpy(&opData, pUsrData, Size); + Job.Number =BlockNum; + Job.Amount =Size; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::Delete(int BlockType, int BlockNum) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opDelete; + Job.Area =BlockType; + Job.Number =BlockNum; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::DBGet(int DBNumber, void * pUsrData, int & Size) +{ + if (!Job.Pending) + { + if (Size<=0) + return SetError(errCliInvalidBlockSize); + Job.Pending =true; + Job.Op =s7opDBGet; + Job.Number =DBNumber; + Job.pData =pUsrData; + Job.pAmount =&Size; + Job.Amount =Size; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::DBFill(int DBNumber, int FillChar) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opDBFill; + Job.Number =DBNumber; + Job.IParam =FillChar; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::GetPlcDateTime(tm &DateTime) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opGetDateTime; + Job.pData =&DateTime; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::SetPlcDateTime(tm * DateTime) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opSetDateTime; + Job.pData =DateTime; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::SetPlcSystemDateTime() +{ + time_t Now; + time(&Now); + struct tm * DateTime = localtime (&Now); + return SetPlcDateTime(DateTime); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::GetOrderCode(PS7OrderCode pUsrData) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opGetOrderCode; + Job.pData =pUsrData; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::GetCpuInfo(PS7CpuInfo pUsrData) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opGetCpuInfo; + Job.pData =pUsrData; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::GetCpInfo(PS7CpInfo pUsrData) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opGetCpInfo; + Job.pData =pUsrData; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::ReadSZL(int ID, int Index, PS7SZL pUsrData, int &Size) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opReadSZL; + Job.ID =ID; + Job.Index =Index; + Job.pData =pUsrData; + Job.pAmount =&Size; + Job.Amount =Size; + Job.IParam =1; // Data has to be copied into user buffer + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::ReadSZLList(PS7SZLList pUsrData, int &ItemsCount) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opReadSzlList; + Job.pData =pUsrData; + Job.pAmount =&ItemsCount; + Job.Amount =ItemsCount; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::PlcHotStart() +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opPlcHotStart; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::PlcColdStart() +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opPlcColdStart; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::PlcStop() +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opPlcStop; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::CopyRamToRom(int Timeout) +{ + if (!Job.Pending) + { + if (Timeout>0) + { + Job.Pending =true; + Job.Op =s7opCopyRamToRom; + Job.IParam =Timeout; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliInvalidParams); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::Compress(int Timeout) +{ + if (!Job.Pending) + { + if (Timeout>0) + { + Job.Pending =true; + Job.Op =s7opCompress; + Job.IParam =Timeout; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliInvalidParams); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::GetPlcStatus(int & Status) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opGetPlcStatus; + Job.pData =&Status; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::GetProtection(PS7Protection pUsrData) +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opGetProtection; + Job.pData =pUsrData; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::SetSessionPassword(char *Password) +{ + if (!Job.Pending) + { + size_t L = strlen(Password); + // checks the len + if ((L<1) || (L>8)) + return SetError(errCliInvalidParams); + Job.Pending =true; + // prepares an 8 char string filled with spaces + memset(&opData,0x20,8); + // copies + strncpy((char*)&opData,Password,L); + Job.Op =s7opSetPassword; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- +int TSnap7MicroClient::ClearSessionPassword() +{ + if (!Job.Pending) + { + Job.Pending =true; + Job.Op =s7opClearPassword; + JobStart =SysGetTick(); + return PerformOperation(); + } + else + return SetError(errCliJobPending); +} +//--------------------------------------------------------------------------- + diff --git a/core/src/drivers/plugins/native/s7comm/snap7/core/s7_micro_client.h b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_micro_client.h new file mode 100644 index 00000000..ddbc83ed --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_micro_client.h @@ -0,0 +1,364 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 Davide Nardella | +| All rights reserved. | +|==============================================================================| +| SNAP7 is free software: you can redistribute it and/or modify | +| it under the terms of the Lesser GNU General Public License as published by | +| the Free Software Foundation, either version 3 of the License, or | +| (at your option) any later version. | +| | +| It means that you can distribute your commercial software linked with | +| SNAP7 without the requirement to distribute the source code of your | +| application and without the requirement that your application be itself | +| distributed under LGPL. | +| | +| SNAP7 is distributed in the hope that it will be useful, | +| but WITHOUT ANY WARRANTY; without even the implied warranty of | +| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | +| Lesser GNU General Public License for more details. | +| | +| You should have received a copy of the GNU General Public License and a | +| copy of Lesser GNU General Public License along with Snap7. | +| If not, see http://www.gnu.org/licenses/ | +|=============================================================================*/ +#ifndef s7_micro_client_h +#define s7_micro_client_h +//--------------------------------------------------------------------------- +#include "s7_peer.h" +//--------------------------------------------------------------------------- + +const longword errCliMask = 0xFFF00000; +const longword errCliBase = 0x000FFFFF; + +const longword errCliInvalidParams = 0x00200000; +const longword errCliJobPending = 0x00300000; +const longword errCliTooManyItems = 0x00400000; +const longword errCliInvalidWordLen = 0x00500000; +const longword errCliPartialDataWritten = 0x00600000; +const longword errCliSizeOverPDU = 0x00700000; +const longword errCliInvalidPlcAnswer = 0x00800000; +const longword errCliAddressOutOfRange = 0x00900000; +const longword errCliInvalidTransportSize = 0x00A00000; +const longword errCliWriteDataSizeMismatch = 0x00B00000; +const longword errCliItemNotAvailable = 0x00C00000; +const longword errCliInvalidValue = 0x00D00000; +const longword errCliCannotStartPLC = 0x00E00000; +const longword errCliAlreadyRun = 0x00F00000; +const longword errCliCannotStopPLC = 0x01000000; +const longword errCliCannotCopyRamToRom = 0x01100000; +const longword errCliCannotCompress = 0x01200000; +const longword errCliAlreadyStop = 0x01300000; +const longword errCliFunNotAvailable = 0x01400000; +const longword errCliUploadSequenceFailed = 0x01500000; +const longword errCliInvalidDataSizeRecvd = 0x01600000; +const longword errCliInvalidBlockType = 0x01700000; +const longword errCliInvalidBlockNumber = 0x01800000; +const longword errCliInvalidBlockSize = 0x01900000; +const longword errCliDownloadSequenceFailed = 0x01A00000; +const longword errCliInsertRefused = 0x01B00000; +const longword errCliDeleteRefused = 0x01C00000; +const longword errCliNeedPassword = 0x01D00000; +const longword errCliInvalidPassword = 0x01E00000; +const longword errCliNoPasswordToSetOrClear = 0x01F00000; +const longword errCliJobTimeout = 0x02000000; +const longword errCliPartialDataRead = 0x02100000; +const longword errCliBufferTooSmall = 0x02200000; +const longword errCliFunctionRefused = 0x02300000; +const longword errCliDestroying = 0x02400000; +const longword errCliInvalidParamNumber = 0x02500000; +const longword errCliCannotChangeParam = 0x02600000; + +const time_t DeltaSecs = 441763200; // Seconds between 1970/1/1 (C time base) and 1984/1/1 (Siemens base) + +#pragma pack(1) + +// Read/Write Multivars +typedef struct{ + int Area; + int WordLen; + int Result; + int DBNumber; + int Start; + int Amount; + void *pdata; +} TS7DataItem, *PS7DataItem; + +typedef int TS7ResultItems[MaxVars]; +typedef TS7ResultItems *PS7ResultItems; + +typedef struct { + int OBCount; + int FBCount; + int FCCount; + int SFBCount; + int SFCCount; + int DBCount; + int SDBCount; +} TS7BlocksList, *PS7BlocksList; + +typedef struct { + int BlkType; + int BlkNumber; + int BlkLang; + int BlkFlags; + int MC7Size; // The real size in bytes + int LoadSize; + int LocalData; + int SBBLength; + int CheckSum; + int Version; + // Chars info + char CodeDate[11]; + char IntfDate[11]; + char Author[9]; + char Family[9]; + char Header[9]; +} TS7BlockInfo, *PS7BlockInfo ; + +typedef word TS7BlocksOfType[0x2000]; +typedef TS7BlocksOfType *PS7BlocksOfType; + +typedef struct { + char Code[21]; // Order Code + byte V1; // Version V1.V2.V3 + byte V2; + byte V3; +} TS7OrderCode, *PS7OrderCode; + +typedef struct { + char ModuleTypeName[33]; + char SerialNumber[25]; + char ASName[25]; + char Copyright[27]; + char ModuleName[25]; +} TS7CpuInfo, *PS7CpuInfo; + +typedef struct { + int MaxPduLengt; + int MaxConnections; + int MaxMpiRate; + int MaxBusRate; +} TS7CpInfo, *PS7CpInfo; + +// See §33.1 of "System Software for S7-300/400 System and Standard Functions" +// and see SFC51 description too +typedef struct { + word LENTHDR; + word N_DR; +} SZL_HEADER, *PSZL_HEADER; + +typedef struct { + SZL_HEADER Header; + byte Data[0x4000-4]; +} TS7SZL, *PS7SZL; + +// SZL List of available SZL IDs : same as SZL but List items are big-endian adjusted +typedef struct { + SZL_HEADER Header; + word List[0x2000-2]; +} TS7SZLList, *PS7SZLList; + +// See §33.19 of "System Software for S7-300/400 System and Standard Functions" +typedef struct { + word sch_schal; + word sch_par; + word sch_rel; + word bart_sch; + word anl_sch; +} TS7Protection, *PS7Protection; + +#define s7opNone 0 +#define s7opReadArea 1 +#define s7opWriteArea 2 +#define s7opReadMultiVars 3 +#define s7opWriteMultiVars 4 +#define s7opDBGet 5 +#define s7opUpload 6 +#define s7opDownload 7 +#define s7opDelete 8 +#define s7opListBlocks 9 +#define s7opAgBlockInfo 10 +#define s7opListBlocksOfType 11 +#define s7opReadSzlList 12 +#define s7opReadSZL 13 +#define s7opGetDateTime 14 +#define s7opSetDateTime 15 +#define s7opGetOrderCode 16 +#define s7opGetCpuInfo 17 +#define s7opGetCpInfo 18 +#define s7opGetPlcStatus 19 +#define s7opPlcHotStart 20 +#define s7opPlcColdStart 21 +#define s7opCopyRamToRom 22 +#define s7opCompress 23 +#define s7opPlcStop 24 +#define s7opGetProtection 25 +#define s7opSetPassword 26 +#define s7opClearPassword 27 +#define s7opDBFill 28 + +// Param Number (to use with setparam) + +// Low level : change them to experiment new connections, their defaults normally work well +const int pc_iso_SendTimeout = 6; +const int pc_iso_RecvTimeout = 7; +const int pc_iso_ConnTimeout = 8; +const int pc_iso_SrcRef = 1; +const int pc_iso_DstRef = 2; +const int pc_iso_SrcTSAP = 3; +const int pc_iso_DstTSAP = 4; +const int pc_iso_IsoPduSize = 5; + +// Client Connection Type +const word CONNTYPE_PG = 0x01; // Connect to the PLC as a PG +const word CONNTYPE_OP = 0x02; // Connect to the PLC as an OP +const word CONNTYPE_BASIC = 0x03; // Basic connection + +#pragma pack() + +// Internal struct for operations +// Commands are not executed directly in the function such as "DBRead(...", +// but this struct is filled and then PerformOperation() is called. +// This allow us to implement async function very easily. + +struct TSnap7Job +{ + int Op; // Operation Code + int Result; // Operation result + bool Pending; // A Job is pending + longword Time; // Job Execution time + // Read/Write + int Area; // Also used for Block type and Block of type + int Number; // Used for DB Number, Block number + int Start; // Offset start + int WordLen; // Word length + // SZL + int ID; // SZL ID + int Index; // SZL Index + // ptr info + void * pData; // User data pointer + int Amount; // Items amount/Size in input + int *pAmount; // Items amount/Size in output + // Generic + int IParam; // Used for full upload and CopyRamToRom extended timeout +}; + +class TSnap7MicroClient: public TSnap7Peer +{ +private: + void FillTime(word SiemensTime, char *PTime); + byte BCDtoByte(byte B); + byte WordToBCD(word Value); + int opReadArea(); + int opWriteArea(); + int opReadMultiVars(); + int opWriteMultiVars(); + int opListBlocks(); + int opListBlocksOfType(); + int opAgBlockInfo(); + int opDBGet(); + int opDBFill(); + int opUpload(); + int opDownload(); + int opDelete(); + int opReadSZL(); + int opReadSZLList(); + int opGetDateTime(); + int opSetDateTime(); + int opGetOrderCode(); + int opGetCpuInfo(); + int opGetCpInfo(); + int opGetPlcStatus(); + int opPlcStop(); + int opPlcHotStart(); + int opPlcColdStart(); + int opCopyRamToRom(); + int opCompress(); + int opGetProtection(); + int opSetPassword(); + int opClearPassword(); + int CpuError(int Error); + longword DWordAt(void * P); + int CheckBlock(int BlockType, int BlockNum, void *pBlock, int Size); + int SubBlockToBlock(int SBB); +protected: + word ConnectionType; + longword JobStart; + TSnap7Job Job; + int DataSizeByte(int WordLength); + int opSize; // last operation size + int PerformOperation(); +public: + TS7Buffer opData; + TSnap7MicroClient(); + ~TSnap7MicroClient(); + int Reset(bool DoReconnect); + void SetConnectionParams(const char *RemAddress, word LocalTSAP, word RemoteTsap); + void SetConnectionType(word ConnType); + int ConnectTo(const char *RemAddress, int Rack, int Slot); + int Connect(); + int Disconnect(); + int GetParam(int ParamNumber, void *pValue); + int SetParam(int ParamNumber, void *pValue); + // Fundamental Data I/O functions + int ReadArea(int Area, int DBNumber, int Start, int Amount, int WordLen, void * pUsrData); + int WriteArea(int Area, int DBNumber, int Start, int Amount, int WordLen, void * pUsrData); + int ReadMultiVars(PS7DataItem Item, int ItemsCount); + int WriteMultiVars(PS7DataItem Item, int ItemsCount); + // Data I/O Helper functions + int DBRead(int DBNumber, int Start, int Size, void * pUsrData); + int DBWrite(int DBNumber, int Start, int Size, void * pUsrData); + int MBRead(int Start, int Size, void * pUsrData); + int MBWrite(int Start, int Size, void * pUsrData); + int EBRead(int Start, int Size, void * pUsrData); + int EBWrite(int Start, int Size, void * pUsrData); + int ABRead(int Start, int Size, void * pUsrData); + int ABWrite(int Start, int Size, void * pUsrData); + int TMRead(int Start, int Amount, void * pUsrData); + int TMWrite(int Start, int Amount, void * pUsrData); + int CTRead(int Start, int Amount, void * pUsrData); + int CTWrite(int Start, int Amount, void * pUsrData); + // Directory functions + int ListBlocks(PS7BlocksList pUsrData); + int GetAgBlockInfo(int BlockType, int BlockNum, PS7BlockInfo pUsrData); + int GetPgBlockInfo(void * pBlock, PS7BlockInfo pUsrData, int Size); + int ListBlocksOfType(int BlockType, TS7BlocksOfType *pUsrData, int & ItemsCount); + // Blocks functions + int Upload(int BlockType, int BlockNum, void * pUsrData, int & Size); + int FullUpload(int BlockType, int BlockNum, void * pUsrData, int & Size); + int Download(int BlockNum, void * pUsrData, int Size); + int Delete(int BlockType, int BlockNum); + int DBGet(int DBNumber, void * pUsrData, int & Size); + int DBFill(int DBNumber, int FillChar); + // Date/Time functions + int GetPlcDateTime(tm &DateTime); + int SetPlcDateTime(tm * DateTime); + int SetPlcSystemDateTime(); + // System Info functions + int GetOrderCode(PS7OrderCode pUsrData); + int GetCpuInfo(PS7CpuInfo pUsrData); + int GetCpInfo(PS7CpInfo pUsrData); + int ReadSZL(int ID, int Index, PS7SZL pUsrData, int &Size); + int ReadSZLList(PS7SZLList pUsrData, int &ItemsCount); + // Control functions + int PlcHotStart(); + int PlcColdStart(); + int PlcStop(); + int CopyRamToRom(int Timeout); + int Compress(int Timeout); + int GetPlcStatus(int &Status); + // Security functions + int GetProtection(PS7Protection pUsrData); + int SetSessionPassword(char *Password); + int ClearSessionPassword(); + // Properties + bool Busy(){ return Job.Pending; }; + int Time(){ return int(Job.Time);} +}; + +typedef TSnap7MicroClient *PSnap7MicroClient; + +//--------------------------------------------------------------------------- +#endif // s7_micro_client_h diff --git a/core/src/drivers/plugins/native/s7comm/snap7/core/s7_partner.cpp b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_partner.cpp new file mode 100644 index 00000000..e9e3fdd3 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_partner.cpp @@ -0,0 +1,1178 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 Davide Nardella | +| All rights reserved. | +|==============================================================================| +| SNAP7 is free software: you can redistribute it and/or modify | +| it under the terms of the Lesser GNU General Public License as published by | +| the Free Software Foundation, either version 3 of the License, or | +| (at your option) any later version. | +| | +| It means that you can distribute your commercial software linked with | +| SNAP7 without the requirement to distribute the source code of your | +| application and without the requirement that your application be itself | +| distributed under LGPL. | +| | +| SNAP7 is distributed in the hope that it will be useful, | +| but WITHOUT ANY WARRANTY; without even the implied warranty of | +| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | +| Lesser GNU General Public License for more details. | +| | +| You should have received a copy of the GNU General Public License and a | +| copy of Lesser GNU General Public License along with Snap7. | +| If not, see http://www.gnu.org/licenses/ | +|=============================================================================*/ +#include "s7_partner.h" +//------------------------------------------------------------------------------ + +static PServersManager ServersManager = NULL; + +//------------------------------------------------------------------------------ +int ServersManager_GetServer(longword BindAddress, PConnectionServer &Server) +{ + if (ServersManager == NULL) + { + ServersManager = new TServersManager(); + } + return ServersManager->GetServer(BindAddress, Server); +} +//------------------------------------------------------------------------------ +void ServersManager_RemovePartner(PConnectionServer Server, PSnap7Partner Partner) +{ + if (ServersManager != NULL) + { + ServersManager->RemovePartner(Server, Partner); + if (ServersManager->ServersCount==0) + { + delete ServersManager; + ServersManager = NULL; + } + } +} +//------------------------------------------------------------------------------ +// CONNECTION SERVERS MANAGER +//------------------------------------------------------------------------------ +TServersManager::TServersManager() +{ + cs = new TSnapCriticalSection; + memset(Servers,0,sizeof(Servers)); + ServersCount=0; +} +//--------------------------------------------------------------------------- +TServersManager::~TServersManager() +{ + int c; + Lock(); + if (ServersCount>0) + { + for (c = 0; c < MaxAdapters; c++) + { + if (Servers[c]!=0) + { + delete Servers[c]; + Servers[c]=0; + ServersCount--; + } + } + } + Unlock(); + delete cs; +} +//--------------------------------------------------------------------------- +void TServersManager::Lock() +{ + cs->Enter(); +} +//--------------------------------------------------------------------------- +void TServersManager::Unlock() +{ + cs->Leave(); +} +//--------------------------------------------------------------------------- +void TServersManager::AddServer(PConnectionServer Server) +{ + int c; + Lock(); + for (c = 0; c < MaxAdapters; c++) + { + if (Servers[c]==0) + { + Servers[c]=Server; + ServersCount++; + break; + } + } + Unlock(); +} +//--------------------------------------------------------------------------- +int TServersManager::CreateServer(longword BindAddress, PConnectionServer &Server) +{ + in_addr sin; + sin.s_addr=BindAddress; + int Result; + + if (ServersCountStartTo(inet_ntoa(sin)); + if (Result!=0) + { + delete Server; + Server=0; + return Result; + } + AddServer(Server); + return 0; + } + else + return errServerNoRoom; +} +//--------------------------------------------------------------------------- +int TServersManager::GetServer(longword BindAddress, PConnectionServer &Server) +{ + int c; + Server=0; + for (c = 0; c < ServersCount; c++) + { + if (Servers[c]->LocalBind==BindAddress) + { + Server=Servers[c]; + break; + } + } + if (Server==0) + return CreateServer(BindAddress, Server); + else + return 0; +} +//--------------------------------------------------------------------------- +void TServersManager::RemovePartner(PConnectionServer Server, PSnap7Partner Partner) +{ + int c; + Server->RemovePartner(Partner); + if (Server->PartnersCount==0) + { + Lock(); + for (c = 0; c < MaxAdapters; c++) + { + if (Servers[c]==Server) + { + Servers[c]=0; + ServersCount--; + break; + } + } + Unlock(); + delete Server; + } +} +//--------------------------------------------------------------------------- +// CONNECTION SERVER +//------------------------------------------------------------------------------ +void TConnListenerThread::Execute() +{ + socket_t Sock; + bool Valid; + + while (!Terminated) + { + if (FListener->CanRead(FListener->WorkInterval)) + { + Sock=FListener->SckAccept(); // in any case we must accept + Valid=Sock!=INVALID_SOCKET; + // check if we are not destroying + if ((!Terminated) && (!FServer->Destroying)) + { + if (Valid) + FServer->Incoming(Sock); + } + else + if (Valid) + Msg_CloseSocket(Sock); + }; + } +} +//------------------------------------------------------------------------------ +TConnectionServer::TConnectionServer() +{ + cs = new TSnapCriticalSection; + memset(Partners,0,sizeof(Partners)); + FRunning = false; + PartnersCount = 0; +} +//------------------------------------------------------------------------------ +TConnectionServer::~TConnectionServer() +{ + Stop(); + delete cs; +} +//--------------------------------------------------------------------------- +void TConnectionServer::Lock() +{ + cs->Enter(); +} +void TConnectionServer::Unlock() +{ + cs->Leave(); +} +//--------------------------------------------------------------------------- +int TConnectionServer::Start() +{ + int Result; + // Creates the listener + SockListener = new TMsgSocket(); + strncpy(SockListener->LocalAddress,FLocalAddress,16); + SockListener->LocalPort=isoTcpPort; + // Binds + Result=SockListener->SckBind(); + if (Result==0) + { + LocalBind=SockListener->LocalBind; + // Listen + Result=SockListener->SckListen(); + if (Result==0) + { + // Creates the Listener thread + ServerThread = new TConnListenerThread(SockListener, this); + ServerThread->Start(); + } + else + delete SockListener; + } + else + delete SockListener; + + FRunning=Result==0; + return Result; +} +//--------------------------------------------------------------------------- +int TConnectionServer::StartTo(const char *Address) +{ + strncpy(FLocalAddress,Address,16); + return Start(); +} +//--------------------------------------------------------------------------- +void TConnectionServer::Stop() +{ + if (FRunning) + { + // Kills the listener thread + ServerThread->Terminate(); + if (ServerThread->WaitFor(csTimeout)!=WAIT_OBJECT_0) + ServerThread->Kill(); + delete ServerThread; + // Kills the listener + delete SockListener; + FRunning = false; + } +} +//--------------------------------------------------------------------------- +PSnap7Partner TConnectionServer::FindPartner(longword Address) +{ + int c; + PSnap7Partner Result; + for (c = 0; c < MaxPartners; c++) + { + Result=Partners[c]; + if (Result!=NULL) + { + if (Result->PeerAddress==Address) + return Result; + } + } + return NULL; +} +//------------------------------------------------------------------------------ +int TConnectionServer::FirstFree() +{ + int i; + for (i = 0; i < MaxPartners; i++) + { + if (Partners[i]==0) + return i; + } + return -1; +} +//------------------------------------------------------------------------------ +int TConnectionServer::RegisterPartner(PSnap7Partner Partner) +{ + PSnap7Partner aPartner; + int idx; + // check if already exists a passive partner linked to the same peer address + aPartner=FindPartner(Partner->PeerAddress); + if (aPartner==NULL) + { + Lock(); + idx=FirstFree(); + if (idx>=0) + { + Partners[idx]=Partner; + PartnersCount++; + } + Unlock(); + if (idx>=0) + return 0; + else + return errParNoRoom; + } + else + return errParAddressInUse; +} +//------------------------------------------------------------------------------ +void TConnectionServer::RemovePartner(PSnap7Partner Partner) +{ + int c; + Lock(); + for (c = 0; c < MaxPartners; c++) + { + if (Partners[c]==Partner) + { + Partners[c]=0; + PartnersCount--; + break; + } + } + Unlock(); +} +//------------------------------------------------------------------------------ +void TConnectionServer::Incoming(socket_t Sock) +{ + longword Address; + PSnap7Partner Partner; + + Address=Msg_GetSockAddr(Sock); + // Looks for a partner that is waiting for a connection from this address + Lock(); + Partner=FindPartner(Address); + Unlock(); + // if partner exists must not be already connected : a partner can be connected + // with only one peer at time + if ((Partner!=NULL) && (!Partner->Stopping) && (!Partner->Connected)) + Partner->SetSocket(Sock); + else + Msg_CloseSocket(Sock); // we are not interested +} +//------------------------------------------------------------------------------ +// PARTHER THREAD +//------------------------------------------------------------------------------ +void TPartnerThread::Execute() +{ + longword TheTime; + + FKaElapsed=SysGetTick(); + while ((!Terminated) && (!FPartner->Destroying)) + { + // Check connection + while (!Terminated && !FPartner->Connected && !FPartner->Destroying) + { + if (!FPartner->ConnectToPeer()) + SysSleep(FRecoveryTime); + } + // Execution + if ((!Terminated) && (!FPartner->Destroying) && (!FPartner->Execute())) + SysSleep(FRecoveryTime); + // Keep Alive + if (!Terminated && (!FPartner->Destroying) && FPartner->Active && FPartner->Connected) + { + TheTime=SysGetTick(); + if (TheTime-FKaElapsed>FPartner->KeepAliveTime) + { + FKaElapsed=TheTime; + if (!FPartner->Ping(FPartner->RemoteAddress)) + FPartner->Disconnect(); + }; + }; + }; +} +//------------------------------------------------------------------------------ +// S7 PARTNER +//------------------------------------------------------------------------------ +TSnap7Partner::TSnap7Partner(bool CreateActive) +{ + // We skip RFC/ISO header, our PDU is the ISO payload + PDUH_in=PS7ReqHeader(&PDU.Payload); + FWorkerThread=0; + OnBRecv = 0; + OnBSend = 0; + Active=CreateActive; + SendEvt = new TSnapEvent(true); + RecvEvt = new TSnapEvent(true); + FSendPending = false; + FRecvPending = false; + memset(&FRecvStatus,0,sizeof(TRecvStatus)); + memset(&FRecvLast,0,sizeof(TRecvLast)); + FSendElapsed = 0; + Destroying = false; + // public + Linked =false; + Running =false; + BindError =false; + BRecvTimeout =3000; + BSendTimeout =3000; + RecoveryTime =500; + KeepAliveTime =5000; + NextByte =0; + PeerAddress =0; + SendTime =0; + RecvTime =0; + BytesSent =0; + BytesRecv =0; + SendErrors =0; + RecvErrors =0; +} +//------------------------------------------------------------------------------ +TSnap7Partner::~TSnap7Partner() +{ + Stop(); + OnBRecv = 0; + OnBSend = 0; + delete SendEvt; + delete RecvEvt; +} +//------------------------------------------------------------------------------ +byte TSnap7Partner::GetNextByte() +{ + NextByte++; + if (NextByte==0xFF) + NextByte=1; + return NextByte; +} +//------------------------------------------------------------------------------ +int TSnap7Partner::Start() +{ + int Result; + PeerAddress=inet_addr(RemoteAddress); + SrcAddress =inet_addr(LocalAddress); + if (!Running) + { + if (!Active) + { + Result=ServersManager_GetServer(SrcAddress,FServer); + if (Result==0) + FServer->RegisterPartner(this); + BindError=Result!=0; + } + else + { + Linked=PeerConnect()==0; + Result=0; // we need to create the worker thread even tough it's not linked + }; + // if ok create the worker thread + if (Result==0) + { + FWorkerThread = new TPartnerThread(this, RecoveryTime); + FWorkerThread->Start(); + } + } + else + Result=0; + + Running=Result==0; + + return Result; +} +//------------------------------------------------------------------------------ +int TSnap7Partner::StartTo(const char *LocAddress, const char *RemAddress, word LocTsap, word RemTsap) +{ + SrcTSap=LocTsap; + DstTSap=RemTsap; + strcpy(LocalAddress,LocAddress); + strcpy(RemoteAddress,RemAddress); + return Start(); +} +//------------------------------------------------------------------------------ +int TSnap7Partner::Stop() +{ + if (Running) + { + Stopping=true; // to prevent incoming connections + CloseWorker(); + if (!Active && (FServer!=0)) + ServersManager_RemovePartner(FServer, this); + if (Connected) + Disconnect(); + Running =false; + Stopping=false; + }; + BindError=false; + return 0; +} +//------------------------------------------------------------------------------ +void TSnap7Partner::Disconnect() +{ + PeerDisconnect(); + Linked=false; +} +//------------------------------------------------------------------------------ +int TSnap7Partner::GetParam(int ParamNumber, void * pValue) +{ + switch (ParamNumber) + { + case p_u16_LocalPort: + *Puint16_t(pValue)=LocalPort; + break; + case p_u16_RemotePort: + *Puint16_t(pValue)=RemotePort; + break; + case p_i32_PingTimeout: + *Pint32_t(pValue)=PingTimeout; + break; + case p_i32_SendTimeout: + *Pint32_t(pValue)=SendTimeout; + break; + case p_i32_RecvTimeout: + *Pint32_t(pValue)=RecvTimeout; + break; + case p_i32_WorkInterval: + *Pint32_t(pValue)=WorkInterval; + break; + case p_u16_SrcRef: + *Puint16_t(pValue)=SrcRef; + break; + case p_u16_DstRef: + *Puint16_t(pValue)=DstRef; + break; + case p_u16_SrcTSap: + *Puint16_t(pValue)=SrcTSap; + break; + case p_i32_PDURequest: + *Pint32_t(pValue)=PDURequest; + break; + case p_i32_BSendTimeout: + *Pint32_t(pValue)=BSendTimeout; + break; + case p_i32_BRecvTimeout: + *Pint32_t(pValue)=BRecvTimeout; + break; + case p_u32_RecoveryTime: + *Puint32_t(pValue)=RecoveryTime; + break; + case p_u32_KeepAliveTime: + *Puint32_t(pValue)=KeepAliveTime; + break; + default: return errParInvalidParamNumber; + } + return 0; +} +//------------------------------------------------------------------------------ +int TSnap7Partner::SetParam(int ParamNumber, void * pValue) +{ + switch (ParamNumber) + { + case p_u16_RemotePort: + if (!Connected && Active) + RemotePort=*Puint16_t(pValue); + else + return errParCannotChangeParam; + break; + case p_i32_PingTimeout: + PingTimeout=*Pint32_t(pValue); + break; + case p_i32_SendTimeout: + SendTimeout=*Pint32_t(pValue); + break; + case p_i32_RecvTimeout: + RecvTimeout=*Pint32_t(pValue); + break; + case p_i32_WorkInterval: + WorkInterval=*Pint32_t(pValue); + break; + case p_u16_SrcRef: + SrcRef=*Puint16_t(pValue); + break; + case p_u16_DstRef: + DstRef=*Puint16_t(pValue); + break; + case p_u16_SrcTSap: + SrcTSap=*Puint16_t(pValue); + break; + case p_i32_PDURequest: + PDURequest=*Pint32_t(pValue); + break; + case p_i32_BSendTimeout: + BSendTimeout=*Pint32_t(pValue); + break; + case p_i32_BRecvTimeout: + BRecvTimeout=*Pint32_t(pValue); + break; + case p_u32_RecoveryTime: + RecoveryTime=*Puint32_t(pValue); + break; + case p_u32_KeepAliveTime: + KeepAliveTime=*Puint32_t(pValue); + break; + default: return errParInvalidParamNumber; + } + return 0; +} +//------------------------------------------------------------------------------ +void TSnap7Partner::ClearRecv() +{ + memset(&FRecvStatus,0,sizeof(TRecvStatus)); + FRecvPending=false; +} +//------------------------------------------------------------------------------ +bool TSnap7Partner::ConnectToPeer() +{ + bool Result; + if (Active) + { + Result=PeerConnect()==0; // try to Connect + Linked=Result; + } + else + Result =false; // nothing : we are waiting for a connection + + return Result; +} +//------------------------------------------------------------------------------ +bool TSnap7Partner::PerformFunctionNegotiate() +{ + PReqFunNegotiateParams ReqParams; + PResFunNegotiateParams ResParams; + TS7Answer23 Answer; + int Size; + + // Setup pointers + ReqParams=PReqFunNegotiateParams(pbyte(PDUH_in)+sizeof(TS7ReqHeader)); + ResParams=PResFunNegotiateParams(pbyte(&Answer)+sizeof(TS7ResHeader23)); + // We are here only because we found a PduType_request, the partner can only + // handle Bs} requests and pdu negotiation requests. + // So, now we must check the incoming function + if (ReqParams->FunNegotiate!=pduNegotiate) + { + LastError=errParInvalidPDU; + return false; + }; + // Prepares the answer + Answer.Header.P=0x32; + Answer.Header.PDUType =0x03; + Answer.Header.AB_EX =0x0000; + Answer.Header.Sequence=PDUH_in->Sequence; + Answer.Header.ParLen =SwapWord(sizeof(TResFunNegotiateParams)); + Answer.Header.DataLen =0x0000; + Answer.Header.Error =0x0000; + // Params point at the } of the header + ResParams->FunNegotiate=pduNegotiate; + ResParams->Unknown=0x0; + // Checks PDU request length + if (SwapWord(ResParams->PDULength)>IsoPayload_Size) + ResParams->PDULength=SwapWord(IsoPayload_Size); + else + ResParams->PDULength=ReqParams->PDULength; + // We offer the same + ResParams->ParallelJobs_1=ReqParams->ParallelJobs_1; + ResParams->ParallelJobs_2=ReqParams->ParallelJobs_2; + // And store the value + PDULength=SwapWord(ResParams->PDULength); + // Sends the answer + Size=sizeof(TS7ResHeader23) + sizeof(TResFunNegotiateParams); + if (isoSendBuffer(&Answer, Size)!=0) + SetError(errParNegotiatingPDU); + + Linked=LastError==0; + return Linked; +} +//------------------------------------------------------------------------------ +void TSnap7Partner::CloseWorker() +{ + int Timeout; + if (FWorkerThread) + { + FWorkerThread->Terminate(); + if (FRecvPending || FSendPending) + Timeout=3000; + else + Timeout=1000; + + if (FWorkerThread->WaitFor(Timeout)!=WAIT_OBJECT_0) + FWorkerThread->Kill(); + try { + delete FWorkerThread; + } + catch (...){ + } + FWorkerThread=0; + } +} +//------------------------------------------------------------------------------ +bool TSnap7Partner::BlockSend() +{ + PBSendReqParams ReqParams; + PBSendReqParams ResParams; + PBsendRequestData DataSendReq; + int TotalSize; + int SentSize; + int Slice; + int MaxSlice; + uintptr_t Offset; + pbyte Source; + bool First, Last; + byte Seq_IN; + int TxIsoSize; + pbyte Data; + pword TotalPackSize; + int DataPtrOffset; + word Extra; + + ClrError(); + TotalSize=TxBuffer.Size; + SentSize =TotalSize; + Offset=0; + First =true; + Seq_IN=0x00; + + // With BSend we can transfer up to 32k (S7300) or 64k (S7400), but splitted + // into slice that cannot exced the PDU size negotiated (including various headers). + MaxSlice=PDULength-sizeof(TS7ReqHeader)-sizeof(TBSendParams)-sizeof(TBsendRequestData)-2; + + ReqParams=PBSendReqParams(pbyte(PDUH_out)+sizeof(TS7ReqHeader)); + ResParams=ReqParams; // pdu 7 is symmetrical + + while ((TotalSize>0) && (LastError==0)) + { + Source=pbyte(&TxBuffer.Data)+Offset; + Slice=TotalSize; + + if (Slice>MaxSlice) + Slice=MaxSlice; + + TotalSize-=Slice; + Offset+=Slice; + Last=TotalSize==0; + + // Prepare send + DataPtrOffset=sizeof(TS7ReqHeader)+sizeof(TBSendParams); + // Header + PDUH_out->P=0x32; // Always 0x32 + PDUH_out->PDUType=PduType_userdata; // 7 + PDUH_out->AB_EX=0x0000; // Always 0x0000 + PDUH_out->Sequence=GetNextWord(); // Autoinc + PDUH_out->ParLen=SwapWord(sizeof(TBSendParams)); // 16 bytes + + ReqParams->Head[0]=0x00; + ReqParams->Head[1]=0x01; + ReqParams->Head[2]=0x12; + ReqParams->Plen =0x08; // length from here up the end of the record + ReqParams->Uk =0x12; + ReqParams->Tg =grBSend; // 0x46 + ReqParams->SubFun =0x01; + ReqParams->Seq =Seq_IN; + ReqParams->Err =0x0000; + if (Last) + ReqParams->EoS =0x00; + else + ReqParams->EoS =0x01; + // Next byte is auto inc and not zero for partial sequences + // Is zero for lonely sequences. + if (First && Last) + ReqParams->IDSeq=0x00; + else + ReqParams->IDSeq=GetNextByte(); + + DataSendReq=PBsendRequestData(pbyte(PDUH_out)+DataPtrOffset); + if (First) + { + // in the first pdu, after data header there is the whole packet length + TotalPackSize=pword(pbyte(DataSendReq)+sizeof(TBsendRequestData)); + Data=pbyte(TotalPackSize)+sizeof(word); + *TotalPackSize=SwapWord(word(TxBuffer.Size)); + Extra=2; // extra bytes (total pack size indicator) + } + else + { + Data=pbyte(DataSendReq)+sizeof(TBsendRequestData); + Extra=0; + }; + + PDUH_out->DataLen=SwapWord(word(sizeof(TBsendRequestData))+Slice+Extra); + DataSendReq->Len =SwapWord(Slice+8+Extra); + TxIsoSize=Slice+sizeof(TS7ReqHeader)+sizeof(TBSendParams)+sizeof(TBsendRequestData)+Extra; + + DataSendReq->FF =0xFF; + DataSendReq->TRSize =TS_ResOctet; + DataSendReq->DHead[0]=0x12; + DataSendReq->DHead[1]=0x06; + DataSendReq->DHead[2]=0x13; + DataSendReq->DHead[3]=0x00; + DataSendReq->R_ID =SwapDWord(TxBuffer.R_ID); + memcpy(Data, Source ,Slice); + + if (isoExchangeBuffer(NULL, TxIsoSize)!=0) + SetError(errParSendingBlock); + + if (LastError==0) + { + Seq_IN=ResParams->Seq; + if (SwapWord(ResParams->Err)!=0) + LastError=errParSendRefused; + } + + if (First) + { + First =false; + MaxSlice+=2; // only in the first frame we have the extra info + }; + }; + + SendTime=SysGetTick()-FSendElapsed; + if (LastError==0) + BytesSent+=SentSize; + + return LastError==0; +} +//------------------------------------------------------------------------------ +bool TSnap7Partner::PickData() +{ + PBSendReqParams ReqParams; + PBSendReqParams ResParams; + PBSendResData ResData; + PBsendRequestData DataSendReq; + pbyte Source, Target; + pword TotalPackSize; + word Slice; + int AnswerLen; + + ClrError(); + // Setup pointers + ReqParams =PBSendReqParams(pbyte(PDUH_in)+sizeof(TS7ReqHeader)); + ResParams =ReqParams; // pdu 7 is symmetrical + DataSendReq=PBsendRequestData(pbyte(ReqParams)+sizeof(TBSendParams)); + + // Checks if PDU is a BSend request + if ((PDUH_in->PDUType!=PduType_userdata) || (ReqParams->Tg!=grBSend)) + { + LastError=errParInvalidPDU; + return false; + } + + if (FRecvStatus.First) + { + TotalPackSize=(word*)(pbyte(DataSendReq)+sizeof(TBsendRequestData)); + FRecvStatus.TotalLength=SwapWord(*TotalPackSize); + Source=pbyte(DataSendReq)+sizeof(TBsendRequestData)+2; + FRecvStatus.In_R_ID=SwapDWord(DataSendReq->R_ID); + FRecvStatus.Offset=0; + Slice=SwapWord(DataSendReq->Len)-10; + } + else { + Slice=SwapWord(DataSendReq->Len)-8; + Source=pbyte(DataSendReq)+sizeof(TBsendRequestData); + } + + FRecvStatus.Done=ReqParams->EoS==0x00; + + Target=pbyte(&RxBuffer)+FRecvStatus.Offset; + memcpy(Target, Source, Slice); + FRecvStatus.Offset+=Slice; + + ResData =PBSendResData(pbyte(ResParams)+sizeof(TBSendParams)); + // Send Answer + PDUH_out->ParLen =SwapWord(sizeof(TBSendParams)); + PDUH_out->DataLen =SwapWord(sizeof(TBSendResData)); + + ResParams->Head[0]=0x00; + ResParams->Head[1]=0x01; + ResParams->Head[2]=0x12; + ResParams->Plen =0x08; // length from here up the end of the record + ResParams->Uk =0x12; + ResParams->Tg =0x86; + ResParams->SubFun =0x01; + ResParams->Seq =FRecvStatus.Seq_Out; + ResParams->Err =0x0000; + ResParams->EoS =0x00; + ResParams->IDSeq =0x00; + + ResData->DHead[0] =0x0A; + ResData->DHead[1] =0x00; + ResData->DHead[2] =0x00; + ResData->DHead[3] =0x00; + + AnswerLen=sizeof(TS7ReqHeader)+sizeof(TBSendParams)+sizeof(TBSendResData); + if (isoSendBuffer(NULL,AnswerLen)!=0) + SetError(errParRecvingBlock); + + return LastError==0; +} +//------------------------------------------------------------------------------ +bool TSnap7Partner::BlockRecv() +{ + bool Result; + if (!FRecvPending) // Start sequence + { + FRecvPending=true; + FRecvStatus.First=true; + FRecvStatus.Done =false; + FRecvStatus.Seq_Out =GetNextByte(); + FRecvStatus.Elapsed =SysGetTick(); + FRecvLast.Done=false; + FRecvLast.Result=0; + FRecvLast.R_ID=0; + FRecvLast.Size=0; + RecvTime =0; + FRecvLast.Count++; + if (FRecvLast.Count==0xFFFFFFFF) + FRecvLast.Count=0; + }; + + Result=PickData(); + FRecvStatus.First=false; + + if (!Result || FRecvStatus.Done) + { + FRecvLast.Result=LastError; + if (Result) + { + BytesRecv+=FRecvStatus.TotalLength; + RecvTime=SysGetTick()-FRecvStatus.Elapsed; + FRecvLast.R_ID=FRecvStatus.In_R_ID; + FRecvLast.Size=FRecvStatus.TotalLength; + }; + RecvEvt->Set(); + if ((OnBRecv!=NULL) && !Destroying) + OnBRecv(FRecvUsrPtr, FRecvLast.Result, FRecvLast.R_ID, &RxBuffer, FRecvLast.Size); + FRecvLast.Done=true; + ClearRecv(); + }; + return Result; +} +//------------------------------------------------------------------------------ +bool TSnap7Partner::ConnectionConfirm() +{ + if (FRecvPending) + ClearRecv(); + IsoConfirmConnection(pdu_type_CC); // <- Connection confirm + return LastTcpError!=WSAECONNRESET; +} +//------------------------------------------------------------------------------ +int TSnap7Partner::Status() +{ + if (Running) + { + if (Linked) + { + if (FRecvPending) + return par_receiving; + else + if (FSendPending) + return par_sending; + else + return par_linked; + } + else + if (Active) + return par_connecting; + else + return par_waiting; + } + else{ + if ((!Active) && BindError) + return par_binderror; + else + return par_stopped; + } +} +//------------------------------------------------------------------------------ +bool TSnap7Partner::Execute() +{ + TPDUKind PduKind; + bool RTimeout; + bool Result =true; + + // Checks if there is something to send (and we are not receiving...) + if (FSendPending && !FRecvPending) + { + Result=BlockSend(); + SendEvt->Set(); + if ((OnBSend!=NULL) && (!Destroying)) + OnBSend(FSendUsrPtr, LastError); + FSendPending=false; + } + + if (Destroying) + return false; + + // Checks if there is something to recv + if (Result && CanRead(WorkInterval)) + { + // Peeks info and returns PDU Kind + isoRecvPDU(&PDU); + if (LastTcpError==0) + { + // First check valid data incoming (most likely situation) + IsoPeek(&PDU,PduKind); + if (PduKind==pkValidData) + { + if (PDUH_in->PDUType==PduType_request) + { + if (FRecvPending) + ClearRecv(); + Result=PerformFunctionNegotiate(); + } + else // Pdu type userdata + Result=BlockRecv(); + } + else + if (PduKind==pkConnectionRequest) + Result=ConnectionConfirm(); + else // nothing else + Purge(); + } + else + Result=false; + }; + + if (LastTcpError==WSAECONNRESET) + { + Result=false; + Linked=false; + } + else + if (!Result) + Disconnect(); + + // Check BRecv sequence timeout + RTimeout= FRecvPending && (SysGetTick()-FRecvStatus.Elapsed>longword(BRecvTimeout)); + + if (RTimeout) + { + LastError=errParFrameTimeout; + RecvEvt->Set(); + if ((OnBRecv!=NULL) && !Destroying) + OnBRecv(FRecvUsrPtr, LastError, 0, &RxBuffer,0); + }; + + if (!Result || RTimeout) + ClearRecv(); // parframetimeout + + return Result; +} + +//------------------------------------------------------------------------------ +int TSnap7Partner::BSend(longword R_ID, void *pUsrData, int Size) +{ + // The block send is managed into the worker thread. + // Sync Bsend consists of AsBSend+WaitAsCompletion + int Result=AsBSend(R_ID, pUsrData, Size); + if (Result==0) + Result=WaitAsBSendCompletion(BSendTimeout); + return Result; +} +//------------------------------------------------------------------------------ +int TSnap7Partner::AsBSend(longword R_ID, void *pUsrData, int Size) +{ + SendTime=0; + if (Linked) + { + if (!FSendPending) + { + memcpy(&TxBuffer.Data, pUsrData, Size); + TxBuffer.R_ID=R_ID; + TxBuffer.Size=Size; + SendEvt->Reset(); + FSendPending=true; + FSendElapsed=SysGetTick(); + return 0; + } + else + return errParBusy; + } + else + return SetError(errParNotLinked); +} +//------------------------------------------------------------------------------ +bool TSnap7Partner::CheckAsBSendCompletion(int &opResult) +{ + if (!Destroying) + { + if (!FSendPending) + opResult=LastError; + else + opResult=errParBusy; + + return !FSendPending; + } + else + { + opResult=errParDestroying; + return true; + } + +} +//------------------------------------------------------------------------------ +int TSnap7Partner::WaitAsBSendCompletion(longword Timeout) +{ + if (SendEvt->WaitFor(BSendTimeout)==WAIT_OBJECT_0) + { + if (!Destroying) + return LastError; + else + return SetError(errParDestroying); + } + else + return SetError(errParSendTimeout); +} +//------------------------------------------------------------------------------ +int TSnap7Partner::SetSendCallback(pfn_ParBSendCompletion pCompletion, void *usrPtr) +{ + OnBSend=pCompletion; + FSendUsrPtr=usrPtr; + return 0; +} +//------------------------------------------------------------------------------ +int TSnap7Partner::BRecv(longword &R_ID, void *pData, int &Size, longword Timeout) +{ + int Result=0; + if (RecvEvt->WaitFor(Timeout)==WAIT_OBJECT_0) + { + R_ID =FRecvLast.R_ID; + Size =FRecvLast.Size; + if (FRecvLast.Result==0) + { + if (pData!=NULL) + memcpy(pData, &RxBuffer, Size); + else + Result=errParInvalidParams; + } + else + Result=FRecvLast.Result; + RecvEvt->Reset(); + } + else + Result=errParRecvTimeout; + + return SetError(Result); +} +//------------------------------------------------------------------------------ +bool TSnap7Partner::CheckAsBRecvCompletion(int &opResult, longword &R_ID, + void *pData, int &Size) +{ + if (Destroying) + { + Size=0; + opResult=errParDestroying; + return true; + } + + bool Result=FRecvLast.Done; + if (Result) + { + Size=FRecvLast.Size; + R_ID=FRecvLast.R_ID; + opResult=FRecvLast.Result; + if ((pData!=NULL) && (Size>0) && (opResult==0)) + memcpy(pData, &RxBuffer, Size); + FRecvLast.Done=false; + } + return Result; +} +//------------------------------------------------------------------------------ +int TSnap7Partner::SetRecvCallback(pfn_ParBRecvCallBack pCompletion, void *usrPtr) +{ + OnBRecv=pCompletion; + FRecvUsrPtr=usrPtr; + return 0; +} +//------------------------------------------------------------------------------ + diff --git a/core/src/drivers/plugins/native/s7comm/snap7/core/s7_partner.h b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_partner.h new file mode 100644 index 00000000..dbc20152 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_partner.h @@ -0,0 +1,284 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 Davide Nardella | +| All rights reserved. | +|==============================================================================| +| SNAP7 is free software: you can redistribute it and/or modify | +| it under the terms of the Lesser GNU General Public License as published by | +| the Free Software Foundation, either version 3 of the License, or | +| (at your option) any later version. | +| | +| It means that you can distribute your commercial software linked with | +| SNAP7 without the requirement to distribute the source code of your | +| application and without the requirement that your application be itself | +| distributed under LGPL. | +| | +| SNAP7 is distributed in the hope that it will be useful, | +| but WITHOUT ANY WARRANTY; without even the implied warranty of | +| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | +| Lesser GNU General Public License for more details. | +| | +| You should have received a copy of the GNU General Public License and a | +| copy of Lesser GNU General Public License along with Snap7. | +| If not, see http://www.gnu.org/licenses/ | +|=============================================================================*/ +#ifndef s7_partner_h +#define s7_partner_h +//--------------------------------------------------------------------------- +#include "snap_threads.h" +#include "s7_peer.h" +//--------------------------------------------------------------------------- +using namespace std; +//--------------------------------------------------------------------------- + +#define MaxPartners 256 +#define MaxAdapters 256 +#define csTimeout 1500 // Connection server destruction timeout + +const int par_stopped = 0; // stopped +const int par_connecting = 1; // running and active connecting +const int par_waiting = 2; // running and waiting for a connection +const int par_linked = 3; // running and connected +const int par_sending = 4; // sending data +const int par_receiving = 5; // receiving data +const int par_binderror = 6; // error starting passive partner + +const longword errParMask = 0xFFF00000; +const longword errParBase = 0x000FFFFF; + +const longword errParAddressInUse = 0x00200000; +const longword errParNoRoom = 0x00300000; +const longword errServerNoRoom = 0x00400000; +const longword errParInvalidParams = 0x00500000; +const longword errParNotLinked = 0x00600000; +const longword errParBusy = 0x00700000; +const longword errParFrameTimeout = 0x00800000; +const longword errParInvalidPDU = 0x00900000; +const longword errParSendTimeout = 0x00A00000; +const longword errParRecvTimeout = 0x00B00000; +const longword errParSendRefused = 0x00C00000; +const longword errParNegotiatingPDU = 0x00D00000; +const longword errParSendingBlock = 0x00E00000; +const longword errParRecvingBlock = 0x00F00000; +const longword errParBindError = 0x01000000; +const longword errParDestroying = 0x01100000; +const longword errParInvalidParamNumber = 0x01200000; // Invalid param (par_get/set_param) +const longword errParCannotChangeParam = 0x01300000; // Cannot change because running +const longword errParBufferTooSmall = 0x01400000; // Raised by LabVIEW wrapper + +class TSnap7Partner; +typedef TSnap7Partner *PSnap7Partner; + +class TConnectionServer; +typedef TConnectionServer *PConnectionServer; + +//------------------------------------------------------------------------------ +// CONNECTION SERVERS MANAGER +//------------------------------------------------------------------------------ +class TServersManager +{ +private: + PConnectionServer Servers[MaxAdapters]; + TSnapCriticalSection *cs; + void Lock(); + void Unlock(); + int CreateServer(longword BindAddress, PConnectionServer &Server); + void AddServer(PConnectionServer Server); +public: + int ServersCount; + TServersManager(); + ~TServersManager(); + + int GetServer(longword BindAddress, PConnectionServer &Server); + void RemovePartner(PConnectionServer Server, PSnap7Partner Partner); + +}; +typedef TServersManager *PServersManager; + +//------------------------------------------------------------------------------ +// CONNECTION SERVER (Don't inherit from TcpSrv to avoid dependence) +//------------------------------------------------------------------------------ + +class TConnListenerThread : public TSnapThread +{ +private: + TMsgSocket *FListener; + TConnectionServer *FServer; +public: + TConnListenerThread(TMsgSocket *Listener, TConnectionServer *Server) + { + FServer=Server; + FListener=Listener; + FreeOnTerminate=false; + }; + void Execute(); +}; +typedef TConnListenerThread *PConnListenerThread; + + +class TConnectionServer +{ +private: + TSnapCriticalSection *cs; + bool FRunning; + // Bind Address + char FLocalAddress[16]; + // Server listener + PConnListenerThread ServerThread; + // Socket listener + PMsgSocket SockListener; + // Finds a partner bound to the address + PSnap7Partner FindPartner(longword Address); + // Locks the Partner list + void Lock(); + // Unlocks the Partner list + void Unlock(); +protected: + // Workers list + PSnap7Partner Partners[MaxPartners]; + bool Destroying; + void Incoming(socket_t Sock); + int Start(); + int FirstFree(); +public: + int PartnersCount; + longword LocalBind; + TConnectionServer(); + ~TConnectionServer(); + + int StartTo(const char *Address); + void Stop(); + int RegisterPartner(PSnap7Partner Partner); + void RemovePartner(PSnap7Partner Partner); + friend class TConnListenerThread; +}; +typedef TConnectionServer * PConnectionServer; + +//------------------------------------------------------------------------------ +// PARTNER THREAD +//------------------------------------------------------------------------------ +class TPartnerThread : public TSnapThread +{ +private: + TSnap7Partner *FPartner; + longword FRecoveryTime; + longword FKaElapsed; +protected: + void Execute(); +public: + TPartnerThread(TSnap7Partner *Partner, longword RecoveryTime) + { + FPartner = Partner; + FRecoveryTime =RecoveryTime; + FreeOnTerminate =false; + }; + ~TPartnerThread(){}; +}; +typedef TPartnerThread *PPartnerThread; +//------------------------------------------------------------------------------ +// S7 PARTNER +//------------------------------------------------------------------------------ + +typedef struct{ + bool First; + bool Done; + uintptr_t Offset; + longword TotalLength; + longword In_R_ID; + longword Elapsed; + byte Seq_Out; +}TRecvStatus; + +typedef struct{ + bool Done; + int Size; + int Result; + longword R_ID; + longword Count; +}TRecvLast; + +extern "C" { +typedef void (S7API *pfn_ParBRecvCallBack)(void * usrPtr, int opResult, longword R_ID, void *pdata, int Size); +typedef void (S7API *pfn_ParBSendCompletion)(void * usrPtr, int opResult); +} + +class TSnap7Partner : public TSnap7Peer +{ +private: + PS7ReqHeader PDUH_in; + void *FRecvUsrPtr; + void *FSendUsrPtr; + PSnapEvent SendEvt; + PSnapEvent RecvEvt; + PConnectionServer FServer; + PPartnerThread FWorkerThread; + bool FSendPending; + bool FRecvPending; + TRecvStatus FRecvStatus; + TRecvLast FRecvLast; + TPendingBuffer TxBuffer; + TPendingBuffer RxBuffer; + longword FSendElapsed; + bool BindError; + byte NextByte; + pfn_ParBRecvCallBack OnBRecv; + pfn_ParBSendCompletion OnBSend; + void ClearRecv(); + byte GetNextByte(); + void CloseWorker(); + bool BlockSend(); + bool PickData(); + bool BlockRecv(); + bool ConnectionConfirm(); +protected: + bool Stopping; + bool Execute(); + void Disconnect(); + bool ConnectToPeer(); + bool PerformFunctionNegotiate(); +public: + bool Active; + bool Running; + longword PeerAddress; + longword SrcAddress; + int BRecvTimeout; + int BSendTimeout; + longword SendTime; + longword RecvTime; + longword RecoveryTime; + longword KeepAliveTime; + longword BytesSent; + longword BytesRecv; + longword SendErrors; + longword RecvErrors; + // The partner is linked when the init sequence is terminated + //(TCP connection + ISO connection + PDU Length negotiation) + bool Linked; + TSnap7Partner(bool CreateActive); + ~TSnap7Partner(); + // Control + int Start(); + int StartTo(const char *LocAddress, const char *RemAddress, word LocTsap, word RemTsap); + int Stop(); + int Status(); + int GetParam(int ParamNumber, void * pValue); + int SetParam(int ParamNumber, void * pValue); + // Block send + int BSend(longword R_ID, void *pUsrData, int Size); + int AsBSend(longword R_ID, void *pUsrData, int Size); + bool CheckAsBSendCompletion(int &opResult); + int WaitAsBSendCompletion(longword Timeout); + int SetSendCallback(pfn_ParBSendCompletion pCompletion, void *usrPtr); + // Block recv + int BRecv(longword &R_ID, void *pData, int &Size, longword Timeout); + bool CheckAsBRecvCompletion(int &opResult, longword &R_ID, + void *pData, int &Size); + int SetRecvCallback(pfn_ParBRecvCallBack pCompletion, void *usrPtr); + + friend class TConnectionServer; + friend class TPartnerThread; +}; + + +#endif // s7_partner_h diff --git a/core/src/drivers/plugins/native/s7comm/snap7/core/s7_peer.cpp b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_peer.cpp new file mode 100644 index 00000000..5bf328a6 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_peer.cpp @@ -0,0 +1,122 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 Davide Nardella | +| All rights reserved. | +|==============================================================================| +| SNAP7 is free software: you can redistribute it and/or modify | +| it under the terms of the Lesser GNU General Public License as published by | +| the Free Software Foundation, either version 3 of the License, or | +| (at your option) any later version. | +| | +| It means that you can distribute your commercial software linked with | +| SNAP7 without the requirement to distribute the source code of your | +| application and without the requirement that your application be itself | +| distributed under LGPL. | +| | +| SNAP7 is distributed in the hope that it will be useful, | +| but WITHOUT ANY WARRANTY; without even the implied warranty of | +| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | +| Lesser GNU General Public License for more details. | +| | +| You should have received a copy of the GNU General Public License and a | +| copy of Lesser GNU General Public License along with Snap7. | +| If not, see http://www.gnu.org/licenses/ | +|=============================================================================*/ +#include "s7_peer.h" +//--------------------------------------------------------------------------- + +TSnap7Peer::TSnap7Peer() +{ + PDUH_out=PS7ReqHeader(&PDU.Payload); + PDURequest=480; // Our request, FPDULength will contain the CPU answer + LastError=0; + cntword = 0; + Destroying = false; +} +//--------------------------------------------------------------------------- +TSnap7Peer::~TSnap7Peer() +{ + Destroying = true; +} +//--------------------------------------------------------------------------- +int TSnap7Peer::SetError(int Error) +{ + if (Error==0) + ClrError(); + else + LastError=Error | LastIsoError | LastTcpError; + return Error; +} +//--------------------------------------------------------------------------- +void TSnap7Peer::ClrError() +{ + LastError=0; + LastIsoError=0; + LastTcpError=0; +} +//--------------------------------------------------------------------------- +word TSnap7Peer::GetNextWord() +{ + if (cntword==0xFFFF) + cntword=0; + return cntword++; +} +//--------------------------------------------------------------------------- +int TSnap7Peer::NegotiatePDULength( ) +{ + int Result, IsoSize = 0; + PReqFunNegotiateParams ReqNegotiate; + PResFunNegotiateParams ResNegotiate; + PS7ResHeader23 Answer; + ClrError(); + // Setup Pointers + ReqNegotiate = PReqFunNegotiateParams(pbyte(PDUH_out) + sizeof(TS7ReqHeader)); + // Header + PDUH_out->P = 0x32; // Always $32 + PDUH_out->PDUType = PduType_request; // $01 + PDUH_out->AB_EX = 0x0000; // Always $0000 + PDUH_out->Sequence = GetNextWord(); // AutoInc + PDUH_out->ParLen = SwapWord(sizeof(TReqFunNegotiateParams)); // 8 bytes + PDUH_out->DataLen = 0x0000; + // Params + ReqNegotiate->FunNegotiate = pduNegotiate; + ReqNegotiate->Unknown = 0x00; + ReqNegotiate->ParallelJobs_1 = 0x0100; + ReqNegotiate->ParallelJobs_2 = 0x0100; + ReqNegotiate->PDULength = SwapWord(PDURequest); + IsoSize = sizeof( TS7ReqHeader ) + sizeof( TReqFunNegotiateParams ); + Result = isoExchangeBuffer(NULL, IsoSize); + if ((Result == 0) && (IsoSize == int(sizeof(TS7ResHeader23) + sizeof(TResFunNegotiateParams)))) + { + // Setup pointers + Answer = PS7ResHeader23(&PDU.Payload); + ResNegotiate = PResFunNegotiateParams(pbyte(Answer) + sizeof(TS7ResHeader23)); + if ( Answer->Error != 0 ) + Result = SetError(errNegotiatingPDU); + if ( Result == 0 ) + PDULength = SwapWord(ResNegotiate->PDULength); + } + return Result; +} +//--------------------------------------------------------------------------- +void TSnap7Peer::PeerDisconnect( ) +{ + ClrError(); + isoDisconnect(true); +} +//--------------------------------------------------------------------------- +int TSnap7Peer::PeerConnect( ) +{ + int Result; + + ClrError(); + Result = isoConnect(); + if (Result == 0) + { + Result = NegotiatePDULength(); + if (Result != 0) + PeerDisconnect(); + } + return Result; +} diff --git a/core/src/drivers/plugins/native/s7comm/snap7/core/s7_peer.h b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_peer.h new file mode 100644 index 00000000..cdc56834 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_peer.h @@ -0,0 +1,58 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 Davide Nardella | +| All rights reserved. | +|==============================================================================| +| SNAP7 is free software: you can redistribute it and/or modify | +| it under the terms of the Lesser GNU General Public License as published by | +| the Free Software Foundation, either version 3 of the License, or | +| (at your option) any later version. | +| | +| It means that you can distribute your commercial software linked with | +| SNAP7 without the requirement to distribute the source code of your | +| application and without the requirement that your application be itself | +| distributed under LGPL. | +| | +| SNAP7 is distributed in the hope that it will be useful, | +| but WITHOUT ANY WARRANTY; without even the implied warranty of | +| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | +| Lesser GNU General Public License for more details. | +| | +| You should have received a copy of the GNU General Public License and a | +| copy of Lesser GNU General Public License along with Snap7. | +| If not, see http://www.gnu.org/licenses/ | +|=============================================================================*/ +#ifndef s7_peer_h +#define s7_peer_h +//--------------------------------------------------------------------------- +#include "s7_types.h" +#include "s7_isotcp.h" +//--------------------------------------------------------------------------- + +const longword errPeerMask = 0xFFF00000; +const longword errPeerBase = 0x000FFFFF; +const longword errNegotiatingPDU = 0x00100000; + +class TSnap7Peer: public TIsoTcpSocket +{ +private: + word cntword; +protected: + bool Destroying; + PS7ReqHeader PDUH_out; + word GetNextWord(); + int SetError(int Error); + int NegotiatePDULength(); + void ClrError(); +public: + int LastError; + int PDULength; + int PDURequest; + TSnap7Peer(); + ~TSnap7Peer(); + void PeerDisconnect(); + int PeerConnect(); +}; +//--------------------------------------------------------------------------- +#endif diff --git a/core/src/drivers/plugins/native/s7comm/snap7/core/s7_server.cpp b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_server.cpp new file mode 100644 index 00000000..c5c2cda1 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_server.cpp @@ -0,0 +1,2158 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 Davide Nardella | +| All rights reserved. | +|==============================================================================| +| SNAP7 is free software: you can redistribute it and/or modify | +| it under the terms of the Lesser GNU General Public License as published by | +| the Free Software Foundation, either version 3 of the License, or | +| (at your option) any later version. | +| | +| It means that you can distribute your commercial software linked with | +| SNAP7 without the requirement to distribute the source code of your | +| application and without the requirement that your application be itself | +| distributed under LGPL. | +| | +| SNAP7 is distributed in the hope that it will be useful, | +| but WITHOUT ANY WARRANTY; without even the implied warranty of | +| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | +| Lesser GNU General Public License for more details. | +| | +| You should have received a copy of the GNU General Public License and a | +| copy of Lesser GNU General Public License along with Snap7. | +| If not, see http://www.gnu.org/licenses/ | +|=============================================================================*/ +#include "s7_server.h" +#include "s7_firmware.h" + +const byte BitMask[8] = {0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80}; + +//------------------------------------------------------------------------------ +// ISO/TCP WORKER CLASS +//------------------------------------------------------------------------------ +bool TIsoTcpWorker::IsoPerformCommand(int &Size) +{ + return true; +} +//--------------------------------------------------------------------------- +bool TIsoTcpWorker::ExecuteSend() +{ + return true; +} +//--------------------------------------------------------------------------- +bool TIsoTcpWorker::ExecuteRecv() +{ + TPDUKind PduKind; + int PayloadSize; + + if (CanRead(WorkInterval)) // should be Small to avoid time wait during the close + { + isoRecvPDU(&PDU); + if (LastTcpError==0) + { + IsoPeek(&PDU,PduKind); + // First check valid data incoming (most likely situation) + if (PduKind==pkValidData) + { + PayloadSize=PDUSize(&PDU)-DataHeaderSize; + return IsoPerformCommand(PayloadSize); + }; + // Connection request incoming + if (PduKind==pkConnectionRequest) + { + IsoConfirmConnection(pdu_type_CC); // <- Connection confirm + return LastTcpError!=WSAECONNRESET; + }; + // Disconnect request incoming (only for isotcp full complient equipment, not S7) + if (PduKind==pkDisconnectRequest) + { + IsoConfirmConnection(pdu_type_DC); // <- Disconnect confirm + return false; + }; + // Empty fragment, maybe an ACK + if (PduKind==pkEmptyFragment) + { + PayloadSize=0; + return IsoPerformCommand(PayloadSize); + }; + // Valid PDU format but we have to discard it + if (PduKind==pkUnrecognizedType) + { + return LastTcpError!=WSAECONNRESET; + }; + // Here we have an Invalid PDU + Purge(); + return true; + } + else + return LastTcpError!=WSAECONNRESET; + } + else + return true; +} +//--------------------------------------------------------------------------- +bool TIsoTcpWorker::Execute() +{ + return ExecuteSend() && ExecuteRecv(); +} +//------------------------------------------------------------------------------ +// S7 WORKER CLASS +//------------------------------------------------------------------------------ +TS7Worker::TS7Worker() +{ + // We skip RFC/ISO header, our PDU is the payload + PDUH_in =PS7ReqHeader(&PDU.Payload); + FPDULength=2048; + DBCnt =0; + LastBlk =Block_DB; +} + +bool TS7Worker::ExecuteRecv() +{ + WorkInterval=FServer->WorkInterval; + return TIsoTcpWorker::ExecuteRecv(); +} +//------------------------------------------------------------------------------ +bool TS7Worker::CheckPDU_in(int PayloadSize) +{ + // Checks the size : packet size must match with header infos + int Size=SwapWord(PDUH_in->ParLen)+SwapWord(PDUH_in->DataLen)+ReqHeaderSize; + if (Size!=PayloadSize) + return false; + // Checks PDUType : must be 1 or 7 + if ((PDUH_in->PDUType!=PduType_request) && + (PDUH_in->PDUType!=PduType_userdata)) + return false; + else + return true; +} +//------------------------------------------------------------------------------ +byte TS7Worker::BCD(word Value) +{ + return ((Value / 10) << 4) + (Value % 10); +} +//------------------------------------------------------------------------------ +void TS7Worker::FillTime(PS7Time PTime) +{ + time_t Now; + time(&Now); + struct tm *DT = localtime (&Now); + + PTime->bcd_year=BCD(DT->tm_year-100); + PTime->bcd_mon =BCD(DT->tm_mon+1); + PTime->bcd_day =BCD(DT->tm_mday); + PTime->bcd_hour=BCD(DT->tm_hour); + PTime->bcd_min =BCD(DT->tm_min); + PTime->bcd_sec =BCD(DT->tm_sec); + PTime->bcd_himsec=0; + PTime->bcd_dow =BCD(DT->tm_wday); +} +//------------------------------------------------------------------------------ +void TS7Worker::DoEvent(longword Code, word RetCode, word Param1, word Param2, + word Param3, word Param4) +{ + FServer->DoEvent(ClientHandle,Code,RetCode,Param1,Param2,Param3,Param4); +} +//------------------------------------------------------------------------------ +void TS7Worker::DoReadEvent(longword Code, word RetCode, word Param1, word Param2, + word Param3, word Param4) +{ + FServer->DoReadEvent(ClientHandle,Code,RetCode,Param1,Param2,Param3,Param4); +} +//------------------------------------------------------------------------------ +void TS7Worker::FragmentSkipped(int Size) +{ +// do nothing could be used for debug purpose +} +//------------------------------------------------------------------------------ +bool TS7Worker::IsoPerformCommand(int &Size) +{ + // Checks for Ack fragment + if (Size==0) + return PerformPDUAck(Size); + // First checks PDU consistence + if (CheckPDU_in(Size)) + { + switch (PDUH_in->PDUType) + { + case PduType_request : return PerformPDURequest(Size); + case PduType_userdata : return PerformPDUUsrData(Size); + } + } + else + DoEvent(evcPDUincoming, evrMalformedPDU, Size, 0, 0, 0); + return false; +} +//------------------------------------------------------------------------------ +bool TS7Worker::PerformPDUAck(int &Size) +{ + // here we could keep track of ack empty fragment for debug purpose + return true; +} +//------------------------------------------------------------------------------ +bool TS7Worker::PerformPDURequest(int &Size) +{ + pbyte P; + byte PDUFun; + bool Result = true; + + // We have to store PDUfun since it will be overwritten + P = pbyte(PDUH_in)+ReqHeaderSize; + PDUFun=*P; + // Watches the function + switch (PDUFun) + { + case pduFuncRead : Result=PerformFunctionRead(); + break; + case pduFuncWrite : Result=PerformFunctionWrite(); + break; + case pduNegotiate : Result=PerformFunctionNegotiate(); + break; + case pduStart : + case pduStop : Result=PerformFunctionControl(PDUFun); + break; + case pduStartUpload : + case pduUpload : + case pduEndUpload : Result=PerformFunctionUpload(); + break; + case pduReqDownload : Result=PerformFunctionDownload(); + break; + // <-- Further (custom) functions can be added here + default: + DoEvent(evcPDUincoming, evrCannotHandlePDU, Size, 0, 0, 0); + }; + return Result; +} +//------------------------------------------------------------------------------ +bool TS7Worker::PerformPDUUsrData(int &Size) +{ + PS7ReqParams7 ReqParams; + byte Tg, SubFun; + bool Result = true; + // Set Pointer to request params + ReqParams=PS7ReqParams7(pbyte(PDUH_in)+ReqHeaderSize); + Tg=ReqParams->Tg; + SubFun=ReqParams->SubFun; + // Switch type_group + switch (Tg) + { + case grProgrammer : Result=PerformGroupProgrammer(); + break; + case grCyclicData : Result=PerformGroupCyclicData(); + break; + case grBlocksInfo : Result=PerformGroupBlockInfo(); + break; + case grSZL : Result=PerformGroupSZL(); + break; + case grPassword : Result=PerformGroupSecurity(); + break; + case grClock : switch (SubFun) + { + case 0x01 : Result=PerformGetClock(); + break; + case 0x02 : Result=PerformSetClock(); + break; + }; + break; + default: + DoEvent(evcPDUincoming, evrInvalidGroupUData, Tg, 0, 0, 0); + }; + return Result; +} +//------------------------------------------------------------------------------ +int TS7Worker::DataSizeByte(int WordLength) +{ + switch (WordLength){ + case S7WLBit : return 1; // S7 sends 1 byte per bit + case S7WLByte : return 1; + case S7WLChar : return 1; + case S7WLWord : return 2; + case S7WLDWord : return 4; + case S7WLInt : return 2; + case S7WLDInt : return 4; + case S7WLReal : return 4; + case S7WLCounter : return 2; + case S7WLTimer : return 2; + default : return 0; + } +} +//============================================================================== +// FUNCTION READ +//============================================================================== +word TS7Worker::RA_NotFound(PResFunReadItem ResItem, TEv &EV) +{ + ResItem->DataLength=SwapWord(0x0004); + ResItem->ReturnCode=Code7ResItemNotAvailable; + ResItem->TransportSize=0x00; + EV.EvRetCode=evrErrAreaNotFound; + return 0; +} +//------------------------------------------------------------------------------ +word TS7Worker::RA_OutOfRange(PResFunReadItem ResItem, TEv &EV) +{ + ResItem->DataLength=SwapWord(0x0004); + ResItem->ReturnCode=Code7AddressOutOfRange; + ResItem->TransportSize=0x00; + EV.EvRetCode=evrErrOutOfRange; + return 0; +} +//------------------------------------------------------------------------------ +word TS7Worker::RA_SizeOverPDU(PResFunReadItem ResItem, TEv &EV) +{ + ResItem->DataLength=SwapWord(0x0004); + ResItem->ReturnCode=byte(SwapWord(Code7DataOverPDU)); + ResItem->TransportSize=0x00; + EV.EvRetCode=evrErrOverPDU; + return 0; +} +//------------------------------------------------------------------------------ +PS7Area TS7Worker::GetArea(byte S7Code, word index) +{ + switch(S7Code) + { + case S7AreaPE : return FServer->HA[srvAreaPE]; + case S7AreaPA : return FServer->HA[srvAreaPA]; + case S7AreaMK : return FServer->HA[srvAreaMK]; + case S7AreaCT : return FServer->HA[srvAreaCT]; + case S7AreaTM : return FServer->HA[srvAreaTM]; + case S7AreaDB : return FServer->FindDB(index); + default : return NULL; + }; +} +//------------------------------------------------------------------------------ +word TS7Worker::ReadArea(PResFunReadItem ResItemData, PReqFunReadItem ReqItemPar, + int &PDURemainder, TEv &EV) +{ + PS7Area P; + word DBNum = 0; + word Elements; + longword Start, Size, ASize, AStart; + longword *PAdd; + byte BitIndex, ByteVal; + int Multiplier; + void *Source = NULL; + PSnapCriticalSection pcs; + + P=NULL; + EV.EvStart =0; + EV.EvSize =0; + EV.EvRetCode =0; + EV.EvIndex =0; + + EV.EvArea=ReqItemPar->Area; + // Get Pointer to selected Area + + if (ReqItemPar->Area==S7AreaDB) + { + DBNum=SwapWord(ReqItemPar->DBNumber); + EV.EvIndex=DBNum; + }; + + if (!FServer->ResourceLess) + { + P = GetArea(ReqItemPar->Area, DBNum); + if (P == NULL) + return RA_NotFound(ResItemData, EV); + } + + // Calcs the amount + Multiplier = DataSizeByte(ReqItemPar->TransportSize); + if (Multiplier==0) + return RA_OutOfRange(ResItemData, EV); + + // Checks timers/counters coherence + if ((ReqItemPar->Area==S7AreaTM) ^ (ReqItemPar->TransportSize==S7WLTimer)) + return RA_OutOfRange(ResItemData, EV); + + if ((ReqItemPar->Area==S7AreaCT) ^ (ReqItemPar->TransportSize==S7WLCounter)) + return RA_OutOfRange(ResItemData, EV); + + // Calcs size + Elements = SwapWord(ReqItemPar->Length); + Size=Multiplier*Elements; + EV.EvSize=Size; + + // The sum of the items must not exceed the PDU size negotiated + if (PDURemainder-Size<=0) + return RA_SizeOverPDU(ResItemData, EV); + else + PDURemainder-=Size; + + // More then 1 bit is not supported by S7 CPU + if ((ReqItemPar->TransportSize==S7WLBit) && (Size>1)) + return RA_OutOfRange(ResItemData, EV); + + // Calcs the start point + PAdd=(longword*)(&ReqItemPar->Area); // points to area since we need 4 bytes for a pointer + Start=SwapDWord(*PAdd & 0xFFFFFF00); + + // Checks if the address is not multiple of 8 when transport size is neither bit nor timer nor counter + if ( + (ReqItemPar->TransportSize!=S7WLBit) && + (ReqItemPar->TransportSize!=S7WLTimer) && + (ReqItemPar->TransportSize!=S7WLCounter) && + ((Start % 8) !=0) + ) + return RA_OutOfRange(ResItemData, EV); + + // AStart is only for callback + if ((ReqItemPar->TransportSize != S7WLBit) && (ReqItemPar->TransportSize != S7WLCounter) && (ReqItemPar->TransportSize != S7WLTimer)) + AStart = Start >> 3; + else + AStart = Start; + + if ((ReqItemPar->TransportSize == S7WLCounter) || (ReqItemPar->TransportSize == S7WLTimer)) + { + Start = Start >> 1; // 1 Timer or Counter = 2 bytes + } + else + { + BitIndex =Start & 0x07; // start bit + Start =Start >> 3; // start byte + } + + EV.EvStart=Start; + + // Checks bounds + if (!FServer->ResourceLess) + { + ASize = P->Size; // Area size + if (Start + Size > ASize) + return RA_OutOfRange(ResItemData, EV); + Source = P->PData + Start; + } + + // Read Event (before copy data) + DoReadEvent(evcDataRead,0,EV.EvArea,EV.EvIndex,EV.EvStart,EV.EvSize); + + if (FServer->ResourceLess) + { + memset(&ResItemData->Data, 0, Size); + if (!FServer->DoReadArea(ClientHandle, EV.EvArea, EV.EvIndex, AStart, Elements, ReqItemPar->TransportSize, &ResItemData->Data)) + return RA_NotFound(ResItemData, EV); + } + else + { + // Lock the area + pcs = P->cs; + pcs->Enter(); + // Get Data + memcpy(&ResItemData->Data, Source, Size); + // Unlock the area + pcs->Leave(); + } + + ResItemData->ReturnCode=0xFF; + // Set Result transport size and, for bit, performs the mask + switch (ReqItemPar->TransportSize) + { + case S7WLBit: + { + ByteVal=ResItemData->Data[0]; + + if ((ByteVal & BitMask[BitIndex])!=0) + ResItemData->Data[0]=0x01; + else + ResItemData->Data[0]=0x00; + + ResItemData->TransportSize=TS_ResBit; + ResItemData->DataLength=SwapWord(Size); + };break; + case S7WLByte: + case S7WLWord: + case S7WLDWord: + { + ResItemData->TransportSize=TS_ResByte; + ResItemData->DataLength=SwapWord(Size*8); + };break; + case S7WLInt: + case S7WLDInt: + { + ResItemData->TransportSize=TS_ResInt; + ResItemData->DataLength=SwapWord(Size*8); + };break; + case S7WLReal: + { + ResItemData->TransportSize=TS_ResReal; + ResItemData->DataLength=SwapWord(Size); + };break; + case S7WLChar: + case S7WLTimer: + case S7WLCounter: + { + ResItemData->TransportSize=TS_ResOctet; + ResItemData->DataLength=SwapWord(Size); + };break; + default : + { + ResItemData->TransportSize=TS_ResByte; + ResItemData->DataLength=SwapWord(Size*8); + };break; + } + EV.EvRetCode=evrNoError; + return Size; +} +//------------------------------------------------------------------------------ +bool TS7Worker::PerformFunctionRead() +{ + PReqFunReadParams ReqParams; + PResFunReadParams ResParams; + TResFunReadData ResData; + TS7Answer23 Answer; + uintptr_t Offset; + word ItemSize; + int ItemsCount, c, + TotalSize, + PDURemainder; + TEv EV; + + PDURemainder=FPDULength; + // Stage 1 : Setup pointers and initial check + ReqParams=PReqFunReadParams(pbyte(PDUH_in)+sizeof(TS7ReqHeader)); + ResParams=PResFunReadParams(pbyte(&Answer)+ResHeaderSize23); // Params after the header + + // trunk to 20 max items. + if (ReqParams->ItemsCount>MaxVars) + ReqParams->ItemsCount=MaxVars; + + ItemsCount=ReqParams->ItemsCount; + + // Stage 2 : gather data + Offset=sizeof(TResFunReadParams); // = 2 + + for (c = 0; c < ItemsCount; c++) + { + ResData[c]=PResFunReadItem(pbyte(ResParams)+Offset); + ItemSize=ReadArea(ResData[c],&ReqParams->Items[c],PDURemainder, EV); + + // S7 doesn't xfer odd byte amount + if ((c1) + DoEvent(evcDataRead,EV.EvRetCode,EV.EvArea,EV.EvIndex,EV.EvStart,EV.EvSize); + } + // Stage 3 : finalize the answer and send the packet + Answer.Header.P=0x32; + Answer.Header.PDUType=0x03; + Answer.Header.AB_EX=0x0000; + Answer.Header.Sequence=PDUH_in->Sequence; + Answer.Header.ParLen=SwapWord(sizeof(TResFunReadParams)); + Answer.Header.Error=0x0000; // this is zero, we will find the error in ResData.ReturnCode + Answer.Header.DataLen=SwapWord(word(Offset)-2); + + ResParams->FunRead =ReqParams->FunRead; + ResParams->ItemCount=ReqParams->ItemsCount; + + TotalSize=ResHeaderSize23+int(Offset); + isoSendBuffer(&Answer, TotalSize); + + // For single item (most likely case) it's better to work with the event after + // we sent the answer + if (ItemsCount==1) + DoEvent(evcDataRead,EV.EvRetCode,EV.EvArea,EV.EvIndex,EV.EvStart,EV.EvSize); + + return true; +} +//============================================================================== +// FUNCTION WRITE +//============================================================================== +byte TS7Worker::WA_NotFound(TEv &EV) +{ + EV.EvRetCode=evrErrAreaNotFound; + return Code7ResItemNotAvailable; +} +//------------------------------------------------------------------------------ +byte TS7Worker::WA_InvalidTransportSize(TEv &EV) +{ + EV.EvRetCode=evrErrTransportSize; + return Code7InvalidTransportSize; +} +//------------------------------------------------------------------------------ +byte TS7Worker::WA_OutOfRange(TEv &EV) +{ + EV.EvRetCode=evrErrOutOfRange; + return Code7AddressOutOfRange; +} +//------------------------------------------------------------------------------ +byte TS7Worker::WA_DataSizeMismatch(TEv &EV) +{ + EV.EvRetCode=evrDataSizeMismatch; + return Code7WriteDataSizeMismatch; +} +//------------------------------------------------------------------------------ +byte TS7Worker::WriteArea(PReqFunWriteDataItem ReqItemData, PReqFunWriteItem ReqItemPar, + TEv &EV) +{ + int Multiplier; + PS7Area P = NULL; + word DBNum = 0; + word Elements; + longword *PAdd; + PSnapCriticalSection pcs; + longword Start, Size, ASize, DataLen, AStart; + pbyte Target = NULL; + byte BitIndex; + + EV.EvStart =0; + EV.EvSize =0; + EV.EvRetCode =evrNoError; + EV.EvIndex =0; + + EV.EvArea=ReqItemPar->Area; + // Get Pointer to selected Area + if (ReqItemPar->Area==S7AreaDB) + { + DBNum=SwapWord(ReqItemPar->DBNumber); + EV.EvIndex=DBNum; + }; + + if (!FServer->ResourceLess) + { + P=GetArea(ReqItemPar->Area, DBNum); + if (P==NULL) + return WA_NotFound(EV); + } + + // Calcs the amount + Multiplier = DataSizeByte(ReqItemPar->TransportSize); + if (Multiplier==0) + return WA_InvalidTransportSize(EV); + + // Checks timers/counters coherence + if ((ReqItemPar->Area==S7AreaTM) ^ (ReqItemPar->TransportSize==S7WLTimer)) + return WA_OutOfRange(EV); + + if ((ReqItemPar->Area==S7AreaCT) ^ (ReqItemPar->TransportSize==S7WLCounter)) + return WA_OutOfRange(EV); + + // Calcs size + Elements = SwapWord(ReqItemPar->Length); + Size = Multiplier*Elements; + EV.EvSize=Size; + + // More) 1 bit is not supported by S7 CPU + if ((ReqItemPar->TransportSize==S7WLBit) && (Size>1)) + return WA_OutOfRange(EV); + + // Calcs the start point ?? + PAdd=(longword*)&ReqItemPar->Area; // points to area since we need 4 bytes for a pointer + Start=SwapDWord(*PAdd & 0xFFFFFF00); + + // Checks if the address is not multiple of 8 when transport size is neither bit nor timer nor counter + if ( + (ReqItemPar->TransportSize!=S7WLBit) && + (ReqItemPar->TransportSize!=S7WLTimer) && + (ReqItemPar->TransportSize!=S7WLCounter) && + ((Start % 8) !=0) + ) + return WA_OutOfRange(EV); + + // AStart is only for callback + if ((ReqItemPar->TransportSize != S7WLBit) && (ReqItemPar->TransportSize != S7WLCounter) && (ReqItemPar->TransportSize != S7WLTimer)) + AStart = Start >> 3; + else + AStart = Start; + + if ((ReqItemPar->TransportSize == S7WLCounter) || (ReqItemPar->TransportSize == S7WLTimer)) + { + Start = Start >> 1; // 1 Timer or Counter = 2 bytes + } + else + { + BitIndex = Start & 0x07; // start bit + Start = Start >> 3; // start byte + } + EV.EvStart =Start; + + if (!FServer->ResourceLess) + { + // Checks bounds + ASize = P->Size; // Area size + if (Start + Size > ASize) + return WA_OutOfRange(EV); + Target = pbyte(P->PData + Start); + } + // Checks data size coherence + DataLen=SwapWord(ReqItemData->DataLength); + + if ((ReqItemData->TransportSize!=TS_ResOctet) && (ReqItemData->TransportSize!=TS_ResReal) && (ReqItemData->TransportSize!=TS_ResBit)) + DataLen=DataLen / 8; + + if (DataLen!=Size) + return WA_DataSizeMismatch(EV); + + if (FServer->ResourceLess) + { + if (!FServer->DoWriteArea(ClientHandle, EV.EvArea, EV.EvIndex, AStart, Elements, ReqItemPar->TransportSize, &ReqItemData->Data[0])) + return WA_NotFound(EV); + } + else + { + if (ReqItemPar->TransportSize==S7WLBit) + { + if ((ReqItemData->Data[0] & 0x01) != 0) // bit set + *Target=*Target | BitMask[BitIndex]; + else // bit reset + *Target=*Target & (~BitMask[BitIndex]); + } + else { + // Lock the area + pcs = P->cs; + pcs->Enter(); + // Write Data + memcpy(Target, &ReqItemData->Data[0], Size); + pcs->Leave(); + }; + } + + return 0xFF; +} +//------------------------------------------------------------------------------ +bool TS7Worker::PerformFunctionWrite() +{ + PReqFunWriteParams ReqParams; + TReqFunWriteData ReqData; + PResFunWrite ResData; + TS7Answer23 Answer; + int L; + + uintptr_t StartData; + int c, ItemsCount; + int ResDSize; + TEv EV; + + // Stage 1 : Setup pointers and initial check + ReqParams=PReqFunWriteParams(pbyte(PDUH_in)+sizeof(TS7ReqHeader)); + ResData =PResFunWrite(pbyte(&Answer)+ResHeaderSize23); + + StartData=sizeof(TS7ReqHeader)+SwapWord(PDUH_in->ParLen); + + ItemsCount=ReqParams->ItemsCount; + ResDSize =ResHeaderSize23+2+ItemsCount; + for (c = 0; c < ItemsCount; c++) + { + ReqData[c]=PReqFunWriteDataItem(pbyte(PDUH_in)+StartData); + + if ((ReqParams->Items[c].TransportSize == S7WLTimer) || (ReqParams->Items[c].TransportSize == S7WLCounter) || (ReqParams->Items[c].TransportSize == S7WLBit)) + L = SwapWord(ReqData[c]->DataLength); + else + L = (SwapWord(ReqData[c]->DataLength) / 8); + + StartData+=L+4; + // the datalength is always even + if ( L % 2 != 0) StartData++; + } + + ResData->FunWrite =pduFuncWrite; + ResData->ItemCount=ReqParams->ItemsCount; + + // Stage 2 : Write data + for (c = 0; c < ItemsCount; c++) + { + ResData->Data[c]=WriteArea(ReqData[c],&ReqParams->Items[c], EV); + // For multiple items we have to create multiple events + if (ItemsCount>1) + DoEvent(evcDataWrite,EV.EvRetCode,EV.EvArea,EV.EvIndex,EV.EvStart,EV.EvSize); + } + + // Stage 3 : finalize the answer + Answer.Header.P=0x32; + Answer.Header.PDUType=0x03; + Answer.Header.AB_EX=0x0000; + Answer.Header.Sequence=PDUH_in->Sequence; + Answer.Header.ParLen=SwapWord(0x02); + Answer.Header.Error=0x0000; // this is zero, we will find the error in ResData.ReturnCode if any + Answer.Header.DataLen=SwapWord(ItemsCount); + + isoSendBuffer(&Answer,ResDSize); + // For single item (most likely case) it's better to fire the event after + // we sent the answer + if (ItemsCount==1) + DoEvent(evcDataWrite,EV.EvRetCode,EV.EvArea,EV.EvIndex,EV.EvStart,EV.EvSize); + return true; +} +//============================================================================== +// FUNCTION NEGOTIATE +//============================================================================== +bool TS7Worker::PerformFunctionNegotiate() +{ + PReqFunNegotiateParams ReqParams; + PResFunNegotiateParams ResParams; + word ReqLen; + TS7Answer23 Answer; + int Size; + + // Setup pointers + ReqParams=PReqFunNegotiateParams(pbyte(PDUH_in)+sizeof(TS7ReqHeader)); + ResParams=PResFunNegotiateParams(pbyte(&Answer)+sizeof(TS7ResHeader23)); + // Prepares the answer + Answer.Header.P=0x32; + Answer.Header.PDUType=0x03; + Answer.Header.AB_EX=0x0000; + Answer.Header.Sequence=PDUH_in->Sequence; + Answer.Header.ParLen=SwapWord(sizeof(TResFunNegotiateParams)); + Answer.Header.DataLen=0x0000; + Answer.Header.Error=0x0000; + // Params point at the end of the header + ResParams->FunNegotiate=pduNegotiate; + ResParams->Unknown=0x0; + // We offer the same + ResParams->ParallelJobs_1=ReqParams->ParallelJobs_1; + ResParams->ParallelJobs_2=ReqParams->ParallelJobs_2; + + if (FServer->ForcePDU == 0) + { + ReqLen = SwapWord(ReqParams->PDULength); + if (ReqLenPDULength = SwapWord(MinPduSize); + else + if (ReqLen>IsoPayload_Size) + ResParams->PDULength = SwapWord(IsoPayload_Size); + else + ResParams->PDULength = ReqParams->PDULength; + } + else + ResParams->PDULength = SwapWord(FServer->ForcePDU); + + FPDULength=SwapWord(ResParams->PDULength); // Stores the value + // Sends the answer + Size=sizeof(TS7ResHeader23) + sizeof(TResFunNegotiateParams); + isoSendBuffer(&Answer, Size); + // Store the event + DoEvent(evcNegotiatePDU, evrNoError, FPDULength, 0, 0, 0); + return true; +} +//============================================================================== +// FUNCTION CONTROL +//============================================================================== +bool TS7Worker::PerformFunctionControl(byte PduFun) +{ + TS7Answer23 Answer; + PResFunCtrl ResParams; + word ParLen; + word CtrlCode; + + // Setup pointer + ResParams=PResFunCtrl(pbyte(&Answer)+sizeof(TS7ResHeader23)); + // Prepares the answer + Answer.Header.P=0x32; + Answer.Header.PDUType=0x03; + Answer.Header.AB_EX=0x0000; + Answer.Header.Sequence=PDUH_in->Sequence; + Answer.Header.ParLen=SwapWord(0x0001); // We send only Res fun without para + Answer.Header.DataLen=0x0000; + Answer.Header.Error=0x0000; + ResParams->ResFun=PduFun; + ResParams->para =0x00; + + ParLen=SwapWord(PDUH_in->ParLen); + if (PduFun==pduStop) + CtrlCode=CodeControlStop; + else + { + switch (ParLen) + { + case 16 : CtrlCode=CodeControlCompress; break; + case 18 : CtrlCode=CodeControlCpyRamRom; break; + case 20 : CtrlCode=CodeControlWarmStart; break; + case 22 : CtrlCode=CodeControlColdStart; break; + case 26 : CtrlCode=CodeControlInsDel; break; + default : CtrlCode=CodeControlUnknown; + } + } + // Sends the answer + isoSendBuffer(&Answer,sizeof(TS7ResHeader23)+1); + // Stores the event + DoEvent(evcControl,0,CtrlCode,0,0,0); + + if ((CtrlCode==CodeControlWarmStart) || (CtrlCode==CodeControlColdStart)) + FServer->CpuStatus=S7CpuStatusRun; + + if (CtrlCode==CodeControlStop) + FServer->CpuStatus=S7CpuStatusStop; + + return true; +} +//============================================================================== +// FUNCTION UPLOAD +//============================================================================== +bool TS7Worker::PerformFunctionUpload() +{ + TS7Answer23 Answer; + + // Upload is not implemented, however to avoid that S7 manager hangs + // we simulate a cpu read/write protected. + // We can see the directory but can't upload/download anything + + // Prepares the answer + Answer.Header.P=0x32; + Answer.Header.PDUType =pduResponse; + Answer.Header.AB_EX=0x0000; + Answer.Header.Sequence=PDUH_in->Sequence; + Answer.Header.ParLen=0; + Answer.Header.DataLen=0; + Answer.Header.Error=SwapWord(Code7NeedPassword); + // Sends the answer + isoSendBuffer(&Answer,sizeof(TS7ResHeader23)); + + DoEvent(evcUpload,evrCannotUpload,evsStartUpload,0,0,0); + return true; +} +//============================================================================== +// FUNCTION DOWNLOAD +//============================================================================== +bool TS7Worker::PerformFunctionDownload() +{ + TS7Answer23 Answer; + + // Download is not implemented, however to avoid that S7 manager hangs + // we simulate a cpu read/write protected. + // We can see the directory but can't upload/download anything + + // Prepares the answer + Answer.Header.P=0x32; + Answer.Header.PDUType =pduResponse; + Answer.Header.AB_EX=0x0000; + Answer.Header.Sequence=PDUH_in->Sequence; + Answer.Header.ParLen=0; + Answer.Header.DataLen=0; + Answer.Header.Error=SwapWord(Code7NeedPassword); + // Sends the answer + isoSendBuffer(&Answer,sizeof(TS7ResHeader23)); + + DoEvent(evcUpload,evrCannotDownload, evsStartDownload,0,0,0); + return true; +} +//============================================================================== +// FUNCTIONS PROGRAMMER AND CYCLIC DATA (NOT IMPLEMENTED...yet) +//============================================================================== +bool TS7Worker::PerformGroupProgrammer() +{ + DoEvent(evcPDUincoming,evrNotImplemented,grProgrammer,0,0,0); + return true; +} +//------------------------------------------------------------------------------ +bool TS7Worker::PerformGroupCyclicData() +{ + DoEvent(evcPDUincoming,evrNotImplemented,grCyclicData,0,0,0); + return true; +} +//============================================================================== +// BLOCK(S) INFO FUNCTIONS +//============================================================================== +void TS7Worker::BLK_ListAll(TCB &CB) +{ + PDataFunListAll Data; + int TotalSize; + + TotalSize = ResHeaderSize17+sizeof(TResFunGetBlockInfo)+sizeof(TDataFunListAll); + // Prepares the answer + CB.Answer.Header.P=0x32; + CB.Answer.Header.PDUType=PduType_userdata; + CB.Answer.Header.AB_EX=0x0000; + CB.Answer.Header.Sequence=PDUH_in->Sequence; + CB.Answer.Header.ParLen =SwapWord(sizeof(TResFunGetBlockInfo)); + CB.Answer.Header.DataLen=SwapWord(sizeof(TDataFunListAll)); + + CB.ResParams->Head[0]=CB.ReqParams->Head[0]; + CB.ResParams->Head[1]=CB.ReqParams->Head[1]; + CB.ResParams->Head[2]=CB.ReqParams->Head[2]; + CB.ResParams->Plen =0x08; + CB.ResParams->Uk =0x12; + CB.ResParams->Tg =0x83; // Type response, group functions info + CB.ResParams->SubFun=SFun_ListAll; + + CB.ResParams->Seq =CB.ReqParams->Seq; + CB.ResParams->Rsvd =0x0000; + CB.ResParams->ErrNo =0x0000; + + Data=PDataFunListAll(pbyte(&CB.Answer)+ResHeaderSize17+sizeof(TResFunGetBlockInfo)); + Data->RetVal=0xFF; + Data->TRSize=TS_ResOctet; + Data->Length=SwapWord(28); // 28 = Size of TDataFunListAll.Blocks + // Fill elements, only DB will have a valid number + Data->Blocks[0].Zero=0x30; + Data->Blocks[0].BType=Block_OB; + Data->Blocks[0].BCount=0x0000; // We don't have OBs + Data->Blocks[1].Zero=0x30; + Data->Blocks[1].BType=Block_FB; + Data->Blocks[1].BCount=0x0000; // We don't have FBs + Data->Blocks[2].Zero=0x30; + Data->Blocks[2].BType=Block_FC; + Data->Blocks[2].BCount=0x0000; // We don't have FCs + Data->Blocks[3].Zero=0x30; + Data->Blocks[3].BType=Block_DB; + Data->Blocks[3].BCount=SwapWord(FServer->DBCount); // Most likely we HAVE DBs + Data->Blocks[4].Zero=0x30; + Data->Blocks[4].BType=Block_SDB; + Data->Blocks[4].BCount=0x0000; // We don't have SDBs + Data->Blocks[5].Zero=0x30; + Data->Blocks[5].BType=Block_SFC; + Data->Blocks[5].BCount=0x0000; // We don't have SFCs + Data->Blocks[6].Zero=0x30; + Data->Blocks[6].BType=Block_SFB; + Data->Blocks[6].BCount=0x0000; // We don't have SFBs + // Sends + isoSendBuffer(&CB.Answer,TotalSize); + DoEvent(evcDirectory, 0, evsGetBlockList, 0, 0, 0); +} +//------------------------------------------------------------------------------ +void TS7Worker::BLK_NoResource_ListBoT(PDataFunGetBot Data, TCB &CB) +{ + CB.DataLength =4; + DBCnt =0; // Reset counter + CB.Answer.Header.DataLen=SwapWord(CB.DataLength); + CB.ResParams->ErrNo =0x0ED2; // function in error + Data->RetVal =0x0A; // No resource available + Data->TSize =0x00; // No transport size; + Data->DataLen =0x0000; // No data; + CB.evError =evrResNotFound; +} +//------------------------------------------------------------------------------ +void TS7Worker::BLK_ListBoT(byte BlockType, bool Start, TCB &CB) +{ + PDataFunGetBot Data; + int MaxItems, TotalSize, cnt; + int HiBound = FServer->DBLimit+1; + + CB.evError=0; + MaxItems=(FPDULength - 32) / 4; + // Prepares the answer + CB.Answer.Header.P=0x32; + CB.Answer.Header.PDUType=PduType_userdata; + CB.Answer.Header.AB_EX=0x0000; + CB.Answer.Header.Sequence=PDUH_in->Sequence; + CB.Answer.Header.ParLen =SwapWord(sizeof(TResFunGetBlockInfo)); + + CB.ResParams->Head[0]=CB.ReqParams->Head[0]; + CB.ResParams->Head[1]=CB.ReqParams->Head[1]; + CB.ResParams->Head[2]=CB.ReqParams->Head[2]; + CB.ResParams->Plen =0x08; + CB.ResParams->Uk =0x12; + CB.ResParams->Tg =0x83; // Type response, group functions info + CB.ResParams->SubFun=SFun_ListBoT; + CB.ResParams->Seq =CB.ReqParams->Seq; + CB.ResParams->Rsvd =0x0000; + Data=PDataFunGetBot(pbyte(&CB.Answer)+ResHeaderSize17+sizeof(TResFunGetBlockInfo)); + + if (BlockType==Block_DB) + { + cnt =0; // Local couter + if (Start) + DBCnt=-1; // Global counter + + if (FServer->DBCount>0) + { + while ((cntDB[DBCnt]!=NULL) + { + Data->Items[cnt].BlockNum=SwapWord(FServer->DB[DBCnt]->Number); + Data->Items[cnt].Unknown =0x22; + Data->Items[cnt].BlockLang=0x05; + cnt++; + }; + }; + + if ((cntRsvd=0x0023; + } + else + CB.ResParams->Rsvd=0x0123; + + if (cnt>0) + { + CB.ResParams->ErrNo =0x0000; + Data->TSize =TS_ResOctet; + Data->RetVal=0xFF; + CB.DataLength=4+(cnt*word(sizeof(TDataFunGetBotItem))); + CB.Answer.Header.DataLen=SwapWord(CB.DataLength); + Data->DataLen=SwapWord(CB.DataLength-4); + } + else + BLK_NoResource_ListBoT(Data, CB); + } + else + BLK_NoResource_ListBoT(Data, CB); + } + else // we store only DBs + BLK_NoResource_ListBoT(Data, CB); + + TotalSize = ResHeaderSize17+sizeof(TResFunGetBlockInfo)+CB.DataLength; + isoSendBuffer(&CB.Answer,TotalSize); + if (Start) + DoEvent(evcDirectory, CB.evError, evsStartListBoT, BlockType, 0, 0); + else + DoEvent(evcDirectory, CB.evError, evsListBoT, BlockType, 0, 0); +} +//------------------------------------------------------------------------------ +void TS7Worker::BLK_NoResource_GetBlkInfo(PResDataBlockInfo Data, TCB &CB) +{ + CB.DataLength =4; + CB.Answer.Header.DataLen=SwapWord(CB.DataLength); + CB.ResParams->ErrNo =0x09D2; // function in error + Data->RetVal =0x0A; // No resource available + Data->TSize =0x00; // No transport size; + Data->Length =0x0000; // No data; + CB.evError =evrResNotFound; +} +//------------------------------------------------------------------------------ +void TS7Worker::BLK_GetBlockNum_GetBlkInfo(int &BlkNum, PReqDataBlockInfo ReqData) +{ + BlkNum = (ReqData->AsciiBlk[4] - 0x30) + + (ReqData->AsciiBlk[3] - 0x30) * 10 + + (ReqData->AsciiBlk[2] - 0x30) * 100 + + (ReqData->AsciiBlk[1] - 0x30) * 1000 + + (ReqData->AsciiBlk[0] - 0x30) * 10000; + + if (BlkNum>65535) + BlkNum=-1; +} +//------------------------------------------------------------------------------ +void TS7Worker::BLK_DoBlockInfo_GetBlkInfo(PS7Area DB, PResDataBlockInfo Data, TCB &CB) +{ + // Prepares the answer + CB.Answer.Header.P=0x32; + CB.Answer.Header.PDUType=PduType_userdata; + CB.Answer.Header.AB_EX=0x0000; + CB.Answer.Header.Sequence=PDUH_in->Sequence; + CB.Answer.Header.ParLen =SwapWord(sizeof(TResFunGetBlockInfo)); + + CB.ResParams->Head[0]=CB.ReqParams->Head[0]; + CB.ResParams->Head[1]=CB.ReqParams->Head[1]; + CB.ResParams->Head[2]=CB.ReqParams->Head[2]; + CB.ResParams->Plen =0x08; + CB.ResParams->Uk =0x12; + CB.ResParams->Tg =0x83; // Type response, group functions info + CB.ResParams->SubFun=SFun_BlkInfo; + CB.ResParams->Seq =CB.ReqParams->Seq; + CB.ResParams->Rsvd =0x0000; + CB.ResParams->ErrNo =0x0000; + + CB.DataLength =sizeof(TResDataBlockInfo); + CB.Answer.Header.DataLen=SwapWord(CB.DataLength); + CB.ResParams->ErrNo =0x0000; + + Data->RetVal =0xFF; + Data->TSize =TS_ResOctet; + Data->Length =SwapWord(78); // this struct - RetValData->Tsize and length + Data->Cst_b =0x01; + Data->BlkType =0x00; + Data->Cst_w1 =0x4A00; + Data->Cst_w2 =0x0022; + Data->Cst_pp =0x7070; + Data->Unknown_1 =0x01; + Data->BlkFlags =0x01; + Data->BlkLang =BlockLangDB; + Data->SubBlkType =0x0A; + Data->CodeTime_dy =SwapWord(5800);// Nov/18/1999 my princess's birthdate + Data->IntfTime_dy =Data->CodeTime_dy; + Data->LocDataLen =0x0000; + Data->BlkNumber =SwapWord(DB->Number); + Data->SbbLen =0x1400; + Data->AddLen =0x0000; + Data->MC7Len =SwapWord(DB->Size); + Data->LenLoadMem =SwapDWord(DB->Size+92); + Data->Version =0x01; + Data->Unknown_2 =0x00; + Data->BlkChksum =0x0000; +} +//------------------------------------------------------------------------------ +void TS7Worker::BLK_GetBlkInfo(TCB &CB) +{ + PReqDataBlockInfo ReqData; + PResDataBlockInfo Data; + int BlkNum; + PS7Area BlkDB; + byte BlkTypeInfo; + int TotalSize; + + CB.evError=0; + Data =PResDataBlockInfo(pbyte(&CB.Answer)+ResHeaderSize17+sizeof(TResFunGetBlockInfo)); + ReqData=PReqDataBlockInfo(pbyte(PDUH_in)+ReqHeaderSize+sizeof(TReqFunGetBlockInfo)); + memset(Data,0,sizeof(TResDataBlockInfo)); // many fields are 0 + + BLK_GetBlockNum_GetBlkInfo(BlkNum, ReqData); + BlkTypeInfo=ReqData->BlkType; + if (BlkTypeInfo==Block_DB) + { + if (BlkNum>=0) + { + BlkDB=FServer->FindDB(BlkNum); + if (BlkDB!=NULL) + BLK_DoBlockInfo_GetBlkInfo(BlkDB, Data, CB); + else + BLK_NoResource_GetBlkInfo(Data, CB); + } + else + BLK_NoResource_GetBlkInfo(Data, CB); + } + else + BLK_NoResource_GetBlkInfo(Data, CB); + + TotalSize = ResHeaderSize17+sizeof(TResFunGetBlockInfo)+sizeof(TResDataBlockInfo); + isoSendBuffer(&CB.Answer, TotalSize); + DoEvent(evcDirectory,CB.evError,evsGetBlockInfo,BlkTypeInfo,BlkNum,0); +} +//------------------------------------------------------------------------------ +bool TS7Worker::PerformGroupBlockInfo() +{ + TCB CB; + pbyte BlockType; + + // Setup pointers + CB.ReqParams=PReqFunGetBlockInfo(pbyte(PDUH_in)+ReqHeaderSize); + CB.ResParams=PResFunGetBlockInfo(pbyte(&CB.Answer)+ResHeaderSize17); + BlockType =pbyte(PDUH_in)+23; + + switch (CB.ReqParams->SubFun) + { + case SFun_ListAll : BLK_ListAll(CB); break; // List all blocks + case SFun_ListBoT : + { + if (CB.ReqParams->Plen==4) + { + LastBlk=*BlockType; + BLK_ListBoT(*BlockType, true, CB); // start sequence from beginning + } + else + BLK_ListBoT(LastBlk, false, CB); // Continue sequence + }; break; + case SFun_BlkInfo : BLK_GetBlkInfo(CB); // Get Block info + } + return true; +} +//============================================================================== +// FUNCTION SZL +//============================================================================== +void TS7Worker::SZLNotAvailable() +{ + SZL.Answer.Header.DataLen=SwapWord(sizeof(SZLNotAvail)); + SZL.ResParams->Err = 0x02D4; + memcpy(SZL.ResData, &SZLNotAvail, sizeof(SZLNotAvail)); + isoSendBuffer(&SZL.Answer,26); + SZL.SZLDone=false; +} +void TS7Worker::SZLSystemState() +{ + SZL.Answer.Header.DataLen=SwapWord(sizeof(SZLSysState)); + SZL.ResParams->Err =0x0000; + memcpy(SZL.ResData,&SZLSysState,sizeof(SZLSysState)); + isoSendBuffer(&SZL.Answer,28); + SZL.SZLDone=true; +} +void TS7Worker::SZLData(void *P, int len) +{ + int MaxSzl=FPDULength-22; + + if (len>MaxSzl) { + len=MaxSzl; + } + + SZL.Answer.Header.DataLen=SwapWord(word(len)); + SZL.ResParams->Err =0x0000; + SZL.ResParams->resvd=0x0000; // this is the end, no more packets + memcpy(SZL.ResData, P, len); + + SZL.ResData[2]=((len-4)>>8) & 0xFF; + SZL.ResData[3]=(len-4) & 0xFF; + + isoSendBuffer(&SZL.Answer,22+len); + SZL.SZLDone=true; +} +// this block is dynamic (contains date/time and cpu status) +void TS7Worker::SZL_ID424() +{ + PS7Time PTime; + pbyte PStatus; + + SZL.Answer.Header.DataLen=SwapWord(sizeof(SZL_ID_0424_IDX_XXXX)); + SZL.ResParams->Err =0x0000; + PTime=PS7Time(pbyte(SZL.ResData)+24); + PStatus =pbyte(SZL.ResData)+15; + memcpy(SZL.ResData,&SZL_ID_0424_IDX_XXXX,sizeof(SZL_ID_0424_IDX_XXXX)); + FillTime(PTime); + *PStatus=FServer->CpuStatus; + SZL.SZLDone=true; + isoSendBuffer(&SZL.Answer,22+sizeof(SZL_ID_0424_IDX_XXXX)); +} + +void TS7Worker::SZL_ID131_IDX003() +{ + word len = sizeof(SZL_ID_0131_IDX_0003); + SZL.Answer.Header.DataLen=SwapWord(len); + SZL.ResParams->Err =0x0000; + SZL.ResParams->resvd=0x0000; // this is the end, no more packets + memcpy(SZL.ResData, &SZL_ID_0131_IDX_0003, len); + // Set the max consistent data window to PDU size + SZL.ResData[18]=((FPDULength)>>8) & 0xFF; + SZL.ResData[19]=(FPDULength) & 0xFF; + + isoSendBuffer(&SZL.Answer,22+len); + SZL.SZLDone=true; +} + +bool TS7Worker::PerformGroupSZL() +{ + SZL.SZLDone=false; + // Setup pointers + SZL.ReqParams=PReqFunReadSZLFirst(pbyte(PDUH_in)+ReqHeaderSize); + SZL.ResParams=PS7ResParams7(pbyte(&SZL.Answer)+ResHeaderSize17); + SZL.ResData =pbyte(&SZL.Answer)+ResHeaderSize17+sizeof(TS7Params7); + // Prepare Answer header + SZL.Answer.Header.P=0x32; + SZL.Answer.Header.PDUType=PduType_userdata; + SZL.Answer.Header.AB_EX=0x0000; + SZL.Answer.Header.Sequence=PDUH_in->Sequence; + SZL.Answer.Header.ParLen =SwapWord(sizeof(TS7Params7)); + + SZL.ResParams->Head[0]=SZL.ReqParams->Head[0]; + SZL.ResParams->Head[1]=SZL.ReqParams->Head[1]; + SZL.ResParams->Head[2]=SZL.ReqParams->Head[2]; + SZL.ResParams->Plen =0x08; + SZL.ResParams->Uk =0x12; + SZL.ResParams->Tg =0x84; // Type response + group szl + SZL.ResParams->SubFun=SZL.ReqParams->SubFun; + SZL.ResParams->Seq =SZL.ReqParams->Seq; + SZL.ResParams->resvd=0x0000; // this is the end, no more packets + + // only two subfunction are defined : 0x01 read, 0x02 system state + if (SZL.ResParams->SubFun==0x02) // 0x02 = subfunction system state + { + SZLSystemState(); + return true; + }; + if (SZL.ResParams->SubFun!=0x01) + { + SZLNotAvailable(); + return true; + }; + // From here we assume subfunction = 0x01 + SZL.ReqData=PS7ReqSZLData(pbyte(PDUH_in)+ReqHeaderSize+sizeof(TReqFunReadSZLFirst));// Data after params + + SZL.ID=SwapWord(SZL.ReqData->ID); + SZL.Index=SwapWord(SZL.ReqData->Index); + + // Switch prebuilt Data Bank (they come from a physical CPU) + switch (SZL.ID) + { + case 0x0000 : SZLData(&SZL_ID_0000_IDX_XXXX,sizeof(SZL_ID_0000_IDX_XXXX));break; + case 0x0F00 : SZLData(&SZL_ID_0F00_IDX_XXXX,sizeof(SZL_ID_0F00_IDX_XXXX));break; + case 0x0002 : SZLData(&SZL_ID_0002_IDX_XXXX,sizeof(SZL_ID_0002_IDX_XXXX));break; + case 0x0011 : SZLData(&SZL_ID_0011_IDX_XXXX,sizeof(SZL_ID_0011_IDX_XXXX));break; + case 0x0012 : SZLData(&SZL_ID_0012_IDX_XXXX,sizeof(SZL_ID_0012_IDX_XXXX));break; + case 0x0013 : SZLData(&SZL_ID_0013_IDX_XXXX,sizeof(SZL_ID_0013_IDX_XXXX));break; + case 0x0014 : SZLData(&SZL_ID_0014_IDX_XXXX,sizeof(SZL_ID_0014_IDX_XXXX));break; + case 0x0015 : SZLData(&SZL_ID_0015_IDX_XXXX,sizeof(SZL_ID_0015_IDX_XXXX));break; + case 0x0F14 : SZLData(&SZL_ID_0F14_IDX_XXXX,sizeof(SZL_ID_0F14_IDX_XXXX));break; + case 0x0019 : SZLData(&SZL_ID_0019_IDX_XXXX,sizeof(SZL_ID_0019_IDX_XXXX));break; + case 0x0F19 : SZLData(&SZL_ID_0F19_IDX_XXXX,sizeof(SZL_ID_0F19_IDX_XXXX));break; + case 0x001C : SZLData(&SZL_ID_001C_IDX_XXXX,sizeof(SZL_ID_001C_IDX_XXXX));break; + case 0x0F1C : SZLData(&SZL_ID_0F1C_IDX_XXXX,sizeof(SZL_ID_0F1C_IDX_XXXX));break; + case 0x0036 : SZLData(&SZL_ID_0036_IDX_XXXX,sizeof(SZL_ID_0036_IDX_XXXX));break; + case 0x0F36 : SZLData(&SZL_ID_0F36_IDX_XXXX,sizeof(SZL_ID_0F36_IDX_XXXX));break; + case 0x0025 : SZLData(&SZL_ID_0025_IDX_XXXX,sizeof(SZL_ID_0025_IDX_XXXX));break; + case 0x0F25 : SZLData(&SZL_ID_0F25_IDX_XXXX,sizeof(SZL_ID_0F25_IDX_XXXX));break; + case 0x0037 : SZLData(&SZL_ID_0037_IDX_XXXX,sizeof(SZL_ID_0037_IDX_XXXX));break; + case 0x0F37 : SZLData(&SZL_ID_0F37_IDX_XXXX,sizeof(SZL_ID_0F37_IDX_XXXX));break; + case 0x0074 : SZLData(&SZL_ID_0074_IDX_XXXX,sizeof(SZL_ID_0074_IDX_XXXX));break; + case 0x0F74 : SZLData(&SZL_ID_0F74_IDX_XXXX,sizeof(SZL_ID_0F74_IDX_XXXX));break; + case 0x0591 : SZLData(&SZL_ID_0591_IDX_XXXX,sizeof(SZL_ID_0591_IDX_XXXX));break; + case 0x0A91 : SZLData(&SZL_ID_0A91_IDX_XXXX,sizeof(SZL_ID_0A91_IDX_XXXX));break; + case 0x0F92 : SZLData(&SZL_ID_0F92_IDX_XXXX,sizeof(SZL_ID_0F92_IDX_XXXX));break; + case 0x0294 : SZLData(&SZL_ID_0294_IDX_XXXX,sizeof(SZL_ID_0294_IDX_XXXX));break; + case 0x0794 : SZLData(&SZL_ID_0794_IDX_XXXX,sizeof(SZL_ID_0794_IDX_XXXX));break; + case 0x0F94 : SZLData(&SZL_ID_0F94_IDX_XXXX,sizeof(SZL_ID_0F94_IDX_XXXX));break; + case 0x0095 : SZLData(&SZL_ID_0095_IDX_XXXX,sizeof(SZL_ID_0095_IDX_XXXX));break; + case 0x0F95 : SZLData(&SZL_ID_0F95_IDX_XXXX,sizeof(SZL_ID_0F95_IDX_XXXX));break; + case 0x00A0 : SZLData(&SZL_ID_00A0_IDX_XXXX,sizeof(SZL_ID_00A0_IDX_XXXX));break; + case 0x0FA0 : SZLData(&SZL_ID_0FA0_IDX_XXXX,sizeof(SZL_ID_0FA0_IDX_XXXX));break; + case 0x0017 : SZLData(&SZL_ID_0017_IDX_XXXX,sizeof(SZL_ID_0017_IDX_XXXX));break; + case 0x0F17 : SZLData(&SZL_ID_0F17_IDX_XXXX,sizeof(SZL_ID_0F17_IDX_XXXX));break; + case 0x0018 : SZLData(&SZL_ID_0018_IDX_XXXX,sizeof(SZL_ID_0018_IDX_XXXX));break; + case 0x0F18 : SZLData(&SZL_ID_0F18_IDX_XXXX,sizeof(SZL_ID_0F18_IDX_XXXX));break; + case 0x001A : SZLData(&SZL_ID_001A_IDX_XXXX,sizeof(SZL_ID_001A_IDX_XXXX));break; + case 0x0F1A : SZLData(&SZL_ID_0F1A_IDX_XXXX,sizeof(SZL_ID_0F1A_IDX_XXXX));break; + case 0x001B : SZLData(&SZL_ID_001B_IDX_XXXX,sizeof(SZL_ID_001B_IDX_XXXX));break; + case 0x0F1B : SZLData(&SZL_ID_0F1B_IDX_XXXX,sizeof(SZL_ID_0F1B_IDX_XXXX));break; + case 0x0021 : SZLData(&SZL_ID_0021_IDX_XXXX,sizeof(SZL_ID_0021_IDX_XXXX));break; + case 0x0A21 : SZLData(&SZL_ID_0A21_IDX_XXXX,sizeof(SZL_ID_0A21_IDX_XXXX));break; + case 0x0F21 : SZLData(&SZL_ID_0F21_IDX_XXXX,sizeof(SZL_ID_0F21_IDX_XXXX));break; + case 0x0023 : SZLData(&SZL_ID_0023_IDX_XXXX,sizeof(SZL_ID_0023_IDX_XXXX));break; + case 0x0F23 : SZLData(&SZL_ID_0F23_IDX_XXXX,sizeof(SZL_ID_0F23_IDX_XXXX));break; + case 0x0024 : SZLData(&SZL_ID_0024_IDX_XXXX,sizeof(SZL_ID_0024_IDX_XXXX));break; + case 0x0124 : SZLData(&SZL_ID_0124_IDX_XXXX,sizeof(SZL_ID_0124_IDX_XXXX));break; + case 0x0424 : SZL_ID424();break; + case 0x0038 : SZLData(&SZL_ID_0038_IDX_XXXX,sizeof(SZL_ID_0038_IDX_XXXX));break; + case 0x0F38 : SZLData(&SZL_ID_0F38_IDX_XXXX,sizeof(SZL_ID_0F38_IDX_XXXX));break; + case 0x003A : SZLData(&SZL_ID_003A_IDX_XXXX,sizeof(SZL_ID_003A_IDX_XXXX));break; + case 0x0F3A : SZLData(&SZL_ID_0F3A_IDX_XXXX,sizeof(SZL_ID_0F3A_IDX_XXXX));break; + case 0x0F9A : SZLData(&SZL_ID_0F9A_IDX_XXXX,sizeof(SZL_ID_0F9A_IDX_XXXX));break; + case 0x0D91 : switch(SZL.Index){ + case 0x0000 : SZLData(&SZL_ID_0D91_IDX_0000,sizeof(SZL_ID_0D91_IDX_0000));break; + default: SZLNotAvailable();break; + }; + break; + case 0x0092 : switch(SZL.Index){ + case 0x0000 : SZLData(&SZL_ID_0092_IDX_0000,sizeof(SZL_ID_0092_IDX_0000));break; + default : SZLNotAvailable();break; + };break; + case 0x0292 : switch(SZL.Index){ + case 0x0000 : SZLData(&SZL_ID_0292_IDX_0000,sizeof(SZL_ID_0292_IDX_0000));break; + default : SZLNotAvailable();break; + };break; + case 0x0692 : switch(SZL.Index){ + case 0x0000 : SZLData(&SZL_ID_0692_IDX_0000,sizeof(SZL_ID_0692_IDX_0000));break; + default : SZLNotAvailable();break; + };break; + case 0x0094 : switch(SZL.Index){ + case 0x0000 : SZLData(&SZL_ID_0094_IDX_0000,sizeof(SZL_ID_0094_IDX_0000));break; + default : SZLNotAvailable();break; + };break; + case 0x0D97 : switch(SZL.Index){ + case 0x0000 : SZLData(&SZL_ID_0D97_IDX_0000,sizeof(SZL_ID_0D97_IDX_0000));break; + default : SZLNotAvailable();break; + };break; + case 0x0111 : switch(SZL.Index){ + case 0x0001 : SZLData(&SZL_ID_0111_IDX_0001,sizeof(SZL_ID_0111_IDX_0001));break; + case 0x0006 : SZLData(&SZL_ID_0111_IDX_0006,sizeof(SZL_ID_0111_IDX_0006));break; + case 0x0007 : SZLData(&SZL_ID_0111_IDX_0007,sizeof(SZL_ID_0111_IDX_0007));break; + default : SZLNotAvailable();break; + };break; + case 0x0F11 : switch(SZL.Index){ + case 0x0001 : SZLData(&SZL_ID_0F11_IDX_0001,sizeof(SZL_ID_0F11_IDX_0001));break; + case 0x0006 : SZLData(&SZL_ID_0F11_IDX_0006,sizeof(SZL_ID_0F11_IDX_0006));break; + case 0x0007 : SZLData(&SZL_ID_0F11_IDX_0007,sizeof(SZL_ID_0F11_IDX_0007));break; + default : SZLNotAvailable();break; + };break; + case 0x0112 : switch(SZL.Index){ + case 0x0000 : SZLData(&SZL_ID_0112_IDX_0000,sizeof(SZL_ID_0112_IDX_0000));break; + case 0x0100 : SZLData(&SZL_ID_0112_IDX_0100,sizeof(SZL_ID_0112_IDX_0100));break; + case 0x0200 : SZLData(&SZL_ID_0112_IDX_0200,sizeof(SZL_ID_0112_IDX_0200));break; + case 0x0400 : SZLData(&SZL_ID_0112_IDX_0400,sizeof(SZL_ID_0112_IDX_0400));break; + default : SZLNotAvailable();break; + };break; + case 0x0F12 : switch(SZL.Index){ + case 0x0000 : SZLData(&SZL_ID_0F12_IDX_0000,sizeof(SZL_ID_0F12_IDX_0000));break; + case 0x0100 : SZLData(&SZL_ID_0F12_IDX_0100,sizeof(SZL_ID_0F12_IDX_0100));break; + case 0x0200 : SZLData(&SZL_ID_0F12_IDX_0200,sizeof(SZL_ID_0F12_IDX_0200));break; + case 0x0400 : SZLData(&SZL_ID_0F12_IDX_0400,sizeof(SZL_ID_0F12_IDX_0400));break; + default : SZLNotAvailable();break; + };break; + case 0x0113 : switch(SZL.Index){ + case 0x0001 : SZLData(&SZL_ID_0113_IDX_0001,sizeof(SZL_ID_0113_IDX_0001));break; + default : SZLNotAvailable();break; + };break; + case 0x0115 : switch(SZL.Index){ + case 0x0800 : SZLData(&SZL_ID_0115_IDX_0800,sizeof(SZL_ID_0115_IDX_0800));break; + default : SZLNotAvailable();break; + };break; + case 0x011C : switch(SZL.Index){ + case 0x0001 : SZLData(&SZL_ID_011C_IDX_0001,sizeof(SZL_ID_011C_IDX_0001));break; + case 0x0002 : SZLData(&SZL_ID_011C_IDX_0002,sizeof(SZL_ID_011C_IDX_0002));break; + case 0x0003 : SZLData(&SZL_ID_011C_IDX_0003,sizeof(SZL_ID_011C_IDX_0003));break; + case 0x0004 : SZLData(&SZL_ID_011C_IDX_0004,sizeof(SZL_ID_011C_IDX_0004));break; + case 0x0005 : SZLData(&SZL_ID_011C_IDX_0005,sizeof(SZL_ID_011C_IDX_0005));break; + case 0x0007 : SZLData(&SZL_ID_011C_IDX_0007,sizeof(SZL_ID_011C_IDX_0007));break; + case 0x0008 : SZLData(&SZL_ID_011C_IDX_0008,sizeof(SZL_ID_011C_IDX_0008));break; + case 0x0009 : SZLData(&SZL_ID_011C_IDX_0009,sizeof(SZL_ID_011C_IDX_0009));break; + case 0x000A : SZLData(&SZL_ID_011C_IDX_000A,sizeof(SZL_ID_011C_IDX_000A));break; + case 0x000B : SZLData(&SZL_ID_011C_IDX_000B,sizeof(SZL_ID_011C_IDX_000B));break; + default : SZLNotAvailable();break; + };break; + case 0x0222 : switch(SZL.Index){ + case 0x0001 : SZLData(&SZL_ID_0222_IDX_0001,sizeof(SZL_ID_0222_IDX_0001));break; + case 0x000A : SZLData(&SZL_ID_0222_IDX_000A,sizeof(SZL_ID_0222_IDX_000A));break; + case 0x0014 : SZLData(&SZL_ID_0222_IDX_0014,sizeof(SZL_ID_0222_IDX_0014));break; + case 0x0028 : SZLData(&SZL_ID_0222_IDX_0028,sizeof(SZL_ID_0222_IDX_0028));break; + case 0x0050 : SZLData(&SZL_ID_0222_IDX_0050,sizeof(SZL_ID_0222_IDX_0050));break; + case 0x0064 : SZLData(&SZL_ID_0222_IDX_0064,sizeof(SZL_ID_0222_IDX_0064));break; + default : SZLNotAvailable();break; + };break; + case 0x0125 : switch(SZL.Index){ + case 0x0000 : SZLData(&SZL_ID_0125_IDX_0000,sizeof(SZL_ID_0125_IDX_0000));break; + case 0x0001 : SZLData(&SZL_ID_0125_IDX_0001,sizeof(SZL_ID_0125_IDX_0001));break; + default : SZLNotAvailable();break; + };break; + case 0x0225 : switch(SZL.Index){ + case 0x0001 : SZLData(&SZL_ID_0225_IDX_0001,sizeof(SZL_ID_0225_IDX_0001));break; + default : SZLNotAvailable();break; + };break; + case 0x0131 : switch(SZL.Index){ + case 0x0001 : SZLData(&SZL_ID_0131_IDX_0001,sizeof(SZL_ID_0131_IDX_0001));break; + case 0x0002 : SZLData(&SZL_ID_0131_IDX_0002,sizeof(SZL_ID_0131_IDX_0002));break; + case 0x0003 : SZL_ID131_IDX003();break; + case 0x0004 : SZLData(&SZL_ID_0131_IDX_0004,sizeof(SZL_ID_0131_IDX_0004));break; + case 0x0005 : SZLData(&SZL_ID_0131_IDX_0005,sizeof(SZL_ID_0131_IDX_0005));break; + case 0x0006 : SZLData(&SZL_ID_0131_IDX_0006,sizeof(SZL_ID_0131_IDX_0006));break; + case 0x0007 : SZLData(&SZL_ID_0131_IDX_0007,sizeof(SZL_ID_0131_IDX_0007));break; + case 0x0008 : SZLData(&SZL_ID_0131_IDX_0008,sizeof(SZL_ID_0131_IDX_0008));break; + case 0x0009 : SZLData(&SZL_ID_0131_IDX_0009,sizeof(SZL_ID_0131_IDX_0009));break; + default : SZLNotAvailable();break; + };break; + case 0x0117 : switch(SZL.Index){ + case 0x0000 : SZLData(&SZL_ID_0117_IDX_0000,sizeof(SZL_ID_0117_IDX_0000));break; + case 0x0001 : SZLData(&SZL_ID_0117_IDX_0001,sizeof(SZL_ID_0117_IDX_0001));break; + case 0x0002 : SZLData(&SZL_ID_0117_IDX_0002,sizeof(SZL_ID_0117_IDX_0002));break; + case 0x0003 : SZLData(&SZL_ID_0117_IDX_0003,sizeof(SZL_ID_0117_IDX_0003));break; + case 0x0004 : SZLData(&SZL_ID_0117_IDX_0004,sizeof(SZL_ID_0117_IDX_0004));break; + default : SZLNotAvailable();break; + };break; + case 0x0118 : switch(SZL.Index){ + case 0x0000 : SZLData(&SZL_ID_0118_IDX_0000,sizeof(SZL_ID_0118_IDX_0000));break; + case 0x0001 : SZLData(&SZL_ID_0118_IDX_0001,sizeof(SZL_ID_0118_IDX_0001));break; + case 0x0002 : SZLData(&SZL_ID_0118_IDX_0002,sizeof(SZL_ID_0118_IDX_0002));break; + case 0x0003 : SZLData(&SZL_ID_0118_IDX_0003,sizeof(SZL_ID_0118_IDX_0003));break; + default : SZLNotAvailable();break; + };break; + case 0x0132 : switch(SZL.Index){ + case 0x0001 : SZLData(&SZL_ID_0132_IDX_0001,sizeof(SZL_ID_0132_IDX_0001));break; + case 0x0002 : SZLData(&SZL_ID_0132_IDX_0002,sizeof(SZL_ID_0132_IDX_0002));break; + case 0x0003 : SZLData(&SZL_ID_0132_IDX_0003,sizeof(SZL_ID_0132_IDX_0003));break; + case 0x0004 : SZLData(&SZL_ID_0132_IDX_0004,sizeof(SZL_ID_0132_IDX_0004));break; + case 0x0005 : SZLData(&SZL_ID_0132_IDX_0005,sizeof(SZL_ID_0132_IDX_0005));break; + case 0x0006 : SZLData(&SZL_ID_0132_IDX_0006,sizeof(SZL_ID_0132_IDX_0006));break; + case 0x0007 : SZLData(&SZL_ID_0132_IDX_0007,sizeof(SZL_ID_0132_IDX_0007));break; + case 0x0008 : SZLData(&SZL_ID_0132_IDX_0008,sizeof(SZL_ID_0132_IDX_0008));break; + case 0x0009 : SZLData(&SZL_ID_0132_IDX_0009,sizeof(SZL_ID_0132_IDX_0009));break; + case 0x000A : SZLData(&SZL_ID_0132_IDX_000A,sizeof(SZL_ID_0132_IDX_000A));break; + case 0x000B : SZLData(&SZL_ID_0132_IDX_000B,sizeof(SZL_ID_0132_IDX_000B));break; + case 0x000C : SZLData(&SZL_ID_0132_IDX_000C,sizeof(SZL_ID_0132_IDX_000C));break; + default : SZLNotAvailable();break; + };break; + case 0x0137 : switch(SZL.Index){ + case 0x07FE : SZLData(&SZL_ID_0137_IDX_07FE,sizeof(SZL_ID_0137_IDX_07FE));break; + default : SZLNotAvailable();break; + };break; + case 0x01A0 : switch(SZL.Index){ + case 0x0000 : SZLData(&SZL_ID_01A0_IDX_0000,sizeof(SZL_ID_01A0_IDX_0000));break; + case 0x0001 : SZLData(&SZL_ID_01A0_IDX_0001,sizeof(SZL_ID_01A0_IDX_0001));break; + case 0x0002 : SZLData(&SZL_ID_01A0_IDX_0002,sizeof(SZL_ID_01A0_IDX_0002));break; + case 0x0003 : SZLData(&SZL_ID_01A0_IDX_0003,sizeof(SZL_ID_01A0_IDX_0003));break; + case 0x0004 : SZLData(&SZL_ID_01A0_IDX_0004,sizeof(SZL_ID_01A0_IDX_0004));break; + case 0x0005 : SZLData(&SZL_ID_01A0_IDX_0005,sizeof(SZL_ID_01A0_IDX_0005));break; + case 0x0006 : SZLData(&SZL_ID_01A0_IDX_0006,sizeof(SZL_ID_01A0_IDX_0006));break; + case 0x0007 : SZLData(&SZL_ID_01A0_IDX_0007,sizeof(SZL_ID_01A0_IDX_0007));break; + case 0x0008 : SZLData(&SZL_ID_01A0_IDX_0008,sizeof(SZL_ID_01A0_IDX_0008));break; + case 0x0009 : SZLData(&SZL_ID_01A0_IDX_0009,sizeof(SZL_ID_01A0_IDX_0009));break; + case 0x000A : SZLData(&SZL_ID_01A0_IDX_000A,sizeof(SZL_ID_01A0_IDX_000A));break; + case 0x000B : SZLData(&SZL_ID_01A0_IDX_000B,sizeof(SZL_ID_01A0_IDX_000B));break; + case 0x000C : SZLData(&SZL_ID_01A0_IDX_000C,sizeof(SZL_ID_01A0_IDX_000C));break; + case 0x000D : SZLData(&SZL_ID_01A0_IDX_000D,sizeof(SZL_ID_01A0_IDX_000D));break; + case 0x000E : SZLData(&SZL_ID_01A0_IDX_000E,sizeof(SZL_ID_01A0_IDX_000E));break; + case 0x000F : SZLData(&SZL_ID_01A0_IDX_000F,sizeof(SZL_ID_01A0_IDX_000F));break; + case 0x0010 : SZLData(&SZL_ID_01A0_IDX_0010,sizeof(SZL_ID_01A0_IDX_0010));break; + case 0x0011 : SZLData(&SZL_ID_01A0_IDX_0011,sizeof(SZL_ID_01A0_IDX_0011));break; + case 0x0012 : SZLData(&SZL_ID_01A0_IDX_0012,sizeof(SZL_ID_01A0_IDX_0012));break; + case 0x0013 : SZLData(&SZL_ID_01A0_IDX_0013,sizeof(SZL_ID_01A0_IDX_0013));break; + case 0x0014 : SZLData(&SZL_ID_01A0_IDX_0014,sizeof(SZL_ID_01A0_IDX_0014));break; + case 0x0015 : SZLData(&SZL_ID_01A0_IDX_0015,sizeof(SZL_ID_01A0_IDX_0015));break; + default : SZLNotAvailable();break; + };break; + case 0x0174 : switch(SZL.Index){ + case 0x0001 : SZLData(&SZL_ID_0174_IDX_0001,sizeof(SZL_ID_0174_IDX_0001));break; + case 0x0004 : SZLData(&SZL_ID_0174_IDX_0004,sizeof(SZL_ID_0174_IDX_0004));break; + case 0x0005 : SZLData(&SZL_ID_0174_IDX_0005,sizeof(SZL_ID_0174_IDX_0005));break; + case 0x0006 : SZLData(&SZL_ID_0174_IDX_0006,sizeof(SZL_ID_0174_IDX_0006));break; + case 0x000B : SZLData(&SZL_ID_0174_IDX_000B,sizeof(SZL_ID_0174_IDX_000B));break; + case 0x000C : SZLData(&SZL_ID_0174_IDX_000C,sizeof(SZL_ID_0174_IDX_000C));break; + default : SZLNotAvailable();break; + };break; + case 0x0194 : switch(SZL.Index){ + case 0x0064 : SZLData(&SZL_ID_0194_IDX_0064,sizeof(SZL_ID_0194_IDX_0064));break; + default : SZLNotAvailable();break; + };break; + case 0x0694 : switch(SZL.Index){ + case 0x0064 : SZLData(&SZL_ID_0694_IDX_0064,sizeof(SZL_ID_0694_IDX_0064));break; + default : SZLNotAvailable();break; + };break; + case 0x0232 : switch(SZL.Index){ + case 0x0001 : SZLData(&SZL_ID_0232_IDX_0001,sizeof(SZL_ID_0232_IDX_0001));break; + case 0x0004 : SZLData(&SZL_ID_0232_IDX_0004,sizeof(SZL_ID_0232_IDX_0004));break; + default : SZLNotAvailable();break; + };break; + case 0x0C91 : switch(SZL.Index){ + case 0x07FE : SZLData(&SZL_ID_0C91_IDX_07FE,sizeof(SZL_ID_0C91_IDX_07FE));break; + default : SZLNotAvailable();break; + };break; + default : SZLNotAvailable();break; + } + // Event + if (SZL.SZLDone) + DoEvent(evcReadSZL,evrNoError,SZL.ID,SZL.Index,0,0); + else + DoEvent(evcReadSZL,evrInvalidSZL,SZL.ID,SZL.Index,0,0); + return true; +} +//------------------------------------------------------------------------------ +bool TS7Worker::PerformGroupSecurity() +{ + PReqFunSecurity ReqParams; + PResParamsSecurity ResParams; + PResDataSecurity ResData; + TS7Answer17 Answer; + int TotalSize; + + ReqParams=PReqFunSecurity(pbyte(PDUH_in)+ReqHeaderSize); + ResParams=PResParamsSecurity(pbyte(&Answer)+ResHeaderSize17); + ResData =PResDataSecurity(pbyte(ResParams)+sizeof(TResParamsSecurity)); + + // Prepares the answer + Answer.Header.P=0x32; + Answer.Header.PDUType=PduType_userdata; + Answer.Header.AB_EX=0x0000; + Answer.Header.Sequence=PDUH_in->Sequence; + Answer.Header.ParLen =SwapWord(sizeof(TResParamsSecurity)); + Answer.Header.DataLen=SwapWord(0x0004); + // Params + ResParams->Head[0]=ReqParams->Head[0]; + ResParams->Head[1]=ReqParams->Head[1]; + ResParams->Head[2]=ReqParams->Head[2]; + ResParams->Plen =0x08; + ResParams->Uk =0x12; + ResParams->Tg =0x85; // Type response, group functions info + ResParams->SubFun=ReqParams->SubFun; + ResParams->Seq =ReqParams->Seq; + ResParams->resvd =0x0000; + ResParams->Err =0x0000; + // Data + ResData->Ret =0x0A; + ResData->TS =0x00; + ResData->DLen=0x0000; + + TotalSize=26; + isoSendBuffer(&Answer,TotalSize); + + switch (ReqParams->SubFun) + { + case SFun_EnterPwd : DoEvent(evcSecurity,evrNoError,evsSetPassword,0,0,0); break; + case SFun_CancelPwd : DoEvent(evcSecurity,evrNoError,evsClrPassword,0,0,0); break; + default : DoEvent(evcSecurity,evrNoError,evsUnknown,0,0,0); + }; + + return true; +} +//------------------------------------------------------------------------------ +bool TS7Worker::PerformGetClock() +{ + PS7ReqParams7 ReqParams; + PS7ResParams7 ResParams; + TS7Answer17 Answer; + PResDataGetTime Data; + PS7Time PTime; + int TotalSize; + + ReqParams=PS7ReqParams7(pbyte(PDUH_in)+ReqHeaderSize); + ResParams=PS7ResParams7(pbyte(&Answer)+ResHeaderSize17); + Data =PResDataGetTime(pbyte(&Answer)+ResHeaderSize17+sizeof(TS7Params7)); + PTime =PS7Time(pbyte(Data)+6); + + // Prepares the answer + Answer.Header.P=0x32; + Answer.Header.PDUType=PduType_userdata; + Answer.Header.AB_EX=0x0000; + Answer.Header.Sequence=PDUH_in->Sequence; + Answer.Header.ParLen =SwapWord(sizeof(TS7Params7)); + Answer.Header.DataLen=SwapWord(sizeof(TResDataGetTime)); + + ResParams->Head[0]=ReqParams->Head[0]; + ResParams->Head[1]=ReqParams->Head[1]; + ResParams->Head[2]=ReqParams->Head[2]; + ResParams->Plen =0x08; + ResParams->Uk =0x12; + ResParams->Tg =0x87; // Type response, group functions info + ResParams->SubFun=ReqParams->SubFun; + ResParams->Seq =ReqParams->Seq; + ResParams->resvd =0x0000; + ResParams->Err =0x0000; + + Data->RetVal =0xFF; + Data->TSize =TS_ResOctet; + Data->Length =SwapWord(10); + Data->Rsvd =0x00; + Data->HiYear =0x20; // Year 2000 + + FillTime(PTime); + + TotalSize=36; + isoSendBuffer(&Answer,TotalSize); + DoEvent(evcClock,evrNoError,evsGetClock,0,0,0); + return true; +} +//------------------------------------------------------------------------------ +bool TS7Worker::PerformSetClock() +{ + PS7ReqParams7 ReqParams; + PS7ResParams7 ResParams; + PResDataSetTime Data; + TS7Answer17 Answer; + int TotalSize; + + ReqParams=PS7ReqParams7(pbyte(PDUH_in)+ReqHeaderSize); + ResParams=PS7ResParams7(pbyte(&Answer)+ResHeaderSize17); + Data =PResDataSetTime(pbyte(&Answer)+ResHeaderSize17+sizeof(TS7Params7)); + + // Prepares the answer + Answer.Header.P=0x32; + Answer.Header.PDUType=PduType_userdata; + Answer.Header.AB_EX=0x0000; + Answer.Header.Sequence=PDUH_in->Sequence; + Answer.Header.ParLen =SwapWord(sizeof(TS7Params7)); + Answer.Header.DataLen=SwapWord(sizeof(TResDataSetTime)); + + ResParams->Head[0]=ReqParams->Head[0]; + ResParams->Head[1]=ReqParams->Head[1]; + ResParams->Head[2]=ReqParams->Head[2]; + ResParams->Plen =0x08; + ResParams->Uk =0x12; + ResParams->Tg =0x87; // Type response, group functions info + ResParams->SubFun=ReqParams->SubFun; + ResParams->Seq =ReqParams->Seq; + ResParams->resvd =0x0000; + ResParams->Err =0x0000; + + Data->RetVal =0x0A; + Data->TSize =0x00; + Data->Length =0x0000; + + TotalSize=26; + isoSendBuffer(&Answer,TotalSize); + DoEvent(evcClock,evrNoError,evsSetClock,0,0,0); + return true; +} +//------------------------------------------------------------------------------ +// S7 SERVER CLASS +//------------------------------------------------------------------------------ +TSnap7Server::TSnap7Server() +{ + CSRWHook = new TSnapCriticalSection(); + OnReadEvent=NULL; + memset(&DB,0,sizeof(DB)); + memset(&HA,0,sizeof(HA)); + DBCount=0; + DBLimit=0; + ForcePDU = 0; + ResourceLess = false; + LocalPort=isoTcpPort; + CpuStatus=S7CpuStatusRun; + WorkInterval=100; +} +//------------------------------------------------------------------------------ +TSnap7Server::~TSnap7Server() +{ + DisposeAll(); + delete CSRWHook; +} +//------------------------------------------------------------------------------ +PWorkerSocket TSnap7Server::CreateWorkerSocket(socket_t Sock) +{ + PWorkerSocket Result; + Result = new TS7Worker(); + Result->SetSocket(Sock); + PS7Worker(Result)->FServer=this; + return Result; +} +//------------------------------------------------------------------------------ +PS7Area TSnap7Server::FindDB(word DBNumber) +{ + int c; + int max=DBLimit+1; + + for (c=0; cNumber==DBNumber) + { + return DB[c]; + } + } + } + return NULL; +} +//------------------------------------------------------------------------------ +int TSnap7Server::IndexOfDB(word DBNumber) +{ + int c; + int max=DBLimit+1; + + for (c=0; cNumber==DBNumber) + { + return c; + } + } + } + return -1; +} +//------------------------------------------------------------------------------ +int TSnap7Server::FindFirstFreeDB() +{ + int c; + for (c=0; c < MaxDB; c++) + { + if (DB[c]==NULL) + return c; + } + return -1; +} +//------------------------------------------------------------------------------ +int TSnap7Server::RegisterDB(word Number, void *pUsrData, word Size) +{ + PS7Area TheArea; + int index; + + if (pUsrData==0) + return errSrvDBNullPointer; + + if (FindDB(Number)!=NULL) + return errSrvAreaAlreadyExists; + + index=FindFirstFreeDB(); + if (index==-1) + return errSrvTooManyDB; + + TheArea =new TS7Area; + TheArea->Number=Number; + TheArea->cs=new TSnapCriticalSection(); + TheArea->PData=pbyte(pUsrData); + TheArea->Size=Size; + DB[index]=TheArea; + DBCount++; + if (DBLimitcs!=0) + delete TheDB->cs; + delete TheDB; + } + } + DBCount=0; + // Unregister other + for (c = srvAreaPE; c < srvAreaDB; c++) + UnregisterSys(c); +} +//------------------------------------------------------------------------------ +int TSnap7Server::RegisterSys(int AreaCode, void *pUsrData, word Size) +{ + PS7Area TheArea; + + if (pUsrData==0) + return errSrvDBNullPointer; + + if ((AreaCodesrvAreaTM)) + return errSrvUnknownArea; + + if (HA[AreaCode]==0) + { + TheArea=new TS7Area; + TheArea->cs=new TSnapCriticalSection(); + TheArea->PData=pbyte(pUsrData); + TheArea->Size=Size; + HA[AreaCode]=TheArea; + return 0; + } + else + return errSrvAreaAlreadyExists; +} +//------------------------------------------------------------------------------ +int TSnap7Server::UnregisterDB(word DBNumber) +{ + PS7Area TheDB; + int index = IndexOfDB(DBNumber); + if (index==-1) + return errSrvInvalidParams; + + // Unregister should be done with the server in stop mode + // however we can minimize the risk... + TheDB=DB[index]; + DB[index]=NULL; + if (TheDB->cs!=NULL) + delete TheDB->cs; + delete TheDB; + DBCount--; + + return 0; +} +//------------------------------------------------------------------------------ +int TSnap7Server::UnregisterSys(int AreaCode) +{ + PS7Area TheArea; + if (HA[AreaCode]!=NULL) + { + // Unregister should be done with the server in stop mode + // however we can minimize the risk... + TheArea=HA[AreaCode]; + HA[AreaCode]=NULL; + if (TheArea->cs!=NULL) + delete TheArea->cs; + delete TheArea; + } + return 0; +} +//------------------------------------------------------------------------------ +int TSnap7Server::StartTo(const char *Address) +{ + return TCustomMsgServer::StartTo(Address, LocalPort); +} +//------------------------------------------------------------------------------ +int TSnap7Server::GetParam(int ParamNumber, void *pValue) +{ + switch (ParamNumber) + { + case p_u16_LocalPort: + *Puint16_t(pValue)=LocalPort; + break; + case p_i32_WorkInterval: + *Pint32_t(pValue)=WorkInterval; + break; + case p_i32_MaxClients: + *Pint32_t(pValue)=MaxClients; + break; + case p_i32_PDURequest: + *Pint32_t(pValue) = ForcePDU; + break; + default: return errSrvInvalidParamNumber; + } + return 0; +} +//------------------------------------------------------------------------------ +int TSnap7Server::SetParam(int ParamNumber, void *pValue) +{ + switch (ParamNumber) + { + case p_u16_LocalPort: + if (Status == SrvStopped) + LocalPort = *Puint16_t(pValue); + else + return errSrvCannotChangeParam; + break; + case p_i32_PDURequest: + if (Status == SrvStopped) + { + int PDU = *Pint32_t(pValue); + if (PDU == 0) + ForcePDU = 0; // ForcePDU=0 --> The server accepts the client's proposal + else + { + if ((PDU < MinPduSize) || (PDU>IsoPayload_Size)) + return errSrvInvalidParams; // Wrong value + else + ForcePDU = PDU; // The server imposes ForcePDU as PDU size + } + } + else + return errSrvCannotChangeParam; + break; + case p_i32_WorkInterval: + WorkInterval=*Pint32_t(pValue); + break; + case p_i32_MaxClients: + if (ClientsCount==0 && Status==SrvStopped) + MaxClients=*Pint32_t(pValue); + else + return errSrvCannotChangeParam; + break; + default: return errSrvInvalidParamNumber; + } + return 0; +} +//------------------------------------------------------------------------------ +int TSnap7Server::RegisterArea(int AreaCode, word Index, void *pUsrData, word Size) +{ + if (AreaCode==srvAreaDB) + return RegisterDB(Index, pUsrData, Size); + else + return RegisterSys(AreaCode,pUsrData, Size); +} +//------------------------------------------------------------------------------ +int TSnap7Server::UnregisterArea(int AreaCode, word Index) +{ + if (AreaCode==srvAreaDB) + return UnregisterDB(Index); + else + if ((AreaCode>=srvAreaPE) && (AreaCode<=srvAreaTM)) + return UnregisterSys(AreaCode); + else + return errSrvInvalidParams; +} +//------------------------------------------------------------------------------ +int TSnap7Server::LockArea(int AreaCode, word DBNumber) +{ + int index; + + if ((AreaCode>=srvAreaPE) && (AreaCode<=srvAreaTM)) + { + if (HA[AreaCode]!=0) + { + HA[AreaCode]->cs->Enter(); + return 0; + } + else + return errSrvInvalidParams; + } + else + if (AreaCode==srvAreaDB) + { + index=IndexOfDB(DBNumber); + if (index!=-1) + { + DB[index]->cs->Enter(); + return 0; + } + else + return errSrvInvalidParams; + } + else + return errSrvInvalidParams; +} +//------------------------------------------------------------------------------ +int TSnap7Server::UnlockArea(int AreaCode, word DBNumber) +{ + int index; + + if ((AreaCode>=srvAreaPE) && (AreaCode<=srvAreaTM)) + { + if (HA[AreaCode]!=0) + { + HA[AreaCode]->cs->Leave(); + return 0; + } + else + return errSrvInvalidParams; + } + else + if (AreaCode==srvAreaDB) + { + index=IndexOfDB(DBNumber); + if (index!=-1) + { + DB[index]->cs->Leave(); + return 0; + } + else + return errSrvInvalidParams; + } + else + return errSrvInvalidParams; +} +//------------------------------------------------------------------------------ +int TSnap7Server::SetReadEventsCallBack(pfn_SrvCallBack PCallBack, void *UsrPtr) +{ + OnReadEvent = PCallBack; + FReadUsrPtr = UsrPtr; + return 0; +} +//--------------------------------------------------------------------------- +int TSnap7Server::SetRWAreaCallBack(pfn_RWAreaCallBack PCallBack, void *UsrPtr) +{ + OnRWArea = PCallBack; + FRWAreaUsrPtr = UsrPtr; + ResourceLess = OnRWArea != NULL; + return 0; +} +//--------------------------------------------------------------------------- +void TSnap7Server::DoReadEvent(int Sender, longword Code, word RetCode, word Param1, + word Param2, word Param3, word Param4) +{ + TSrvEvent SrvReadEvent; + if (!Destroying && (OnReadEvent != NULL)) + { + CSEvent->Enter(); + + time(&SrvReadEvent.EvtTime); + SrvReadEvent.EvtSender = Sender; + SrvReadEvent.EvtCode = Code; + SrvReadEvent.EvtRetCode = RetCode; + SrvReadEvent.EvtParam1 = Param1; + SrvReadEvent.EvtParam2 = Param2; + SrvReadEvent.EvtParam3 = Param3; + SrvReadEvent.EvtParam4 = Param4; + + try + { // callback is outside here, we have to shield it + OnReadEvent(FReadUsrPtr, &SrvReadEvent, sizeof (TSrvEvent)); + } catch (...) + { + }; + CSEvent->Leave(); + }; +} +//--------------------------------------------------------------------------- +bool TSnap7Server::DoReadArea(int Sender, int Area, int DBNumber, int Start, int Size, int WordLen, void *pUsrData) +{ + TS7Tag Tag; + bool Result = false; + if (!Destroying && (OnRWArea != NULL)) + { + CSRWHook->Enter(); + try + { + Tag.Area = Area; + Tag.DBNumber = DBNumber; + Tag.Start = Start; + Tag.Size = Size; + Tag.WordLen = WordLen; + // callback is outside here, we have to shield it + Result = OnRWArea(FRWAreaUsrPtr, Sender, OperationRead, &Tag, pUsrData) == 0; + } + catch (...) + { + Result = false; + }; + CSRWHook->Leave(); + } + return Result; +} +//--------------------------------------------------------------------------- +bool TSnap7Server::DoWriteArea(int Sender, int Area, int DBNumber, int Start, int Size, int WordLen, void *pUsrData) +{ + TS7Tag Tag; + bool Result = false; + if (!Destroying && (OnRWArea != NULL)) + { + CSRWHook->Enter(); + try + { + Tag.Area = Area; + Tag.DBNumber = DBNumber; + Tag.Start = Start; + Tag.Size = Size; + Tag.WordLen = WordLen; + // callback is outside here, we have to shield it + Result = OnRWArea(FRWAreaUsrPtr, Sender, OperationWrite, &Tag, pUsrData) == 0; + } + catch (...) + { + Result = false; + }; + CSRWHook->Leave(); + } + return Result; +} + + diff --git a/core/src/drivers/plugins/native/s7comm/snap7/core/s7_server.h b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_server.h new file mode 100644 index 00000000..6d502cac --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_server.h @@ -0,0 +1,261 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 Davide Nardella | +| All rights reserved. | +|==============================================================================| +| SNAP7 is free software: you can redistribute it and/or modify | +| it under the terms of the Lesser GNU General Public License as published by | +| the Free Software Foundation, either version 3 of the License, or | +| (at your option) any later version. | +| | +| It means that you can distribute your commercial software linked with | +| SNAP7 without the requirement to distribute the source code of your | +| application and without the requirement that your application be itself | +| distributed under LGPL. | +| | +| SNAP7 is distributed in the hope that it will be useful, | +| but WITHOUT ANY WARRANTY; without even the implied warranty of | +| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | +| Lesser GNU General Public License for more details. | +| | +| You should have received a copy of the GNU General Public License and a | +| copy of Lesser GNU General Public License along with Snap7. | +| If not, see http://www.gnu.org/licenses/ | +|=============================================================================*/ +#ifndef s7_server_h +#define s7_server_h +//--------------------------------------------------------------------------- +#include "snap_tcpsrvr.h" +#include "s7_types.h" +#include "s7_isotcp.h" +//--------------------------------------------------------------------------- + +// Maximum number of DB, change it to increase/decrease the limit. +// The DB table size is 12*MaxDB bytes + +#define MaxDB 2048 // Like a S7 318 +#define MinPduSize 240 +#define CPU315PduSize 240 +//--------------------------------------------------------------------------- +// Server Interface errors +const longword errSrvDBNullPointer = 0x00200000; // Pssed null as PData +const longword errSrvAreaAlreadyExists = 0x00300000; // Area Re-registration +const longword errSrvUnknownArea = 0x00400000; // Unknown area +const longword errSrvInvalidParams = 0x00500000; // Invalid param(s) supplied +const longword errSrvTooManyDB = 0x00600000; // Cannot register DB +const longword errSrvInvalidParamNumber = 0x00700000; // Invalid param (srv_get/set_param) +const longword errSrvCannotChangeParam = 0x00800000; // Cannot change because running + +// Server Area ID (use with Register/unregister - Lock/unlock Area) +const int srvAreaPE = 0; +const int srvAreaPA = 1; +const int srvAreaMK = 2; +const int srvAreaCT = 3; +const int srvAreaTM = 4; +const int srvAreaDB = 5; + +typedef struct{ + word Number; // Number (only for DB) + word Size; // Area size (in bytes) + pbyte PData; // Pointer to area + PSnapCriticalSection cs; +}TS7Area, *PS7Area; + +//------------------------------------------------------------------------------ +// ISOTCP WORKER CLASS +//------------------------------------------------------------------------------ +class TIsoTcpWorker : public TIsoTcpSocket +{ +protected: + virtual bool IsoPerformCommand(int &Size); + virtual bool ExecuteSend(); + virtual bool ExecuteRecv(); +public: + TIsoTcpWorker(){}; + ~TIsoTcpWorker(){}; + // Worker execution + bool Execute(); +}; +//------------------------------------------------------------------------------ +// S7 WORKER CLASS +//------------------------------------------------------------------------------ + +// SZL frame +typedef struct{ + TS7Answer17 Answer; + PReqFunReadSZLFirst ReqParams; + PS7ReqSZLData ReqData; + PS7ResParams7 ResParams; + pbyte ResData; + int ID; + int Index; + bool SZLDone; +}TSZL; + +// Current Event Info +typedef struct{ + word EvRetCode; + word EvArea; + word EvIndex; + word EvStart; + word EvSize; +}TEv; + +// Current Block info +typedef struct{ + PReqFunGetBlockInfo ReqParams; + PResFunGetBlockInfo ResParams; + TS7Answer17 Answer; + word evError; + word DataLength; +}TCB; + +class TSnap7Server; // forward declaration + +class TS7Worker : public TIsoTcpWorker +{ +private: + PS7ReqHeader PDUH_in; + int DBCnt; + byte LastBlk; + TSZL SZL; + byte BCD(word Value); + // Checks the consistence of the incoming PDU + bool CheckPDU_in(int PayloadSize); + void FillTime(PS7Time PTime); +protected: + int DataSizeByte(int WordLength); + bool ExecuteRecv(); + void DoEvent(longword Code, word RetCode, word Param1, word Param2, + word Param3, word Param4); + void DoReadEvent(longword Code, word RetCode, word Param1, word Param2, + word Param3, word Param4); + void FragmentSkipped(int Size); + // Entry parse + bool IsoPerformCommand(int &Size); + // First stage parse + bool PerformPDUAck(int &Size); + bool PerformPDURequest(int &Size); + bool PerformPDUUsrData(int &Size); + // Second stage parse : PDU Request + PS7Area GetArea(byte S7Code, word index); + // Group Read Area + bool PerformFunctionRead(); + // Subfunctions Read Data + word ReadArea(PResFunReadItem ResItemData, PReqFunReadItem ReqItemPar, + int &PDURemainder,TEv &EV); + word RA_NotFound(PResFunReadItem ResItem, TEv &EV); + word RA_OutOfRange(PResFunReadItem ResItem, TEv &EV); + word RA_SizeOverPDU(PResFunReadItem ResItem, TEv &EV); + // Group Write Area + bool PerformFunctionWrite(); + // Subfunctions Write Data + byte WriteArea(PReqFunWriteDataItem ReqItemData, PReqFunWriteItem ReqItemPar, + TEv &EV); + byte WA_NotFound(TEv &EV); + byte WA_InvalidTransportSize(TEv &EV); + byte WA_OutOfRange(TEv &EV); + byte WA_DataSizeMismatch(TEv &EV); + // Negotiate PDU Length + bool PerformFunctionNegotiate(); + // Control + bool PerformFunctionControl(byte PduFun); + // Up/Download + bool PerformFunctionUpload(); + bool PerformFunctionDownload(); + // Second stage parse : PDU User data + bool PerformGroupProgrammer(); + bool PerformGroupCyclicData(); + bool PerformGroupSecurity(); + // Group Block(s) Info + bool PerformGroupBlockInfo(); + // Subfunctions Block info + void BLK_ListAll(TCB &CB); + void BLK_ListBoT(byte BlockType, bool Start, TCB &CB); + void BLK_NoResource_ListBoT(PDataFunGetBot Data, TCB &CB); + void BLK_GetBlkInfo(TCB &CB); + void BLK_NoResource_GetBlkInfo(PResDataBlockInfo Data, TCB &CB); + void BLK_GetBlockNum_GetBlkInfo(int &BlkNum, PReqDataBlockInfo ReqData); + void BLK_DoBlockInfo_GetBlkInfo(PS7Area DB, PResDataBlockInfo Data, TCB &CB); + // Clock Group + bool PerformGetClock(); + bool PerformSetClock(); + // SZL Group + bool PerformGroupSZL(); + // Subfunctions (called by PerformGroupSZL) + void SZLNotAvailable(); + void SZLSystemState(); + void SZLData(void *P, int len); + void SZL_ID424(); + void SZL_ID131_IDX003(); +public: + TSnap7Server *FServer; + int FPDULength; + TS7Worker(); + ~TS7Worker(){}; +}; + +typedef TS7Worker *PS7Worker; +//------------------------------------------------------------------------------ +// S7 SERVER CLASS +//------------------------------------------------------------------------------ +extern "C" +{ + typedef int (S7API *pfn_RWAreaCallBack)(void *usrPtr, int Sender, int Operation, PS7Tag PTag, void *pUsrData); +} +const int OperationRead = 0; +const int OperationWrite = 1; + +class TSnap7Server : public TCustomMsgServer +{ +private: + // Read Callback related + pfn_SrvCallBack OnReadEvent; + pfn_RWAreaCallBack OnRWArea; + // Critical section to lock Read/Write Hook Area + PSnapCriticalSection CSRWHook; + void *FReadUsrPtr; + void *FRWAreaUsrPtr; + void DisposeAll(); + int FindFirstFreeDB(); + int IndexOfDB(word DBNumber); +protected: + int DBCount; + int DBLimit; + PS7Area DB[MaxDB]; // DB + PS7Area HA[5]; // MK,PE,PA,TM,CT + PS7Area FindDB(word DBNumber); + PWorkerSocket CreateWorkerSocket(socket_t Sock); + bool ResourceLess; + word ForcePDU; + int RegisterDB(word Number, void *pUsrData, word Size); + int RegisterSys(int AreaCode, void *pUsrData, word Size); + int UnregisterDB(word DBNumber); + int UnregisterSys(int AreaCode); + // The Read event + void DoReadEvent(int Sender, longword Code, word RetCode, word Param1, + word Param2, word Param3, word Param4); + bool DoReadArea(int Sender, int Area, int DBNumber, int Start, int Size, int WordLen, void *pUsrData); + bool DoWriteArea(int Sender, int Area, int DBNumber, int Start, int Size, int WordLen, void *pUsrData); +public: + int WorkInterval; + byte CpuStatus; + TSnap7Server(); + ~TSnap7Server(); + int StartTo(const char *Address); + int GetParam(int ParamNumber, void *pValue); + int SetParam(int ParamNumber, void *pValue); + int RegisterArea(int AreaCode, word Index, void *pUsrData, word Size); + int UnregisterArea(int AreaCode, word Index); + int LockArea(int AreaCode, word DBNumber); + int UnlockArea(int AreaCode, word DBNumber); + // Sets Event callback + int SetReadEventsCallBack(pfn_SrvCallBack PCallBack, void *UsrPtr); + int SetRWAreaCallBack(pfn_RWAreaCallBack PCallBack, void *UsrPtr); + friend class TS7Worker; +}; +typedef TSnap7Server *PSnap7Server; + +#endif // s7_server_h + diff --git a/core/src/drivers/plugins/native/s7comm/snap7/core/s7_text.cpp b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_text.cpp new file mode 100644 index 00000000..d1ec4c68 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_text.cpp @@ -0,0 +1,788 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 Davide Nardella | +| All rights reserved. | +|==============================================================================| +| SNAP7 is free software: you can redistribute it and/or modify | +| it under the terms of the Lesser GNU General Public License as published by | +| the Free Software Foundation, either version 3 of the License, or | +| (at your option) any later version. | +| | +| It means that you can distribute your commercial software linked with | +| SNAP7 without the requirement to distribute the source code of your | +| application and without the requirement that your application be itself | +| distributed under LGPL. | +| | +| SNAP7 is distributed in the hope that it will be useful, | +| but WITHOUT ANY WARRANTY; without even the implied warranty of | +| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | +| Lesser GNU General Public License for more details. | +| | +| You should have received a copy of the GNU General Public License and a | +| copy of Lesser GNU General Public License along with Snap7. | +| If not, see http://www.gnu.org/licenses/ | +|=============================================================================*/ +#include "s7_text.h" +//--------------------------------------------------------------------------- +#ifndef OS_WINDOWS +static char* itoa(int value, char* result, int base) { + // check that the base if valid + if (base < 2 || base > 36){ + *result = '\0'; return result; + + } + char* ptr = result, *ptr1 = result, tmp_char; + int tmp_value; + + do { + tmp_value = value; + value /= base; + *ptr++ = "zyxwvutsrqponmlkjihgfedcba9876543210123456789abcdefghijklmnopqrstuvwxyz" [35 + (tmp_value - value * base)]; + } while ( value ); + + // Apply negative sign + if (tmp_value < 0) *ptr++ = '-'; + *ptr-- = '\0'; + while(ptr1 < ptr) { + tmp_char = *ptr; + *ptr--= *ptr1; + *ptr1++ = tmp_char; + } + return result; +} +#endif +//--------------------------------------------------------------------------- +char* NumToString(int Value, int Base, int Len, char* Result) +{ + char CNumber[64]; + char Pad[65] = "0000000000000000000000000000000000000000000000000000000000000000"; + itoa(Value, CNumber, Base); + + if (Len > 0) + { + int Delta = Len - strlen(CNumber); // Len is max 8 in this program + if (Delta > 0) + { + strncpy(Result, Pad, Delta); + Result[Delta] = '\0'; + strcat(Result, CNumber); + } + else + strcpy(Result, CNumber); + } + else + strcpy(Result, CNumber); + + return Result; +} +//--------------------------------------------------------------------------- +char* IntToString(int Value, char* Result) +{ + return NumToString(Value, 10, 0, Result); +} +//--------------------------------------------------------------------------- +char* TimeToString(time_t dt, char* Result) +{ + struct tm * DateTime = localtime(&dt); + if (DateTime != NULL) + strftime(Result, 50, "%Y-%m-%d %H:%M:%S", DateTime); + else + *Result = '\0'; + return Result; +} + +//--------------------------------------------------------------------------- +char* IpAddressToString(int IP, char* Result) +{ + in_addr Addr; + Addr.s_addr = IP; + strcpy(Result, inet_ntoa(Addr)); + return Result; +} +//--------------------------------------------------------------------------- +#define WSAEINVALIDADDRESS 12001 + +char* TcpTextOf(int Error, char* Result) +{ + switch (Error) + { + case 0: *Result='\0';break; + case WSAEINTR: strcpy(Result," TCP : Interrupted system call\0");break; + case WSAEBADF: strcpy(Result," TCP : Bad file number\0");break; + case WSAEACCES: strcpy(Result," TCP : Permission denied\0");break; + case WSAEFAULT: strcpy(Result," TCP : Bad address\0");break; + case WSAEINVAL: strcpy(Result," TCP : Invalid argument\0");break; + case WSAEMFILE: strcpy(Result," TCP : Too many open files\0");break; + case WSAEWOULDBLOCK: strcpy(Result," TCP : Operation would block\0");break; + case WSAEINPROGRESS: strcpy(Result," TCP : Operation now in progress\0");break; + case WSAEALREADY: strcpy(Result," TCP : Operation already in progress\0");break; + case WSAENOTSOCK: strcpy(Result," TCP : Socket operation on non socket\0");break; + case WSAEDESTADDRREQ: strcpy(Result," TCP : Destination address required\0");break; + case WSAEMSGSIZE: strcpy(Result," TCP : Message too long\0");break; + case WSAEPROTOTYPE: strcpy(Result," TCP : Protocol wrong type for Socket\0");break; + case WSAENOPROTOOPT: strcpy(Result," TCP : Protocol not available\0");break; + case WSAEPROTONOSUPPORT: strcpy(Result," TCP : Protocol not supported\0");break; + case WSAESOCKTNOSUPPORT: strcpy(Result," TCP : Socket not supported\0");break; + case WSAEOPNOTSUPP: strcpy(Result," TCP : Operation not supported on Socket\0");break; + case WSAEPFNOSUPPORT: strcpy(Result," TCP : Protocol family not supported\0");break; + case WSAEAFNOSUPPORT: strcpy(Result," TCP : Address family not supported\0");break; + case WSAEADDRINUSE: strcpy(Result," TCP : Address already in use\0");break; + case WSAEADDRNOTAVAIL: strcpy(Result," TCP : Can't assign requested address\0");break; + case WSAENETDOWN: strcpy(Result," TCP : Network is down\0");break; + case WSAENETUNREACH: strcpy(Result," TCP : Network is unreachable\0");break; + case WSAENETRESET: strcpy(Result," TCP : Network dropped connection on reset\0");break; + case WSAECONNABORTED: strcpy(Result," TCP : Software caused connection abort\0");break; + case WSAECONNRESET: strcpy(Result," TCP : Connection reset by peer\0");break; + case WSAENOBUFS: strcpy(Result," TCP : No Buffer space available\0");break; + case WSAEISCONN: strcpy(Result," TCP : Socket is already connected\0");break; + case WSAENOTCONN: strcpy(Result," TCP : Socket is not connected\0");break; + case WSAESHUTDOWN: strcpy(Result," TCP : Can't send after Socket shutdown\0");break; + case WSAETOOMANYREFS: strcpy(Result," TCP : Too many references:can't splice\0");break; + case WSAETIMEDOUT: strcpy(Result," TCP : Connection timed out\0");break; + case WSAECONNREFUSED: strcpy(Result," TCP : Connection refused\0");break; + case WSAELOOP: strcpy(Result," TCP : Too many levels of symbolic links\0");break; + case WSAENAMETOOLONG: strcpy(Result," TCP : File name is too long\0");break; + case WSAEHOSTDOWN: strcpy(Result," TCP : Host is down\0");break; + case WSAEHOSTUNREACH: strcpy(Result," TCP : Unreachable peer\0");break; + case WSAENOTEMPTY: strcpy(Result," TCP : Directory is not empty\0");break; + case WSAEUSERS: strcpy(Result," TCP : Too many users\0");break; + case WSAEDQUOT: strcpy(Result," TCP : Disk quota exceeded\0");break; + case WSAESTALE: strcpy(Result," TCP : Stale NFS file handle\0");break; + case WSAEREMOTE: strcpy(Result," TCP : Too many levels of remote in path\0");break; + #ifdef OS_WINDOWS + case WSAEPROCLIM: strcpy(Result," TCP : Too many processes\0");break; + case WSASYSNOTREADY: strcpy(Result," TCP : Network subsystem is unusable\0");break; + case WSAVERNOTSUPPORTED: strcpy(Result," TCP : Winsock DLL cannot support this application\0");break; + case WSANOTINITIALISED: strcpy(Result," TCP : Winsock not initialized\0");break; + case WSAEDISCON: strcpy(Result," TCP : Disconnect\0");break; + case WSAHOST_NOT_FOUND: strcpy(Result," TCP : Host not found\0");break; + case WSATRY_AGAIN: strcpy(Result," TCP : Non authoritative - host not found\0");break; + case WSANO_RECOVERY: strcpy(Result," TCP : Non recoverable error\0");break; + case WSANO_DATA: strcpy(Result," TCP : Valid name, no data record of requested type\0");break; + #endif + case WSAEINVALIDADDRESS: strcpy(Result," TCP : Invalid address\0");break; + default: + { + char CNumber[16]; + strcpy(Result, " TCP : Other Socket error ("); + strcat(Result, IntToString(Error, CNumber)); + strcat(Result, ")"); + break; + } + } + return Result; +} +//--------------------------------------------------------------------------- +char* IsoTextOf(int Error, char* Result) +{ + switch (Error) + { + case 0 : *Result='\0';break; + case errIsoConnect: strcpy(Result," ISO : Connection error\0");break; + case errIsoDisconnect: strcpy(Result," ISO : Disconnect error\0");break; + case errIsoInvalidPDU: strcpy(Result," ISO : Bad PDU format\0");break; + case errIsoInvalidDataSize: strcpy(Result," ISO : Datasize passed to send/recv buffer is invalid\0");break; + case errIsoNullPointer: strcpy(Result," ISO : Null passed as pointer\0");break; + case errIsoShortPacket: strcpy(Result," ISO : A short packet received\0");break; + case errIsoTooManyFragments: strcpy(Result," ISO : Too many packets without EoT flag\0");break; + case errIsoPduOverflow: strcpy(Result," ISO : The sum of fragments data exceded maximum packet size\0");break; + case errIsoSendPacket: strcpy(Result," ISO : An error occurred during send\0");break; + case errIsoRecvPacket: strcpy(Result," ISO : An error occurred during recv\0");break; + case errIsoInvalidParams: strcpy(Result," ISO : Invalid connection params (wrong TSAPs)\0");break; + default: + { + char CNumber[16]; + strcpy(Result, " ISO : Unknown error (0x"); + strcat(Result, NumToString(Error, 16, 8, CNumber)); + strcat(Result, ")"); + break; + } + } + return Result; +} +//--------------------------------------------------------------------------- +char* CliTextOf(int Error, char* Result) +{ + switch (Error) + { + case 0 : *Result='\0';break; + case errNegotiatingPDU : strcpy(Result,"CPU : Error in PDU negotiation\0");break; + case errCliInvalidParams : strcpy(Result,"CLI : invalid param(s) supplied\0");break; + case errCliJobPending : strcpy(Result,"CLI : Job pending\0");break; + case errCliTooManyItems : strcpy(Result,"CLI : too may items (>20) in multi read/write\0");break; + case errCliInvalidWordLen : strcpy(Result,"CLI : invalid WordLength\0");break; + case errCliPartialDataWritten : strcpy(Result,"CLI : Partial data written\0");break; + case errCliSizeOverPDU : strcpy(Result,"CPU : total data exceeds the PDU size\0");break; + case errCliInvalidPlcAnswer : strcpy(Result,"CLI : invalid CPU answer\0");break; + case errCliAddressOutOfRange : strcpy(Result,"CPU : Address out of range\0");break; + case errCliInvalidTransportSize : strcpy(Result,"CPU : Invalid Transport size\0");break; + case errCliWriteDataSizeMismatch : strcpy(Result,"CPU : Data size mismatch\0");break; + case errCliItemNotAvailable : strcpy(Result,"CPU : Item not available\0");break; + case errCliInvalidValue : strcpy(Result,"CPU : Invalid value supplied\0");break; + case errCliCannotStartPLC : strcpy(Result,"CPU : Cannot start PLC\0");break; + case errCliAlreadyRun : strcpy(Result,"CPU : PLC already RUN\0");break; + case errCliCannotStopPLC : strcpy(Result,"CPU : Cannot stop PLC\0");break; + case errCliCannotCopyRamToRom : strcpy(Result,"CPU : Cannot copy RAM to ROM\0");break; + case errCliCannotCompress : strcpy(Result,"CPU : Cannot compress\0");break; + case errCliAlreadyStop : strcpy(Result,"CPU : PLC already STOP\0");break; + case errCliFunNotAvailable : strcpy(Result,"CPU : Function not available\0");break; + case errCliUploadSequenceFailed : strcpy(Result,"CPU : Upload sequence failed\0");break; + case errCliInvalidDataSizeRecvd : strcpy(Result,"CLI : Invalid data size received\0");break; + case errCliInvalidBlockType : strcpy(Result,"CLI : Invalid block type\0");break; + case errCliInvalidBlockNumber : strcpy(Result,"CLI : Invalid block number\0");break; + case errCliInvalidBlockSize : strcpy(Result,"CLI : Invalid block size\0");break; + case errCliDownloadSequenceFailed : strcpy(Result,"CPU : Download sequence failed\0");break; + case errCliInsertRefused : strcpy(Result,"CPU : block insert refused\0");break; + case errCliDeleteRefused : strcpy(Result,"CPU : block delete refused\0");break; + case errCliNeedPassword : strcpy(Result,"CPU : Function not authorized for current protection level\0");break; + case errCliInvalidPassword : strcpy(Result,"CPU : Invalid password\0");break; + case errCliNoPasswordToSetOrClear : strcpy(Result,"CPU : No password to set or clear\0");break; + case errCliJobTimeout : strcpy(Result,"CLI : Job Timeout\0");break; + case errCliFunctionRefused : strcpy(Result,"CLI : function refused by CPU (Unknown error)\0");break; + case errCliPartialDataRead : strcpy(Result,"CLI : Partial data read\0");break; + case errCliBufferTooSmall : strcpy(Result,"CLI : The buffer supplied is too small to accomplish the operation\0");break; + case errCliDestroying : strcpy(Result,"CLI : Cannot perform (destroying)\0");break; + case errCliInvalidParamNumber : strcpy(Result,"CLI : Invalid Param Number\0");break; + case errCliCannotChangeParam : strcpy(Result,"CLI : Cannot change this param now\0");break; + default : + { + char CNumber[16]; + strcpy(Result, "CLI : Unknown error (0x"); + strcat(Result, NumToString(Error, 16, 8, CNumber)); + strcat(Result, ")"); + break; + } + }; + return Result; +} +//--------------------------------------------------------------------------- +char* SrvTextOf(int Error, char* Result) +{ + switch (Error) + { + case 0: *Result = '\0'; break; + case errSrvCannotStart: strcpy(Result, "SRV : Server cannot start\0"); break; + case errSrvDBNullPointer: strcpy(Result, "SRV : Null passed as area pointer\0"); break; + case errSrvAreaAlreadyExists: strcpy(Result, "SRV : Cannot register area since already exists\0"); break; + case errSrvUnknownArea: strcpy(Result, "SRV : Unknown Area code\0"); break; + case errSrvInvalidParams: strcpy(Result, "SRV : Invalid param(s) supplied\0"); break; + case errSrvTooManyDB: strcpy(Result, "SRV : DB Limit reached\0"); break; + case errSrvInvalidParamNumber: strcpy(Result, "SRV : Invalid Param Number\0"); break; + case errSrvCannotChangeParam: strcpy(Result, "SRV : Cannot change this param now\0");break; + default: + { + char CNumber[16]; + strcpy(Result, "SRV : Unknown error (0x"); + strcat(Result, NumToString(Error, 16, 8, CNumber)); + strcat(Result, ")"); + break; + } + }; + return Result; +} +//--------------------------------------------------------------------------- +char* ParTextOf(int Error, char* Result) +{ + switch(Error) + { + case 0: *Result = '\0'; break; + case errParAddressInUse : strcpy(Result, "PAR : Local address already in use");break; + case errParNoRoom : strcpy(Result, "PAR : No more partners available");break; + case errServerNoRoom : strcpy(Result, "PAR : No more servers available");break; + case errParInvalidParams : strcpy(Result, "PAR : Invalid parameter supplied");break; + case errParNotLinked : strcpy(Result, "PAR : Cannot perform, Partner not linked");break; + case errParBusy : strcpy(Result, "PAR : Cannot perform, Partner Busy");break; + case errParFrameTimeout : strcpy(Result, "PAR : Frame timeout");break; + case errParInvalidPDU : strcpy(Result, "PAR : Invalid PDU received");break; + case errParSendTimeout : strcpy(Result, "PAR : Send timeout");break; + case errParRecvTimeout : strcpy(Result, "PAR : Recv timeout");break; + case errParSendRefused : strcpy(Result, "PAR : Send refused by peer");break; + case errParNegotiatingPDU : strcpy(Result, "PAR : Error negotiating PDU");break; + case errParSendingBlock : strcpy(Result, "PAR : Error Sending Block");break; + case errParRecvingBlock : strcpy(Result, "PAR : Error Receiving Block");break; + case errParBindError : strcpy(Result, "PAR : Error Binding");break; + case errParDestroying : strcpy(Result, "PAR : Cannot perform (destroying)");break; + case errParInvalidParamNumber: strcpy(Result, "PAR : Invalid Param Number");break; + case errParCannotChangeParam : strcpy(Result, "PAR : Cannot change this param now");break; + case errParBufferTooSmall : strcpy(Result, "PAR : The buffer supplied is too small to accomplish the operation");break; + default: + { + char CNumber[16]; + strcpy(Result, "PAR : Unknown error (0x"); + strcat(Result, NumToString(Error, 16, 8, CNumber)); + strcat(Result, ")"); + break; + } + } + return Result; +} +//--------------------------------------------------------------------------- +char* ErrCliText(int Error, char * Result, int TextLen) +{ + char TcpError[128]; + char IsoError[128]; + char CliError[256]; + if (Error != 0) + { + switch (Error) + { + case errLibInvalidParam : strncpy(Result,"LIB : Invalid param supplied\0",TextLen);break; + case errLibInvalidObject: strncpy(Result, "LIB : Invalid object supplied\0", TextLen); break; + default : + { + CliTextOf(Error & ErrS7Mask, CliError); + strcat(CliError, IsoTextOf(Error & ErrIsoMask, IsoError)); + strcat(CliError, TcpTextOf(Error & ErrTcpMask, TcpError)); + strncpy(Result, CliError, TextLen); + } + } + } + else + strncpy(Result, "OK\0", TextLen); + return Result; +} +//--------------------------------------------------------------------------- +char* ErrSrvText(int Error, char* Result, int TextLen) +{ + char TcpError[128]; + char IsoError[128]; + char SrvError[256]; + if (Error != 0) + { + switch (Error) + { + case errLibInvalidParam: strncpy(Result, "LIB : Invalid param supplied\0", TextLen); break; + case errLibInvalidObject: strncpy(Result, "LIB : Invalid object supplied\0", TextLen); break; + default: + { + SrvTextOf(Error & ErrS7Mask, SrvError); + strcat(SrvError, IsoTextOf(Error & ErrIsoMask, IsoError)); + strcat(SrvError, TcpTextOf(Error & ErrTcpMask, TcpError)); + strncpy(Result, SrvError, TextLen); + } + } + } + else + strncpy(Result, "OK\0", TextLen); + return Result; +} +//--------------------------------------------------------------------------- +char* ErrParText(int Error, char* Result, int TextLen) +{ + char TcpError[128]; + char IsoError[128]; + char ParError[256]; + if (Error != 0) + { + switch (Error) + { + case errLibInvalidParam: strncpy(Result, "LIB : Invalid param supplied\0", TextLen); break; + case errLibInvalidObject: strncpy(Result, "LIB : Invalid object supplied\0", TextLen); break; + default: + { + ParTextOf(Error & ErrS7Mask, ParError); + strcat(ParError, IsoTextOf(Error & ErrIsoMask, IsoError)); + strcat(ParError, TcpTextOf(Error & ErrTcpMask, TcpError)); + strncpy(Result, ParError, TextLen); + } + } + } + else + strncpy(Result, "OK\0", TextLen); + return Result; +} +//--------------------------------------------------------------------------- +// SERVER EVENTS TEXT +//--------------------------------------------------------------------------- +char* SenderText(TSrvEvent &Event, char* Result) +{ + char Buf[64]; + char Add[16]; + TimeToString(Event.EvtTime, Buf); + if (Event.EvtSender != 0) + { + strcat(Buf, " ["); + strcat(Buf, IpAddressToString(Event.EvtSender, Add)); + strcat(Buf, "] "); + } + else + strcat(Buf, " Server "); + strcpy(Result, Buf); + return Result; +} +//--------------------------------------------------------------------------- +char* TcpServerEventText(TSrvEvent &Event, char* Result) +{ + char S[256]; + char Buf[128]; + + strcpy(S, SenderText(Event, Buf)); + + switch (Event.EvtCode) + { + case evcServerStarted : strcat(S,"started");break; + case evcServerStopped : strcat(S,"stopped");break; + case evcListenerCannotStart: + strcat(S, "Cannot start listener - Socket Error : "); + strcat(S, TcpTextOf(Event.EvtRetCode,Buf)); + break; + case evcClientAdded : strcat(S,"Client added");break; + case evcClientRejected : strcat(S,"Client refused");break; + case evcClientNoRoom : strcat(S,"A client was refused due to maximum connections number");break; + case evcClientException : strcat(S,"Client exception");break; + case evcClientDisconnected : strcat(S,"Client disconnected by peer");break; + case evcClientTerminated : strcat(S,"Client terminated");break; + case evcClientsDropped: + strcat(S, IntToString(Event.EvtParam1, Buf)); + strcat(S, " clients have been dropped bacause unresponsive"); + break; + default: + strcat(S, "Unknown event ("); + strcat(S, IntToString(Event.EvtCode, Buf)); + strcat(S,")"); + break; + }; + strcpy(Result, S); + return Result; +} +//--------------------------------------------------------------------------- +char* PDUText(TSrvEvent &Event, char* Result) +{ + char S[256]; + char Buf[128]; + switch (Event.EvtRetCode) + { + case evrFragmentRejected: + strcpy(S, "Fragment of "); + strcat(S, IntToString(Event.EvtParam1, Buf)); + strcat(S, " bytes rejected"); + break; + case evrMalformedPDU: + strcpy(S, "Malformed PDU of "); + strcat(S, IntToString(Event.EvtParam1, Buf)); + strcat(S, " bytes rejected"); + break; + case evrSparseBytes: + strcpy(S, "Message of sparse "); + strcat(S, IntToString(Event.EvtParam1, Buf)); + strcat(S, " bytes rejected"); + break; + case evrCannotHandlePDU: + strcpy(S, "Cannot handle this PDU"); + break; + case evrNotImplemented: + switch (Event.EvtParam1) + { + case grCyclicData: + strcpy(S, "Function group cyclic data not yet implemented"); + break; + case grProgrammer: + strcpy(S, "Function group programmer not yet implemented"); + break; + } + break; + default: + strcpy(S, "Unknown Return code ("); + strcat(S, IntToString(Event.EvtRetCode, Buf)); + strcat(S, ")"); + break; + } + strcpy(Result, S); + return Result; +} +//--------------------------------------------------------------------------- +char* TxtArea(TSrvEvent &Event, char* Result) +{ + char S[64]; + char Buf[32]; + switch (Event.EvtParam1) + { + case S7AreaPE: strcpy(S, "Area : PE, "); break; + case S7AreaPA: strcpy(S, "Area : PA, "); break; + case S7AreaMK: strcpy(S, "Area : MK, "); break; + case S7AreaCT: strcpy(S, "Area : CT, "); break; + case S7AreaTM: strcpy(S, "Area : TM, "); break; + case S7AreaDB: + strcpy(S, "Area : DB"); + strcat(S, IntToString(Event.EvtParam2, Buf)); + strcat(S,", "); + break; + default: + strcpy(S, "Unknown area ("); + strcat(S, IntToString(Event.EvtParam2, Buf)); + strcat(S,")"); + break; + } + strcpy(Result, S); + return Result; +} +//--------------------------------------------------------------------------- +char* TxtStartSize(TSrvEvent &Event, char* Result) +{ + char N[32]; + strcpy(Result, "Start : "); + strcat(Result, IntToString(Event.EvtParam3, N)); + strcat(Result, ", Size : "); + strcat(Result, IntToString(Event.EvtParam4, N)); + return Result; +} +//--------------------------------------------------------------------------- +char* TxtDataResult(TSrvEvent &Event, char* Result) +{ + char N[32]; + switch (Event.EvtRetCode) + { + case evrNoError: + strcpy(Result," --> OK"); + break; + case evrErrException: + strcpy(Result, " --> Exception error"); + break; + case evrErrAreaNotFound: + strcpy(Result, " --> Area not found"); + break; + case evrErrOutOfRange: + strcpy(Result, " --> Out of range"); + break; + case evrErrOverPDU: + strcpy(Result, " --> Data size exceeds PDU size"); + break; + case evrErrTransportSize: + strcpy(Result, " --> Invalid transport size"); + break; + case evrDataSizeMismatch: + strcpy(Result, " --> Data size mismatch"); + break; + default: + strcpy(Result, " --> Unknown error code ("); + strcat(Result, IntToString(Event.EvtRetCode, N)); + strcat(Result,")"); + break; + }; + return Result; +} +//--------------------------------------------------------------------------- +char* ControlText(word Code, char* Result) +{ + char N[64]; + strcpy(Result, "CPU Control request : "); + switch (Code) + { + case CodeControlUnknown: + strcat(Result,"Unknown"); + break; + case CodeControlColdStart: + strcat(Result, "Cold START --> OK"); + break; + case CodeControlWarmStart: + strcat(Result, "Warm START --> OK"); + break; + case CodeControlStop: + strcat(Result, "STOP --> OK"); + break; + case CodeControlCompress: + strcat(Result, "Memory compress --> OK"); + break; + case CodeControlCpyRamRom: + strcat(Result, "Copy Ram to Rom --> OK"); + break; + case CodeControlInsDel: + strcat(Result, "Block Insert or Delete --> OK"); + break; + default : + strcat(Result, "Unknown control code ("); + strcat(Result, IntToString(Code, N)); + strcat(Result,")"); + } + return Result; +} +//--------------------------------------------------------------------------- +char* ClockText(word Code, char* Result) +{ + if (Code==evsGetClock) + strcpy(Result,"System clock read requested"); + else + strcpy(Result, "System clock write requested"); + return Result; +} +//--------------------------------------------------------------------------- +char* ReadSZLText(TSrvEvent &Event, char* Result) +{ + char S[128]; + char N[64]; + strcpy(S, "Read SZL request, ID:0x"); + strcat(S, NumToString(Event.EvtParam1, 16, 4, N)); + strcat(S, " INDEX:0x"); + strcat(S, NumToString(Event.EvtParam2, 16, 4, N)); + + if (Event.EvtRetCode == evrNoError) + strcat(S, " --> OK"); + else + strcat(S, " --> NOT AVAILABLE"); + strcpy(Result, S); + return Result; +} +//--------------------------------------------------------------------------- +char* UploadText(TSrvEvent &Event, char* Result) +{ + strcpy(Result,"Block upload requested --> NOT PERFORMED (due to invalid security level)"); + return Result; +} +//--------------------------------------------------------------------------- +char* DownloadText(TSrvEvent &Event, char* Result) +{ + strcpy(Result, "Block download requested --> NOT PERFORMED (due to invalid security level)"); + return Result; +} +//--------------------------------------------------------------------------- +char* StrBlockType(word Code, char* Result) +{ + char N[64]; + switch (Code) + { + case Block_OB: + strcpy(Result, "OB"); + break; + case Block_DB: + strcpy(Result, "DB"); + break; + case Block_SDB: + strcpy(Result, "SDB"); + break; + case Block_FC: + strcpy(Result, "FC"); + break; + case Block_SFC: + strcpy(Result, "SFC"); + break; + case Block_FB: + strcpy(Result, "FB"); + break; + case Block_SFB: + strcpy(Result, "SFB"); + break; + default: + strcpy(Result, "[Unknown 0x"); + strcat(Result, NumToString(Code, 16, 4, N)); + strcat(Result,"]"); + break; + }; + return Result; +} +//--------------------------------------------------------------------------- +char* BlockInfoText(TSrvEvent &Event, char* Result) +{ + char S[64]; + switch (Event.EvtParam1) + { + case evsGetBlockList: + strcpy(Result, "Block list requested"); + break; + case evsStartListBoT: + strcpy(Result, "Block of type "); + strcat(Result, StrBlockType(Event.EvtParam2,S)); + strcat(Result, " list requested (start sequence)"); + break; + case evsListBoT: + strcpy(Result, "Block of type "); + strcat(Result, StrBlockType(Event.EvtParam2, S)); + strcat(Result, " list requested (next part)"); + break; + case evsGetBlockInfo: + strcpy(Result, "Block info requested "); + strcat(Result, StrBlockType(Event.EvtParam2, S)); + strcat(Result, " "); + strcat(Result, IntToString(Event.EvtParam3,S)); + break; + }; + if (Event.EvtRetCode == evrNoError) + strcat(Result, " --> OK"); + else + strcat(Result, " --> NOT AVAILABLE"); + return Result; +} +//--------------------------------------------------------------------------- +char* SecurityText(TSrvEvent &Event, char* Result) +{ + switch (Event.EvtParam1) + { + case evsSetPassword: + strcpy(Result,"Security request : Set session password --> OK"); + break; + case evsClrPassword: + strcpy(Result, "Security request : Clear session password --> OK"); + break; + default: + strcpy(Result, "Security request : Unknown Subfunction"); + break; + }; + return Result; +} +//--------------------------------------------------------------------------- +char* EvtSrvText(TSrvEvent &Event, char* Result, int TextLen) +{ + char S[256]; + char C[128]; + + if (Event.EvtCode > evcSnap7Base) + { + strcpy(S, SenderText(Event, C)); + switch (Event.EvtCode) + { + case evcPDUincoming: + strcat(S, "PDU incoming : "); + strcat(S,PDUText(Event,C)); + break; + case evcDataRead: + strcat(S, "Read request, "); + strcat(S, TxtArea(Event, C)); + strcat(S, TxtStartSize(Event, C)); + strcat(S, TxtDataResult(Event, C)); + break; + case evcDataWrite: + strcat(S, "Write request, "); + strcat(S, TxtArea(Event, C)); + strcat(S, TxtStartSize(Event, C)); + strcat(S, TxtDataResult(Event, C)); + break; + case evcNegotiatePDU: + strcat(S, "The client requires a PDU size of "); + strcat(S, IntToString(Event.EvtParam1, C)); + strcat(S," bytes"); + break; + case evcControl: + strcat(S, ControlText(Event.EvtParam1,C)); + break; + case evcReadSZL: + strcat(S, ReadSZLText(Event,C)); + break; + case evcClock: + strcat(S, ClockText(Event.EvtParam1,C)); + break; + case evcUpload: + strcat(S, UploadText(Event,C)); + break; + case evcDownload: + strcat(S, DownloadText(Event,C)); + break; + case evcDirectory: + strcat(S, BlockInfoText(Event,C)); + break; + case evcSecurity: + strcat(S, SecurityText(Event,C)); + break; + default: + strcat(S, "Unknown event ("); + strcat(S, IntToString(Event.EvtCode, C)); + strcat(S,")"); + break; + } + } + else + strcpy(S,TcpServerEventText(Event,C)); + + strncpy(Result, S, TextLen); + return Result; +} + diff --git a/core/src/drivers/plugins/native/s7comm/snap7/core/s7_text.h b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_text.h new file mode 100644 index 00000000..b8b5ba3d --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_text.h @@ -0,0 +1,49 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 Davide Nardella | +| All rights reserved. | +|==============================================================================| +| SNAP7 is free software: you can redistribute it and/or modify | +| it under the terms of the Lesser GNU General Public License as published by | +| the Free Software Foundation, either version 3 of the License, or | +| (at your option) any later version. | +| | +| It means that you can distribute your commercial software linked with | +| SNAP7 without the requirement to distribute the source code of your | +| application and without the requirement that your application be itself | +| distributed under LGPL. | +| | +| SNAP7 is distributed in the hope that it will be useful, | +| but WITHOUT ANY WARRANTY; without even the implied warranty of | +| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | +| Lesser GNU General Public License for more details. | +| | +| You should have received a copy of the GNU General Public License and a | +| copy of Lesser GNU General Public License along with Snap7. | +| If not, see http://www.gnu.org/licenses/ | +|=============================================================================*/ +#ifndef s7_text_h +#define s7_text_h +//--------------------------------------------------------------------------- +#include "s7_micro_client.h" +#include "s7_server.h" +#include "s7_partner.h" +//--------------------------------------------------------------------------- + +const int errLibInvalidParam = -1; +const int errLibInvalidObject = -2; +// Errors areas definition +const longword ErrTcpMask = 0x0000FFFF; +const longword ErrIsoMask = 0x000F0000; +const longword ErrS7Mask = 0xFFF00000; + +char* ErrCliText(int Error, char* Result, int TextLen); +char* ErrSrvText(int Error, char* Result, int TextLen); +char* ErrParText(int Error, char* Result, int TextLen); +char* EvtSrvText(TSrvEvent &Event, char* Result, int TextLen); + + +#endif + + diff --git a/core/src/drivers/plugins/native/s7comm/snap7/core/s7_types.h b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_types.h new file mode 100644 index 00000000..eca31c62 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/core/s7_types.h @@ -0,0 +1,1066 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 Davide Nardella | +| All rights reserved. | +|==============================================================================| +| SNAP7 is free software: you can redistribute it and/or modify | +| it under the terms of the Lesser GNU General Public License as published by | +| the Free Software Foundation, either version 3 of the License, or | +| (at your option) any later version. | +| | +| It means that you can distribute your commercial software linked with | +| SNAP7 without the requirement to distribute the source code of your | +| application and without the requirement that your application be itself | +| distributed under LGPL. | +| | +| SNAP7 is distributed in the hope that it will be useful, | +| but WITHOUT ANY WARRANTY; without even the implied warranty of | +| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | +| Lesser GNU General Public License for more details. | +| | +| You should have received a copy of the GNU General Public License and a | +| copy of Lesser GNU General Public License along with Snap7. | +| If not, see http://www.gnu.org/licenses/ | +|=============================================================================*/ +#ifndef s7_types_h +#define s7_types_h +//------------------------------------------------------------------------------ +#include "s7_isotcp.h" +//------------------------------------------------------------------------------ +// EXPORT CONSTANTS +// Everything added in this section has to be copied into wrappers interface +//------------------------------------------------------------------------------ + +#ifdef OS_WINDOWS +#define SM7API __stdcall +#else +#define SM7API +#endif + + // Area ID +const byte S7AreaPE = 0x81; +const byte S7AreaPA = 0x82; +const byte S7AreaMK = 0x83; +const byte S7AreaDB = 0x84; +const byte S7AreaCT = 0x1C; +const byte S7AreaTM = 0x1D; + +const int MaxVars = 20; + +const int S7WLBit = 0x01; +const int S7WLByte = 0x02; +const int S7WLChar = 0x03; +const int S7WLWord = 0x04; +const int S7WLInt = 0x05; +const int S7WLDWord = 0x06; +const int S7WLDInt = 0x07; +const int S7WLReal = 0x08; +const int S7WLCounter = 0x1C; +const int S7WLTimer = 0x1D; + + // Block type +const byte Block_OB = 0x38; +const byte Block_DB = 0x41; +const byte Block_SDB = 0x42; +const byte Block_FC = 0x43; +const byte Block_SFC = 0x44; +const byte Block_FB = 0x45; +const byte Block_SFB = 0x46; + + // Sub Block Type +const byte SubBlk_OB = 0x08; +const byte SubBlk_DB = 0x0A; +const byte SubBlk_SDB = 0x0B; +const byte SubBlk_FC = 0x0C; +const byte SubBlk_SFC = 0x0D; +const byte SubBlk_FB = 0x0E; +const byte SubBlk_SFB = 0x0F; + + // Block languages +const byte BlockLangAWL = 0x01; +const byte BlockLangKOP = 0x02; +const byte BlockLangFUP = 0x03; +const byte BlockLangSCL = 0x04; +const byte BlockLangDB = 0x05; +const byte BlockLangGRAPH = 0x06; + + // CPU status +const byte S7CpuStatusUnknown = 0x00; +const byte S7CpuStatusRun = 0x08; +const byte S7CpuStatusStop = 0x04; + +const longword evcSnap7Base = 0x00008000; +// S7 Server Event Code +const longword evcPDUincoming = 0x00010000; +const longword evcDataRead = 0x00020000; +const longword evcDataWrite = 0x00040000; +const longword evcNegotiatePDU = 0x00080000; +const longword evcReadSZL = 0x00100000; +const longword evcClock = 0x00200000; +const longword evcUpload = 0x00400000; +const longword evcDownload = 0x00800000; +const longword evcDirectory = 0x01000000; +const longword evcSecurity = 0x02000000; +const longword evcControl = 0x04000000; +const longword evcReserved_08000000 = 0x08000000; +const longword evcReserved_10000000 = 0x10000000; +const longword evcReserved_20000000 = 0x20000000; +const longword evcReserved_40000000 = 0x40000000; +const longword evcReserved_80000000 = 0x80000000; +// Event SubCodes +const word evsUnknown = 0x0000; +const word evsStartUpload = 0x0001; +const word evsStartDownload = 0x0001; +const word evsGetBlockList = 0x0001; +const word evsStartListBoT = 0x0002; +const word evsListBoT = 0x0003; +const word evsGetBlockInfo = 0x0004; +const word evsGetClock = 0x0001; +const word evsSetClock = 0x0002; +const word evsSetPassword = 0x0001; +const word evsClrPassword = 0x0002; +// Event Result +const word evrNoError = 0; +const word evrFragmentRejected = 0x0001; +const word evrMalformedPDU = 0x0002; +const word evrSparseBytes = 0x0003; +const word evrCannotHandlePDU = 0x0004; +const word evrNotImplemented = 0x0005; +const word evrErrException = 0x0006; +const word evrErrAreaNotFound = 0x0007; +const word evrErrOutOfRange = 0x0008; +const word evrErrOverPDU = 0x0009; +const word evrErrTransportSize = 0x000A; +const word evrInvalidGroupUData = 0x000B; +const word evrInvalidSZL = 0x000C; +const word evrDataSizeMismatch = 0x000D; +const word evrCannotUpload = 0x000E; +const word evrCannotDownload = 0x000F; +const word evrUploadInvalidID = 0x0010; +const word evrResNotFound = 0x0011; + + // Async mode +const int amPolling = 0; +const int amEvent = 1; +const int amCallBack = 2; + +//------------------------------------------------------------------------------ +// PARAMS LIST +// Notes for Local/Remote Port +// If the local port for a server and remote port for a client is != 102 they +// will be *no more compatible with S7 IsoTCP* +// A good reason to change them could be inside a debug session under Unix. +// Increasing the port over 1024 avoids the need of be root. +// Obviously you need to work with the couple Snap7Client/Snap7Server and change +// both, or, use iptable and nat the port. +//------------------------------------------------------------------------------ +const int p_u16_LocalPort = 1; +const int p_u16_RemotePort = 2; +const int p_i32_PingTimeout = 3; +const int p_i32_SendTimeout = 4; +const int p_i32_RecvTimeout = 5; +const int p_i32_WorkInterval = 6; +const int p_u16_SrcRef = 7; +const int p_u16_DstRef = 8; +const int p_u16_SrcTSap = 9; +const int p_i32_PDURequest = 10; +const int p_i32_MaxClients = 11; +const int p_i32_BSendTimeout = 12; +const int p_i32_BRecvTimeout = 13; +const int p_u32_RecoveryTime = 14; +const int p_u32_KeepAliveTime = 15; + +// Bool param is passed as int32_t : 0->false, 1->true +// String param (only set) is passed as pointer + +typedef int16_t *Pint16_t; +typedef uint16_t *Puint16_t; +typedef int32_t *Pint32_t; +typedef uint32_t *Puint32_t; +typedef int64_t *Pint64_t; +typedef uint64_t *Puint64_t; +typedef uintptr_t *Puintptr_t; +//----------------------------------------------------------------------------- +// INTERNALS CONSTANTS +//------------------------------------------------------------------------------ + +const word DBMaxName = 0xFFFF; // max number (name) of DB + +const longword errS7Mask = 0xFFF00000; +const longword errS7Base = 0x000FFFFF; +const longword errS7notConnected = errS7Base+0x0001; // Client not connected +const longword errS7InvalidMode = errS7Base+0x0002; // Requested a connection to... +const longword errS7InvalidPDUin = errS7Base+0x0003; // Malformed input PDU + +// S7 outcoming Error code +const word Code7Ok = 0x0000; +const word Code7AddressOutOfRange = 0x0005; +const word Code7InvalidTransportSize = 0x0006; +const word Code7WriteDataSizeMismatch = 0x0007; +const word Code7ResItemNotAvailable = 0x000A; +const word Code7ResItemNotAvailable1 = 0xD209; +const word Code7InvalidValue = 0xDC01; +const word Code7NeedPassword = 0xD241; +const word Code7InvalidPassword = 0xD602; +const word Code7NoPasswordToClear = 0xD604; +const word Code7NoPasswordToSet = 0xD605; +const word Code7FunNotAvailable = 0x8104; +const word Code7DataOverPDU = 0x8500; + +// Result transport size +const byte TS_ResBit = 0x03; +const byte TS_ResByte = 0x04; +const byte TS_ResInt = 0x05; +const byte TS_ResReal = 0x07; +const byte TS_ResOctet = 0x09; + +// Client Job status (lib internals, not S7) +const int JobComplete = 0; +const int JobPending = 1; + +// Control codes +const word CodeControlUnknown = 0; +const word CodeControlColdStart = 1; // Cold start +const word CodeControlWarmStart = 2; // Warm start +const word CodeControlStop = 3; // Stop +const word CodeControlCompress = 4; // Compress +const word CodeControlCpyRamRom = 5; // Copy Ram to Rom +const word CodeControlInsDel = 6; // Insert in working ram the block downloaded + // Delete from working ram the block selected +// PDU Type +const byte PduType_request = 1; // family request +const byte PduType_response = 3; // family response +const byte PduType_userdata = 7; // family user data + +// PDU Functions +const byte pduResponse = 0x02; // Response (when error) +const byte pduFuncRead = 0x04; // Read area +const byte pduFuncWrite = 0x05; // Write area +const byte pduNegotiate = 0xF0; // Negotiate PDU length +const byte pduStart = 0x28; // CPU start +const byte pduStop = 0x29; // CPU stop +const byte pduStartUpload = 0x1D; // Start Upload +const byte pduUpload = 0x1E; // Upload +const byte pduEndUpload = 0x1F; // EndUpload +const byte pduReqDownload = 0x1A; // Start Download request +const byte pduDownload = 0x1B; // Download request +const byte pduDownloadEnded = 0x1C; // Download end request +const byte pduControl = 0x28; // Control (insert/delete..) + +// PDU SubFunctions +const byte SFun_ListAll = 0x01; // List all blocks +const byte SFun_ListBoT = 0x02; // List Blocks of type +const byte SFun_BlkInfo = 0x03; // Get Block info +const byte SFun_ReadSZL = 0x01; // Read SZL +const byte SFun_ReadClock = 0x01; // Read Clock (Date and Time) +const byte SFun_SetClock = 0x02; // Set Clock (Date and Time) +const byte SFun_EnterPwd = 0x01; // Enter password for this session +const byte SFun_CancelPwd = 0x02; // Cancel password for this session +const byte SFun_Insert = 0x50; // Insert block +const byte SFun_Delete = 0x42; // Delete block + +typedef tm *PTimeStruct; + +//============================================================================== +// HEADERS +//============================================================================== +#pragma pack(1) + +// Tag Struct +typedef struct{ + int Area; + int DBNumber; + int Start; + int Size; + int WordLen; +}TS7Tag, *PS7Tag; + +// Incoming header, it will be mapped onto IsoPDU payload +typedef struct { + byte P; // Telegram ID, always 32 + byte PDUType; // Header type 1 or 7 + word AB_EX; // AB currently unknown, maybe it can be used for long numbers. + word Sequence; // Message ID. This can be used to make sure a received answer + word ParLen; // Length of parameters which follow this header + word DataLen; // Length of data which follow the parameters +}TS7ReqHeader; + +typedef TS7ReqHeader* PS7ReqHeader; + +// Outcoming 12 bytes header , response for Request type 1 +typedef struct{ + byte P; // Telegram ID, always 32 + byte PDUType; // Header type 2 or 3 + word AB_EX; // AB currently unknown, maybe it can be used for long numbers. + word Sequence; // Message ID. This can be used to make sure a received answer + word ParLen; // Length of parameters which follow this header + word DataLen; // Length of data which follow the parameters + word Error; // Error code +} TS7ResHeader23; + +typedef TS7ResHeader23* PS7ResHeader23; + +// Outcoming 10 bytes header , response for Request type 7 +typedef struct{ + byte P; // Telegram ID, always 32 + byte PDUType; // Header type 1 or 7 + word AB_EX; // AB currently unknown, maybe it can be used for long numbers. + word Sequence; // Message ID. This can be used to make sure a received answer + word ParLen; // Length of parameters which follow this header + word DataLen; // Length of data which follow the parameters +}TS7ResHeader17; + +typedef TS7ResHeader17* PS7ResHeader17; + +// Outcoming 10 bytes header , response for Request type 8 (server control) +typedef struct { + byte P; // Telegram ID, always 32 + byte PDUType; // Header type 8 + word AB_EX; // Zero + word Sequence; // Message ID. This can be used to make sure a received answer + word DataLen; // Length of data which follow this header + word Error; // Error code +} TS7ResHeader8; + +typedef TS7ResHeader8* PS7ResHeader8; + +// Outcoming answer buffer header type 2 or header type 3 +typedef struct{ + TS7ResHeader23 Header; + byte ResData [IsoPayload_Size - sizeof(TS7ResHeader23)]; +} TS7Answer23; + +typedef TS7Answer23* PS7Answer23; + +// Outcoming buffer header type 1 or header type 7 +typedef struct { + TS7ResHeader17 Header; + byte ResData [IsoPayload_Size - sizeof(TS7ResHeader17)]; +} TS7Answer17; + +typedef TS7Answer17* PS7Answer17; + +typedef byte TTimeBuffer[8]; +typedef byte *PTimeBuffer[8]; + +typedef struct{ + byte bcd_year; + byte bcd_mon; + byte bcd_day; + byte bcd_hour; + byte bcd_min; + byte bcd_sec; + byte bcd_himsec; + byte bcd_dow; +}TS7Time, *PS7Time; + +typedef byte TS7Buffer[65536]; +typedef byte *PS7Buffer; + +const int ReqHeaderSize = sizeof(TS7ReqHeader); +const int ResHeaderSize23 = sizeof(TS7ResHeader23); +const int ResHeaderSize17 = sizeof(TS7ResHeader17); + +// Most used request type parameters record +typedef struct { + byte Head[3];// 0x00 0x01 0x12 + byte Plen; // par len 0x04 + byte Uk; // unknown + byte Tg; // type and group (4 bits type and 4 bits group) + byte SubFun; // subfunction + byte Seq; // sequence +}TReqFunTypedParams; + +//============================================================================== +// FUNCTION NEGOTIATE +//============================================================================== +typedef struct { + byte FunNegotiate; + byte Unknown; + word ParallelJobs_1; + word ParallelJobs_2; + word PDULength; +}TReqFunNegotiateParams; + +typedef TReqFunNegotiateParams* PReqFunNegotiateParams; + +typedef struct { + byte FunNegotiate; + byte Unknown; + word ParallelJobs_1; + word ParallelJobs_2; + word PDULength; +}TResFunNegotiateParams; + +typedef TResFunNegotiateParams* PResFunNegotiateParams; + +//============================================================================== +// FUNCTION READ +//============================================================================== +typedef struct { + byte ItemHead[3]; + byte TransportSize; + word Length; + word DBNumber; + byte Area; + byte Address[3]; +}TReqFunReadItem, * PReqFunReadItem; + +//typedef TReqFunReadItem; + +typedef struct { + byte FunRead; + byte ItemsCount; + TReqFunReadItem Items[MaxVars]; +}TReqFunReadParams; + +typedef TReqFunReadParams* PReqFunReadParams; + +typedef struct { + byte FunRead; + byte ItemCount; +}TResFunReadParams; + +typedef TResFunReadParams* PResFunReadParams; + +typedef struct { + byte ReturnCode; + byte TransportSize; + word DataLength; + byte Data[IsoPayload_Size - 17]; // 17 = header + params + data header - 1 +}TResFunReadItem, *PResFunReadItem; + +typedef PResFunReadItem TResFunReadData[MaxVars]; + +//============================================================================== +// FUNCTION WRITE +//============================================================================== +typedef struct { + byte ItemHead[3]; + byte TransportSize; + word Length; + word DBNumber; + byte Area; + byte Address[3]; +}TReqFunWriteItem, * PReqFunWriteItem; + +typedef struct { + byte FunWrite; + byte ItemsCount; + TReqFunWriteItem Items[MaxVars]; +}TReqFunWriteParams; + +typedef TReqFunWriteParams* PReqFunWriteParams; + +typedef struct { + byte ReturnCode; + byte TransportSize; + word DataLength; + byte Data [IsoPayload_Size - 17]; // 17 = header + params + data header -1 +}TReqFunWriteDataItem, *PReqFunWriteDataItem; + +typedef PReqFunWriteDataItem TReqFunWriteData[MaxVars]; + +typedef struct { + byte FunWrite; + byte ItemCount; + byte Data[MaxVars]; +}TResFunWrite; + +typedef TResFunWrite* PResFunWrite; + +//============================================================================== +// GROUP UPLOAD +//============================================================================== +typedef struct { + byte FunSUpld; // function start upload 0x1D + byte Uk6 [6]; // Unknown 6 bytes + byte Upload_ID; + byte Len_1; + byte Prefix; + byte BlkPrfx; // always 0x30 + byte BlkType; + byte AsciiBlk[5]; // BlockNum in ascii + byte A; // always 0x41 ('A') +}TReqFunStartUploadParams; + +typedef TReqFunStartUploadParams* PReqFunStartUploadParams; + +typedef struct { + byte FunSUpld; // function start upload 0x1D + byte Data_1[6]; + byte Upload_ID; + byte Uk[3]; + byte LenLoad[5]; +}TResFunStartUploadParams; + +typedef TResFunStartUploadParams* PResFunStartUploadParams; + +typedef struct { + byte FunUpld; // function upload 0x1E + byte Uk6[6]; // Unknown 6 bytes + byte Upload_ID; +}TReqFunUploadParams; + +typedef TReqFunUploadParams* PReqFunUploadParams; + +typedef struct { + byte FunUpld; // function upload 0x1E + byte EoU; // 0 = End Of Upload, 1 = Upload in progress +}TResFunUploadParams; + +typedef TResFunUploadParams* PResFunUploadParams; + +typedef struct { + word Length; // Payload length - 4 + byte Uk_00; // Unknown 0x00 + byte Uk_FB; // Unknown 0xFB + // from here is the same of TS7CompactBlockInfo + word Cst_pp; + byte Uk_01; // Unknown 0x01 + byte BlkFlags; + byte BlkLang; + byte SubBlkType; + word BlkNum; + u_int LenLoadMem; + u_int BlkSec; + u_int CodeTime_ms; + word CodeTime_dy; + u_int IntfTime_ms; + word IntfTime_dy; + word SbbLen; + word AddLen; + word LocDataLen; + word MC7Len; +}TResFunUploadDataHeaderFirst; + +typedef TResFunUploadDataHeaderFirst* PResFunUploadDataHeaderFirst; + +typedef struct { + word Length;// Payload length - 4 + byte Uk_00; // Unknown 0x00 + byte Uk_FB; // Unknown 0xFB +}TResFunUploadDataHeaderNext; + +typedef TResFunUploadDataHeaderNext* PResFunUploadDataHeaderNext; + +typedef struct { + word Length;// Payload length - 4 + byte Uk_00; // Unknown 0x00 + byte Uk_FB; // Unknown 0xFB +}TResFunUploadDataHeader; + +typedef TResFunUploadDataHeader* PResFunUploadDataHeader; + +typedef struct { + byte ID; // 0x65 + word Seq; // Sequence + byte Const_1[10]; + word Lo_bound; + word Hi_Bound; + byte u_shortLen;// 0x02 byte + // 0x04 word + // 0x05 int + // 0x06 dword + // 0x07 dint + // 0x08 real + byte c1, c2; + char Author[8]; + char Family[8]; + char Header[8]; + byte B1; // 0x11 + byte B2; // 0x00 + word Chksum; + byte Uk_8[8]; +}TArrayUpldFooter; + +typedef TArrayUpldFooter* PArrayUpldFooter; + +typedef struct { + byte FunEUpld; // function end upload 0x1F + byte Uk6[6]; // Unknown 6 bytes + byte Upload_ID; +}TReqFunEndUploadParams; + +typedef TReqFunEndUploadParams* PReqFunEndUploadParams; + +typedef struct { + byte FunEUpld; // function end upload 0x1F +}TResFunEndUploadParams; + +typedef TResFunEndUploadParams* PResFunEndUploadParams; + +//============================================================================== +// GROUP DOWNLOAD +//============================================================================== +typedef struct { + byte FunSDwnld; // function start Download 0x1A + byte Uk6[6]; // Unknown 6 bytes + byte Dwnld_ID; + byte Len_1; // 0x09 + byte Prefix; // 0x5F + byte BlkPrfx; // always 0x30 + byte BlkType; + byte AsciiBlk[5]; // BlockNum in ascii + byte P; // 0x50 ('P') + byte Len_2; // 0x0D + byte Uk1; // 0x01 + byte AsciiLoad[6];// load memory size (MC7 size + 92) + byte AsciiMC7[6]; // Block size in bytes +}TReqStartDownloadParams; + +typedef TReqStartDownloadParams* PReqStartDownloadParams; +typedef byte TResStartDownloadParams; +typedef TResStartDownloadParams* PResStartDownloadParams; + +typedef struct { + byte Fun; // pduDownload or pduDownloadEnded + byte Uk7[7]; + byte Len_1; // 0x09 + byte Prefix; // 0x5F + byte BlkPrfx; // always 0x30 + byte BlkType; + byte AsciiBlk[5]; // BlockNum in ascii + byte P; // 0x50 ('P') +}TReqDownloadParams; + +typedef TReqDownloadParams* PReqDownloadParams; + +typedef struct { + byte FunDwnld; // 0x1B + byte EoS; // End of sequence : 0x00 - Sequence in progress : 0x01 +}TResDownloadParams; + +typedef TResDownloadParams* PResDownloadParams; + +typedef struct { + word DataLen; + word FB_00; // 0x00 0xFB +}TResDownloadDataHeader; + +typedef TResDownloadDataHeader* PResDownloadDataHeader; +typedef byte TResEndDownloadParams; +typedef TResEndDownloadParams* PResEndDownloadParams; + +typedef struct { + word Cst_pp; + byte Uk_01; // Unknown 0x01 + byte BlkFlags; + byte BlkLang; + byte SubBlkType; + word BlkNum; + u_int LenLoadMem; + u_int BlkSec; + u_int CodeTime_ms; + word CodeTime_dy; + u_int IntfTime_ms; + word IntfTime_dy; + word SbbLen; + word AddLen; + word LocDataLen; + word MC7Len; +}TS7CompactBlockInfo; + +typedef TS7CompactBlockInfo* PS7CompactBlockInfo; + +typedef struct { + byte Uk_20[20]; + byte Author[8]; + byte Family[8]; + byte Header[8]; + byte B1; // 0x11 + byte B2; // 0x00 + word Chksum; + byte Uk_12[8]; +}TS7BlockFooter; + +typedef TS7BlockFooter* PS7BlockFooter; + +//============================================================================== +// FUNCTION INSERT/DELETE +//============================================================================== +typedef struct { + byte Fun; // plc control 0x28 + byte Uk7[7]; // unknown 7 + word Len_1; // Length part 1 : 10 + byte NumOfBlocks; // number of blocks to insert + byte ByteZero; // 0x00 + byte AsciiZero; // 0x30 '0' + byte BlkType; + byte AsciiBlk[5]; // BlockNum in ascii + byte SFun; // 0x50 or 0x42 + byte Len_2; // Length part 2 : 0x05 bytes + char Cmd[5]; // ascii '_INSE' or '_DELE' +}TReqControlBlockParams; + +typedef TReqControlBlockParams* PReqControlBlockParams; + +//============================================================================== +// FUNCTIONS START/STOP/COPY RAM TO ROM/COMPRESS +//============================================================================== +typedef struct { + byte Fun; // stop 0x29 + byte Uk_5[5]; // unknown 5 bytes 0x00 + byte Len_2; // Length part 2 : 0x09 + char Cmd[9]; // ascii 'P_PROGRAM' +}TReqFunPlcStop; + +typedef TReqFunPlcStop* PReqFunPlcStop; + +typedef struct { + byte Fun; // start 0x28 + byte Uk_7[7]; // unknown 7 + word Len_1; // Length part 1 : 0x0000 + byte Len_2; // Length part 2 : 0x09 + char Cmd [9]; // ascii 'P_PROGRAM' +}TReqFunPlcHotStart; + +typedef TReqFunPlcHotStart* PReqFunPlcHotStart; + +typedef struct { + byte Fun; // start 0x28 + byte Uk_7[7]; // unknown 7 + word Len_1; // Length part 1 : 0x0002 + word SFun; // 'C ' 0x4320 + byte Len_2; // Length part 2 : 0x09 + char Cmd[9]; // ascii 'P_PROGRAM' +}TReqFunPlcColdStart; + +typedef TReqFunPlcColdStart* PReqFunPlcColdStart; + +typedef struct { + byte Fun; // pduControl 0x28 + byte Uk_7[7]; // unknown 7 + word Len_1; // Length part 1 : 0x0002 + word SFun; // 'EP' 0x4550 + byte Len_2; // Length part 2 : 0x05 + char Cmd[5]; // ascii '_MODU' +}TReqFunCopyRamToRom; + +typedef TReqFunCopyRamToRom* PReqFunCopyRamToRom; + +typedef struct { + byte Fun; // pduControl 0x28 + byte Uk_7[7]; // unknown 7 + word Len_1; // Length part 1 : 0x00 + byte Len_2; // Length part 2 : 0x05 + char Cmd[5]; // ascii '_GARB' +}TReqFunCompress; + +typedef TReqFunCompress* PReqFunCompress; + +typedef struct { + byte ResFun; + byte para; +}TResFunCtrl; + +typedef TResFunCtrl* PResFunCtrl; + +//============================================================================== +// FUNCTIONS USERDATA +//============================================================================== +typedef struct { + byte Head[3]; // Always 0x00 0x01 0x12 + byte Plen; // par len 0x04 or 0x08 + byte Uk; // unknown + byte Tg; // type and group (4 bits type and 4 bits group) + byte SubFun; // subfunction + byte Seq; // sequence + word resvd; // present if plen=0x08 (S7 manager online functions) + word Err; // present if plen=0x08 (S7 manager online functions) +}TS7Params7; + +typedef TS7Params7* PS7ReqParams7; +typedef TS7Params7* PS7ResParams7; + +// for convenience Hi order bit of type are included (0x4X) +const byte grProgrammer = 0x41; +const byte grCyclicData = 0x42; +const byte grBlocksInfo = 0x43; +const byte grSZL = 0x44; +const byte grPassword = 0x45; +const byte grBSend = 0x46; +const byte grClock = 0x47; +const byte grSecurity = 0x45; + +//============================================================================== +// GROUP SECURITY +//============================================================================== +typedef TReqFunTypedParams TReqFunSecurity; +typedef TReqFunSecurity* PReqFunSecurity; + +typedef char TS7Password[8]; + +typedef struct { + byte Ret; // 0xFF for request + byte TS; // 0x09 Transport size + word DLen; // Data len : 8 bytes + byte Pwd[8]; // Password encoded into "AG" format +}TReqDataSecurity; + +typedef TReqDataSecurity* PReqDataSecurity; +typedef TS7Params7 TResParamsSecurity; +typedef TResParamsSecurity* PResParamsSecurity; + +typedef struct { + byte Ret; + byte TS; + word DLen; +}TResDataSecurity; + +typedef TResDataSecurity* PResDataSecurity; + +//============================================================================== +// GROUP BLOCKS SZL +//============================================================================== +typedef TReqFunTypedParams TReqFunReadSZLFirst; +typedef TReqFunReadSZLFirst* PReqFunReadSZLFirst; + +typedef struct { + byte Head[3]; // 0x00 0x01 0x12 + byte Plen; // par len 0x04 + byte Uk; // unknown + byte Tg; // type and group (4 bits type and 4 bits group) + byte SubFun; // subfunction + byte Seq; // sequence + word Rsvd; // Reserved 0x0000 + word ErrNo; // Error Code +}TReqFunReadSZLNext; + +typedef TReqFunReadSZLNext* PReqFunReadSZLNext; + +typedef struct { + byte Ret; // 0xFF for request + byte TS; // 0x09 Transport size + word DLen; // Data len + word ID; // SZL-ID + word Index;// SZL-Index +}TS7ReqSZLData; + +typedef TS7ReqSZLData* PS7ReqSZLData; + +typedef struct { + byte Ret; + byte TS; + word DLen; + word ID; + word Index; + word ListLen; + word ListCount; + word Data[32747]; +}TS7ResSZLDataFirst; + +typedef TS7ResSZLDataFirst* PS7ResSZLDataFirst; + +typedef struct { + byte Ret; + byte TS; + word DLen; + word Data[32751]; +}TS7ResSZLDataNext; + +typedef TS7ResSZLDataNext* PS7ResSZLDataNext; + +typedef struct { + byte Ret; + byte OtherInfo[9]; + word Count; + word Items[32747]; +}TS7ResSZLData_0; + +typedef TS7ResSZLData_0* PS7ResSZLData_0; + +//============================================================================== +// GROUP CLOCK +//============================================================================== +typedef TReqFunTypedParams TReqFunDateTime; +typedef TReqFunDateTime* PReqFunDateTime; + +typedef byte TReqDataGetDateTime[4]; + +typedef longword *PReqDataGetDateTime; + +typedef struct { + byte RetVal; + byte TSize; + word Length; + byte Rsvd; + byte HiYear; + TTimeBuffer Time; +}TResDataGetTime; + +typedef TResDataGetTime* PResDataGetTime; +typedef TResDataGetTime TReqDataSetTime; +typedef TReqDataSetTime* PReqDataSetTime; + +typedef struct { + byte RetVal; + byte TSize; + word Length; +}TResDataSetTime; + +typedef TResDataSetTime* PResDataSetTime; + +//============================================================================== +// GROUP BLOCKS INFO +//============================================================================== +typedef TReqFunTypedParams TReqFunGetBlockInfo; +typedef TReqFunGetBlockInfo* PReqFunGetBlockInfo; + +typedef byte TReqDataFunBlocks[4]; +typedef u_char* PReqDataFunBlocks; + +typedef struct { + byte Head[3]; // 0x00 0x01 0x12 + byte Plen; // par len 0x04 + byte Uk; // unknown + byte Tg; // type and group (4 bits type and 4 bits group) + byte SubFun; // subfunction + byte Seq; // sequence + word Rsvd; // Reserved 0x0000 + word ErrNo; // Error Code +}TResFunGetBlockInfo; + +typedef TResFunGetBlockInfo* PResFunGetBlockInfo; + +typedef struct { + byte Zero; // always 0x30 -> Ascii 0 + byte BType; // Block Type + word BCount; // Block count +}TResFunGetBlockItem; + +typedef struct { + byte RetVal; + byte TRSize; + word Length; + TResFunGetBlockItem Blocks[7]; +}TDataFunListAll; + +typedef TDataFunListAll* PDataFunListAll; + +typedef struct { + word BlockNum; + byte Unknown; + byte BlockLang; +}TDataFunGetBotItem; + +typedef struct { + byte RetVal; + byte TSize; + word DataLen; + TDataFunGetBotItem Items[(IsoPayload_Size - 29 ) / 4]; +}TDataFunGetBot; +// Note : 29 is the size of headers iso, COPT, S7 header, params, data + +typedef TDataFunGetBot* PDataFunGetBot; + +typedef struct { + byte RetVal; // 0xFF + byte TSize; // Octet (0x09) + word Length; // 0x0002 + byte Zero; // Ascii '0' (0x30) + byte BlkType; +}TReqDataBlockOfType; + +typedef TReqDataBlockOfType* PReqDataBlockOfType; + +typedef struct { + byte RetVal; + byte TSize; + word DataLen; + byte BlkPrfx; // always 0x30 + byte BlkType; + byte AsciiBlk[5]; // BlockNum in ascii + byte A; // always 0x41 ('A') +}TReqDataBlockInfo; + +typedef TReqDataBlockInfo* PReqDataBlockInfo; + +typedef struct { + byte RetVal; + byte TSize; + word Length; + byte Cst_b; + byte BlkType; + word Cst_w1; + word Cst_w2; + word Cst_pp; + byte Unknown_1; + byte BlkFlags; + byte BlkLang; + byte SubBlkType; + word BlkNumber; + u_int LenLoadMem; + byte BlkSec[4]; + u_int CodeTime_ms; + word CodeTime_dy; + u_int IntfTime_ms; + word IntfTime_dy; + word SbbLen; + word AddLen; + word LocDataLen; + word MC7Len; + byte Author[8]; + byte Family[8]; + byte Header[8]; + byte Version; + byte Unknown_2; + word BlkChksum; + byte Resvd1[4]; + byte Resvd2[4]; +}TResDataBlockInfo; + +typedef TResDataBlockInfo* PResDataBlockInfo; + +//============================================================================== +// BSEND / BRECV +//============================================================================== +typedef struct { + int Size; + longword R_ID; + byte Data[65536]; +}TPendingBuffer; + +typedef struct { + TTPKT TPKT; + TCOTP_DT COTP; + byte P; + byte PDUType; +}TPacketInfo; + +typedef struct { + byte Head[3];// Always 0x00 0x01 0x12 + byte Plen; // par len 0x04 or 0x08 + byte Uk; // unknown (0x12) + byte Tg; // type and group, 4 bits type and 4 bits group (0x46) + byte SubFun; // subfunction (0x01) + byte Seq; // sequence + byte IDSeq; // ID Sequence (come from partner) + byte EoS; // End of Sequence = 0x00 Sequence in progress = 0x01; + word Err; // +}TBSendParams; + +typedef TBSendParams* PBSendReqParams; +typedef TBSendParams* PBSendResParams; + +// Data frame + +typedef struct { + byte FF; // 0xFF + byte TRSize; // Transport Size 0x09 (octet) + word Len; // This Telegram Length + byte DHead[4];// sequence 0x12 0x06 0x13 0x00 + u_int R_ID; // R_ID +}TBsendRequestData; + +typedef TBsendRequestData* PBsendRequestData; + +typedef struct { + byte DHead[4]; // sequence 0x0A 0x00 0x00 0x00 +}TBSendResData; + +typedef TBSendResData* PBSendResData; + +#pragma pack() +#endif // s7_types_h diff --git a/core/src/drivers/plugins/native/s7comm/snap7/lib/snap7_libmain.cpp b/core/src/drivers/plugins/native/s7comm/snap7/lib/snap7_libmain.cpp new file mode 100644 index 00000000..5887c6e6 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/lib/snap7_libmain.cpp @@ -0,0 +1,1196 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 Davide Nardella | +| All rights reserved. | +|==============================================================================| +| SNAP7 is free software: you can redistribute it and/or modify | +| it under the terms of the Lesser GNU General Public License as published by | +| the Free Software Foundation, either version 3 of the License, or | +| (at your option) any later version. | +| | +| It means that you can distribute your commercial software linked with | +| SNAP7 without the requirement to distribute the source code of your | +| application and without the requirement that your application be itself | +| distributed under LGPL. | +| | +| SNAP7 is distributed in the hope that it will be useful, | +| but WITHOUT ANY WARRANTY; without even the implied warranty of | +| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | +| Lesser GNU General Public License for more details. | +| | +| You should have received a copy of the GNU General Public License and a | +| copy of Lesser GNU General Public License along with Snap7. | +| If not, see http://www.gnu.org/licenses/ | +|=============================================================================*/ +#include "snap7_libmain.h" + +#ifndef OS_WINDOWS +void libinit(void) __attribute__((constructor)); +void libdone(void) __attribute__((destructor)); +#endif + +static bool libresult = true; + +void libinit(void) +{ + // in future expansions here can be inserted some initialization code + libresult=true; +} + +void libdone(void) +{ + // in future expansions here can be inserted some destruction code +} + +#ifdef OS_WINDOWS +BOOL APIENTRY DllMain (HINSTANCE hInst, + DWORD reason, + LPVOID reserved) +{ + switch (reason) + { + case DLL_PROCESS_ATTACH: + libinit(); + break; + case DLL_PROCESS_DETACH: + libdone(); + break; + case DLL_THREAD_ATTACH: + break; + case DLL_THREAD_DETACH: + break; + } + return libresult; +} +#endif + +//*************************************************************************** +// CLIENT +//*************************************************************************** +S7Object S7API Cli_Create() +{ + return S7Object(new TSnap7Client()); +} +//--------------------------------------------------------------------------- +void S7API Cli_Destroy(S7Object &Client) +{ + if (Client) + { + delete PSnap7Client(Client); + Client=0; + } +} +//--------------------------------------------------------------------------- +int S7API Cli_SetConnectionParams(S7Object Client, const char *Address, word LocalTSAP, word RemoteTSAP) +{ + if (Client) + { + PSnap7Client(Client)->SetConnectionParams(Address, LocalTSAP, RemoteTSAP); + return 0; + } + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_SetConnectionType(S7Object Client, word ConnectionType) +{ + if (Client) + { + PSnap7Client(Client)->SetConnectionType(ConnectionType); + return 0; + } + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_ConnectTo(S7Object Client, const char *Address, int Rack, int Slot) +{ + if (Client) + return PSnap7Client(Client)->ConnectTo(Address, Rack, Slot); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_Connect(S7Object Client) +{ + if (Client) + return PSnap7Client(Client)->Connect(); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_Disconnect(S7Object Client) +{ + if (Client) + return PSnap7Client(Client)->Disconnect(); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_GetParam(S7Object Client, int ParamNumber, void *pValue) +{ + if (Client) + return PSnap7Client(Client)->GetParam(ParamNumber, pValue); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_SetParam(S7Object Client, int ParamNumber, void *pValue) +{ + if (Client) + return PSnap7Client(Client)->SetParam(ParamNumber, pValue); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_SetAsCallback(S7Object Client, pfn_CliCompletion pCompletion, void *usrPtr) +{ + if (Client) + return PSnap7Client(Client)->SetAsCallback(pCompletion, usrPtr); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_ReadArea(S7Object Client, int Area, int DBNumber, int Start, int Amount, int WordLen, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->ReadArea(Area, DBNumber, Start, Amount, WordLen, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_WriteArea(S7Object Client, int Area, int DBNumber, int Start, int Amount, int WordLen, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->WriteArea(Area, DBNumber, Start, Amount, WordLen, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_ReadMultiVars(S7Object Client, PS7DataItem Item, int ItemsCount) +{ + if (Client) + return PSnap7Client(Client)->ReadMultiVars(Item, ItemsCount); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_WriteMultiVars(S7Object Client, PS7DataItem Item, int ItemsCount) +{ + if (Client) + return PSnap7Client(Client)->WriteMultiVars(Item, ItemsCount); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_DBRead(S7Object Client, int DBNumber, int Start, int Size, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->DBRead(DBNumber, Start, Size, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_DBWrite(S7Object Client, int DBNumber, int Start, int Size, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->DBWrite(DBNumber, Start, Size, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_MBRead(S7Object Client, int Start, int Size, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->MBRead(Start, Size, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_MBWrite(S7Object Client, int Start, int Size, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->MBWrite(Start, Size, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_EBRead(S7Object Client, int Start, int Size, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->EBRead(Start, Size, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_EBWrite(S7Object Client, int Start, int Size, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->EBWrite(Start, Size, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_ABRead(S7Object Client, int Start, int Size, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->ABRead(Start, Size, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_ABWrite(S7Object Client, int Start, int Size, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->ABWrite(Start, Size, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_TMRead(S7Object Client, int Start, int Amount, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->TMRead(Start, Amount, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_TMWrite(S7Object Client, int Start, int Amount, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->TMWrite(Start, Amount, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_CTRead(S7Object Client, int Start, int Amount, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->CTRead(Start, Amount, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_CTWrite(S7Object Client, int Start, int Amount, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->CTWrite(Start, Amount, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_ListBlocks(S7Object Client, TS7BlocksList *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->ListBlocks(pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_GetAgBlockInfo(S7Object Client, int BlockType, int BlockNum, TS7BlockInfo *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->GetAgBlockInfo(BlockType, BlockNum, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_GetPgBlockInfo(S7Object Client, void *pBlock, TS7BlockInfo *pUsrData, int Size) +{ + if (Client) + return PSnap7Client(Client)->GetPgBlockInfo(pBlock, pUsrData, Size); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_ListBlocksOfType(S7Object Client, int BlockType, TS7BlocksOfType *pUsrData, int &ItemsCount) +{ + if (Client) + return PSnap7Client(Client)->ListBlocksOfType(BlockType, pUsrData, ItemsCount); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_Upload(S7Object Client, int BlockType, int BlockNum, void *pUsrData, int &Size) +{ + if (Client) + return PSnap7Client(Client)->Upload(BlockType, BlockNum, pUsrData, Size); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_FullUpload(S7Object Client, int BlockType, int BlockNum, void *pUsrData, int &Size) +{ + if (Client) + return PSnap7Client(Client)->FullUpload(BlockType, BlockNum, pUsrData, Size); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_Download(S7Object Client, int BlockNum, void *pUsrData, int Size) +{ + if (Client) + return PSnap7Client(Client)->Download(BlockNum, pUsrData, Size); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_Delete(S7Object Client, int BlockType, int BlockNum) +{ + if (Client) + return PSnap7Client(Client)->Delete(BlockType, BlockNum); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_DBGet(S7Object Client, int DBNumber, void *pUsrData, int &Size) +{ + if (Client) + return PSnap7Client(Client)->DBGet(DBNumber, pUsrData, Size); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_DBFill(S7Object Client, int DBNumber, int FillChar) +{ + if (Client) + return PSnap7Client(Client)->DBFill(DBNumber, FillChar); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_GetPlcDateTime(S7Object Client, tm &DateTime) +{ + if (Client) + return PSnap7Client(Client)->GetPlcDateTime(DateTime); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_SetPlcDateTime(S7Object Client, tm *DateTime) +{ + if (Client) + return PSnap7Client(Client)->SetPlcDateTime(DateTime); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_SetPlcSystemDateTime(S7Object Client) +{ + if (Client) + return PSnap7Client(Client)->SetPlcSystemDateTime(); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_GetOrderCode(S7Object Client, TS7OrderCode *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->GetOrderCode(pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_GetCpuInfo(S7Object Client, TS7CpuInfo *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->GetCpuInfo(pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_GetCpInfo(S7Object Client, TS7CpInfo *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->GetCpInfo(pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_ReadSZL(S7Object Client, int ID, int Index, TS7SZL *pUsrData, int &Size) +{ + if (Client) + return PSnap7Client(Client)->ReadSZL(ID, Index, pUsrData, Size); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_ReadSZLList(S7Object Client, TS7SZLList *pUsrData, int &ItemsCount) +{ + if (Client) + return PSnap7Client(Client)->ReadSZLList(pUsrData, ItemsCount); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_PlcHotStart(S7Object Client) +{ + if (Client) + return PSnap7Client(Client)->PlcHotStart(); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_PlcColdStart(S7Object Client) +{ + if (Client) + return PSnap7Client(Client)->PlcColdStart(); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_PlcStop(S7Object Client) +{ + if (Client) + return PSnap7Client(Client)->PlcStop(); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_CopyRamToRom(S7Object Client, int Timeout) +{ + if (Client) + return PSnap7Client(Client)->CopyRamToRom(Timeout); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_Compress(S7Object Client, int Timeout) +{ + if (Client) + return PSnap7Client(Client)->Compress(Timeout); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_GetPlcStatus(S7Object Client, int &Status) +{ + if (Client) + return PSnap7Client(Client)->GetPlcStatus(Status); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_GetProtection(S7Object Client, TS7Protection *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->GetProtection(pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_SetSessionPassword(S7Object Client, char *Password) +{ + if (Client) + return PSnap7Client(Client)->SetSessionPassword(Password); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_ClearSessionPassword(S7Object Client) +{ + if (Client) + return PSnap7Client(Client)->ClearSessionPassword(); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_IsoExchangeBuffer(S7Object Client, void *pUsrData, int &Size) +{ + if (Client) + return PSnap7Client(Client)->isoExchangeBuffer(pUsrData, Size); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_GetExecTime(S7Object Client, int &Time) +{ + if (Client) + { + Time=PSnap7Client(Client)->Time(); + return 0; + } + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_GetLastError(S7Object Client, int &LastError) +{ + if (Client) + { + LastError=PSnap7Client(Client)->LastError; + return 0; + } + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_GetPduLength(S7Object Client, int &Requested, int &Negotiated) +{ + if (Client) + { + Negotiated=PSnap7Client(Client)->PDULength; + Requested =PSnap7Client(Client)->PDURequest; + return 0; + } + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_ErrorText(int Error, char *Text, int TextLen) +{ + try{ + ErrCliText(Error, Text, TextLen); + Text[TextLen-1] = '\0'; + } + catch (...){ + return errLibInvalidParam; + } + return 0; +} +//--------------------------------------------------------------------------- +int S7API Cli_GetConnected(S7Object Client, int &Connected) +{ + Connected=0; + if (Client) + { + Connected=PSnap7Client(Client)->Connected; + return 0; + } + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsReadArea(S7Object Client, int Area, int DBNumber, int Start, int Amount, int WordLen, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->AsReadArea(Area, DBNumber, Start, Amount, WordLen, pUsrData); + else + return errLibInvalidParam; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsWriteArea(S7Object Client, int Area, int DBNumber, int Start, int Amount, int WordLen, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->AsWriteArea(Area, DBNumber, Start, Amount, WordLen, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsDBRead(S7Object Client, int DBNumber, int Start, int Size, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->AsDBRead(DBNumber, Start, Size, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsDBWrite(S7Object Client, int DBNumber, int Start, int Size, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->AsDBWrite(DBNumber, Start, Size, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsMBRead(S7Object Client, int Start, int Size, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->AsMBRead(Start, Size, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsMBWrite(S7Object Client, int Start, int Size, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->AsMBWrite(Start, Size, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsEBRead(S7Object Client, int Start, int Size, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->AsEBRead(Start, Size, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsEBWrite(S7Object Client, int Start, int Size, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->AsEBWrite(Start, Size, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsABRead(S7Object Client, int Start, int Size, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->AsABRead(Start, Size, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsABWrite(S7Object Client, int Start, int Size, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->AsABWrite(Start, Size, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsTMRead(S7Object Client, int Start, int Amount, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->AsTMRead(Start, Amount, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsTMWrite(S7Object Client, int Start, int Amount, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->AsTMWrite(Start, Amount, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsCTRead(S7Object Client, int Start, int Amount, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->AsCTRead(Start, Amount, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsCTWrite(S7Object Client, int Start, int Amount, void *pUsrData) +{ + if (Client) + return PSnap7Client(Client)->AsCTWrite(Start, Amount, pUsrData); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsListBlocksOfType(S7Object Client, int BlockType, TS7BlocksOfType *pUsrData, int &ItemsCount) +{ + if (Client) + return PSnap7Client(Client)->AsListBlocksOfType(BlockType, pUsrData, ItemsCount); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsReadSZL(S7Object Client, int ID, int Index, TS7SZL *pUsrData, int &Size) +{ + if (Client) + return PSnap7Client(Client)->AsReadSZL(ID, Index, pUsrData, Size); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsReadSZLList(S7Object Client, TS7SZLList *pUsrData, int &ItemsCount) +{ + if (Client) + return PSnap7Client(Client)->AsReadSZLList(pUsrData, ItemsCount); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsUpload(S7Object Client, int BlockType, int BlockNum, void *pUsrData, int &Size) +{ + if (Client) + return PSnap7Client(Client)->AsUpload(BlockType, BlockNum, pUsrData, Size); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsFullUpload(S7Object Client, int BlockType, int BlockNum, void *pUsrData, int &Size) +{ + if (Client) + return PSnap7Client(Client)->AsFullUpload(BlockType, BlockNum, pUsrData, Size); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsDownload(S7Object Client, int BlockNum, void *pUsrData, int Size) +{ + if (Client) + return PSnap7Client(Client)->AsDownload(BlockNum, pUsrData, Size); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsCopyRamToRom(S7Object Client, int Timeout) +{ + if (Client) + return PSnap7Client(Client)->AsCopyRamToRom(Timeout); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsCompress(S7Object Client, int Timeout) +{ + if (Client) + return PSnap7Client(Client)->Compress(Timeout); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsDBGet(S7Object Client, int DBNumber, void *pUsrData, int &Size) +{ + if (Client) + return PSnap7Client(Client)->AsDBGet(DBNumber, pUsrData, Size); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_AsDBFill(S7Object Client, int DBNumber, int FillChar) +{ + if (Client) + return PSnap7Client(Client)->AsDBFill(DBNumber, FillChar); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_CheckAsCompletion(S7Object Client, int &opResult) +{ + if (Client) + { + if (PSnap7Client(Client)->CheckAsCompletion(opResult)) + return JobComplete; + else + return JobPending; + } + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Cli_WaitAsCompletion(S7Object Client, int Timeout) +{ + if (Client) + return PSnap7Client(Client)->WaitAsCompletion(Timeout); + else + return errLibInvalidObject; +} +//*************************************************************************** +// SERVER +//*************************************************************************** +S7Object S7API Srv_Create() +{ + return S7Object(new TSnap7Server()); +} +//--------------------------------------------------------------------------- +void S7API Srv_Destroy(S7Object &Server) +{ + if (Server) + { + delete PSnap7Server(Server); + Server=0; + } +} +//--------------------------------------------------------------------------- +int S7API Srv_GetParam(S7Object Server, int ParamNumber, void *pValue) +{ + if (Server) + return PSnap7Server(Server)->GetParam(ParamNumber, pValue); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Srv_SetParam(S7Object Server, int ParamNumber, void *pValue) +{ + if (Server) + return PSnap7Server(Server)->SetParam(ParamNumber, pValue); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Srv_StartTo(S7Object Server, const char *Address) +{ + if (Server) + return PSnap7Server(Server)->StartTo(Address); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Srv_Start(S7Object Server) +{ + if (Server) + return PSnap7Server(Server)->Start(); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Srv_Stop(S7Object Server) +{ + if (Server) + { + PSnap7Server(Server)->Stop(); + return 0; + } + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Srv_RegisterArea(S7Object Server, int AreaCode, word Index, void *pUsrData, int Size) +{ + if (Server) + return PSnap7Server(Server)->RegisterArea(AreaCode, Index, pUsrData, Size); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Srv_UnregisterArea(S7Object Server, int AreaCode, word Index) +{ + if (Server) + return PSnap7Server(Server)->UnregisterArea(AreaCode, Index); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Srv_LockArea(S7Object Server, int AreaCode, word Index) +{ + if (Server) + return PSnap7Server(Server)->LockArea(AreaCode, Index); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Srv_UnlockArea(S7Object Server, int AreaCode, word Index) +{ + if (Server) + return PSnap7Server(Server)->UnlockArea(AreaCode, Index); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Srv_GetStatus(S7Object Server, int &ServerStatus, int &CpuStatus, int &ClientsCount) +{ + if (Server) + { + ServerStatus=PSnap7Server(Server)->Status; + CpuStatus=PSnap7Server(Server)->CpuStatus; + ClientsCount=PSnap7Server(Server)->ClientsCount; + return 0; + } + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Srv_SetCpuStatus(S7Object Server, int CpuStatus) +{ + if (Server) + { + PSnap7Server(Server)->CpuStatus=CpuStatus; + return 0; + } + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Srv_ErrorText(int Error, char *Text, int TextLen) +{ + try{ + ErrSrvText(Error, Text, TextLen); + Text[TextLen-1] = '\0'; + } + catch (...){ + return errLibInvalidParam; + } + return 0; +} +//--------------------------------------------------------------------------- +int S7API Srv_EventText(TSrvEvent &Event, char *Text, int TextLen) +{ + try{ + EvtSrvText(Event, Text, TextLen); + //Text[TextLen] = '\0'; + } + catch (...){ + return errLibInvalidParam; + } + return 0; +} +//--------------------------------------------------------------------------- +int S7API Srv_PickEvent(S7Object Server, TSrvEvent *pEvent, int &EvtReady) +{ + EvtReady=0; + if (Server) + { + EvtReady=int(PSnap7Server(Server)->PickEvent(pEvent)); + return 0; + } + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Srv_ClearEvents(S7Object Server) +{ + if (Server) + { + PSnap7Server(Server)->EventsFlush(); + return 0; + } + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Srv_GetMask(S7Object Server, int MaskKind, longword &Mask) +{ + if (Server) + { + Mask=0; + if ((MaskKind==mkEvent) || (MaskKind==mkLog)) + { + if (MaskKind==mkEvent) + Mask=PSnap7Server(Server)->EventMask; + else + Mask=PSnap7Server(Server)->LogMask; + return 0; + } + else + return errLibInvalidParam; + } + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Srv_SetMask(S7Object Server, int MaskKind, longword Mask) +{ + if (Server) + { + if ((MaskKind==mkEvent) || (MaskKind==mkLog)) + { + if (MaskKind==mkEvent) + PSnap7Server(Server)->EventMask=Mask; + else + PSnap7Server(Server)->LogMask=Mask; + return 0; + } + else + return errLibInvalidParam; + } + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Srv_SetEventsCallback(S7Object Server, pfn_SrvCallBack pCallback, void *usrPtr) +{ + if (Server) + return PSnap7Server(Server)->SetEventsCallBack(pCallback, usrPtr); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Srv_SetReadEventsCallback(S7Object Server, pfn_SrvCallBack pCallback, void *usrPtr) +{ + if (Server) + return PSnap7Server(Server)->SetReadEventsCallBack(pCallback, usrPtr); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Srv_SetRWAreaCallback(S7Object Server, pfn_RWAreaCallBack pCallback, void *usrPtr) +{ + if (Server) + return PSnap7Server(Server)->SetRWAreaCallBack(pCallback, usrPtr); + else + return errLibInvalidObject; +} +//*************************************************************************** +// PARTNER +//*************************************************************************** +S7Object S7API Par_Create(int Active) +{ + return S7Object(new TSnap7Partner(Active!=0)); +} +//--------------------------------------------------------------------------- +void S7API Par_Destroy(S7Object &Partner) +{ + if (Partner) + { + delete PSnap7Partner(Partner); + Partner=0; + } +} +//--------------------------------------------------------------------------- +int S7API Par_GetParam(S7Object Partner, int ParamNumber, void *pValue) +{ + if (Partner) + return PSnap7Partner(Partner)->GetParam(ParamNumber, pValue); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Par_SetParam(S7Object Partner, int ParamNumber, void *pValue) +{ + if (Partner) + return PSnap7Partner(Partner)->SetParam(ParamNumber, pValue); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Par_StartTo(S7Object Partner, const char *LocalAddress, const char *RemoteAddress, + word LocTsap, word RemTsap) +{ + if (Partner) + return PSnap7Partner(Partner)->StartTo(LocalAddress, RemoteAddress, LocTsap, RemTsap); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Par_Start(S7Object Partner) +{ + if (Partner) + return PSnap7Partner(Partner)->Start(); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Par_Stop(S7Object Partner) +{ + if (Partner) + return PSnap7Partner(Partner)->Stop(); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Par_BSend(S7Object Partner, longword R_ID, void *pUsrData, int Size) +{ + if (Partner) + return PSnap7Partner(Partner)->BSend(R_ID, pUsrData, Size); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Par_AsBSend(S7Object Partner, longword R_ID, void *pUsrData, int Size) +{ + if (Partner) + return PSnap7Partner(Partner)->AsBSend(R_ID, pUsrData, Size); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Par_CheckAsBSendCompletion(S7Object Partner, int &opResult) +{ + if (Partner) + { + if (PSnap7Partner(Partner)->CheckAsBSendCompletion(opResult)) + return JobComplete; + else + return JobPending; + } + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Par_WaitAsBSendCompletion(S7Object Partner, longword Timeout) +{ + if (Partner) + return PSnap7Partner(Partner)->WaitAsBSendCompletion(Timeout); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Par_SetSendCallback(S7Object Partner, pfn_ParBSendCompletion pCompletion, void *usrPtr) +{ + if (Partner) + return PSnap7Partner(Partner)->SetSendCallback(pCompletion, usrPtr); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Par_BRecv(S7Object Partner, longword &R_ID, void *pData, int &Size, longword Timeout) +{ + if (Partner) + return PSnap7Partner(Partner)->BRecv(R_ID, pData, Size, Timeout); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Par_CheckAsBRecvCompletion(S7Object Partner, int &opResult, longword &R_ID, + void *pData, int &Size) +{ + if (Partner) + { + if (PSnap7Partner(Partner)->CheckAsBRecvCompletion(opResult, R_ID, pData, Size)) + return JobComplete; + else + return JobPending; + } + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Par_SetRecvCallback(S7Object Partner, pfn_ParBRecvCallBack pCompletion, void *usrPtr) +{ + if (Partner) + return PSnap7Partner(Partner)->SetRecvCallback(pCompletion, usrPtr); + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Par_GetTimes(S7Object Partner, longword &SendTime, longword &RecvTime) +{ + if (Partner) + { + SendTime=PSnap7Partner(Partner)->SendTime; + RecvTime=PSnap7Partner(Partner)->RecvTime; + return 0; + } + else + return errLibInvalidObject; + +} +//--------------------------------------------------------------------------- +int S7API Par_GetStats(S7Object Partner, longword &BytesSent, longword &BytesRecv, + longword &SendErrors, longword &RecvErrors) +{ + if (Partner) + { + BytesSent=PSnap7Partner(Partner)->BytesSent; + BytesRecv=PSnap7Partner(Partner)->BytesRecv; + SendErrors=PSnap7Partner(Partner)->SendErrors; + RecvErrors=PSnap7Partner(Partner)->RecvErrors; + return 0; + } + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Par_GetLastError(S7Object Partner, int &LastError) +{ + if (Partner) + { + LastError=PSnap7Partner(Partner)->LastError; + return 0; + } + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Par_GetStatus(S7Object Partner, int &Status) +{ + if (Partner) + { + Status=PSnap7Partner(Partner)->Status(); + return 0; + } + else + return errLibInvalidObject; +} +//--------------------------------------------------------------------------- +int S7API Par_ErrorText(int Error, char *Text, int TextLen) +{ + try{ + ErrParText(Error, Text, TextLen); + Text[TextLen - 1] = '\0'; + } + catch (...){ + return errLibInvalidParam; + } + return 0; +} + diff --git a/core/src/drivers/plugins/native/s7comm/snap7/lib/snap7_libmain.h b/core/src/drivers/plugins/native/s7comm/snap7/lib/snap7_libmain.h new file mode 100644 index 00000000..d2eec096 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/lib/snap7_libmain.h @@ -0,0 +1,201 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 Davide Nardella | +| All rights reserved. | +|==============================================================================| +| SNAP7 is free software: you can redistribute it and/or modify | +| it under the terms of the Lesser GNU General Public License as published by | +| the Free Software Foundation, either version 3 of the License, or | +| (at your option) any later version. | +| | +| It means that you can distribute your commercial software linked with | +| SNAP7 without the requirement to distribute the source code of your | +| application and without the requirement that your application be itself | +| distributed under LGPL. | +| | +| SNAP7 is distributed in the hope that it will be useful, | +| but WITHOUT ANY WARRANTY; without even the implied warranty of | +| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | +| Lesser GNU General Public License for more details. | +| | +| You should have received a copy of the GNU General Public License and a | +| copy of Lesser GNU General Public License along with Snap7. | +| If not, see http://www.gnu.org/licenses/ | +|=============================================================================*/ +#ifndef snap7_libmain_h +#define snap7_libmain_h +//--------------------------------------------------------------------------- +#include "s7_client.h" +#include "s7_server.h" +#include "s7_partner.h" +#include "s7_text.h" +//--------------------------------------------------------------------------- + +const int mkEvent = 0; +const int mkLog = 1; + +typedef uintptr_t S7Object; // multi platform/processor object reference + +//============================================================================== +// CLIENT EXPORT LIST - Sync functions +//============================================================================== +EXPORTSPEC S7Object S7API Cli_Create(); +EXPORTSPEC void S7API Cli_Destroy(S7Object &Client); +EXPORTSPEC int S7API Cli_Connect(S7Object Client); +EXPORTSPEC int S7API Cli_SetConnectionParams(S7Object Client, const char *Address, word LocalTSAP, word RemoteTSAP); +EXPORTSPEC int S7API Cli_SetConnectionType(S7Object Client, word ConnectionType); +EXPORTSPEC int S7API Cli_ConnectTo(S7Object Client, const char *Address, int Rack, int Slot); +EXPORTSPEC int S7API Cli_Disconnect(S7Object Client); +EXPORTSPEC int S7API Cli_GetParam(S7Object Client, int ParamNumber, void *pValue); +EXPORTSPEC int S7API Cli_SetParam(S7Object Client, int ParamNumber, void *pValue); +EXPORTSPEC int S7API Cli_SetAsCallback(S7Object Client, pfn_CliCompletion pCompletion, void *usrPtr); +// Data I/O functions +EXPORTSPEC int S7API Cli_ReadArea(S7Object Client, int Area, int DBNumber, int Start, int Amount, int WordLen, void *pUsrData); +EXPORTSPEC int S7API Cli_WriteArea(S7Object Client, int Area, int DBNumber, int Start, int Amount, int WordLen, void *pUsrData); +EXPORTSPEC int S7API Cli_ReadMultiVars(S7Object Client, PS7DataItem Item, int ItemsCount); +EXPORTSPEC int S7API Cli_WriteMultiVars(S7Object Client, PS7DataItem Item, int ItemsCount); +// Data I/O Lean functions +EXPORTSPEC int S7API Cli_DBRead(S7Object Client, int DBNumber, int Start, int Size, void *pUsrData); +EXPORTSPEC int S7API Cli_DBWrite(S7Object Client, int DBNumber, int Start, int Size, void *pUsrData); +EXPORTSPEC int S7API Cli_MBRead(S7Object Client, int Start, int Size, void *pUsrData); +EXPORTSPEC int S7API Cli_MBWrite(S7Object Client, int Start, int Size, void *pUsrData); +EXPORTSPEC int S7API Cli_EBRead(S7Object Client, int Start, int Size, void *pUsrData); +EXPORTSPEC int S7API Cli_EBWrite(S7Object Client, int Start, int Size, void *pUsrData); +EXPORTSPEC int S7API Cli_ABRead(S7Object Client, int Start, int Size, void *pUsrData); +EXPORTSPEC int S7API Cli_ABWrite(S7Object Client, int Start, int Size, void *pUsrData); +EXPORTSPEC int S7API Cli_TMRead(S7Object Client, int Start, int Amount, void *pUsrData); +EXPORTSPEC int S7API Cli_TMWrite(S7Object Client, int Start, int Amount, void *pUsrData); +EXPORTSPEC int S7API Cli_CTRead(S7Object Client, int Start, int Amount, void *pUsrData); +EXPORTSPEC int S7API Cli_CTWrite(S7Object Client, int Start, int Amount, void *pUsrData); +// Directory functions +EXPORTSPEC int S7API Cli_ListBlocks(S7Object Client, TS7BlocksList *pUsrData); +EXPORTSPEC int S7API Cli_GetAgBlockInfo(S7Object Client, int BlockType, int BlockNum, TS7BlockInfo *pUsrData); +EXPORTSPEC int S7API Cli_GetPgBlockInfo(S7Object Client, void *pBlock, TS7BlockInfo *pUsrData, int Size); +EXPORTSPEC int S7API Cli_ListBlocksOfType(S7Object Client, int BlockType, TS7BlocksOfType *pUsrData, int &ItemsCount); +// Blocks functions +EXPORTSPEC int S7API Cli_Upload(S7Object Client, int BlockType, int BlockNum, void *pUsrData, int &Size); +EXPORTSPEC int S7API Cli_FullUpload(S7Object Client, int BlockType, int BlockNum, void *pUsrData, int &Size); +EXPORTSPEC int S7API Cli_Download(S7Object Client, int BlockNum, void *pUsrData, int Size); +EXPORTSPEC int S7API Cli_Delete(S7Object Client, int BlockType, int BlockNum); +EXPORTSPEC int S7API Cli_DBGet(S7Object Client, int DBNumber, void *pUsrData, int &Size); +EXPORTSPEC int S7API Cli_DBFill(S7Object Client, int DBNumber, int FillChar); +// Date/Time functions +EXPORTSPEC int S7API Cli_GetPlcDateTime(S7Object Client, tm &DateTime); +EXPORTSPEC int S7API Cli_SetPlcDateTime(S7Object Client, tm *DateTime); +EXPORTSPEC int S7API Cli_SetPlcSystemDateTime(S7Object Client); +// System Info functions +EXPORTSPEC int S7API Cli_GetOrderCode(S7Object Client, TS7OrderCode *pUsrData); +EXPORTSPEC int S7API Cli_GetCpuInfo(S7Object Client, TS7CpuInfo *pUsrData); +EXPORTSPEC int S7API Cli_GetCpInfo(S7Object Client, TS7CpInfo *pUsrData); +EXPORTSPEC int S7API Cli_ReadSZL(S7Object Client, int ID, int Index, TS7SZL *pUsrData, int &Size); +EXPORTSPEC int S7API Cli_ReadSZLList(S7Object Client, TS7SZLList *pUsrData, int &ItemsCount); +// Control functions +EXPORTSPEC int S7API Cli_PlcHotStart(S7Object Client); +EXPORTSPEC int S7API Cli_PlcColdStart(S7Object Client); +EXPORTSPEC int S7API Cli_PlcStop(S7Object Client); +EXPORTSPEC int S7API Cli_CopyRamToRom(S7Object Client, int Timeout); +EXPORTSPEC int S7API Cli_Compress(S7Object Client, int Timeout); +EXPORTSPEC int S7API Cli_GetPlcStatus(S7Object Client, int &Status); +// Security functions +EXPORTSPEC int S7API Cli_GetProtection(S7Object Client, TS7Protection *pUsrData); +EXPORTSPEC int S7API Cli_SetSessionPassword(S7Object Client, char *Password); +EXPORTSPEC int S7API Cli_ClearSessionPassword(S7Object Client); +// Low level +EXPORTSPEC int S7API Cli_IsoExchangeBuffer(S7Object Client, void *pUsrData, int &Size); +// Misc +EXPORTSPEC int S7API Cli_GetExecTime(S7Object Client, int &Time); +EXPORTSPEC int S7API Cli_GetLastError(S7Object Client, int &LastError); +EXPORTSPEC int S7API Cli_GetPduLength(S7Object Client, int &Requested, int &Negotiated); +EXPORTSPEC int S7API Cli_ErrorText(int Error, char *Text, int TextLen); +EXPORTSPEC int S7API Cli_GetConnected(S7Object Client, int &Connected); +//============================================================================== +// CLIENT EXPORT LIST - Async functions +//============================================================================== +EXPORTSPEC int S7API Cli_AsReadArea(S7Object Client, int Area, int DBNumber, int Start, int Amount, int WordLen, void *pUsrData); +EXPORTSPEC int S7API Cli_AsWriteArea(S7Object Client, int Area, int DBNumber, int Start, int Amount, int WordLen, void *pUsrData); +EXPORTSPEC int S7API Cli_AsDBRead(S7Object Client, int DBNumber, int Start, int Size, void *pUsrData); +EXPORTSPEC int S7API Cli_AsDBWrite(S7Object Client, int DBNumber, int Start, int Size, void *pUsrData); +EXPORTSPEC int S7API Cli_AsMBRead(S7Object Client, int Start, int Size, void *pUsrData); +EXPORTSPEC int S7API Cli_AsMBWrite(S7Object Client, int Start, int Size, void *pUsrData); +EXPORTSPEC int S7API Cli_AsEBRead(S7Object Client, int Start, int Size, void *pUsrData); +EXPORTSPEC int S7API Cli_AsEBWrite(S7Object Client, int Start, int Size, void *pUsrData); +EXPORTSPEC int S7API Cli_AsABRead(S7Object Client, int Start, int Size, void *pUsrData); +EXPORTSPEC int S7API Cli_AsABWrite(S7Object Client, int Start, int Size, void *pUsrData); +EXPORTSPEC int S7API Cli_AsTMRead(S7Object Client, int Start, int Amount, void *pUsrData); +EXPORTSPEC int S7API Cli_AsTMWrite(S7Object Client, int Start, int Amount, void *pUsrData); +EXPORTSPEC int S7API Cli_AsCTRead(S7Object Client, int Start, int Amount, void *pUsrData); +EXPORTSPEC int S7API Cli_AsCTWrite(S7Object Client, int Start, int Amount, void *pUsrData); +EXPORTSPEC int S7API Cli_AsListBlocksOfType(S7Object Client, int BlockType, TS7BlocksOfType *pUsrData, int &ItemsCount); +EXPORTSPEC int S7API Cli_AsReadSZL(S7Object Client, int ID, int Index, TS7SZL *pUsrData, int &Size); +EXPORTSPEC int S7API Cli_AsReadSZLList(S7Object Client, TS7SZLList *pUsrData, int &ItemsCount); +EXPORTSPEC int S7API Cli_AsUpload(S7Object Client, int BlockType, int BlockNum, void *pUsrData, int &Size); +EXPORTSPEC int S7API Cli_AsFullUpload(S7Object Client, int BlockType, int BlockNum, void *pUsrData, int &Size); +EXPORTSPEC int S7API Cli_AsDownload(S7Object Client, int BlockNum, void *pUsrData, int Size); +EXPORTSPEC int S7API Cli_AsCopyRamToRom(S7Object Client, int Timeout); +EXPORTSPEC int S7API Cli_AsCompress(S7Object Client, int Timeout); +EXPORTSPEC int S7API Cli_AsDBGet(S7Object Client, int DBNumber, void *pUsrData, int &Size); +EXPORTSPEC int S7API Cli_AsDBFill(S7Object Client, int DBNumber, int FillChar); +EXPORTSPEC int S7API Cli_CheckAsCompletion(S7Object Client, int &opResult); +EXPORTSPEC int S7API Cli_WaitAsCompletion(S7Object Client, int Timeout); +//============================================================================== +// SERVER EXPORT LIST +//============================================================================== +EXPORTSPEC S7Object S7API Srv_Create(); +EXPORTSPEC void S7API Srv_Destroy(S7Object &Server); +EXPORTSPEC int S7API Srv_GetParam(S7Object Server, int ParamNumber, void *pValue); +EXPORTSPEC int S7API Srv_SetParam(S7Object Server, int ParamNumber, void *pValue); +EXPORTSPEC int S7API Srv_Start(S7Object Server); +EXPORTSPEC int S7API Srv_StartTo(S7Object Server, const char *Address); +EXPORTSPEC int S7API Srv_Stop(S7Object Server); +// Data +EXPORTSPEC int S7API Srv_RegisterArea(S7Object Server, int AreaCode, word Index, void *pUsrData, int Size); +EXPORTSPEC int S7API Srv_UnregisterArea(S7Object Server, int AreaCode, word Index); +EXPORTSPEC int S7API Srv_LockArea(S7Object Server, int AreaCode, word Index); +EXPORTSPEC int S7API Srv_UnlockArea(S7Object Server, int AreaCode, word Index); +// Events +EXPORTSPEC int S7API Srv_ClearEvents(S7Object Server); +EXPORTSPEC int S7API Srv_PickEvent(S7Object Server, TSrvEvent *pEvent, int &EvtReady); +EXPORTSPEC int S7API Srv_GetMask(S7Object Server, int MaskKind, longword &Mask); +EXPORTSPEC int S7API Srv_SetMask(S7Object Server, int MaskKind, longword Mask); +EXPORTSPEC int S7API Srv_SetEventsCallback(S7Object Server, pfn_SrvCallBack pCallback, void *usrPtr); +EXPORTSPEC int S7API Srv_SetReadEventsCallback(S7Object Server, pfn_SrvCallBack pCallback, void *usrPtr); +EXPORTSPEC int S7API Srv_EventText(TSrvEvent &Event, char *Text, int TextLen); +EXPORTSPEC int S7API Srv_SetRWAreaCallback(S7Object Server, pfn_RWAreaCallBack pCallback, void *usrPtr); +// Misc +EXPORTSPEC int S7API Srv_GetStatus(S7Object Server, int &ServerStatus, int &CpuStatus, int &ClientsCount); +EXPORTSPEC int S7API Srv_SetCpuStatus(S7Object Server, int CpuStatus); +EXPORTSPEC int S7API Srv_ErrorText(int Error, char *Text, int TextLen); +//============================================================================== +// PARTNER EXPORT LIST +//============================================================================== +EXPORTSPEC S7Object S7API Par_Create(int Active); +EXPORTSPEC void S7API Par_Destroy(S7Object &Partner); +EXPORTSPEC int S7API Par_GetParam(S7Object Partner, int ParamNumber, void *pValue); +EXPORTSPEC int S7API Par_SetParam(S7Object Partner, int ParamNumber, void *pValue); +EXPORTSPEC int S7API Par_Start(S7Object Partner); +EXPORTSPEC int S7API Par_StartTo(S7Object Partner, const char *LocalAddress, const char *RemoteAddress, + word LocTsap, word RemTsap); +EXPORTSPEC int S7API Par_Stop(S7Object Partner); +// BSend +EXPORTSPEC int S7API Par_BSend(S7Object Partner, longword R_ID, void *pUsrData, int Size); +EXPORTSPEC int S7API Par_AsBSend(S7Object Partner, longword R_ID, void *pUsrData, int Size); +EXPORTSPEC int S7API Par_CheckAsBSendCompletion(S7Object Partner, int &opResult); +EXPORTSPEC int S7API Par_WaitAsBSendCompletion(S7Object Partner, longword Timeout); +EXPORTSPEC int S7API Par_SetSendCallback(S7Object Partner, pfn_ParBSendCompletion pCompletion, void *usrPtr); +// BRecv +EXPORTSPEC int S7API Par_BRecv(S7Object Partner, longword &R_ID, void *pData, int &Size, longword Timeout); +EXPORTSPEC int S7API Par_CheckAsBRecvCompletion(S7Object Partner, int &opResult, longword &R_ID, + void *pData, int &Size); +EXPORTSPEC int S7API Par_SetRecvCallback(S7Object Partner, pfn_ParBRecvCallBack pCompletion, void *usrPtr); +// Stat +EXPORTSPEC int S7API Par_GetTimes(S7Object Partner, longword &SendTime, longword &RecvTime); +EXPORTSPEC int S7API Par_GetStats(S7Object Partner, longword &BytesSent, longword &BytesRecv, + longword &SendErrors, longword &RecvErrors); +EXPORTSPEC int S7API Par_GetLastError(S7Object Partner, int &LastError); +EXPORTSPEC int S7API Par_GetStatus(S7Object Partner, int &Status); +EXPORTSPEC int S7API Par_ErrorText(int Error, char *Text, int TextLen); + + + +#endif // snap7_libmain_h diff --git a/core/src/drivers/plugins/native/s7comm/snap7/sys/snap_msgsock.cpp b/core/src/drivers/plugins/native/s7comm/snap7/sys/snap_msgsock.cpp new file mode 100644 index 00000000..6f384c2e --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/sys/snap_msgsock.cpp @@ -0,0 +1,923 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 Davide Nardella | +| All rights reserved. | +|==============================================================================| +| SNAP7 is free software: you can redistribute it and/or modify | +| it under the terms of the Lesser GNU General Public License as published by | +| the Free Software Foundation, either version 3 of the License, or | +| (at your option) any later version. | +| | +| It means that you can distribute your commercial software linked with | +| SNAP7 without the requirement to distribute the source code of your | +| application and without the requirement that your application be itself | +| distributed under LGPL. | +| | +| SNAP7 is distributed in the hope that it will be useful, | +| but WITHOUT ANY WARRANTY; without even the implied warranty of | +| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | +| Lesser GNU General Public License for more details. | +| | +| You should have received a copy of the GNU General Public License and a | +| copy of Lesser GNU General Public License along with Snap7. | +| If not, see http://www.gnu.org/licenses/ | +|=============================================================================*/ + +#include "snap_msgsock.h" + +//--------------------------------------------------------------------------- + +static SocketsLayer SocketsLayerInitializer; + +//--------------------------------------------------------------------------- +// Base class endian aware +//--------------------------------------------------------------------------- +TSnapBase::TSnapBase() +{ + int x = 1; + LittleEndian=*(char *)&x == 1; +} +//--------------------------------------------------------------------------- +word TSnapBase::SwapWord(word Value) +{ + if (LittleEndian) + return ((Value >> 8) & 0xFF) | ((Value << 8) & 0xFF00); + else + return Value; +} +//--------------------------------------------------------------------------- +longword TSnapBase::SwapDWord(longword Value) +{ + if (LittleEndian) + return (Value >> 24) | ((Value << 8) & 0x00FF0000) | ((Value >> 8) & 0x0000FF00) | (Value << 24); + else + return Value; +} +//--------------------------------------------------------------------------- +void Msg_CloseSocket(socket_t FSocket) +{ + #ifdef OS_WINDOWS + closesocket(FSocket); + #else + close(FSocket); + #endif +} +//--------------------------------------------------------------------------- +longword Msg_GetSockAddr(socket_t FSocket) +{ + sockaddr_in RemoteSin; + #ifdef OS_WINDOWS + int namelen = sizeof(RemoteSin); + #else + uint32_t namelen = sizeof(RemoteSin); + #endif + namelen=sizeof(sockaddr_in); + if (getpeername(FSocket,(struct sockaddr*)&RemoteSin, &namelen)==0) + return RemoteSin.sin_addr.s_addr; + else + return 0; +} +//--------------------------------------------------------------------------- +TMsgSocket::TMsgSocket() +{ + Pinger = new TPinger(); + // Set Defaults + strcpy(LocalAddress,"0.0.0.0"); + LocalPort=0; + strcpy(RemoteAddress,"127.0.0.1"); + RemotePort=0; + WorkInterval=100; + RecvTimeout=500; + SendTimeout=10; + PingTimeout=750; + Connected=false; + FSocket=INVALID_SOCKET; + LastTcpError=0; + LocalBind=0; +} +//--------------------------------------------------------------------------- +TMsgSocket::~TMsgSocket() +{ + DestroySocket(); + delete Pinger; +} +//--------------------------------------------------------------------------- +void TMsgSocket::SetSin(sockaddr_in &sin, char *Address, u_short Port) +{ + uint32_t in_addr; + in_addr=inet_addr(Address); + memset(&sin, 0, sizeof(sin)); + LastTcpError=0; + + if (in_addr!=INADDR_NONE) + { + sin.sin_addr.s_addr = in_addr; + sin.sin_family = AF_INET; + sin.sin_port = htons(Port); + } + else + LastTcpError=WSAEINVALIDADDRESS; +} +//--------------------------------------------------------------------------- +void TMsgSocket::GetSin(sockaddr_in sin, char *Address, u_short &Port) +{ + strcpy(Address,inet_ntoa(sin.sin_addr)); + Port=htons(sin.sin_port); +} +//--------------------------------------------------------------------------- +void TMsgSocket::GetLocal() +{ + #ifdef OS_WINDOWS + int namelen = sizeof(LocalSin); + #else + uint32_t namelen = sizeof(LocalSin); + #endif + if (getsockname(FSocket, (struct sockaddr*)&LocalSin, &namelen)==0) + GetSin(LocalSin, LocalAddress, LocalPort); +} +//--------------------------------------------------------------------------- +void TMsgSocket::GetRemote() +{ + #ifdef OS_WINDOWS + int namelen = sizeof(RemoteSin); + #else + uint32_t namelen = sizeof(RemoteSin); + #endif + if (getpeername(FSocket,(struct sockaddr*)&RemoteSin, &namelen)==0) + GetSin(RemoteSin, RemoteAddress, RemotePort); +} +//--------------------------------------------------------------------------- +int TMsgSocket::GetLastSocketError() +{ +#ifdef OS_WINDOWS + return WSAGetLastError(); +#else + return errno; +#endif +} +//--------------------------------------------------------------------------- +void TMsgSocket::Purge() +{ + // small buffer to empty the socket + char Trash[512]; + int Read; + if (LastTcpError!=WSAECONNRESET) + { + if (CanRead(0)) { + do + { + Read=recv(FSocket, Trash, 512, MSG_NOSIGNAL ); + } while(Read==512); + } + } +} +//--------------------------------------------------------------------------- +void TMsgSocket::CreateSocket() +{ + DestroySocket(); + LastTcpError=0; + FSocket =socket(AF_INET, SOCK_STREAM, IPPROTO_TCP ); + if (FSocket!=INVALID_SOCKET) + SetSocketOptions(); + else + LastTcpError =GetLastSocketError(); +} +//--------------------------------------------------------------------------- +void TMsgSocket::GotSocket() +{ + ClientHandle=RemoteSin.sin_addr.s_addr; + // could be inherited it if wee need further actions on the socket +} +//--------------------------------------------------------------------------- +void TMsgSocket::SetSocket(socket_t s) +{ + FSocket=s; + if (FSocket!=INVALID_SOCKET) + { + SetSocketOptions(); + GetLocal(); + GetRemote(); + GotSocket(); + } + Connected=FSocket!=INVALID_SOCKET; +} +//--------------------------------------------------------------------------- +void TMsgSocket::DestroySocket() +{ + if(FSocket != INVALID_SOCKET) + { + if (shutdown(FSocket, SD_SEND)==0) + Purge(); + #ifdef OS_WINDOWS + closesocket(FSocket); + #else + close(FSocket); + #endif + FSocket=INVALID_SOCKET; + } + LastTcpError=0; +} +//--------------------------------------------------------------------------- +int TMsgSocket::WaitingData() +{ + int result = 0; + u_long x = 0; +#ifdef OS_WINDOWS + if (ioctlsocket(FSocket, FIONREAD, &x) == 0) + result = x; +#else + if (ioctl(FSocket, FIONREAD, &x) == 0) + result = x; +#endif + if (result>MaxPacketSize) + result = MaxPacketSize; + return result; +} +//--------------------------------------------------------------------------- +int TMsgSocket::WaitForData(int Size, int Timeout) +{ + longword Elapsed; + + // Check for connection active + if (CanRead(0) && (WaitingData()==0)) + LastTcpError=WSAECONNRESET; + else + LastTcpError=0; + + // Enter main loop + if (LastTcpError==0) + { + Elapsed =SysGetTick(); + while((WaitingData()=(longword)(Timeout)) + LastTcpError =WSAETIMEDOUT; + else + SysSleep(1); + } + } + if(LastTcpError==WSAECONNRESET) + Connected =false; + + return LastTcpError; +} +//--------------------------------------------------------------------------- +void TMsgSocket::SetSocketOptions() +{ + int NoDelay = 1; + int KeepAlive = 1; + LastTcpError=0; + SockCheck(setsockopt(FSocket, IPPROTO_TCP, TCP_NODELAY,(char*)&NoDelay, sizeof(NoDelay))); + + if (LastTcpError==0) + SockCheck(setsockopt(FSocket, SOL_SOCKET, SO_KEEPALIVE,(char*)&KeepAlive, sizeof(KeepAlive))); +} +//--------------------------------------------------------------------------- +int TMsgSocket::SockCheck(int SockResult) +{ + if (SockResult == (int)(SOCKET_ERROR)) + LastTcpError = GetLastSocketError(); + + return LastTcpError; +} +//--------------------------------------------------------------------------- +bool TMsgSocket::CanWrite(int Timeout) +{ + timeval TimeV; + int64_t x; + fd_set FDset; + + if(FSocket == INVALID_SOCKET) + return false; + + TimeV.tv_usec = (Timeout % 1000) * 1000; + TimeV.tv_sec = Timeout / 1000; + + FD_ZERO(&FDset); + FD_SET(FSocket, &FDset); + + x = select(FSocket + 1, NULL, &FDset, NULL, &TimeV); //<-Ignore this warning in 64bit Visual Studio + if (x==(int)SOCKET_ERROR) + { + LastTcpError = GetLastSocketError(); + x=0; + } + return (x > 0); +} +//--------------------------------------------------------------------------- +bool TMsgSocket::CanRead(int Timeout) +{ + timeval TimeV; + int64_t x; + fd_set FDset; + + if(FSocket == INVALID_SOCKET) + return false; + + TimeV.tv_usec = (Timeout % 1000) * 1000; + TimeV.tv_sec = Timeout / 1000; + + FD_ZERO(&FDset); + FD_SET(FSocket, &FDset); + + x = select(FSocket + 1, &FDset, NULL, NULL, &TimeV); //<-Ignore this warning in 64bit Visual Studio + if (x==(int)SOCKET_ERROR) + { + LastTcpError = GetLastSocketError(); + x=0; + } + return (x > 0); +} +//--------------------------------------------------------------------------- +#ifdef NON_BLOCKING_CONNECT +// +// Non blocking connection (UNIX) Thanks to Rolf Stalder +// +int TMsgSocket::SckConnect() +{ + int n, flags, err; + socklen_t len; + fd_set rset, wset; + struct timeval tval; + + SetSin(RemoteSin, RemoteAddress, RemotePort); + + if (LastTcpError == 0) { + CreateSocket(); + if (LastTcpError == 0) { + flags = fcntl(FSocket, F_GETFL, 0); + if (flags >= 0) { + if (fcntl(FSocket, F_SETFL, flags | O_NONBLOCK) != -1) { + n = connect(FSocket, (struct sockaddr*)&RemoteSin, sizeof(RemoteSin)); + if (n < 0) { + if (errno != EINPROGRESS) { + LastTcpError = GetLastSocketError(); + } + else { + // still connecting ... + FD_ZERO(&rset); + FD_SET(FSocket, &rset); + wset = rset; + tval.tv_sec = PingTimeout / 1000; + tval.tv_usec = (PingTimeout % 1000) * 1000; + + n = select(FSocket+1, &rset, &wset, NULL, + (PingTimeout ? &tval : NULL)); + if (n == 0) { + // timeout + LastTcpError = WSAEHOSTUNREACH; + } + else { + if (FD_ISSET(FSocket, &rset) || FD_ISSET(FSocket, &wset)) { + err = 0; + len = sizeof(err); + if (getsockopt( + FSocket, SOL_SOCKET, SO_ERROR, &err, &len) == 0) { + if (err) { + LastTcpError = err; + } + else { + if (fcntl(FSocket, F_SETFL, flags) != -1) { + GetLocal(); + ClientHandle = LocalSin.sin_addr.s_addr; + } + else { + LastTcpError = GetLastSocketError(); + } + } + } + else { + LastTcpError = GetLastSocketError(); + } + } + else { + LastTcpError = -1; + } + } + } // still connecting + } + else if (n == 0) { + // connected immediatly + GetLocal(); + ClientHandle = LocalSin.sin_addr.s_addr; + } + } + else { + LastTcpError = GetLastSocketError(); + } // fcntl(F_SETFL) + } + else { + LastTcpError = GetLastSocketError(); + } // fcntl(F_GETFL) + } //valid socket + } // LastTcpError==0 + Connected=LastTcpError==0; + return LastTcpError; +} +#else +// +// Regular connection (Windows) +// +int TMsgSocket::SckConnect() +{ + int Result; + SetSin(RemoteSin, RemoteAddress, RemotePort); + if (LastTcpError==0) + { + if (Ping(RemoteSin)) + { + CreateSocket(); + if (LastTcpError==0) + { + Result=connect(FSocket, (struct sockaddr*)&RemoteSin, sizeof(RemoteSin)); + if (SockCheck(Result)==0) + { + GetLocal(); + // Client handle is self_address (here the connection is ACTIVE) + ClientHandle=LocalSin.sin_addr.s_addr; + } + } + } + else + LastTcpError=WSAEHOSTUNREACH; + } + Connected=LastTcpError==0; + return LastTcpError; +} +#endif +//--------------------------------------------------------------------------- +void TMsgSocket::SckDisconnect() +{ + DestroySocket(); + Connected=false; +} +//--------------------------------------------------------------------------- +void TMsgSocket::ForceClose() +{ + if(FSocket != INVALID_SOCKET) + { + try { + #ifdef OS_WINDOWS + closesocket(FSocket); + #else + close(FSocket); + #endif + } catch (...) { + } + FSocket=INVALID_SOCKET; + } + LastTcpError=0; +} +//--------------------------------------------------------------------------- +int TMsgSocket::SckBind() +{ + int Res; + int Opt=1; + SetSin(LocalSin, LocalAddress, LocalPort); + if (LastTcpError==0) + { + CreateSocket(); + if (LastTcpError==0) + { + setsockopt(FSocket ,SOL_SOCKET, SO_REUSEADDR, (const char *)&Opt, sizeof(int)); + Res =bind(FSocket, (struct sockaddr*)&LocalSin, sizeof(sockaddr_in)); + SockCheck(Res); + if (Res==0) + { + LocalBind=LocalSin.sin_addr.s_addr; + } + } + } + else + LastTcpError=WSAEINVALIDADDRESS; + + return LastTcpError; +} +//--------------------------------------------------------------------------- +int TMsgSocket::SckListen() +{ + LastTcpError=0; + SockCheck(listen(FSocket ,SOMAXCONN)); + return LastTcpError; +} +//--------------------------------------------------------------------------- +bool TMsgSocket::Ping(char *Host) +{ + return Pinger->Ping(Host, PingTimeout); +} +//--------------------------------------------------------------------------- +bool TMsgSocket::Ping(sockaddr_in Addr) +{ + if (PingTimeout == 0) + return true; + else + return Pinger->Ping(Addr.sin_addr.s_addr, PingTimeout); +} +//--------------------------------------------------------------------------- +socket_t TMsgSocket::SckAccept() +{ + socket_t result; + LastTcpError=0; + result = accept(FSocket, NULL, NULL); + if(result==INVALID_SOCKET) + LastTcpError =GetLastSocketError(); + return result; +} +//--------------------------------------------------------------------------- +int TMsgSocket::SendPacket(void *Data, int Size) +{ + int Result; + + LastTcpError=0; + if (SendTimeout>0) + { + if (!CanWrite(SendTimeout)) + { + LastTcpError = WSAETIMEDOUT; + return LastTcpError; + } + } + if (send(FSocket, (char*)Data, Size, MSG_NOSIGNAL)==Size) + return 0; + else + Result =SOCKET_ERROR; + + SockCheck(Result); + return Result; +} +//--------------------------------------------------------------------------- +bool TMsgSocket::PacketReady(int Size) +{ + return (WaitingData()>=Size); +} +//--------------------------------------------------------------------------- +int TMsgSocket::Receive(void *Data, int BufSize, int &SizeRecvd) +{ + LastTcpError=0; + if (CanRead(RecvTimeout)) + { + SizeRecvd=recv(FSocket ,(char*)Data ,BufSize ,MSG_NOSIGNAL ); + + if (SizeRecvd>0) // something read (default case) + LastTcpError=0; + else + if (SizeRecvd==0) + LastTcpError = WSAECONNRESET; // Connection reset by Peer + else + LastTcpError=GetLastSocketError(); // we need to know what happened + } + else + LastTcpError = WSAETIMEDOUT; + + if (LastTcpError==WSAECONNRESET) + Connected = false; + + return LastTcpError; +} +//--------------------------------------------------------------------------- +int TMsgSocket::RecvPacket(void *Data, int Size) +{ + int BytesRead; + WaitForData(Size, RecvTimeout); + if (LastTcpError==0) + { + BytesRead=recv(FSocket, (char*)Data, Size, MSG_NOSIGNAL); + if (BytesRead==0) + LastTcpError = WSAECONNRESET; // Connection reset by Peer + else + if (BytesRead<0) + LastTcpError = GetLastSocketError(); + } + else // After the timeout the bytes waiting were less then we expected + if (LastTcpError==WSAETIMEDOUT) + Purge(); + + if (LastTcpError==WSAECONNRESET) + Connected =false; + + return LastTcpError; +} +//--------------------------------------------------------------------------- +int TMsgSocket::PeekPacket(void *Data, int Size) +{ + int BytesRead; + WaitForData(Size, RecvTimeout); + if (LastTcpError==0) + { + BytesRead=recv(FSocket, (char*)Data, Size, MSG_PEEK | MSG_NOSIGNAL ); + if (BytesRead==0) + LastTcpError = WSAECONNRESET; // Connection reset by Peer + else + if (BytesRead<0) + LastTcpError = GetLastSocketError(); + } + else // After the timeout the bytes waiting were less then we expected + if (LastTcpError==WSAETIMEDOUT) + Purge(); + + if (LastTcpError==WSAECONNRESET) + Connected =false; + + return LastTcpError; +} +//--------------------------------------------------------------------------- +bool TMsgSocket::Execute() +{ + return true; +} +//============================================================================== +// PING +//============================================================================== + +static int PingKind; + +#ifdef OS_WINDOWS +// iphlpapi, is loaded dinamically because if this fails we can still try +// to use raw sockets + +static char const *iphlpapi = "\\iphlpapi.dll"; +#pragma pack(1) + +//typedef byte TTxBuffer[40]; +typedef byte TTxBuffer[32]; +#pragma pack() + +typedef HANDLE (__stdcall *pfn_IcmpCreateFile)(); +typedef bool (__stdcall *pfn_IcmpCloseHandle)(HANDLE PingHandle); + +typedef int (__stdcall *pfn_IcmpSendEcho2)( + HANDLE PingHandle, + void *Event, + void *AcpRoutine, + void *AcpContext, + unsigned long DestinationAddress, + void *RequestData, + int RequestSize, + void *not_used, //should be *IP_OPTION_INFORMATION but we don't use it + void *ReplyBuffer, + int ReplySize, + int Timeout +); + +static pfn_IcmpCreateFile IcmpCreateFile; +static pfn_IcmpCloseHandle IcmpCloseHandle; +static pfn_IcmpSendEcho2 IcmpSendEcho2; +static HINSTANCE IcmpDllHandle = 0; +static bool IcmpAvail = false; + +bool IcmpInit() +{ + char iphlppath[MAX_PATH+12]; + + int PathLen = GetSystemDirectoryA(iphlppath, MAX_PATH); + if (PathLen != 0) + { + strcat(iphlppath, iphlpapi); + IcmpDllHandle = LoadLibraryA(iphlppath); + } + else + IcmpDllHandle = 0; + + if (IcmpDllHandle != 0) + { + IcmpCreateFile=(pfn_IcmpCreateFile)GetProcAddress(IcmpDllHandle,"IcmpCreateFile"); + IcmpCloseHandle=(pfn_IcmpCloseHandle)GetProcAddress(IcmpDllHandle,"IcmpCloseHandle"); + IcmpSendEcho2=(pfn_IcmpSendEcho2)GetProcAddress(IcmpDllHandle,"IcmpSendEcho2"); + return (IcmpCreateFile!=NULL) && (IcmpCloseHandle!=NULL) && (IcmpSendEcho2!=NULL); + } + else + return false; +} + +void IcmpDone() +{ + if (IcmpDllHandle!=0) + FreeLibrary(IcmpDllHandle); + IcmpAvail=false; +} +#endif + +//--------------------------------------------------------------------------- +// RAW Socket Pinger +//--------------------------------------------------------------------------- +TRawSocketPinger::TRawSocketPinger() +{ + FSocket =socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); + FId =word(size_t(this)); + FSeq =0; +} +//--------------------------------------------------------------------------- +TRawSocketPinger::~TRawSocketPinger() +{ + if (FSocket!=INVALID_SOCKET) + { + #ifdef OS_WINDOWS + closesocket(FSocket); + #else + close(FSocket); + #endif + FSocket=INVALID_SOCKET; + }; +} +//--------------------------------------------------------------------------- +void TRawSocketPinger::InitPacket() +{ + memset(&IcmpBuffer,0,ICmpBufferSize); + FSeq++; + + SendPacket=PIcmpPacket(pbyte(&IcmpBuffer)+sizeof(TIPHeader)); + SendPacket->Header.ic_type=ICMP_ECHORQ; + SendPacket->Header.ic_code=0; + SendPacket->Header.ic_cksum=0; + SendPacket->Header.ic_id=FId; + SendPacket->Header.ic_seq=FSeq; + + memset(&SendPacket->Data,0,sizeof(SendPacket->Data)); + SendPacket->Header.ic_cksum=PacketChecksum(); +} +//--------------------------------------------------------------------------- +word TRawSocketPinger::PacketChecksum() +{ + word *P = (word*)(SendPacket); + longword Sum = 0; + int c; + for (c = 0; c < int(sizeof(TIcmpPacket) / 2); c++) { + Sum+=*P; + P++; + } + Sum=(Sum >> 16) + (Sum & 0xFFFF); + Sum=Sum+(Sum >> 16); + return word(~Sum); +} +//--------------------------------------------------------------------------- +bool TRawSocketPinger::CanRead(int Timeout) +{ + timeval TimeV; + int64_t x; + fd_set FDset; + + TimeV.tv_usec = (Timeout % 1000) * 1000; + TimeV.tv_sec = Timeout / 1000; + + FD_ZERO(&FDset); + FD_SET(FSocket, &FDset); + + x = select(FSocket + 1, &FDset, NULL, NULL, &TimeV); //<-Ignore this warning in 64bit Visual Studio + if (x==(int)(SOCKET_ERROR)) + x=0; + return (x > 0); +} +//--------------------------------------------------------------------------- +bool TRawSocketPinger::Ping(longword ip_addr, int Timeout) +{ + sockaddr_in LSockAddr; + sockaddr_in RSockAddr; + PIcmpReply Reply; + + if (FSocket==INVALID_SOCKET) + return true; + + // Init packet + InitPacket(); + Reply=PIcmpReply(&IcmpBuffer); + // Init Remote and Local Addresses struct + RSockAddr.sin_family=AF_INET; + RSockAddr.sin_port=0; + RSockAddr.sin_addr.s_addr=ip_addr; + + LSockAddr.sin_family=AF_INET; + LSockAddr.sin_port=0; + LSockAddr.sin_addr.s_addr=inet_addr("0.0.0.0"); + + // Bind to local + if (bind(FSocket, (struct sockaddr*)&LSockAddr, sizeof(sockaddr_in))!=0) + return false; + // Connect to remote (not a really TCP connection, only to setup the socket) + if (connect(FSocket, (struct sockaddr*)&RSockAddr, sizeof(sockaddr_in))!=0) + return false; + // Send ICMP packet + if (send(FSocket, (char*)SendPacket, sizeof(TIcmpPacket), MSG_NOSIGNAL)!=int(sizeof(TIcmpPacket))) + return false; + // Wait for a reply + if (!CanRead(Timeout)) + return false;// time expired + // Get the answer + if (recv(FSocket, (char*)&IcmpBuffer, ICmpBufferSize, MSG_NOSIGNAL)IPH.ip_src==RSockAddr.sin_addr.s_addr) && // the peer is what we are looking for + (Reply->ICmpReply.Header.ic_type==ICMP_ECHORP); // type = reply +} +//--------------------------------------------------------------------------- +// Pinger +//--------------------------------------------------------------------------- +TPinger::TPinger() +{ +} +//--------------------------------------------------------------------------- +TPinger::~TPinger() +{ +} +//--------------------------------------------------------------------------- +bool TPinger::RawPing(longword ip_addr, int Timeout) +{ + PRawSocketPinger RawPinger = new TRawSocketPinger(); + bool Result; + Result=RawPinger->Ping(ip_addr, Timeout); + delete RawPinger; + return Result; +} +//--------------------------------------------------------------------------- +#ifdef OS_WINDOWS +bool TPinger::WinPing(longword ip_addr, int Timeout) +{ + HANDLE PingHandle; + TTxBuffer TxBuffer; + TIcmpBuffer IcmpBuffer; + bool Result; + + PingHandle = IcmpCreateFile(); + if (PingHandle != INVALID_HANDLE_VALUE) + { + memset(&TxBuffer,'\55',sizeof(TTxBuffer)); + Result=(IcmpSendEcho2(PingHandle, NULL, NULL, NULL, ip_addr, + &TxBuffer, sizeof(TxBuffer), NULL, &IcmpBuffer, ICmpBufferSize, Timeout))>0; + IcmpCloseHandle(PingHandle); + return Result; + } + else + return false; +} +#endif +//--------------------------------------------------------------------------- +bool TPinger::Ping(char *Host, int Timeout) +{ + longword Addr; + Addr=inet_addr(Host); + return Ping(Addr, Timeout); +} +//--------------------------------------------------------------------------- +bool TPinger::Ping(longword ip_addr, int Timeout) +{ +#ifdef OS_WINDOWS + if (PingKind==pkWinHelper) + return WinPing(ip_addr, Timeout); + else +#endif + if (PingKind==pkRawSocket) + return RawPing(ip_addr, Timeout); + else + return true; // we still need to continue +} +//--------------------------------------------------------------------------- +// Checks if raw sockets are allowed +//--------------------------------------------------------------------------- +bool RawSocketsCheck() +{ + socket_t RawSocket; + bool Result; + RawSocket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); + + Result=RawSocket != INVALID_SOCKET; + if (Result) + #ifdef OS_WINDOWS + closesocket(RawSocket); + #else + close(RawSocket); + #endif + + return Result; +} +//--------------------------------------------------------------------------- +// Sockets init +// - Winsock Startup (Windows) +// - ICMP Helper Load (Windows) +// - Check for raw socket (Unix or Windows if ICMP load failed) +//--------------------------------------------------------------------------- +SocketsLayer::SocketsLayer() +{ +#ifdef OS_WINDOWS + timeBeginPeriod(1); // it's not strictly related to socket but here is a nice place + WSAStartup(0x202,&wsaData); + if (IcmpInit()) + PingKind=pkWinHelper; + else +#endif + if (RawSocketsCheck()) + PingKind=pkRawSocket; + else + PingKind=pkCannotPing; +} + +SocketsLayer::~SocketsLayer() +{ +#ifdef OS_WINDOWS + IcmpDone(); + WSACleanup(); + timeEndPeriod(1); +#endif +} + + diff --git a/core/src/drivers/plugins/native/s7comm/snap7/sys/snap_msgsock.h b/core/src/drivers/plugins/native/s7comm/snap7/sys/snap_msgsock.h new file mode 100644 index 00000000..d9826ba9 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/sys/snap_msgsock.h @@ -0,0 +1,339 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 Davide Nardella | +| All rights reserved. | +|==============================================================================| +| SNAP7 is free software: you can redistribute it and/or modify | +| it under the terms of the Lesser GNU General Public License as published by | +| the Free Software Foundation, either version 3 of the License, or | +| (at your option) any later version. | +| | +| It means that you can distribute your commercial software linked with | +| SNAP7 without the requirement to distribute the source code of your | +| application and without the requirement that your application be itself | +| distributed under LGPL. | +| | +| SNAP7 is distributed in the hope that it will be useful, | +| but WITHOUT ANY WARRANTY; without even the implied warranty of | +| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | +| Lesser GNU General Public License for more details. | +| | +| You should have received a copy of the GNU General Public License and a | +| copy of Lesser GNU General Public License along with Snap7. | +| If not, see http://www.gnu.org/licenses/ | +|=============================================================================*/ +#ifndef snap_msgsock_h +#define snap_msgsock_h +//--------------------------------------------------------------------------- +#include "snap_platform.h" +#include "snap_sysutils.h" +//---------------------------------------------------------------------------- +#if defined(OS_WINDOWS) || defined (OS_SOLARIS) || defined(OS_OSX) +# define MSG_NOSIGNAL 0 +#endif +//---------------------------------------------------------------------------- +// Non blocking connection to avoid root priviledges under UNIX +// i.e. raw socket pinger is not more used. +// Thanks to Rolf Stalder that made it ;) +//---------------------------------------------------------------------------- +#ifdef PLATFORM_UNIX + #define NON_BLOCKING_CONNECT +#endif +#ifdef NON_BLOCKING_CONNECT + #include +#endif +//---------------------------------------------------------------------------- +/* + In Windows sizeof socket varies depending of the platform : + win32 -> sizeof(SOCKET) = 4 + win64 -> sizeof(SOCKET) = 8 + + Even though sizeof(SOCKET) is 8, should be safe to cast it to int, because + the value constitutes an index in per-process table of limited size + and not a real pointer. + + Other Os define the socket as int regardless of the processor. + + We want to sleep peacefully, so it's better to define a portable socket. +*/ + +#ifdef OS_WINDOWS +typedef SOCKET socket_t; +#else +typedef int socket_t; +#endif + +//---------------------------------------------------------------------------- +#define SD_RECEIVE 0x00 +#define SD_SEND 0x01 +#define SD_BOTH 0x02 +#define MaxPacketSize 65536 + +//---------------------------------------------------------------------------- +// For other platform we need to re-define next constants +#if defined(PLATFORM_UNIX) || defined(OS_OSX) + +#define INVALID_SOCKET (socket_t)(~0) +#define SOCKET_ERROR (-1) + +#define WSAEINTR EINTR +#define WSAEBADF EBADF +#define WSAEACCES EACCES +#define WSAEFAULT EFAULT +#define WSAEINVAL EINVAL +#define WSAEMFILE EMFILE +#define WSAEWOULDBLOCK EWOULDBLOCK +#define WSAEINPROGRESS EINPROGRESS +#define WSAEALREADY EALREADY +#define WSAENOTSOCK ENOTSOCK +#define WSAEDESTADDRREQ EDESTADDRREQ +#define WSAEMSGSIZE EMSGSIZE +#define WSAEPROTOTYPE EPROTOTYPE +#define WSAENOPROTOOPT ENOPROTOOPT +#define WSAEPROTONOSUPPORT EPROTONOSUPPORT +#define WSAESOCKTNOSUPPORT ESOCKTNOSUPPORT +#define WSAEOPNOTSUPP EOPNOTSUPP +#define WSAEPFNOSUPPORT EPFNOSUPPORT +#define WSAEAFNOSUPPORT EAFNOSUPPORT +#define WSAEADDRINUSE EADDRINUSE +#define WSAEADDRNOTAVAIL EADDRNOTAVAIL +#define WSAENETDOWN ENETDOWN +#define WSAENETUNREACH ENETUNREACH +#define WSAENETRESET ENETRESET +#define WSAECONNABORTED ECONNABORTED +#define WSAECONNRESET ECONNRESET +#define WSAENOBUFS ENOBUFS +#define WSAEISCONN EISCONN +#define WSAENOTCONN ENOTCONN +#define WSAESHUTDOWN ESHUTDOWN +#define WSAETOOMANYREFS ETOOMANYREFS +#define WSAETIMEDOUT ETIMEDOUT +#define WSAECONNREFUSED ECONNREFUSED +#define WSAELOOP ELOOP +#define WSAENAMETOOLONG ENAMETOOLONG +#define WSAEHOSTDOWN EHOSTDOWN +#define WSAEHOSTUNREACH EHOSTUNREACH +#define WSAENOTEMPTY ENOTEMPTY +#define WSAEUSERS EUSERS +#define WSAEDQUOT EDQUOT +#define WSAESTALE ESTALE +#define WSAEREMOTE EREMOTE +#endif + +#define WSAEINVALIDADDRESS 12001 + +#define ICmpBufferSize 4096 +typedef byte TIcmpBuffer[ICmpBufferSize]; + +// Ping result +#define PR_CANNOT_PERFORM -1 // cannot ping : + // unix : no root rights or SUID flag set to + // open raw sockets + // windows : neither helper DLL found nor raw + // sockets can be opened (no administrator rights) + // In this case the execution continues whitout + // the benefit of the smart-connect. + +#define PR_SUCCESS 0 // Host found +#define PR_ERROR 1 // Ping Error, Ping was performed but ... + // - host didn't replied (not found) + // - routing error + // - TTL expired + // - ... all other icmp error that we don't need + // to know. + +// Ping Kind +#define pkCannotPing 1 // see PR_CANNOT_PERFORM comments +#define pkWinHelper 2 // use iphlpapi.dll (only windows) +#define pkRawSocket 3 // use raw sockets (unix/windows) + +const byte ICMP_ECHORP = 0; // ECHO Reply +const byte ICMP_ECHORQ = 8; // ECHO Request +//--------------------------------------------------------------------------- +// RAW SOCKET PING STRUCTS +//--------------------------------------------------------------------------- +#pragma pack(1) +typedef struct{ + byte ip_hl_v; + byte ip_tos; + word ip_len; + word ip_id ; + word ip_off; + byte ip_ttl; + byte ip_p; + word ip_sum; + longword ip_src; + longword ip_dst; +}TIPHeader; + +typedef struct{ + byte ic_type; // Type of message + byte ic_code; // Code + word ic_cksum; // 16 bit checksum + word ic_id; // ID (ic1 : ipv4) + word ic_seq; // Sequence +}TIcmpHeader; + +typedef struct{ + TIcmpHeader Header; + byte Data[32]; // use the well known default +}TIcmpPacket, *PIcmpPacket; + +typedef struct{ + TIPHeader IPH; + TIcmpPacket ICmpReply; +}TIcmpReply, *PIcmpReply; +#pragma pack() + +//--------------------------------------------------------------------------- +class TRawSocketPinger +{ +private: + socket_t FSocket; + PIcmpPacket SendPacket; + TIcmpBuffer IcmpBuffer; + word FId, FSeq; + void InitPacket(); + word PacketChecksum(); + bool CanRead(int Timeout); +public: + bool Ping(longword ip_addr, int Timeout); + TRawSocketPinger(); + ~TRawSocketPinger(); +}; +typedef TRawSocketPinger *PRawSocketPinger; +//--------------------------------------------------------------------------- +class TPinger +{ +private: + PRawSocketPinger RawPinger; + bool RawAvail; +#ifdef OS_WINDOWS + bool WinPing(longword ip_addr, int Timeout); +#endif + bool RawPing(longword ip_addr, int Timeout); +public: + TPinger(); + ~TPinger(); + bool Ping(char *Host, int Timeout); + bool Ping(longword ip_addr, int Timeout); +}; +typedef TPinger *PPinger; +//--------------------------------------------------------------------------- +class TSnapBase // base class endian-aware +{ +private: + bool LittleEndian; +protected: + longword SwapDWord(longword Value); + word SwapWord(word Value); +public: + TSnapBase(); +}; +//--------------------------------------------------------------------------- +class TMsgSocket : public TSnapBase +{ +private: + PPinger Pinger; + int GetLastSocketError(); + int SockCheck(int SockResult); + void DestroySocket(); + void SetSocketOptions(); + bool CanWrite(int Timeout); + void GetLocal(); + void GetRemote(); + void SetSin(sockaddr_in &sin, char *Address, u_short Port); + void GetSin(sockaddr_in sin, char *Address, u_short &Port); +protected: + socket_t FSocket; + sockaddr_in LocalSin; + sockaddr_in RemoteSin; + //-------------------------------------------------------------------------- + // low level socket + void CreateSocket(); + // Called when a socket is assigned externally + void GotSocket(); + // Returns how many bytes are ready to be read in the winsock buffer + int WaitingData(); + // Waits until there at least "size" bytes ready to be read or until receive timeout occurs + int WaitForData(int Size, int Timeout); + // Clear socket input buffer + void Purge(); +public: + longword ClientHandle; + longword LocalBind; + // Coordinates Address:Port + char LocalAddress[16]; + char RemoteAddress[16]; + word LocalPort; + word RemotePort; + // "speed" of the socket listener (used server-side) + int WorkInterval; + // Timeouts : 3 different values for fine tuning. + // Send timeout should be small since with work with small packets and TCP_NO_DELAY + // option, so we don't expect "time to wait". + // Recv timeout depends of equipment's processing time : we send a packet, the equipment + // processes the message, finally it sends the answer. In any case Recv timeout > Send Timeout. + // PingTimeout is the maximum time interval during which we expect that the PLC answers. + // By default is 750 ms, increase it if there are many switch/repeaters. + int PingTimeout; + int RecvTimeout; + int SendTimeout; + //int ConnTimeout; + // Output : Last operation error + int LastTcpError; + // Output : Connected to the remote Host/Peer/Client + bool Connected; + //-------------------------------------------------------------------------- + TMsgSocket(); + virtual ~TMsgSocket(); + // Returns true if "something" can be read during the Timeout interval.. + bool CanRead(int Timeout); + // Connects to a peer (using RemoteAddress and RemotePort) + int SckConnect(); // (client-side) + // Disconnects from a peer (gracefully) + void SckDisconnect(); + // Disconnects RAW + void ForceClose(); + // Binds to a local adapter (using LocalAddress and LocalPort) (server-side) + int SckBind(); + // Listens for an incoming connection (server-side) + int SckListen(); + // Set an external socket reference (tipically from a listener) + void SetSocket(socket_t s); + // Accepts an incoming connection returning a socket descriptor (server-side) + socket_t SckAccept(); + // Pings the peer before connecting + bool Ping(char *Host); + bool Ping(sockaddr_in Addr); + // Sends a packet + int SendPacket(void *Data, int Size); + // Returns true if a Packet at least of "Size" bytes is ready to be read + bool PacketReady(int Size); + // Receives everything + int Receive(void *Data, int BufSize, int & SizeRecvd); + // Receives a packet of size specified. + int RecvPacket(void *Data, int Size); + // Peeks a packet of size specified without extract it from the socket queue + int PeekPacket(void *Data, int Size); + virtual bool Execute(); +}; + +typedef TMsgSocket *PMsgSocket; +//--------------------------------------------------------------------------- +void Msg_CloseSocket(socket_t FSocket); +longword Msg_GetSockAddr(socket_t FSocket); +//--------------------------------------------------------------------------- +class SocketsLayer +{ +private: +#ifdef OS_WINDOWS + WSADATA wsaData; +#endif +public: + SocketsLayer(); + ~SocketsLayer(); +}; + +#endif // snap_msgsock_h diff --git a/core/src/drivers/plugins/native/s7comm/snap7/sys/snap_platform.h b/core/src/drivers/plugins/native/s7comm/snap7/sys/snap_platform.h new file mode 100644 index 00000000..53e8a2f8 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/sys/snap_platform.h @@ -0,0 +1,130 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 Davide Nardella | +| All rights reserved. | +|==============================================================================| +| SNAP7 is free software: you can redistribute it and/or modify | +| it under the terms of the Lesser GNU General Public License as published by | +| the Free Software Foundation, either version 3 of the License, or | +| (at your option) any later version. | +| | +| It means that you can distribute your commercial software linked with | +| SNAP7 without the requirement to distribute the source code of your | +| application and without the requirement that your application be itself | +| distributed under LGPL. | +| | +| SNAP7 is distributed in the hope that it will be useful, | +| but WITHOUT ANY WARRANTY; without even the implied warranty of | +| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | +| Lesser GNU General Public License for more details. | +| | +| You should have received a copy of the GNU General Public License and a | +| copy of Lesser GNU General Public License along with Snap7. | +| If not, see http://www.gnu.org/licenses/ | +|=============================================================================*/ +#ifndef snap_platform_h +#define snap_platform_h +//--------------------------------------------------------------------------- +#if defined (_WIN32)|| defined(_WIN64)|| defined(__WIN32__) || defined(__WINDOWS__) +# define OS_WINDOWS +#endif + +// Visual Studio needs this to use the correct time_t size +#if defined (_WIN32) && !defined(_WIN64) && !defined(_EMBEDDING_VS2013UP) + # define _USE_32BIT_TIME_T +#endif + +// Linux, BSD and Solaris define "unix", OSX doesn't, even though it derives from BSD +#if defined(unix) || defined(__unix__) || defined(__unix) +# define PLATFORM_UNIX +#endif + +#if BSD>=0 +# define OS_BSD +#endif + +#if __APPLE__ +# define OS_OSX +#endif + +#if defined(__SVR4) || defined(__svr4__) +# define OS_SOLARIS +// Thanks to Rolf Stalder now it's possible to use pthreads also for Solaris +// In any case the Solaris native threads model is still present and can be +// used uncommenting the #define line below. +# undef OS_SOLARIS_NATIVE_THREADS +// # define OS_SOLARIS_NATIVE_THREADS +#endif + +#if defined(PLATFORM_UNIX) +# include +# include +# if defined(_POSIX_VERSION) +# define POSIX +# endif +#endif + +#ifdef OS_OSX +# include +#endif + +#if (!defined (OS_WINDOWS)) && (!defined(PLATFORM_UNIX)) && (!defined(OS_BSD)) && (!defined(OS_OSX)) +# error platform still unsupported (please add it yourself and report ;-) +#endif + +#include +#include +#include +#include + +#ifdef OS_WINDOWS +# define WIN32_LEAN_AND_MEAN +# include +# include +# include +#endif + +#ifdef OS_SOLARIS +# include +# include +# include +#endif + +#if defined(PLATFORM_UNIX) || defined(OS_OSX) +# include +# include +# include +# include +# include +# include +# include +#endif + +#ifdef OS_WINDOWS +# define EXPORTSPEC extern "C" __declspec ( dllexport ) +# define S7API __stdcall +#else +# define EXPORTSPEC extern "C" +# define S7API +#endif + +// Exact length types regardless of platform/processor +// We absolute need of them, all structs have an exact size that +// must be the same across the processor used 32/64 bit + +// *Use them* if you change/expand the code and avoid long, u_long and so on... + +typedef uint8_t byte; +typedef uint16_t word; +typedef uint32_t longword; +typedef byte *pbyte; +typedef word *pword; +typedef uintptr_t snap_obj; // multi platform/processor object reference + +#ifndef OS_WINDOWS +# define INFINITE 0XFFFFFFFF +#endif + + +#endif // snap_platform_h diff --git a/core/src/drivers/plugins/native/s7comm/snap7/sys/snap_sysutils.cpp b/core/src/drivers/plugins/native/s7comm/snap7/sys/snap_sysutils.cpp new file mode 100644 index 00000000..92b6f7c8 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/sys/snap_sysutils.cpp @@ -0,0 +1,73 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 Davide Nardella | +| All rights reserved. | +|==============================================================================| +| SNAP7 is free software: you can redistribute it and/or modify | +| it under the terms of the Lesser GNU General Public License as published by | +| the Free Software Foundation, either version 3 of the License, or | +| (at your option) any later version. | +| | +| It means that you can distribute your commercial software linked with | +| SNAP7 without the requirement to distribute the source code of your | +| application and without the requirement that your application be itself | +| distributed under LGPL. | +| | +| SNAP7 is distributed in the hope that it will be useful, | +| but WITHOUT ANY WARRANTY; without even the implied warranty of | +| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | +| Lesser GNU General Public License for more details. | +| | +| You should have received a copy of the GNU General Public License and a | +| copy of Lesser GNU General Public License along with Snap7. | +| If not, see http://www.gnu.org/licenses/ | +|=============================================================================*/ + +#include "snap_sysutils.h" + +#ifdef OS_OSX +int clock_gettime(int clk_id, struct timespec* t) +{ + struct timeval now; + int rv = gettimeofday(&now, NULL); + if (rv) return rv; + t->tv_sec = now.tv_sec; + t->tv_nsec = now.tv_usec * 1000; + return 0; +} +#endif + +//--------------------------------------------------------------------------- +longword SysGetTick() +{ +#ifdef OS_WINDOWS + return timeGetTime(); +#else + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (longword) (ts.tv_sec * 1000) + (ts.tv_nsec / 1000000); +#endif +} +//--------------------------------------------------------------------------- +void SysSleep(longword Delay_ms) +{ +#ifdef OS_WINDOWS + Sleep(Delay_ms); +#else + struct timespec ts; + ts.tv_sec = (time_t)(Delay_ms / 1000); + ts.tv_nsec =(long)((Delay_ms - ts.tv_sec) * 1000000); + nanosleep(&ts, (struct timespec *)0); +#endif +} +//--------------------------------------------------------------------------- +longword DeltaTime(longword &Elapsed) +{ + longword TheTime; + TheTime=SysGetTick(); + // Checks for rollover + if (TheTimeDestroying) + { + try + { + if (!WorkerSocket->Execute()) // False -> End of Activities + SelfClose = true; + } catch (...) + { + Exception = true; + } + }; + if (!FServer->Destroying) + { + // Exception detected during Worker activity + if (Exception) + { + WorkerSocket->ForceClose(); + FServer->DoEvent(WorkerSocket->ClientHandle, evcClientException, 0, 0, 0, 0, 0); + } + else + if (SelfClose) + { + FServer->DoEvent(WorkerSocket->ClientHandle, evcClientDisconnected, 0, 0, 0, 0, 0); + } + else + FServer->DoEvent(WorkerSocket->ClientHandle, evcClientTerminated, 0, 0, 0, 0, 0); + } + delete WorkerSocket; + // Delete reference from list + FServer->Delete(Index); +} +//--------------------------------------------------------------------------- +// LISTENER THREAD +//--------------------------------------------------------------------------- + +TMsgListenerThread::TMsgListenerThread(TMsgSocket *Listener, TCustomMsgServer *Server) +{ + FServer = Server; + FListener = Listener; + FreeOnTerminate = false; +} +//--------------------------------------------------------------------------- + +void TMsgListenerThread::Execute() +{ + socket_t Sock; + bool Valid; + + while (!Terminated) + { + if (FListener->CanRead(FListener->WorkInterval)) + { + Sock = FListener->SckAccept(); // in any case we must accept + Valid = Sock != INVALID_SOCKET; + // check if we are not destroying + if ((!Terminated) && (!FServer->Destroying)) + { + if (Valid) + FServer->Incoming(Sock); + } + else + if (Valid) + Msg_CloseSocket(Sock); + }; + } +} +//--------------------------------------------------------------------------- +// TCP SERVER +//--------------------------------------------------------------------------- +TCustomMsgServer::TCustomMsgServer() +{ + strcpy(FLocalAddress, "0.0.0.0"); + CSList = new TSnapCriticalSection(); + CSEvent = new TSnapCriticalSection(); + FEventQueue = new TMsgEventQueue(MaxEvents, sizeof (TSrvEvent)); + memset(Workers, 0, sizeof (Workers)); + for (int i = 0; i < MaxWorkers; i++) + Workers[i] = NULL; + Status = SrvStopped; + EventMask = 0xFFFFFFFF; + LogMask = 0xFFFFFFFF; + Destroying = false; + FLastError = 0; + ClientsCount = 0; + LocalBind = 0; + MaxClients = MaxWorkers; + OnEvent = NULL; +} +//--------------------------------------------------------------------------- +TCustomMsgServer::~TCustomMsgServer() +{ + Destroying = true; + Stop(); + OnEvent = NULL; + delete CSList; + delete CSEvent; + delete FEventQueue; +} +//--------------------------------------------------------------------------- +void TCustomMsgServer::LockList() +{ + CSList->Enter(); +} +//--------------------------------------------------------------------------- +void TCustomMsgServer::UnlockList() +{ + CSList->Leave(); +} +//--------------------------------------------------------------------------- +int TCustomMsgServer::FirstFree() +{ + int i; + for (i = 0; i < MaxWorkers; i++) + { + if (Workers[i] == 0) + return i; + } + return -1; +} +//--------------------------------------------------------------------------- + +int TCustomMsgServer::StartListener() +{ + int Result; + // Creates the listener + SockListener = new TMsgSocket(); + strncpy(SockListener->LocalAddress, FLocalAddress, 16); + SockListener->LocalPort = LocalPort; + // Binds + Result = SockListener->SckBind(); + if (Result == 0) + { + LocalBind = SockListener->LocalBind; + // Listen + Result = SockListener->SckListen(); + if (Result == 0) + { + // Creates the Listener thread + ServerThread = new TMsgListenerThread(SockListener, this); + ServerThread->Start(); + } + else + delete SockListener; + } + else + delete SockListener; + + return Result; +} +//--------------------------------------------------------------------------- +void TCustomMsgServer::TerminateAll() +{ + int c; + longword Elapsed; + bool Timeout; + + if (ClientsCount > 0) + { + for (c = 0; c < MaxWorkers; c++) + { + if (Workers[c] != 0) + PMsgWorkerThread(Workers[c])->Terminate(); + } + // Wait for closing + Elapsed = SysGetTick(); + Timeout = false; + while (!Timeout && (ClientsCount > 0)) + { + Timeout = DeltaTime(Elapsed) > WkTimeout; + if (!Timeout) + SysSleep(100); + }; + if (ClientsCount > 0) + KillAll(); // one o more threads are hanged + ClientsCount = 0; + } +} +//--------------------------------------------------------------------------- +void TCustomMsgServer::KillAll() +{ + int c, cnt = 0; + LockList(); + for (c = 0; c < MaxWorkers; c++) + { + if (Workers[c] != 0) + try + { + PMsgWorkerThread(Workers[c])->Kill(); + PMsgWorkerThread(Workers[c])->WorkerSocket->ForceClose(); + delete PMsgWorkerThread(Workers[c]); + Workers[c] = 0; + cnt++; + } catch (...) + { + }; + } + UnlockList(); + DoEvent(0, evcClientsDropped, 0, cnt, 0, 0, 0); +} +//--------------------------------------------------------------------------- +bool TCustomMsgServer::CanAccept(socket_t Socket) +{ + return ((MaxClients == 0) || (ClientsCount < MaxClients)); +} +//--------------------------------------------------------------------------- +PWorkerSocket TCustomMsgServer::CreateWorkerSocket(socket_t Sock) +{ + PWorkerSocket Result; + // Creates a funny default class : a tcp echo worker + Result = new TEcoTcpWorker(); + Result->SetSocket(Sock); + return Result; +} +//--------------------------------------------------------------------------- +void TCustomMsgServer::DoEvent(int Sender, longword Code, word RetCode, word Param1, word Param2, word Param3, word Param4) +{ + TSrvEvent SrvEvent; + bool GoLog = (Code & LogMask) != 0; + bool GoEvent = (Code & EventMask) != 0; + + if (!Destroying && (GoLog || GoEvent)) + { + CSEvent->Enter(); + + time(&SrvEvent.EvtTime); + SrvEvent.EvtSender = Sender; + SrvEvent.EvtCode = Code; + SrvEvent.EvtRetCode = RetCode; + SrvEvent.EvtParam1 = Param1; + SrvEvent.EvtParam2 = Param2; + SrvEvent.EvtParam3 = Param3; + SrvEvent.EvtParam4 = Param4; + + if (GoEvent && (OnEvent != NULL)) + try + { // callback is outside here, we have to shield it + OnEvent(FUsrPtr, &SrvEvent, sizeof (TSrvEvent)); + } catch (...) + { + }; + + if (GoLog) + FEventQueue->Insert(&SrvEvent); + + CSEvent->Leave(); + }; +} +//--------------------------------------------------------------------------- +void TCustomMsgServer::Delete(int Index) +{ + LockList(); + Workers[Index] = 0; + ClientsCount--; + UnlockList(); +} +//--------------------------------------------------------------------------- +void TCustomMsgServer::Incoming(socket_t Sock) +{ + int idx; + PWorkerSocket WorkerSocket; + longword ClientHandle = Msg_GetSockAddr(Sock); + + if (CanAccept(Sock)) + { + LockList(); + // First position available in the thread buffer + idx = FirstFree(); + if (idx >= 0) + { + // Creates the Worker and assigns it the connected socket + WorkerSocket = CreateWorkerSocket(Sock); + // Creates the Worker thread + Workers[idx] = new TMsgWorkerThread(WorkerSocket, this); + PMsgWorkerThread(Workers[idx])->Index = idx; + // Update the number + ClientsCount++; + // And Starts the worker + PMsgWorkerThread(Workers[idx])->Start(); + DoEvent(WorkerSocket->ClientHandle, evcClientAdded, 0, 0, 0, 0, 0); + } + else + { + DoEvent(ClientHandle, evcClientNoRoom, 0, 0, 0, 0, 0); + Msg_CloseSocket(Sock); + } + UnlockList(); + } + else + { + Msg_CloseSocket(Sock); + DoEvent(ClientHandle, evcClientRejected, 0, 0, 0, 0, 0); + }; +} +//--------------------------------------------------------------------------- +int TCustomMsgServer::Start() +{ + int Result = 0; + if (Status != SrvRunning) + { + Result = StartListener(); + if (Result != 0) + { + DoEvent(0, evcListenerCannotStart, Result, 0, 0, 0, 0); + Status = SrvError; + } + else + { + DoEvent(0, evcServerStarted, SockListener->ClientHandle, LocalPort, 0, 0, 0); + Status = SrvRunning; + }; + }; + FLastError = Result; + return Result; +} +//--------------------------------------------------------------------------- +int TCustomMsgServer::StartTo(const char *Address, word Port) +{ + strncpy(FLocalAddress, Address, 16); + LocalPort = Port; + return Start(); +} +//--------------------------------------------------------------------------- +void TCustomMsgServer::Stop() +{ + if (Status == SrvRunning) + { + // Kills the listener thread + ServerThread->Terminate(); + if (ServerThread->WaitFor(ThTimeout) != WAIT_OBJECT_0) + ServerThread->Kill(); + delete ServerThread; + // Kills the listener + delete SockListener; + + // Terminate all client threads + TerminateAll(); + + Status = SrvStopped; + LocalBind = 0; + DoEvent(0, evcServerStopped, 0, 0, 0, 0, 0); + }; + FLastError = 0; +} +//--------------------------------------------------------------------------- +int TCustomMsgServer::SetEventsCallBack(pfn_SrvCallBack PCallBack, void *UsrPtr) +{ + OnEvent = PCallBack; + FUsrPtr = UsrPtr; + return 0; +} +//--------------------------------------------------------------------------- +bool TCustomMsgServer::PickEvent(void *pEvent) +{ + try + { + return FEventQueue->Extract(pEvent); + } catch (...) + { + return false; + }; +} +//--------------------------------------------------------------------------- +bool TCustomMsgServer::EventEmpty() +{ + return FEventQueue->Empty(); +} +//--------------------------------------------------------------------------- +void TCustomMsgServer::EventsFlush() +{ + CSEvent->Enter(); + FEventQueue->Flush(); + CSEvent->Leave(); +} +//--------------------------------------------------------------------------- + + diff --git a/core/src/drivers/plugins/native/s7comm/snap7/sys/snap_tcpsrvr.h b/core/src/drivers/plugins/native/s7comm/snap7/sys/snap_tcpsrvr.h new file mode 100644 index 00000000..7264b766 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/sys/snap_tcpsrvr.h @@ -0,0 +1,247 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 Davide Nardella | +| All rights reserved. | +|==============================================================================| +| SNAP7 is free software: you can redistribute it and/or modify | +| it under the terms of the Lesser GNU General Public License as published by | +| the Free Software Foundation, either version 3 of the License, or | +| (at your option) any later version. | +| | +| It means that you can distribute your commercial software linked with | +| SNAP7 without the requirement to distribute the source code of your | +| application and without the requirement that your application be itself | +| distributed under LGPL. | +| | +| SNAP7 is distributed in the hope that it will be useful, | +| but WITHOUT ANY WARRANTY; without even the implied warranty of | +| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | +| Lesser GNU General Public License for more details. | +| | +| You should have received a copy of the GNU General Public License and a | +| copy of Lesser GNU General Public License along with Snap7. | +| If not, see http://www.gnu.org/licenses/ | +|=============================================================================*/ +#ifndef snap_tcpsrvr_h +#define snap_tcpsrvr_h +//--------------------------------------------------------------------------- +#include "snap_msgsock.h" +#include "snap_threads.h" +//--------------------------------------------------------------------------- + +#define MaxWorkers 1024 +#define MaxEvents 1500 + +const int SrvStopped = 0; +const int SrvRunning = 1; +const int SrvError = 2; + +const longword evcServerStarted = 0x00000001; +const longword evcServerStopped = 0x00000002; +const longword evcListenerCannotStart = 0x00000004; +const longword evcClientAdded = 0x00000008; +const longword evcClientRejected = 0x00000010; +const longword evcClientNoRoom = 0x00000020; +const longword evcClientException = 0x00000040; +const longword evcClientDisconnected = 0x00000080; +const longword evcClientTerminated = 0x00000100; +const longword evcClientsDropped = 0x00000200; +const longword evcReserved_00000400 = 0x00000400; +const longword evcReserved_00000800 = 0x00000800; +const longword evcReserved_00001000 = 0x00001000; +const longword evcReserved_00002000 = 0x00002000; +const longword evcReserved_00004000 = 0x00004000; +const longword evcReserved_00008000 = 0x00008000; + +// Server Interface errors +const longword errSrvBase = 0x0000FFFF; +const longword errSrvMask = 0xFFFF0000; +const longword errSrvCannotStart = 0x00100000; + +const longword ThTimeout = 2000; // Thread timeout +const longword WkTimeout = 3000; // Workers termination timeout + +#pragma pack(1) + +typedef struct{ + time_t EvtTime; // Timestamp + int EvtSender; // Sender + longword EvtCode; // Event code + word EvtRetCode; // Event result + word EvtParam1; // Param 1 (if available) + word EvtParam2; // Param 2 (if available) + word EvtParam3; // Param 3 (if available) + word EvtParam4; // Param 4 (if available) +}TSrvEvent, *PSrvEvent; + +extern "C" +{ +typedef void (S7API *pfn_SrvCallBack)(void * usrPtr, PSrvEvent PEvent, int Size); +} +#pragma pack() + +//--------------------------------------------------------------------------- +// EVENTS QUEUE +//--------------------------------------------------------------------------- +class TMsgEventQueue +{ +private: + int IndexIn; // <-- insert index + int IndexOut; // --> extract index + int Max; // Buffer upper bound [0..Max] + int FCapacity; // Queue capacity + pbyte Buffer; + int FBlockSize; +public: + TMsgEventQueue(const int Capacity, const int BlockSize); + ~TMsgEventQueue(); + void Flush(); + void Insert(void *lpdata); + bool Extract(void *lpdata); + bool Empty(); + bool Full(); +}; +typedef TMsgEventQueue *PMsgEventQueue; + +//--------------------------------------------------------------------------- +// WORKER THREAD +//--------------------------------------------------------------------------- +class TCustomMsgServer; // forward declaration + +// It's created when connection is accepted, it will interface with the client. +class TMsgWorkerThread : public TSnapThread +{ +private: + TCustomMsgServer *FServer; +protected: + TMsgSocket *WorkerSocket; +public: + int Index; + friend class TCustomMsgServer; + TMsgWorkerThread(TMsgSocket *Socket, TCustomMsgServer *Server); + void Execute(); +}; +typedef TMsgWorkerThread *PMsgWorkerThread; + +//--------------------------------------------------------------------------- +// LISTENER THREAD +//--------------------------------------------------------------------------- +// It listens for incoming connection. +class TMsgListenerThread : public TSnapThread +{ +private: + TMsgSocket *FListener; + TCustomMsgServer *FServer; +public: + TMsgListenerThread(TMsgSocket *Listener, TCustomMsgServer *Server); + void Execute(); +}; +typedef TMsgListenerThread *PMsgListenerThread; + +//--------------------------------------------------------------------------- +// TCP SERVER +//--------------------------------------------------------------------------- +typedef TMsgSocket *PWorkerSocket; + +class TCustomMsgServer +{ +private: + int FLastError; + char FLocalAddress[16]; + // Socket listener + PMsgSocket SockListener; + // Server listener + PMsgListenerThread ServerThread; + // Critical section to lock Workers list activities + PSnapCriticalSection CSList; + // Event queue + PMsgEventQueue FEventQueue; + // Callback related + pfn_SrvCallBack OnEvent; + void *FUsrPtr; + // private methods + int StartListener(); + void LockList(); + void UnlockList(); + int FirstFree(); +protected: + bool Destroying; + // Critical section to lock Event activities + PSnapCriticalSection CSEvent; + // Workers list + void *Workers[MaxWorkers]; + // Terminates all worker threads + virtual void TerminateAll(); + // Kills all worker threads that are unresponsive + void KillAll(); + // if (true the connection is accepted, otherwise the connection + // is closed gracefully + virtual bool CanAccept(socket_t Socket); + // Returns the class of the worker socket, override it for real servers + virtual PWorkerSocket CreateWorkerSocket(socket_t Sock); + // Handles the event + virtual void DoEvent(int Sender, longword Code, word RetCode, word Param1, + word Param2, word Param3, word Param4); + // Delete the worker from the list (It's invoked by Worker Thread) + void Delete(int Index); + // Incoming connection (It's invoked by ServerThread, the listener) + virtual void Incoming(socket_t Sock); +public: + friend class TMsgWorkerThread; + friend class TMsgListenerThread; + word LocalPort; + longword LocalBind; + longword LogMask; + longword EventMask; + int Status; + int ClientsCount; + int MaxClients; + TCustomMsgServer(); + virtual ~TCustomMsgServer(); + // Starts the server + int Start(); + int StartTo(const char *Address, word Port); + // Stops the server + void Stop(); + // Sets Event callback + int SetEventsCallBack(pfn_SrvCallBack PCallBack, void *UsrPtr); + // Pick an event from the circular queue + bool PickEvent(void *pEvent); + // Returns true if (the Event queue is empty + bool EventEmpty(); + // Flushes Event queue + void EventsFlush(); +}; + +//--------------------------------------------------------------------------- +// TCP WORKER +//--------------------------------------------------------------------------- +// Default worker class, a simply tcp echo to test the connection and +// data I/O to use the server outside the project +class TEcoTcpWorker : public TMsgSocket +{ +public: + bool Execute() + { + byte Buffer[4096]; + int Size; + + if (CanRead(WorkInterval)) // Small time to avoid time wait during the close + { + Receive(&Buffer,sizeof(Buffer),Size); + if ((LastTcpError==0) && (Size>0)) + { + SendPacket(&Buffer,Size); + return LastTcpError==0; + } + else + return false; + } + else + return true; + }; +}; + +//--------------------------------------------------------------------------- +#endif // snap_tcpsrvr_h diff --git a/core/src/drivers/plugins/native/s7comm/snap7/sys/snap_threads.cpp b/core/src/drivers/plugins/native/s7comm/snap7/sys/snap_threads.cpp new file mode 100644 index 00000000..f1120b59 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/sys/snap_threads.cpp @@ -0,0 +1,162 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 Davide Nardella | +| All rights reserved. | +|==============================================================================| +| SNAP7 is free software: you can redistribute it and/or modify | +| it under the terms of the Lesser GNU General Public License as published by | +| the Free Software Foundation, either version 3 of the License, or | +| (at your option) any later version. | +| | +| It means that you can distribute your commercial software linked with | +| SNAP7 without the requirement to distribute the source code of your | +| application and without the requirement that your application be itself | +| distributed under LGPL. | +| | +| SNAP7 is distributed in the hope that it will be useful, | +| but WITHOUT ANY WARRANTY; without even the implied warranty of | +| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | +| Lesser GNU General Public License for more details. | +| | +| You should have received a copy of the GNU General Public License and a | +| copy of Lesser GNU General Public License along with Snap7. | +| If not, see http://www.gnu.org/licenses/ | +|=============================================================================*/ + +#include "snap_threads.h" +//--------------------------------------------------------------------------- +#ifdef OS_WINDOWS +DWORD WINAPI ThreadProc(LPVOID param) +#else + +void* ThreadProc(void* param) +#endif +{ + PSnapThread Thread; + // Unix but not Solaris +#if (defined(POSIX) || defined(OS_OSX)) && (!defined(OS_SOLARIS_NATIVE_THREADS)) + int last_type, last_state; + pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &last_type); + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &last_state); +#endif + Thread = PSnapThread(param); + + if (!Thread->Terminated) + try + { + Thread->Execute(); + } catch (...) + { + }; + Thread->Closed = true; + if (Thread->FreeOnTerminate) + { + delete Thread; + }; +#ifdef OS_WINDOWS + ExitThread(0); +#endif +#if defined(POSIX) && (!defined(OS_SOLARIS_NATIVE_THREADS)) + pthread_exit((void*)0); +#endif +#if defined(OS_OSX) + pthread_exit((void*)0); +#endif +#ifdef OS_SOLARIS_NATIVE_THREADS + thr_exit((void*)0); +#endif + return 0; // never reach, only to avoid compiler warning +} +//--------------------------------------------------------------------------- +TSnapThread::TSnapThread() +{ + Started = false; + Closed=false; + Terminated = false; + FreeOnTerminate = false; +} +//--------------------------------------------------------------------------- +TSnapThread::~TSnapThread() +{ + if (Started && !Closed) + { + Terminate(); + Join(); + }; +#ifdef OS_WINDOWS + if (Started) + CloseHandle(th); +#endif +} +//--------------------------------------------------------------------------- +void TSnapThread::ThreadCreate() +{ +#ifdef OS_WINDOWS + th = CreateThread(0, 0, ThreadProc, this, 0, 0); +#endif +#if defined(POSIX) && (!defined(OS_SOLARIS_NATIVE_THREADS)) + pthread_attr_t a; + pthread_attr_init(&a); + pthread_attr_setdetachstate(&a, PTHREAD_CREATE_DETACHED); + pthread_create(&th, &a, &ThreadProc, this); +#endif +#if defined(OS_OSX) + pthread_create(&th, 0, &ThreadProc, this); +#endif +#ifdef OS_SOLARIS_NATIVE_THREADS + thr_create(0, // default stack base + 0, // default stack size + &ThreadProc, // Thread routine + this, // argument + 0, + &th); +#endif +} +//--------------------------------------------------------------------------- +void TSnapThread::Start() +{ + if (!Started) + { + ThreadCreate(); + Started = true; + } +} +//--------------------------------------------------------------------------- +void TSnapThread::Terminate() +{ + Terminated = true; +} +//--------------------------------------------------------------------------- +void TSnapThread::Kill() +{ + if (Started && !Closed) + { + ThreadKill(); + Closed = true; + } +} +//--------------------------------------------------------------------------- +void TSnapThread::Join() +{ + if (Started && !Closed) + { + ThreadJoin(); + Closed = true; + } +} +//--------------------------------------------------------------------------- +longword TSnapThread::WaitFor(uint64_t Timeout) +{ + if (Started) + { + if (!Closed) + return ThreadWait(Timeout); + else + return WAIT_OBJECT_0; + } + else + return WAIT_OBJECT_0; +} +//--------------------------------------------------------------------------- + diff --git a/core/src/drivers/plugins/native/s7comm/snap7/sys/snap_threads.h b/core/src/drivers/plugins/native/s7comm/snap7/sys/snap_threads.h new file mode 100644 index 00000000..2d3458c3 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/sys/snap_threads.h @@ -0,0 +1,45 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 Davide Nardella | +| All rights reserved. | +|==============================================================================| +| SNAP7 is free software: you can redistribute it and/or modify | +| it under the terms of the Lesser GNU General Public License as published by | +| the Free Software Foundation, either version 3 of the License, or | +| (at your option) any later version. | +| | +| It means that you can distribute your commercial software linked with | +| SNAP7 without the requirement to distribute the source code of your | +| application and without the requirement that your application be itself | +| distributed under LGPL. | +| | +| SNAP7 is distributed in the hope that it will be useful, | +| but WITHOUT ANY WARRANTY; without even the implied warranty of | +| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | +| Lesser GNU General Public License for more details. | +| | +| You should have received a copy of the GNU General Public License and a | +| copy of Lesser GNU General Public License along with Snap7. | +| If not, see http://www.gnu.org/licenses/ | +|=============================================================================*/ +#ifndef snap_threads_h +#define snap_threads_h +//--------------------------------------------------------------------------- +#include "snap_platform.h" + +#ifdef OS_WINDOWS +# include "win_threads.h" +#endif +#if defined(POSIX) && (!defined(OS_SOLARIS_NATIVE_THREADS)) +# include "unix_threads.h" +#endif +#ifdef OS_SOLARIS_NATIVE_THREADS +# include "sol_threads.h" +#endif +#if defined(OS_OSX) +# include "unix_threads.h" +#endif + +//--------------------------------------------------------------------------- +#endif // snap_threads_h diff --git a/core/src/drivers/plugins/native/s7comm/snap7/sys/sol_threads.h b/core/src/drivers/plugins/native/s7comm/snap7/sys/sol_threads.h new file mode 100644 index 00000000..0bfa2374 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/sys/sol_threads.h @@ -0,0 +1,208 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 Davide Nardella | +| All rights reserved. | +|==============================================================================| +| SNAP7 is free software: you can redistribute it and/or modify | +| it under the terms of the Lesser GNU General Public License as published by | +| the Free Software Foundation, either version 3 of the License, or | +| (at your option) any later version. | +| | +| It means that you can distribute your commercial software linked with | +| SNAP7 without the requirement to distribute the source code of your | +| application and without the requirement that your application be itself | +| distributed under LGPL. | +| | +| SNAP7 is distributed in the hope that it will be useful, | +| but WITHOUT ANY WARRANTY; without even the implied warranty of | +| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | +| Lesser GNU General Public License for more details. | +| | +| You should have received a copy of the GNU General Public License and a | +| copy of Lesser GNU General Public License along with Snap7. | +| If not, see http://www.gnu.org/licenses/ | +|==============================================================================| +| | +| Solaris 11 Threads support | +| | +|=============================================================================*/ +#ifndef sol_threads_h +#define sol_threads_h +//--------------------------------------------------------------------------- +#include "snap_platform.h" +#include "snap_sysutils.h" +#include +#include +//--------------------------------------------------------------------------- + +class TSnapCriticalSection { +private: + mutex_t mx; + int result; +public: + + TSnapCriticalSection() { + mutex_init(&mx, USYNC_THREAD, 0); + }; + + ~TSnapCriticalSection() { + mutex_destroy(&mx); + }; + + void Enter() { + mutex_lock(&mx); + }; + + void Leave() { + mutex_unlock(&mx); + }; + + bool TryEnter() { + return mutex_trylock(&mx) == 0; + }; +}; +typedef TSnapCriticalSection *PSnapCriticalSection; + +//--------------------------------------------------------------------------- +const longword WAIT_OBJECT_0 = 0x00000000L; +const longword WAIT_ABANDONED = 0x00000080L; +const longword WAIT_TIMEOUT = 0x00000102L; +const longword WAIT_FAILED = 0xFFFFFFFFL; + +class TSnapEvent { +private: + cond_t CVariable; + mutex_t Mutex; + bool AutoReset; + bool State; +public: + + TSnapEvent(bool ManualReset) + { + AutoReset = !ManualReset; + cond_init(&CVariable, USYNC_THREAD, 0) == 0; + mutex_init(&Mutex, USYNC_THREAD, 0); + State = false; + } + + ~TSnapEvent() + { + cond_destroy(&CVariable); + mutex_destroy(&Mutex); + }; + + void Set() + { + mutex_lock(&Mutex); + State = true; + if (AutoReset) + cond_signal(&CVariable); + else + cond_broadcast(&CVariable); + mutex_unlock(&Mutex); + }; + + void Reset() + { + mutex_lock(&Mutex); + State = false; + mutex_unlock(&Mutex); + } + + longword WaitForever() + { + mutex_lock(&Mutex); + while (!State) // <-- to avoid spurious wakeups + cond_wait(&CVariable, &Mutex); + if (AutoReset) + State = false; + mutex_unlock(&Mutex); + return WAIT_OBJECT_0; + }; + + longword WaitFor(int64_t Timeout) + { + longword Result = WAIT_OBJECT_0; + if (Timeout == 0) + Timeout = 1; // 0 is not allowed + + if (Timeout > 0) { + mutex_lock(&Mutex); + if (!State) { + timespec ts; + timeval tv; + gettimeofday(&tv, NULL); + uint64_t nsecs = ((uint64_t) tv.tv_sec) * 1000000000 + + Timeout * 1000000 + + ((uint64_t) tv.tv_usec) * 1000; + ts.tv_sec = nsecs / 1000000000; + ts.tv_nsec = (nsecs - ((uint64_t) ts.tv_sec) * 1000000000); + do + { + Result = cond_timedwait(&CVariable, &Mutex, &ts); + if (Result == ETIMEDOUT) + Result = WAIT_TIMEOUT; + } + while (Result == 0 && !State); + } + else + if (AutoReset) // take the ownership + State = false; + mutex_unlock(&Mutex); + return Result; + } + else // Timeout<0 + return WaitForever(); + }; +}; +typedef TSnapEvent *PSnapEvent; +//--------------------------------------------------------------------------- + +class TSnapThread { +private: + thread_t th; + bool FCreateSuspended; + void ThreadCreate(); + + void ThreadJoin() + { + thr_join(th, 0, 0); + }; + + void ThreadKill() + { + thr_kill(th, 0); + }; + + longword ThreadWait(uint64_t Timeout) + { + longword Elapsed = SysGetTick(); + while (!Closed && !(DeltaTime(Elapsed) > Timeout)) + SysSleep(100); + if (Closed) + return WAIT_OBJECT_0; + else + return WAIT_TIMEOUT; + }; +protected: + bool Started; +public: + bool Terminated; + bool Closed; + bool FreeOnTerminate; + TSnapThread(); + virtual ~TSnapThread(); + + virtual void Execute() { + }; + void Start(); + void Terminate(); + void Kill(); + void Join(); + longword WaitFor(uint64_t Timeout); +}; +typedef TSnapThread *PSnapThread; + +//--------------------------------------------------------------------------- +#endif // sol_threads_h diff --git a/core/src/drivers/plugins/native/s7comm/snap7/sys/unix_threads.h b/core/src/drivers/plugins/native/s7comm/snap7/sys/unix_threads.h new file mode 100644 index 00000000..90ee88db --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/sys/unix_threads.h @@ -0,0 +1,228 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 Davide Nardella | +| All rights reserved. | +|==============================================================================| +| SNAP7 is free software: you can redistribute it and/or modify | +| it under the terms of the Lesser GNU General Public License as published by | +| the Free Software Foundation, either version 3 of the License, or | +| (at your option) any later version. | +| | +| It means that you can distribute your commercial software linked with | +| SNAP7 without the requirement to distribute the source code of your | +| application and without the requirement that your application be itself | +| distributed under LGPL. | +| | +| SNAP7 is distributed in the hope that it will be useful, | +| but WITHOUT ANY WARRANTY; without even the implied warranty of | +| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | +| Lesser GNU General Public License for more details. | +| | +| You should have received a copy of the GNU General Public License and a | +| copy of Lesser GNU General Public License along with Snap7. | +| If not, see http://www.gnu.org/licenses/ | +|==============================================================================| +| | +| Posix Threads support (Linux, FreeBSD) | +| | +|=============================================================================*/ +#ifndef unix_threads_h +#define unix_threads_h +//--------------------------------------------------------------------------- +#include "snap_platform.h" +#include "snap_sysutils.h" +#include +#include +//--------------------------------------------------------------------------- + +class TSnapCriticalSection +{ +private: + pthread_mutex_t mx; +// int result; +public: + + TSnapCriticalSection() + { + /* + + This would be the best code, but very often it causes a segmentation fault in many + unix systems (the problem seems to be pthread_mutexattr_destroy()). + So, to avoid problems in future kernel/libc release, we use the "safe" default. + + pthread_mutexattr_t mxAttr; + pthread_mutexattr_settype(&mxAttr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&mx, &mxAttr); + pthread_mutexattr_destroy(&mxAttr); + + */ + pthread_mutex_init(&mx, 0); + }; + + ~TSnapCriticalSection() + { + pthread_mutex_destroy(&mx); + }; + + void Enter() + { + pthread_mutex_lock(&mx); + }; + + void Leave() + { + pthread_mutex_unlock(&mx); + }; + + bool TryEnter() + { + return pthread_mutex_trylock(&mx) == 0; + }; +}; +typedef TSnapCriticalSection *PSnapCriticalSection; + +//--------------------------------------------------------------------------- +const longword WAIT_OBJECT_0 = 0x00000000L; +const longword WAIT_ABANDONED = 0x00000080L; +const longword WAIT_TIMEOUT = 0x00000102L; +const longword WAIT_FAILED = 0xFFFFFFFFL; + +class TSnapEvent +{ +private: + pthread_cond_t CVariable; + pthread_mutex_t Mutex; + bool AutoReset; + bool State; +public: + + TSnapEvent(bool ManualReset) + { + AutoReset = !ManualReset; + if (pthread_cond_init(&CVariable, 0) == 0) + pthread_mutex_init(&Mutex, 0); + State = false; + } + + ~TSnapEvent() + { + pthread_cond_destroy(&CVariable); + pthread_mutex_destroy(&Mutex); + }; + + void Set() + { + pthread_mutex_lock(&Mutex); + State = true; + if (AutoReset) + pthread_cond_signal(&CVariable); + else + pthread_cond_broadcast(&CVariable); + pthread_mutex_unlock(&Mutex); + }; + + void Reset() + { + pthread_mutex_lock(&Mutex); + State = false; + pthread_mutex_unlock(&Mutex); + } + + longword WaitForever() + { + pthread_mutex_lock(&Mutex); + while (!State) // <-- to avoid spurious wakeups + pthread_cond_wait(&CVariable, &Mutex); + if (AutoReset) + State = false; + pthread_mutex_unlock(&Mutex); + return WAIT_OBJECT_0; + }; + + longword WaitFor(int64_t Timeout) + { + longword Result = WAIT_OBJECT_0; + if (Timeout == 0) + Timeout = 1; // 0 is not allowed + + if (Timeout > 0) + { + pthread_mutex_lock(&Mutex); + if (!State) + { + timespec ts; + timeval tv; + gettimeofday(&tv, NULL); + uint64_t nsecs = ((uint64_t) tv.tv_sec) * 1000000000 + + Timeout * 1000000 + + ((uint64_t) tv.tv_usec) * 1000; + ts.tv_sec = nsecs / 1000000000; + ts.tv_nsec = (nsecs - ((uint64_t) ts.tv_sec) * 1000000000); + do { + Result = pthread_cond_timedwait(&CVariable, &Mutex, &ts); + if (Result == ETIMEDOUT) + Result = WAIT_TIMEOUT; + } while (Result == 0 && !State); + } + else + if (AutoReset) // take the ownership + State = false; + pthread_mutex_unlock(&Mutex); + return Result; + } + else // Timeout<0 + return WaitForever(); + }; +}; +typedef TSnapEvent *PSnapEvent; +//--------------------------------------------------------------------------- +class TSnapThread +{ +private: + pthread_t th; + bool FCreateSuspended; + void ThreadCreate(); + + void ThreadJoin() + { + pthread_join(th, 0); + }; + + void ThreadKill() + { + pthread_cancel(th); + }; + + longword ThreadWait(uint64_t Timeout) + { + longword Elapsed = SysGetTick(); + while (!Closed && !(DeltaTime(Elapsed) > Timeout)) + SysSleep(100); + if (Closed) + return WAIT_OBJECT_0; + else + return WAIT_TIMEOUT; + }; +protected: + bool Started; +public: + bool Terminated; + bool Closed; + bool FreeOnTerminate; + TSnapThread(); + virtual ~TSnapThread(); + + virtual void Execute() + { + }; + void Start(); + void Terminate(); + void Kill(); + void Join(); + longword WaitFor(uint64_t Timeout); +}; +typedef TSnapThread *PSnapThread; + +//--------------------------------------------------------------------------- +#endif // unix_threads_h diff --git a/core/src/drivers/plugins/native/s7comm/snap7/sys/win_threads.h b/core/src/drivers/plugins/native/s7comm/snap7/sys/win_threads.h new file mode 100644 index 00000000..b57484ee --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/snap7/sys/win_threads.h @@ -0,0 +1,159 @@ +/*=============================================================================| +| PROJECT SNAP7 1.4.3 | +|==============================================================================| +| Copyright (C) 2013, 2025 Davide Nardella | +| All rights reserved. | +|==============================================================================| +| SNAP7 is free software: you can redistribute it and/or modify | +| it under the terms of the Lesser GNU General Public License as published by | +| the Free Software Foundation, either version 3 of the License, or | +| (at your option) any later version. | +| | +| It means that you can distribute your commercial software linked with | +| SNAP7 without the requirement to distribute the source code of your | +| application and without the requirement that your application be itself | +| distributed under LGPL. | +| | +| SNAP7 is distributed in the hope that it will be useful, | +| but WITHOUT ANY WARRANTY; without even the implied warranty of | +| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | +| Lesser GNU General Public License for more details. | +| | +| You should have received a copy of the GNU General Public License and a | +| copy of Lesser GNU General Public License along with Snap7. | +| If not, see http://www.gnu.org/licenses/ | +|==============================================================================| +| | +| Windows Threads support (Windows, ReactOS) | +| | +|=============================================================================*/ +#ifndef win_threads_h +#define win_threads_h +//--------------------------------------------------------------------------- +#include "snap_platform.h" +#include "snap_sysutils.h" +//--------------------------------------------------------------------------- + +class TSnapCriticalSection +{ +private: + CRITICAL_SECTION cs; +public: + + TSnapCriticalSection() + { + InitializeCriticalSection(&cs); + }; + + ~TSnapCriticalSection() + { + DeleteCriticalSection(&cs); + }; + + void Enter() + { + EnterCriticalSection(&cs); + }; + + void Leave() + { + LeaveCriticalSection(&cs); + }; + + bool TryEnter() + { + return (TryEnterCriticalSection(&cs) != 0); + }; +}; +typedef TSnapCriticalSection *PSnapCriticalSection; +//--------------------------------------------------------------------------- + +class TSnapEvent +{ +private: + HANDLE Event; +public: + + TSnapEvent(bool ManualReset) + { + Event = CreateEvent(0, ManualReset, false, 0); + }; + + ~TSnapEvent() + { + if (Event != 0) + CloseHandle(Event); + }; + + void Set() + { + if (Event != 0) + SetEvent(Event); + }; + + void Reset() + { + if (Event != 0) + ResetEvent(Event); + }; + + longword WaitForever() + { + if (Event != 0) + return WaitForSingleObject(Event, INFINITE); + else + return WAIT_FAILED; + }; + + longword WaitFor(int64_t Timeout) { + if (Event != 0) + return WaitForSingleObject(Event, DWORD(Timeout)); + else + return WAIT_FAILED; + }; +}; +typedef TSnapEvent *PSnapEvent; +//--------------------------------------------------------------------------- + +class TSnapThread { +private: + HANDLE th; + bool FCreateSuspended; + void ThreadCreate(); + + void ThreadJoin() + { + WaitForSingleObject(th, INFINITE); + }; + + void ThreadKill() + { + TerminateThread(th, 0); + }; + + longword ThreadWait(uint64_t Timeout) + { + return WaitForSingleObject(th, DWORD(Timeout)); + }; +protected: + bool Started; +public: + bool Terminated; + bool Closed; + bool FreeOnTerminate; + TSnapThread(); + virtual ~TSnapThread(); + + virtual void Execute() + { + }; + void Start(); + void Terminate(); + void Kill(); + void Join(); + longword WaitFor(uint64_t Timeout); +}; +typedef TSnapThread *PSnapThread; + +//--------------------------------------------------------------------------- +#endif // win_threads_h From ac99834c63b6cb9525376bb1d100939d302f98c6 Mon Sep 17 00:00:00 2001 From: Thiago Alves Date: Tue, 13 Jan 2026 20:11:38 -0500 Subject: [PATCH 07/11] Fix S7 area code comparison bug in RWArea callback The callback receives S7 protocol area codes (S7AreaPE=0x81, S7AreaPA=0x82, S7AreaMK=0x83, S7AreaDB=0x84) but we were comparing against server area codes (srvAreaPE=0, srvAreaPA=1, srvAreaMK=2, srvAreaDB=5). This caused find_area_runtime() to always return NULL, resulting in the callback returning 0 without filling any data - hence all reads returned zeros. Co-Authored-By: Claude Opus 4.5 --- .../plugins/native/s7comm/s7comm_plugin.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/core/src/drivers/plugins/native/s7comm/s7comm_plugin.cpp b/core/src/drivers/plugins/native/s7comm/s7comm_plugin.cpp index b4f37d3b..35a7e648 100644 --- a/core/src/drivers/plugins/native/s7comm/s7comm_plugin.cpp +++ b/core/src/drivers/plugins/native/s7comm/s7comm_plugin.cpp @@ -674,16 +674,19 @@ static s7comm_db_runtime_t* find_db_runtime(int db_number) } /** - * @brief Find system area runtime structure by S7 area code + * @brief Find system area runtime structure by S7 protocol area code + * + * Note: The callback receives S7 protocol area codes (S7AreaPE=0x81, S7AreaPA=0x82, etc.) + * not server area codes (srvAreaPE=0, srvAreaPA=1, etc.) */ static s7comm_area_runtime_t* find_area_runtime(int area) { switch (area) { - case srvAreaPE: + case S7AreaPE: return g_pe_runtime.enabled ? &g_pe_runtime : NULL; - case srvAreaPA: + case S7AreaPA: return g_pa_runtime.enabled ? &g_pa_runtime : NULL; - case srvAreaMK: + case S7AreaMK: return g_mk_runtime.enabled ? &g_mk_runtime : NULL; default: return NULL; @@ -1057,8 +1060,8 @@ static int s7comm_rw_area_callback(void *usrPtr, int Sender, int Operation, PS7T int start_buffer; int size = PTag->Size; - /* Determine mapping based on S7 area */ - if (PTag->Area == srvAreaDB) { + /* Determine mapping based on S7 protocol area code */ + if (PTag->Area == S7AreaDB) { /* Data block - look up configuration */ s7comm_db_runtime_t *db = find_db_runtime(PTag->DBNumber); if (db == NULL) { From 937c1dbca7b56522e03f0b55388964bda53fa04c Mon Sep 17 00:00:00 2001 From: Thiago Alves Date: Tue, 13 Jan 2026 20:33:05 -0500 Subject: [PATCH 08/11] Add debug logging to S7Comm RWArea callback Added detailed logging to trace: - Callback invocation with area code, operation, size - Area/DB lookup results - Buffer read operations with pointer values - Read result data This will help diagnose why reads are returning zeros. Co-Authored-By: Claude Opus 4.5 --- .DS_Store | Bin 0 -> 6148 bytes CLAUDE.md | 110 ++++++++++++++++++ core/.DS_Store | Bin 0 -> 6148 bytes core/src/.DS_Store | Bin 0 -> 6148 bytes core/src/drivers/.DS_Store | Bin 0 -> 6148 bytes core/src/drivers/plugins/.DS_Store | Bin 0 -> 6148 bytes core/src/drivers/plugins/native/.DS_Store | Bin 0 -> 6148 bytes .../drivers/plugins/native/s7comm/.DS_Store | Bin 0 -> 6148 bytes .../plugins/native/s7comm/s7comm_plugin.cpp | 36 +++++- 9 files changed, 144 insertions(+), 2 deletions(-) create mode 100644 .DS_Store create mode 100644 CLAUDE.md create mode 100644 core/.DS_Store create mode 100644 core/src/.DS_Store create mode 100644 core/src/drivers/.DS_Store create mode 100644 core/src/drivers/plugins/.DS_Store create mode 100644 core/src/drivers/plugins/native/.DS_Store create mode 100644 core/src/drivers/plugins/native/s7comm/.DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..833e2ea319a36208c2d5b7b925861a772128e037 GIT binary patch literal 6148 zcmeHK!AiqG5Z!H~CKRCuMUM+!3$|h_;w8lT14i_qQj?}=FlHrb&7l->)gSUt{2pg^ zH$|y}ClNaXGjB6Hv&p=K-3()ldy}ZcSd%ekgCf=(FkcAHqb^9zckt{qpNuyll`g+249B0tnnofJ&mN@Lp zT4LJS@3mXvpu0DlIriqx?$PPsK6yyxv*wV)=cZ)cUHLLmZe~-lNcZde#`*w4+0d?HCSp?TL&1`0svazRsv(} zB``-AbPbjoVFrZjR6w1|O^d;GI{1Z&a}Aanbvomw`QYZx+;k{h-5u5!Dx7gwBUxgA z82HQp_I@BuJpWI=@BdX08DfAK_@4}LtMB(csLhiXKLUydGQ_~IGVl)CfKMO* literal 0 HcmV?d00001 diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..3ae8281a --- /dev/null +++ b/CLAUDE.md @@ -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` diff --git a/core/.DS_Store b/core/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..639efdfd86ec72f7a6e1ef72992fd90d2be1ef8e GIT binary patch literal 6148 zcmeHKPfNov6i?jLmLc?@=rQ1R;3jS&UP_%`z=|GJX1fk8)@HPwJ&ZxG`i1-^ejeXT zQgFC~ClU7^B)^yUCry6PB#$x1y=l~C%wddK&=5H)O9ahJU27(|0myNVd=e%ygwcCA zF}}YJxb_CiS;j_T`E~tCoEM$W2VZM6*EcpT%W7G7{-Z4YA}FT07i2d$x|A{rYdr|B z;&C~2w$Ef*1aUgfR6-n$A>{TtP9s@*a+*fD%JsFwvTb|l?9OKWUR#`W=WQ|T9`!qI zaojtc&uwdG|KRju_>?@S@>R3RfovsP25Wc&#cU?ZM@a~a!%$;-@v}6M=>u5m3F<^b zVt^PR28e-GV!+%2R%?|uO?xEd}bgU&XM;LSs zRvN(r!gVU3PUWV=;5r@b!o;}-D~&pxaZ`M7^X0mD;p*!!zfj?fyBet{28e-02C()8 zaq#{>hhJuCk*`9iBL;|p|H=Sw4*Y=!yEAv|w|(MWE1=y&L&3NT6%f!D9sw}Gb7W6B dwO^nPajwBiBhG?$l@3S`0YwOP#K12w@ClcvP4EB! literal 0 HcmV?d00001 diff --git a/core/src/.DS_Store b/core/src/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..51e56e1e1f569b5a7b80601fc79c804a5d6c7591 GIT binary patch literal 6148 zcmeHK%}T>S5Z-O8-B5%a6g@6@E!c{!h?h|73mDOZN^MNhV9b`JZ4RZ7tG z4C19|GWsa#oGuL>b*;>Leaxj*WU##yJI zUg6WZQgJY!_k)XQkoVlp6P1kmQ8Gw%LKF@ll=q~9H-}QPp9ox zT^==ObvbPwv>SDK*xH}X9I>^#cYM}+jGq$qY?$QmqtmisaSksqS=dyV-NgZXB&IC5 zCVra4D!B*Ge8Krbgv0)K>=#x&;8N zfLRO7v6sLcY0rRomD?7Bn{=>C8|PUp44QPtZS%qHo4M^!xPCjVFLgNM zoS5T0$TO({YTiXIod7Hq{<#7n641&ruHr6#1*V9b`LwTDv3RbR+A@p+ut z-GJ4AHxWAnyWi~m>}Ed5{s4gJ&B8W70{|Rsgo2a;A#eZLCO^A?QbD4n)i@1j($Y;0B?$EiDa-h-NZQ-3;3d;a8xMwd#( zL9YA3RWzIr8rx?onfg&Ooalro9Ae1rb(Dl^-cz$AOm(hj98RrP8#Hzoi*BbWPuk0- zT(pn6t)@Kg94?nNXJ`N5^kVQBKPBqLFe#99En60Ictzz)QP18aiB&Q}Po9-WGcp6r z05iZ0tQiCLAaLqyRyUW+3@`&fGeG-;#75{^%na(S13S7tlfObpf;zn=2&F~WVrCFM zD8i&7np9z*7{a8ZUD`O;VrI~!gU~DEJa*;$@gnrS5Z-NTO(;SSiXIod7Hq{<#7l_v1&ruHr6we3FlI}WHiuHkRbR+A@p+ut z-5i3&qlle>-EVe&b~7Jje;8xjoreR)x{NUk8X`w!ji7n0t6_o>xte3aqM*p8K_Y`S z6a7UKetVmx%x5`^S^fJzf@u<&lQQYJyA z`@u~d6_f7ng-mllPNSJhh{Fg%?r-BXl!YtjX_%>8PdhBzwkO^F<#IIaiL=3~CzgYg zQNJfnhsUdxZS5T%onK8}lGjweX&yOHu4Knx1@EBLN?LfcG?D2O*sGi>mXH`A28aP- zU;`O2hk@1Ez}l%~Vt^Ry{Z<~HwF25BG!%?0Q2_zHb_sw1?jr}vsr>?Vh;s~< V8gUl1t8_rR2q;3RBL;qffiJ!HOaA}> literal 0 HcmV?d00001 diff --git a/core/src/drivers/plugins/native/.DS_Store b/core/src/drivers/plugins/native/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..0a78a9b7626ba854293d4f4ff0e13beb01d46ffc GIT binary patch literal 6148 zcmeHK%}T>S5Z-NTO(;SSiXIod7Hq|~h?h|73mDOZN=;1BV45vWY7V84tG}Ed5{xHV)Xc_bvYcs|yXowt@3PE$Ft7d``xte3fW`35;{Yd&1 z6a7UKetVN8Y{(v&WaaPw@aIvOq}}c(uhkn{&27uFTGqY$EHgLt(q%I87PmOMk}~oO z-Se-*X*Ox^p36A(!g#t+31KjWkh_~O4rDfx%Q#3>uB#oEZQGOf{%SSocf@IL-4Uza z$)MX2!~XGlZCiVXM`xFlmuMEtH_aml%9ZRGEZ`lKQc17wB93JI1ok4Qh$SQjhyh}N z7}!7t%wb@)Hn4UonHV4jer5po2LTPyF_>#qTL*M_eMWx+5e0O7OCSn^j=@|bctE&L z1=OkBJTbUV2fHwFj=@}`PG?-L4D*a#TP-FI@s)fcwaSa%#Uo9pW5= Wxkj7??J6CRE&_@W>WG0~VBiY{`AeJt literal 0 HcmV?d00001 diff --git a/core/src/drivers/plugins/native/s7comm/.DS_Store b/core/src/drivers/plugins/native/s7comm/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..923d7d4718a2181370ab540c413aa28be4561009 GIT binary patch literal 6148 zcmeHK%}T>S5Z-O0rW7FuMUM+!3$|h_;w8lT0!H+pQWH}&7_+5G&7l->)fe(jd>&_Z zH)1ho5jz9B-~8@oKgj+t#<(|&dW<=YF&i2pN2Ny4T^nkcWJHc*ghe(96B&a2Zeo8O z@Y^jGG0E;(_Wk?ANfKvyxBI~xO>1p^!?x|Ved|BU!q043 zBWLSWrg;#jQZi= z7+jZwUzj}CV5w1;Gp=Taam>usPi2-6@nSrJ@9X$Wf;Fqa= size) max_bytes = size; + plugin_logger_debug(&g_logger, "read_bool: type=%d, start=%d, size=%d, max_bytes=%d, buffer_size=%d", + type, start_buffer, size, max_bytes, g_runtime_args.buffer_size); + for (int byte_idx = 0; byte_idx < max_bytes; byte_idx++) { uint8_t byte_val = 0; int plc_idx = start_buffer + byte_idx; @@ -764,6 +773,11 @@ static void read_openplc_bool_to_buffer(uint8_t *dest, int size, s7comm_buffer_t if (ptr != NULL && *ptr) { byte_val |= (1 << bit_idx); } + /* Debug first byte only to avoid log spam */ + if (byte_idx == 0 && bit_idx < 4) { + plugin_logger_debug(&g_logger, "read_bool: [%d][%d] ptr=%p val=%d", + plc_idx, bit_idx, (void*)ptr, ptr ? *ptr : -1); + } } dest[byte_idx] = byte_val; } @@ -1052,7 +1066,15 @@ static int s7comm_rw_area_callback(void *usrPtr, int Sender, int Operation, PS7T (void)usrPtr; (void)Sender; + plugin_logger_debug(&g_logger, "RWArea callback: Area=0x%02X, DBNum=%d, Start=%d, Size=%d, Op=%s", + PTag ? PTag->Area : 0, + PTag ? PTag->DBNumber : 0, + PTag ? PTag->Start : 0, + PTag ? PTag->Size : 0, + Operation == OperationRead ? "READ" : "WRITE"); + if (pUsrData == NULL || PTag == NULL) { + plugin_logger_warn(&g_logger, "RWArea callback: NULL pointer (pUsrData=%p, PTag=%p)", pUsrData, PTag); return -1; } @@ -1065,20 +1087,23 @@ static int s7comm_rw_area_callback(void *usrPtr, int Sender, int Operation, PS7T /* Data block - look up configuration */ s7comm_db_runtime_t *db = find_db_runtime(PTag->DBNumber); if (db == NULL) { - /* DB not configured - let Snap7 handle with its registered buffer */ + plugin_logger_warn(&g_logger, "RWArea: DB%d not configured, returning zeros", PTag->DBNumber); return 0; } type = db->type; start_buffer = db->start_buffer + (PTag->Start / get_type_size(type)); + plugin_logger_debug(&g_logger, "RWArea: DB%d -> type=%d, start_buffer=%d", PTag->DBNumber, type, start_buffer); } else { /* System area (PE, PA, MK) */ s7comm_area_runtime_t *area = find_area_runtime(PTag->Area); if (area == NULL) { - /* Area not configured - let Snap7 handle with its registered buffer */ + plugin_logger_warn(&g_logger, "RWArea: Area 0x%02X not configured (S7AreaPE=0x%02X, S7AreaPA=0x%02X, S7AreaMK=0x%02X)", + PTag->Area, S7AreaPE, S7AreaPA, S7AreaMK); return 0; } type = area->type; start_buffer = area->start_buffer + (PTag->Start / get_type_size(type)); + plugin_logger_debug(&g_logger, "RWArea: Area 0x%02X -> type=%d, start_buffer=%d", PTag->Area, type, start_buffer); } if (Operation == OperationRead) { @@ -1089,6 +1114,13 @@ static int s7comm_rw_area_callback(void *usrPtr, int Sender, int Operation, PS7T g_runtime_args.mutex_take(g_runtime_args.buffer_mutex); read_openplc_to_buffer((uint8_t *)pUsrData, size, type, start_buffer); g_runtime_args.mutex_give(g_runtime_args.buffer_mutex); + + /* Debug: dump first few bytes of read data */ + uint8_t *data = (uint8_t *)pUsrData; + if (size >= 1) { + plugin_logger_debug(&g_logger, "RWArea READ result: [0]=0x%02X (size=%d, type=%d, start=%d)", + data[0], size, type, start_buffer); + } } else if (Operation == OperationWrite) { /* * S7 client is WRITing - journal the changes From 1c73290fe557f3a78d7a989616e3776702f5cf4d Mon Sep 17 00:00:00 2001 From: Thiago Alves Date: Tue, 13 Jan 2026 20:37:54 -0500 Subject: [PATCH 09/11] Remove apt cache cleanup from install script The rm -rf /var/lib/apt/lists/* was a Docker-style optimization that's counterproductive on regular systems - it forces users to redownload the entire apt cache on every subsequent apt operation. Co-Authored-By: Claude Opus 4.5 --- install.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/install.sh b/install.sh index 918cfcf8..e2c3b043 100755 --- a/install.sh +++ b/install.sh @@ -186,8 +186,7 @@ install_deps_apt() { gcc \ make \ cmake \ - pkg-config \ - && rm -rf /var/lib/apt/lists/* + pkg-config } # For yum-based distros (RHEL 7, CentOS 7, Amazon Linux) From 95a9b8277a84307acc114a6bc2155e7c983ddcda Mon Sep 17 00:00:00 2001 From: Thiago Alves Date: Tue, 13 Jan 2026 20:40:00 -0500 Subject: [PATCH 10/11] Move apt cache cleanup to Dockerfiles Docker-specific optimizations like apt cache cleanup belong in the Dockerfile, not the install script. This keeps install.sh generic for all platforms while still optimizing Docker image size. Co-Authored-By: Claude Opus 4.5 --- Dockerfile | 3 +++ Dockerfile.dev | 3 +++ 2 files changed, 6 insertions(+) diff --git a/Dockerfile b/Dockerfile index 5528a3ff..5b93974c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 diff --git a/Dockerfile.dev b/Dockerfile.dev index 3ef85bb7..a5620bbc 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -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"] From 3f08119064db4c1f9ee8191b9200333cc538c5bf Mon Sep 17 00:00:00 2001 From: Thiago Alves Date: Tue, 13 Jan 2026 21:21:39 -0500 Subject: [PATCH 11/11] Remove debug logging from S7Comm plugin S7Comm plugin with journal-based writes is now working correctly. Removed temporary debug logging that was added for troubleshooting. Co-Authored-By: Claude Opus 4.5 --- .../plugins/native/s7comm/s7comm_plugin.cpp | 32 ++----------------- 1 file changed, 2 insertions(+), 30 deletions(-) diff --git a/core/src/drivers/plugins/native/s7comm/s7comm_plugin.cpp b/core/src/drivers/plugins/native/s7comm/s7comm_plugin.cpp index 8a6708bb..1ac1cec2 100644 --- a/core/src/drivers/plugins/native/s7comm/s7comm_plugin.cpp +++ b/core/src/drivers/plugins/native/s7comm/s7comm_plugin.cpp @@ -750,21 +750,16 @@ static void read_openplc_bool_to_buffer(uint8_t *dest, int size, s7comm_buffer_t buffer = g_runtime_args.bool_memory; break; default: - plugin_logger_warn(&g_logger, "read_bool: unknown type %d", type); return; } if (buffer == NULL) { - plugin_logger_warn(&g_logger, "read_bool: buffer is NULL for type %d", type); return; } int max_bytes = g_runtime_args.buffer_size - start_buffer; if (max_bytes > size) max_bytes = size; - plugin_logger_debug(&g_logger, "read_bool: type=%d, start=%d, size=%d, max_bytes=%d, buffer_size=%d", - type, start_buffer, size, max_bytes, g_runtime_args.buffer_size); - for (int byte_idx = 0; byte_idx < max_bytes; byte_idx++) { uint8_t byte_val = 0; int plc_idx = start_buffer + byte_idx; @@ -773,11 +768,6 @@ static void read_openplc_bool_to_buffer(uint8_t *dest, int size, s7comm_buffer_t if (ptr != NULL && *ptr) { byte_val |= (1 << bit_idx); } - /* Debug first byte only to avoid log spam */ - if (byte_idx == 0 && bit_idx < 4) { - plugin_logger_debug(&g_logger, "read_bool: [%d][%d] ptr=%p val=%d", - plc_idx, bit_idx, (void*)ptr, ptr ? *ptr : -1); - } } dest[byte_idx] = byte_val; } @@ -1066,15 +1056,7 @@ static int s7comm_rw_area_callback(void *usrPtr, int Sender, int Operation, PS7T (void)usrPtr; (void)Sender; - plugin_logger_debug(&g_logger, "RWArea callback: Area=0x%02X, DBNum=%d, Start=%d, Size=%d, Op=%s", - PTag ? PTag->Area : 0, - PTag ? PTag->DBNumber : 0, - PTag ? PTag->Start : 0, - PTag ? PTag->Size : 0, - Operation == OperationRead ? "READ" : "WRITE"); - if (pUsrData == NULL || PTag == NULL) { - plugin_logger_warn(&g_logger, "RWArea callback: NULL pointer (pUsrData=%p, PTag=%p)", pUsrData, PTag); return -1; } @@ -1087,23 +1069,20 @@ static int s7comm_rw_area_callback(void *usrPtr, int Sender, int Operation, PS7T /* Data block - look up configuration */ s7comm_db_runtime_t *db = find_db_runtime(PTag->DBNumber); if (db == NULL) { - plugin_logger_warn(&g_logger, "RWArea: DB%d not configured, returning zeros", PTag->DBNumber); + /* DB not configured - return zeros for read, ignore write */ return 0; } type = db->type; start_buffer = db->start_buffer + (PTag->Start / get_type_size(type)); - plugin_logger_debug(&g_logger, "RWArea: DB%d -> type=%d, start_buffer=%d", PTag->DBNumber, type, start_buffer); } else { /* System area (PE, PA, MK) */ s7comm_area_runtime_t *area = find_area_runtime(PTag->Area); if (area == NULL) { - plugin_logger_warn(&g_logger, "RWArea: Area 0x%02X not configured (S7AreaPE=0x%02X, S7AreaPA=0x%02X, S7AreaMK=0x%02X)", - PTag->Area, S7AreaPE, S7AreaPA, S7AreaMK); + /* Area not configured - return zeros for read, ignore write */ return 0; } type = area->type; start_buffer = area->start_buffer + (PTag->Start / get_type_size(type)); - plugin_logger_debug(&g_logger, "RWArea: Area 0x%02X -> type=%d, start_buffer=%d", PTag->Area, type, start_buffer); } if (Operation == OperationRead) { @@ -1114,13 +1093,6 @@ static int s7comm_rw_area_callback(void *usrPtr, int Sender, int Operation, PS7T g_runtime_args.mutex_take(g_runtime_args.buffer_mutex); read_openplc_to_buffer((uint8_t *)pUsrData, size, type, start_buffer); g_runtime_args.mutex_give(g_runtime_args.buffer_mutex); - - /* Debug: dump first few bytes of read data */ - uint8_t *data = (uint8_t *)pUsrData; - if (size >= 1) { - plugin_logger_debug(&g_logger, "RWArea READ result: [0]=0x%02X (size=%d, type=%d, start=%d)", - data[0], size, type, start_buffer); - } } else if (Operation == OperationWrite) { /* * S7 client is WRITing - journal the changes