@enyo-energy/energy-app-sdk 0.0.121 → 0.0.122
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/dist/cjs/energy-app-permission.type.cjs +1 -0
- package/dist/cjs/energy-app-permission.type.d.cts +3 -2
- package/dist/cjs/energy-app.cjs +17 -0
- package/dist/cjs/energy-app.d.cts +16 -0
- package/dist/cjs/enyo-energy-app-sdk.d.cts +3 -0
- package/dist/cjs/implementations/udp/EnergyAppUdpServer.cjs +174 -0
- package/dist/cjs/implementations/udp/EnergyAppUdpServer.d.cts +42 -0
- package/dist/cjs/index.cjs +1 -0
- package/dist/cjs/index.d.cts +1 -0
- package/dist/cjs/packages/energy-app-udp.cjs +2 -0
- package/dist/cjs/packages/energy-app-udp.d.cts +110 -0
- package/dist/cjs/version.cjs +1 -1
- package/dist/cjs/version.d.cts +1 -1
- package/dist/energy-app-permission.type.d.ts +3 -2
- package/dist/energy-app-permission.type.js +1 -0
- package/dist/energy-app.d.ts +16 -0
- package/dist/energy-app.js +17 -0
- package/dist/enyo-energy-app-sdk.d.ts +3 -0
- package/dist/implementations/udp/EnergyAppUdpServer.d.ts +42 -0
- package/dist/implementations/udp/EnergyAppUdpServer.js +169 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/packages/energy-app-udp.d.ts +110 -0
- package/dist/packages/energy-app-udp.js +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -43,4 +43,5 @@ var EnergyAppPermissionTypeEnum;
|
|
|
43
43
|
EnergyAppPermissionTypeEnum["Bluetooth"] = "Bluetooth";
|
|
44
44
|
EnergyAppPermissionTypeEnum["Wifi"] = "Wifi";
|
|
45
45
|
EnergyAppPermissionTypeEnum["ChildProcess"] = "ChildProcess";
|
|
46
|
+
EnergyAppPermissionTypeEnum["Udp"] = "Udp";
|
|
46
47
|
})(EnergyAppPermissionTypeEnum || (exports.EnergyAppPermissionTypeEnum = EnergyAppPermissionTypeEnum = {}));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type EnergyAppPermissionType = 'RestrictedInternetAccess' | 'NetworkDeviceDiscovery' | 'NetworkDeviceSearch' | 'NetworkDeviceAccess' | 'Modbus' | 'Storage' | 'Appliance' | 'AllAppliances' | 'SendDataBusValues' | 'SubscribeDataBus' | 'SendDataBusCommands' | 'OcppServer' | 'ChargingCard' | 'Vehicle' | 'Charge' | 'SecretManager' | 'LocationZipCode' | 'LocationCoordinates' | 'Timeseries' | 'EnergyManagerInfo' | 'ElectricityTariff' | 'WeatherForecastRegister' | 'WeatherForecastUse' | 'PvForecastRegister' | 'PvForecastUse' | 'PvSystemRegister' | 'PvSystemUse' | 'InverterControlCommands' | 'BatteryControlCommands' | 'ChargerControlCommands' | 'ModbusRtu' | 'EnergyPrices' | 'EnergyManager' | 'EebusDeviceManagement' | 'EebusDataAccess' | 'EebusControl' | 'Mqtt' | 'Bluetooth' | 'Wifi' | 'ChildProcess';
|
|
1
|
+
export type EnergyAppPermissionType = 'RestrictedInternetAccess' | 'NetworkDeviceDiscovery' | 'NetworkDeviceSearch' | 'NetworkDeviceAccess' | 'Modbus' | 'Storage' | 'Appliance' | 'AllAppliances' | 'SendDataBusValues' | 'SubscribeDataBus' | 'SendDataBusCommands' | 'OcppServer' | 'ChargingCard' | 'Vehicle' | 'Charge' | 'SecretManager' | 'LocationZipCode' | 'LocationCoordinates' | 'Timeseries' | 'EnergyManagerInfo' | 'ElectricityTariff' | 'WeatherForecastRegister' | 'WeatherForecastUse' | 'PvForecastRegister' | 'PvForecastUse' | 'PvSystemRegister' | 'PvSystemUse' | 'InverterControlCommands' | 'BatteryControlCommands' | 'ChargerControlCommands' | 'ModbusRtu' | 'EnergyPrices' | 'EnergyManager' | 'EebusDeviceManagement' | 'EebusDataAccess' | 'EebusControl' | 'Mqtt' | 'Bluetooth' | 'Wifi' | 'ChildProcess' | 'Udp';
|
|
2
2
|
export declare enum EnergyAppPermissionTypeEnum {
|
|
3
3
|
RestrictedInternetAccess = "RestrictedInternetAccess",
|
|
4
4
|
NetworkDeviceDiscovery = "NetworkDeviceDiscovery",
|
|
@@ -39,5 +39,6 @@ export declare enum EnergyAppPermissionTypeEnum {
|
|
|
39
39
|
Mqtt = "Mqtt",
|
|
40
40
|
Bluetooth = "Bluetooth",
|
|
41
41
|
Wifi = "Wifi",
|
|
42
|
-
ChildProcess = "ChildProcess"
|
|
42
|
+
ChildProcess = "ChildProcess",
|
|
43
|
+
Udp = "Udp"
|
|
43
44
|
}
|
package/dist/cjs/energy-app.cjs
CHANGED
|
@@ -21,6 +21,7 @@ const version_js_1 = require("./version.cjs");
|
|
|
21
21
|
*/
|
|
22
22
|
class EnergyApp {
|
|
23
23
|
energyAppSdk;
|
|
24
|
+
udpInstance;
|
|
24
25
|
constructor() {
|
|
25
26
|
// in our runtime, there is an instance of energyAppSdk available which needs to be used here
|
|
26
27
|
// if the energyAppSdk is not available, instantiate a mocked version for local development
|
|
@@ -246,6 +247,22 @@ class EnergyApp {
|
|
|
246
247
|
useWifi() {
|
|
247
248
|
return this.energyAppSdk.useWifi();
|
|
248
249
|
}
|
|
250
|
+
/**
|
|
251
|
+
* Gets the UDP communication API for binding sockets and exchanging
|
|
252
|
+
* datagrams. Requires the `Udp` permission to be granted.
|
|
253
|
+
*
|
|
254
|
+
* Lazily instantiates a single {@link EnergyAppUdpServer} on first call
|
|
255
|
+
* and returns the same instance on subsequent calls. The runtime SDK's
|
|
256
|
+
* permission gate is invoked on every call so that revoked permissions
|
|
257
|
+
* are surfaced consistently with the other `use*` accessors.
|
|
258
|
+
*
|
|
259
|
+
* @returns The UDP API instance.
|
|
260
|
+
* @throws {EnergyAppPermissionNotGrantedError} If the `Udp` permission
|
|
261
|
+
* is not granted.
|
|
262
|
+
*/
|
|
263
|
+
useUdp() {
|
|
264
|
+
return this.energyAppSdk.useUdp();
|
|
265
|
+
}
|
|
249
266
|
/**
|
|
250
267
|
* Gets the current SDK version.
|
|
251
268
|
* @returns The semantic version string of the SDK
|
|
@@ -31,6 +31,7 @@ import { EnergyAppBluetooth } from "./packages/energy-app-bluetooth.cjs";
|
|
|
31
31
|
import { EnergyAppDiagnostics } from "./packages/energy-app-diagnostics.cjs";
|
|
32
32
|
import { EnergyAppLearningPhase } from "./packages/energy-app-learning-phase.cjs";
|
|
33
33
|
import { EnergyAppWifi } from "./packages/energy-app-wifi.cjs";
|
|
34
|
+
import { EnergyAppUdp } from "./packages/energy-app-udp.cjs";
|
|
34
35
|
/**
|
|
35
36
|
* Concrete implementation of {@link EnyoEnergyAppSdk} that delegates every call
|
|
36
37
|
* to the runtime-provided `energyAppSdkInstance` global.
|
|
@@ -50,6 +51,7 @@ import { EnergyAppWifi } from "./packages/energy-app-wifi.cjs";
|
|
|
50
51
|
*/
|
|
51
52
|
export declare class EnergyApp implements EnyoEnergyAppSdk {
|
|
52
53
|
private readonly energyAppSdk;
|
|
54
|
+
private udpInstance;
|
|
53
55
|
constructor();
|
|
54
56
|
isSystemOnline(): boolean;
|
|
55
57
|
/**
|
|
@@ -184,6 +186,20 @@ export declare class EnergyApp implements EnyoEnergyAppSdk {
|
|
|
184
186
|
* @returns The WiFi API instance
|
|
185
187
|
*/
|
|
186
188
|
useWifi(): EnergyAppWifi;
|
|
189
|
+
/**
|
|
190
|
+
* Gets the UDP communication API for binding sockets and exchanging
|
|
191
|
+
* datagrams. Requires the `Udp` permission to be granted.
|
|
192
|
+
*
|
|
193
|
+
* Lazily instantiates a single {@link EnergyAppUdpServer} on first call
|
|
194
|
+
* and returns the same instance on subsequent calls. The runtime SDK's
|
|
195
|
+
* permission gate is invoked on every call so that revoked permissions
|
|
196
|
+
* are surfaced consistently with the other `use*` accessors.
|
|
197
|
+
*
|
|
198
|
+
* @returns The UDP API instance.
|
|
199
|
+
* @throws {EnergyAppPermissionNotGrantedError} If the `Udp` permission
|
|
200
|
+
* is not granted.
|
|
201
|
+
*/
|
|
202
|
+
useUdp(): EnergyAppUdp;
|
|
187
203
|
/**
|
|
188
204
|
* Gets the current SDK version.
|
|
189
205
|
* @returns The semantic version string of the SDK
|
|
@@ -30,6 +30,7 @@ import { EnergyAppBluetooth } from "./packages/energy-app-bluetooth.cjs";
|
|
|
30
30
|
import { EnergyAppDiagnostics } from "./packages/energy-app-diagnostics.cjs";
|
|
31
31
|
import { EnergyAppLearningPhase } from "./packages/energy-app-learning-phase.cjs";
|
|
32
32
|
import { EnergyAppWifi } from "./packages/energy-app-wifi.cjs";
|
|
33
|
+
import { EnergyAppUdp } from "./packages/energy-app-udp.cjs";
|
|
33
34
|
export declare enum EnergyAppStateEnum {
|
|
34
35
|
Launching = "launching",
|
|
35
36
|
Running = "running",
|
|
@@ -118,4 +119,6 @@ export interface EnyoEnergyAppSdk {
|
|
|
118
119
|
useLearningPhase: () => EnergyAppLearningPhase;
|
|
119
120
|
/** Get the WiFi API for scanning and listing known SSIDs */
|
|
120
121
|
useWifi: () => EnergyAppWifi;
|
|
122
|
+
/** Get the UDP communication API for binding sockets and exchanging datagrams */
|
|
123
|
+
useUdp: () => EnergyAppUdp;
|
|
121
124
|
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EnergyAppUdpServer = exports.EnergyAppUdpBindError = void 0;
|
|
4
|
+
const node_dgram_1 = require("node:dgram");
|
|
5
|
+
const node_crypto_1 = require("node:crypto");
|
|
6
|
+
/**
|
|
7
|
+
* Error thrown when a UDP bind fails because the requested port is already
|
|
8
|
+
* in use (EADDRINUSE on the underlying socket).
|
|
9
|
+
*
|
|
10
|
+
* Other bind errors are surfaced as their original Error so this type stays
|
|
11
|
+
* a precise signal that "the port is taken".
|
|
12
|
+
*/
|
|
13
|
+
class EnergyAppUdpBindError extends Error {
|
|
14
|
+
port;
|
|
15
|
+
cause;
|
|
16
|
+
/**
|
|
17
|
+
* @param port - The port the bind was attempted on.
|
|
18
|
+
* @param cause - The original error emitted by the dgram socket.
|
|
19
|
+
*/
|
|
20
|
+
constructor(port, cause) {
|
|
21
|
+
super(`Failed to bind UDP socket on port ${port}: address in use`);
|
|
22
|
+
this.port = port;
|
|
23
|
+
this.cause = cause;
|
|
24
|
+
this.name = 'EnergyAppUdpBindError';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
exports.EnergyAppUdpBindError = EnergyAppUdpBindError;
|
|
28
|
+
/**
|
|
29
|
+
* Default implementation of {@link EnergyAppUdpSocket} backed by `node:dgram`.
|
|
30
|
+
*
|
|
31
|
+
* Manages a single dgram socket and dispatches inbound messages to all
|
|
32
|
+
* registered handlers. Created internally by {@link EnergyAppUdpServer.bind}.
|
|
33
|
+
*/
|
|
34
|
+
class EnergyAppUdpSocketImpl {
|
|
35
|
+
socket;
|
|
36
|
+
handlers = new Map();
|
|
37
|
+
closed = false;
|
|
38
|
+
/**
|
|
39
|
+
* @param socket - The bound dgram socket this wrapper owns.
|
|
40
|
+
*/
|
|
41
|
+
constructor(socket) {
|
|
42
|
+
this.socket = socket;
|
|
43
|
+
this.socket.on('message', (data, rinfo) => {
|
|
44
|
+
const msg = {
|
|
45
|
+
data,
|
|
46
|
+
remotePort: rinfo.port,
|
|
47
|
+
remoteAddress: rinfo.address,
|
|
48
|
+
};
|
|
49
|
+
for (const handler of this.handlers.values()) {
|
|
50
|
+
handler(msg);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Returns the locally bound address descriptor. The dgram socket reports
|
|
56
|
+
* the kernel-assigned port if the bind was issued with port 0.
|
|
57
|
+
*
|
|
58
|
+
* @throws Error if the socket has been closed.
|
|
59
|
+
*/
|
|
60
|
+
address() {
|
|
61
|
+
if (this.closed) {
|
|
62
|
+
throw new Error('UDP socket is closed');
|
|
63
|
+
}
|
|
64
|
+
const info = this.socket.address();
|
|
65
|
+
return {
|
|
66
|
+
address: info.address,
|
|
67
|
+
port: info.port,
|
|
68
|
+
family: info.family,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Register an inbound-message handler.
|
|
73
|
+
*
|
|
74
|
+
* @param handler - Callback invoked for every received datagram.
|
|
75
|
+
* @returns A handle id that can be passed to {@link off}.
|
|
76
|
+
*/
|
|
77
|
+
onMessage(handler) {
|
|
78
|
+
const id = (0, node_crypto_1.randomUUID)();
|
|
79
|
+
this.handlers.set(id, handler);
|
|
80
|
+
return id;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Remove a previously registered handler. Unknown ids are ignored.
|
|
84
|
+
*
|
|
85
|
+
* @param handlerId - The id returned by {@link onMessage}.
|
|
86
|
+
*/
|
|
87
|
+
off(handlerId) {
|
|
88
|
+
this.handlers.delete(handlerId);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Send a datagram. Resolves when the dgram callback fires with no error.
|
|
92
|
+
*
|
|
93
|
+
* @param data - Payload bytes.
|
|
94
|
+
* @param port - Destination port.
|
|
95
|
+
* @param address - Destination host/IP.
|
|
96
|
+
* @returns Promise that resolves once the kernel accepted the datagram.
|
|
97
|
+
* @throws Error if the socket has been closed or the send call fails.
|
|
98
|
+
*/
|
|
99
|
+
send(data, port, address) {
|
|
100
|
+
if (this.closed) {
|
|
101
|
+
return Promise.reject(new Error('UDP socket is closed'));
|
|
102
|
+
}
|
|
103
|
+
return new Promise((resolve, reject) => {
|
|
104
|
+
this.socket.send(data, port, address, (err) => {
|
|
105
|
+
if (err) {
|
|
106
|
+
reject(err);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
resolve();
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Close the underlying dgram socket. Idempotent.
|
|
115
|
+
*
|
|
116
|
+
* @returns Promise that resolves once the dgram `close` event fires (or
|
|
117
|
+
* immediately if the socket was already closed).
|
|
118
|
+
*/
|
|
119
|
+
close() {
|
|
120
|
+
if (this.closed) {
|
|
121
|
+
return Promise.resolve();
|
|
122
|
+
}
|
|
123
|
+
this.closed = true;
|
|
124
|
+
return new Promise((resolve) => {
|
|
125
|
+
this.socket.once('close', () => resolve());
|
|
126
|
+
this.socket.close();
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Default implementation of {@link EnergyAppUdp}.
|
|
132
|
+
*
|
|
133
|
+
* Thin wrapper around `node:dgram` that creates IPv4 UDP sockets. Used as the
|
|
134
|
+
* concrete instance returned by {@link EnergyApp.useUdp}.
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* ```typescript
|
|
138
|
+
* const udp = new EnergyAppUdpServer();
|
|
139
|
+
* const socket = await udp.bind({ port: 9000, reuseAddr: true });
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
class EnergyAppUdpServer {
|
|
143
|
+
/**
|
|
144
|
+
* Bind a UDP socket using `node:dgram`.
|
|
145
|
+
*
|
|
146
|
+
* Resolves once the `listening` event fires. If the underlying socket
|
|
147
|
+
* emits an `error` with code `EADDRINUSE`, the promise rejects with
|
|
148
|
+
* {@link EnergyAppUdpBindError}; other errors propagate unchanged.
|
|
149
|
+
*
|
|
150
|
+
* @param options - Bind configuration (port, host, reuseAddr).
|
|
151
|
+
* @returns A bound {@link EnergyAppUdpSocket}.
|
|
152
|
+
*/
|
|
153
|
+
bind(options) {
|
|
154
|
+
return new Promise((resolve, reject) => {
|
|
155
|
+
const socket = (0, node_dgram_1.createSocket)({ type: 'udp4', reuseAddr: false });
|
|
156
|
+
const onError = (err) => {
|
|
157
|
+
socket.removeListener('listening', onListening);
|
|
158
|
+
if (err.code === 'EADDRINUSE') {
|
|
159
|
+
reject(new EnergyAppUdpBindError(options.port, err));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
reject(err);
|
|
163
|
+
};
|
|
164
|
+
const onListening = () => {
|
|
165
|
+
socket.removeListener('error', onError);
|
|
166
|
+
resolve(new EnergyAppUdpSocketImpl(socket));
|
|
167
|
+
};
|
|
168
|
+
socket.once('error', onError);
|
|
169
|
+
socket.once('listening', onListening);
|
|
170
|
+
socket.bind(options.port, '0.0.0.0');
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
exports.EnergyAppUdpServer = EnergyAppUdpServer;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { EnergyAppUdp, EnergyAppUdpSocket, UdpBindOptions } from '../../packages/energy-app-udp.cjs';
|
|
2
|
+
/**
|
|
3
|
+
* Error thrown when a UDP bind fails because the requested port is already
|
|
4
|
+
* in use (EADDRINUSE on the underlying socket).
|
|
5
|
+
*
|
|
6
|
+
* Other bind errors are surfaced as their original Error so this type stays
|
|
7
|
+
* a precise signal that "the port is taken".
|
|
8
|
+
*/
|
|
9
|
+
export declare class EnergyAppUdpBindError extends Error {
|
|
10
|
+
readonly port: number;
|
|
11
|
+
readonly cause: Error;
|
|
12
|
+
/**
|
|
13
|
+
* @param port - The port the bind was attempted on.
|
|
14
|
+
* @param cause - The original error emitted by the dgram socket.
|
|
15
|
+
*/
|
|
16
|
+
constructor(port: number, cause: Error);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Default implementation of {@link EnergyAppUdp}.
|
|
20
|
+
*
|
|
21
|
+
* Thin wrapper around `node:dgram` that creates IPv4 UDP sockets. Used as the
|
|
22
|
+
* concrete instance returned by {@link EnergyApp.useUdp}.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* const udp = new EnergyAppUdpServer();
|
|
27
|
+
* const socket = await udp.bind({ port: 9000, reuseAddr: true });
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare class EnergyAppUdpServer implements EnergyAppUdp {
|
|
31
|
+
/**
|
|
32
|
+
* Bind a UDP socket using `node:dgram`.
|
|
33
|
+
*
|
|
34
|
+
* Resolves once the `listening` event fires. If the underlying socket
|
|
35
|
+
* emits an `error` with code `EADDRINUSE`, the promise rejects with
|
|
36
|
+
* {@link EnergyAppUdpBindError}; other errors propagate unchanged.
|
|
37
|
+
*
|
|
38
|
+
* @param options - Bind configuration (port, host, reuseAddr).
|
|
39
|
+
* @returns A bound {@link EnergyAppUdpSocket}.
|
|
40
|
+
*/
|
|
41
|
+
bind(options: UdpBindOptions): Promise<EnergyAppUdpSocket>;
|
|
42
|
+
}
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -59,6 +59,7 @@ __exportStar(require("./types/enyo-learning-phase.cjs"), exports);
|
|
|
59
59
|
__exportStar(require("./packages/energy-app-learning-phase.cjs"), exports);
|
|
60
60
|
__exportStar(require("./types/enyo-wifi.cjs"), exports);
|
|
61
61
|
__exportStar(require("./packages/energy-app-wifi.cjs"), exports);
|
|
62
|
+
__exportStar(require("./packages/energy-app-udp.cjs"), exports);
|
|
62
63
|
__exportStar(require("./types/enyo-air-conditioning-appliance.cjs"), exports);
|
|
63
64
|
__exportStar(require("./types/enyo-onboarding.cjs"), exports);
|
|
64
65
|
__exportStar(require("./packages/energy-app-onboarding.cjs"), exports);
|
package/dist/cjs/index.d.cts
CHANGED
|
@@ -43,6 +43,7 @@ export * from './types/enyo-learning-phase.cjs';
|
|
|
43
43
|
export * from './packages/energy-app-learning-phase.cjs';
|
|
44
44
|
export * from './types/enyo-wifi.cjs';
|
|
45
45
|
export * from './packages/energy-app-wifi.cjs';
|
|
46
|
+
export * from './packages/energy-app-udp.cjs';
|
|
46
47
|
export * from './types/enyo-air-conditioning-appliance.cjs';
|
|
47
48
|
export * from './types/enyo-onboarding.cjs';
|
|
48
49
|
export * from './packages/energy-app-onboarding.cjs';
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options used when binding a UDP socket via {@link EnergyAppUdp.bind}.
|
|
3
|
+
*/
|
|
4
|
+
export interface UdpBindOptions {
|
|
5
|
+
/** Bind port. 0 selects an ephemeral port. */
|
|
6
|
+
port: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* A single inbound datagram delivered to a handler registered with
|
|
10
|
+
* {@link EnergyAppUdpSocket.onMessage}.
|
|
11
|
+
*/
|
|
12
|
+
export interface UdpInboundMessage {
|
|
13
|
+
/** Raw payload bytes of the datagram. */
|
|
14
|
+
data: Buffer;
|
|
15
|
+
/** Remote port the datagram originated from. */
|
|
16
|
+
remotePort: number;
|
|
17
|
+
/** Remote address (IPv4/IPv6 textual form) the datagram originated from. */
|
|
18
|
+
remoteAddress: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Represents an active UDP socket bound to a local address/port.
|
|
22
|
+
*
|
|
23
|
+
* Obtained by calling {@link EnergyAppUdp.bind}. Provides methods for sending
|
|
24
|
+
* datagrams, subscribing to inbound messages, querying the resolved local
|
|
25
|
+
* address, and closing the socket.
|
|
26
|
+
*/
|
|
27
|
+
export interface EnergyAppUdpSocket {
|
|
28
|
+
/**
|
|
29
|
+
* The address the socket is actually bound to.
|
|
30
|
+
* If port 0 was requested, the kernel-assigned port is returned here.
|
|
31
|
+
*
|
|
32
|
+
* @returns The bound local address descriptor.
|
|
33
|
+
* @throws If the socket has been closed.
|
|
34
|
+
*/
|
|
35
|
+
address(): {
|
|
36
|
+
address: string;
|
|
37
|
+
port: number;
|
|
38
|
+
family: 'IPv4' | 'IPv6';
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Subscribe to inbound datagrams.
|
|
42
|
+
*
|
|
43
|
+
* Multiple handlers can be attached and each will receive a copy of every
|
|
44
|
+
* inbound message. The returned handle can be passed to {@link off} to
|
|
45
|
+
* stop receiving messages on that subscription.
|
|
46
|
+
*
|
|
47
|
+
* @param handler - Callback invoked for every datagram received.
|
|
48
|
+
* @returns A unique handle that identifies this subscription.
|
|
49
|
+
*/
|
|
50
|
+
onMessage(handler: (msg: UdpInboundMessage) => void): string;
|
|
51
|
+
/**
|
|
52
|
+
* Stop listening — removes a previously registered handler.
|
|
53
|
+
*
|
|
54
|
+
* @param handlerId - The id returned by {@link onMessage}. Unknown ids
|
|
55
|
+
* are ignored.
|
|
56
|
+
*/
|
|
57
|
+
off(handlerId: string): void;
|
|
58
|
+
/**
|
|
59
|
+
* Send a datagram to the given peer.
|
|
60
|
+
*
|
|
61
|
+
* @param data - Raw payload bytes to transmit.
|
|
62
|
+
* @param port - Destination port on the peer.
|
|
63
|
+
* @param address - Destination address on the peer (IPv4/IPv6 textual form
|
|
64
|
+
* or hostname).
|
|
65
|
+
* @returns A promise that resolves once the kernel has accepted the
|
|
66
|
+
* datagram and rejects if the underlying send call fails.
|
|
67
|
+
*/
|
|
68
|
+
send(data: Buffer, port: number, address: string): Promise<void>;
|
|
69
|
+
/**
|
|
70
|
+
* Close the socket and release the bound port.
|
|
71
|
+
*
|
|
72
|
+
* Subsequent calls to {@link send} reject and {@link address} throws.
|
|
73
|
+
* Idempotent close-after-close resolves without error.
|
|
74
|
+
*
|
|
75
|
+
* @returns A promise that resolves once the underlying socket has emitted
|
|
76
|
+
* its `close` event.
|
|
77
|
+
*/
|
|
78
|
+
close(): Promise<void>;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Interface for raw UDP communication in enyo packages.
|
|
82
|
+
*
|
|
83
|
+
* Provides a thin abstraction over `node:dgram` for binding sockets and
|
|
84
|
+
* exchanging datagrams. Requires the package to declare the `Udp` permission.
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```typescript
|
|
88
|
+
* const udp = energyApp.useUdp();
|
|
89
|
+
* const socket = await udp.bind({ port: 0 });
|
|
90
|
+
* const handlerId = socket.onMessage((msg) => {
|
|
91
|
+
* console.log(`Received ${msg.data.length} bytes from ${msg.remoteAddress}:${msg.remotePort}`);
|
|
92
|
+
* });
|
|
93
|
+
* await socket.send(Buffer.from('hello'), 5005, '192.168.1.42');
|
|
94
|
+
* socket.off(handlerId);
|
|
95
|
+
* await socket.close();
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
export interface EnergyAppUdp {
|
|
99
|
+
/**
|
|
100
|
+
* Bind a UDP socket on the requested host/port.
|
|
101
|
+
*
|
|
102
|
+
* Resolves once the underlying socket has emitted its `listening` event.
|
|
103
|
+
*
|
|
104
|
+
* @param options - Bind configuration (see {@link UdpBindOptions}).
|
|
105
|
+
* @returns A bound socket ready to send and receive datagrams.
|
|
106
|
+
* @throws {EnergyAppUdpBindError} If the requested port is already in use
|
|
107
|
+
* (EADDRINUSE). Other errors propagate as their original Error.
|
|
108
|
+
*/
|
|
109
|
+
bind(options: UdpBindOptions): Promise<EnergyAppUdpSocket>;
|
|
110
|
+
}
|
package/dist/cjs/version.cjs
CHANGED
|
@@ -9,7 +9,7 @@ exports.getSdkVersion = getSdkVersion;
|
|
|
9
9
|
/**
|
|
10
10
|
* Current version of the enyo Energy App SDK.
|
|
11
11
|
*/
|
|
12
|
-
exports.SDK_VERSION = '0.0.
|
|
12
|
+
exports.SDK_VERSION = '0.0.122';
|
|
13
13
|
/**
|
|
14
14
|
* Gets the current SDK version.
|
|
15
15
|
* @returns The semantic version string of the SDK
|
package/dist/cjs/version.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type EnergyAppPermissionType = 'RestrictedInternetAccess' | 'NetworkDeviceDiscovery' | 'NetworkDeviceSearch' | 'NetworkDeviceAccess' | 'Modbus' | 'Storage' | 'Appliance' | 'AllAppliances' | 'SendDataBusValues' | 'SubscribeDataBus' | 'SendDataBusCommands' | 'OcppServer' | 'ChargingCard' | 'Vehicle' | 'Charge' | 'SecretManager' | 'LocationZipCode' | 'LocationCoordinates' | 'Timeseries' | 'EnergyManagerInfo' | 'ElectricityTariff' | 'WeatherForecastRegister' | 'WeatherForecastUse' | 'PvForecastRegister' | 'PvForecastUse' | 'PvSystemRegister' | 'PvSystemUse' | 'InverterControlCommands' | 'BatteryControlCommands' | 'ChargerControlCommands' | 'ModbusRtu' | 'EnergyPrices' | 'EnergyManager' | 'EebusDeviceManagement' | 'EebusDataAccess' | 'EebusControl' | 'Mqtt' | 'Bluetooth' | 'Wifi' | 'ChildProcess';
|
|
1
|
+
export type EnergyAppPermissionType = 'RestrictedInternetAccess' | 'NetworkDeviceDiscovery' | 'NetworkDeviceSearch' | 'NetworkDeviceAccess' | 'Modbus' | 'Storage' | 'Appliance' | 'AllAppliances' | 'SendDataBusValues' | 'SubscribeDataBus' | 'SendDataBusCommands' | 'OcppServer' | 'ChargingCard' | 'Vehicle' | 'Charge' | 'SecretManager' | 'LocationZipCode' | 'LocationCoordinates' | 'Timeseries' | 'EnergyManagerInfo' | 'ElectricityTariff' | 'WeatherForecastRegister' | 'WeatherForecastUse' | 'PvForecastRegister' | 'PvForecastUse' | 'PvSystemRegister' | 'PvSystemUse' | 'InverterControlCommands' | 'BatteryControlCommands' | 'ChargerControlCommands' | 'ModbusRtu' | 'EnergyPrices' | 'EnergyManager' | 'EebusDeviceManagement' | 'EebusDataAccess' | 'EebusControl' | 'Mqtt' | 'Bluetooth' | 'Wifi' | 'ChildProcess' | 'Udp';
|
|
2
2
|
export declare enum EnergyAppPermissionTypeEnum {
|
|
3
3
|
RestrictedInternetAccess = "RestrictedInternetAccess",
|
|
4
4
|
NetworkDeviceDiscovery = "NetworkDeviceDiscovery",
|
|
@@ -39,5 +39,6 @@ export declare enum EnergyAppPermissionTypeEnum {
|
|
|
39
39
|
Mqtt = "Mqtt",
|
|
40
40
|
Bluetooth = "Bluetooth",
|
|
41
41
|
Wifi = "Wifi",
|
|
42
|
-
ChildProcess = "ChildProcess"
|
|
42
|
+
ChildProcess = "ChildProcess",
|
|
43
|
+
Udp = "Udp"
|
|
43
44
|
}
|
|
@@ -40,4 +40,5 @@ export var EnergyAppPermissionTypeEnum;
|
|
|
40
40
|
EnergyAppPermissionTypeEnum["Bluetooth"] = "Bluetooth";
|
|
41
41
|
EnergyAppPermissionTypeEnum["Wifi"] = "Wifi";
|
|
42
42
|
EnergyAppPermissionTypeEnum["ChildProcess"] = "ChildProcess";
|
|
43
|
+
EnergyAppPermissionTypeEnum["Udp"] = "Udp";
|
|
43
44
|
})(EnergyAppPermissionTypeEnum || (EnergyAppPermissionTypeEnum = {}));
|
package/dist/energy-app.d.ts
CHANGED
|
@@ -31,6 +31,7 @@ import { EnergyAppBluetooth } from "./packages/energy-app-bluetooth.js";
|
|
|
31
31
|
import { EnergyAppDiagnostics } from "./packages/energy-app-diagnostics.js";
|
|
32
32
|
import { EnergyAppLearningPhase } from "./packages/energy-app-learning-phase.js";
|
|
33
33
|
import { EnergyAppWifi } from "./packages/energy-app-wifi.js";
|
|
34
|
+
import { EnergyAppUdp } from "./packages/energy-app-udp.js";
|
|
34
35
|
/**
|
|
35
36
|
* Concrete implementation of {@link EnyoEnergyAppSdk} that delegates every call
|
|
36
37
|
* to the runtime-provided `energyAppSdkInstance` global.
|
|
@@ -50,6 +51,7 @@ import { EnergyAppWifi } from "./packages/energy-app-wifi.js";
|
|
|
50
51
|
*/
|
|
51
52
|
export declare class EnergyApp implements EnyoEnergyAppSdk {
|
|
52
53
|
private readonly energyAppSdk;
|
|
54
|
+
private udpInstance;
|
|
53
55
|
constructor();
|
|
54
56
|
isSystemOnline(): boolean;
|
|
55
57
|
/**
|
|
@@ -184,6 +186,20 @@ export declare class EnergyApp implements EnyoEnergyAppSdk {
|
|
|
184
186
|
* @returns The WiFi API instance
|
|
185
187
|
*/
|
|
186
188
|
useWifi(): EnergyAppWifi;
|
|
189
|
+
/**
|
|
190
|
+
* Gets the UDP communication API for binding sockets and exchanging
|
|
191
|
+
* datagrams. Requires the `Udp` permission to be granted.
|
|
192
|
+
*
|
|
193
|
+
* Lazily instantiates a single {@link EnergyAppUdpServer} on first call
|
|
194
|
+
* and returns the same instance on subsequent calls. The runtime SDK's
|
|
195
|
+
* permission gate is invoked on every call so that revoked permissions
|
|
196
|
+
* are surfaced consistently with the other `use*` accessors.
|
|
197
|
+
*
|
|
198
|
+
* @returns The UDP API instance.
|
|
199
|
+
* @throws {EnergyAppPermissionNotGrantedError} If the `Udp` permission
|
|
200
|
+
* is not granted.
|
|
201
|
+
*/
|
|
202
|
+
useUdp(): EnergyAppUdp;
|
|
187
203
|
/**
|
|
188
204
|
* Gets the current SDK version.
|
|
189
205
|
* @returns The semantic version string of the SDK
|
package/dist/energy-app.js
CHANGED
|
@@ -18,6 +18,7 @@ import { getSdkVersion } from "./version.js";
|
|
|
18
18
|
*/
|
|
19
19
|
export class EnergyApp {
|
|
20
20
|
energyAppSdk;
|
|
21
|
+
udpInstance;
|
|
21
22
|
constructor() {
|
|
22
23
|
// in our runtime, there is an instance of energyAppSdk available which needs to be used here
|
|
23
24
|
// if the energyAppSdk is not available, instantiate a mocked version for local development
|
|
@@ -243,6 +244,22 @@ export class EnergyApp {
|
|
|
243
244
|
useWifi() {
|
|
244
245
|
return this.energyAppSdk.useWifi();
|
|
245
246
|
}
|
|
247
|
+
/**
|
|
248
|
+
* Gets the UDP communication API for binding sockets and exchanging
|
|
249
|
+
* datagrams. Requires the `Udp` permission to be granted.
|
|
250
|
+
*
|
|
251
|
+
* Lazily instantiates a single {@link EnergyAppUdpServer} on first call
|
|
252
|
+
* and returns the same instance on subsequent calls. The runtime SDK's
|
|
253
|
+
* permission gate is invoked on every call so that revoked permissions
|
|
254
|
+
* are surfaced consistently with the other `use*` accessors.
|
|
255
|
+
*
|
|
256
|
+
* @returns The UDP API instance.
|
|
257
|
+
* @throws {EnergyAppPermissionNotGrantedError} If the `Udp` permission
|
|
258
|
+
* is not granted.
|
|
259
|
+
*/
|
|
260
|
+
useUdp() {
|
|
261
|
+
return this.energyAppSdk.useUdp();
|
|
262
|
+
}
|
|
246
263
|
/**
|
|
247
264
|
* Gets the current SDK version.
|
|
248
265
|
* @returns The semantic version string of the SDK
|
|
@@ -30,6 +30,7 @@ import { EnergyAppBluetooth } from "./packages/energy-app-bluetooth.js";
|
|
|
30
30
|
import { EnergyAppDiagnostics } from "./packages/energy-app-diagnostics.js";
|
|
31
31
|
import { EnergyAppLearningPhase } from "./packages/energy-app-learning-phase.js";
|
|
32
32
|
import { EnergyAppWifi } from "./packages/energy-app-wifi.js";
|
|
33
|
+
import { EnergyAppUdp } from "./packages/energy-app-udp.js";
|
|
33
34
|
export declare enum EnergyAppStateEnum {
|
|
34
35
|
Launching = "launching",
|
|
35
36
|
Running = "running",
|
|
@@ -118,4 +119,6 @@ export interface EnyoEnergyAppSdk {
|
|
|
118
119
|
useLearningPhase: () => EnergyAppLearningPhase;
|
|
119
120
|
/** Get the WiFi API for scanning and listing known SSIDs */
|
|
120
121
|
useWifi: () => EnergyAppWifi;
|
|
122
|
+
/** Get the UDP communication API for binding sockets and exchanging datagrams */
|
|
123
|
+
useUdp: () => EnergyAppUdp;
|
|
121
124
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { EnergyAppUdp, EnergyAppUdpSocket, UdpBindOptions } from '../../packages/energy-app-udp.js';
|
|
2
|
+
/**
|
|
3
|
+
* Error thrown when a UDP bind fails because the requested port is already
|
|
4
|
+
* in use (EADDRINUSE on the underlying socket).
|
|
5
|
+
*
|
|
6
|
+
* Other bind errors are surfaced as their original Error so this type stays
|
|
7
|
+
* a precise signal that "the port is taken".
|
|
8
|
+
*/
|
|
9
|
+
export declare class EnergyAppUdpBindError extends Error {
|
|
10
|
+
readonly port: number;
|
|
11
|
+
readonly cause: Error;
|
|
12
|
+
/**
|
|
13
|
+
* @param port - The port the bind was attempted on.
|
|
14
|
+
* @param cause - The original error emitted by the dgram socket.
|
|
15
|
+
*/
|
|
16
|
+
constructor(port: number, cause: Error);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Default implementation of {@link EnergyAppUdp}.
|
|
20
|
+
*
|
|
21
|
+
* Thin wrapper around `node:dgram` that creates IPv4 UDP sockets. Used as the
|
|
22
|
+
* concrete instance returned by {@link EnergyApp.useUdp}.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* const udp = new EnergyAppUdpServer();
|
|
27
|
+
* const socket = await udp.bind({ port: 9000, reuseAddr: true });
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare class EnergyAppUdpServer implements EnergyAppUdp {
|
|
31
|
+
/**
|
|
32
|
+
* Bind a UDP socket using `node:dgram`.
|
|
33
|
+
*
|
|
34
|
+
* Resolves once the `listening` event fires. If the underlying socket
|
|
35
|
+
* emits an `error` with code `EADDRINUSE`, the promise rejects with
|
|
36
|
+
* {@link EnergyAppUdpBindError}; other errors propagate unchanged.
|
|
37
|
+
*
|
|
38
|
+
* @param options - Bind configuration (port, host, reuseAddr).
|
|
39
|
+
* @returns A bound {@link EnergyAppUdpSocket}.
|
|
40
|
+
*/
|
|
41
|
+
bind(options: UdpBindOptions): Promise<EnergyAppUdpSocket>;
|
|
42
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { createSocket } from 'node:dgram';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
/**
|
|
4
|
+
* Error thrown when a UDP bind fails because the requested port is already
|
|
5
|
+
* in use (EADDRINUSE on the underlying socket).
|
|
6
|
+
*
|
|
7
|
+
* Other bind errors are surfaced as their original Error so this type stays
|
|
8
|
+
* a precise signal that "the port is taken".
|
|
9
|
+
*/
|
|
10
|
+
export class EnergyAppUdpBindError extends Error {
|
|
11
|
+
port;
|
|
12
|
+
cause;
|
|
13
|
+
/**
|
|
14
|
+
* @param port - The port the bind was attempted on.
|
|
15
|
+
* @param cause - The original error emitted by the dgram socket.
|
|
16
|
+
*/
|
|
17
|
+
constructor(port, cause) {
|
|
18
|
+
super(`Failed to bind UDP socket on port ${port}: address in use`);
|
|
19
|
+
this.port = port;
|
|
20
|
+
this.cause = cause;
|
|
21
|
+
this.name = 'EnergyAppUdpBindError';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Default implementation of {@link EnergyAppUdpSocket} backed by `node:dgram`.
|
|
26
|
+
*
|
|
27
|
+
* Manages a single dgram socket and dispatches inbound messages to all
|
|
28
|
+
* registered handlers. Created internally by {@link EnergyAppUdpServer.bind}.
|
|
29
|
+
*/
|
|
30
|
+
class EnergyAppUdpSocketImpl {
|
|
31
|
+
socket;
|
|
32
|
+
handlers = new Map();
|
|
33
|
+
closed = false;
|
|
34
|
+
/**
|
|
35
|
+
* @param socket - The bound dgram socket this wrapper owns.
|
|
36
|
+
*/
|
|
37
|
+
constructor(socket) {
|
|
38
|
+
this.socket = socket;
|
|
39
|
+
this.socket.on('message', (data, rinfo) => {
|
|
40
|
+
const msg = {
|
|
41
|
+
data,
|
|
42
|
+
remotePort: rinfo.port,
|
|
43
|
+
remoteAddress: rinfo.address,
|
|
44
|
+
};
|
|
45
|
+
for (const handler of this.handlers.values()) {
|
|
46
|
+
handler(msg);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Returns the locally bound address descriptor. The dgram socket reports
|
|
52
|
+
* the kernel-assigned port if the bind was issued with port 0.
|
|
53
|
+
*
|
|
54
|
+
* @throws Error if the socket has been closed.
|
|
55
|
+
*/
|
|
56
|
+
address() {
|
|
57
|
+
if (this.closed) {
|
|
58
|
+
throw new Error('UDP socket is closed');
|
|
59
|
+
}
|
|
60
|
+
const info = this.socket.address();
|
|
61
|
+
return {
|
|
62
|
+
address: info.address,
|
|
63
|
+
port: info.port,
|
|
64
|
+
family: info.family,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Register an inbound-message handler.
|
|
69
|
+
*
|
|
70
|
+
* @param handler - Callback invoked for every received datagram.
|
|
71
|
+
* @returns A handle id that can be passed to {@link off}.
|
|
72
|
+
*/
|
|
73
|
+
onMessage(handler) {
|
|
74
|
+
const id = randomUUID();
|
|
75
|
+
this.handlers.set(id, handler);
|
|
76
|
+
return id;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Remove a previously registered handler. Unknown ids are ignored.
|
|
80
|
+
*
|
|
81
|
+
* @param handlerId - The id returned by {@link onMessage}.
|
|
82
|
+
*/
|
|
83
|
+
off(handlerId) {
|
|
84
|
+
this.handlers.delete(handlerId);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Send a datagram. Resolves when the dgram callback fires with no error.
|
|
88
|
+
*
|
|
89
|
+
* @param data - Payload bytes.
|
|
90
|
+
* @param port - Destination port.
|
|
91
|
+
* @param address - Destination host/IP.
|
|
92
|
+
* @returns Promise that resolves once the kernel accepted the datagram.
|
|
93
|
+
* @throws Error if the socket has been closed or the send call fails.
|
|
94
|
+
*/
|
|
95
|
+
send(data, port, address) {
|
|
96
|
+
if (this.closed) {
|
|
97
|
+
return Promise.reject(new Error('UDP socket is closed'));
|
|
98
|
+
}
|
|
99
|
+
return new Promise((resolve, reject) => {
|
|
100
|
+
this.socket.send(data, port, address, (err) => {
|
|
101
|
+
if (err) {
|
|
102
|
+
reject(err);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
resolve();
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Close the underlying dgram socket. Idempotent.
|
|
111
|
+
*
|
|
112
|
+
* @returns Promise that resolves once the dgram `close` event fires (or
|
|
113
|
+
* immediately if the socket was already closed).
|
|
114
|
+
*/
|
|
115
|
+
close() {
|
|
116
|
+
if (this.closed) {
|
|
117
|
+
return Promise.resolve();
|
|
118
|
+
}
|
|
119
|
+
this.closed = true;
|
|
120
|
+
return new Promise((resolve) => {
|
|
121
|
+
this.socket.once('close', () => resolve());
|
|
122
|
+
this.socket.close();
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Default implementation of {@link EnergyAppUdp}.
|
|
128
|
+
*
|
|
129
|
+
* Thin wrapper around `node:dgram` that creates IPv4 UDP sockets. Used as the
|
|
130
|
+
* concrete instance returned by {@link EnergyApp.useUdp}.
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* ```typescript
|
|
134
|
+
* const udp = new EnergyAppUdpServer();
|
|
135
|
+
* const socket = await udp.bind({ port: 9000, reuseAddr: true });
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
export class EnergyAppUdpServer {
|
|
139
|
+
/**
|
|
140
|
+
* Bind a UDP socket using `node:dgram`.
|
|
141
|
+
*
|
|
142
|
+
* Resolves once the `listening` event fires. If the underlying socket
|
|
143
|
+
* emits an `error` with code `EADDRINUSE`, the promise rejects with
|
|
144
|
+
* {@link EnergyAppUdpBindError}; other errors propagate unchanged.
|
|
145
|
+
*
|
|
146
|
+
* @param options - Bind configuration (port, host, reuseAddr).
|
|
147
|
+
* @returns A bound {@link EnergyAppUdpSocket}.
|
|
148
|
+
*/
|
|
149
|
+
bind(options) {
|
|
150
|
+
return new Promise((resolve, reject) => {
|
|
151
|
+
const socket = createSocket({ type: 'udp4', reuseAddr: false });
|
|
152
|
+
const onError = (err) => {
|
|
153
|
+
socket.removeListener('listening', onListening);
|
|
154
|
+
if (err.code === 'EADDRINUSE') {
|
|
155
|
+
reject(new EnergyAppUdpBindError(options.port, err));
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
reject(err);
|
|
159
|
+
};
|
|
160
|
+
const onListening = () => {
|
|
161
|
+
socket.removeListener('error', onError);
|
|
162
|
+
resolve(new EnergyAppUdpSocketImpl(socket));
|
|
163
|
+
};
|
|
164
|
+
socket.once('error', onError);
|
|
165
|
+
socket.once('listening', onListening);
|
|
166
|
+
socket.bind(options.port, '0.0.0.0');
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -43,6 +43,7 @@ export * from './types/enyo-learning-phase.js';
|
|
|
43
43
|
export * from './packages/energy-app-learning-phase.js';
|
|
44
44
|
export * from './types/enyo-wifi.js';
|
|
45
45
|
export * from './packages/energy-app-wifi.js';
|
|
46
|
+
export * from './packages/energy-app-udp.js';
|
|
46
47
|
export * from './types/enyo-air-conditioning-appliance.js';
|
|
47
48
|
export * from './types/enyo-onboarding.js';
|
|
48
49
|
export * from './packages/energy-app-onboarding.js';
|
package/dist/index.js
CHANGED
|
@@ -43,6 +43,7 @@ export * from './types/enyo-learning-phase.js';
|
|
|
43
43
|
export * from './packages/energy-app-learning-phase.js';
|
|
44
44
|
export * from './types/enyo-wifi.js';
|
|
45
45
|
export * from './packages/energy-app-wifi.js';
|
|
46
|
+
export * from './packages/energy-app-udp.js';
|
|
46
47
|
export * from './types/enyo-air-conditioning-appliance.js';
|
|
47
48
|
export * from './types/enyo-onboarding.js';
|
|
48
49
|
export * from './packages/energy-app-onboarding.js';
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options used when binding a UDP socket via {@link EnergyAppUdp.bind}.
|
|
3
|
+
*/
|
|
4
|
+
export interface UdpBindOptions {
|
|
5
|
+
/** Bind port. 0 selects an ephemeral port. */
|
|
6
|
+
port: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* A single inbound datagram delivered to a handler registered with
|
|
10
|
+
* {@link EnergyAppUdpSocket.onMessage}.
|
|
11
|
+
*/
|
|
12
|
+
export interface UdpInboundMessage {
|
|
13
|
+
/** Raw payload bytes of the datagram. */
|
|
14
|
+
data: Buffer;
|
|
15
|
+
/** Remote port the datagram originated from. */
|
|
16
|
+
remotePort: number;
|
|
17
|
+
/** Remote address (IPv4/IPv6 textual form) the datagram originated from. */
|
|
18
|
+
remoteAddress: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Represents an active UDP socket bound to a local address/port.
|
|
22
|
+
*
|
|
23
|
+
* Obtained by calling {@link EnergyAppUdp.bind}. Provides methods for sending
|
|
24
|
+
* datagrams, subscribing to inbound messages, querying the resolved local
|
|
25
|
+
* address, and closing the socket.
|
|
26
|
+
*/
|
|
27
|
+
export interface EnergyAppUdpSocket {
|
|
28
|
+
/**
|
|
29
|
+
* The address the socket is actually bound to.
|
|
30
|
+
* If port 0 was requested, the kernel-assigned port is returned here.
|
|
31
|
+
*
|
|
32
|
+
* @returns The bound local address descriptor.
|
|
33
|
+
* @throws If the socket has been closed.
|
|
34
|
+
*/
|
|
35
|
+
address(): {
|
|
36
|
+
address: string;
|
|
37
|
+
port: number;
|
|
38
|
+
family: 'IPv4' | 'IPv6';
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Subscribe to inbound datagrams.
|
|
42
|
+
*
|
|
43
|
+
* Multiple handlers can be attached and each will receive a copy of every
|
|
44
|
+
* inbound message. The returned handle can be passed to {@link off} to
|
|
45
|
+
* stop receiving messages on that subscription.
|
|
46
|
+
*
|
|
47
|
+
* @param handler - Callback invoked for every datagram received.
|
|
48
|
+
* @returns A unique handle that identifies this subscription.
|
|
49
|
+
*/
|
|
50
|
+
onMessage(handler: (msg: UdpInboundMessage) => void): string;
|
|
51
|
+
/**
|
|
52
|
+
* Stop listening — removes a previously registered handler.
|
|
53
|
+
*
|
|
54
|
+
* @param handlerId - The id returned by {@link onMessage}. Unknown ids
|
|
55
|
+
* are ignored.
|
|
56
|
+
*/
|
|
57
|
+
off(handlerId: string): void;
|
|
58
|
+
/**
|
|
59
|
+
* Send a datagram to the given peer.
|
|
60
|
+
*
|
|
61
|
+
* @param data - Raw payload bytes to transmit.
|
|
62
|
+
* @param port - Destination port on the peer.
|
|
63
|
+
* @param address - Destination address on the peer (IPv4/IPv6 textual form
|
|
64
|
+
* or hostname).
|
|
65
|
+
* @returns A promise that resolves once the kernel has accepted the
|
|
66
|
+
* datagram and rejects if the underlying send call fails.
|
|
67
|
+
*/
|
|
68
|
+
send(data: Buffer, port: number, address: string): Promise<void>;
|
|
69
|
+
/**
|
|
70
|
+
* Close the socket and release the bound port.
|
|
71
|
+
*
|
|
72
|
+
* Subsequent calls to {@link send} reject and {@link address} throws.
|
|
73
|
+
* Idempotent close-after-close resolves without error.
|
|
74
|
+
*
|
|
75
|
+
* @returns A promise that resolves once the underlying socket has emitted
|
|
76
|
+
* its `close` event.
|
|
77
|
+
*/
|
|
78
|
+
close(): Promise<void>;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Interface for raw UDP communication in enyo packages.
|
|
82
|
+
*
|
|
83
|
+
* Provides a thin abstraction over `node:dgram` for binding sockets and
|
|
84
|
+
* exchanging datagrams. Requires the package to declare the `Udp` permission.
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```typescript
|
|
88
|
+
* const udp = energyApp.useUdp();
|
|
89
|
+
* const socket = await udp.bind({ port: 0 });
|
|
90
|
+
* const handlerId = socket.onMessage((msg) => {
|
|
91
|
+
* console.log(`Received ${msg.data.length} bytes from ${msg.remoteAddress}:${msg.remotePort}`);
|
|
92
|
+
* });
|
|
93
|
+
* await socket.send(Buffer.from('hello'), 5005, '192.168.1.42');
|
|
94
|
+
* socket.off(handlerId);
|
|
95
|
+
* await socket.close();
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
export interface EnergyAppUdp {
|
|
99
|
+
/**
|
|
100
|
+
* Bind a UDP socket on the requested host/port.
|
|
101
|
+
*
|
|
102
|
+
* Resolves once the underlying socket has emitted its `listening` event.
|
|
103
|
+
*
|
|
104
|
+
* @param options - Bind configuration (see {@link UdpBindOptions}).
|
|
105
|
+
* @returns A bound socket ready to send and receive datagrams.
|
|
106
|
+
* @throws {EnergyAppUdpBindError} If the requested port is already in use
|
|
107
|
+
* (EADDRINUSE). Other errors propagate as their original Error.
|
|
108
|
+
*/
|
|
109
|
+
bind(options: UdpBindOptions): Promise<EnergyAppUdpSocket>;
|
|
110
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED