node-red-contrib-s2 0.1.1

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.
Files changed (40) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/LICENSE +21 -0
  3. package/README.md +119 -0
  4. package/dist/lib/index.d.ts +4 -0
  5. package/dist/lib/index.js +17 -0
  6. package/dist/lib/index.js.map +1 -0
  7. package/dist/lib/s2/messages.d.ts +342 -0
  8. package/dist/lib/s2/messages.js +330 -0
  9. package/dist/lib/s2/messages.js.map +1 -0
  10. package/dist/lib/s2/schedule.d.ts +35 -0
  11. package/dist/lib/s2/schedule.js +109 -0
  12. package/dist/lib/s2/schedule.js.map +1 -0
  13. package/dist/lib/s2/session.d.ts +138 -0
  14. package/dist/lib/s2/session.js +304 -0
  15. package/dist/lib/s2/session.js.map +1 -0
  16. package/dist/lib/transport/websocket.d.ts +58 -0
  17. package/dist/lib/transport/websocket.js +149 -0
  18. package/dist/lib/transport/websocket.js.map +1 -0
  19. package/dist/nodes/s2-cem-config/index.d.ts +3 -0
  20. package/dist/nodes/s2-cem-config/index.html +54 -0
  21. package/dist/nodes/s2-cem-config/index.js +16 -0
  22. package/dist/nodes/s2-cem-config/index.js.map +1 -0
  23. package/dist/nodes/s2-rm/icons/s2-logo.svg +14 -0
  24. package/dist/nodes/s2-rm/index.d.ts +34 -0
  25. package/dist/nodes/s2-rm/index.html +167 -0
  26. package/dist/nodes/s2-rm/index.js +655 -0
  27. package/dist/nodes/s2-rm/index.js.map +1 -0
  28. package/dist/nodes/s2-rm-config/index.d.ts +3 -0
  29. package/dist/nodes/s2-rm-config/index.html +254 -0
  30. package/dist/nodes/s2-rm-config/index.js +20 -0
  31. package/dist/nodes/s2-rm-config/index.js.map +1 -0
  32. package/dist/nodes/s2-websocket/icons/s2-logo.svg +14 -0
  33. package/dist/nodes/s2-websocket/index.d.ts +22 -0
  34. package/dist/nodes/s2-websocket/index.html +83 -0
  35. package/dist/nodes/s2-websocket/index.js +127 -0
  36. package/dist/nodes/s2-websocket/index.js.map +1 -0
  37. package/dist/types/config-nodes.d.ts +39 -0
  38. package/dist/types/config-nodes.js +3 -0
  39. package/dist/types/config-nodes.js.map +1 -0
  40. package/package.json +84 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,16 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
6
+
7
+ ## [0.1.0]
8
+
9
+ ### Added
10
+
11
+ - `s2-rm` node: S2 Resource Manager session handling (handshake, control type selection, instructions).
12
+ - `s2-rm-config` / `s2-cem-config` nodes for RM identity and CEM connection configuration.
13
+ - `s2-websocket` node: WebSocket transport to a CEM, with reconnect/backoff handling.
14
+ - Operation Mode Based Control (OMBC) and Power Envelope Based Control (PEBC) support.
15
+ - PowerMeasurement and PowerForecast forwarding.
16
+ - Multiple concurrent CEM sessions.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Victron Energy BV
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,119 @@
1
+ # node-red-contrib-s2
2
+
3
+ Node-RED nodes for the [S2 energy management protocol](https://s2standard.org/) (EN 50491-12-2).
4
+
5
+ S2 is a European standard for demand-side energy flexibility. It defines how a Customer Energy Manager (CEM) communicates with Resource Managers (RMs) to coordinate energy consumption, production, and storage.
6
+
7
+ ## Requirements
8
+
9
+ - Node-RED >= 4.1.0
10
+ - Node.js >= 24
11
+
12
+ ## Nodes
13
+
14
+ | Node | Description |
15
+ |------|-------------|
16
+ | **s2-rm** | S2 Resource Manager - manages protocol sessions with one or more CEMs |
17
+ | **s2-rm-config** | Configuration for RM identity: resource ID, name, roles, control types, serial number |
18
+ | **s2-cem-config** | Configuration for CEM connection (WebSocket URL and credentials) |
19
+ | **s2-websocket** | WebSocket transport for S2 communication with a CEM |
20
+
21
+ ## Features
22
+
23
+ - S2 protocol handshake and session management
24
+ - Operation Mode Based Control (OMBC)
25
+ - Power Envelope Based Control (PEBC) with configurable power constraints
26
+ - PowerMeasurement forwarding (3-phase symmetric or per-phase L1/L2/L3)
27
+ - PowerForecast support
28
+ - Multiple concurrent CEM sessions
29
+ - Configurable RM roles (Consumer, Producer, Storage)
30
+ - Context variable templates in serial number (e.g. `{{global.vrmId}}`)
31
+
32
+ ## Installation
33
+
34
+ Install via the Node-RED palette manager, or from the command line:
35
+
36
+ ```bash
37
+ cd ~/.node-red
38
+ npm install node-red-contrib-s2
39
+ ```
40
+
41
+ ## Quick start
42
+
43
+ 1. Add an **s2-rm-config** node and configure your Resource Manager identity (name, roles, control types).
44
+ 2. Add an **s2-cem-config** node with the WebSocket URL and credentials of your CEM.
45
+ 3. Wire an **s2-websocket** node to an **s2-rm** node:
46
+ - s2-websocket output 2 -> s2-rm input
47
+ - s2-rm output 1 -> s2-websocket input
48
+ 4. s2-rm output 2 carries S2 messages from the CEM (e.g. SelectControlType, ReceptionStatus).
49
+ 5. s2-rm output 3 carries instructions from the CEM (e.g. PEBC.Instruction, OMBC.Instruction).
50
+
51
+ ## Sending PowerMeasurements
52
+
53
+ To send power measurements to the CEM, inject a message into the s2-rm input:
54
+
55
+ ```json
56
+ {
57
+ "payload": {
58
+ "command": "PowerMeasurement",
59
+ "cemId": "cem",
60
+ "values": [
61
+ { "commodity_quantity": "ELECTRIC.POWER.3_PHASE_SYMMETRIC", "value": 1500 }
62
+ ]
63
+ }
64
+ }
65
+ ```
66
+
67
+ The s2-rm node emits a `PowerMeasurementStart` signal on output 1 when the CEM selects a control type, so you can use that to trigger periodic measurements.
68
+
69
+ ## Sending PowerForecasts
70
+
71
+ ```json
72
+ {
73
+ "payload": {
74
+ "command": "Forecast",
75
+ "cemId": "cem",
76
+ "forecast": {
77
+ "startTime": "2026-04-14T10:00:00Z",
78
+ "elements": [
79
+ {
80
+ "duration": 900000,
81
+ "power_values": [
82
+ { "commodity_quantity": "ELECTRIC.POWER.3_PHASE_SYMMETRIC", "value_expected": 1500 }
83
+ ]
84
+ }
85
+ ]
86
+ }
87
+ }
88
+ }
89
+ ```
90
+
91
+ ## Updating PEBC PowerConstraints
92
+
93
+ ```json
94
+ {
95
+ "payload": {
96
+ "command": "PowerConstraints",
97
+ "cemId": "cem",
98
+ "constraints": {
99
+ "commodityQuantity": "ELECTRIC.POWER.3_PHASE_SYMMETRIC",
100
+ "minPower": -3000,
101
+ "maxPower": 3000
102
+ }
103
+ }
104
+ }
105
+ ```
106
+
107
+ Constraints are stored at the node level and automatically sent when a CEM selects PEBC.
108
+
109
+ ## Development
110
+
111
+ ```bash
112
+ npm install
113
+ npm run build
114
+ npm test
115
+ ```
116
+
117
+ ## License
118
+
119
+ [MIT](LICENSE) - Copyright (c) 2026 Victron Energy BV
@@ -0,0 +1,4 @@
1
+ export { S2Session, State } from './s2/session';
2
+ export type { StateValue, S2SessionOptions, ControlTypeConfig } from './s2/session';
3
+ export { MessageType, ControlType, ReceptionStatusResult, parse, serialize, makeReceptionStatus, makeHandshake, makeResourceManagerDetails, generateId } from './s2/messages';
4
+ export type { MessageTypeValue, ControlTypeValue, ReceptionStatusResultValue, S2IncomingMessage, S2Role, PowerMeasurementValue, RmDetails } from './s2/messages';
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateId = exports.makeResourceManagerDetails = exports.makeHandshake = exports.makeReceptionStatus = exports.serialize = exports.parse = exports.ReceptionStatusResult = exports.ControlType = exports.MessageType = exports.State = exports.S2Session = void 0;
4
+ var session_1 = require("./s2/session");
5
+ Object.defineProperty(exports, "S2Session", { enumerable: true, get: function () { return session_1.S2Session; } });
6
+ Object.defineProperty(exports, "State", { enumerable: true, get: function () { return session_1.State; } });
7
+ var messages_1 = require("./s2/messages");
8
+ Object.defineProperty(exports, "MessageType", { enumerable: true, get: function () { return messages_1.MessageType; } });
9
+ Object.defineProperty(exports, "ControlType", { enumerable: true, get: function () { return messages_1.ControlType; } });
10
+ Object.defineProperty(exports, "ReceptionStatusResult", { enumerable: true, get: function () { return messages_1.ReceptionStatusResult; } });
11
+ Object.defineProperty(exports, "parse", { enumerable: true, get: function () { return messages_1.parse; } });
12
+ Object.defineProperty(exports, "serialize", { enumerable: true, get: function () { return messages_1.serialize; } });
13
+ Object.defineProperty(exports, "makeReceptionStatus", { enumerable: true, get: function () { return messages_1.makeReceptionStatus; } });
14
+ Object.defineProperty(exports, "makeHandshake", { enumerable: true, get: function () { return messages_1.makeHandshake; } });
15
+ Object.defineProperty(exports, "makeResourceManagerDetails", { enumerable: true, get: function () { return messages_1.makeResourceManagerDetails; } });
16
+ Object.defineProperty(exports, "generateId", { enumerable: true, get: function () { return messages_1.generateId; } });
17
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/lib/index.ts"],"names":[],"mappings":";;;AAAA,wCAA+C;AAAtC,oGAAA,SAAS,OAAA;AAAE,gGAAA,KAAK,OAAA;AAGzB,0CAUsB;AATpB,uGAAA,WAAW,OAAA;AACX,uGAAA,WAAW,OAAA;AACX,iHAAA,qBAAqB,OAAA;AACrB,iGAAA,KAAK,OAAA;AACL,qGAAA,SAAS,OAAA;AACT,+GAAA,mBAAmB,OAAA;AACnB,yGAAA,aAAa,OAAA;AACb,sHAAA,0BAA0B,OAAA;AAC1B,sGAAA,UAAU,OAAA"}
@@ -0,0 +1,342 @@
1
+ /**
2
+ * S2 protocol message type constants (EN 50491-12-2).
3
+ * These are the message_type values used in S2 JSON messages.
4
+ */
5
+ export declare const MessageType: Readonly<{
6
+ readonly HANDSHAKE: "Handshake";
7
+ readonly HANDSHAKE_RESPONSE: "HandshakeResponse";
8
+ readonly SESSION_REQUEST: "SessionRequest";
9
+ readonly RESOURCE_MANAGER_DETAILS: "ResourceManagerDetails";
10
+ readonly POWER_MEASUREMENT: "PowerMeasurement";
11
+ readonly RECEPTION_STATUS: "ReceptionStatus";
12
+ readonly REVOKE_OBJECT: "RevokeObject";
13
+ readonly SELECT_CONTROL_TYPE: "SelectControlType";
14
+ readonly INSTRUCTION: "Instruction";
15
+ readonly FRBC_ACTUATOR_STATUS: "FRBC.ActuatorStatus";
16
+ readonly FRBC_FILL_LEVEL_TARGET_PROFILE: "FRBC.FillLevelTargetProfile";
17
+ readonly FRBC_LEAKAGE_BEHAVIOUR: "FRBC.LeakagebBehaviour";
18
+ readonly FRBC_STORAGE_STATUS: "FRBC.StorageStatus";
19
+ readonly FRBC_SYSTEM_DESCRIPTION: "FRBC.SystemDescription";
20
+ readonly FRBC_TIMER_STATUS: "FRBC.TimerStatus";
21
+ readonly FRBC_USAGE_FORECAST: "FRBC.UsageForecast";
22
+ readonly FRBC_INSTRUCTION: "FRBC.Instruction";
23
+ readonly DDBC_ACTUAL_POWER: "DDBC.ActualPower";
24
+ readonly DDBC_AVERAGE_DEMAND_RATE_FORECAST: "DDBC.AverageDemandRateForecast";
25
+ readonly DDBC_SYSTEM_DESCRIPTION: "DDBC.SystemDescription";
26
+ readonly DDBC_TIMER_STATUS: "DDBC.TimerStatus";
27
+ readonly DDBC_INSTRUCTION: "DDBC.Instruction";
28
+ readonly PPBC_POWER_PROFILE_STATUS: "PPBC.PowerProfileStatus";
29
+ readonly PPBC_SCHEDULE_INSTRUCTION: "PPBC.ScheduleInstruction";
30
+ readonly PPBC_START_INTERRUPTION_INSTRUCTION: "PPBC.StartInterruptionInstruction";
31
+ readonly PPBC_END_INTERRUPTION_INSTRUCTION: "PPBC.EndInterruptionInstruction";
32
+ readonly PPBC_POWER_PROFILE: "PPBC.PowerProfileDefinition";
33
+ readonly OMBC_SYSTEM_DESCRIPTION: "OMBC.SystemDescription";
34
+ readonly OMBC_STATUS: "OMBC.Status";
35
+ readonly OMBC_INSTRUCTION: "OMBC.Instruction";
36
+ readonly PEBC_POWER_CONSTRAINTS: "PEBC.PowerConstraints";
37
+ readonly PEBC_DEVICE_CONSTRAINTS: "PEBC.DeviceConstraints";
38
+ readonly PEBC_ENERGY_CONSTRAINT: "PEBC.EnergyConstraint";
39
+ readonly PEBC_INSTRUCTION: "PEBC.Instruction";
40
+ readonly POWER_FORECAST: "PowerForecast";
41
+ readonly INSTRUCTION_STATUS_UPDATE: "InstructionStatusUpdate";
42
+ }>;
43
+ export type MessageTypeValue = (typeof MessageType)[keyof typeof MessageType];
44
+ /**
45
+ * S2 control type identifiers (full names as used in s2python / s2-ws-json).
46
+ */
47
+ export declare const ControlType: Readonly<{
48
+ readonly NOT_CONTROLABLE: "NOT_CONTROLABLE";
49
+ readonly OMBC: "OPERATION_MODE_BASED_CONTROL";
50
+ readonly FRBC: "FILL_RATE_BASED_CONTROL";
51
+ readonly DDBC: "DEMAND_DRIVEN_BASED_CONTROL";
52
+ readonly PPBC: "POWER_PROFILE_BASED_CONTROL";
53
+ readonly PEBC: "POWER_ENVELOPE_BASED_CONTROL";
54
+ readonly NO_SELECTION: "NO_SELECTION";
55
+ }>;
56
+ export type ControlTypeValue = (typeof ControlType)[keyof typeof ControlType];
57
+ /**
58
+ * ReceptionStatus result values (matches s2python ReceptionStatusValues).
59
+ */
60
+ /**
61
+ * Standard European grid connection types with their max power capacity at 230V nominal.
62
+ * Used to set PEBC power constraint outer limits (bidirectional: [-maxWatts, +maxWatts]).
63
+ */
64
+ export declare const GRID_CONNECTIONS: Readonly<{
65
+ readonly '1x16A': {
66
+ readonly phases: 1;
67
+ readonly ampsPerPhase: 16;
68
+ readonly maxWatts: 3680;
69
+ };
70
+ readonly '3x16A': {
71
+ readonly phases: 3;
72
+ readonly ampsPerPhase: 16;
73
+ readonly maxWatts: 11040;
74
+ };
75
+ readonly '3x25A': {
76
+ readonly phases: 3;
77
+ readonly ampsPerPhase: 25;
78
+ readonly maxWatts: 17250;
79
+ };
80
+ readonly '3x32A': {
81
+ readonly phases: 3;
82
+ readonly ampsPerPhase: 32;
83
+ readonly maxWatts: 22080;
84
+ };
85
+ readonly '3x40A': {
86
+ readonly phases: 3;
87
+ readonly ampsPerPhase: 40;
88
+ readonly maxWatts: 27600;
89
+ };
90
+ readonly '3x63A': {
91
+ readonly phases: 3;
92
+ readonly ampsPerPhase: 63;
93
+ readonly maxWatts: 43470;
94
+ };
95
+ }>;
96
+ export type GridConnectionKey = keyof typeof GRID_CONNECTIONS;
97
+ /**
98
+ * Returns the max power in watts for a given grid connection.
99
+ * For 'custom', returns customMaxPowerW if provided.
100
+ * Returns null if the connection is unknown or unset.
101
+ */
102
+ export declare function gridConnectionToWatts(gridConnection: string | undefined, customMaxPowerW?: number): number | null;
103
+ export declare const ReceptionStatusResult: Readonly<{
104
+ readonly OK: "OK";
105
+ readonly INVALID_DATA: "INVALID_DATA";
106
+ readonly INVALID_MESSAGE: "INVALID_MESSAGE";
107
+ readonly INVALID_CONTENT: "INVALID_CONTENT";
108
+ readonly TEMPORARY_ERROR: "TEMPORARY_ERROR";
109
+ readonly PERMANENT_ERROR: "PERMANENT_ERROR";
110
+ }>;
111
+ export type ReceptionStatusResultValue = (typeof ReceptionStatusResult)[keyof typeof ReceptionStatusResult];
112
+ export declare const InstructionStatus: Readonly<{
113
+ readonly NEW: "NEW";
114
+ readonly ACCEPTED: "ACCEPTED";
115
+ readonly REJECTED: "REJECTED";
116
+ readonly REVOKED: "REVOKED";
117
+ readonly STARTED: "STARTED";
118
+ readonly SUCCEEDED: "SUCCEEDED";
119
+ readonly ABORTED: "ABORTED";
120
+ }>;
121
+ export type InstructionStatusValue = (typeof InstructionStatus)[keyof typeof InstructionStatus];
122
+ export declare const RevokableObjects: Readonly<{
123
+ readonly DDBC_AVERAGE_DEMAND_RATE_FORECAST: "DDBC.AverageDemandRateForecast";
124
+ readonly DDBC_INSTRUCTION: "DDBC.Instruction";
125
+ readonly DDBC_SYSTEM_DESCRIPTION: "DDBC.SystemDescription";
126
+ readonly FRBC_FILL_LEVEL_TARGET_PROFILE: "FRBC.FillLevelTargetProfile";
127
+ readonly FRBC_INSTRUCTION: "FRBC.Instruction";
128
+ readonly FRBC_LEAKAGE_BEHAVIOUR: "FRBC.LeakageBehaviour";
129
+ readonly FRBC_SYSTEM_DESCRIPTION: "FRBC.SystemDescription";
130
+ readonly FRBC_USAGE_FORECAST: "FRBC.UsageForecast";
131
+ readonly OMBC_INSTRUCTION: "OMBC.Instruction";
132
+ readonly OMBC_SYSTEM_DESCRIPTION: "OMBC.SystemDescription";
133
+ readonly PEBC_ENERGY_CONSTRAINT: "PEBC.EnergyConstraint";
134
+ readonly PEBC_INSTRUCTION: "PEBC.Instruction";
135
+ readonly PEBC_POWER_CONSTRAINTS: "PEBC.PowerConstraints";
136
+ readonly PPBC_END_INTERRUPTION_INSTRUCTION: "PPBC.EndInterruptionInstruction";
137
+ readonly PPBC_POWER_PROFILE_DEFINITION: "PPBC.PowerProfileDefinition";
138
+ readonly PPBC_SCHEDULE_INSTRUCTION: "PPBC.ScheduleInstruction";
139
+ readonly PPBC_START_INTERRUPTION_INSTRUCTION: "PPBC.StartInterruptionInstruction";
140
+ }>;
141
+ export type RevokableObjectsValue = (typeof RevokableObjects)[keyof typeof RevokableObjects];
142
+ /** Base shape for any S2 message parsed from JSON. */
143
+ export interface S2IncomingMessage {
144
+ message_type: string;
145
+ message_id?: string;
146
+ [key: string]: unknown;
147
+ }
148
+ export interface S2Role {
149
+ role: string;
150
+ commodity: string;
151
+ }
152
+ export interface PowerMeasurementValue {
153
+ commodity_quantity: string;
154
+ value: number;
155
+ }
156
+ export interface S2HandshakeMessage {
157
+ message_type: typeof MessageType.HANDSHAKE;
158
+ message_id: string;
159
+ role: 'RM';
160
+ supported_protocol_versions: string[];
161
+ }
162
+ export interface S2ReceptionStatusMessage {
163
+ message_type: typeof MessageType.RECEPTION_STATUS;
164
+ subject_message_id: string;
165
+ status: ReceptionStatusResultValue;
166
+ diagnostic_label?: string;
167
+ }
168
+ export interface S2RevokeObjectMessage {
169
+ message_type: typeof MessageType.REVOKE_OBJECT;
170
+ message_id: string;
171
+ object_id: string;
172
+ object_type: RevokableObjectsValue;
173
+ }
174
+ export interface InstructionStatusUpdateMessage {
175
+ message_type: typeof MessageType.INSTRUCTION_STATUS_UPDATE;
176
+ message_id: string;
177
+ instruction_id: string;
178
+ status_type: InstructionStatusValue;
179
+ timestamp: string;
180
+ }
181
+ export interface S2ResourceManagerDetailsMessage {
182
+ message_type: typeof MessageType.RESOURCE_MANAGER_DETAILS;
183
+ message_id: string;
184
+ resource_id: string;
185
+ name: string;
186
+ roles: S2Role[];
187
+ manufacturer: string;
188
+ model: string;
189
+ serial_number: string;
190
+ firmware_version: string;
191
+ available_control_types: string[];
192
+ provides_forecast: boolean;
193
+ provides_power_measurement_types: string[];
194
+ instruction_processing_delay: number;
195
+ }
196
+ export interface S2OMBCSystemDescriptionMessage {
197
+ message_type: typeof MessageType.OMBC_SYSTEM_DESCRIPTION;
198
+ message_id: string;
199
+ valid_from: string;
200
+ operation_modes: unknown[];
201
+ transitions: unknown[];
202
+ timers: unknown[];
203
+ }
204
+ export interface S2OMBCStatusMessage {
205
+ message_type: typeof MessageType.OMBC_STATUS;
206
+ message_id: string;
207
+ active_operation_mode_id: string;
208
+ operation_mode_factor: number;
209
+ previous_operation_mode_id?: string;
210
+ transition_timestamp?: string;
211
+ }
212
+ export interface S2PowerMeasurementMessage {
213
+ message_type: typeof MessageType.POWER_MEASUREMENT;
214
+ message_id: string;
215
+ measurement_timestamp: string;
216
+ values: PowerMeasurementValue[];
217
+ }
218
+ export interface RmDetails {
219
+ resourceId: string;
220
+ name: string;
221
+ roles: S2Role[];
222
+ availableControlTypes: string[];
223
+ providesForecast: boolean;
224
+ providesPowerMeasurementTypes?: string[];
225
+ instructionProcessingDelay?: number;
226
+ manufacturer?: string;
227
+ model?: string;
228
+ serialNumber?: string;
229
+ firmwareVersion?: string;
230
+ }
231
+ export interface OMBCSystemDescriptionConfig {
232
+ operationModes: unknown[];
233
+ transitions?: unknown[];
234
+ timers?: unknown[];
235
+ }
236
+ export interface OMBCStatusConfig {
237
+ activeOperationModeId: string;
238
+ operationModeFactor?: number;
239
+ previousOperationModeId?: string;
240
+ transitionTimestamp?: string;
241
+ }
242
+ /**
243
+ * Simplified input for building a PEBC.PowerConstraints message.
244
+ * Produces a single commodity constraint with matching LOWER_LIMIT and
245
+ * UPPER_LIMIT ranges, consequence_type DEFER - as required by most CEMs.
246
+ */
247
+ export interface PEBCPowerConstraintsInput {
248
+ /** e.g. 'ELECTRIC.POWER.3_PHASE_SYMMETRIC' */
249
+ commodityQuantity: string;
250
+ /** Lower bound of the allowed power range in Watts (may be negative for export) */
251
+ minPower: number;
252
+ /** Upper bound of the allowed power range in Watts */
253
+ maxPower: number;
254
+ /** ISO 8601 datetime; defaults to now */
255
+ validFrom?: string;
256
+ }
257
+ export interface PowerForecastValue {
258
+ commodity_quantity: string;
259
+ value_expected: number;
260
+ value_upper_limit?: number;
261
+ value_lower_limit?: number;
262
+ }
263
+ export interface PowerForecastElement {
264
+ duration: number;
265
+ power_values: PowerForecastValue[];
266
+ }
267
+ export interface PowerForecastInput {
268
+ startTime: string;
269
+ elements: PowerForecastElement[];
270
+ }
271
+ /**
272
+ * Parse a raw WebSocket message string into an S2 message object.
273
+ * Returns null and calls onError if parsing fails.
274
+ *
275
+ * @param raw - raw JSON string
276
+ * @param onError - called with an Error if parsing fails
277
+ */
278
+ export declare function parse(raw: string, onError: (err: Error) => void): S2IncomingMessage | null;
279
+ /**
280
+ * Serialize an S2 message object to a string for sending over the wire.
281
+ */
282
+ export declare function serialize(msg: object): string;
283
+ /**
284
+ * Create a ReceptionStatus message acknowledging a received message.
285
+ *
286
+ * @param subjectMessageId - message_id of the message being acknowledged
287
+ * @param status - one of ReceptionStatusResult values
288
+ * @param diagnosticLabel - optional human-readable diagnostic
289
+ */
290
+ export declare function makeReceptionStatus(subjectMessageId: string, status: ReceptionStatusResultValue, diagnosticLabel?: string): S2ReceptionStatusMessage;
291
+ /**
292
+ * Create a Handshake message (sent by RM on connect).
293
+ *
294
+ * @param rmId - unique ID identifying this Resource Manager
295
+ */
296
+ export declare function makeHandshake(rmId: string): S2HandshakeMessage;
297
+ /**
298
+ * Create a ResourceManagerDetails message (sent by RM after HandshakeResponse).
299
+ */
300
+ export declare function makeResourceManagerDetails(details: RmDetails): S2ResourceManagerDetailsMessage;
301
+ /**
302
+ * Create an OMBC.SystemDescription message.
303
+ */
304
+ export declare function makeOMBCSystemDescription(config: OMBCSystemDescriptionConfig): S2OMBCSystemDescriptionMessage;
305
+ /**
306
+ * Create an OMBC.Status message.
307
+ */
308
+ export declare function makeOMBCStatus(status: OMBCStatusConfig): S2OMBCStatusMessage;
309
+ /**
310
+ * Create a PowerMeasurement message (sent by RM to report current power).
311
+ *
312
+ * @param values - array of { commodity_quantity, value } objects
313
+ */
314
+ export declare function makePowerMeasurement(values: PowerMeasurementValue[]): S2PowerMeasurementMessage;
315
+ /**
316
+ * Build a PEBC.PowerConstraints message with matching LOWER_LIMIT and
317
+ * UPPER_LIMIT ranges and consequence_type DEFER.
318
+ *
319
+ * Wire format (s2-ws-json 0.0.2-beta):
320
+ * id + allowed_limit_ranges are top-level fields on the message itself,
321
+ * not nested under a power_constraints array.
322
+ */
323
+ export declare function makePEBCPowerConstraints(input: PEBCPowerConstraintsInput): object;
324
+ /**
325
+ * Build a PowerForecast message from pre-formed elements.
326
+ *
327
+ * @param input.startTime - ISO 8601 timezone-aware datetime for the first element
328
+ * @param input.elements - array of { duration (ms), power_values: [{ commodity_quantity, value_expected }] }
329
+ */
330
+ export declare function makePowerForecast(input: PowerForecastInput): object;
331
+ /**
332
+ * Create an InstructionStatusUpdate message reporting the current lifecycle state of an instruction.
333
+ *
334
+ * @param instructionId - the `id` field of the instruction (not its message_id)
335
+ * @param status - one of InstructionStatus values
336
+ */
337
+ export declare function makeInstructionStatusUpdate(instructionId: string, status: InstructionStatusValue): InstructionStatusUpdateMessage;
338
+ /**
339
+ * Generate a UUID v4 message ID.
340
+ * Node >=14.17.4 is required, which includes crypto.randomUUID.
341
+ */
342
+ export declare function generateId(): string;