Python blocks fix; Modbus echo response fix#92
Merged
Conversation
Per Modbus specification, FC5 (Write Single Coil) and FC6 (Write Single Register) responses should echo the requested value. However, pymodbus implements these by calling setValues() then getValues(), using the read-back value in the response. OpenPLC uses a journal-based write system where writes are queued and applied at the next PLC scan cycle. This caused getValues() to return the old buffer value instead of the just-written value, making clients like libmodbus report errors due to request/response mismatch. This fix introduces OpenPLCDeviceContext that caches FC5/FC6 write values and returns them on the subsequent getValues() call. The cache uses asyncio task ID to isolate concurrent requests, preventing race conditions. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The iec_python.h header is included during compilation of generated PLC code. Python Function Blocks generate inline C code that calls getpid(), but the header only included sys/types.h (for pid_t) and was missing unistd.h (for getpid() declaration), causing compilation to fail with "implicit declaration of function 'getpid'" error. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The runtime crashed when stopping a PLC with Python Function Blocks that were actively printing to stdout. This happened because: 1. python_loader.c was compiled into libplc_*.so (not main executable) 2. Runner threads reading Python stdout were detached and never tracked 3. When dlclose() unloaded the library, runner threads crashed because their code (runner_thread function) was unmapped from memory Changes: - Track all Python blocks in an array with thread IDs, PIDs, and shm info - Use fork()/exec() instead of popen() to get the Python subprocess PID - Add python_blocks_cleanup() function that: - Sends SIGTERM to all Python processes (then SIGKILL if needed) - Joins all runner threads (they exit on EOF when Python dies) - Unmaps and unlinks shared memory regions - Removes Python script files - Call python_blocks_cleanup() before dlclose() in unload_plc_program() - Add null checks to LOG_INFO/LOG_ERROR macros for safety during cleanup Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
fix: Python Function Block compilation and cleanup
…e-echo fix: Correct FC5/FC6 Modbus response to echo requested value
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This pull request introduces major improvements to the Python function block integration and Modbus slave behavior in OpenPLC. The changes focus on robust resource management for Python blocks (ensuring subprocesses, threads, and shared memory are cleaned up safely), and on compliance with the Modbus protocol for certain function codes. Additionally, new error handling and logging mechanisms are introduced to improve maintainability and debuggability.
Python Function Block Resource Management
python_blocks) for all Python function blocks, including their subprocess PIDs, runner threads, shared memory, and script files, with a mutex for thread safety. This enables coordinated cleanup and prevents resource leaks.python_blocks_cleanup(), a function that gracefully terminates all Python subprocesses, joins runner threads, unmaps/unlinks shared memory, and deletes script files, preventing crashes during plugin unload. This is now called before unloading the plugin shared library. [1] [2] [3]fork()andexeclp(), with their stdout/stderr piped to a dedicated runner thread for logging, replacing the earlierpopen()approach. This allows for proper process control and cleanup. [1] [2]Modbus Protocol Compliance
OpenPLCDeviceContext, a custom Modbus device context that ensures the correct echo response for FC5 (Write Single Coil) and FC6 (Write Single Register) per the Modbus specification, by caching values per asyncio task and returning them as required.OpenPLCDeviceContextfor proper FC5/FC6 handling.Other Improvements
<unistd.h>,<signal.h>,<stdbool.h>,<sys/wait.h>). [1] [2]These changes significantly improve the stability, correctness, and maintainability of both the Python function block subsystem and Modbus slave implementation.