From bc7496949f085778126a0dd63c008ab8e9c5dc63 Mon Sep 17 00:00:00 2001 From: Thiago Alves Date: Mon, 12 Jan 2026 17:47:29 -0500 Subject: [PATCH 01/12] Add S7Comm plugin documentation and implementation plan - Add comprehensive implementation plan covering: - Analysis of OpenPLC v3 S7Comm/Snap7 implementation - Snap7 library API documentation - JSON configuration schema design - Plugin structure and data structures - 7-phase implementation roadmap - Testing strategy - Add user guide with: - Configuration reference - Example configurations - S7 address mapping guide - Client connection examples - Troubleshooting guide - Add default s7comm_config.json with standard mappings Co-Authored-By: Claude Opus 4.5 --- .../native/s7comm/docs/IMPLEMENTATION_PLAN.md | 1385 +++++++++++++++++ .../plugins/native/s7comm/docs/USER_GUIDE.md | 522 +++++++ .../plugins/native/s7comm/s7comm_config.json | 133 ++ 3 files changed, 2040 insertions(+) create mode 100644 core/src/drivers/plugins/native/s7comm/docs/IMPLEMENTATION_PLAN.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.json 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/USER_GUIDE.md b/core/src/drivers/plugins/native/s7comm/docs/USER_GUIDE.md new file mode 100644 index 00000000..e3ff87aa --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/docs/USER_GUIDE.md @@ -0,0 +1,522 @@ +# 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 +- Thread-safe operation synchronized with PLC scan cycle + +## 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 + +## Support + +For issues and feature requests: +- GitHub Issues: https://github.com/thiagoralves/OpenPLC_v3/issues +- OpenPLC Forum: https://openplc.discussion.community/ + +--- + +*Document Version: 1.0* +*Last Updated: 2026-01-12* 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 + } +} From 1a86e538b07f7c1cbd2e29710dd621cf6f252243 Mon Sep 17 00:00:00 2001 From: Thiago Alves Date: Mon, 12 Jan 2026 19:05:24 -0500 Subject: [PATCH 02/12] Add S7Comm plugin Phase 1 implementation with Snap7 sources Phase 1 implementation of S7Comm plugin for OpenPLC Runtime v4: - Include Snap7 library source code (v1.4.3) for self-contained build - Add plugin source files (s7comm_plugin.cpp/h) - Add CMakeLists.txt for standalone plugin compilation - Implement plugin lifecycle: init, start_loop, stop_loop, cleanup - Implement cycle hooks for buffer synchronization - Register S7 system areas (PE, PA, MK) and data blocks (DB1, DB2, DB10, DB20, DB100, DB200) - Handle big-endian conversion for S7 protocol compatibility - Add event logging for client connections Data block mapping (Phase 1 - hardcoded): - DB1: bool_input (%IX) - DB2: bool_output (%QX) - DB10: int_input (%IW) - DB20: int_output (%QW) - DB100: int_memory (%MW) - DB200: dint_memory (%MD) Note: JSON configuration support will be added in Phase 2. Co-Authored-By: Claude Opus 4.5 --- .../plugins/native/s7comm/CMakeLists.txt | 139 + .../plugins/native/s7comm/s7comm_plugin.cpp | 501 +++ .../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 + 33 files changed, 17482 insertions(+) create mode 100644 core/src/drivers/plugins/native/s7comm/CMakeLists.txt 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..166f4051 --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/CMakeLists.txt @@ -0,0 +1,139 @@ +# 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 +) + +# ============================================================================= +# Plugin Source Files +# ============================================================================= + +set(PLUGIN_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/s7comm_plugin.cpp + ${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 + ${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} + ${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 + $<$:-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/s7comm_plugin.cpp b/core/src/drivers/plugins/native/s7comm/s7comm_plugin.cpp new file mode 100644 index 00000000..4d9d6d0b --- /dev/null +++ b/core/src/drivers/plugins/native/s7comm/s7comm_plugin.cpp @@ -0,0 +1,501 @@ +/** + * @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. + * + * Phase 1 Implementation: + * - Basic server lifecycle (init, start, stop, cleanup) + * - Hardcoded configuration for testing + * - Data area registration and synchronization + * - Event logging for connections + */ + +#include +#include +#include +#include + +/* Snap7 includes */ +#include "snap7_libmain.h" +#include "s7_types.h" + +/* Plugin includes */ +extern "C" { +#include "plugin_logger.h" +#include "plugin_types.h" +#include "s7comm_plugin.h" +} + +/* + * ============================================================================= + * Configuration Constants (Phase 1 - hardcoded) + * These will be moved to JSON configuration in Phase 2 + * ============================================================================= + */ +#define S7COMM_DEFAULT_PORT 102 +#define S7COMM_DEFAULT_MAX_CLIENTS 32 +#define S7COMM_BUFFER_SIZE 1024 + +/* Data block numbers for mapping (user-friendly numbers for Phase 1) */ +#define DB_BOOL_INPUT 1 /* Maps to bool_input - %IX */ +#define DB_BOOL_OUTPUT 2 /* Maps to bool_output - %QX */ +#define DB_INT_INPUT 10 /* Maps to int_input - %IW */ +#define DB_INT_OUTPUT 20 /* Maps to int_output - %QW */ +#define DB_INT_MEMORY 100 /* Maps to int_memory - %MW */ +#define DB_DINT_MEMORY 200 /* Maps to dint_memory - %MD */ + +/* Size of each DB in bytes */ +#define DB_BOOL_SIZE 128 /* 1024 bits = 128 bytes */ +#define DB_INT_SIZE 2048 /* 1024 words = 2048 bytes */ +#define DB_DINT_SIZE 4096 /* 1024 dwords = 4096 bytes */ + +/* + * ============================================================================= + * Plugin State + * ============================================================================= + */ +static plugin_logger_t g_logger; +static plugin_runtime_args_t g_runtime_args; +static bool g_initialized = false; +static bool g_running = false; + +/* Snap7 server handle (S7Object is uintptr_t, use 0 for null) */ +static S7Object g_server = 0; + +/* Data buffers for S7 areas (registered with Snap7) */ +static uint8_t g_db_bool_input[DB_BOOL_SIZE]; +static uint8_t g_db_bool_output[DB_BOOL_SIZE]; +static uint8_t g_db_int_input[DB_INT_SIZE]; +static uint8_t g_db_int_output[DB_INT_SIZE]; +static uint8_t g_db_int_memory[DB_INT_SIZE]; +static uint8_t g_db_dint_memory[DB_DINT_SIZE]; + +/* System area buffers */ +static uint8_t g_pe_area[DB_BOOL_SIZE]; /* Process inputs (I area) */ +static uint8_t g_pa_area[DB_BOOL_SIZE]; /* Process outputs (Q area) */ +static uint8_t g_mk_area[256]; /* Markers (M area) */ + +/* + * ============================================================================= + * Forward Declarations + * ============================================================================= + */ +static void s7comm_event_callback(void *usrPtr, PSrvEvent PEvent, int Size); +static void sync_openplc_to_s7(void); +static void sync_s7_to_openplc(void); + +/* + * ============================================================================= + * 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); +} + +/* + * ============================================================================= + * 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..."); + + 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); + plugin_logger_info(&g_logger, "Config path: %s", + g_runtime_args.plugin_specific_config_file_path); + + /* Clear all data buffers */ + memset(g_db_bool_input, 0, sizeof(g_db_bool_input)); + memset(g_db_bool_output, 0, sizeof(g_db_bool_output)); + memset(g_db_int_input, 0, sizeof(g_db_int_input)); + memset(g_db_int_output, 0, sizeof(g_db_int_output)); + memset(g_db_int_memory, 0, sizeof(g_db_int_memory)); + memset(g_db_dint_memory, 0, sizeof(g_db_dint_memory)); + memset(g_pe_area, 0, sizeof(g_pe_area)); + memset(g_pa_area, 0, sizeof(g_pa_area)); + memset(g_mk_area, 0, sizeof(g_mk_area)); + + /* Create Snap7 server */ + g_server = Srv_Create(); + if (g_server == 0) { + plugin_logger_error(&g_logger, "Failed to create Snap7 server"); + return -1; + } + + /* Configure server parameters */ + uint16_t port = S7COMM_DEFAULT_PORT; + int max_clients = S7COMM_DEFAULT_MAX_CLIENTS; + + Srv_SetParam(g_server, p_u16_LocalPort, &port); + Srv_SetParam(g_server, p_i32_MaxClients, &max_clients); + + /* Set event mask to log important events */ + longword event_mask = evcServerStarted | evcServerStopped | + evcClientAdded | evcClientDisconnected | + evcClientRejected | evcListenerCannotStart; + Srv_SetMask(g_server, mkEvent, event_mask); + + /* Set event callback for logging */ + Srv_SetEventsCallback(g_server, s7comm_event_callback, NULL); + + /* Register system areas (PE, PA, MK) */ + int result; + + result = Srv_RegisterArea(g_server, srvAreaPE, 0, g_pe_area, sizeof(g_pe_area)); + if (result != 0) { + plugin_logger_warn(&g_logger, "Failed to register PE area: 0x%08X", result); + } + + result = Srv_RegisterArea(g_server, srvAreaPA, 0, g_pa_area, sizeof(g_pa_area)); + if (result != 0) { + plugin_logger_warn(&g_logger, "Failed to register PA area: 0x%08X", result); + } + + result = Srv_RegisterArea(g_server, srvAreaMK, 0, g_mk_area, sizeof(g_mk_area)); + if (result != 0) { + plugin_logger_warn(&g_logger, "Failed to register MK area: 0x%08X", result); + } + + /* Register data blocks */ + result = Srv_RegisterArea(g_server, srvAreaDB, DB_BOOL_INPUT, + g_db_bool_input, sizeof(g_db_bool_input)); + if (result != 0) { + plugin_logger_warn(&g_logger, "Failed to register DB%d: 0x%08X", DB_BOOL_INPUT, result); + } + + result = Srv_RegisterArea(g_server, srvAreaDB, DB_BOOL_OUTPUT, + g_db_bool_output, sizeof(g_db_bool_output)); + if (result != 0) { + plugin_logger_warn(&g_logger, "Failed to register DB%d: 0x%08X", DB_BOOL_OUTPUT, result); + } + + result = Srv_RegisterArea(g_server, srvAreaDB, DB_INT_INPUT, + g_db_int_input, sizeof(g_db_int_input)); + if (result != 0) { + plugin_logger_warn(&g_logger, "Failed to register DB%d: 0x%08X", DB_INT_INPUT, result); + } + + result = Srv_RegisterArea(g_server, srvAreaDB, DB_INT_OUTPUT, + g_db_int_output, sizeof(g_db_int_output)); + if (result != 0) { + plugin_logger_warn(&g_logger, "Failed to register DB%d: 0x%08X", DB_INT_OUTPUT, result); + } + + result = Srv_RegisterArea(g_server, srvAreaDB, DB_INT_MEMORY, + g_db_int_memory, sizeof(g_db_int_memory)); + if (result != 0) { + plugin_logger_warn(&g_logger, "Failed to register DB%d: 0x%08X", DB_INT_MEMORY, result); + } + + result = Srv_RegisterArea(g_server, srvAreaDB, DB_DINT_MEMORY, + g_db_dint_memory, sizeof(g_db_dint_memory)); + if (result != 0) { + plugin_logger_warn(&g_logger, "Failed to register DB%d: 0x%08X", DB_DINT_MEMORY, result); + } + + g_initialized = true; + plugin_logger_info(&g_logger, "S7Comm plugin initialized successfully"); + plugin_logger_info(&g_logger, "Registered areas: PE, PA, MK, DB%d, DB%d, DB%d, DB%d, DB%d, DB%d", + DB_BOOL_INPUT, DB_BOOL_OUTPUT, DB_INT_INPUT, + DB_INT_OUTPUT, DB_INT_MEMORY, DB_DINT_MEMORY); + + 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_running) { + plugin_logger_warn(&g_logger, "Server already running"); + return; + } + + plugin_logger_info(&g_logger, "Starting S7 server on port %d...", S7COMM_DEFAULT_PORT); + + /* Start the server - listens on all interfaces */ + int result = Srv_Start(g_server); + if (result != 0) { + plugin_logger_error(&g_logger, "Failed to start S7 server: 0x%08X", result); + plugin_logger_error(&g_logger, "Note: Port 102 requires root privileges on Linux"); + 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_info(&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; + } + + g_initialized = false; + plugin_logger_info(&g_logger, "S7Comm plugin cleanup complete"); +} + +/** + * @brief Called at the start of each PLC scan cycle + * + * Synchronizes OpenPLC input buffers to S7 data areas. + * Called with buffer mutex already held by PLC cycle manager. + */ +extern "C" void cycle_start(void) +{ + if (!g_initialized || !g_running) { + return; + } + + /* Sync OpenPLC inputs to S7 buffers */ + sync_openplc_to_s7(); +} + +/** + * @brief Called at the end of each PLC scan cycle + * + * Synchronizes S7 data areas to OpenPLC output buffers. + * Called with buffer mutex already held by PLC cycle manager. + */ +extern "C" void cycle_end(void) +{ + if (!g_initialized || !g_running) { + return; + } + + /* Sync S7 buffers to OpenPLC outputs */ + sync_s7_to_openplc(); +} + +/* + * ============================================================================= + * 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: + plugin_logger_info(&g_logger, "Client connected (ID: %d)", PEvent->EvtSender); + break; + case evcClientDisconnected: + 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; + default: + /* Ignore other events */ + break; + } +} + +/* + * ============================================================================= + * Buffer Synchronization Functions + * ============================================================================= + */ + +/** + * @brief Sync OpenPLC buffers to S7 data areas + * + * Copies current OpenPLC input/output/memory values to S7 buffers + * so S7 clients can read them. + */ +static void sync_openplc_to_s7(void) +{ + int i, byte_idx, bit_idx; + uint8_t byte_val; + + /* Sync bool_input to PE area and DB1 */ + for (byte_idx = 0; byte_idx < DB_BOOL_SIZE && byte_idx < g_runtime_args.buffer_size; byte_idx++) { + byte_val = 0; + for (bit_idx = 0; bit_idx < 8; bit_idx++) { + IEC_BOOL *ptr = g_runtime_args.bool_input[byte_idx][bit_idx]; + if (ptr != NULL && *ptr) { + byte_val |= (1 << bit_idx); + } + } + g_pe_area[byte_idx] = byte_val; + g_db_bool_input[byte_idx] = byte_val; + } + + /* Sync bool_output to PA area and DB2 */ + for (byte_idx = 0; byte_idx < DB_BOOL_SIZE && byte_idx < g_runtime_args.buffer_size; byte_idx++) { + byte_val = 0; + for (bit_idx = 0; bit_idx < 8; bit_idx++) { + IEC_BOOL *ptr = g_runtime_args.bool_output[byte_idx][bit_idx]; + if (ptr != NULL && *ptr) { + byte_val |= (1 << bit_idx); + } + } + g_pa_area[byte_idx] = byte_val; + g_db_bool_output[byte_idx] = byte_val; + } + + /* Sync int_input to DB10 (with big-endian conversion) */ + uint16_t *db_int_input = (uint16_t *)g_db_int_input; + for (i = 0; i < S7COMM_BUFFER_SIZE && i < g_runtime_args.buffer_size; i++) { + IEC_UINT *ptr = g_runtime_args.int_input[i]; + if (ptr != NULL) { + db_int_input[i] = swap16(*ptr); + } + } + + /* Sync int_output to DB20 (with big-endian conversion) */ + uint16_t *db_int_output = (uint16_t *)g_db_int_output; + for (i = 0; i < S7COMM_BUFFER_SIZE && i < g_runtime_args.buffer_size; i++) { + IEC_UINT *ptr = g_runtime_args.int_output[i]; + if (ptr != NULL) { + db_int_output[i] = swap16(*ptr); + } + } + + /* Sync int_memory to DB100 (with big-endian conversion) */ + uint16_t *db_int_memory = (uint16_t *)g_db_int_memory; + for (i = 0; i < S7COMM_BUFFER_SIZE && i < g_runtime_args.buffer_size; i++) { + IEC_UINT *ptr = g_runtime_args.int_memory[i]; + if (ptr != NULL) { + db_int_memory[i] = swap16(*ptr); + } + } + + /* Sync dint_memory to DB200 (with big-endian conversion) */ + uint32_t *db_dint_memory = (uint32_t *)g_db_dint_memory; + for (i = 0; i < S7COMM_BUFFER_SIZE && i < g_runtime_args.buffer_size; i++) { + IEC_UDINT *ptr = g_runtime_args.dint_memory[i]; + if (ptr != NULL) { + db_dint_memory[i] = swap32(*ptr); + } + } +} + +/** + * @brief Sync S7 data areas to OpenPLC buffers + * + * Copies values written by S7 clients back to OpenPLC output/memory buffers. + */ +static void sync_s7_to_openplc(void) +{ + int i, byte_idx, bit_idx; + uint8_t byte_val; + + /* Sync PA area and DB2 to bool_output */ + for (byte_idx = 0; byte_idx < DB_BOOL_SIZE && byte_idx < g_runtime_args.buffer_size; byte_idx++) { + byte_val = g_db_bool_output[byte_idx]; + for (bit_idx = 0; bit_idx < 8; bit_idx++) { + IEC_BOOL *ptr = g_runtime_args.bool_output[byte_idx][bit_idx]; + if (ptr != NULL) { + *ptr = (byte_val >> bit_idx) & 0x01; + } + } + } + + /* Sync DB20 to int_output (with big-endian conversion) */ + uint16_t *db_int_output = (uint16_t *)g_db_int_output; + for (i = 0; i < S7COMM_BUFFER_SIZE && i < g_runtime_args.buffer_size; i++) { + IEC_UINT *ptr = g_runtime_args.int_output[i]; + if (ptr != NULL) { + *ptr = swap16(db_int_output[i]); + } + } + + /* Sync DB100 to int_memory (with big-endian conversion) */ + uint16_t *db_int_memory = (uint16_t *)g_db_int_memory; + for (i = 0; i < S7COMM_BUFFER_SIZE && i < g_runtime_args.buffer_size; i++) { + IEC_UINT *ptr = g_runtime_args.int_memory[i]; + if (ptr != NULL) { + *ptr = swap16(db_int_memory[i]); + } + } + + /* Sync DB200 to dint_memory (with big-endian conversion) */ + uint32_t *db_dint_memory = (uint32_t *)g_db_dint_memory; + for (i = 0; i < S7COMM_BUFFER_SIZE && i < g_runtime_args.buffer_size; i++) { + IEC_UDINT *ptr = g_runtime_args.dint_memory[i]; + if (ptr != NULL) { + *ptr = swap32(db_dint_memory[i]); + } + } +} 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 a577b51a626c443abf06114ec6640a06c68da08a Mon Sep 17 00:00:00 2001 From: Thiago Alves Date: Mon, 12 Jan 2026 19:15:05 -0500 Subject: [PATCH 03/12] Add native plugin build system to install.sh Add build_native_plugins() function that automatically scans for native plugins with CMakeLists.txt and builds them during installation. This enables Docker images to include pre-built native plugins like s7comm. The function: - Scans core/src/drivers/plugins/native/ for CMakeLists.txt files - Creates isolated build directories for each plugin - Passes OPENPLC_ROOT to cmake for proper header resolution - Copies built .so files to build/plugins/ - Provides summary of successful/failed builds Co-Authored-By: Claude Opus 4.5 --- install.sh | 105 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/install.sh b/install.sh index 3a3ad460..918cfcf8 100755 --- a/install.sh +++ b/install.sh @@ -380,6 +380,107 @@ setup_plugin_venvs() { return 0 } +# Function to build native plugins that have CMakeLists.txt +build_native_plugins() { + local native_plugins_dir="$OPENPLC_DIR/core/src/drivers/plugins/native" + local plugins_output_dir="$OPENPLC_DIR/build/plugins" + + log_info "Scanning for native plugins to build..." + + # Check if native plugins directory exists + if [ ! -d "$native_plugins_dir" ]; then + log_warning "Native plugins directory not found: $native_plugins_dir" + return 0 + fi + + # Create plugins output directory + mkdir -p "$plugins_output_dir" + + # Find directories with CMakeLists.txt (indicates buildable plugin) + local plugins_found=0 + local plugins_built=0 + local plugins_failed=0 + + for plugin_dir in "$native_plugins_dir"/*/; do + # Skip if not a directory + [ -d "$plugin_dir" ] || continue + + local plugin_name=$(basename "$plugin_dir") + local cmake_file="$plugin_dir/CMakeLists.txt" + + # Skip if no CMakeLists.txt + if [ ! -f "$cmake_file" ]; then + continue + fi + + plugins_found=$((plugins_found + 1)) + log_info "Found native plugin: $plugin_name" + + # Create build directory for this plugin + local plugin_build_dir="$plugin_dir/build" + + # Clean existing build directory + if [ -d "$plugin_build_dir" ]; then + log_info "Cleaning existing build directory for $plugin_name..." + rm -rf "$plugin_build_dir" + fi + + mkdir -p "$plugin_build_dir" + + # Build the plugin + log_info "Building $plugin_name..." + ( + cd "$plugin_build_dir" || exit 1 + + # Configure with cmake, passing OpenPLC root directory + if ! cmake -DOPENPLC_ROOT="$OPENPLC_DIR" ..; then + log_error "CMake configuration failed for $plugin_name" + exit 1 + fi + + # Build with make + if ! make -j"$(nproc)"; then + log_error "Compilation failed for $plugin_name" + exit 1 + fi + ) + + if [ $? -eq 0 ]; then + # Copy built plugin to central plugins directory + local built_lib=$(find "$plugin_build_dir" -name "*.so" -type f 2>/dev/null | head -1) + if [ -n "$built_lib" ] && [ -f "$built_lib" ]; then + cp "$built_lib" "$plugins_output_dir/" + log_success "Built and installed: $plugin_name ($(basename "$built_lib"))" + plugins_built=$((plugins_built + 1)) + else + log_warning "No .so file found after building $plugin_name" + plugins_failed=$((plugins_failed + 1)) + fi + else + log_error "Failed to build $plugin_name" + plugins_failed=$((plugins_failed + 1)) + fi + done + + if [ $plugins_found -eq 0 ]; then + log_info "No native plugins with CMakeLists.txt found" + return 0 + fi + + log_info "Native plugin build summary: $plugins_built/$plugins_found succeeded" + + if [ $plugins_failed -gt 0 ]; then + log_warning "$plugins_failed plugin(s) failed to build" + # Don't fail installation if some plugins fail - they may be optional + fi + + if [ $plugins_built -gt 0 ]; then + log_success "Native plugins built and installed to: $plugins_output_dir" + fi + + return 0 +} + # Setup runtime directory (needed for both Linux and Docker) # On MSYS2, use /run/runtime which maps to the MSYS2 installation directory if is_msys2; then @@ -419,6 +520,10 @@ echo "Compiling OpenPLC..." if compile_plc; then echo "Build process completed successfully!" + # Build native plugins after main compilation + echo "Building native plugins..." + build_native_plugins + # Create installation marker (must be done before starting the service) touch "$OPENPLC_DIR/.installed" echo "Installation completed at $(date)" > "$OPENPLC_DIR/.installed" From e9cc72954f811192ce0ab496f35865fa55c1474d Mon Sep 17 00:00:00 2001 From: Thiago Alves Date: Mon, 12 Jan 2026 19:42:39 -0500 Subject: [PATCH 04/12] Implement Phase 2: JSON configuration system for S7Comm plugin Add full JSON configuration support using embedded cJSON library: Configuration features: - Server settings (port, max_clients, timeouts, PDU size) - PLC identity (name, module_type, serial_number, etc.) - Dynamic data block allocation with configurable mappings - System areas (PE, PA, MK) with configurable sizes - Logging options (connections, data_access, errors) Implementation changes: - Add cJSON library (v1.7.18, MIT license) for JSON parsing - Add s7comm_config.h with configuration structures - Add s7comm_config.c with parser and validation - Refactor s7comm_plugin.cpp to use configuration: - Dynamic memory allocation for data blocks - All server parameters from config - Support for all buffer types (bool, int, dint, lint) - 64-bit (LINT) support with swap64 endianness conversion - Update CMakeLists.txt to include new source files The plugin now reads s7comm_config.json at startup and dynamically allocates and registers S7 data areas based on configuration. Co-Authored-By: Claude Opus 4.5 --- .../plugins/native/s7comm/CMakeLists.txt | 11 + .../plugins/native/s7comm/cjson/cJSON.c | 3143 +++++++++++++++++ .../plugins/native/s7comm/cjson/cJSON.h | 300 ++ .../plugins/native/s7comm/s7comm_config.c | 477 +++ .../plugins/native/s7comm/s7comm_config.h | 177 + .../plugins/native/s7comm/s7comm_plugin.cpp | 869 ++++- 6 files changed, 4783 insertions(+), 194 deletions(-) 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/s7comm_config.c create mode 100644 core/src/drivers/plugins/native/s7comm/s7comm_config.h diff --git a/core/src/drivers/plugins/native/s7comm/CMakeLists.txt b/core/src/drivers/plugins/native/s7comm/CMakeLists.txt index 166f4051..d3602b66 100644 --- a/core/src/drivers/plugins/native/s7comm/CMakeLists.txt +++ b/core/src/drivers/plugins/native/s7comm/CMakeLists.txt @@ -42,12 +42,21 @@ 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 ) @@ -60,6 +69,7 @@ include_directories( ${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 @@ -73,6 +83,7 @@ add_library(s7comm_plugin SHARED ${SNAP7_CORE_SOURCES} ${SNAP7_SYS_SOURCES} ${SNAP7_LIB_SOURCES} + ${CJSON_SOURCES} ${PLUGIN_SOURCES} ) 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/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_plugin.cpp b/core/src/drivers/plugins/native/s7comm/s7comm_plugin.cpp index 4d9d6d0b..34c5a239 100644 --- a/core/src/drivers/plugins/native/s7comm/s7comm_plugin.cpp +++ b/core/src/drivers/plugins/native/s7comm/s7comm_plugin.cpp @@ -5,11 +5,12 @@ * 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. * - * Phase 1 Implementation: - * - Basic server lifecycle (init, start, stop, cleanup) - * - Hardcoded configuration for testing - * - Data area registration and synchronization - * - Event logging for connections + * Phase 2 Implementation: + * - JSON configuration parsing with cJSON + * - Dynamic data block allocation based on config + * - Configurable server parameters + * - PLC identity configuration + * - All areas mapped from configuration file */ #include @@ -26,30 +27,29 @@ extern "C" { #include "plugin_logger.h" #include "plugin_types.h" #include "s7comm_plugin.h" +#include "s7comm_config.h" } /* * ============================================================================= - * Configuration Constants (Phase 1 - hardcoded) - * These will be moved to JSON configuration in Phase 2 + * Constants * ============================================================================= */ -#define S7COMM_DEFAULT_PORT 102 -#define S7COMM_DEFAULT_MAX_CLIENTS 32 -#define S7COMM_BUFFER_SIZE 1024 +#define S7COMM_MAX_DB_SIZE 65536 /* Maximum size for a single DB buffer */ -/* Data block numbers for mapping (user-friendly numbers for Phase 1) */ -#define DB_BOOL_INPUT 1 /* Maps to bool_input - %IX */ -#define DB_BOOL_OUTPUT 2 /* Maps to bool_output - %QX */ -#define DB_INT_INPUT 10 /* Maps to int_input - %IW */ -#define DB_INT_OUTPUT 20 /* Maps to int_output - %QW */ -#define DB_INT_MEMORY 100 /* Maps to int_memory - %MW */ -#define DB_DINT_MEMORY 200 /* Maps to dint_memory - %MD */ - -/* Size of each DB in bytes */ -#define DB_BOOL_SIZE 128 /* 1024 bits = 128 bytes */ -#define DB_INT_SIZE 2048 /* 1024 words = 2048 bytes */ -#define DB_DINT_SIZE 4096 /* 1024 dwords = 4096 bytes */ +/* + * ============================================================================= + * 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 *buffer; /* Allocated data buffer */ +} s7comm_db_runtime_t; /* * ============================================================================= @@ -58,24 +58,22 @@ extern "C" { */ 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; -/* Data buffers for S7 areas (registered with Snap7) */ -static uint8_t g_db_bool_input[DB_BOOL_SIZE]; -static uint8_t g_db_bool_output[DB_BOOL_SIZE]; -static uint8_t g_db_int_input[DB_INT_SIZE]; -static uint8_t g_db_int_output[DB_INT_SIZE]; -static uint8_t g_db_int_memory[DB_INT_SIZE]; -static uint8_t g_db_dint_memory[DB_DINT_SIZE]; +/* 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 buffers */ -static uint8_t g_pe_area[DB_BOOL_SIZE]; /* Process inputs (I area) */ -static uint8_t g_pa_area[DB_BOOL_SIZE]; /* Process outputs (Q area) */ -static uint8_t g_mk_area[256]; /* Markers (M area) */ +/* System area buffers (dynamically allocated based on config) */ +static uint8_t *g_pe_area = NULL; +static uint8_t *g_pa_area = NULL; +static uint8_t *g_mk_area = NULL; /* * ============================================================================= @@ -85,6 +83,9 @@ static uint8_t g_mk_area[256]; /* Markers (M area) */ static void s7comm_event_callback(void *usrPtr, PSrvEvent PEvent, int Size); static void sync_openplc_to_s7(void); static void sync_s7_to_openplc(void); +static int allocate_data_blocks(void); +static void free_data_blocks(void); +static int register_all_areas(void); /* * ============================================================================= @@ -105,6 +106,167 @@ static inline uint32_t swap32(uint32_t val) ((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 data block buffers based on configuration + */ +static int allocate_data_blocks(void) +{ + g_num_db_runtime = 0; + + /* Allocate system areas */ + if (g_config.pe_area.enabled && g_config.pe_area.size_bytes > 0) { + g_pe_area = (uint8_t *)calloc(1, g_config.pe_area.size_bytes); + if (g_pe_area == NULL) { + plugin_logger_error(&g_logger, "Failed to allocate PE area buffer"); + return -1; + } + } + + if (g_config.pa_area.enabled && g_config.pa_area.size_bytes > 0) { + g_pa_area = (uint8_t *)calloc(1, g_config.pa_area.size_bytes); + if (g_pa_area == NULL) { + plugin_logger_error(&g_logger, "Failed to allocate PA area buffer"); + return -1; + } + } + + if (g_config.mk_area.enabled && g_config.mk_area.size_bytes > 0) { + g_mk_area = (uint8_t *)calloc(1, g_config.mk_area.size_bytes); + if (g_mk_area == NULL) { + 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; + + db_rt->buffer = (uint8_t *)calloc(1, db_cfg->size_bytes); + if (db_rt->buffer == NULL) { + plugin_logger_error(&g_logger, "Failed to allocate DB%d buffer", db_cfg->db_number); + return -1; + } + + g_num_db_runtime++; + plugin_logger_debug(&g_logger, "Allocated DB%d: %d bytes, type=%s, start=%d", + db_cfg->db_number, db_cfg->size_bytes, + s7comm_buffer_type_name(db_cfg->mapping.type), + db_cfg->mapping.start_buffer); + } + + return 0; +} + +/** + * @brief Free all allocated data block buffers + */ +static void free_data_blocks(void) +{ + /* Free system areas */ + if (g_pe_area != NULL) { + free(g_pe_area); + g_pe_area = NULL; + } + if (g_pa_area != NULL) { + free(g_pa_area); + g_pa_area = NULL; + } + if (g_mk_area != NULL) { + free(g_mk_area); + g_mk_area = NULL; + } + + /* Free data blocks */ + for (int i = 0; i < g_num_db_runtime; i++) { + if (g_db_runtime[i].buffer != NULL) { + free(g_db_runtime[i].buffer); + g_db_runtime[i].buffer = NULL; + } + } + g_num_db_runtime = 0; +} + +/** + * @brief Register all areas with the Snap7 server + */ +static int register_all_areas(void) +{ + int result; + + /* Register system areas */ + if (g_config.pe_area.enabled && g_pe_area != NULL) { + result = Srv_RegisterArea(g_server, srvAreaPE, 0, g_pe_area, g_config.pe_area.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_config.pe_area.size_bytes); + } + } + + if (g_config.pa_area.enabled && g_pa_area != NULL) { + result = Srv_RegisterArea(g_server, srvAreaPA, 0, g_pa_area, g_config.pa_area.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_config.pa_area.size_bytes); + } + } + + if (g_config.mk_area.enabled && g_mk_area != NULL) { + result = Srv_RegisterArea(g_server, srvAreaMK, 0, g_mk_area, g_config.mk_area.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_config.mk_area.size_bytes); + } + } + + /* Register data blocks */ + 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->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 @@ -132,104 +294,117 @@ extern "C" int init(void *args) plugin_logger_init(&g_logger, "S7COMM", args); plugin_logger_info(&g_logger, "Buffer size: %d", g_runtime_args.buffer_size); - plugin_logger_info(&g_logger, "Config path: %s", - g_runtime_args.plugin_specific_config_file_path); - - /* Clear all data buffers */ - memset(g_db_bool_input, 0, sizeof(g_db_bool_input)); - memset(g_db_bool_output, 0, sizeof(g_db_bool_output)); - memset(g_db_int_input, 0, sizeof(g_db_int_input)); - memset(g_db_int_output, 0, sizeof(g_db_int_output)); - memset(g_db_int_memory, 0, sizeof(g_db_int_memory)); - memset(g_db_dint_memory, 0, sizeof(g_db_dint_memory)); - memset(g_pe_area, 0, sizeof(g_pe_area)); - memset(g_pa_area, 0, sizeof(g_pa_area)); - memset(g_mk_area, 0, sizeof(g_mk_area)); + + /* 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 data block buffers */ + if (allocate_data_blocks() != 0) { + plugin_logger_error(&g_logger, "Failed to allocate data block buffers"); + free_data_blocks(); + return -1; + } /* Create Snap7 server */ g_server = Srv_Create(); if (g_server == 0) { plugin_logger_error(&g_logger, "Failed to create Snap7 server"); + free_data_blocks(); return -1; } - /* Configure server parameters */ - uint16_t port = S7COMM_DEFAULT_PORT; - int max_clients = S7COMM_DEFAULT_MAX_CLIENTS; + /* 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); - - /* Set event mask to log important events */ - longword event_mask = evcServerStarted | evcServerStopped | - evcClientAdded | evcClientDisconnected | - evcClientRejected | evcListenerCannotStart; + 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); - /* Register system areas (PE, PA, MK) */ - int result; - - result = Srv_RegisterArea(g_server, srvAreaPE, 0, g_pe_area, sizeof(g_pe_area)); - if (result != 0) { - plugin_logger_warn(&g_logger, "Failed to register PE area: 0x%08X", result); - } - - result = Srv_RegisterArea(g_server, srvAreaPA, 0, g_pa_area, sizeof(g_pa_area)); - if (result != 0) { - plugin_logger_warn(&g_logger, "Failed to register PA area: 0x%08X", result); - } - - result = Srv_RegisterArea(g_server, srvAreaMK, 0, g_mk_area, sizeof(g_mk_area)); - if (result != 0) { - plugin_logger_warn(&g_logger, "Failed to register MK area: 0x%08X", result); - } - - /* Register data blocks */ - result = Srv_RegisterArea(g_server, srvAreaDB, DB_BOOL_INPUT, - g_db_bool_input, sizeof(g_db_bool_input)); - if (result != 0) { - plugin_logger_warn(&g_logger, "Failed to register DB%d: 0x%08X", DB_BOOL_INPUT, result); - } + /* Register all areas with the server */ + register_all_areas(); - result = Srv_RegisterArea(g_server, srvAreaDB, DB_BOOL_OUTPUT, - g_db_bool_output, sizeof(g_db_bool_output)); - if (result != 0) { - plugin_logger_warn(&g_logger, "Failed to register DB%d: 0x%08X", DB_BOOL_OUTPUT, result); - } + g_initialized = true; + plugin_logger_info(&g_logger, "S7Comm plugin initialized successfully"); - result = Srv_RegisterArea(g_server, srvAreaDB, DB_INT_INPUT, - g_db_int_input, sizeof(g_db_int_input)); - if (result != 0) { - plugin_logger_warn(&g_logger, "Failed to register DB%d: 0x%08X", DB_INT_INPUT, result); + /* Log registered areas summary */ + if (g_config.pe_area.enabled) { + plugin_logger_info(&g_logger, "PE area: %d bytes -> %s", + g_config.pe_area.size_bytes, + s7comm_buffer_type_name(g_config.pe_area.mapping.type)); } - - result = Srv_RegisterArea(g_server, srvAreaDB, DB_INT_OUTPUT, - g_db_int_output, sizeof(g_db_int_output)); - if (result != 0) { - plugin_logger_warn(&g_logger, "Failed to register DB%d: 0x%08X", DB_INT_OUTPUT, result); + if (g_config.pa_area.enabled) { + plugin_logger_info(&g_logger, "PA area: %d bytes -> %s", + g_config.pa_area.size_bytes, + s7comm_buffer_type_name(g_config.pa_area.mapping.type)); } - - result = Srv_RegisterArea(g_server, srvAreaDB, DB_INT_MEMORY, - g_db_int_memory, sizeof(g_db_int_memory)); - if (result != 0) { - plugin_logger_warn(&g_logger, "Failed to register DB%d: 0x%08X", DB_INT_MEMORY, result); + if (g_config.mk_area.enabled) { + plugin_logger_info(&g_logger, "MK area: %d bytes -> %s", + g_config.mk_area.size_bytes, + s7comm_buffer_type_name(g_config.mk_area.mapping.type)); } - - result = Srv_RegisterArea(g_server, srvAreaDB, DB_DINT_MEMORY, - g_db_dint_memory, sizeof(g_db_dint_memory)); - if (result != 0) { - plugin_logger_warn(&g_logger, "Failed to register DB%d: 0x%08X", DB_DINT_MEMORY, result); + 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); } - g_initialized = true; - plugin_logger_info(&g_logger, "S7Comm plugin initialized successfully"); - plugin_logger_info(&g_logger, "Registered areas: PE, PA, MK, DB%d, DB%d, DB%d, DB%d, DB%d, DB%d", - DB_BOOL_INPUT, DB_BOOL_OUTPUT, DB_INT_INPUT, - DB_INT_OUTPUT, DB_INT_MEMORY, DB_DINT_MEMORY); - return 0; } @@ -243,18 +418,32 @@ extern "C" void start_loop(void) 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 port %d...", S7COMM_DEFAULT_PORT); + 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); + } - /* Start the server - listens on all interfaces */ - int result = Srv_Start(g_server); if (result != 0) { plugin_logger_error(&g_logger, "Failed to start S7 server: 0x%08X", result); - plugin_logger_error(&g_logger, "Note: Port 102 requires root privileges on Linux"); + if (g_config.port < 1024) { + plugin_logger_error(&g_logger, "Note: Port %d requires root privileges on Linux", g_config.port); + } return; } @@ -268,7 +457,7 @@ extern "C" void start_loop(void) extern "C" void stop_loop(void) { if (!g_running) { - plugin_logger_info(&g_logger, "Server already stopped"); + plugin_logger_debug(&g_logger, "Server already stopped"); return; } @@ -296,7 +485,10 @@ extern "C" void cleanup(void) g_server = 0; } + free_data_blocks(); + g_initialized = false; + g_config_loaded = false; plugin_logger_info(&g_logger, "S7Comm plugin cleanup complete"); } @@ -308,7 +500,7 @@ extern "C" void cleanup(void) */ extern "C" void cycle_start(void) { - if (!g_initialized || !g_running) { + if (!g_initialized || !g_running || !g_config.enabled) { return; } @@ -324,7 +516,7 @@ extern "C" void cycle_start(void) */ extern "C" void cycle_end(void) { - if (!g_initialized || !g_running) { + if (!g_initialized || !g_running || !g_config.enabled) { return; } @@ -354,10 +546,14 @@ static void s7comm_event_callback(void *usrPtr, PSrvEvent PEvent, int Size) plugin_logger_info(&g_logger, "S7 server stopped"); break; case evcClientAdded: - plugin_logger_info(&g_logger, "Client connected (ID: %d)", PEvent->EvtSender); + if (g_config.logging.log_connections) { + plugin_logger_info(&g_logger, "Client connected (ID: %d)", PEvent->EvtSender); + } break; case evcClientDisconnected: - plugin_logger_info(&g_logger, "Client disconnected (ID: %d)", PEvent->EvtSender); + 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); @@ -365,6 +561,21 @@ static void s7comm_event_callback(void *usrPtr, PSrvEvent PEvent, int Size) 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; @@ -378,124 +589,394 @@ static void s7comm_event_callback(void *usrPtr, PSrvEvent PEvent, int Size) */ /** - * @brief Sync OpenPLC buffers to S7 data areas - * - * Copies current OpenPLC input/output/memory values to S7 buffers - * so S7 clients can read them. + * @brief Sync a bool buffer to S7 byte array */ -static void sync_openplc_to_s7(void) +static void sync_bool_to_s7(uint8_t *s7_buf, int s7_size, s7comm_buffer_type_t type, int start_buffer) { - int i, byte_idx, bit_idx; - uint8_t byte_val; - - /* Sync bool_input to PE area and DB1 */ - for (byte_idx = 0; byte_idx < DB_BOOL_SIZE && byte_idx < g_runtime_args.buffer_size; byte_idx++) { - byte_val = 0; - for (bit_idx = 0; bit_idx < 8; bit_idx++) { - IEC_BOOL *ptr = g_runtime_args.bool_input[byte_idx][bit_idx]; + 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 > s7_size) max_bytes = s7_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); } } - g_pe_area[byte_idx] = byte_val; - g_db_bool_input[byte_idx] = byte_val; + s7_buf[byte_idx] = byte_val; } +} - /* Sync bool_output to PA area and DB2 */ - for (byte_idx = 0; byte_idx < DB_BOOL_SIZE && byte_idx < g_runtime_args.buffer_size; byte_idx++) { - byte_val = 0; - for (bit_idx = 0; bit_idx < 8; bit_idx++) { - IEC_BOOL *ptr = g_runtime_args.bool_output[byte_idx][bit_idx]; - if (ptr != NULL && *ptr) { - byte_val |= (1 << bit_idx); +/** + * @brief Sync S7 byte array to bool buffer + */ +static void sync_s7_to_bool(uint8_t *s7_buf, int s7_size, s7comm_buffer_type_t type, int start_buffer) +{ + IEC_BOOL *(*buffer)[8] = NULL; + + switch (type) { + 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; /* Don't write to inputs */ + } + + int max_bytes = g_runtime_args.buffer_size - start_buffer; + if (max_bytes > s7_size) max_bytes = s7_size; + + for (int byte_idx = 0; byte_idx < max_bytes; byte_idx++) { + uint8_t byte_val = s7_buf[byte_idx]; + 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 >> bit_idx) & 0x01; } } - g_pa_area[byte_idx] = byte_val; - g_db_bool_output[byte_idx] = byte_val; } +} - /* Sync int_input to DB10 (with big-endian conversion) */ - uint16_t *db_int_input = (uint16_t *)g_db_int_input; - for (i = 0; i < S7COMM_BUFFER_SIZE && i < g_runtime_args.buffer_size; i++) { - IEC_UINT *ptr = g_runtime_args.int_input[i]; - if (ptr != NULL) { - db_int_input[i] = swap16(*ptr); - } +/** + * @brief Sync int buffer to S7 word array (with big-endian conversion) + */ +static void sync_int_to_s7(uint8_t *s7_buf, int s7_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; } - /* Sync int_output to DB20 (with big-endian conversion) */ - uint16_t *db_int_output = (uint16_t *)g_db_int_output; - for (i = 0; i < S7COMM_BUFFER_SIZE && i < g_runtime_args.buffer_size; i++) { - IEC_UINT *ptr = g_runtime_args.int_output[i]; + uint16_t *s7_words = (uint16_t *)s7_buf; + int num_words = s7_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) { - db_int_output[i] = swap16(*ptr); + s7_words[i] = swap16(*ptr); } } +} + +/** + * @brief Sync S7 word array to int buffer (with big-endian conversion) + */ +static void sync_s7_to_int(uint8_t *s7_buf, int s7_size, s7comm_buffer_type_t type, int start_buffer) +{ + IEC_UINT **buffer = NULL; + + switch (type) { + 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; /* Don't write to inputs */ + } + + uint16_t *s7_words = (uint16_t *)s7_buf; + int num_words = s7_size / 2; + int max_words = g_runtime_args.buffer_size - start_buffer; + if (max_words > num_words) max_words = num_words; - /* Sync int_memory to DB100 (with big-endian conversion) */ - uint16_t *db_int_memory = (uint16_t *)g_db_int_memory; - for (i = 0; i < S7COMM_BUFFER_SIZE && i < g_runtime_args.buffer_size; i++) { - IEC_UINT *ptr = g_runtime_args.int_memory[i]; + for (int i = 0; i < max_words; i++) { + IEC_UINT *ptr = buffer[start_buffer + i]; if (ptr != NULL) { - db_int_memory[i] = swap16(*ptr); + *ptr = swap16(s7_words[i]); } } +} + +/** + * @brief Sync dint buffer to S7 dword array (with big-endian conversion) + */ +static void sync_dint_to_s7(uint8_t *s7_buf, int s7_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; + } - /* Sync dint_memory to DB200 (with big-endian conversion) */ - uint32_t *db_dint_memory = (uint32_t *)g_db_dint_memory; - for (i = 0; i < S7COMM_BUFFER_SIZE && i < g_runtime_args.buffer_size; i++) { - IEC_UDINT *ptr = g_runtime_args.dint_memory[i]; + uint32_t *s7_dwords = (uint32_t *)s7_buf; + int num_dwords = s7_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) { - db_dint_memory[i] = swap32(*ptr); + s7_dwords[i] = swap32(*ptr); } } } /** - * @brief Sync S7 data areas to OpenPLC buffers - * - * Copies values written by S7 clients back to OpenPLC output/memory buffers. + * @brief Sync S7 dword array to dint buffer (with big-endian conversion) */ -static void sync_s7_to_openplc(void) +static void sync_s7_to_dint(uint8_t *s7_buf, int s7_size, s7comm_buffer_type_t type, int start_buffer) { - int i, byte_idx, bit_idx; - uint8_t byte_val; - - /* Sync PA area and DB2 to bool_output */ - for (byte_idx = 0; byte_idx < DB_BOOL_SIZE && byte_idx < g_runtime_args.buffer_size; byte_idx++) { - byte_val = g_db_bool_output[byte_idx]; - for (bit_idx = 0; bit_idx < 8; bit_idx++) { - IEC_BOOL *ptr = g_runtime_args.bool_output[byte_idx][bit_idx]; - if (ptr != NULL) { - *ptr = (byte_val >> bit_idx) & 0x01; - } - } + IEC_UDINT **buffer = NULL; + + switch (type) { + 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; /* Don't write to inputs */ } - /* Sync DB20 to int_output (with big-endian conversion) */ - uint16_t *db_int_output = (uint16_t *)g_db_int_output; - for (i = 0; i < S7COMM_BUFFER_SIZE && i < g_runtime_args.buffer_size; i++) { - IEC_UINT *ptr = g_runtime_args.int_output[i]; + uint32_t *s7_dwords = (uint32_t *)s7_buf; + int num_dwords = s7_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) { - *ptr = swap16(db_int_output[i]); + *ptr = swap32(s7_dwords[i]); } } +} + +/** + * @brief Sync lint buffer to S7 lword array (with big-endian conversion) + */ +static void sync_lint_to_s7(uint8_t *s7_buf, int s7_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; + } - /* Sync DB100 to int_memory (with big-endian conversion) */ - uint16_t *db_int_memory = (uint16_t *)g_db_int_memory; - for (i = 0; i < S7COMM_BUFFER_SIZE && i < g_runtime_args.buffer_size; i++) { - IEC_UINT *ptr = g_runtime_args.int_memory[i]; + uint64_t *s7_lwords = (uint64_t *)s7_buf; + int num_lwords = s7_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) { - *ptr = swap16(db_int_memory[i]); + s7_lwords[i] = swap64(*ptr); } } +} - /* Sync DB200 to dint_memory (with big-endian conversion) */ - uint32_t *db_dint_memory = (uint32_t *)g_db_dint_memory; - for (i = 0; i < S7COMM_BUFFER_SIZE && i < g_runtime_args.buffer_size; i++) { - IEC_UDINT *ptr = g_runtime_args.dint_memory[i]; +/** + * @brief Sync S7 lword array to lint buffer (with big-endian conversion) + */ +static void sync_s7_to_lint(uint8_t *s7_buf, int s7_size, s7comm_buffer_type_t type, int start_buffer) +{ + IEC_ULINT **buffer = NULL; + + switch (type) { + 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; /* Don't write to inputs */ + } + + uint64_t *s7_lwords = (uint64_t *)s7_buf; + int num_lwords = s7_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) { - *ptr = swap32(db_dint_memory[i]); + *ptr = swap64(s7_lwords[i]); } } } + +/** + * @brief Dispatch sync from OpenPLC to S7 based on buffer type + */ +static void sync_buffer_to_s7(uint8_t *s7_buf, int s7_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: + sync_bool_to_s7(s7_buf, s7_size, type, start_buffer); + break; + + case BUFFER_TYPE_INT_INPUT: + case BUFFER_TYPE_INT_OUTPUT: + case BUFFER_TYPE_INT_MEMORY: + sync_int_to_s7(s7_buf, s7_size, type, start_buffer); + break; + + case BUFFER_TYPE_DINT_INPUT: + case BUFFER_TYPE_DINT_OUTPUT: + case BUFFER_TYPE_DINT_MEMORY: + sync_dint_to_s7(s7_buf, s7_size, type, start_buffer); + break; + + case BUFFER_TYPE_LINT_INPUT: + case BUFFER_TYPE_LINT_OUTPUT: + case BUFFER_TYPE_LINT_MEMORY: + sync_lint_to_s7(s7_buf, s7_size, type, start_buffer); + break; + + default: + break; + } +} + +/** + * @brief Dispatch sync from S7 to OpenPLC based on buffer type + */ +static void sync_s7_to_buffer(uint8_t *s7_buf, int s7_size, s7comm_buffer_type_t type, int start_buffer) +{ + switch (type) { + case BUFFER_TYPE_BOOL_OUTPUT: + case BUFFER_TYPE_BOOL_MEMORY: + sync_s7_to_bool(s7_buf, s7_size, type, start_buffer); + break; + + case BUFFER_TYPE_INT_OUTPUT: + case BUFFER_TYPE_INT_MEMORY: + sync_s7_to_int(s7_buf, s7_size, type, start_buffer); + break; + + case BUFFER_TYPE_DINT_OUTPUT: + case BUFFER_TYPE_DINT_MEMORY: + sync_s7_to_dint(s7_buf, s7_size, type, start_buffer); + break; + + case BUFFER_TYPE_LINT_OUTPUT: + case BUFFER_TYPE_LINT_MEMORY: + sync_s7_to_lint(s7_buf, s7_size, type, start_buffer); + break; + + default: + /* Don't write to input buffers */ + break; + } +} + +/** + * @brief Sync OpenPLC buffers to S7 data areas + * + * Copies current OpenPLC input/output/memory values to S7 buffers + * so S7 clients can read them. + */ +static void sync_openplc_to_s7(void) +{ + /* Sync system areas */ + if (g_config.pe_area.enabled && g_pe_area != NULL) { + sync_buffer_to_s7(g_pe_area, g_config.pe_area.size_bytes, + g_config.pe_area.mapping.type, + g_config.pe_area.mapping.start_buffer); + } + + if (g_config.pa_area.enabled && g_pa_area != NULL) { + sync_buffer_to_s7(g_pa_area, g_config.pa_area.size_bytes, + g_config.pa_area.mapping.type, + g_config.pa_area.mapping.start_buffer); + } + + if (g_config.mk_area.enabled && g_mk_area != NULL) { + sync_buffer_to_s7(g_mk_area, g_config.mk_area.size_bytes, + g_config.mk_area.mapping.type, + g_config.mk_area.mapping.start_buffer); + } + + /* Sync data blocks */ + for (int i = 0; i < g_num_db_runtime; i++) { + s7comm_db_runtime_t *db = &g_db_runtime[i]; + sync_buffer_to_s7(db->buffer, db->size_bytes, db->type, db->start_buffer); + } +} + +/** + * @brief Sync S7 data areas to OpenPLC buffers + * + * Copies values written by S7 clients back to OpenPLC output/memory buffers. + */ +static void sync_s7_to_openplc(void) +{ + /* Sync system areas (only outputs and markers) */ + if (g_config.pa_area.enabled && g_pa_area != NULL) { + sync_s7_to_buffer(g_pa_area, g_config.pa_area.size_bytes, + g_config.pa_area.mapping.type, + g_config.pa_area.mapping.start_buffer); + } + + if (g_config.mk_area.enabled && g_mk_area != NULL) { + sync_s7_to_buffer(g_mk_area, g_config.mk_area.size_bytes, + g_config.mk_area.mapping.type, + g_config.mk_area.mapping.start_buffer); + } + + /* Sync data blocks (only outputs and memory) */ + for (int i = 0; i < g_num_db_runtime; i++) { + s7comm_db_runtime_t *db = &g_db_runtime[i]; + sync_s7_to_buffer(db->buffer, db->size_bytes, db->type, db->start_buffer); + } +} From d5819c16724bad58fede3866cb2aa2038dfcd15c Mon Sep 17 00:00:00 2001 From: Thiago Alves Date: Mon, 12 Jan 2026 20:22:59 -0500 Subject: [PATCH 05/12] Implement Phase 3/4: Double-buffering for S7Comm plugin Refactor buffer synchronization to use double-buffering pattern: - Add shadow buffers for all S7 areas (system areas + data blocks) - Add S7 mutex (pthread_mutex_t) to protect S7 buffers during sync - Implement three-step sync in cycle_end: 1. Lock mutex, copy S7 -> shadow (capture client writes) 2. Sync shadow <-> OpenPLC (slow part, no S7 mutex held) 3. Lock mutex, copy shadow -> S7 (publish new values) - Remove sync from cycle_start (now no-op) This allows S7 clients to read/write asynchronously from the main PLC cycle with minimal mutex contention. Co-Authored-By: Claude Opus 4.5 --- .../plugins/native/s7comm/s7comm_plugin.cpp | 578 +++++++++++------- 1 file changed, 355 insertions(+), 223 deletions(-) diff --git a/core/src/drivers/plugins/native/s7comm/s7comm_plugin.cpp b/core/src/drivers/plugins/native/s7comm/s7comm_plugin.cpp index 34c5a239..571b877a 100644 --- a/core/src/drivers/plugins/native/s7comm/s7comm_plugin.cpp +++ b/core/src/drivers/plugins/native/s7comm/s7comm_plugin.cpp @@ -5,12 +5,15 @@ * 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. * - * Phase 2 Implementation: - * - JSON configuration parsing with cJSON - * - Dynamic data block allocation based on config - * - Configurable server parameters - * - PLC identity configuration - * - All areas mapped from configuration file + * Phase 3/4 Implementation - Double Buffering: + * - S7 buffers: What Snap7 clients read/write (accessed asynchronously) + * - Shadow buffers: Used for sync with OpenPLC + * - S7 mutex: Protects S7 buffers during brief memcpy at cycle_end + * - Sync only at cycle_end, minimizing mutex contention + * + * Data flow: + * - S7 clients read/write S7 buffers asynchronously (lock-free most of the time) + * - At cycle_end: brief lock -> memcpy S7<->shadow -> unlock -> sync shadow<->OpenPLC */ #include @@ -39,7 +42,7 @@ extern "C" { /* * ============================================================================= - * Data Block Runtime Structure + * Data Block Runtime Structure (with double-buffering) * ============================================================================= */ typedef struct { @@ -48,9 +51,24 @@ typedef struct { int start_buffer; /* Starting buffer index */ int size_bytes; /* Size in bytes */ bool bit_addressing; /* Bit-level access enabled */ - uint8_t *buffer; /* Allocated data buffer */ + uint8_t *s7_buffer; /* S7 buffer (registered with Snap7) */ + uint8_t *shadow_buffer; /* Shadow buffer for sync with OpenPLC */ } s7comm_db_runtime_t; +/* + * ============================================================================= + * System Area Runtime Structure (with double-buffering) + * ============================================================================= + */ +typedef struct { + bool enabled; + int size_bytes; + s7comm_buffer_type_t type; + int start_buffer; + uint8_t *s7_buffer; /* S7 buffer (registered with Snap7) */ + uint8_t *shadow_buffer; /* Shadow buffer for sync with OpenPLC */ +} s7comm_area_runtime_t; + /* * ============================================================================= * Plugin State @@ -66,14 +84,17 @@ static bool g_config_loaded = false; /* Snap7 server handle (S7Object is uintptr_t, use 0 for null) */ static S7Object g_server = 0; +/* S7 buffer mutex - protects S7 buffers during sync */ +static pthread_mutex_t g_s7_mutex = PTHREAD_MUTEX_INITIALIZER; + /* 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 buffers (dynamically allocated based on config) */ -static uint8_t *g_pe_area = NULL; -static uint8_t *g_pa_area = NULL; -static uint8_t *g_mk_area = NULL; +/* System area runtime (with double-buffering) */ +static s7comm_area_runtime_t g_pe_runtime; +static s7comm_area_runtime_t g_pa_runtime; +static s7comm_area_runtime_t g_mk_runtime; /* * ============================================================================= @@ -81,10 +102,10 @@ static uint8_t *g_mk_area = NULL; * ============================================================================= */ static void s7comm_event_callback(void *usrPtr, PSrvEvent PEvent, int Size); -static void sync_openplc_to_s7(void); -static void sync_s7_to_openplc(void); -static int allocate_data_blocks(void); -static void free_data_blocks(void); +static void sync_shadow_to_openplc(void); +static void sync_openplc_to_shadow(void); +static int allocate_buffers(void); +static void free_buffers(void); static int register_all_areas(void); /* @@ -125,38 +146,79 @@ static inline uint64_t swap64(uint64_t val) */ /** - * @brief Allocate data block buffers based on configuration + * @brief Allocate a system area with double-buffering */ -static int allocate_data_blocks(void) +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; + } + + /* Allocate shadow buffer (for sync with OpenPLC) */ + area->shadow_buffer = (uint8_t *)calloc(1, config->size_bytes); + if (area->shadow_buffer == NULL) { + free(area->s7_buffer); + area->s7_buffer = NULL; + return -1; + } + + return 0; +} + +/** + * @brief Free a system area's buffers + */ +static void free_area(s7comm_area_runtime_t *area) +{ + if (area->s7_buffer != NULL) { + free(area->s7_buffer); + area->s7_buffer = NULL; + } + if (area->shadow_buffer != NULL) { + free(area->shadow_buffer); + area->shadow_buffer = NULL; + } + area->enabled = false; +} + +/** + * @brief Allocate all buffers (S7 + shadow) based on configuration + */ +static int allocate_buffers(void) { g_num_db_runtime = 0; - /* Allocate system areas */ - if (g_config.pe_area.enabled && g_config.pe_area.size_bytes > 0) { - g_pe_area = (uint8_t *)calloc(1, g_config.pe_area.size_bytes); - if (g_pe_area == NULL) { - plugin_logger_error(&g_logger, "Failed to allocate PE area buffer"); - return -1; - } + /* Allocate system areas with double-buffering */ + if (allocate_area(&g_pe_runtime, &g_config.pe_area) != 0) { + plugin_logger_error(&g_logger, "Failed to allocate PE area buffers"); + return -1; } - if (g_config.pa_area.enabled && g_config.pa_area.size_bytes > 0) { - g_pa_area = (uint8_t *)calloc(1, g_config.pa_area.size_bytes); - if (g_pa_area == NULL) { - plugin_logger_error(&g_logger, "Failed to allocate PA 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 buffers"); + return -1; } - if (g_config.mk_area.enabled && g_config.mk_area.size_bytes > 0) { - g_mk_area = (uint8_t *)calloc(1, g_config.mk_area.size_bytes); - if (g_mk_area == NULL) { - plugin_logger_error(&g_logger, "Failed to allocate MK 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 buffers"); + return -1; } - /* Allocate data blocks */ + /* Allocate data blocks with double-buffering */ for (int i = 0; i < g_config.num_data_blocks; i++) { const s7comm_data_block_t *db_cfg = &g_config.data_blocks[i]; @@ -173,90 +235,94 @@ static int allocate_data_blocks(void) db_rt->size_bytes = db_cfg->size_bytes; db_rt->bit_addressing = db_cfg->mapping.bit_addressing; - db_rt->buffer = (uint8_t *)calloc(1, db_cfg->size_bytes); - if (db_rt->buffer == NULL) { - plugin_logger_error(&g_logger, "Failed to allocate DB%d buffer", db_cfg->db_number); + /* 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; + } + + /* Allocate shadow buffer */ + db_rt->shadow_buffer = (uint8_t *)calloc(1, db_cfg->size_bytes); + if (db_rt->shadow_buffer == NULL) { + plugin_logger_error(&g_logger, "Failed to allocate DB%d shadow buffer", db_cfg->db_number); + free(db_rt->s7_buffer); + db_rt->s7_buffer = NULL; return -1; } g_num_db_runtime++; - plugin_logger_debug(&g_logger, "Allocated DB%d: %d bytes, type=%s, start=%d", + plugin_logger_debug(&g_logger, "Allocated DB%d: %d bytes (double-buffered), type=%s", db_cfg->db_number, db_cfg->size_bytes, - s7comm_buffer_type_name(db_cfg->mapping.type), - db_cfg->mapping.start_buffer); + s7comm_buffer_type_name(db_cfg->mapping.type)); } return 0; } /** - * @brief Free all allocated data block buffers + * @brief Free all allocated buffers */ -static void free_data_blocks(void) +static void free_buffers(void) { /* Free system areas */ - if (g_pe_area != NULL) { - free(g_pe_area); - g_pe_area = NULL; - } - if (g_pa_area != NULL) { - free(g_pa_area); - g_pa_area = NULL; - } - if (g_mk_area != NULL) { - free(g_mk_area); - g_mk_area = NULL; - } + 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].buffer != NULL) { - free(g_db_runtime[i].buffer); - g_db_runtime[i].buffer = NULL; + if (g_db_runtime[i].s7_buffer != NULL) { + free(g_db_runtime[i].s7_buffer); + g_db_runtime[i].s7_buffer = NULL; + } + if (g_db_runtime[i].shadow_buffer != NULL) { + free(g_db_runtime[i].shadow_buffer); + g_db_runtime[i].shadow_buffer = NULL; } } g_num_db_runtime = 0; } /** - * @brief Register all areas with the Snap7 server + * @brief Register all S7 areas with the Snap7 server */ static int register_all_areas(void) { int result; - /* Register system areas */ - if (g_config.pe_area.enabled && g_pe_area != NULL) { - result = Srv_RegisterArea(g_server, srvAreaPE, 0, g_pe_area, g_config.pe_area.size_bytes); + /* 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_config.pe_area.size_bytes); + plugin_logger_debug(&g_logger, "Registered PE area: %d bytes", g_pe_runtime.size_bytes); } } - if (g_config.pa_area.enabled && g_pa_area != NULL) { - result = Srv_RegisterArea(g_server, srvAreaPA, 0, g_pa_area, g_config.pa_area.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_config.pa_area.size_bytes); + plugin_logger_debug(&g_logger, "Registered PA area: %d bytes", g_pa_runtime.size_bytes); } } - if (g_config.mk_area.enabled && g_mk_area != NULL) { - result = Srv_RegisterArea(g_server, srvAreaMK, 0, g_mk_area, g_config.mk_area.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_config.mk_area.size_bytes); + plugin_logger_debug(&g_logger, "Registered MK area: %d bytes", g_mk_runtime.size_bytes); } } - /* Register data blocks */ + /* 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->buffer, db->size_bytes); + 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 { @@ -280,7 +346,7 @@ 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..."); + plugin_logger_info(&g_logger, "Initializing S7Comm plugin (double-buffered)..."); if (!args) { plugin_logger_error(&g_logger, "init args is NULL"); @@ -295,6 +361,9 @@ extern "C" int init(void *args) plugin_logger_info(&g_logger, "Buffer size: %d", g_runtime_args.buffer_size); + /* Initialize S7 buffer mutex */ + pthread_mutex_init(&g_s7_mutex, NULL); + /* Parse configuration file */ const char *config_path = g_runtime_args.plugin_specific_config_file_path; if (config_path == NULL || config_path[0] == '\0') { @@ -326,10 +395,10 @@ extern "C" int init(void *args) 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 data block buffers */ - if (allocate_data_blocks() != 0) { - plugin_logger_error(&g_logger, "Failed to allocate data block buffers"); - free_data_blocks(); + /* Allocate all buffers (S7 + shadow for double-buffering) */ + if (allocate_buffers() != 0) { + plugin_logger_error(&g_logger, "Failed to allocate buffers"); + free_buffers(); return -1; } @@ -337,7 +406,7 @@ extern "C" int init(void *args) g_server = Srv_Create(); if (g_server == 0) { plugin_logger_error(&g_logger, "Failed to create Snap7 server"); - free_data_blocks(); + free_buffers(); return -1; } @@ -375,27 +444,30 @@ extern "C" int init(void *args) /* Set event callback for logging */ Srv_SetEventsCallback(g_server, s7comm_event_callback, NULL); - /* Register all areas with the server */ + /* Register all S7 areas with the server */ register_all_areas(); g_initialized = true; - plugin_logger_info(&g_logger, "S7Comm plugin initialized successfully"); + plugin_logger_info(&g_logger, "S7Comm plugin initialized successfully (double-buffered mode)"); /* Log registered areas summary */ - if (g_config.pe_area.enabled) { - plugin_logger_info(&g_logger, "PE area: %d bytes -> %s", - g_config.pe_area.size_bytes, - s7comm_buffer_type_name(g_config.pe_area.mapping.type)); + 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_config.pa_area.enabled) { - plugin_logger_info(&g_logger, "PA area: %d bytes -> %s", - g_config.pa_area.size_bytes, - s7comm_buffer_type_name(g_config.pa_area.mapping.type)); + 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_config.mk_area.enabled) { - plugin_logger_info(&g_logger, "MK area: %d bytes -> %s", - g_config.mk_area.size_bytes, - s7comm_buffer_type_name(g_config.mk_area.mapping.type)); + 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]", @@ -485,7 +557,8 @@ extern "C" void cleanup(void) g_server = 0; } - free_data_blocks(); + free_buffers(); + pthread_mutex_destroy(&g_s7_mutex); g_initialized = false; g_config_loaded = false; @@ -495,24 +568,28 @@ extern "C" 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 by PLC cycle manager. + * With double-buffering, we don't sync at cycle_start. + * S7 clients read from their own buffer asynchronously. + * All sync happens at cycle_end. */ extern "C" void cycle_start(void) { - if (!g_initialized || !g_running || !g_config.enabled) { - return; - } - - /* Sync OpenPLC inputs to S7 buffers */ - sync_openplc_to_s7(); + /* No-op with double-buffering - S7 reads asynchronously from its buffer */ } /** * @brief Called at the end of each PLC scan cycle * - * Synchronizes S7 data areas to OpenPLC output buffers. - * Called with buffer mutex already held by PLC cycle manager. + * Double-buffer sync strategy: + * 1. Lock S7 mutex (briefly) + * 2. Copy S7 buffers -> shadow buffers (capture S7 client writes) + * 3. Unlock S7 mutex + * 4. Sync shadow buffers <-> OpenPLC buffers (OpenPLC mutex already held) + * 5. Lock S7 mutex (briefly) + * 6. Copy shadow buffers -> S7 buffers (publish new values to S7 clients) + * 7. Unlock S7 mutex + * + * This minimizes S7 mutex hold time - only held during memcpy operations. */ extern "C" void cycle_end(void) { @@ -520,8 +597,64 @@ extern "C" void cycle_end(void) return; } - /* Sync S7 buffers to OpenPLC outputs */ - sync_s7_to_openplc(); + /* + * Step 1: Lock S7 mutex and copy S7 -> shadow (capture S7 writes) + * This captures what S7 clients have written to output/memory areas + */ + pthread_mutex_lock(&g_s7_mutex); + + /* Copy system areas S7 -> shadow */ + if (g_pa_runtime.enabled) { + memcpy(g_pa_runtime.shadow_buffer, g_pa_runtime.s7_buffer, g_pa_runtime.size_bytes); + } + if (g_mk_runtime.enabled) { + memcpy(g_mk_runtime.shadow_buffer, g_mk_runtime.s7_buffer, g_mk_runtime.size_bytes); + } + + /* Copy data blocks S7 -> shadow (for output/memory types) */ + for (int i = 0; i < g_num_db_runtime; i++) { + s7comm_db_runtime_t *db = &g_db_runtime[i]; + /* Copy all DBs - we'll filter by type during OpenPLC sync */ + memcpy(db->shadow_buffer, db->s7_buffer, db->size_bytes); + } + + pthread_mutex_unlock(&g_s7_mutex); + + /* + * Step 2: Sync shadow <-> OpenPLC (mutex already held by PLC cycle manager) + * This is the "slow" part that accesses OpenPLC buffers + */ + + /* Shadow -> OpenPLC: Apply S7 client writes to PLC outputs/memory */ + sync_shadow_to_openplc(); + + /* OpenPLC -> Shadow: Get latest PLC values for S7 clients to read */ + sync_openplc_to_shadow(); + + /* + * Step 3: Lock S7 mutex and copy shadow -> S7 (publish to S7 clients) + * This makes new input values visible to S7 clients + */ + pthread_mutex_lock(&g_s7_mutex); + + /* Copy system areas shadow -> S7 */ + if (g_pe_runtime.enabled) { + memcpy(g_pe_runtime.s7_buffer, g_pe_runtime.shadow_buffer, g_pe_runtime.size_bytes); + } + if (g_pa_runtime.enabled) { + memcpy(g_pa_runtime.s7_buffer, g_pa_runtime.shadow_buffer, g_pa_runtime.size_bytes); + } + if (g_mk_runtime.enabled) { + memcpy(g_mk_runtime.s7_buffer, g_mk_runtime.shadow_buffer, g_mk_runtime.size_bytes); + } + + /* Copy data blocks shadow -> S7 */ + for (int i = 0; i < g_num_db_runtime; i++) { + s7comm_db_runtime_t *db = &g_db_runtime[i]; + memcpy(db->s7_buffer, db->shadow_buffer, db->size_bytes); + } + + pthread_mutex_unlock(&g_s7_mutex); } /* @@ -584,21 +717,18 @@ static void s7comm_event_callback(void *usrPtr, PSrvEvent PEvent, int Size) /* * ============================================================================= - * Buffer Synchronization Functions + * Buffer Synchronization Functions (Shadow <-> OpenPLC) * ============================================================================= */ /** - * @brief Sync a bool buffer to S7 byte array + * @brief Sync a bool buffer from shadow to OpenPLC */ -static void sync_bool_to_s7(uint8_t *s7_buf, int s7_size, s7comm_buffer_type_t type, int start_buffer) +static void sync_shadow_bool_to_openplc(uint8_t *shadow, 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; @@ -606,33 +736,35 @@ static void sync_bool_to_s7(uint8_t *s7_buf, int s7_size, s7comm_buffer_type_t t buffer = g_runtime_args.bool_memory; break; default: - return; + return; /* Don't write to inputs */ } int max_bytes = g_runtime_args.buffer_size - start_buffer; - if (max_bytes > s7_size) max_bytes = s7_size; + if (max_bytes > size) max_bytes = size; for (int byte_idx = 0; byte_idx < max_bytes; byte_idx++) { - uint8_t byte_val = 0; + uint8_t byte_val = shadow[byte_idx]; 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); + if (ptr != NULL) { + *ptr = (byte_val >> bit_idx) & 0x01; } } - s7_buf[byte_idx] = byte_val; } } /** - * @brief Sync S7 byte array to bool buffer + * @brief Sync OpenPLC bool buffer to shadow */ -static void sync_s7_to_bool(uint8_t *s7_buf, int s7_size, s7comm_buffer_type_t type, int start_buffer) +static void sync_openplc_bool_to_shadow(uint8_t *shadow, 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; @@ -640,35 +772,33 @@ static void sync_s7_to_bool(uint8_t *s7_buf, int s7_size, s7comm_buffer_type_t t buffer = g_runtime_args.bool_memory; break; default: - return; /* Don't write to inputs */ + return; } int max_bytes = g_runtime_args.buffer_size - start_buffer; - if (max_bytes > s7_size) max_bytes = s7_size; + if (max_bytes > size) max_bytes = size; for (int byte_idx = 0; byte_idx < max_bytes; byte_idx++) { - uint8_t byte_val = s7_buf[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 >> bit_idx) & 0x01; + if (ptr != NULL && *ptr) { + byte_val |= (1 << bit_idx); } } + shadow[byte_idx] = byte_val; } } /** - * @brief Sync int buffer to S7 word array (with big-endian conversion) + * @brief Sync shadow int buffer to OpenPLC (with endian conversion) */ -static void sync_int_to_s7(uint8_t *s7_buf, int s7_size, s7comm_buffer_type_t type, int start_buffer) +static void sync_shadow_int_to_openplc(uint8_t *shadow, 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; @@ -676,30 +806,33 @@ static void sync_int_to_s7(uint8_t *s7_buf, int s7_size, s7comm_buffer_type_t ty buffer = g_runtime_args.int_memory; break; default: - return; + return; /* Don't write to inputs */ } - uint16_t *s7_words = (uint16_t *)s7_buf; - int num_words = s7_size / 2; + uint16_t *shadow_words = (uint16_t *)shadow; + 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); + *ptr = swap16(shadow_words[i]); } } } /** - * @brief Sync S7 word array to int buffer (with big-endian conversion) + * @brief Sync OpenPLC int buffer to shadow (with endian conversion) */ -static void sync_s7_to_int(uint8_t *s7_buf, int s7_size, s7comm_buffer_type_t type, int start_buffer) +static void sync_openplc_int_to_shadow(uint8_t *shadow, 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; @@ -707,33 +840,30 @@ static void sync_s7_to_int(uint8_t *s7_buf, int s7_size, s7comm_buffer_type_t ty buffer = g_runtime_args.int_memory; break; default: - return; /* Don't write to inputs */ + return; } - uint16_t *s7_words = (uint16_t *)s7_buf; - int num_words = s7_size / 2; + uint16_t *shadow_words = (uint16_t *)shadow; + 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) { - *ptr = swap16(s7_words[i]); + shadow_words[i] = swap16(*ptr); } } } /** - * @brief Sync dint buffer to S7 dword array (with big-endian conversion) + * @brief Sync shadow dint buffer to OpenPLC (with endian conversion) */ -static void sync_dint_to_s7(uint8_t *s7_buf, int s7_size, s7comm_buffer_type_t type, int start_buffer) +static void sync_shadow_dint_to_openplc(uint8_t *shadow, 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; @@ -741,30 +871,33 @@ static void sync_dint_to_s7(uint8_t *s7_buf, int s7_size, s7comm_buffer_type_t t buffer = g_runtime_args.dint_memory; break; default: - return; + return; /* Don't write to inputs */ } - uint32_t *s7_dwords = (uint32_t *)s7_buf; - int num_dwords = s7_size / 4; + uint32_t *shadow_dwords = (uint32_t *)shadow; + 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); + *ptr = swap32(shadow_dwords[i]); } } } /** - * @brief Sync S7 dword array to dint buffer (with big-endian conversion) + * @brief Sync OpenPLC dint buffer to shadow (with endian conversion) */ -static void sync_s7_to_dint(uint8_t *s7_buf, int s7_size, s7comm_buffer_type_t type, int start_buffer) +static void sync_openplc_dint_to_shadow(uint8_t *shadow, 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; @@ -772,33 +905,30 @@ static void sync_s7_to_dint(uint8_t *s7_buf, int s7_size, s7comm_buffer_type_t t buffer = g_runtime_args.dint_memory; break; default: - return; /* Don't write to inputs */ + return; } - uint32_t *s7_dwords = (uint32_t *)s7_buf; - int num_dwords = s7_size / 4; + uint32_t *shadow_dwords = (uint32_t *)shadow; + 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) { - *ptr = swap32(s7_dwords[i]); + shadow_dwords[i] = swap32(*ptr); } } } /** - * @brief Sync lint buffer to S7 lword array (with big-endian conversion) + * @brief Sync shadow lint buffer to OpenPLC (with endian conversion) */ -static void sync_lint_to_s7(uint8_t *s7_buf, int s7_size, s7comm_buffer_type_t type, int start_buffer) +static void sync_shadow_lint_to_openplc(uint8_t *shadow, 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; @@ -806,30 +936,33 @@ static void sync_lint_to_s7(uint8_t *s7_buf, int s7_size, s7comm_buffer_type_t t buffer = g_runtime_args.lint_memory; break; default: - return; + return; /* Don't write to inputs */ } - uint64_t *s7_lwords = (uint64_t *)s7_buf; - int num_lwords = s7_size / 8; + uint64_t *shadow_lwords = (uint64_t *)shadow; + 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); + *ptr = swap64(shadow_lwords[i]); } } } /** - * @brief Sync S7 lword array to lint buffer (with big-endian conversion) + * @brief Sync OpenPLC lint buffer to shadow (with endian conversion) */ -static void sync_s7_to_lint(uint8_t *s7_buf, int s7_size, s7comm_buffer_type_t type, int start_buffer) +static void sync_openplc_lint_to_shadow(uint8_t *shadow, 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; @@ -837,146 +970,145 @@ static void sync_s7_to_lint(uint8_t *s7_buf, int s7_size, s7comm_buffer_type_t t buffer = g_runtime_args.lint_memory; break; default: - return; /* Don't write to inputs */ + return; } - uint64_t *s7_lwords = (uint64_t *)s7_buf; - int num_lwords = s7_size / 8; + uint64_t *shadow_lwords = (uint64_t *)shadow; + 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) { - *ptr = swap64(s7_lwords[i]); + shadow_lwords[i] = swap64(*ptr); } } } /** - * @brief Dispatch sync from OpenPLC to S7 based on buffer type + * @brief Dispatch sync from shadow to OpenPLC based on buffer type */ -static void sync_buffer_to_s7(uint8_t *s7_buf, int s7_size, s7comm_buffer_type_t type, int start_buffer) +static void sync_shadow_to_openplc_by_type(uint8_t *shadow, 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: - sync_bool_to_s7(s7_buf, s7_size, type, start_buffer); + sync_shadow_bool_to_openplc(shadow, size, type, start_buffer); break; - case BUFFER_TYPE_INT_INPUT: case BUFFER_TYPE_INT_OUTPUT: case BUFFER_TYPE_INT_MEMORY: - sync_int_to_s7(s7_buf, s7_size, type, start_buffer); + sync_shadow_int_to_openplc(shadow, size, type, start_buffer); break; - case BUFFER_TYPE_DINT_INPUT: case BUFFER_TYPE_DINT_OUTPUT: case BUFFER_TYPE_DINT_MEMORY: - sync_dint_to_s7(s7_buf, s7_size, type, start_buffer); + sync_shadow_dint_to_openplc(shadow, size, type, start_buffer); break; - case BUFFER_TYPE_LINT_INPUT: case BUFFER_TYPE_LINT_OUTPUT: case BUFFER_TYPE_LINT_MEMORY: - sync_lint_to_s7(s7_buf, s7_size, type, start_buffer); + sync_shadow_lint_to_openplc(shadow, size, type, start_buffer); break; default: + /* Input types are not written to OpenPLC from S7 */ break; } } /** - * @brief Dispatch sync from S7 to OpenPLC based on buffer type + * @brief Dispatch sync from OpenPLC to shadow based on buffer type */ -static void sync_s7_to_buffer(uint8_t *s7_buf, int s7_size, s7comm_buffer_type_t type, int start_buffer) +static void sync_openplc_to_shadow_by_type(uint8_t *shadow, 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: - sync_s7_to_bool(s7_buf, s7_size, type, start_buffer); + sync_openplc_bool_to_shadow(shadow, size, type, start_buffer); break; + case BUFFER_TYPE_INT_INPUT: case BUFFER_TYPE_INT_OUTPUT: case BUFFER_TYPE_INT_MEMORY: - sync_s7_to_int(s7_buf, s7_size, type, start_buffer); + sync_openplc_int_to_shadow(shadow, size, type, start_buffer); break; + case BUFFER_TYPE_DINT_INPUT: case BUFFER_TYPE_DINT_OUTPUT: case BUFFER_TYPE_DINT_MEMORY: - sync_s7_to_dint(s7_buf, s7_size, type, start_buffer); + sync_openplc_dint_to_shadow(shadow, size, type, start_buffer); break; + case BUFFER_TYPE_LINT_INPUT: case BUFFER_TYPE_LINT_OUTPUT: case BUFFER_TYPE_LINT_MEMORY: - sync_s7_to_lint(s7_buf, s7_size, type, start_buffer); + sync_openplc_lint_to_shadow(shadow, size, type, start_buffer); break; default: - /* Don't write to input buffers */ break; } } /** - * @brief Sync OpenPLC buffers to S7 data areas + * @brief Sync shadow buffers to OpenPLC * - * Copies current OpenPLC input/output/memory values to S7 buffers - * so S7 clients can read them. + * Applies S7 client writes (outputs/memory) to OpenPLC buffers. + * Only output and memory types are written - inputs are read-only from S7 perspective. */ -static void sync_openplc_to_s7(void) +static void sync_shadow_to_openplc(void) { - /* Sync system areas */ - if (g_config.pe_area.enabled && g_pe_area != NULL) { - sync_buffer_to_s7(g_pe_area, g_config.pe_area.size_bytes, - g_config.pe_area.mapping.type, - g_config.pe_area.mapping.start_buffer); + /* Sync PA area (outputs) shadow -> OpenPLC */ + if (g_pa_runtime.enabled) { + sync_shadow_to_openplc_by_type(g_pa_runtime.shadow_buffer, g_pa_runtime.size_bytes, + g_pa_runtime.type, g_pa_runtime.start_buffer); } - if (g_config.pa_area.enabled && g_pa_area != NULL) { - sync_buffer_to_s7(g_pa_area, g_config.pa_area.size_bytes, - g_config.pa_area.mapping.type, - g_config.pa_area.mapping.start_buffer); + /* Sync MK area (markers/memory) shadow -> OpenPLC */ + if (g_mk_runtime.enabled) { + sync_shadow_to_openplc_by_type(g_mk_runtime.shadow_buffer, g_mk_runtime.size_bytes, + g_mk_runtime.type, g_mk_runtime.start_buffer); } - if (g_config.mk_area.enabled && g_mk_area != NULL) { - sync_buffer_to_s7(g_mk_area, g_config.mk_area.size_bytes, - g_config.mk_area.mapping.type, - g_config.mk_area.mapping.start_buffer); - } - - /* Sync data blocks */ + /* Sync data blocks shadow -> OpenPLC (only output/memory types) */ for (int i = 0; i < g_num_db_runtime; i++) { s7comm_db_runtime_t *db = &g_db_runtime[i]; - sync_buffer_to_s7(db->buffer, db->size_bytes, db->type, db->start_buffer); + sync_shadow_to_openplc_by_type(db->shadow_buffer, db->size_bytes, db->type, db->start_buffer); } } /** - * @brief Sync S7 data areas to OpenPLC buffers + * @brief Sync OpenPLC buffers to shadow * - * Copies values written by S7 clients back to OpenPLC output/memory buffers. + * Copies current OpenPLC values to shadow buffers so S7 clients can read them. + * All types are synced - inputs, outputs, and memory. */ -static void sync_s7_to_openplc(void) +static void sync_openplc_to_shadow(void) { - /* Sync system areas (only outputs and markers) */ - if (g_config.pa_area.enabled && g_pa_area != NULL) { - sync_s7_to_buffer(g_pa_area, g_config.pa_area.size_bytes, - g_config.pa_area.mapping.type, - g_config.pa_area.mapping.start_buffer); + /* Sync PE area (inputs) OpenPLC -> shadow */ + if (g_pe_runtime.enabled) { + sync_openplc_to_shadow_by_type(g_pe_runtime.shadow_buffer, g_pe_runtime.size_bytes, + g_pe_runtime.type, g_pe_runtime.start_buffer); + } + + /* Sync PA area (outputs) OpenPLC -> shadow */ + if (g_pa_runtime.enabled) { + sync_openplc_to_shadow_by_type(g_pa_runtime.shadow_buffer, g_pa_runtime.size_bytes, + g_pa_runtime.type, g_pa_runtime.start_buffer); } - if (g_config.mk_area.enabled && g_mk_area != NULL) { - sync_s7_to_buffer(g_mk_area, g_config.mk_area.size_bytes, - g_config.mk_area.mapping.type, - g_config.mk_area.mapping.start_buffer); + /* Sync MK area (markers) OpenPLC -> shadow */ + if (g_mk_runtime.enabled) { + sync_openplc_to_shadow_by_type(g_mk_runtime.shadow_buffer, g_mk_runtime.size_bytes, + g_mk_runtime.type, g_mk_runtime.start_buffer); } - /* Sync data blocks (only outputs and memory) */ + /* Sync all data blocks OpenPLC -> shadow */ for (int i = 0; i < g_num_db_runtime; i++) { s7comm_db_runtime_t *db = &g_db_runtime[i]; - sync_s7_to_buffer(db->buffer, db->size_bytes, db->type, db->start_buffer); + sync_openplc_to_shadow_by_type(db->shadow_buffer, db->size_bytes, db->type, db->start_buffer); } } From 563bcf86ad9beb7291643afdeebcf532d46e548a Mon Sep 17 00:00:00 2001 From: Thiago Alves Date: Mon, 12 Jan 2026 20:25:41 -0500 Subject: [PATCH 06/12] Optimize: Skip buffer sync when no S7 clients connected Use Srv_GetStatus to check client count at the start of cycle_end. If no clients are connected, return early without any mutex operations or buffer copies, reducing overhead when S7 server is idle. Co-Authored-By: Claude Opus 4.5 --- .../plugins/native/s7comm/s7comm_plugin.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/core/src/drivers/plugins/native/s7comm/s7comm_plugin.cpp b/core/src/drivers/plugins/native/s7comm/s7comm_plugin.cpp index 571b877a..d9b6fdd6 100644 --- a/core/src/drivers/plugins/native/s7comm/s7comm_plugin.cpp +++ b/core/src/drivers/plugins/native/s7comm/s7comm_plugin.cpp @@ -580,7 +580,9 @@ extern "C" void cycle_start(void) /** * @brief Called at the end of each PLC scan cycle * - * Double-buffer sync strategy: + * Optimization: If no clients are connected, skip sync entirely. + * + * Double-buffer sync strategy (when clients connected): * 1. Lock S7 mutex (briefly) * 2. Copy S7 buffers -> shadow buffers (capture S7 client writes) * 3. Unlock S7 mutex @@ -597,6 +599,17 @@ extern "C" void cycle_end(void) return; } + /* Check if any clients are connected - skip sync if none */ + int server_status = 0; + int cpu_status = 0; + int clients_count = 0; + Srv_GetStatus(g_server, &server_status, &cpu_status, &clients_count); + + if (clients_count == 0) { + /* No clients connected - no need to sync buffers */ + return; + } + /* * Step 1: Lock S7 mutex and copy S7 -> shadow (capture S7 writes) * This captures what S7 clients have written to output/memory areas From 5401b53e861b1d0a79a87bb975d76051f200b444 Mon Sep 17 00:00:00 2001 From: Thiago Alves Date: Mon, 12 Jan 2026 20:33:10 -0500 Subject: [PATCH 07/12] Add JSON schema reference and update S7Comm documentation - Add JSON_SCHEMA.md with complete configuration reference for Editor - Document all fields, types, constraints, and default values - Add Editor UI recommendations and validation rules - Update USER_GUIDE.md with double-buffering architecture section - Add data flow diagram and related documentation links Co-Authored-By: Claude Opus 4.5 --- .../plugins/native/s7comm/docs/JSON_SCHEMA.md | 460 ++++++++++++++++++ .../plugins/native/s7comm/docs/USER_GUIDE.md | 40 +- 2 files changed, 496 insertions(+), 4 deletions(-) create mode 100644 core/src/drivers/plugins/native/s7comm/docs/JSON_SCHEMA.md 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 index e3ff87aa..05e08cf7 100644 --- a/core/src/drivers/plugins/native/s7comm/docs/USER_GUIDE.md +++ b/core/src/drivers/plugins/native/s7comm/docs/USER_GUIDE.md @@ -11,7 +11,9 @@ The S7Comm plugin enables OpenPLC Runtime v4 to communicate using the Siemens S7 - Support for all OpenPLC buffer types (BOOL, BYTE, UINT, UDINT, ULINT) - Multiple concurrent client connections - Configurable PLC identity for S7 client compatibility -- Thread-safe operation synchronized with PLC scan cycle +- 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 @@ -510,13 +512,43 @@ client.ConnectTo('192.168.1.100', 0, 0, (err) => { 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/thiagoralves/OpenPLC_v3/issues -- OpenPLC Forum: https://openplc.discussion.community/ +- GitHub Issues: https://github.com/Autonomy-Logic/openplc-runtime/issues --- -*Document Version: 1.0* +*Document Version: 1.1* *Last Updated: 2026-01-12* From 6b633625d90d39f11b22024727f064370df878d8 Mon Sep 17 00:00:00 2001 From: Thiago Alves Date: Mon, 12 Jan 2026 22:57:04 -0500 Subject: [PATCH 08/12] Fix Srv_GetStatus call and C++-only compiler flags - Fix Srv_GetStatus: use references (int&) not pointers (int*) - Fix CMakeLists.txt: apply -Wno-class-memaccess only to C++ files using COMPILE_LANGUAGE generator expression Co-Authored-By: Claude Opus 4.5 --- core/src/drivers/plugins/native/s7comm/CMakeLists.txt | 5 +++-- core/src/drivers/plugins/native/s7comm/s7comm_plugin.cpp | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/core/src/drivers/plugins/native/s7comm/CMakeLists.txt b/core/src/drivers/plugins/native/s7comm/CMakeLists.txt index d3602b66..be15fd78 100644 --- a/core/src/drivers/plugins/native/s7comm/CMakeLists.txt +++ b/core/src/drivers/plugins/native/s7comm/CMakeLists.txt @@ -106,8 +106,9 @@ target_compile_options(s7comm_plugin PRIVATE -Wno-unused-parameter -Wno-sign-compare -Wno-deprecated-declarations - $<$:-Wno-class-memaccess> - $<$:-Wno-macro-redefined> + # C++-only warning flags (only apply to C++ files) + $<$,$>:-Wno-class-memaccess> + $<$,$>:-Wno-macro-redefined> ) # ============================================================================= diff --git a/core/src/drivers/plugins/native/s7comm/s7comm_plugin.cpp b/core/src/drivers/plugins/native/s7comm/s7comm_plugin.cpp index d9b6fdd6..bf3e02a5 100644 --- a/core/src/drivers/plugins/native/s7comm/s7comm_plugin.cpp +++ b/core/src/drivers/plugins/native/s7comm/s7comm_plugin.cpp @@ -603,7 +603,7 @@ extern "C" void cycle_end(void) int server_status = 0; int cpu_status = 0; int clients_count = 0; - Srv_GetStatus(g_server, &server_status, &cpu_status, &clients_count); + Srv_GetStatus(g_server, server_status, cpu_status, clients_count); if (clients_count == 0) { /* No clients connected - no need to sync buffers */ From 6c5f204112ea7e8238848b8144e3b14418abe1b2 Mon Sep 17 00:00:00 2001 From: Thiago Alves Date: Mon, 12 Jan 2026 23:16:43 -0500 Subject: [PATCH 09/12] feat: Register S7Comm plugin in plugins configuration Add S7Comm native plugin entry to plugins.conf and plugins_default.conf so the runtime will recognize and load the S7Comm server when a config file is provided by the editor. Plugin configuration format: - name: s7comm - path: ./core/src/drivers/plugins/native/s7comm/libs7comm_plugin.so - enabled: 0 (disabled by default, enabled when config file is present) - type: 1 (native plugin) - config_path: ./core/src/drivers/plugins/native/s7comm/s7comm_config.json Co-Authored-By: Claude Opus 4.5 --- plugins.conf | 1 + plugins_default.conf | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins.conf b/plugins.conf index ae0d73b0..5fcd867c 100644 --- a/plugins.conf +++ b/plugins.conf @@ -1,2 +1,3 @@ modbus_slave,./core/src/drivers/plugins/python/modbus_slave/simple_modbus.py,0,0,./core/src/drivers/plugins/python/modbus_slave/modbus_slave_config.json,./venvs/modbus_slave modbus_master,./core/src/drivers/plugins/python/modbus_master/modbus_master_plugin.py,0,0,./core/src/drivers/plugins/python/modbus_master/modbus_master.json,./venvs/modbus_master +s7comm,./core/src/drivers/plugins/native/s7comm/libs7comm_plugin.so,0,1,./core/src/drivers/plugins/native/s7comm/s7comm_config.json, diff --git a/plugins_default.conf b/plugins_default.conf index 0c1bd5b0..617848d2 100644 --- a/plugins_default.conf +++ b/plugins_default.conf @@ -1,2 +1,3 @@ modbus_slave,./core/src/drivers/plugins/python/modbus_slave/simple_modbus.py,0,0,./core/src/drivers/plugins/python/modbus_slave/modbus_slave_config.json,./venvs/modbus_slave -modbus_master,./core/src/drivers/plugins/python/modbus_master/modbus_master_plugin.py,0,0,./core/src/drivers/plugins/python/modbus_master/modbus_master.json,./venvs/modbus_master \ No newline at end of file +modbus_master,./core/src/drivers/plugins/python/modbus_master/modbus_master_plugin.py,0,0,./core/src/drivers/plugins/python/modbus_master/modbus_master.json,./venvs/modbus_master +s7comm,./core/src/drivers/plugins/native/s7comm/libs7comm_plugin.so,0,1,./core/src/drivers/plugins/native/s7comm/s7comm_config.json, \ No newline at end of file From 556736247c76bc3cb09bad78b28b406e45143b14 Mon Sep 17 00:00:00 2001 From: Thiago Alves Date: Mon, 12 Jan 2026 23:23:56 -0500 Subject: [PATCH 10/12] fix: Correct S7Comm plugin library path Update path to match where the installer places the built library: core/src/drivers/plugins/native/s7comm/build/plugins/libs7comm_plugin.so Co-Authored-By: Claude Opus 4.5 --- plugins.conf | 2 +- plugins_default.conf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins.conf b/plugins.conf index 5fcd867c..d09403cb 100644 --- a/plugins.conf +++ b/plugins.conf @@ -1,3 +1,3 @@ modbus_slave,./core/src/drivers/plugins/python/modbus_slave/simple_modbus.py,0,0,./core/src/drivers/plugins/python/modbus_slave/modbus_slave_config.json,./venvs/modbus_slave modbus_master,./core/src/drivers/plugins/python/modbus_master/modbus_master_plugin.py,0,0,./core/src/drivers/plugins/python/modbus_master/modbus_master.json,./venvs/modbus_master -s7comm,./core/src/drivers/plugins/native/s7comm/libs7comm_plugin.so,0,1,./core/src/drivers/plugins/native/s7comm/s7comm_config.json, +s7comm,./core/src/drivers/plugins/native/s7comm/build/plugins/libs7comm_plugin.so,0,1,./core/src/drivers/plugins/native/s7comm/s7comm_config.json, diff --git a/plugins_default.conf b/plugins_default.conf index 617848d2..5ea5e8d3 100644 --- a/plugins_default.conf +++ b/plugins_default.conf @@ -1,3 +1,3 @@ modbus_slave,./core/src/drivers/plugins/python/modbus_slave/simple_modbus.py,0,0,./core/src/drivers/plugins/python/modbus_slave/modbus_slave_config.json,./venvs/modbus_slave modbus_master,./core/src/drivers/plugins/python/modbus_master/modbus_master_plugin.py,0,0,./core/src/drivers/plugins/python/modbus_master/modbus_master.json,./venvs/modbus_master -s7comm,./core/src/drivers/plugins/native/s7comm/libs7comm_plugin.so,0,1,./core/src/drivers/plugins/native/s7comm/s7comm_config.json, \ No newline at end of file +s7comm,./core/src/drivers/plugins/native/s7comm/build/plugins/libs7comm_plugin.so,0,1,./core/src/drivers/plugins/native/s7comm/s7comm_config.json, \ No newline at end of file From 4e0151ec9c69ba808e78ff21d48001be61f305f2 Mon Sep 17 00:00:00 2001 From: Thiago Alves Date: Mon, 12 Jan 2026 23:56:09 -0500 Subject: [PATCH 11/12] fix: Remove faulty client count optimization in S7Comm sync The Srv_GetStatus client count check was incorrectly returning 0 even when clients were connected, causing buffer sync to be skipped. Remove this optimization to ensure buffer sync always happens. Co-Authored-By: Claude Opus 4.5 --- .../plugins/native/s7comm/s7comm_plugin.cpp | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/core/src/drivers/plugins/native/s7comm/s7comm_plugin.cpp b/core/src/drivers/plugins/native/s7comm/s7comm_plugin.cpp index bf3e02a5..571b877a 100644 --- a/core/src/drivers/plugins/native/s7comm/s7comm_plugin.cpp +++ b/core/src/drivers/plugins/native/s7comm/s7comm_plugin.cpp @@ -580,9 +580,7 @@ extern "C" void cycle_start(void) /** * @brief Called at the end of each PLC scan cycle * - * Optimization: If no clients are connected, skip sync entirely. - * - * Double-buffer sync strategy (when clients connected): + * Double-buffer sync strategy: * 1. Lock S7 mutex (briefly) * 2. Copy S7 buffers -> shadow buffers (capture S7 client writes) * 3. Unlock S7 mutex @@ -599,17 +597,6 @@ extern "C" void cycle_end(void) return; } - /* Check if any clients are connected - skip sync if none */ - int server_status = 0; - int cpu_status = 0; - int clients_count = 0; - Srv_GetStatus(g_server, server_status, cpu_status, clients_count); - - if (clients_count == 0) { - /* No clients connected - no need to sync buffers */ - return; - } - /* * Step 1: Lock S7 mutex and copy S7 -> shadow (capture S7 writes) * This captures what S7 clients have written to output/memory areas From faa3766182f4757091ebc29f8515a61ae98a55ee Mon Sep 17 00:00:00 2001 From: Thiago Alves Date: Tue, 13 Jan 2026 08:46:12 -0500 Subject: [PATCH 12/12] fix(s7comm): Correct buffer sync strategy for bidirectional S7 communication Change the sync strategy to copy ENTIRE buffers in both directions: - cycle_start: Copy ENTIRE S7 buffer -> OpenPLC buffer (all types) - cycle_end: Copy ENTIRE OpenPLC buffer -> S7 buffer (all types) This allows S7 clients to write to any location (inputs, outputs, memory). Values actively driven by the PLC program or I/O drivers will naturally overwrite S7 writes during the scan cycle, but S7 writes to other locations (e.g., outputs used only as contacts) will persist. Fixes issue where PLC outputs were being overwritten with zeros from the S7 buffer. Co-Authored-By: Claude Opus 4.5 --- .../plugins/native/s7comm/s7comm_plugin.cpp | 236 ++++++++++-------- 1 file changed, 125 insertions(+), 111 deletions(-) diff --git a/core/src/drivers/plugins/native/s7comm/s7comm_plugin.cpp b/core/src/drivers/plugins/native/s7comm/s7comm_plugin.cpp index 571b877a..ff5554c0 100644 --- a/core/src/drivers/plugins/native/s7comm/s7comm_plugin.cpp +++ b/core/src/drivers/plugins/native/s7comm/s7comm_plugin.cpp @@ -5,15 +5,21 @@ * 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. * - * Phase 3/4 Implementation - Double Buffering: + * Buffer Sync Strategy: * - S7 buffers: What Snap7 clients read/write (accessed asynchronously) - * - Shadow buffers: Used for sync with OpenPLC - * - S7 mutex: Protects S7 buffers during brief memcpy at cycle_end - * - Sync only at cycle_end, minimizing mutex contention + * - Shadow buffers: Used for sync with OpenPLC (minimizes mutex hold time) + * - S7 mutex: Protects S7 buffers during brief memcpy operations * - * Data flow: - * - S7 clients read/write S7 buffers asynchronously (lock-free most of the time) - * - At cycle_end: brief lock -> memcpy S7<->shadow -> unlock -> sync shadow<->OpenPLC + * Data flow (full bidirectional sync for ALL types): + * - cycle_start: Copy ENTIRE S7 buffer -> OpenPLC buffer + * S7 client writes become visible to the PLC program + * + * - cycle_end: Copy ENTIRE OpenPLC buffer -> S7 buffer + * PLC program outputs become visible to S7 clients + * + * This approach allows S7 clients to write to any location. Values that are + * actively driven by the PLC program or I/O drivers will be overwritten + * during the scan cycle, but S7 writes to other locations will persist. */ #include @@ -102,11 +108,11 @@ static s7comm_area_runtime_t g_mk_runtime; * ============================================================================= */ static void s7comm_event_callback(void *usrPtr, PSrvEvent PEvent, int Size); -static void sync_shadow_to_openplc(void); -static void sync_openplc_to_shadow(void); static int allocate_buffers(void); static void free_buffers(void); static int register_all_areas(void); +static void sync_shadow_to_openplc_by_type(uint8_t *shadow, int size, s7comm_buffer_type_t type, int start_buffer); +static void sync_openplc_to_shadow_by_type(uint8_t *shadow, int size, s7comm_buffer_type_t type, int start_buffer); /* * ============================================================================= @@ -568,42 +574,27 @@ extern "C" void cleanup(void) /** * @brief Called at the start of each PLC scan cycle * - * With double-buffering, we don't sync at cycle_start. - * S7 clients read from their own buffer asynchronously. - * All sync happens at cycle_end. + * Copy ENTIRE S7 buffer -> OpenPLC buffer for ALL types. + * This allows S7 clients to write to any location. Values that are actively + * driven by the PLC program or I/O drivers will be overwritten during the + * scan cycle, but S7 writes to other locations will persist. */ extern "C" void cycle_start(void) -{ - /* No-op with double-buffering - S7 reads asynchronously from its buffer */ -} - -/** - * @brief Called at the end of each PLC scan cycle - * - * Double-buffer sync strategy: - * 1. Lock S7 mutex (briefly) - * 2. Copy S7 buffers -> shadow buffers (capture S7 client writes) - * 3. Unlock S7 mutex - * 4. Sync shadow buffers <-> OpenPLC buffers (OpenPLC mutex already held) - * 5. Lock S7 mutex (briefly) - * 6. Copy shadow buffers -> S7 buffers (publish new values to S7 clients) - * 7. Unlock S7 mutex - * - * This minimizes S7 mutex hold time - only held during memcpy operations. - */ -extern "C" void cycle_end(void) { if (!g_initialized || !g_running || !g_config.enabled) { return; } /* - * Step 1: Lock S7 mutex and copy S7 -> shadow (capture S7 writes) - * This captures what S7 clients have written to output/memory areas + * Lock S7 mutex and copy S7 -> shadow for ALL areas + * This captures everything S7 clients have written */ pthread_mutex_lock(&g_s7_mutex); - /* Copy system areas S7 -> shadow */ + /* Copy all system areas S7 -> shadow */ + if (g_pe_runtime.enabled) { + memcpy(g_pe_runtime.shadow_buffer, g_pe_runtime.s7_buffer, g_pe_runtime.size_bytes); + } if (g_pa_runtime.enabled) { memcpy(g_pa_runtime.shadow_buffer, g_pa_runtime.s7_buffer, g_pa_runtime.size_bytes); } @@ -611,33 +602,86 @@ extern "C" void cycle_end(void) memcpy(g_mk_runtime.shadow_buffer, g_mk_runtime.s7_buffer, g_mk_runtime.size_bytes); } - /* Copy data blocks S7 -> shadow (for output/memory types) */ + /* Copy all data blocks S7 -> shadow */ for (int i = 0; i < g_num_db_runtime; i++) { s7comm_db_runtime_t *db = &g_db_runtime[i]; - /* Copy all DBs - we'll filter by type during OpenPLC sync */ memcpy(db->shadow_buffer, db->s7_buffer, db->size_bytes); } pthread_mutex_unlock(&g_s7_mutex); /* - * Step 2: Sync shadow <-> OpenPLC (mutex already held by PLC cycle manager) - * This is the "slow" part that accesses OpenPLC buffers + * Sync shadow -> OpenPLC for ALL areas + * S7 client writes become visible to the PLC program + */ + + /* Sync all system areas shadow -> OpenPLC */ + if (g_pe_runtime.enabled) { + sync_shadow_to_openplc_by_type(g_pe_runtime.shadow_buffer, g_pe_runtime.size_bytes, + g_pe_runtime.type, g_pe_runtime.start_buffer); + } + if (g_pa_runtime.enabled) { + sync_shadow_to_openplc_by_type(g_pa_runtime.shadow_buffer, g_pa_runtime.size_bytes, + g_pa_runtime.type, g_pa_runtime.start_buffer); + } + if (g_mk_runtime.enabled) { + sync_shadow_to_openplc_by_type(g_mk_runtime.shadow_buffer, g_mk_runtime.size_bytes, + g_mk_runtime.type, g_mk_runtime.start_buffer); + } + + /* Sync all data blocks shadow -> OpenPLC */ + for (int i = 0; i < g_num_db_runtime; i++) { + s7comm_db_runtime_t *db = &g_db_runtime[i]; + sync_shadow_to_openplc_by_type(db->shadow_buffer, db->size_bytes, db->type, db->start_buffer); + } +} + +/** + * @brief Called at the end of each PLC scan cycle + * + * Copy ENTIRE OpenPLC buffer -> S7 buffer for ALL types. + * This makes all PLC values visible to S7 clients. Any values driven by + * the PLC program or I/O drivers during the scan cycle will overwrite + * whatever S7 clients wrote at cycle_start. + */ +extern "C" void cycle_end(void) +{ + if (!g_initialized || !g_running || !g_config.enabled) { + return; + } + + /* + * Sync OpenPLC -> shadow for ALL areas + * This captures the final state after PLC execution */ - /* Shadow -> OpenPLC: Apply S7 client writes to PLC outputs/memory */ - sync_shadow_to_openplc(); + /* Sync all system areas OpenPLC -> shadow */ + if (g_pe_runtime.enabled) { + sync_openplc_to_shadow_by_type(g_pe_runtime.shadow_buffer, g_pe_runtime.size_bytes, + g_pe_runtime.type, g_pe_runtime.start_buffer); + } + if (g_pa_runtime.enabled) { + sync_openplc_to_shadow_by_type(g_pa_runtime.shadow_buffer, g_pa_runtime.size_bytes, + g_pa_runtime.type, g_pa_runtime.start_buffer); + } + if (g_mk_runtime.enabled) { + sync_openplc_to_shadow_by_type(g_mk_runtime.shadow_buffer, g_mk_runtime.size_bytes, + g_mk_runtime.type, g_mk_runtime.start_buffer); + } - /* OpenPLC -> Shadow: Get latest PLC values for S7 clients to read */ - sync_openplc_to_shadow(); + /* Sync all data blocks OpenPLC -> shadow */ + for (int i = 0; i < g_num_db_runtime; i++) { + s7comm_db_runtime_t *db = &g_db_runtime[i]; + sync_openplc_to_shadow_by_type(db->shadow_buffer, db->size_bytes, db->type, db->start_buffer); + } /* - * Step 3: Lock S7 mutex and copy shadow -> S7 (publish to S7 clients) - * This makes new input values visible to S7 clients + * Lock S7 mutex and copy shadow -> S7 for ALL areas + * This makes PLC values visible to S7 clients */ pthread_mutex_lock(&g_s7_mutex); - /* Copy system areas shadow -> S7 */ + /* Copy all system areas shadow -> S7 */ if (g_pe_runtime.enabled) { memcpy(g_pe_runtime.s7_buffer, g_pe_runtime.shadow_buffer, g_pe_runtime.size_bytes); } @@ -648,7 +692,7 @@ extern "C" void cycle_end(void) memcpy(g_mk_runtime.s7_buffer, g_mk_runtime.shadow_buffer, g_mk_runtime.size_bytes); } - /* Copy data blocks shadow -> S7 */ + /* Copy all data blocks shadow -> S7 */ for (int i = 0; i < g_num_db_runtime; i++) { s7comm_db_runtime_t *db = &g_db_runtime[i]; memcpy(db->s7_buffer, db->shadow_buffer, db->size_bytes); @@ -723,12 +767,18 @@ static void s7comm_event_callback(void *usrPtr, PSrvEvent PEvent, int Size) /** * @brief Sync a bool buffer from shadow to OpenPLC + * + * Copies S7 client writes to OpenPLC buffers. Used at cycle_start for ALL types. + * The PLC program and I/O drivers will overwrite values they actively control. */ static void sync_shadow_bool_to_openplc(uint8_t *shadow, 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; @@ -736,7 +786,7 @@ static void sync_shadow_bool_to_openplc(uint8_t *shadow, int size, s7comm_buffer buffer = g_runtime_args.bool_memory; break; default: - return; /* Don't write to inputs */ + return; } int max_bytes = g_runtime_args.buffer_size - start_buffer; @@ -793,12 +843,18 @@ static void sync_openplc_bool_to_shadow(uint8_t *shadow, int size, s7comm_buffer /** * @brief Sync shadow int buffer to OpenPLC (with endian conversion) + * + * Copies S7 client writes to OpenPLC buffers. Used at cycle_start for ALL types. + * The PLC program and I/O drivers will overwrite values they actively control. */ static void sync_shadow_int_to_openplc(uint8_t *shadow, 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; @@ -806,7 +862,7 @@ static void sync_shadow_int_to_openplc(uint8_t *shadow, int size, s7comm_buffer_ buffer = g_runtime_args.int_memory; break; default: - return; /* Don't write to inputs */ + return; } uint16_t *shadow_words = (uint16_t *)shadow; @@ -858,12 +914,18 @@ static void sync_openplc_int_to_shadow(uint8_t *shadow, int size, s7comm_buffer_ /** * @brief Sync shadow dint buffer to OpenPLC (with endian conversion) + * + * Copies S7 client writes to OpenPLC buffers. Used at cycle_start for ALL types. + * The PLC program and I/O drivers will overwrite values they actively control. */ static void sync_shadow_dint_to_openplc(uint8_t *shadow, 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; @@ -871,7 +933,7 @@ static void sync_shadow_dint_to_openplc(uint8_t *shadow, int size, s7comm_buffer buffer = g_runtime_args.dint_memory; break; default: - return; /* Don't write to inputs */ + return; } uint32_t *shadow_dwords = (uint32_t *)shadow; @@ -923,12 +985,18 @@ static void sync_openplc_dint_to_shadow(uint8_t *shadow, int size, s7comm_buffer /** * @brief Sync shadow lint buffer to OpenPLC (with endian conversion) + * + * Copies S7 client writes to OpenPLC buffers. Used at cycle_start for ALL types. + * The PLC program and I/O drivers will overwrite values they actively control. */ static void sync_shadow_lint_to_openplc(uint8_t *shadow, 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; @@ -936,7 +1004,7 @@ static void sync_shadow_lint_to_openplc(uint8_t *shadow, int size, s7comm_buffer buffer = g_runtime_args.lint_memory; break; default: - return; /* Don't write to inputs */ + return; } uint64_t *shadow_lwords = (uint64_t *)shadow; @@ -988,32 +1056,38 @@ static void sync_openplc_lint_to_shadow(uint8_t *shadow, int size, s7comm_buffer /** * @brief Dispatch sync from shadow to OpenPLC based on buffer type + * + * Used by cycle_start() for ALL types. Copies S7 client writes to OpenPLC buffers. + * Values driven by the PLC program or I/O drivers will be overwritten during the scan cycle. */ static void sync_shadow_to_openplc_by_type(uint8_t *shadow, 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: sync_shadow_bool_to_openplc(shadow, size, type, start_buffer); break; + case BUFFER_TYPE_INT_INPUT: case BUFFER_TYPE_INT_OUTPUT: case BUFFER_TYPE_INT_MEMORY: sync_shadow_int_to_openplc(shadow, size, type, start_buffer); break; + case BUFFER_TYPE_DINT_INPUT: case BUFFER_TYPE_DINT_OUTPUT: case BUFFER_TYPE_DINT_MEMORY: sync_shadow_dint_to_openplc(shadow, size, type, start_buffer); break; + case BUFFER_TYPE_LINT_INPUT: case BUFFER_TYPE_LINT_OUTPUT: case BUFFER_TYPE_LINT_MEMORY: sync_shadow_lint_to_openplc(shadow, size, type, start_buffer); break; default: - /* Input types are not written to OpenPLC from S7 */ break; } } @@ -1052,63 +1126,3 @@ static void sync_openplc_to_shadow_by_type(uint8_t *shadow, int size, s7comm_buf break; } } - -/** - * @brief Sync shadow buffers to OpenPLC - * - * Applies S7 client writes (outputs/memory) to OpenPLC buffers. - * Only output and memory types are written - inputs are read-only from S7 perspective. - */ -static void sync_shadow_to_openplc(void) -{ - /* Sync PA area (outputs) shadow -> OpenPLC */ - if (g_pa_runtime.enabled) { - sync_shadow_to_openplc_by_type(g_pa_runtime.shadow_buffer, g_pa_runtime.size_bytes, - g_pa_runtime.type, g_pa_runtime.start_buffer); - } - - /* Sync MK area (markers/memory) shadow -> OpenPLC */ - if (g_mk_runtime.enabled) { - sync_shadow_to_openplc_by_type(g_mk_runtime.shadow_buffer, g_mk_runtime.size_bytes, - g_mk_runtime.type, g_mk_runtime.start_buffer); - } - - /* Sync data blocks shadow -> OpenPLC (only output/memory types) */ - for (int i = 0; i < g_num_db_runtime; i++) { - s7comm_db_runtime_t *db = &g_db_runtime[i]; - sync_shadow_to_openplc_by_type(db->shadow_buffer, db->size_bytes, db->type, db->start_buffer); - } -} - -/** - * @brief Sync OpenPLC buffers to shadow - * - * Copies current OpenPLC values to shadow buffers so S7 clients can read them. - * All types are synced - inputs, outputs, and memory. - */ -static void sync_openplc_to_shadow(void) -{ - /* Sync PE area (inputs) OpenPLC -> shadow */ - if (g_pe_runtime.enabled) { - sync_openplc_to_shadow_by_type(g_pe_runtime.shadow_buffer, g_pe_runtime.size_bytes, - g_pe_runtime.type, g_pe_runtime.start_buffer); - } - - /* Sync PA area (outputs) OpenPLC -> shadow */ - if (g_pa_runtime.enabled) { - sync_openplc_to_shadow_by_type(g_pa_runtime.shadow_buffer, g_pa_runtime.size_bytes, - g_pa_runtime.type, g_pa_runtime.start_buffer); - } - - /* Sync MK area (markers) OpenPLC -> shadow */ - if (g_mk_runtime.enabled) { - sync_openplc_to_shadow_by_type(g_mk_runtime.shadow_buffer, g_mk_runtime.size_bytes, - g_mk_runtime.type, g_mk_runtime.start_buffer); - } - - /* Sync all data blocks OpenPLC -> shadow */ - for (int i = 0; i < g_num_db_runtime; i++) { - s7comm_db_runtime_t *db = &g_db_runtime[i]; - sync_openplc_to_shadow_by_type(db->shadow_buffer, db->size_bytes, db->type, db->start_buffer); - } -}