diff --git a/core/src/drivers/README.md b/core/src/drivers/README.md index 021c88e5..df69a407 100644 --- a/core/src/drivers/README.md +++ b/core/src/drivers/README.md @@ -91,9 +91,9 @@ void start_loop(void); // Called when plugin should start operations void stop_loop(void); // Called when plugin should stop operations void cleanup(void); // Called when plugin is being unloaded -// Reserved hooks (symbols loaded but not currently invoked by the runtime) -void cycle_start(void); // Reserved for future per-cycle start hook -void cycle_end(void); // Reserved for future per-cycle end hook +// Per-cycle hooks (called during each PLC scan cycle, synchronized with PLC execution) +void cycle_start(void); // Called at start of each scan cycle, before PLC logic +void cycle_end(void); // Called at end of each scan cycle, after PLC logic ``` **Important: Native Plugin Args Lifetime** @@ -573,6 +573,14 @@ int plugin_driver_start(plugin_driver_t *driver); // Returns 0 on success int plugin_driver_stop(plugin_driver_t *driver); +// Call cycle_start for all active native plugins (called at start of each PLC scan cycle) +// Plugins opt-in by implementing cycle_start(); opt-out by not implementing it +void plugin_driver_cycle_start(plugin_driver_t *driver); + +// Call cycle_end for all active native plugins (called at end of each PLC scan cycle) +// Plugins opt-in by implementing cycle_end(); opt-out by not implementing it +void plugin_driver_cycle_end(plugin_driver_t *driver); + // Destroy the plugin driver and free resources (calls 'cleanup' on plugins) void plugin_driver_destroy(plugin_driver_t *driver); ``` @@ -640,10 +648,42 @@ void plugin_driver_destroy(plugin_driver_t *driver); 2. **Plugin Lifecycle Management:** * `init()`: Perform one-time setup (load config, initialize data structures). * `start_loop()`: Start long-running tasks (servers, periodic threads). - * `run_cycle()`: Keep this function lightweight if called frequently by the main OpenPLC loop. * `cleanup()`: Reliably release all resources (stop threads, close connections, free memory). -3. **Memory Management (Python):** +3. **Native Plugin Cycle Hooks (Real-Time Synchronization):** + + Native plugins can optionally implement `cycle_start()` and `cycle_end()` functions to synchronize with the PLC scan cycle. These hooks are called during each scan cycle while the buffer mutex is held, allowing direct access to I/O buffers without additional locking. + + * `cycle_start()`: Called at the beginning of each scan cycle, before PLC logic execution. Use this to read inputs or prepare data for the PLC program. + * `cycle_end()`: Called at the end of each scan cycle, after PLC logic execution. Use this to process outputs or perform post-cycle operations. + + **Opt-in/Opt-out:** Plugins opt-in by implementing these functions. If a plugin does not implement them (NULL pointer), the runtime simply skips calling them for that plugin. This allows plugins to choose between: + * **Synchronous operation**: Implement cycle hooks to run in lockstep with the PLC scan cycle (real-time). + * **Asynchronous operation**: Use `start_loop()` to create separate threads that run independently. + + **Important:** Keep cycle hook implementations as fast as possible since they run in the critical path of the PLC scan cycle. Long-running operations will increase scan cycle time and may cause timing issues. + + ```c + // Example: Native plugin with cycle hooks + static plugin_runtime_args_t g_args; + + int init(void *args) { + memcpy(&g_args, args, sizeof(plugin_runtime_args_t)); + return 0; + } + + void cycle_start(void) { + // Read inputs before PLC logic runs + // Buffer mutex is already held - safe to access buffers directly + } + + void cycle_end(void) { + // Process outputs after PLC logic runs + // Buffer mutex is still held - safe to access buffers directly + } + ``` + +4. **Memory Management (Python):** * Python's garbage collector handles memory. However, explicitly close files, sockets, or release other external resources in `cleanup()`. ## Dependencies diff --git a/core/src/drivers/plugin_driver.c b/core/src/drivers/plugin_driver.c index 9c90e7ba..b278bab7 100644 --- a/core/src/drivers/plugin_driver.c +++ b/core/src/drivers/plugin_driver.c @@ -345,7 +345,7 @@ int plugin_driver_start(plugin_driver_t *driver) { // Acquire GIL for this specific Python call PyGILState_STATE local_gil = PyGILState_Ensure(); - PyObject *res = PyObject_CallNoArgs(plugin->python_plugin->pFuncStart); + PyObject *res = PyObject_CallNoArgs(plugin->python_plugin->pFuncStart); if (!res) { PyErr_Print(); @@ -562,7 +562,6 @@ void plugin_driver_destroy(plugin_driver_t *driver) } } - PyGILState_Release(local_gstate); PyEval_RestoreThread(main_tstate); Py_FinalizeEx(); @@ -968,6 +967,64 @@ void python_plugin_cycle(plugin_instance_t *plugin) // and call the cycle function } +// Call cycle_start for all active native plugins that have registered the hook +// This should be called at the beginning of each PLC scan cycle, before PLC logic execution +// Plugins opt-in by implementing cycle_start(); opt-out by not implementing it (NULL pointer) +void plugin_driver_cycle_start(plugin_driver_t *driver) +{ + if (!driver || driver->plugin_count == 0) + { + return; + } + + for (int i = 0; i < driver->plugin_count; i++) + { + plugin_instance_t *plugin = &driver->plugins[i]; + + // Skip disabled or non-running plugins + if (!plugin->config.enabled || !plugin->running) + { + continue; + } + + // Only native plugins support cycle hooks (they can run in real-time) + if (plugin->config.type == PLUGIN_TYPE_NATIVE && plugin->native_plugin && + plugin->native_plugin->cycle_start) + { + plugin->native_plugin->cycle_start(); + } + } +} + +// Call cycle_end for all active native plugins that have registered the hook +// This should be called at the end of each PLC scan cycle, after PLC logic execution +// Plugins opt-in by implementing cycle_end(); opt-out by not implementing it (NULL pointer) +void plugin_driver_cycle_end(plugin_driver_t *driver) +{ + if (!driver || driver->plugin_count == 0) + { + return; + } + + for (int i = 0; i < driver->plugin_count; i++) + { + plugin_instance_t *plugin = &driver->plugins[i]; + + // Skip disabled or non-running plugins + if (!plugin->config.enabled || !plugin->running) + { + continue; + } + + // Only native plugins support cycle hooks (they can run in real-time) + if (plugin->config.type == PLUGIN_TYPE_NATIVE && plugin->native_plugin && + plugin->native_plugin->cycle_end) + { + plugin->native_plugin->cycle_end(); + } + } +} + // Cleanup Python plugin static void python_plugin_cleanup(plugin_instance_t *plugin) { diff --git a/core/src/drivers/plugin_driver.h b/core/src/drivers/plugin_driver.h index da84babe..5bb70109 100644 --- a/core/src/drivers/plugin_driver.h +++ b/core/src/drivers/plugin_driver.h @@ -106,6 +106,12 @@ void plugin_driver_destroy(plugin_driver_t *driver); int plugin_mutex_take(pthread_mutex_t *mutex); int plugin_mutex_give(pthread_mutex_t *mutex); +// Cycle hook functions for native plugins (called during PLC scan cycle) +// These iterate through all active native plugins and call their cycle hooks +// Plugins opt-in by implementing cycle_start/cycle_end; opt-out by not implementing them +void plugin_driver_cycle_start(plugin_driver_t *driver); +void plugin_driver_cycle_end(plugin_driver_t *driver); + // Python plugin functions int python_plugin_get_symbols(plugin_instance_t *plugin); diff --git a/core/src/plc_app/plc_state_manager.c b/core/src/plc_app/plc_state_manager.c index 47e5eb46..c9f84be7 100644 --- a/core/src/plc_app/plc_state_manager.c +++ b/core/src/plc_app/plc_state_manager.c @@ -53,10 +53,16 @@ void *plc_cycle_thread(void *arg) scan_cycle_time_start(); plugin_mutex_take(&plugin_driver->buffer_mutex); + // Call cycle_start for all active native plugins that registered the hook + plugin_driver_cycle_start(plugin_driver); + // Execute the PLC cycle ext_config_run__(tick__++); ext_updateTime(); + // Call cycle_end for all active native plugins that registered the hook + plugin_driver_cycle_end(plugin_driver); + // Update Watchdog Heartbeat atomic_store(&plc_heartbeat, time(NULL)); @@ -126,7 +132,6 @@ int load_plc_program(PluginManager *pm) return -1; } - return 0; }