JavaScript client for SEN from Node.js.
SEN is a general-purpose, distributed, object-oriented system for applications that demand high modularity and rich communication.
import { Sen } from 'sen-ether-client';
const sen = await Sen.connect();
const diagnostics = await sen.interest('SELECT * FROM hmi.diagnostics');
const probe = await diagnostics.waitFor('EtherProbe');
probe.on('change:label', ({ value }) => {
console.log('label changed:', value);
});
console.log(await probe.get('label'));
await probe.set('label', 'from-js');
console.log(await probe.call('ping', ['hello']));
await sen.close();npm install sen-ether-clientsen-ether-client speaks SEN ether directly, so compatibility is tied to SEN protocol
versions, not to a specific SEN release name.
| sen-ether-client | Kernel protocol | Ether protocol |
|---|---|---|
| 0.1.x | 9 | 2 |
If a remote kernel reports another kernel or ether protocol version during the
SEN handshake, sen-ether-client treats it as incompatible until that protocol version
is explicitly supported.
The protocol STL files used to maintain this codec are shipped in
resources/protocol. The source SEN release recorded there is informational;
runtime compatibility is checked only with the kernel and ether protocol
numbers announced by the remote process.
The runtime imports generated protocol constants from those STL files, so the hot path does not parse STL while receiving updates.
By default, sen-ether-client uses SEN ether multicast discovery and connects to the
visible SEN processes:
const sen = await Sen.connect();If your SEN ether discovery port is configured through SEN's environment,
sen-ether-client reads the same variable:
export SEN_ETHER_DISCOVERY_PORT=60543For machines with more than one network interface, select the multicast interface explicitly with either its IPv4 address or interface name:
const sen = await Sen.connect({ interfaceAddress: 'enp0s25' });If your setup uses a SEN TCP discovery hub instead of multicast, pass it explicitly:
const sen = await Sen.connect({
tcpHub: '127.0.0.1:65222'
});Connected sessions are monitored through SEN ether presence beams. If the
remote process stops announcing itself for presenceTimeoutMs milliseconds
(default 5000), the client closes the stale connection and restarts the
configured interests.
sen-ether-client can work with several SEN sessions from the same client. The session
is inferred from the query:
const hmi = await sen.interest('SELECT * FROM hmi.diagnostics');
const world = await sen.interest('SELECT * FROM world1.environment');You can also navigate explicitly through sessions and buses:
const sen = await Sen.connect();
console.log(sen.listSessions());
console.log(await sen.discoverBuses());
// [{ session: 'hmi', bus: 'diagnostics', qualified: 'hmi.diagnostics' }]
const hmi = await sen.session('hmi');
console.log(hmi.listBuses());
const diagnostics = await hmi.bus('diagnostics');
const probe = await diagnostics.waitFor('EtherProbe');discoverBuses() does not create interests and does not join any SEN bus. It
uses discovery to find sessions and opens lightweight process connections only
to read bus announcements. If buses are not announced immediately after the
process connection, it waits up to busDiscoverySettleMs milliseconds.
You can also connect to one explicit session:
const hmi = await Sen.connect({
session: 'hmi'
});
const diagnostics = await hmi.interest('SELECT * FROM hmi.diagnostics');Create an interest with a normal SEN query:
const tracks = await sen.interest('SELECT * FROM world1.environment');Listen for objects and changes:
tracks.on('object', object => {
console.log(object.name, object.className);
});
tracks.on('change', ({ object, name, value }) => {
console.log(object.name, name, value);
});For browser gateways or high-frequency telemetry, batch changes and decode only the properties needed by the UI:
const tracks = await sen.interest('SELECT hmi.tactical.BaseTrack FROM hmi.loadtest', {
properties: ['latitude', 'longitude', 'altitude', 'trackHeading'],
changeMode: 'batch',
coalesce: true
});
tracks.on('changes', ({ changes }) => {
websocket.send(JSON.stringify(changes.map(({ object, name, value, timestampNs }) => ({
object: object.name,
name,
value,
timestampNs: timestampNs?.toString()
}))));
});Get an object by name, id, class name, or predicate:
const aircraft = await tracks.waitFor('blue-air-1');
const firstAircraft = await tracks.waitFor(
object => object.className === 'rpr.Aircraft'
);Read and write properties:
const label = await probe.get('label');
await probe.set('label', 'ready');Call methods:
const result = await probe.call('ping', ['hello']);Subscribe to property changes:
probe.on('change:label', ({ value, previous, timestampNs }) => {
console.log(previous, '->', value, timestampNs);
});SEN timestamps are exposed as nanosecond BigInt values (timestampNs) so the
64-bit source timestamp is not rounded by JavaScript numbers.
Subscribe to SEN runtime events:
probe.on('probeEvent', event => {
console.log(event.args);
});List visible SEN processes:
npx sen-ether-scan --tcp-hub 127.0.0.1:65222 --timeout 3000Probe a bus:
npx sen-ether-probe \
--tcp-hub 127.0.0.1:65222 \
--bus hmi.diagnosticsThe public import is:
import { Sen, SenInterest, SenRemoteObject } from 'sen-ether-client';See API.md for the complete public interface.