From 2b1a81b05d5690fe5df998a20a97228c3313bbd7 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 01:22:56 +0000 Subject: [PATCH 1/3] Add Python Function Blocks support This commit adds runtime support for Python Function Blocks, enabling PLC programs to include function blocks written in Python that communicate with the runtime via shared memory. Changes: - Add core/src/plc_app/include/iec_python.h: Header declaring Python FB loader functions (create_shm_name, python_block_loader) - Add core/src/plc_app/python_loader.c: Implementation ported from v3, adapted to use v4's logging API (log_info, log_error) - Update scripts/compile.sh: Include Python header in generated code compilation and link python_loader with pthread and rt libraries The Python FB loader creates shared memory regions for input/output data exchange and spawns Python processes that run the user's function block code. This matches the behavior of OpenPLC Runtime v3. Co-Authored-By: Thiago Alves --- core/src/plc_app/include/iec_python.h | 74 ++++++++++ core/src/plc_app/python_loader.c | 196 ++++++++++++++++++++++++++ scripts/compile.sh | 23 +-- 3 files changed, 284 insertions(+), 9 deletions(-) create mode 100644 core/src/plc_app/include/iec_python.h create mode 100644 core/src/plc_app/python_loader.c diff --git a/core/src/plc_app/include/iec_python.h b/core/src/plc_app/include/iec_python.h new file mode 100644 index 00000000..7a2ae06d --- /dev/null +++ b/core/src/plc_app/include/iec_python.h @@ -0,0 +1,74 @@ +//----------------------------------------------------------------------------- +// Copyright 2025 Thiago Alves +// This file is part of the OpenPLC Runtime. +// +// OpenPLC is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// OpenPLC 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 +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with OpenPLC. If not, see . +//------ +// +// This header declares the Python Function Block loader functions. +// These functions are used by the generated PLC code to load and execute +// Python function blocks via shared memory communication. +// +// Thiago Alves, Dec 2025 +//----------------------------------------------------------------------------- + +#ifndef IEC_PYTHON_H +#define IEC_PYTHON_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + + /** + * @brief Create a unique shared memory name + * + * Creates a unique name for shared memory regions using mkstemp. + * The name is suitable for use with shm_open(). + * + * @param buf Buffer to store the generated name + * @param size Size of the buffer + * @return 0 on success, -1 on failure + */ + int create_shm_name(char *buf, size_t size); + + /** + * @brief Load and start a Python function block + * + * Writes the Python script to disk, creates shared memory regions for + * input/output data exchange, and spawns a Python process to execute + * the function block. + * + * @param script_name Path where the Python script will be written + * @param script_content The Python script content (with format specifiers for pid and shm_name) + * @param shm_name Base name for shared memory regions + * @param shm_in_size Size of the input shared memory region + * @param shm_out_size Size of the output shared memory region + * @param shm_in_ptr Pointer to store the mapped input shared memory address + * @param shm_out_ptr Pointer to store the mapped output shared memory address + * @param pid PLC process ID (passed to Python script for monitoring) + * @return 0 on success, -1 on failure + */ + int python_block_loader(const char *script_name, const char *script_content, char *shm_name, + size_t shm_in_size, size_t shm_out_size, void **shm_in_ptr, + void **shm_out_ptr, pid_t pid); + +#ifdef __cplusplus +} +#endif + +#endif /* IEC_PYTHON_H */ diff --git a/core/src/plc_app/python_loader.c b/core/src/plc_app/python_loader.c new file mode 100644 index 00000000..e1db7b6f --- /dev/null +++ b/core/src/plc_app/python_loader.c @@ -0,0 +1,196 @@ +//----------------------------------------------------------------------------- +// Copyright 2025 Thiago Alves +// This file is part of the OpenPLC Runtime. +// +// OpenPLC is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// OpenPLC 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 +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with OpenPLC. If not, see . +//------ +// +// This file is responsible for loading function blocks written in Python. +// Python function blocks communicate with the PLC runtime via shared memory. +// +// Thiago Alves, Dec 2025 +//----------------------------------------------------------------------------- + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "include/iec_python.h" +#include "utils/log.h" + +/** + * @brief Thread function that runs the Python script and logs its output + * + * This function is spawned as a detached thread to run the Python interpreter + * and capture its stdout/stderr output for logging. + * + * @param arg The command string to execute (will be freed by this function) + * @return NULL + */ +static void *runner_thread(void *arg) +{ + const char *cmd = (const char *)arg; + FILE *fp = popen(cmd, "r"); + if (fp == NULL) + { + log_error("[Python] Failed to start process: %s", cmd); + free((void *)cmd); + return NULL; + } + + char buffer[512]; + while (fgets(buffer, sizeof(buffer), fp) != NULL) + { + // Remove trailing newline if present + size_t len = strlen(buffer); + if (len > 0 && buffer[len - 1] == '\n') + { + buffer[len - 1] = '\0'; + } + log_info("[Python] %s", buffer); + } + + pclose(fp); + free((void *)cmd); + return NULL; +} + +int create_shm_name(char *buf, size_t size) +{ + char shm_mask[] = "/tmp/shmXXXXXXXXXXXX"; + int fd = mkstemp(shm_mask); + if (fd == -1) + { + log_error("[Python loader] mkstemp failed: %s", strerror(errno)); + return -1; + } + close(fd); + + snprintf(buf, size, "/%s", strrchr(shm_mask, '/') + 1); + unlink(shm_mask); + + return 0; +} + +int python_block_loader(const char *script_name, const char *script_content, char *shm_name, + size_t shm_in_size, size_t shm_out_size, void **shm_in_ptr, + void **shm_out_ptr, pid_t pid) +{ + char shm_in_name[256]; + char shm_out_name[256]; + + // Write the Python script to disk + FILE *fp = fopen(script_name, "w"); + if (!fp) + { + log_error("[Python loader] Failed to write Python script: %s", strerror(errno)); + return -1; + } + chmod(script_name, 0640); + + log_info("[Python loader] Random shared memory location: %s", shm_name); + + snprintf(shm_in_name, sizeof(shm_in_name), "%s_in", shm_name); + snprintf(shm_out_name, sizeof(shm_out_name), "%s_out", shm_name); + + // Write script content with format specifiers replaced + fprintf(fp, script_content, pid, shm_name, shm_name); + fflush(fp); + fsync(fileno(fp)); + fclose(fp); + + // Map shared memory for inputs + int shm_in_fd = shm_open(shm_in_name, O_CREAT | O_RDWR, 0660); + if (shm_in_fd < 0) + { + log_error("[Python loader] shm_open (input) error: %s", strerror(errno)); + return -1; + } + if (ftruncate(shm_in_fd, shm_in_size) == -1) + { + log_error("[Python loader] ftruncate (input) error: %s", strerror(errno)); + close(shm_in_fd); + return -1; + } + *shm_in_ptr = mmap(NULL, shm_in_size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_in_fd, 0); + if (*shm_in_ptr == MAP_FAILED) + { + log_error("[Python loader] mmap (input) error: %s", strerror(errno)); + close(shm_in_fd); + return -1; + } + + // Map shared memory for outputs + int shm_out_fd = shm_open(shm_out_name, O_CREAT | O_RDWR, 0660); + if (shm_out_fd < 0) + { + log_error("[Python loader] shm_open (output) error: %s", strerror(errno)); + close(shm_in_fd); + munmap(*shm_in_ptr, shm_in_size); + shm_unlink(shm_in_name); + return -1; + } + if (ftruncate(shm_out_fd, shm_out_size) == -1) + { + log_error("[Python loader] ftruncate (output) error: %s", strerror(errno)); + close(shm_out_fd); + close(shm_in_fd); + munmap(*shm_in_ptr, shm_in_size); + shm_unlink(shm_in_name); + return -1; + } + *shm_out_ptr = mmap(NULL, shm_out_size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_out_fd, 0); + if (*shm_out_ptr == MAP_FAILED) + { + log_error("[Python loader] mmap (output) error: %s", strerror(errno)); + close(shm_out_fd); + close(shm_in_fd); + munmap(*shm_in_ptr, shm_in_size); + shm_unlink(shm_in_name); + return -1; + } + + // Close file descriptors (mapping remains valid) + close(shm_in_fd); + close(shm_out_fd); + + // Prepare command to run Python script + char *cmd = malloc(512); + if (cmd == NULL) + { + log_error("[Python loader] malloc failed for cmd buffer"); + return -1; + } + snprintf(cmd, 512, "python3 -u %s 2>&1", script_name); + + // Spawn thread to run Python process + pthread_t tid; + if (pthread_create(&tid, NULL, runner_thread, cmd) != 0) + { + log_error("[Python loader] pthread_create failed: %s", strerror(errno)); + free(cmd); + return -1; + } + pthread_detach(tid); + + log_info("[Python loader] Started Python function block: %s", script_name); + + return 0; +} diff --git a/scripts/compile.sh b/scripts/compile.sh index 60317d42..ef732e24 100755 --- a/scripts/compile.sh +++ b/scripts/compile.sh @@ -6,12 +6,14 @@ ROOT="core/generated" LIB_PATH="$ROOT/lib" SRC_PATH="$ROOT" BUILD_PATH="build" +PYTHON_INCLUDE_PATH="core/src/plc_app/include" +PYTHON_LOADER_SRC="core/src/plc_app/python_loader.c" FLAGS="-w -O3 -fPIC" check_required_files() { local missing_files=() - + if [ ! -f "$SRC_PATH/Config0.c" ]; then missing_files+=("$SRC_PATH/Config0.c") fi @@ -27,7 +29,7 @@ check_required_files() { if [ ! -d "$LIB_PATH" ]; then missing_files+=("$LIB_PATH (directory)") fi - + if [ ${#missing_files[@]} -ne 0 ]; then echo "[ERROR] Missing required source files:" >&2 printf ' %s\n' "${missing_files[@]}" >&2 @@ -46,17 +48,20 @@ fi # Compile objects into build/ echo "[INFO] Compiling Config0.c..." -gcc $FLAGS -I "$LIB_PATH" -c "$SRC_PATH/Config0.c" -o "$BUILD_PATH/Config0.o" +gcc $FLAGS -I "$LIB_PATH" -I "$PYTHON_INCLUDE_PATH" -include iec_python.h -c "$SRC_PATH/Config0.c" -o "$BUILD_PATH/Config0.o" echo "[INFO] Compiling Res0.c..." -gcc $FLAGS -I "$LIB_PATH" -c "$SRC_PATH/Res0.c" -o "$BUILD_PATH/Res0.o" +gcc $FLAGS -I "$LIB_PATH" -I "$PYTHON_INCLUDE_PATH" -include iec_python.h -c "$SRC_PATH/Res0.c" -o "$BUILD_PATH/Res0.o" echo "[INFO] Compiling debug.c..." -gcc $FLAGS -I "$LIB_PATH" -c "$SRC_PATH/debug.c" -o "$BUILD_PATH/debug.o" +gcc $FLAGS -I "$LIB_PATH" -c "$SRC_PATH/debug.c" -o "$BUILD_PATH/debug.o" echo "[INFO] Compiling glueVars.c..." -gcc $FLAGS -I "$LIB_PATH" -c "$SRC_PATH/glueVars.c" -o "$BUILD_PATH/glueVars.o" +gcc $FLAGS -I "$LIB_PATH" -c "$SRC_PATH/glueVars.c" -o "$BUILD_PATH/glueVars.o" echo "[INFO] Compiling c_blocks_code.cpp..." -g++ $FLAGS -I "$LIB_PATH" -c "$SRC_PATH/c_blocks_code.cpp" -o "$BUILD_PATH/c_blocks_code.o" +g++ $FLAGS -I "$LIB_PATH" -c "$SRC_PATH/c_blocks_code.cpp" -o "$BUILD_PATH/c_blocks_code.o" +echo "[INFO] Compiling python_loader.c..." +gcc $FLAGS -I "core/src/plc_app" -c "$PYTHON_LOADER_SRC" -o "$BUILD_PATH/python_loader.o" # Link shared library into build/ -echo "[INFO] Compiling shared library..." +echo "[INFO] Linking shared library..." g++ $FLAGS -shared -o "$BUILD_PATH/new_libplc.so" "$BUILD_PATH/Config0.o" \ - "$BUILD_PATH/Res0.o" "$BUILD_PATH/debug.o" "$BUILD_PATH/glueVars.o" "$BUILD_PATH/c_blocks_code.o" + "$BUILD_PATH/Res0.o" "$BUILD_PATH/debug.o" "$BUILD_PATH/glueVars.o" \ + "$BUILD_PATH/c_blocks_code.o" "$BUILD_PATH/python_loader.o" -lpthread -lrt From c1a76884848b345d76b977003526ef2285e83704 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 13:01:25 +0000 Subject: [PATCH 2/3] Fix logging accessibility: use function pointers instead of direct calls The python_loader.c was calling log_info() and log_error() directly, but these functions are defined in plc_main, not in libplc.so. Since plc_main is not linked with -rdynamic, these symbols would not be resolved at runtime. This commit implements Option B (function pointers/callbacks) to fix the issue: - Add static function pointers for logging in python_loader.c - Add fallback logging to stderr when loggers are not set - Add python_loader_set_loggers() function to inject logging callbacks - Update iec_python.h with the setter function declaration - Update symbols_init() in image_tables.c to wire up logging callbacks after loading libplc.so This approach matches the existing plugin system pattern where logging functions are passed via callbacks rather than relying on symbol export. Co-Authored-By: Thiago Alves --- core/src/plc_app/image_tables.c | 11 ++++ core/src/plc_app/include/iec_python.h | 13 ++++ core/src/plc_app/python_loader.c | 88 ++++++++++++++++++++++----- 3 files changed, 97 insertions(+), 15 deletions(-) diff --git a/core/src/plc_app/image_tables.c b/core/src/plc_app/image_tables.c index 14a41bcc..d67834df 100644 --- a/core/src/plc_app/image_tables.c +++ b/core/src/plc_app/image_tables.c @@ -2,6 +2,7 @@ #include #include "image_tables.h" +#include "include/iec_python.h" #include "log.h" #include "utils.h" @@ -102,6 +103,16 @@ int symbols_init(PluginManager *pm) dint_input, dint_output, lint_input, lint_output, int_memory, dint_memory, lint_memory); + // Initialize Python loader logging callbacks (optional - only present if Python FBs are used) + void (*ext_python_loader_set_loggers)(void (*)(const char *, ...), void (*)(const char *, ...)); + *(void **)(&ext_python_loader_set_loggers) = + plugin_manager_get_func(pm, void (*)(unsigned long), "python_loader_set_loggers"); + if (ext_python_loader_set_loggers) + { + ext_python_loader_set_loggers(log_info, log_error); + log_info("Python loader logging callbacks initialized"); + } + return 0; } diff --git a/core/src/plc_app/include/iec_python.h b/core/src/plc_app/include/iec_python.h index 7a2ae06d..8afca3bb 100644 --- a/core/src/plc_app/include/iec_python.h +++ b/core/src/plc_app/include/iec_python.h @@ -67,6 +67,19 @@ extern "C" size_t shm_in_size, size_t shm_out_size, void **shm_in_ptr, void **shm_out_ptr, pid_t pid); + /** + * @brief Set logging function pointers for the Python loader + * + * This function must be called after loading libplc.so to inject the + * runtime's logging functions. Without this, logging will fall back + * to stderr output. + * + * @param log_info_func Pointer to the log_info function + * @param log_error_func Pointer to the log_error function + */ + void python_loader_set_loggers(void (*log_info_func)(const char *, ...), + void (*log_error_func)(const char *, ...)); + #ifdef __cplusplus } #endif diff --git a/core/src/plc_app/python_loader.c b/core/src/plc_app/python_loader.c index e1db7b6f..dc625751 100644 --- a/core/src/plc_app/python_loader.c +++ b/core/src/plc_app/python_loader.c @@ -19,12 +19,17 @@ // This file is responsible for loading function blocks written in Python. // Python function blocks communicate with the PLC runtime via shared memory. // +// Logging is done via function pointers that are set by the runtime after +// loading libplc.so. This avoids symbol resolution issues between the +// shared library and the main executable. +// // Thiago Alves, Dec 2025 //----------------------------------------------------------------------------- #include #include #include +#include #include #include #include @@ -33,7 +38,60 @@ #include #include "include/iec_python.h" -#include "utils/log.h" + +// Function pointers for logging - set by python_loader_set_loggers() +static void (*py_log_info)(const char *fmt, ...) = NULL; +static void (*py_log_error)(const char *fmt, ...) = NULL; + +// Fallback logging to stderr when loggers not set +static void fallback_log(const char *level, const char *fmt, va_list args) +{ + fprintf(stderr, "[%s] ", level); + vfprintf(stderr, fmt, args); + fprintf(stderr, "\n"); +} + +static void py_log_info_fallback(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + fallback_log("INFO", fmt, args); + va_end(args); +} + +static void py_log_error_fallback(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + fallback_log("ERROR", fmt, args); + va_end(args); +} + +// Wrapper macros for logging +#define LOG_INFO(...) \ + do \ + { \ + if (py_log_info) \ + py_log_info(__VA_ARGS__); \ + else \ + py_log_info_fallback(__VA_ARGS__); \ + } while (0) + +#define LOG_ERROR(...) \ + do \ + { \ + if (py_log_error) \ + py_log_error(__VA_ARGS__); \ + else \ + py_log_error_fallback(__VA_ARGS__); \ + } while (0) + +void python_loader_set_loggers(void (*log_info_func)(const char *, ...), + void (*log_error_func)(const char *, ...)) +{ + py_log_info = log_info_func; + py_log_error = log_error_func; +} /** * @brief Thread function that runs the Python script and logs its output @@ -50,7 +108,7 @@ static void *runner_thread(void *arg) FILE *fp = popen(cmd, "r"); if (fp == NULL) { - log_error("[Python] Failed to start process: %s", cmd); + LOG_ERROR("[Python] Failed to start process: %s", cmd); free((void *)cmd); return NULL; } @@ -64,7 +122,7 @@ static void *runner_thread(void *arg) { buffer[len - 1] = '\0'; } - log_info("[Python] %s", buffer); + LOG_INFO("[Python] %s", buffer); } pclose(fp); @@ -78,7 +136,7 @@ int create_shm_name(char *buf, size_t size) int fd = mkstemp(shm_mask); if (fd == -1) { - log_error("[Python loader] mkstemp failed: %s", strerror(errno)); + LOG_ERROR("[Python loader] mkstemp failed: %s", strerror(errno)); return -1; } close(fd); @@ -100,12 +158,12 @@ int python_block_loader(const char *script_name, const char *script_content, cha FILE *fp = fopen(script_name, "w"); if (!fp) { - log_error("[Python loader] Failed to write Python script: %s", strerror(errno)); + LOG_ERROR("[Python loader] Failed to write Python script: %s", strerror(errno)); return -1; } chmod(script_name, 0640); - log_info("[Python loader] Random shared memory location: %s", shm_name); + LOG_INFO("[Python loader] Random shared memory location: %s", shm_name); snprintf(shm_in_name, sizeof(shm_in_name), "%s_in", shm_name); snprintf(shm_out_name, sizeof(shm_out_name), "%s_out", shm_name); @@ -120,19 +178,19 @@ int python_block_loader(const char *script_name, const char *script_content, cha int shm_in_fd = shm_open(shm_in_name, O_CREAT | O_RDWR, 0660); if (shm_in_fd < 0) { - log_error("[Python loader] shm_open (input) error: %s", strerror(errno)); + LOG_ERROR("[Python loader] shm_open (input) error: %s", strerror(errno)); return -1; } if (ftruncate(shm_in_fd, shm_in_size) == -1) { - log_error("[Python loader] ftruncate (input) error: %s", strerror(errno)); + LOG_ERROR("[Python loader] ftruncate (input) error: %s", strerror(errno)); close(shm_in_fd); return -1; } *shm_in_ptr = mmap(NULL, shm_in_size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_in_fd, 0); if (*shm_in_ptr == MAP_FAILED) { - log_error("[Python loader] mmap (input) error: %s", strerror(errno)); + LOG_ERROR("[Python loader] mmap (input) error: %s", strerror(errno)); close(shm_in_fd); return -1; } @@ -141,7 +199,7 @@ int python_block_loader(const char *script_name, const char *script_content, cha int shm_out_fd = shm_open(shm_out_name, O_CREAT | O_RDWR, 0660); if (shm_out_fd < 0) { - log_error("[Python loader] shm_open (output) error: %s", strerror(errno)); + LOG_ERROR("[Python loader] shm_open (output) error: %s", strerror(errno)); close(shm_in_fd); munmap(*shm_in_ptr, shm_in_size); shm_unlink(shm_in_name); @@ -149,7 +207,7 @@ int python_block_loader(const char *script_name, const char *script_content, cha } if (ftruncate(shm_out_fd, shm_out_size) == -1) { - log_error("[Python loader] ftruncate (output) error: %s", strerror(errno)); + LOG_ERROR("[Python loader] ftruncate (output) error: %s", strerror(errno)); close(shm_out_fd); close(shm_in_fd); munmap(*shm_in_ptr, shm_in_size); @@ -159,7 +217,7 @@ int python_block_loader(const char *script_name, const char *script_content, cha *shm_out_ptr = mmap(NULL, shm_out_size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_out_fd, 0); if (*shm_out_ptr == MAP_FAILED) { - log_error("[Python loader] mmap (output) error: %s", strerror(errno)); + LOG_ERROR("[Python loader] mmap (output) error: %s", strerror(errno)); close(shm_out_fd); close(shm_in_fd); munmap(*shm_in_ptr, shm_in_size); @@ -175,7 +233,7 @@ int python_block_loader(const char *script_name, const char *script_content, cha char *cmd = malloc(512); if (cmd == NULL) { - log_error("[Python loader] malloc failed for cmd buffer"); + LOG_ERROR("[Python loader] malloc failed for cmd buffer"); return -1; } snprintf(cmd, 512, "python3 -u %s 2>&1", script_name); @@ -184,13 +242,13 @@ int python_block_loader(const char *script_name, const char *script_content, cha pthread_t tid; if (pthread_create(&tid, NULL, runner_thread, cmd) != 0) { - log_error("[Python loader] pthread_create failed: %s", strerror(errno)); + LOG_ERROR("[Python loader] pthread_create failed: %s", strerror(errno)); free(cmd); return -1; } pthread_detach(tid); - log_info("[Python loader] Started Python function block: %s", script_name); + LOG_INFO("[Python loader] Started Python function block: %s", script_name); return 0; } From f72489eef0441eb5880b75f22ead868f814f43ec Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 17:45:03 +0000 Subject: [PATCH 3/3] Simplify python_loader logging: remove fallback mechanism Per review feedback, since python_loader.c is always compiled into libplc.so and symbols_init() always runs before any Python FB code executes, the logging callbacks will always be set. Therefore, the fallback logging to stderr is unnecessary. Changes: - Remove fallback_log, py_log_info_fallback, py_log_error_fallback functions - Remove NULL checks in LOG_INFO/LOG_ERROR macros - Simplify macros to directly call the function pointers - Remove stdarg.h include (no longer needed) Co-Authored-By: Thiago Alves --- core/src/plc_app/python_loader.c | 51 ++++---------------------------- 1 file changed, 6 insertions(+), 45 deletions(-) diff --git a/core/src/plc_app/python_loader.c b/core/src/plc_app/python_loader.c index dc625751..1883a7b5 100644 --- a/core/src/plc_app/python_loader.c +++ b/core/src/plc_app/python_loader.c @@ -29,7 +29,6 @@ #include #include #include -#include #include #include #include @@ -40,51 +39,13 @@ #include "include/iec_python.h" // Function pointers for logging - set by python_loader_set_loggers() -static void (*py_log_info)(const char *fmt, ...) = NULL; -static void (*py_log_error)(const char *fmt, ...) = NULL; +// These are always initialized by symbols_init() before any Python FB code runs +static void (*py_log_info)(const char *fmt, ...); +static void (*py_log_error)(const char *fmt, ...); -// Fallback logging to stderr when loggers not set -static void fallback_log(const char *level, const char *fmt, va_list args) -{ - fprintf(stderr, "[%s] ", level); - vfprintf(stderr, fmt, args); - fprintf(stderr, "\n"); -} - -static void py_log_info_fallback(const char *fmt, ...) -{ - va_list args; - va_start(args, fmt); - fallback_log("INFO", fmt, args); - va_end(args); -} - -static void py_log_error_fallback(const char *fmt, ...) -{ - va_list args; - va_start(args, fmt); - fallback_log("ERROR", fmt, args); - va_end(args); -} - -// Wrapper macros for logging -#define LOG_INFO(...) \ - do \ - { \ - if (py_log_info) \ - py_log_info(__VA_ARGS__); \ - else \ - py_log_info_fallback(__VA_ARGS__); \ - } while (0) - -#define LOG_ERROR(...) \ - do \ - { \ - if (py_log_error) \ - py_log_error(__VA_ARGS__); \ - else \ - py_log_error_fallback(__VA_ARGS__); \ - } while (0) +// Simple wrapper macros for logging +#define LOG_INFO(...) py_log_info(__VA_ARGS__) +#define LOG_ERROR(...) py_log_error(__VA_ARGS__) void python_loader_set_loggers(void (*log_info_func)(const char *, ...), void (*log_error_func)(const char *, ...))