You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CHANGELOG.md
+21Lines changed: 21 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,5 +1,26 @@
1
1
# Changelog
2
2
3
+
## [4.0.3] - 2026-04-08
4
+
5
+
### Added
6
+
7
+
-**Auto-publish.** When an `EventDispatcherInterface` is provided and `autoPublish` is enabled, the daemon automatically calls `publish()` for every session that has active subscriptions. The client's existing PSR-14 event dispatch fires `DataChangeReceived`, `EventNotificationReceived`, `AlarmActivated`, and all other subscription events automatically — no manual publish loop required. Acknowledgements are tracked and sent internally. A self-rescheduling one-shot timer adapts to each session's minimum publishing interval.
8
+
-**`AutoPublisher`** — new internal class managing per-session publish cycles with self-rescheduling timers, automatic acknowledgement tracking, connection recovery, and backoff on consecutive errors (stops after 5).
9
+
-**Auto-connect.**`SessionManagerDaemon::autoConnect(array $connections)` accepts pre-configured connection definitions. On the first event loop tick after startup, the daemon connects to each endpoint, creates subscriptions, and registers monitored items and event monitored items as specified. Combined with auto-publish, this enables fully declarative monitoring — zero application code needed.
10
+
-**`CommandHandler::autoConnectSession()`** — opens a session to a given endpoint and creates subscriptions with monitored items in a single call. Subscription tracking (and auto-publish start) is wired automatically.
11
+
-**Event dispatcher injection.**`CommandHandler` accepts an optional `EventDispatcherInterface` and injects it into every `ClientBuilder` created via `handleOpen()`. This enables PSR-14 event delivery for all OPC UA client events in daemon-managed sessions.
12
+
-**Manual `publish()` blocking.** When auto-publish is active for a session, manual `publish()` calls via IPC return an `auto_publish_active` error to prevent conflicting publish cycles.
13
+
-`Session::getMinPublishingInterval()` — returns the minimum publishing interval (in seconds) across all tracked subscriptions, used by `AutoPublisher` for timer scheduling.
14
+
-`Session::addSubscription()` now accepts an optional `float $publishingInterval` parameter (default 500.0 ms) to track the revised publishing interval from `SubscriptionResult`.
15
+
-`CommandHandler::attemptSessionRecovery()` visibility changed from `private` to `public` to allow the daemon to pass it as a recovery callback to `AutoPublisher`.
16
+
17
+
### Changed
18
+
19
+
-`SessionManagerDaemon` constructor accepts two new optional parameters: `?EventDispatcherInterface $clientEventDispatcher` and `bool $autoPublish`.
20
+
-`CommandHandler` constructor accepts a new optional parameter: `?EventDispatcherInterface $clientEventDispatcher`.
21
+
-`trackSubscriptionChanges()` now stores `revisedPublishingInterval` from `SubscriptionResult` and triggers `AutoPublisher::startSession()`/`stopSession()` when a session's first subscription is created or its last subscription is deleted.
22
+
-`cleanupExpiredSessions()` and `shutdown()` now stop auto-publish timers before disconnecting sessions.
-**Auto-publish** — daemon automatically publishes for sessions with active subscriptions and dispatches PSR-14 events (`DataChangeReceived`, `AlarmActivated`, etc.) — no manual publish loop needed
33
+
-**Auto-connect** — daemon can auto-connect and register subscriptions at startup from pre-configured connection definitions
32
34
-**Automatic cleanup** — expired sessions are disconnected after configurable inactivity timeout
33
35
-**Graceful shutdown** — SIGTERM/SIGINT cleanly disconnect all active sessions
34
36
@@ -145,6 +147,48 @@ foreach ($response->notifications as $notif) {
145
147
}
146
148
```
147
149
150
+
### Auto-publish (no manual publish loop)
151
+
152
+
When the daemon is started with an `EventDispatcherInterface` and `autoPublish: true`, it automatically calls `publish()` for sessions that have subscriptions. The client's PSR-14 events are dispatched to your listeners:
153
+
154
+
```php
155
+
use PhpOpcua\Client\Event\DataChangeReceived;
156
+
use PhpOpcua\Client\Event\AlarmActivated;
157
+
use Psr\EventDispatcher\EventDispatcherInterface;
158
+
159
+
// 1. Start daemon with auto-publish
160
+
$daemon = new SessionManagerDaemon(
161
+
socketPath: '/tmp/opcua.sock',
162
+
clientEventDispatcher: $yourPsr14Dispatcher,
163
+
autoPublish: true,
164
+
);
165
+
166
+
// 2. Pre-configure connections to auto-connect on startup
./vendor/bin/pest tests/Integration/ --group=integration # integration only
297
343
```
298
344
299
-
340+ tests (unit + integration). Integration tests run against [uanetstandard-test-suite](https://github.com/php-opcua/uanetstandard-test-suite) — a Docker-based OPC UA environment built on the OPC Foundation's UA-.NETStandard reference implementation — covering browse, read/write, subscriptions, method calls, path resolution, connection state, security, type serialization, session persistence, session recovery, and all v4.0.0 DTOs.
345
+
380+ tests (unit + integration). Integration tests run against [uanetstandard-test-suite](https://github.com/php-opcua/uanetstandard-test-suite) — a Docker-based OPC UA environment built on the OPC Foundation's UA-.NETStandard reference implementation — covering browse, read/write, subscriptions, method calls, path resolution, connection state, security, type serialization, session persistence, session recovery, and all v4.0.0 DTOs.
300
346
301
347
> **Note on coverage:**`SessionManagerDaemon` is excluded from coverage reports because it runs as a separate long-lived process (ReactPHP event loop). PHP coverage tools (pcov, xdebug) only instrument the test runner process — they cannot track code executing inside a subprocess started via `proc_open()`. The daemon is fully tested by the integration suite, which starts a real daemon, sends IPC commands, and verifies responses. This is a known limitation shared by other daemon-based PHP packages (Laravel Horizon, Symfony Messenger, RoadRunner workers).
When the daemon is started with a PSR-14 `EventDispatcherInterface` and `autoPublish: true`, it automatically manages the publish cycle for sessions with active subscriptions.
309
+
310
+
### How It Works
311
+
312
+
1. A session creates its first subscription via `createSubscription()` → the daemon starts an auto-publish timer for that session
313
+
2. The timer calls `Client::publish()`, which dispatches PSR-14 events internally: `DataChangeReceived`, `EventNotificationReceived`, `AlarmActivated`, `SubscriptionKeepAlive`, etc.
314
+
3. Acknowledgements are tracked and sent automatically on the next publish call
315
+
4. When `moreNotifications` is `true`, the next publish is scheduled with near-zero delay to drain queued notifications quickly
316
+
5. When all subscriptions are deleted, the auto-publish timer is stopped
317
+
318
+
### Timer Scheduling
319
+
320
+
Auto-publish uses self-rescheduling one-shot timers (not periodic timers) to avoid callback accumulation when `publish()` blocks. The next timer delay depends on the result:
`Client::publish()` is a synchronous call that blocks the ReactPHP event loop until the OPC UA server responds with a notification or a keep-alive. The maximum block duration is bounded by `maxKeepAliveCount × publishingInterval` (default: 10 × 500ms = 5s). IPC requests queue during the block but are not lost (30s IPC timeout). To minimize blocking, use a lower `maxKeepAliveCount` (e.g., 3–5).
333
+
334
+
### Manual Publish Blocking
335
+
336
+
When auto-publish is active for a session, manual `publish()` calls via IPC return an `auto_publish_active` error. This prevents conflicting publish cycles.
337
+
338
+
### Programmatic Configuration
339
+
340
+
```php
341
+
use PhpOpcua\SessionManager\Daemon\SessionManagerDaemon;
The daemon can auto-connect to pre-configured endpoints and register subscriptions at startup. Combined with auto-publish, this enables fully declarative monitoring with zero application code.
Connections are established on the first event loop tick after the daemon starts. Failed connections are logged but do not prevent the daemon from starting.
Start the daemon with auto-publish enabled. The client's existing PSR-14 events are dispatched automatically for every subscription notification — no manual `publish()` loop needed.
223
+
224
+
```php
225
+
use PhpOpcua\Client\Event\DataChangeReceived;
226
+
use PhpOpcua\Client\Event\EventNotificationReceived;
227
+
use PhpOpcua\Client\Event\AlarmActivated;
228
+
use PhpOpcua\SessionManager\Daemon\SessionManagerDaemon;
$dispatcher->listen(AlarmActivated::class, function (AlarmActivated $e) {
278
+
echo "ALARM from {$e->sourceName}: {$e->message} (severity: {$e->severity})\n";
279
+
});
280
+
```
281
+
282
+
## Auto-Publish with Runtime Subscriptions
283
+
284
+
Auto-publish also works for subscriptions created at runtime via `ManagedClient`. Any session that creates a subscription gets auto-published automatically:
systemd or Supervisor supported. See doc/daemon.md for examples.
94
95
96
+
### Auto-Publish
97
+
98
+
When `autoPublish: true` and an `EventDispatcherInterface` is provided, the daemon automatically calls `publish()` for sessions with active subscriptions. The client's internal `dispatchPublishEvents()` fires PSR-14 events (`DataChangeReceived`, `EventNotificationReceived`, `AlarmActivated`, `SubscriptionKeepAlive`, etc.) — no manual publish loop needed.
99
+
100
+
```php
101
+
$daemon = new SessionManagerDaemon(
102
+
socketPath: '/tmp/opcua.sock',
103
+
clientEventDispatcher: $dispatcher,
104
+
autoPublish: true,
105
+
);
106
+
$daemon->run();
107
+
```
108
+
109
+
Acknowledgements are tracked internally. Self-rescheduling timers adapt to each session's minimum publishing interval. When `moreNotifications` is true, the next publish fires with 10ms delay. On connection errors, recovery is attempted automatically. After 5 consecutive generic errors, auto-publish stops for that session.
110
+
111
+
Manual `publish()` via IPC is blocked while auto-publish is active (returns `auto_publish_active` error).
112
+
113
+
### Auto-Connect
114
+
115
+
Pre-configure connections to auto-connect at daemon startup:
0 commit comments