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
@@ -0,0 +1,330 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RevokableObjects = exports.InstructionStatus = exports.ReceptionStatusResult = exports.GRID_CONNECTIONS = exports.ControlType = exports.MessageType = void 0;
4
+ exports.gridConnectionToWatts = gridConnectionToWatts;
5
+ exports.parse = parse;
6
+ exports.serialize = serialize;
7
+ exports.makeReceptionStatus = makeReceptionStatus;
8
+ exports.makeHandshake = makeHandshake;
9
+ exports.makeResourceManagerDetails = makeResourceManagerDetails;
10
+ exports.makeOMBCSystemDescription = makeOMBCSystemDescription;
11
+ exports.makeOMBCStatus = makeOMBCStatus;
12
+ exports.makePowerMeasurement = makePowerMeasurement;
13
+ exports.makePEBCPowerConstraints = makePEBCPowerConstraints;
14
+ exports.makePowerForecast = makePowerForecast;
15
+ exports.makeInstructionStatusUpdate = makeInstructionStatusUpdate;
16
+ exports.generateId = generateId;
17
+ const crypto_1 = require("crypto");
18
+ /**
19
+ * S2 protocol message type constants (EN 50491-12-2).
20
+ * These are the message_type values used in S2 JSON messages.
21
+ */
22
+ exports.MessageType = Object.freeze({
23
+ // Handshake
24
+ HANDSHAKE: 'Handshake',
25
+ HANDSHAKE_RESPONSE: 'HandshakeResponse',
26
+ // Session
27
+ SESSION_REQUEST: 'SessionRequest',
28
+ // Resource Manager -> CEM
29
+ RESOURCE_MANAGER_DETAILS: 'ResourceManagerDetails',
30
+ POWER_MEASUREMENT: 'PowerMeasurement',
31
+ RECEPTION_STATUS: 'ReceptionStatus',
32
+ REVOKE_OBJECT: 'RevokeObject',
33
+ SELECT_CONTROL_TYPE: 'SelectControlType',
34
+ // CEM -> Resource Manager
35
+ INSTRUCTION: 'Instruction',
36
+ // Fill-rate Based Control (FRBC)
37
+ FRBC_ACTUATOR_STATUS: 'FRBC.ActuatorStatus',
38
+ FRBC_FILL_LEVEL_TARGET_PROFILE: 'FRBC.FillLevelTargetProfile',
39
+ FRBC_LEAKAGE_BEHAVIOUR: 'FRBC.LeakagebBehaviour',
40
+ FRBC_STORAGE_STATUS: 'FRBC.StorageStatus',
41
+ FRBC_SYSTEM_DESCRIPTION: 'FRBC.SystemDescription',
42
+ FRBC_TIMER_STATUS: 'FRBC.TimerStatus',
43
+ FRBC_USAGE_FORECAST: 'FRBC.UsageForecast',
44
+ FRBC_INSTRUCTION: 'FRBC.Instruction',
45
+ // Demand Driven Based Control (DDBC)
46
+ DDBC_ACTUAL_POWER: 'DDBC.ActualPower',
47
+ DDBC_AVERAGE_DEMAND_RATE_FORECAST: 'DDBC.AverageDemandRateForecast',
48
+ DDBC_SYSTEM_DESCRIPTION: 'DDBC.SystemDescription',
49
+ DDBC_TIMER_STATUS: 'DDBC.TimerStatus',
50
+ DDBC_INSTRUCTION: 'DDBC.Instruction',
51
+ // Power Profile Based Control (PPBC)
52
+ PPBC_POWER_PROFILE_STATUS: 'PPBC.PowerProfileStatus',
53
+ PPBC_SCHEDULE_INSTRUCTION: 'PPBC.ScheduleInstruction',
54
+ PPBC_START_INTERRUPTION_INSTRUCTION: 'PPBC.StartInterruptionInstruction',
55
+ PPBC_END_INTERRUPTION_INSTRUCTION: 'PPBC.EndInterruptionInstruction',
56
+ PPBC_POWER_PROFILE: 'PPBC.PowerProfileDefinition',
57
+ // Operation Mode Based Control (OMBC)
58
+ OMBC_SYSTEM_DESCRIPTION: 'OMBC.SystemDescription',
59
+ OMBC_STATUS: 'OMBC.Status',
60
+ OMBC_INSTRUCTION: 'OMBC.Instruction',
61
+ // Power Envelope Based Control (PEBC)
62
+ PEBC_POWER_CONSTRAINTS: 'PEBC.PowerConstraints',
63
+ PEBC_DEVICE_CONSTRAINTS: 'PEBC.DeviceConstraints',
64
+ PEBC_ENERGY_CONSTRAINT: 'PEBC.EnergyConstraint',
65
+ PEBC_INSTRUCTION: 'PEBC.Instruction',
66
+ // Common
67
+ POWER_FORECAST: 'PowerForecast',
68
+ INSTRUCTION_STATUS_UPDATE: 'InstructionStatusUpdate'
69
+ });
70
+ /**
71
+ * S2 control type identifiers (full names as used in s2python / s2-ws-json).
72
+ */
73
+ exports.ControlType = Object.freeze({
74
+ NOT_CONTROLABLE: 'NOT_CONTROLABLE',
75
+ OMBC: 'OPERATION_MODE_BASED_CONTROL',
76
+ FRBC: 'FILL_RATE_BASED_CONTROL',
77
+ DDBC: 'DEMAND_DRIVEN_BASED_CONTROL',
78
+ PPBC: 'POWER_PROFILE_BASED_CONTROL',
79
+ PEBC: 'POWER_ENVELOPE_BASED_CONTROL',
80
+ NO_SELECTION: 'NO_SELECTION'
81
+ });
82
+ /**
83
+ * ReceptionStatus result values (matches s2python ReceptionStatusValues).
84
+ */
85
+ /**
86
+ * Standard European grid connection types with their max power capacity at 230V nominal.
87
+ * Used to set PEBC power constraint outer limits (bidirectional: [-maxWatts, +maxWatts]).
88
+ */
89
+ exports.GRID_CONNECTIONS = Object.freeze({
90
+ '1x16A': { phases: 1, ampsPerPhase: 16, maxWatts: 3680 },
91
+ '3x16A': { phases: 3, ampsPerPhase: 16, maxWatts: 11040 },
92
+ '3x25A': { phases: 3, ampsPerPhase: 25, maxWatts: 17250 },
93
+ '3x32A': { phases: 3, ampsPerPhase: 32, maxWatts: 22080 },
94
+ '3x40A': { phases: 3, ampsPerPhase: 40, maxWatts: 27600 },
95
+ '3x63A': { phases: 3, ampsPerPhase: 63, maxWatts: 43470 }
96
+ });
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
+ function gridConnectionToWatts(gridConnection, customMaxPowerW) {
103
+ if (!gridConnection)
104
+ return null;
105
+ if (gridConnection === 'custom')
106
+ return customMaxPowerW !== null && customMaxPowerW !== void 0 ? customMaxPowerW : null;
107
+ const conn = exports.GRID_CONNECTIONS[gridConnection];
108
+ return conn ? conn.maxWatts : null;
109
+ }
110
+ exports.ReceptionStatusResult = Object.freeze({
111
+ OK: 'OK',
112
+ INVALID_DATA: 'INVALID_DATA',
113
+ INVALID_MESSAGE: 'INVALID_MESSAGE',
114
+ INVALID_CONTENT: 'INVALID_CONTENT',
115
+ TEMPORARY_ERROR: 'TEMPORARY_ERROR',
116
+ PERMANENT_ERROR: 'PERMANENT_ERROR'
117
+ });
118
+ exports.InstructionStatus = Object.freeze({
119
+ NEW: 'NEW',
120
+ ACCEPTED: 'ACCEPTED',
121
+ REJECTED: 'REJECTED',
122
+ REVOKED: 'REVOKED',
123
+ STARTED: 'STARTED',
124
+ SUCCEEDED: 'SUCCEEDED',
125
+ ABORTED: 'ABORTED'
126
+ });
127
+ exports.RevokableObjects = Object.freeze({
128
+ DDBC_AVERAGE_DEMAND_RATE_FORECAST: 'DDBC.AverageDemandRateForecast',
129
+ DDBC_INSTRUCTION: 'DDBC.Instruction',
130
+ DDBC_SYSTEM_DESCRIPTION: 'DDBC.SystemDescription',
131
+ FRBC_FILL_LEVEL_TARGET_PROFILE: 'FRBC.FillLevelTargetProfile',
132
+ FRBC_INSTRUCTION: 'FRBC.Instruction',
133
+ FRBC_LEAKAGE_BEHAVIOUR: 'FRBC.LeakageBehaviour',
134
+ FRBC_SYSTEM_DESCRIPTION: 'FRBC.SystemDescription',
135
+ FRBC_USAGE_FORECAST: 'FRBC.UsageForecast',
136
+ OMBC_INSTRUCTION: 'OMBC.Instruction',
137
+ OMBC_SYSTEM_DESCRIPTION: 'OMBC.SystemDescription',
138
+ PEBC_ENERGY_CONSTRAINT: 'PEBC.EnergyConstraint',
139
+ PEBC_INSTRUCTION: 'PEBC.Instruction',
140
+ PEBC_POWER_CONSTRAINTS: 'PEBC.PowerConstraints',
141
+ PPBC_END_INTERRUPTION_INSTRUCTION: 'PPBC.EndInterruptionInstruction',
142
+ PPBC_POWER_PROFILE_DEFINITION: 'PPBC.PowerProfileDefinition',
143
+ PPBC_SCHEDULE_INSTRUCTION: 'PPBC.ScheduleInstruction',
144
+ PPBC_START_INTERRUPTION_INSTRUCTION: 'PPBC.StartInterruptionInstruction'
145
+ });
146
+ /**
147
+ * Parse a raw WebSocket message string into an S2 message object.
148
+ * Returns null and calls onError if parsing fails.
149
+ *
150
+ * @param raw - raw JSON string
151
+ * @param onError - called with an Error if parsing fails
152
+ */
153
+ function parse(raw, onError) {
154
+ try {
155
+ const msg = JSON.parse(raw);
156
+ if (!msg.message_type) {
157
+ onError(new Error('S2 message missing message_type'));
158
+ return null;
159
+ }
160
+ return msg;
161
+ }
162
+ catch (err) {
163
+ onError(new Error(`Failed to parse S2 message: ${err.message}`));
164
+ return null;
165
+ }
166
+ }
167
+ /**
168
+ * Serialize an S2 message object to a string for sending over the wire.
169
+ */
170
+ function serialize(msg) {
171
+ return JSON.stringify(msg);
172
+ }
173
+ /**
174
+ * Create a ReceptionStatus message acknowledging a received message.
175
+ *
176
+ * @param subjectMessageId - message_id of the message being acknowledged
177
+ * @param status - one of ReceptionStatusResult values
178
+ * @param diagnosticLabel - optional human-readable diagnostic
179
+ */
180
+ function makeReceptionStatus(subjectMessageId, status, diagnosticLabel) {
181
+ const msg = {
182
+ message_type: exports.MessageType.RECEPTION_STATUS,
183
+ subject_message_id: subjectMessageId,
184
+ status
185
+ };
186
+ if (diagnosticLabel) {
187
+ msg.diagnostic_label = diagnosticLabel;
188
+ }
189
+ return msg;
190
+ }
191
+ /**
192
+ * Create a Handshake message (sent by RM on connect).
193
+ *
194
+ * @param rmId - unique ID identifying this Resource Manager
195
+ */
196
+ function makeHandshake(rmId) {
197
+ void rmId; // included in API for symmetry; not sent in Handshake per spec
198
+ return {
199
+ message_type: exports.MessageType.HANDSHAKE,
200
+ message_id: generateId(),
201
+ role: 'RM',
202
+ supported_protocol_versions: ['0.0.2-beta']
203
+ };
204
+ }
205
+ /**
206
+ * Create a ResourceManagerDetails message (sent by RM after HandshakeResponse).
207
+ */
208
+ function makeResourceManagerDetails(details) {
209
+ return {
210
+ message_type: exports.MessageType.RESOURCE_MANAGER_DETAILS,
211
+ message_id: generateId(),
212
+ resource_id: details.resourceId,
213
+ name: details.name,
214
+ roles: details.roles,
215
+ manufacturer: details.manufacturer || '',
216
+ model: details.model || '',
217
+ serial_number: details.serialNumber || '',
218
+ firmware_version: details.firmwareVersion || '',
219
+ available_control_types: details.availableControlTypes,
220
+ provides_forecast: details.providesForecast || false,
221
+ provides_power_measurement_types: details.providesPowerMeasurementTypes || [],
222
+ instruction_processing_delay: details.instructionProcessingDelay || 0
223
+ };
224
+ }
225
+ /**
226
+ * Create an OMBC.SystemDescription message.
227
+ */
228
+ function makeOMBCSystemDescription(config) {
229
+ return {
230
+ message_type: exports.MessageType.OMBC_SYSTEM_DESCRIPTION,
231
+ message_id: generateId(),
232
+ valid_from: new Date().toISOString(),
233
+ operation_modes: config.operationModes || [],
234
+ transitions: config.transitions || [],
235
+ timers: config.timers || []
236
+ };
237
+ }
238
+ /**
239
+ * Create an OMBC.Status message.
240
+ */
241
+ function makeOMBCStatus(status) {
242
+ const msg = {
243
+ message_type: exports.MessageType.OMBC_STATUS,
244
+ message_id: generateId(),
245
+ active_operation_mode_id: status.activeOperationModeId,
246
+ operation_mode_factor: status.operationModeFactor !== undefined ? status.operationModeFactor : 1
247
+ };
248
+ if (status.previousOperationModeId) {
249
+ msg.previous_operation_mode_id = status.previousOperationModeId;
250
+ }
251
+ if (status.transitionTimestamp) {
252
+ msg.transition_timestamp = status.transitionTimestamp;
253
+ }
254
+ return msg;
255
+ }
256
+ /**
257
+ * Create a PowerMeasurement message (sent by RM to report current power).
258
+ *
259
+ * @param values - array of { commodity_quantity, value } objects
260
+ */
261
+ function makePowerMeasurement(values) {
262
+ return {
263
+ message_type: exports.MessageType.POWER_MEASUREMENT,
264
+ message_id: generateId(),
265
+ measurement_timestamp: new Date().toISOString(),
266
+ values
267
+ };
268
+ }
269
+ /**
270
+ * Build a PEBC.PowerConstraints message with matching LOWER_LIMIT and
271
+ * UPPER_LIMIT ranges and consequence_type DEFER.
272
+ *
273
+ * Wire format (s2-ws-json 0.0.2-beta):
274
+ * id + allowed_limit_ranges are top-level fields on the message itself,
275
+ * not nested under a power_constraints array.
276
+ */
277
+ function makePEBCPowerConstraints(input) {
278
+ var _a;
279
+ const validFrom = (_a = input.validFrom) !== null && _a !== void 0 ? _a : new Date().toISOString();
280
+ const rangeBoundary = { start_of_range: input.minPower, end_of_range: input.maxPower };
281
+ return {
282
+ message_type: exports.MessageType.PEBC_POWER_CONSTRAINTS,
283
+ message_id: generateId(),
284
+ id: generateId(),
285
+ valid_from: validFrom,
286
+ valid_until: null,
287
+ consequence_type: 'DEFER',
288
+ allowed_limit_ranges: [
289
+ { commodity_quantity: input.commodityQuantity, limit_type: 'LOWER_LIMIT', range_boundary: rangeBoundary, abnormal_condition_only: false },
290
+ { commodity_quantity: input.commodityQuantity, limit_type: 'UPPER_LIMIT', range_boundary: rangeBoundary, abnormal_condition_only: false }
291
+ ]
292
+ };
293
+ }
294
+ /**
295
+ * Build a PowerForecast message from pre-formed elements.
296
+ *
297
+ * @param input.startTime - ISO 8601 timezone-aware datetime for the first element
298
+ * @param input.elements - array of { duration (ms), power_values: [{ commodity_quantity, value_expected }] }
299
+ */
300
+ function makePowerForecast(input) {
301
+ return {
302
+ message_type: exports.MessageType.POWER_FORECAST,
303
+ message_id: generateId(),
304
+ start_time: input.startTime,
305
+ elements: input.elements
306
+ };
307
+ }
308
+ /**
309
+ * Create an InstructionStatusUpdate message reporting the current lifecycle state of an instruction.
310
+ *
311
+ * @param instructionId - the `id` field of the instruction (not its message_id)
312
+ * @param status - one of InstructionStatus values
313
+ */
314
+ function makeInstructionStatusUpdate(instructionId, status) {
315
+ return {
316
+ message_type: exports.MessageType.INSTRUCTION_STATUS_UPDATE,
317
+ message_id: generateId(),
318
+ instruction_id: instructionId,
319
+ status_type: status,
320
+ timestamp: new Date().toISOString()
321
+ };
322
+ }
323
+ /**
324
+ * Generate a UUID v4 message ID.
325
+ * Node >=14.17.4 is required, which includes crypto.randomUUID.
326
+ */
327
+ function generateId() {
328
+ return (0, crypto_1.randomUUID)();
329
+ }
330
+ //# sourceMappingURL=messages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messages.js","sourceRoot":"","sources":["../../../src/lib/s2/messages.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ import type { PowerForecastInput } from './messages';
2
+ export interface ScheduleElement {
3
+ startMs: number;
4
+ endMs: number;
5
+ duration: number;
6
+ lowerBound: number | null;
7
+ upperBound: number | null;
8
+ }
9
+ export interface PebcSchedule {
10
+ receivedAt: number;
11
+ cemId: string;
12
+ instructionId: string;
13
+ commodityQuantity: string;
14
+ elements: ScheduleElement[];
15
+ }
16
+ /**
17
+ * Parse a PEBC.Instruction message into a PebcSchedule with absolute timestamps.
18
+ * Returns null if the message contains no usable power_envelopes.
19
+ */
20
+ export declare function parsePebcInstruction(msg: Record<string, unknown>, receivedAt: number, cemId?: string): PebcSchedule | null;
21
+ /**
22
+ * Return the element active at nowMs, or null if outside the schedule window.
23
+ */
24
+ export declare function getActiveElement(schedule: PebcSchedule, nowMs: number): ScheduleElement | null;
25
+ /**
26
+ * Return the start time (ms) of the element that follows the currently active one,
27
+ * or null if in the last element or outside the schedule window.
28
+ */
29
+ export declare function getNextElementStart(schedule: PebcSchedule, nowMs: number): number | null;
30
+ /**
31
+ * Return a copy of forecast with value_expected (and value_upper/lower_limit when present)
32
+ * clamped to the tightest PEBC bounds overlapping each forecast element's time window.
33
+ * Elements with no overlapping PEBC bound are passed through unchanged.
34
+ */
35
+ export declare function capForecastToSchedule(forecast: PowerForecastInput, schedule: PebcSchedule): PowerForecastInput;
@@ -0,0 +1,109 @@
1
+ 'use strict';
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parsePebcInstruction = parsePebcInstruction;
4
+ exports.getActiveElement = getActiveElement;
5
+ exports.getNextElementStart = getNextElementStart;
6
+ exports.capForecastToSchedule = capForecastToSchedule;
7
+ /**
8
+ * Parse a PEBC.Instruction message into a PebcSchedule with absolute timestamps.
9
+ * Returns null if the message contains no usable power_envelopes.
10
+ */
11
+ function parsePebcInstruction(msg, receivedAt, cemId = '') {
12
+ var _a;
13
+ const envelopes = msg.power_envelopes;
14
+ if (!Array.isArray(envelopes) || envelopes.length === 0)
15
+ return null;
16
+ const envelope = envelopes[0];
17
+ const rawElements = (_a = envelope.power_envelope_elements) !== null && _a !== void 0 ? _a : [];
18
+ const executionTimeMs = msg.execution_time
19
+ ? new Date(msg.execution_time).getTime()
20
+ : receivedAt;
21
+ let cursor = executionTimeMs;
22
+ const elements = rawElements.map((el) => {
23
+ var _a, _b;
24
+ const startMs = cursor;
25
+ const endMs = cursor + el.duration;
26
+ cursor = endMs;
27
+ return {
28
+ startMs,
29
+ endMs,
30
+ duration: el.duration,
31
+ lowerBound: (_a = el.lower_limit) !== null && _a !== void 0 ? _a : null,
32
+ upperBound: (_b = el.upper_limit) !== null && _b !== void 0 ? _b : null
33
+ };
34
+ });
35
+ return {
36
+ receivedAt,
37
+ cemId,
38
+ instructionId: msg.id,
39
+ commodityQuantity: envelope.commodity_quantity,
40
+ elements
41
+ };
42
+ }
43
+ /**
44
+ * Return the element active at nowMs, or null if outside the schedule window.
45
+ */
46
+ function getActiveElement(schedule, nowMs) {
47
+ for (const el of schedule.elements) {
48
+ if (nowMs >= el.startMs && nowMs < el.endMs)
49
+ return el;
50
+ }
51
+ return null;
52
+ }
53
+ /**
54
+ * Return the start time (ms) of the element that follows the currently active one,
55
+ * or null if in the last element or outside the schedule window.
56
+ */
57
+ function getNextElementStart(schedule, nowMs) {
58
+ for (let i = 0; i < schedule.elements.length - 1; i++) {
59
+ const el = schedule.elements[i];
60
+ if (nowMs >= el.startMs && nowMs < el.endMs)
61
+ return schedule.elements[i + 1].startMs;
62
+ }
63
+ return null;
64
+ }
65
+ /**
66
+ * Return a copy of forecast with value_expected (and value_upper/lower_limit when present)
67
+ * clamped to the tightest PEBC bounds overlapping each forecast element's time window.
68
+ * Elements with no overlapping PEBC bound are passed through unchanged.
69
+ */
70
+ function capForecastToSchedule(forecast, schedule) {
71
+ let cursor = new Date(forecast.startTime).getTime();
72
+ const elements = forecast.elements.map(el => {
73
+ const elStart = cursor;
74
+ const elEnd = cursor + el.duration;
75
+ cursor = elEnd;
76
+ let effectiveUpper = null;
77
+ let effectiveLower = null;
78
+ for (const schedEl of schedule.elements) {
79
+ if (elEnd <= schedEl.startMs || elStart >= schedEl.endMs)
80
+ continue;
81
+ if (schedEl.upperBound !== null) {
82
+ effectiveUpper = effectiveUpper === null ? schedEl.upperBound : Math.min(effectiveUpper, schedEl.upperBound);
83
+ }
84
+ if (schedEl.lowerBound !== null) {
85
+ effectiveLower = effectiveLower === null ? schedEl.lowerBound : Math.max(effectiveLower, schedEl.lowerBound);
86
+ }
87
+ }
88
+ if (effectiveUpper === null && effectiveLower === null)
89
+ return el;
90
+ const power_values = el.power_values.map(pv => {
91
+ let expected = pv.value_expected;
92
+ if (effectiveUpper !== null)
93
+ expected = Math.min(expected, effectiveUpper);
94
+ if (effectiveLower !== null)
95
+ expected = Math.max(expected, effectiveLower);
96
+ const capped = { ...pv, value_expected: expected };
97
+ if (pv.value_upper_limit !== undefined && effectiveUpper !== null) {
98
+ capped.value_upper_limit = Math.min(pv.value_upper_limit, effectiveUpper);
99
+ }
100
+ if (pv.value_lower_limit !== undefined && effectiveLower !== null) {
101
+ capped.value_lower_limit = Math.max(pv.value_lower_limit, effectiveLower);
102
+ }
103
+ return capped;
104
+ });
105
+ return { ...el, power_values };
106
+ });
107
+ return { ...forecast, elements };
108
+ }
109
+ //# sourceMappingURL=schedule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schedule.js","sourceRoot":"","sources":["../../../src/lib/s2/schedule.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,138 @@
1
+ import { ControlTypeValue, InstructionStatusValue, S2IncomingMessage, RmDetails, OMBCSystemDescriptionConfig, OMBCStatusConfig, PEBCPowerConstraintsInput } from './messages';
2
+ /**
3
+ * S2 session states.
4
+ */
5
+ export declare const State: Readonly<{
6
+ readonly HANDSHAKING: "HANDSHAKING";
7
+ readonly CONNECTED: "CONNECTED";
8
+ }>;
9
+ export type StateValue = (typeof State)[keyof typeof State];
10
+ export interface OMBCConfig {
11
+ systemDescription?: OMBCSystemDescriptionConfig;
12
+ status?: OMBCStatusConfig;
13
+ }
14
+ export interface ControlTypeConfig {
15
+ OMBC?: OMBCConfig;
16
+ [key: string]: unknown;
17
+ }
18
+ export interface S2SessionOptions {
19
+ cemId: string;
20
+ rmDetails?: RmDetails;
21
+ controlTypeConfig?: ControlTypeConfig;
22
+ onSend?: (msg: object) => void;
23
+ onStateChange?: (state: StateValue) => void;
24
+ onMessage?: (msg: S2IncomingMessage) => void;
25
+ onInstruction?: (msg: S2IncomingMessage) => void;
26
+ onError?: (err: Error) => void;
27
+ retryDelayMs?: number;
28
+ /** When true, skip auto-sending InstructionStatusUpdate(ACCEPTED) on instruction receipt.
29
+ * ReceptionStatus and OMBC.Status are still sent. Use to reduce D-Bus traffic when
30
+ * the CEM does not require acknowledgment messages. */
31
+ skipInstructionStatus?: boolean;
32
+ }
33
+ /**
34
+ * S2Session manages the S2 protocol state for a single CEM connection.
35
+ *
36
+ * The RM (us) initiates the Handshake immediately after the CEM connects.
37
+ * This applies to both transports:
38
+ * - D-Bus (acload): CEM calls Connect on D-Bus, RM sends Handshake via Message signal
39
+ * - WebSocket (future): CEM opens WS connection, RM sends Handshake over the socket
40
+ *
41
+ * Usage:
42
+ * const session = new S2Session({
43
+ * cemId,
44
+ * rmDetails,
45
+ * controlTypeConfig: { // optional control type configuration
46
+ * OMBC: {
47
+ * systemDescription: { operationModes: [...], transitions: [], timers: [] },
48
+ * status: { activeOperationModeId: 'mode-1', operationModeFactor: 1 }
49
+ * }
50
+ * },
51
+ * onSend: (msg) => <send S2 message object to CEM>,
52
+ * onStateChange: (state) => ...,
53
+ * onMessage: (msg) => <forward received message downstream>,
54
+ * onError: (err) => ...
55
+ * })
56
+ *
57
+ * session.start() // sends Handshake, call after Connect
58
+ * session.handleMessage(msg) // call for each Message from the CEM
59
+ * session.send(msg) // send an outbound S2 message (only when CONNECTED)
60
+ */
61
+ export declare class S2Session {
62
+ private readonly _cemId;
63
+ private readonly _rmDetails;
64
+ private readonly _controlTypeConfig;
65
+ private readonly _onSend;
66
+ private readonly _onStateChange;
67
+ private readonly _onMessage;
68
+ private readonly _onInstruction;
69
+ private readonly _onError;
70
+ private readonly _retryDelayMs;
71
+ private readonly _skipInstructionStatus;
72
+ private readonly _sentMessages;
73
+ private readonly _retryTimers;
74
+ private _state;
75
+ private _selectedControlType;
76
+ private _lastKeepAlive;
77
+ private _pebcPowerConstraints;
78
+ private _currentOMBCStatus;
79
+ constructor({ cemId, rmDetails, controlTypeConfig, onSend, onStateChange, onMessage, onInstruction, onError, retryDelayMs, skipInstructionStatus }: S2SessionOptions);
80
+ get cemId(): string;
81
+ get state(): StateValue;
82
+ get selectedControlType(): ControlTypeValue | string;
83
+ get lastKeepAlive(): Date | null;
84
+ get currentOMBCStatus(): OMBCStatusConfig | null;
85
+ /**
86
+ * Cancel pending retry timers and clear the sent-message buffer.
87
+ * Call when the session is no longer needed (disconnect, node close).
88
+ */
89
+ dispose(): void;
90
+ /**
91
+ * Record that a keepalive was received from the CEM.
92
+ */
93
+ keepAlive(): void;
94
+ /**
95
+ * Send the initial Handshake to the CEM. Call once after the CEM connects.
96
+ */
97
+ start(): void;
98
+ /**
99
+ * Process an S2 message received from the CEM.
100
+ * Accepts a raw JSON string or an already-parsed object.
101
+ */
102
+ handleMessage(raw: string | S2IncomingMessage): void;
103
+ /**
104
+ * Send an S2 message object to the CEM. Only valid when CONNECTED.
105
+ */
106
+ send(msg: object): void;
107
+ /**
108
+ * Store PEBC power constraints and send them to the CEM immediately if
109
+ * PEBC is the active control type. Stored constraints are also sent
110
+ * automatically when SelectControlType(PEBC) is received.
111
+ */
112
+ setPEBCPowerConstraints(input: PEBCPowerConstraintsInput): void;
113
+ /**
114
+ * Update the tracked OMBC status and send OMBC.Status to the CEM.
115
+ * Call after an OMBC.Instruction is dispatched to inform the CEM of the new
116
+ * active operation mode. Also updates the status used when SELECT_CONTROL_TYPE
117
+ * is received again (e.g. after a CEM reconnect).
118
+ */
119
+ updateOMBCStatus(status: OMBCStatusConfig): void;
120
+ /**
121
+ * Send an InstructionStatusUpdate for an instruction previously received from the CEM.
122
+ * Use this to report STARTED, SUCCEEDED, ABORTED, etc. after ACCEPTED was auto-sent.
123
+ *
124
+ * @param instructionId - the `id` field of the instruction (not its message_id)
125
+ * @param status - one of InstructionStatus values
126
+ */
127
+ sendInstructionStatus(instructionId: string, status: InstructionStatusValue): void;
128
+ /**
129
+ * Buffer the message by its message_id (if present) and forward to onSend.
130
+ * Buffered messages can be retried when the CEM responds with TEMPORARY_ERROR.
131
+ */
132
+ private _send;
133
+ private _setState;
134
+ private _handleHandshakeResponse;
135
+ private _handleSelectControlType;
136
+ private _sendOMBCSystemDescriptionAndStatus;
137
+ private _ackAndForward;
138
+ }