sen-ether-client 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/API.md ADDED
@@ -0,0 +1,239 @@
1
+ # sen-ether-client API
2
+
3
+ Public import:
4
+
5
+ ```js
6
+ import { Sen, SenInterest, SenRemoteObject } from 'sen-ether-client';
7
+ ```
8
+
9
+ ## Compatibility
10
+
11
+ `sen-ether-client@0.1.x` supports:
12
+
13
+ - kernel protocol `9`
14
+ - ether protocol `2`
15
+
16
+ These protocol versions are checked during the SEN handshake. A different
17
+ kernel or ether protocol should be treated as unsupported unless `sen-ether-client`
18
+ explicitly adds support for it.
19
+
20
+ The protocol STL files are included in `resources/protocol` as the source for
21
+ the codec. The SEN release noted in that folder is informational; it is not a
22
+ compatibility check.
23
+
24
+ The generated protocol module is loaded at runtime; STL parsing is a maintenance
25
+ step, not part of connection or message decoding.
26
+
27
+ ## Sen
28
+
29
+ ```js
30
+ const sen = await Sen.connect();
31
+
32
+ // with explicit options:
33
+ const sen = new Sen(options);
34
+ await sen.connect(options);
35
+ ```
36
+
37
+ Connection options:
38
+
39
+ - `interfaceAddress`: local interface address or interface name for multicast
40
+ discovery.
41
+ - `tcpHub`: optional SEN TCP discovery hub as `host:port`. If omitted,
42
+ multicast discovery is used.
43
+ - `session`: optional SEN session name. If omitted, `Sen` can use queries for
44
+ different sessions and connects to each one on demand.
45
+ - `timeout`: discovery and operation timeout in ms.
46
+ - `discoverySettleMs`: discovery settle time after the first process is found.
47
+ Defaults to `100`.
48
+ - `reconnect`: whether to reconnect and restart interests.
49
+ - `reconnectDelayMs`: delay between reconnect attempts.
50
+ - `maxReconnectAttempts`: maximum reconnect attempts.
51
+ - `participantReadyTimeoutMs`: short non-fatal grace timeout for bus
52
+ participant acknowledgements. Defaults to `1000`.
53
+ - `socketKeepAlive`: enable TCP keepalive. Defaults to `true`.
54
+ - `socketIdleTimeoutMs`: optional TCP idle timeout. Defaults to `0` because
55
+ valid SEN connections can be quiet on TCP while bus data flows separately.
56
+
57
+ `Sen.connect()` uses multicast discovery. `sen-ether-client` reads this SEN environment
58
+ variable as its multicast default:
59
+
60
+ - `SEN_ETHER_DISCOVERY_PORT`
61
+
62
+ Multicast group, bind address and interface selection are explicit `sen-ether-client`
63
+ options, not SEN environment variables.
64
+
65
+ Preferred multi-session usage:
66
+
67
+ ```js
68
+ const sen = await Sen.connect();
69
+
70
+ const hmi = await sen.interest('SELECT * FROM hmi.diagnostics');
71
+ const world = await sen.interest('SELECT * FROM world1.environment');
72
+ ```
73
+
74
+ TCP discovery hub usage:
75
+
76
+ ```js
77
+ const sen = await Sen.connect({ tcpHub: '127.0.0.1:65222' });
78
+ ```
79
+
80
+ Explicit single-session usage is still supported:
81
+
82
+ ```js
83
+ const hmi = await Sen.connect({ session: 'hmi' });
84
+ await hmi.interest('SELECT * FROM hmi.diagnostics');
85
+ ```
86
+
87
+ If a SEN bus name itself contains dots and is not a session-qualified bus, pass
88
+ it explicitly. This is useful for standalone scenarios that run in one Ether
89
+ session but publish on a bus such as `scenario.environment`:
90
+
91
+ ```js
92
+ const scenario = await Sen.connect({
93
+ session: 'scenario'
94
+ });
95
+
96
+ const objects = await scenario.interest('SELECT * FROM scenario.environment', {
97
+ bus: 'scenario.environment',
98
+ forceBus: true
99
+ });
100
+ ```
101
+
102
+ Main methods:
103
+
104
+ - `await sen.connect(options)`
105
+ - `await sen.interest(query, options)`
106
+ - `await sen.session(name)`
107
+ - `sen.listSessions()`
108
+ - `sen.listBuses(options)`
109
+ - `await sen.bus(name, options)`
110
+ - `sen.objects()`
111
+ - `sen.getObject(selector)`
112
+ - `await sen.waitForObject(selector, options)`
113
+ - `await sen.close()`
114
+
115
+ Session and bus navigation:
116
+
117
+ ```js
118
+ const sen = await Sen.connect();
119
+
120
+ for (const sessionName of sen.listSessions()) {
121
+ const session = await sen.session(sessionName);
122
+ console.log(sessionName, session.listBuses());
123
+ }
124
+
125
+ const diagnostics = await sen.session('hmi').then(hmi => hmi.bus('diagnostics'));
126
+ ```
127
+
128
+ Main events:
129
+
130
+ - `connect`
131
+ - `close`
132
+ - `reconnecting`
133
+ - `reconnect`
134
+ - `reconnectError`
135
+ - `warning`
136
+ - `object`
137
+ - `remove`
138
+ - `change`
139
+ - `event`
140
+
141
+ ## SenInterest
142
+
143
+ Returned by `await sen.interest(query)`.
144
+
145
+ ```js
146
+ const interest = await sen.interest('SELECT * FROM hmi.diagnostics');
147
+ const object = await interest.waitFor('EtherProbe');
148
+ ```
149
+
150
+ Main methods:
151
+
152
+ - `interest.objects()`
153
+ - `interest.get(selector)`
154
+ - `await interest.waitFor(selector, options)`
155
+ - `interest.close()`
156
+
157
+ Main events:
158
+
159
+ - `object`
160
+ - `remove`
161
+ - `change`
162
+ - `changes`
163
+ - `event`
164
+ - `stale`
165
+ - `restart`
166
+
167
+ For browser gateways or high-frequency telemetry, request only the properties
168
+ you need and emit batches instead of one JS event per property update:
169
+
170
+ ```js
171
+ const tracks = await sen.interest('SELECT hmi.tactical.BaseTrack FROM hmi.loadtest', {
172
+ properties: ['latitude', 'longitude', 'altitude', 'trackHeading'],
173
+ changeMode: 'batch',
174
+ batchIntervalMs: 16,
175
+ batchMaxSize: 1000,
176
+ maxQueuedChanges: 10000,
177
+ backpressure: 'drop-oldest',
178
+ coalesce: true
179
+ });
180
+
181
+ tracks.on('changes', ({ changes, dropped }) => {
182
+ // Send one compact WebSocket frame to the browser.
183
+ });
184
+ ```
185
+
186
+ `changeMode: 'individual'` is the default and preserves the traditional
187
+ `change`/`change:<property>` events. `changeMode: 'both'` emits both forms.
188
+
189
+ ## SenRemoteObject
190
+
191
+ Returned by `interest.waitFor(...)`, `interest.getObject(...)`, or
192
+ `sen.getObject(...)`.
193
+
194
+ ```js
195
+ console.log(await object.get('label'));
196
+ await object.set('label', 'from-js');
197
+ console.log(await object.call('ping', ['hello']));
198
+ ```
199
+
200
+ Main properties:
201
+
202
+ - `id`
203
+ - `name`
204
+ - `className`
205
+ - `snapshot`
206
+ - `timestampNs`: latest SEN source timestamp as a nanosecond `BigInt`
207
+ - `propertyTimestamps`: `Map<string, bigint>` with the latest known timestamp per property
208
+
209
+ Main methods:
210
+
211
+ - `object.matches(selector)`
212
+ - `await object.waitForType(options)`
213
+ - `await object.get(property)`
214
+ - `object.getPropertyTimestamp(property)`
215
+ - `await object.set(property, value)`
216
+ - `await object.call(method, args)`
217
+
218
+ Main events:
219
+
220
+ - `change`
221
+ - `change:<property>`
222
+ - SEN runtime event names emitted by the remote object.
223
+ - `stale`
224
+
225
+ `change.timestampNs` is also a nanosecond `BigInt`. This keeps SEN's original
226
+ 64-bit timestamp precision. Convert it explicitly at JSON boundaries:
227
+
228
+ ```js
229
+ tracks.on('change', ({ object, name, value, timestampNs }) => {
230
+ websocket.send(JSON.stringify({
231
+ object: object.name,
232
+ name,
233
+ value,
234
+ timestampNs: timestampNs?.toString()
235
+ }));
236
+ });
237
+ ```
238
+
239
+ Low-level protocol modules are intentionally not public API.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Marcos Pérez
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,227 @@
1
+ # sen-ether-client
2
+
3
+ JavaScript client for SEN from Node.js.
4
+
5
+ [SEN](https://github.com/airbus/sen) is a general-purpose, distributed,
6
+ object-oriented system for applications that demand high modularity and rich
7
+ communication.
8
+
9
+ ```js
10
+ import { Sen } from 'sen-ether-client';
11
+
12
+ const sen = await Sen.connect();
13
+
14
+ const diagnostics = await sen.interest('SELECT * FROM hmi.diagnostics');
15
+ const probe = await diagnostics.waitFor('EtherProbe');
16
+
17
+ probe.on('change:label', ({ value }) => {
18
+ console.log('label changed:', value);
19
+ });
20
+
21
+ console.log(await probe.get('label'));
22
+ await probe.set('label', 'from-js');
23
+ console.log(await probe.call('ping', ['hello']));
24
+
25
+ await sen.close();
26
+ ```
27
+
28
+ ## Install
29
+
30
+ ```bash
31
+ npm install sen-ether-client
32
+ ```
33
+
34
+ ## Compatibility
35
+
36
+ `sen-ether-client` speaks SEN ether directly, so compatibility is tied to SEN protocol
37
+ versions, not to a specific SEN release name.
38
+
39
+ | sen-ether-client | Kernel protocol | Ether protocol |
40
+ | --- | ---: | ---: |
41
+ | 0.1.x | 9 | 2 |
42
+
43
+ If a remote kernel reports another kernel or ether protocol version during the
44
+ SEN handshake, `sen-ether-client` treats it as incompatible until that protocol version
45
+ is explicitly supported.
46
+
47
+ The protocol STL files used to maintain this codec are shipped in
48
+ `resources/protocol`. The source SEN release recorded there is informational;
49
+ runtime compatibility is checked only with the kernel and ether protocol
50
+ numbers announced by the remote process.
51
+
52
+ The runtime imports generated protocol constants from those STL files, so the
53
+ hot path does not parse STL while receiving updates.
54
+
55
+ ## Connect
56
+
57
+ By default, `sen-ether-client` uses SEN ether multicast discovery and connects to the
58
+ visible SEN processes:
59
+
60
+ ```js
61
+ const sen = await Sen.connect();
62
+ ```
63
+
64
+ If your SEN ether discovery port is configured through SEN's environment,
65
+ `sen-ether-client` reads the same variable:
66
+
67
+ ```bash
68
+ export SEN_ETHER_DISCOVERY_PORT=60543
69
+ ```
70
+
71
+ For machines with more than one network interface, select the multicast
72
+ interface explicitly with either its IPv4 address or interface name:
73
+
74
+ ```js
75
+ const sen = await Sen.connect({ interfaceAddress: 'enp0s25' });
76
+ ```
77
+
78
+ If your setup uses a SEN TCP discovery hub instead of multicast, pass it
79
+ explicitly:
80
+
81
+ ```js
82
+ const sen = await Sen.connect({
83
+ tcpHub: '127.0.0.1:65222'
84
+ });
85
+ ```
86
+
87
+ `sen-ether-client` can work with several SEN sessions from the same client. The session
88
+ is inferred from the query:
89
+
90
+ ```js
91
+ const hmi = await sen.interest('SELECT * FROM hmi.diagnostics');
92
+ const world = await sen.interest('SELECT * FROM world1.environment');
93
+ ```
94
+
95
+ You can also navigate explicitly through sessions and buses:
96
+
97
+ ```js
98
+ const sen = await Sen.connect();
99
+
100
+ console.log(sen.listSessions());
101
+
102
+ const hmi = await sen.session('hmi');
103
+ console.log(hmi.listBuses());
104
+
105
+ const diagnostics = await hmi.bus('diagnostics');
106
+ const probe = await diagnostics.waitFor('EtherProbe');
107
+ ```
108
+
109
+ You can also connect to one explicit session:
110
+
111
+ ```js
112
+ const hmi = await Sen.connect({
113
+ session: 'hmi'
114
+ });
115
+
116
+ const diagnostics = await hmi.interest('SELECT * FROM hmi.diagnostics');
117
+ ```
118
+
119
+ ## Interests
120
+
121
+ Create an interest with a normal SEN query:
122
+
123
+ ```js
124
+ const tracks = await sen.interest('SELECT * FROM world1.environment');
125
+ ```
126
+
127
+ Listen for objects and changes:
128
+
129
+ ```js
130
+ tracks.on('object', object => {
131
+ console.log(object.name, object.className);
132
+ });
133
+
134
+ tracks.on('change', ({ object, name, value }) => {
135
+ console.log(object.name, name, value);
136
+ });
137
+ ```
138
+
139
+ For browser gateways or high-frequency telemetry, batch changes and decode only
140
+ the properties needed by the UI:
141
+
142
+ ```js
143
+ const tracks = await sen.interest('SELECT hmi.tactical.BaseTrack FROM hmi.loadtest', {
144
+ properties: ['latitude', 'longitude', 'altitude', 'trackHeading'],
145
+ changeMode: 'batch',
146
+ coalesce: true
147
+ });
148
+
149
+ tracks.on('changes', ({ changes }) => {
150
+ websocket.send(JSON.stringify(changes.map(({ object, name, value, timestampNs }) => ({
151
+ object: object.name,
152
+ name,
153
+ value,
154
+ timestampNs: timestampNs?.toString()
155
+ }))));
156
+ });
157
+ ```
158
+
159
+ Get an object by name, id, class name, or predicate:
160
+
161
+ ```js
162
+ const aircraft = await tracks.waitFor('blue-air-1');
163
+
164
+ const firstAircraft = await tracks.waitFor(
165
+ object => object.className === 'rpr.Aircraft'
166
+ );
167
+ ```
168
+
169
+ ## Objects
170
+
171
+ Read and write properties:
172
+
173
+ ```js
174
+ const label = await probe.get('label');
175
+ await probe.set('label', 'ready');
176
+ ```
177
+
178
+ Call methods:
179
+
180
+ ```js
181
+ const result = await probe.call('ping', ['hello']);
182
+ ```
183
+
184
+ Subscribe to property changes:
185
+
186
+ ```js
187
+ probe.on('change:label', ({ value, previous, timestampNs }) => {
188
+ console.log(previous, '->', value, timestampNs);
189
+ });
190
+ ```
191
+
192
+ SEN timestamps are exposed as nanosecond `BigInt` values (`timestampNs`) so the
193
+ 64-bit source timestamp is not rounded by JavaScript numbers.
194
+
195
+ Subscribe to SEN runtime events:
196
+
197
+ ```js
198
+ probe.on('probeEvent', event => {
199
+ console.log(event.args);
200
+ });
201
+ ```
202
+
203
+ ## CLI
204
+
205
+ List visible SEN processes:
206
+
207
+ ```bash
208
+ npx sen-ether-scan --tcp-hub 127.0.0.1:65222 --timeout 3000
209
+ ```
210
+
211
+ Probe a bus:
212
+
213
+ ```bash
214
+ npx sen-ether-probe \
215
+ --tcp-hub 127.0.0.1:65222 \
216
+ --bus hmi.diagnostics
217
+ ```
218
+
219
+ ## API
220
+
221
+ The public import is:
222
+
223
+ ```js
224
+ import { Sen, SenInterest, SenRemoteObject } from 'sen-ether-client';
225
+ ```
226
+
227
+ See [API.md](./API.md) for the complete public interface.