From 863e6df3147caab855192ce8dcc1e223a1224c5d Mon Sep 17 00:00:00 2001 From: kuba Date: Sat, 10 Jan 2026 22:17:23 +0100 Subject: [PATCH 1/9] Added linux-gpiod plugin --- .../src/drivers/plugins/native/gpiod/Makefile | 28 ++ .../plugins/native/gpiod/gpiod_plugin.c | 256 ++++++++++++++++++ .../plugins/native/gpiod/test_plugin_loader.c | 131 +++++++++ 3 files changed, 415 insertions(+) create mode 100644 core/src/drivers/plugins/native/gpiod/Makefile create mode 100644 core/src/drivers/plugins/native/gpiod/gpiod_plugin.c create mode 100644 core/src/drivers/plugins/native/gpiod/test_plugin_loader.c diff --git a/core/src/drivers/plugins/native/gpiod/Makefile b/core/src/drivers/plugins/native/gpiod/Makefile new file mode 100644 index 00000000..f8e354c7 --- /dev/null +++ b/core/src/drivers/plugins/native/gpiod/Makefile @@ -0,0 +1,28 @@ +# Makefile for test plugin + +CC = gcc +CFLAGS = -fPIC -I../../../../lib -I../../../../drivers -I.. +LDFLAGS = -shared -lpthread -lgpiod + +TARGET = gpiod_plugin.so +SOURCE = gpiod_plugin.c +PLUGIN_LOGGER = ../plugin_logger.c + +LOADER_TARGET = test_plugin_loader +LOADER_SOURCE = test_plugin_loader.c + +all: $(TARGET) $(LOADER_TARGET) + +$(TARGET): $(SOURCE) $(PLUGIN_LOGGER) + $(CC) $(CFLAGS) $(SOURCE) $(PLUGIN_LOGGER) $(LDFLAGS) -o $(TARGET) + +$(LOADER_TARGET): $(LOADER_SOURCE) + $(CC) -o $(LOADER_TARGET) $(LOADER_SOURCE) -ldl -lpthread -lgpiod + +test: $(TARGET) $(LOADER_TARGET) + ./$(LOADER_TARGET) + +clean: + rm -f $(TARGET) $(LOADER_TARGET) + +.PHONY: all clean test diff --git a/core/src/drivers/plugins/native/gpiod/gpiod_plugin.c b/core/src/drivers/plugins/native/gpiod/gpiod_plugin.c new file mode 100644 index 00000000..87642089 --- /dev/null +++ b/core/src/drivers/plugins/native/gpiod/gpiod_plugin.c @@ -0,0 +1,256 @@ +#include +#include +#include +#include +#include +#include + +/* OpenPLC plugin includes */ +#include "../plugin_logger.h" +#include "iec_types.h" +#include "plugin_types.h" + +/* Logger */ +static plugin_logger_t g_logger; + +/* Runtime args */ +static plugin_runtime_args_t *g_runtime_args = NULL; + +/* Plugin state */ +static int plugin_initialized = 0; +static int plugin_running = 0; + +/* Fixed-size arrays */ +#define BUFFER_SIZE 1024 +#define BITS_PER_BYTE 8 + +static struct gpiod_line_request *inputs[BUFFER_SIZE][BITS_PER_BYTE] = {0}; +static struct gpiod_line_request *outputs[BUFFER_SIZE][BITS_PER_BYTE] = {0}; +static int io_initialized = 0; + +/* ----------------- IEC parser ----------------- */ +static int parse_iec(const char *s, uint16_t *byte, uint8_t *bit, int *is_input) +{ + char io, type; + int b, bt; + if (sscanf(s, "%%%c%c%d.%d", &io, &type, &b, &bt) != 4) return -1; + if (type != 'X' || bt < 0 || bt > 7) return -1; + *byte = (uint16_t)b; + *bit = (uint8_t)bt; + *is_input = (io == 'I') ? 1 : 0; + return 0; +} + +static int resolve_line_offset(const char *chip_name, const char *line_name, unsigned *offset_out) +{ + struct gpiod_chip *chip = gpiod_chip_open(chip_name); + if (!chip) return -1; + + struct gpiod_chip_info *chip_info = gpiod_chip_get_info(chip); + if (!chip_info) { + gpiod_chip_close(chip); + return -1; + } + + unsigned nlines = gpiod_chip_info_get_num_lines(chip_info); + gpiod_chip_info_free(chip_info); + + struct gpiod_line_info *info; + for (unsigned i = 0; i < nlines; i++) { + info = gpiod_chip_get_line_info(chip, i); + if (!info) continue; + const char *name = gpiod_line_info_get_name(info); + if (name && strcmp(name, line_name) == 0) { + *offset_out = i; + gpiod_line_info_free(info); + gpiod_chip_close(chip); + return 0; + } + gpiod_line_info_free(info); + } + gpiod_chip_close(chip); + return -1; +} + +static int parse_line_identifier(const char *s, const char *chip, unsigned *offset) +{ + char *end; + unsigned long v = strtoul(s, &end, 10); + if (*end == '\0') { *offset = (unsigned)v; return 0; } + return resolve_line_offset(chip, s, offset); +} + +static int init_io_from_csv(const char *filename) +{ + FILE *fp = fopen(filename, "r"); + if (!fp) { + plugin_logger_error(&g_logger, "Failed to open CSV: %s", filename); + return -1; + } + + char line[256]; + fgets(line, sizeof(line), fp); // skip header + + for (int b = 0; b < BUFFER_SIZE; b++) + for (int bit = 0; bit < BITS_PER_BYTE; bit++) + inputs[b][bit] = outputs[b][bit] = NULL; + + while (fgets(line, sizeof(line), fp)) { + char *iec = strtok(line, ","); + char *chip = strtok(NULL, ","); + char *lin = strtok(NULL, ","); + if (!iec || !chip || !lin) continue; + + uint16_t byte; + uint8_t bit; + int is_input; + if (parse_iec(iec, &byte, &bit, &is_input) != 0) continue; + if (byte >= BUFFER_SIZE || bit >= BITS_PER_BYTE) continue; + + unsigned offset; + if (parse_line_identifier(lin, chip, &offset) != 0) { + plugin_logger_warn(&g_logger, "Line %s not found on %s", lin, chip); + continue; + } + + struct gpiod_chip *gchip = gpiod_chip_open(chip); + if (!gchip) { + plugin_logger_error(&g_logger, "Cannot open chip %s", chip); + fclose(fp); + return -1; + } + + struct gpiod_line_settings *settings = gpiod_line_settings_new(); + enum gpiod_line_direction direction = is_input ? GPIOD_LINE_DIRECTION_INPUT : GPIOD_LINE_DIRECTION_OUTPUT; + + gpiod_line_settings_set_direction(settings, direction); + if (!is_input) + gpiod_line_settings_set_output_value(settings, 0); + + struct gpiod_line_config *lcfg = gpiod_line_config_new(); + gpiod_line_config_add_line_settings(lcfg, &offset, 1, settings); + + struct gpiod_request_config *rcfg = gpiod_request_config_new(); + + /* --- Dynamic consumer name based on IEC address --- */ + char consumer_name[64]; + snprintf(consumer_name, sizeof(consumer_name), "OpenPLC-%s", iec); + gpiod_request_config_set_consumer(rcfg, consumer_name); + + + + struct gpiod_line_request *req = gpiod_chip_request_lines(gchip, rcfg, lcfg); + if (!req) { + plugin_logger_error(&g_logger, "Line request failed"); + gpiod_line_config_free(lcfg); + gpiod_request_config_free(rcfg); + gpiod_line_settings_free(settings); + gpiod_chip_close(gchip); + fclose(fp); + return -1; + } + + if (is_input) + inputs[byte][bit] = req; + else + outputs[byte][bit] = req; + + gpiod_line_config_free(lcfg); + gpiod_request_config_free(rcfg); + gpiod_line_settings_free(settings); + gpiod_chip_close(gchip); + } + + fclose(fp); + io_initialized = 1; + return 0; +} + +/* ----------------- Plugin hooks ----------------- */ +int init(void *args) +{ + plugin_logger_init(&g_logger, "GPIOD_PLUGIN", NULL); + plugin_logger_info(&g_logger, "Initializing GPIOD plugin..."); + + if (!args) { plugin_logger_error(&g_logger, "init args NULL"); return -1; } + g_runtime_args = (plugin_runtime_args_t *)args; + plugin_logger_init(&g_logger, "GPIOD_PLUGIN", args); + + plugin_logger_info(&g_logger, "Plugin CSV: %s", g_runtime_args->plugin_specific_config_file_path); + + if (init_io_from_csv(g_runtime_args->plugin_specific_config_file_path) != 0) { + plugin_logger_error(&g_logger, "Failed to initialize I/O from CSV"); + return -1; + } + + plugin_initialized = 1; + plugin_logger_info(&g_logger, "GPIOD plugin initialized successfully"); + return 0; +} + +void start_loop(void) +{ + if (!plugin_initialized) { plugin_logger_error(&g_logger, "Cannot start - not initialized"); return; } + plugin_logger_info(&g_logger, "GPIOD plugin loop started"); + plugin_running = 1; +} + +void stop_loop(void) +{ + if (!plugin_running) return; + plugin_logger_info(&g_logger, "GPIOD plugin loop stopped"); + plugin_running = 0; +} + +void cycle_start(void) +{ + if (!plugin_initialized || !plugin_running || !io_initialized) return; + + /* Buffer mutex already held by OpenPLC */ + for (int b = 0; b < BUFFER_SIZE; b++) { + for (int bit = 0; bit < BITS_PER_BYTE; bit++) { + if (inputs[b][bit]) { + int val = gpiod_line_request_get_value(inputs[b][bit], 0); + if (val >= 0) + *g_runtime_args->bool_input[b][bit] = val ? 1 : 0; + } + } + } +} + +void cycle_end(void) +{ + if (!plugin_initialized || !plugin_running || !io_initialized) return; + + /* Buffer mutex already held by OpenPLC */ + for (int b = 0; b < BUFFER_SIZE; b++) { + for (int bit = 0; bit < BITS_PER_BYTE; bit++) { + if (outputs[b][bit]) { + int val = *g_runtime_args->bool_output[b][bit] ? 1 : 0; + gpiod_line_request_set_value(outputs[b][bit], 0, val); + } + } + } +} + +void cleanup(void) +{ + plugin_logger_info(&g_logger, "Cleaning up GPIOD plugin..."); + + if (plugin_running) stop_loop(); + + if (io_initialized) { + for (int b = 0; b < BUFFER_SIZE; b++) { + for (int bit = 0; bit < BITS_PER_BYTE; bit++) { + if (inputs[b][bit]) { gpiod_line_request_release(inputs[b][bit]); inputs[b][bit] = NULL; } + if (outputs[b][bit]) { gpiod_line_request_release(outputs[b][bit]); outputs[b][bit] = NULL; } + } + } + io_initialized = 0; + } + + plugin_initialized = 0; + g_runtime_args = NULL; + plugin_logger_info(&g_logger, "GPIOD plugin cleanup complete"); +} + diff --git a/core/src/drivers/plugins/native/gpiod/test_plugin_loader.c b/core/src/drivers/plugins/native/gpiod/test_plugin_loader.c new file mode 100644 index 00000000..04f07839 --- /dev/null +++ b/core/src/drivers/plugins/native/gpiod/test_plugin_loader.c @@ -0,0 +1,131 @@ +#include +#include +#include +#include +#include + +// Define plugin_runtime_args_t structure locally (same as in plugin) +// TODO: Ideally, include from a shared header but avoiding Python dependencies here +typedef struct +{ + // Buffer pointers + void *(*bool_input)[8]; + void *(*bool_output)[8]; + void **byte_input; + void **byte_output; + void **int_input; + void **int_output; + void **dint_input; + void **dint_output; + void **lint_input; + void **lint_output; + void **int_memory; + void **dint_memory; + void **lint_memory; + + // Mutex functions + int (*mutex_take)(pthread_mutex_t *mutex); + int (*mutex_give)(pthread_mutex_t *mutex); + pthread_mutex_t *buffer_mutex; + char plugin_specific_config_file_path[256]; + + // Buffer size information + int buffer_size; + int bits_per_buffer; +} plugin_runtime_args_t; + +// Function pointer types +typedef int (*plugin_init_func_t)(void *); + +// Simple mutex functions for testing +int test_mutex_take(pthread_mutex_t *mutex) { + return pthread_mutex_lock(mutex); +} + +int test_mutex_give(pthread_mutex_t *mutex) { + return pthread_mutex_unlock(mutex); +} + +int main() { + printf("Testing native plugin loading...\n"); + + // Create a mock runtime args structure + plugin_runtime_args_t args; + memset(&args, 0, sizeof(plugin_runtime_args_t)); + + // Initialize with test values + args.buffer_size = 1024; + args.bits_per_buffer = 8; + strcpy(args.plugin_specific_config_file_path, "./test_config.ini"); + + // Create a test mutex + pthread_mutex_t test_mutex; + pthread_mutex_init(&test_mutex, NULL); + args.buffer_mutex = &test_mutex; + args.mutex_take = test_mutex_take; + args.mutex_give = test_mutex_give; + + // Load the plugin + void *handle = dlopen("./gpiod_plugin.so", RTLD_LAZY); + if (!handle) { + fprintf(stderr, "Failed to load plugin: %s\n", dlerror()); + return 1; + } + + printf("Plugin loaded successfully!\n"); + + // Clear any existing error + dlerror(); + + // Get the init function + plugin_init_func_t init_func = (plugin_init_func_t)dlsym(handle, "init"); + if (!init_func) { + fprintf(stderr, "Failed to find 'init' function: %s\n", dlerror()); + dlclose(handle); + return 1; + } + + printf("Found 'init' function!\n"); + + // Call the init function + int result = init_func(&args); + if (result != 0) { + fprintf(stderr, "Plugin init failed with code: %d\n", result); + dlclose(handle); + return 1; + } + + printf("Plugin initialized successfully!\n"); + + // Test other functions if they exist + void (*start_func)() = (void (*)())dlsym(handle, "start_loop"); + if (start_func) { + printf("Found 'start_loop' function, calling it...\n"); + start_func(); + } else { + printf("'start_loop' function not found (optional)\n"); + } + + void (*stop_func)() = (void (*)())dlsym(handle, "stop_loop"); + if (stop_func) { + printf("Found 'stop_loop' function, calling it...\n"); + stop_func(); + } else { + printf("'stop_loop' function not found (optional)\n"); + } + + void (*cleanup_func)() = (void (*)())dlsym(handle, "cleanup"); + if (cleanup_func) { + printf("Found 'cleanup' function, calling it...\n"); + cleanup_func(); + } else { + printf("'cleanup' function not found (optional)\n"); + } + + // Close the plugin + dlclose(handle); + pthread_mutex_destroy(&test_mutex); + + printf("Plugin test completed successfully!\n"); + return 0; +} From 27641b2d1627206341ae31a67ec8d12c638f6c7c Mon Sep 17 00:00:00 2001 From: kuba Date: Sat, 10 Jan 2026 22:28:29 +0100 Subject: [PATCH 2/9] clang-format --- .../plugins/native/gpiod/gpiod_plugin.c | 149 ++++++++++++------ 1 file changed, 99 insertions(+), 50 deletions(-) diff --git a/core/src/drivers/plugins/native/gpiod/gpiod_plugin.c b/core/src/drivers/plugins/native/gpiod/gpiod_plugin.c index 87642089..3d102c70 100644 --- a/core/src/drivers/plugins/native/gpiod/gpiod_plugin.c +++ b/core/src/drivers/plugins/native/gpiod/gpiod_plugin.c @@ -1,9 +1,9 @@ +#include +#include +#include #include #include #include -#include -#include -#include /* OpenPLC plugin includes */ #include "../plugin_logger.h" @@ -18,14 +18,15 @@ static plugin_runtime_args_t *g_runtime_args = NULL; /* Plugin state */ static int plugin_initialized = 0; -static int plugin_running = 0; +static int plugin_running = 0; /* Fixed-size arrays */ #define BUFFER_SIZE 1024 #define BITS_PER_BYTE 8 -static struct gpiod_line_request *inputs[BUFFER_SIZE][BITS_PER_BYTE] = {0}; +static struct gpiod_line_request *inputs[BUFFER_SIZE][BITS_PER_BYTE] = {0}; static struct gpiod_line_request *outputs[BUFFER_SIZE][BITS_PER_BYTE] = {0}; + static int io_initialized = 0; /* ----------------- IEC parser ----------------- */ @@ -33,10 +34,12 @@ static int parse_iec(const char *s, uint16_t *byte, uint8_t *bit, int *is_input) { char io, type; int b, bt; - if (sscanf(s, "%%%c%c%d.%d", &io, &type, &b, &bt) != 4) return -1; - if (type != 'X' || bt < 0 || bt > 7) return -1; - *byte = (uint16_t)b; - *bit = (uint8_t)bt; + if (sscanf(s, "%%%c%c%d.%d", &io, &type, &b, &bt) != 4) + return -1; + if (type != 'X' || bt < 0 || bt > 7) + return -1; + *byte = (uint16_t)b; + *bit = (uint8_t)bt; *is_input = (io == 'I') ? 1 : 0; return 0; } @@ -44,23 +47,28 @@ static int parse_iec(const char *s, uint16_t *byte, uint8_t *bit, int *is_input) static int resolve_line_offset(const char *chip_name, const char *line_name, unsigned *offset_out) { struct gpiod_chip *chip = gpiod_chip_open(chip_name); - if (!chip) return -1; + if (!chip) + return -1; struct gpiod_chip_info *chip_info = gpiod_chip_get_info(chip); - if (!chip_info) { + if (!chip_info) + { gpiod_chip_close(chip); return -1; - } + } unsigned nlines = gpiod_chip_info_get_num_lines(chip_info); gpiod_chip_info_free(chip_info); struct gpiod_line_info *info; - for (unsigned i = 0; i < nlines; i++) { + for (unsigned i = 0; i < nlines; i++) + { info = gpiod_chip_get_line_info(chip, i); - if (!info) continue; + if (!info) + continue; const char *name = gpiod_line_info_get_name(info); - if (name && strcmp(name, line_name) == 0) { + if (name && strcmp(name, line_name) == 0) + { *offset_out = i; gpiod_line_info_free(info); gpiod_chip_close(chip); @@ -76,14 +84,19 @@ static int parse_line_identifier(const char *s, const char *chip, unsigned *offs { char *end; unsigned long v = strtoul(s, &end, 10); - if (*end == '\0') { *offset = (unsigned)v; return 0; } + if (*end == '\0') + { + *offset = (unsigned)v; + return 0; + } return resolve_line_offset(chip, s, offset); } static int init_io_from_csv(const char *filename) { FILE *fp = fopen(filename, "r"); - if (!fp) { + if (!fp) + { plugin_logger_error(&g_logger, "Failed to open CSV: %s", filename); return -1; } @@ -95,34 +108,41 @@ static int init_io_from_csv(const char *filename) for (int bit = 0; bit < BITS_PER_BYTE; bit++) inputs[b][bit] = outputs[b][bit] = NULL; - while (fgets(line, sizeof(line), fp)) { + while (fgets(line, sizeof(line), fp)) + { char *iec = strtok(line, ","); char *chip = strtok(NULL, ","); char *lin = strtok(NULL, ","); - if (!iec || !chip || !lin) continue; + if (!iec || !chip || !lin) + continue; uint16_t byte; uint8_t bit; int is_input; - if (parse_iec(iec, &byte, &bit, &is_input) != 0) continue; - if (byte >= BUFFER_SIZE || bit >= BITS_PER_BYTE) continue; + if (parse_iec(iec, &byte, &bit, &is_input) != 0) + continue; + if (byte >= BUFFER_SIZE || bit >= BITS_PER_BYTE) + continue; unsigned offset; - if (parse_line_identifier(lin, chip, &offset) != 0) { + if (parse_line_identifier(lin, chip, &offset) != 0) + { plugin_logger_warn(&g_logger, "Line %s not found on %s", lin, chip); continue; } struct gpiod_chip *gchip = gpiod_chip_open(chip); - if (!gchip) { + if (!gchip) + { plugin_logger_error(&g_logger, "Cannot open chip %s", chip); fclose(fp); return -1; } struct gpiod_line_settings *settings = gpiod_line_settings_new(); - enum gpiod_line_direction direction = is_input ? GPIOD_LINE_DIRECTION_INPUT : GPIOD_LINE_DIRECTION_OUTPUT; - + enum gpiod_line_direction direction = + is_input ? GPIOD_LINE_DIRECTION_INPUT : GPIOD_LINE_DIRECTION_OUTPUT; + gpiod_line_settings_set_direction(settings, direction); if (!is_input) gpiod_line_settings_set_output_value(settings, 0); @@ -137,10 +157,9 @@ static int init_io_from_csv(const char *filename) snprintf(consumer_name, sizeof(consumer_name), "OpenPLC-%s", iec); gpiod_request_config_set_consumer(rcfg, consumer_name); - - struct gpiod_line_request *req = gpiod_chip_request_lines(gchip, rcfg, lcfg); - if (!req) { + if (!req) + { plugin_logger_error(&g_logger, "Line request failed"); gpiod_line_config_free(lcfg); gpiod_request_config_free(rcfg); @@ -172,13 +191,19 @@ int init(void *args) plugin_logger_init(&g_logger, "GPIOD_PLUGIN", NULL); plugin_logger_info(&g_logger, "Initializing GPIOD plugin..."); - if (!args) { plugin_logger_error(&g_logger, "init args NULL"); return -1; } + if (!args) + { + plugin_logger_error(&g_logger, "init args NULL"); + return -1; + } g_runtime_args = (plugin_runtime_args_t *)args; plugin_logger_init(&g_logger, "GPIOD_PLUGIN", args); - plugin_logger_info(&g_logger, "Plugin CSV: %s", g_runtime_args->plugin_specific_config_file_path); + plugin_logger_info(&g_logger, "Plugin CSV: %s", + g_runtime_args->plugin_specific_config_file_path); - if (init_io_from_csv(g_runtime_args->plugin_specific_config_file_path) != 0) { + if (init_io_from_csv(g_runtime_args->plugin_specific_config_file_path) != 0) + { plugin_logger_error(&g_logger, "Failed to initialize I/O from CSV"); return -1; } @@ -190,26 +215,35 @@ int init(void *args) void start_loop(void) { - if (!plugin_initialized) { plugin_logger_error(&g_logger, "Cannot start - not initialized"); return; } + if (!plugin_initialized) + { + plugin_logger_error(&g_logger, "Cannot start - not initialized"); + return; + } plugin_logger_info(&g_logger, "GPIOD plugin loop started"); plugin_running = 1; } void stop_loop(void) { - if (!plugin_running) return; + if (!plugin_running) + return; plugin_logger_info(&g_logger, "GPIOD plugin loop stopped"); plugin_running = 0; } void cycle_start(void) { - if (!plugin_initialized || !plugin_running || !io_initialized) return; + if (!plugin_initialized || !plugin_running || !io_initialized) + return; /* Buffer mutex already held by OpenPLC */ - for (int b = 0; b < BUFFER_SIZE; b++) { - for (int bit = 0; bit < BITS_PER_BYTE; bit++) { - if (inputs[b][bit]) { + for (int b = 0; b < BUFFER_SIZE; b++) + { + for (int bit = 0; bit < BITS_PER_BYTE; bit++) + { + if (inputs[b][bit]) + { int val = gpiod_line_request_get_value(inputs[b][bit], 0); if (val >= 0) *g_runtime_args->bool_input[b][bit] = val ? 1 : 0; @@ -220,12 +254,16 @@ void cycle_start(void) void cycle_end(void) { - if (!plugin_initialized || !plugin_running || !io_initialized) return; + if (!plugin_initialized || !plugin_running || !io_initialized) + return; /* Buffer mutex already held by OpenPLC */ - for (int b = 0; b < BUFFER_SIZE; b++) { - for (int bit = 0; bit < BITS_PER_BYTE; bit++) { - if (outputs[b][bit]) { + for (int b = 0; b < BUFFER_SIZE; b++) + { + for (int bit = 0; bit < BITS_PER_BYTE; bit++) + { + if (outputs[b][bit]) + { int val = *g_runtime_args->bool_output[b][bit] ? 1 : 0; gpiod_line_request_set_value(outputs[b][bit], 0, val); } @@ -237,20 +275,31 @@ void cleanup(void) { plugin_logger_info(&g_logger, "Cleaning up GPIOD plugin..."); - if (plugin_running) stop_loop(); - - if (io_initialized) { - for (int b = 0; b < BUFFER_SIZE; b++) { - for (int bit = 0; bit < BITS_PER_BYTE; bit++) { - if (inputs[b][bit]) { gpiod_line_request_release(inputs[b][bit]); inputs[b][bit] = NULL; } - if (outputs[b][bit]) { gpiod_line_request_release(outputs[b][bit]); outputs[b][bit] = NULL; } + if (plugin_running) + stop_loop(); + + if (io_initialized) + { + for (int b = 0; b < BUFFER_SIZE; b++) + { + for (int bit = 0; bit < BITS_PER_BYTE; bit++) + { + if (inputs[b][bit]) + { + gpiod_line_request_release(inputs[b][bit]); + inputs[b][bit] = NULL; + } + if (outputs[b][bit]) + { + gpiod_line_request_release(outputs[b][bit]); + outputs[b][bit] = NULL; + } } } io_initialized = 0; } plugin_initialized = 0; - g_runtime_args = NULL; + g_runtime_args = NULL; plugin_logger_info(&g_logger, "GPIOD plugin cleanup complete"); } - From 7c59eb4eea9c0fd7e234a394a630f059bf8ca70c Mon Sep 17 00:00:00 2001 From: kuba Date: Sat, 10 Jan 2026 23:02:41 +0100 Subject: [PATCH 3/9] use args copy --- .../plugins/native/gpiod/gpiod_plugin.c | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/core/src/drivers/plugins/native/gpiod/gpiod_plugin.c b/core/src/drivers/plugins/native/gpiod/gpiod_plugin.c index 3d102c70..df1794cf 100644 --- a/core/src/drivers/plugins/native/gpiod/gpiod_plugin.c +++ b/core/src/drivers/plugins/native/gpiod/gpiod_plugin.c @@ -14,7 +14,7 @@ static plugin_logger_t g_logger; /* Runtime args */ -static plugin_runtime_args_t *g_runtime_args = NULL; +static plugin_runtime_args_t g_args; // Plugin-owned copy /* Plugin state */ static int plugin_initialized = 0; @@ -193,16 +193,16 @@ int init(void *args) if (!args) { - plugin_logger_error(&g_logger, "init args NULL"); return -1; } - g_runtime_args = (plugin_runtime_args_t *)args; - plugin_logger_init(&g_logger, "GPIOD_PLUGIN", args); + // Copy the entire structure + memcpy(&g_args, args, sizeof(plugin_runtime_args_t)); + // Now g_args can be safely used in start_loop, stop_loop, etc. - plugin_logger_info(&g_logger, "Plugin CSV: %s", - g_runtime_args->plugin_specific_config_file_path); + plugin_logger_init(&g_logger, "GPIOD_PLUGIN", args); + plugin_logger_info(&g_logger, "Plugin CSV: %s", g_args.plugin_specific_config_file_path); - if (init_io_from_csv(g_runtime_args->plugin_specific_config_file_path) != 0) + if (init_io_from_csv(g_args.plugin_specific_config_file_path) != 0) { plugin_logger_error(&g_logger, "Failed to initialize I/O from CSV"); return -1; @@ -246,7 +246,7 @@ void cycle_start(void) { int val = gpiod_line_request_get_value(inputs[b][bit], 0); if (val >= 0) - *g_runtime_args->bool_input[b][bit] = val ? 1 : 0; + *g_args.bool_input[b][bit] = val ? 1 : 0; } } } @@ -264,7 +264,7 @@ void cycle_end(void) { if (outputs[b][bit]) { - int val = *g_runtime_args->bool_output[b][bit] ? 1 : 0; + int val = *g_args.bool_output[b][bit] ? 1 : 0; gpiod_line_request_set_value(outputs[b][bit], 0, val); } } @@ -300,6 +300,6 @@ void cleanup(void) } plugin_initialized = 0; - g_runtime_args = NULL; + memset(&g_args, 0, sizeof(plugin_runtime_args_t)); plugin_logger_info(&g_logger, "GPIOD plugin cleanup complete"); } From d964cae73b142f3964df8a0a9a1f4855e35f8c22 Mon Sep 17 00:00:00 2001 From: kuba Date: Sat, 10 Jan 2026 23:25:28 +0100 Subject: [PATCH 4/9] strip CR/LF from csv --- core/src/drivers/plugins/native/gpiod/gpiod_plugin.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/core/src/drivers/plugins/native/gpiod/gpiod_plugin.c b/core/src/drivers/plugins/native/gpiod/gpiod_plugin.c index df1794cf..92202952 100644 --- a/core/src/drivers/plugins/native/gpiod/gpiod_plugin.c +++ b/core/src/drivers/plugins/native/gpiod/gpiod_plugin.c @@ -110,6 +110,10 @@ static int init_io_from_csv(const char *filename) while (fgets(line, sizeof(line), fp)) { + /* Strip trailing CR/LF */ + line[strcspn(line, "\r")] = '\0'; + line[strcspn(line, "\n")] = '\0'; + char *iec = strtok(line, ","); char *chip = strtok(NULL, ","); char *lin = strtok(NULL, ","); @@ -127,7 +131,7 @@ static int init_io_from_csv(const char *filename) unsigned offset; if (parse_line_identifier(lin, chip, &offset) != 0) { - plugin_logger_warn(&g_logger, "Line %s not found on %s", lin, chip); + plugin_logger_warn(&g_logger, "Line %s:%s not found", chip,lin); continue; } @@ -193,6 +197,7 @@ int init(void *args) if (!args) { + plugin_logger_error(&g_logger, "init args NULL"); return -1; } // Copy the entire structure From 9a26f514bc6ac1029dda393e64e88920710a670e Mon Sep 17 00:00:00 2001 From: kuba Date: Sun, 11 Jan 2026 19:58:47 +0100 Subject: [PATCH 5/9] Use more libgpiod types --- .../plugins/native/gpiod/gpiod_plugin.c | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/core/src/drivers/plugins/native/gpiod/gpiod_plugin.c b/core/src/drivers/plugins/native/gpiod/gpiod_plugin.c index 92202952..c8180c84 100644 --- a/core/src/drivers/plugins/native/gpiod/gpiod_plugin.c +++ b/core/src/drivers/plugins/native/gpiod/gpiod_plugin.c @@ -131,7 +131,7 @@ static int init_io_from_csv(const char *filename) unsigned offset; if (parse_line_identifier(lin, chip, &offset) != 0) { - plugin_logger_warn(&g_logger, "Line %s:%s not found", chip,lin); + plugin_logger_warn(&g_logger, "Line %s:%s not found", chip, lin); continue; } @@ -139,8 +139,7 @@ static int init_io_from_csv(const char *filename) if (!gchip) { plugin_logger_error(&g_logger, "Cannot open chip %s", chip); - fclose(fp); - return -1; + goto error; } struct gpiod_line_settings *settings = gpiod_line_settings_new(); @@ -169,8 +168,7 @@ static int init_io_from_csv(const char *filename) gpiod_request_config_free(rcfg); gpiod_line_settings_free(settings); gpiod_chip_close(gchip); - fclose(fp); - return -1; + goto error; } if (is_input) @@ -187,6 +185,10 @@ static int init_io_from_csv(const char *filename) fclose(fp); io_initialized = 1; return 0; + +error: + fclose(fp); + return -1; } /* ----------------- Plugin hooks ----------------- */ @@ -239,6 +241,8 @@ void stop_loop(void) void cycle_start(void) { + enum gpiod_line_value val; + if (!plugin_initialized || !plugin_running || !io_initialized) return; @@ -249,9 +253,9 @@ void cycle_start(void) { if (inputs[b][bit]) { - int val = gpiod_line_request_get_value(inputs[b][bit], 0); - if (val >= 0) - *g_args.bool_input[b][bit] = val ? 1 : 0; + val = gpiod_line_request_get_value(inputs[b][bit], 0); + if (val != GPIOD_LINE_VALUE_ERROR) + *g_args.bool_input[b][bit] = val ? true : false; } } } @@ -259,6 +263,8 @@ void cycle_start(void) void cycle_end(void) { + enum gpiod_line_value val; + if (!plugin_initialized || !plugin_running || !io_initialized) return; @@ -269,7 +275,8 @@ void cycle_end(void) { if (outputs[b][bit]) { - int val = *g_args.bool_output[b][bit] ? 1 : 0; + val = *g_args.bool_output[b][bit] ? GPIOD_LINE_VALUE_ACTIVE + : GPIOD_LINE_VALUE_INACTIVE; gpiod_line_request_set_value(outputs[b][bit], 0, val); } } From 867679185f727e2d4fbbc7ab0b6349ce7d878ac4 Mon Sep 17 00:00:00 2001 From: kuba Date: Sun, 11 Jan 2026 20:06:33 +0100 Subject: [PATCH 6/9] Added readme --- .../drivers/plugins/native/gpiod/README.md | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 core/src/drivers/plugins/native/gpiod/README.md diff --git a/core/src/drivers/plugins/native/gpiod/README.md b/core/src/drivers/plugins/native/gpiod/README.md new file mode 100644 index 00000000..d568425c --- /dev/null +++ b/core/src/drivers/plugins/native/gpiod/README.md @@ -0,0 +1,75 @@ +# GPIOD Plugin for OpenPLC + +A native plugin for OpenPLC that provides GPIO (General Purpose Input/Output) support using the `libgpiod` library. + +## Dependencies + +### Required Packages +To compile this plugin, you must have the `libgpiod-dev` package installed: + +```bash +# On Debian/Ubuntu +sudo apt-get install libgpiod-dev + +# On Fedora/RHEL +sudo dnf install libgpiod-devel +``` + +## Building + +```bash +make +``` + +## Configuration + +The plugin requires a CSV configuration file that maps IEC addresses to GPIO lines. The file path is specified in the OpenPLC plugin configuration. + +### CSV File Format + +The CSV file contains three columns: `IEC_ADDRESS`, `CHIP_NAME`, `LINE_IDENTIFIER` + +**Header (required):** +``` +IEC_ADDRESS,CHIP_NAME,LINE_IDENTIFIER +``` + +**Field Descriptions:** +- **IEC_ADDRESS**: IEC 61131-3 format address + - Input format: `%IX.` (e.g., `%IX0.0`) + - Output format: `%QX.` (e.g., `%QX0.1`) + - ``: Buffer index (0-1023) + - ``: Bit position within byte (0-7) + +- **CHIP_NAME**: GPIO chip device name + - Typically `gpiochip0`, `gpiochip1`, etc. + - Found in `/dev/gpiochip*` + +- **LINE_IDENTIFIER**: GPIO line reference + - Can be a numeric offset (e.g., `0`, `5`, `17`) + - Or a named line (e.g., `LED_STATUS`, `BUTTON_RESET`) + - Named lines are resolved via the chip's line information + +### Example I/O mapping CSV File + +**File: io-map.csv** + +```csv +IEC_ADDRESS,CHIP_NAME,LINE_IDENTIFIER +%IX0.2,/dev/gpiochip0,GPIO2 +%IX0.3,/dev/gpiochip0,GPIO3 +%IX0.4,/dev/gpiochip0,GPIO4 +%IX0.3,/dev/gpiochip0,GPIO5 +%QX0.0,/dev/gpiochip0,GPIO10 +%QX0.1,/dev/gpiochip0,GPIO11 +%QX0.2,/dev/gpiochip0,GPIO12 +%QX0.3,/dev/gpiochip0,GPIO13 +``` + +### Example Plugin Configuration + +Add following line to **plugins.conf** +```csv +gpiod,./core/src/drivers/plugins/native/gpiod/gpiod_plugin.so,1,1,./config/io-map.csv + +``` From 5df84c658335e78399adef133985f54015863655 Mon Sep 17 00:00:00 2001 From: kuba Date: Sun, 11 Jan 2026 20:13:31 +0100 Subject: [PATCH 7/9] README.md uodated --- .../drivers/plugins/native/gpiod/README.md | 67 ++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/core/src/drivers/plugins/native/gpiod/README.md b/core/src/drivers/plugins/native/gpiod/README.md index d568425c..abccdb33 100644 --- a/core/src/drivers/plugins/native/gpiod/README.md +++ b/core/src/drivers/plugins/native/gpiod/README.md @@ -1,6 +1,7 @@ # GPIOD Plugin for OpenPLC A native plugin for OpenPLC that provides GPIO (General Purpose Input/Output) support using the `libgpiod` library. +It can be used on an Linux device with GPIOs, not only on RaspberryPi, even on PC with USB-GPIO devices ## Dependencies @@ -66,10 +67,74 @@ IEC_ADDRESS,CHIP_NAME,LINE_IDENTIFIER %QX0.3,/dev/gpiochip0,GPIO13 ``` -### Example Plugin Configuration +### Plugin Configuration Add following line to **plugins.conf** ```csv gpiod,./core/src/drivers/plugins/native/gpiod/gpiod_plugin.so,1,1,./config/io-map.csv +``` + +### Running plugin +When OpenPLC runtime is running mapped GPIO lines are controlled by OpenPLC, and consumer names are set +according given map configuration. + +Here is an example for raspberrypi: +```bash +pi@raspberrypi:~ $ gpioinfo +gpiochip0 - 54 lines: + line 0: "ID_SDA" input + line 1: "ID_SCL" input + line 2: "GPIO2" input consumer="OpenPLC-%IX0.2" + line 3: "GPIO3" input consumer="OpenPLC-%IX0.3" + line 4: "GPIO4" input consumer="OpenPLC-%IX0.4" + line 5: "GPIO5" input consumer="OpenPLC-%IX0.3" + line 6: "GPIO6" input + line 7: "GPIO7" input + line 8: "GPIO8" input + line 9: "GPIO9" input + line 10: "GPIO10" output consumer="OpenPLC-%QX0.0" + line 11: "GPIO11" output consumer="OpenPLC-%QX0.1" + line 12: "GPIO12" output consumer="OpenPLC-%QX0.2" + line 13: "GPIO13" output consumer="OpenPLC-%QX0.3" + line 14: "GPIO14" input + line 15: "GPIO15" input + line 16: "GPIO16" input + line 17: "GPIO17" input + line 18: "GPIO18" input + line 19: "GPIO19" input + line 20: "GPIO20" input + line 21: "GPIO21" input + line 22: "GPIO22" input + line 23: "GPIO23" input + line 24: "GPIO24" input + line 25: "GPIO25" input + line 26: "GPIO26" input + line 27: "GPIO27" input + line 28: "SDA0" input + line 29: "SCL0" input + line 30: "NC" input + line 31: "LAN_RUN" output + line 32: "CAM_GPIO1" output + line 33: "NC" input + line 34: "NC" input + line 35: "PWR_LOW_N" input consumer="PWR" + line 36: "NC" input + line 37: "NC" input + line 38: "USB_LIMIT" output + line 39: "NC" input + line 40: "PWM0_OUT" input + line 41: "CAM_GPIO0" output consumer="cam1_regulator" + line 42: "NC" input + line 43: "NC" input + line 44: "ETH_CLK" input + line 45: "PWM1_OUT" input + line 46: "HDMI_HPD_N" input active-low consumer="hpd" + line 47: "STATUS_LED" output consumer="ACT" + line 48: "SD_CLK_R" input + line 49: "SD_CMD_R" input + line 50: "SD_DATA0_R" input + line 51: "SD_DATA1_R" input + line 52: "SD_DATA2_R" input + line 53: "SD_DATA3_R" input ``` From 419f57a87d264f0852bc79d9dda46f3c740fc83f Mon Sep 17 00:00:00 2001 From: kuba Date: Sun, 11 Jan 2026 20:15:50 +0100 Subject: [PATCH 8/9] Uodated README.md --- core/src/drivers/plugins/native/gpiod/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/drivers/plugins/native/gpiod/README.md b/core/src/drivers/plugins/native/gpiod/README.md index abccdb33..47ee76d6 100644 --- a/core/src/drivers/plugins/native/gpiod/README.md +++ b/core/src/drivers/plugins/native/gpiod/README.md @@ -42,14 +42,14 @@ IEC_ADDRESS,CHIP_NAME,LINE_IDENTIFIER - ``: Buffer index (0-1023) - ``: Bit position within byte (0-7) -- **CHIP_NAME**: GPIO chip device name - - Typically `gpiochip0`, `gpiochip1`, etc. - - Found in `/dev/gpiochip*` +- **CHIP_NAME**: GPIO chip device path + - Typically `/dev/gpiochip0`, `/dev/gpiochip1`, etc. + - Found in `/dev/*` directory - **LINE_IDENTIFIER**: GPIO line reference - Can be a numeric offset (e.g., `0`, `5`, `17`) - Or a named line (e.g., `LED_STATUS`, `BUTTON_RESET`) - - Named lines are resolved via the chip's line information + - Named lines are resolved via the chip's line names information ### Example I/O mapping CSV File From 57f49c4f0ca1f0e0e46c630b08ac6b44d4707710 Mon Sep 17 00:00:00 2001 From: kuba Date: Sun, 11 Jan 2026 20:18:12 +0100 Subject: [PATCH 9/9] typo fixed --- core/src/drivers/plugins/native/gpiod/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/drivers/plugins/native/gpiod/README.md b/core/src/drivers/plugins/native/gpiod/README.md index 47ee76d6..4143e027 100644 --- a/core/src/drivers/plugins/native/gpiod/README.md +++ b/core/src/drivers/plugins/native/gpiod/README.md @@ -1,7 +1,7 @@ # GPIOD Plugin for OpenPLC A native plugin for OpenPLC that provides GPIO (General Purpose Input/Output) support using the `libgpiod` library. -It can be used on an Linux device with GPIOs, not only on RaspberryPi, even on PC with USB-GPIO devices +It can be used on any Linux device with GPIOs, not only on RaspberryPi, even on PC with USB-GPIO devices ## Dependencies