Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 45 additions & 5 deletions core/src/drivers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**
Expand Down Expand Up @@ -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);
```
Expand Down Expand Up @@ -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
Expand Down
61 changes: 59 additions & 2 deletions core/src/drivers/plugin_driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -562,7 +562,6 @@ void plugin_driver_destroy(plugin_driver_t *driver)
}
}


PyGILState_Release(local_gstate);
PyEval_RestoreThread(main_tstate);
Py_FinalizeEx();
Expand Down Expand Up @@ -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)
{
Expand Down
6 changes: 6 additions & 0 deletions core/src/drivers/plugin_driver.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
7 changes: 6 additions & 1 deletion core/src/plc_app/plc_state_manager.c
Original file line number Diff line number Diff line change
Expand Up @@ -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));

Expand Down Expand Up @@ -126,7 +132,6 @@ int load_plc_program(PluginManager *pm)

return -1;
}


return 0;
}
Expand Down