@attraccess/plugin-wago 0.1.0-nightly.950.1 → 0.1.0-nightly.951.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.
- package/dist/index.js +141 -32
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -51749,7 +51749,7 @@ function parseOperationalMessage(prefix, topic, payload) {
|
|
|
51749
51749
|
const value = parseObject(payload, "operational message");
|
|
51750
51750
|
const timestamp = requiredTimestamp(value.timestamp);
|
|
51751
51751
|
const sequence = requiredSequence(value.sequence);
|
|
51752
|
-
if (
|
|
51752
|
+
if (typeof value.streamId !== "string" || !value.streamId.trim() || value.streamId.length > 128)
|
|
51753
51753
|
throw new Error("operational streamId is invalid");
|
|
51754
51754
|
const streamId = value.streamId;
|
|
51755
51755
|
if (suffix === "state") {
|
|
@@ -51760,7 +51760,7 @@ function parseOperationalMessage(prefix, topic, payload) {
|
|
|
51760
51760
|
message: {
|
|
51761
51761
|
category: "state",
|
|
51762
51762
|
timestamp,
|
|
51763
|
-
|
|
51763
|
+
streamId,
|
|
51764
51764
|
sequence,
|
|
51765
51765
|
connected: value.connected,
|
|
51766
51766
|
revision: value.revision,
|
|
@@ -51772,7 +51772,6 @@ function parseOperationalMessage(prefix, topic, payload) {
|
|
|
51772
51772
|
};
|
|
51773
51773
|
}
|
|
51774
51774
|
if (suffix === "measurements") {
|
|
51775
|
-
if (streamId === void 0) throw new Error("operational streamId is required for measurements");
|
|
51776
51775
|
return {
|
|
51777
51776
|
hardwareId,
|
|
51778
51777
|
message: {
|
|
@@ -51792,7 +51791,7 @@ function parseOperationalMessage(prefix, topic, payload) {
|
|
|
51792
51791
|
message: {
|
|
51793
51792
|
category: "fault",
|
|
51794
51793
|
timestamp,
|
|
51795
|
-
|
|
51794
|
+
streamId,
|
|
51796
51795
|
sequence,
|
|
51797
51796
|
channelId: value.channelId,
|
|
51798
51797
|
code: value.code,
|
|
@@ -51807,7 +51806,7 @@ function parseOperationalMessage(prefix, topic, payload) {
|
|
|
51807
51806
|
message: {
|
|
51808
51807
|
category: "acknowledgement",
|
|
51809
51808
|
timestamp,
|
|
51810
|
-
|
|
51809
|
+
streamId,
|
|
51811
51810
|
sequence,
|
|
51812
51811
|
id: value.id,
|
|
51813
51812
|
status: value.status,
|
|
@@ -58179,6 +58178,7 @@ var MAX_CACHE_ENTRIES = 2e3;
|
|
|
58179
58178
|
var MAX_PENDING_DISPATCHES = 100;
|
|
58180
58179
|
var MAX_CONCURRENT_DISPATCHES = 10;
|
|
58181
58180
|
var MAX_TIMEOUT_MS = 2147483647;
|
|
58181
|
+
var MAX_RETIRED_STREAMS = 128;
|
|
58182
58182
|
var WagoFlowService = class {
|
|
58183
58183
|
constructor(context) {
|
|
58184
58184
|
__publicField(this, "context", context);
|
|
@@ -58187,8 +58187,12 @@ var WagoFlowService = class {
|
|
|
58187
58187
|
__publicField(this, "settings");
|
|
58188
58188
|
__publicField(this, "cache", /* @__PURE__ */ new Map());
|
|
58189
58189
|
__publicField(this, "controllerByHardwareId", /* @__PURE__ */ new Map());
|
|
58190
|
-
__publicField(this, "
|
|
58191
|
-
__publicField(this, "
|
|
58190
|
+
__publicField(this, "streams", /* @__PURE__ */ new Map());
|
|
58191
|
+
__publicField(this, "offlineControllers", /* @__PURE__ */ new Set());
|
|
58192
|
+
__publicField(this, "unavailableHardware", /* @__PURE__ */ new Set());
|
|
58193
|
+
__publicField(this, "unavailableConfiguration", /* @__PURE__ */ new Set());
|
|
58194
|
+
__publicField(this, "appliedConfigurations", /* @__PURE__ */ new Map());
|
|
58195
|
+
__publicField(this, "processingByController", /* @__PURE__ */ new Map());
|
|
58192
58196
|
__publicField(this, "channelCache", /* @__PURE__ */ new Map());
|
|
58193
58197
|
__publicField(this, "waiters", /* @__PURE__ */ new Set());
|
|
58194
58198
|
__publicField(this, "waitersByKey", /* @__PURE__ */ new Map());
|
|
@@ -58221,6 +58225,14 @@ var WagoFlowService = class {
|
|
|
58221
58225
|
const settings = await this.settings.findOneBy({ id: 1 });
|
|
58222
58226
|
if (!settings) return;
|
|
58223
58227
|
const controllers = await this.controllers.find({ where: { trustState: "claimed" } });
|
|
58228
|
+
const controllerIds = new Set(controllers.map((controller) => controller.id));
|
|
58229
|
+
for (const id of this.offlineControllers) if (!controllerIds.has(id)) this.offlineControllers.delete(id);
|
|
58230
|
+
for (const id of this.unavailableHardware) if (!controllerIds.has(id)) this.unavailableHardware.delete(id);
|
|
58231
|
+
for (const id of this.unavailableConfiguration)
|
|
58232
|
+
if (!controllerIds.has(id)) this.unavailableConfiguration.delete(id);
|
|
58233
|
+
for (const id of this.appliedConfigurations.keys())
|
|
58234
|
+
if (!controllerIds.has(id)) this.appliedConfigurations.delete(id);
|
|
58235
|
+
for (const id of this.streams.keys()) if (!controllerIds.has(id)) this.streams.delete(id);
|
|
58224
58236
|
this.channelCache.clear();
|
|
58225
58237
|
const revisions = await this.loadLatestAppliedRevisions(controllers.map((controller) => controller.id));
|
|
58226
58238
|
for (const revision of revisions) this.cacheChannels(revision);
|
|
@@ -58295,7 +58307,7 @@ var WagoFlowService = class {
|
|
|
58295
58307
|
};
|
|
58296
58308
|
properties.minimumIntervalMs = { type: "number", title: "Minimum interval (ms)", minimum: 0 };
|
|
58297
58309
|
if (channel?.capabilities.includes("measurement"))
|
|
58298
|
-
properties.minimumChange = { type: "number", title: "Minimum change", minimum: 0 };
|
|
58310
|
+
properties.minimumChange = { type: "number", title: "Minimum change (wire units)", minimum: 0 };
|
|
58299
58311
|
}
|
|
58300
58312
|
if (kind !== "event")
|
|
58301
58313
|
properties.category = {
|
|
@@ -58305,7 +58317,10 @@ var WagoFlowService = class {
|
|
|
58305
58317
|
oneOf: this.readCategories(channel).map((category) => ({ const: category, title: category }))
|
|
58306
58318
|
};
|
|
58307
58319
|
if (kind === "wait") {
|
|
58308
|
-
properties.equals = {
|
|
58320
|
+
properties.equals = {
|
|
58321
|
+
type: config.category === "measurement" ? "number" : "boolean",
|
|
58322
|
+
title: config.category === "measurement" ? "Equals (wire value)" : "Equals"
|
|
58323
|
+
};
|
|
58309
58324
|
properties.timeoutMs = { type: "number", title: "Timeout (ms)", minimum: 1, maximum: MAX_TIMEOUT_MS };
|
|
58310
58325
|
}
|
|
58311
58326
|
return { dynamic: true, type: "object", properties, required: ["controllerId", "channelId", "category"] };
|
|
@@ -58317,7 +58332,7 @@ var WagoFlowService = class {
|
|
|
58317
58332
|
const entries = [...this.cache.values()].filter(
|
|
58318
58333
|
(state) => state.controllerId === config.controllerId && state.channelId === config.channelId && (!config.category || state.category === config.category)
|
|
58319
58334
|
);
|
|
58320
|
-
return entries.sort((left, right) => right.
|
|
58335
|
+
return entries.sort((left, right) => right.receivedAt - left.receivedAt)[0] ?? null;
|
|
58321
58336
|
}
|
|
58322
58337
|
async wait(config) {
|
|
58323
58338
|
if (typeof config.controllerId !== "number" || typeof config.channelId !== "string" || typeof config.category !== "string")
|
|
@@ -58325,8 +58340,8 @@ var WagoFlowService = class {
|
|
|
58325
58340
|
const current = this.read(config);
|
|
58326
58341
|
if (current && this.matchesCondition(current, config)) return current;
|
|
58327
58342
|
const timeoutMs = typeof config.timeoutMs === "number" && Number.isFinite(config.timeoutMs) && config.timeoutMs > 0 ? Math.min(config.timeoutMs, MAX_TIMEOUT_MS) : 3e4;
|
|
58343
|
+
const waiterKey = this.cacheKey(config.controllerId, config.channelId, config.category);
|
|
58328
58344
|
return new Promise((resolve2) => {
|
|
58329
|
-
const waiterKey = this.cacheKey(config.controllerId, config.channelId, config.category);
|
|
58330
58345
|
const cleanup = () => {
|
|
58331
58346
|
clearTimeout(timer);
|
|
58332
58347
|
this.waiters.delete(wake);
|
|
@@ -58362,29 +58377,90 @@ var WagoFlowService = class {
|
|
|
58362
58377
|
const entry = this.controllerByHardwareId.get(hardwareId);
|
|
58363
58378
|
if (!entry || entry.serverId !== serverId) return;
|
|
58364
58379
|
const { controller } = entry;
|
|
58365
|
-
const previous = this.sequences.get(controller.id);
|
|
58366
58380
|
const eventTime = Date.parse(event.timestamp);
|
|
58367
|
-
|
|
58368
|
-
|
|
58381
|
+
if (eventTime > Date.now()) {
|
|
58382
|
+
this.context.logger.warn(`Ignoring future-dated WAGO event for ${controller.hardwareId}`);
|
|
58383
|
+
return;
|
|
58384
|
+
}
|
|
58385
|
+
const previous = this.processingByController.get(controller.id) ?? Promise.resolve();
|
|
58386
|
+
const processing = previous.catch(() => void 0).then(() => this.processMessage(controller, event, eventTime));
|
|
58387
|
+
this.processingByController.set(controller.id, processing);
|
|
58388
|
+
try {
|
|
58389
|
+
await processing;
|
|
58390
|
+
} finally {
|
|
58391
|
+
if (this.processingByController.get(controller.id) === processing)
|
|
58392
|
+
this.processingByController.delete(controller.id);
|
|
58393
|
+
}
|
|
58394
|
+
}
|
|
58395
|
+
async processMessage(controller, event, eventTime) {
|
|
58396
|
+
const channels = await this.channels(controller.id);
|
|
58397
|
+
let stream = this.streams.get(controller.id);
|
|
58398
|
+
if (stream?.exhausted) return;
|
|
58399
|
+
if (!stream || stream.active !== event.streamId) {
|
|
58400
|
+
if (event.category !== "state" || stream?.retired.has(event.streamId) || stream && (!event.connected || Date.now() - eventTime > STALE_AFTER_MS2 || eventTime <= stream.latestSourceTime)) {
|
|
58401
|
+
this.context.logger.warn(`Ignoring unestablished or retired WAGO stream for ${controller.hardwareId}`);
|
|
58402
|
+
return;
|
|
58403
|
+
}
|
|
58404
|
+
if (stream && stream.retired.size >= MAX_RETIRED_STREAMS) {
|
|
58405
|
+
stream.exhausted = true;
|
|
58406
|
+
for (const state of this.cache.values()) if (state.controllerId === controller.id) state.invalidated = true;
|
|
58407
|
+
this.context.logger.warn(
|
|
58408
|
+
`WAGO stream history exhausted for ${controller.hardwareId}; refusing further samples`
|
|
58409
|
+
);
|
|
58410
|
+
return;
|
|
58411
|
+
}
|
|
58412
|
+
const retired = stream?.retired ?? /* @__PURE__ */ new Set();
|
|
58413
|
+
if (stream) retired.add(stream.active);
|
|
58414
|
+
stream = {
|
|
58415
|
+
active: event.streamId,
|
|
58416
|
+
latestSourceTime: eventTime,
|
|
58417
|
+
sampleNotBefore: eventTime,
|
|
58418
|
+
retired,
|
|
58419
|
+
sequences: /* @__PURE__ */ new Map()
|
|
58420
|
+
};
|
|
58421
|
+
this.streams.set(controller.id, stream);
|
|
58422
|
+
for (const state of this.cache.values()) if (state.controllerId === controller.id) state.invalidated = true;
|
|
58423
|
+
}
|
|
58424
|
+
const previous = stream.sequences.get(event.category);
|
|
58425
|
+
if (previous !== void 0 && event.sequence <= previous) {
|
|
58369
58426
|
this.context.logger.warn(`Ignoring duplicate or out-of-order WAGO event for ${controller.hardwareId}`);
|
|
58370
58427
|
return;
|
|
58371
58428
|
}
|
|
58372
|
-
if (previous !== void 0 && event.sequence <= previous)
|
|
58373
|
-
this.context.logger.warn(`Resetting WAGO event sequence after controller reconnect for ${controller.hardwareId}`);
|
|
58374
58429
|
if (previous !== void 0 && event.sequence > previous + 1)
|
|
58375
58430
|
this.context.logger.warn(
|
|
58376
58431
|
`WAGO event sequence gap for ${controller.hardwareId}: ${previous} to ${event.sequence}`
|
|
58377
58432
|
);
|
|
58378
|
-
|
|
58379
|
-
|
|
58380
|
-
|
|
58381
|
-
|
|
58382
|
-
|
|
58383
|
-
|
|
58384
|
-
|
|
58385
|
-
|
|
58386
|
-
|
|
58387
|
-
|
|
58433
|
+
stream.sequences.set(event.category, event.sequence);
|
|
58434
|
+
stream.latestSourceTime = Math.max(stream.latestSourceTime, eventTime);
|
|
58435
|
+
if (event.category === "state") {
|
|
58436
|
+
const wasUnavailable = this.offlineControllers.has(controller.id) || this.unavailableHardware.has(controller.id) || this.unavailableConfiguration.has(controller.id) || stream.stateTimestamp !== void 0 && Date.now() - stream.stateTimestamp > STALE_AFTER_MS2;
|
|
58437
|
+
stream.stateTimestamp = eventTime;
|
|
58438
|
+
if (event.connected) this.offlineControllers.delete(controller.id);
|
|
58439
|
+
else this.offlineControllers.add(controller.id);
|
|
58440
|
+
if (event.readiness?.hardwareAvailable === false) this.unavailableHardware.add(controller.id);
|
|
58441
|
+
else if (event.readiness?.hardwareAvailable === true) this.unavailableHardware.delete(controller.id);
|
|
58442
|
+
const applied = this.appliedConfigurations.get(controller.id);
|
|
58443
|
+
if (applied && event.revision === applied.revision && event.contentHash === applied.contentHash)
|
|
58444
|
+
this.unavailableConfiguration.delete(controller.id);
|
|
58445
|
+
else this.unavailableConfiguration.add(controller.id);
|
|
58446
|
+
if (wasUnavailable || !event.connected || this.unavailableHardware.has(controller.id) || this.unavailableConfiguration.has(controller.id))
|
|
58447
|
+
stream.sampleNotBefore = Math.max(stream.sampleNotBefore, eventTime);
|
|
58448
|
+
for (const state of this.cache.values()) {
|
|
58449
|
+
if (state.controllerId !== controller.id) continue;
|
|
58450
|
+
if (wasUnavailable || !event.connected || this.unavailableHardware.has(controller.id) || this.unavailableConfiguration.has(controller.id) || state.category === "state")
|
|
58451
|
+
state.invalidated = true;
|
|
58452
|
+
}
|
|
58453
|
+
for (const channel of channels) {
|
|
58454
|
+
const value = channel.capabilities.includes("input") && Object.hasOwn(event.inputs ?? {}, channel.id) ? event.inputs[channel.id] : channel.capabilities.includes("output") && Object.hasOwn(event.outputs, channel.id) ? event.outputs[channel.id] : void 0;
|
|
58455
|
+
if (typeof value === "boolean") this.store(controller, channel.id, event, value);
|
|
58456
|
+
}
|
|
58457
|
+
} else if ("channelId" in event) {
|
|
58458
|
+
const channel = channels.find((channel2) => channel2.id === event.channelId);
|
|
58459
|
+
if (!channel || event.category === "measurement" && !channel.capabilities.includes("measurement")) return;
|
|
58460
|
+
this.store(controller, event.channelId, event, event.category === "measurement" ? event.value : event);
|
|
58461
|
+
}
|
|
58462
|
+
}
|
|
58463
|
+
store(controller, channelId, event, value) {
|
|
58388
58464
|
const state = {
|
|
58389
58465
|
controllerId: controller.id,
|
|
58390
58466
|
hardwareId: controller.hardwareId,
|
|
@@ -58393,7 +58469,12 @@ var WagoFlowService = class {
|
|
|
58393
58469
|
value,
|
|
58394
58470
|
timestamp: event.timestamp,
|
|
58395
58471
|
sequence: event.sequence,
|
|
58396
|
-
|
|
58472
|
+
streamId: event.streamId,
|
|
58473
|
+
...event.category === "measurement" ? { unit: event.unit, kind: event.kind } : {},
|
|
58474
|
+
...event.category === "state" ? { revision: event.revision, contentHash: event.contentHash } : {},
|
|
58475
|
+
receivedAt: Date.now(),
|
|
58476
|
+
offline: this.offlineControllers.has(controller.id),
|
|
58477
|
+
invalidated: this.unavailableHardware.has(controller.id) || this.unavailableConfiguration.has(controller.id) || Date.parse(event.timestamp) < (this.streams.get(controller.id)?.sampleNotBefore ?? 0)
|
|
58397
58478
|
};
|
|
58398
58479
|
const cacheKey = this.cacheKey(controller.id, channelId, event.category);
|
|
58399
58480
|
const previous = this.cache.get(cacheKey);
|
|
@@ -58435,6 +58516,16 @@ var WagoFlowService = class {
|
|
|
58435
58516
|
cacheChannels(revision) {
|
|
58436
58517
|
try {
|
|
58437
58518
|
const channels = JSON.parse(revision.snapshot).logicalChannels;
|
|
58519
|
+
const previous = this.appliedConfigurations.get(revision.controllerId);
|
|
58520
|
+
if (previous?.revision !== revision.revision || previous?.contentHash !== revision.contentHash) {
|
|
58521
|
+
this.unavailableConfiguration.add(revision.controllerId);
|
|
58522
|
+
for (const state of this.cache.values())
|
|
58523
|
+
if (state.controllerId === revision.controllerId) state.invalidated = true;
|
|
58524
|
+
}
|
|
58525
|
+
this.appliedConfigurations.set(revision.controllerId, {
|
|
58526
|
+
revision: revision.revision,
|
|
58527
|
+
contentHash: revision.contentHash
|
|
58528
|
+
});
|
|
58438
58529
|
this.channelCache.set(revision.controllerId, channels);
|
|
58439
58530
|
return channels;
|
|
58440
58531
|
} catch {
|
|
@@ -58447,21 +58538,21 @@ var WagoFlowService = class {
|
|
|
58447
58538
|
}
|
|
58448
58539
|
categories(channel) {
|
|
58449
58540
|
return [
|
|
58450
|
-
...channel?.capabilities.
|
|
58541
|
+
...channel?.capabilities.some((capability) => capability === "input" || capability === "output") ? ["state"] : [],
|
|
58451
58542
|
...channel?.capabilities.includes("measurement") ? ["measurement"] : [],
|
|
58452
58543
|
"fault"
|
|
58453
58544
|
];
|
|
58454
58545
|
}
|
|
58455
58546
|
readCategories(channel) {
|
|
58456
58547
|
return [
|
|
58457
|
-
...channel?.capabilities.
|
|
58548
|
+
...channel?.capabilities.some((capability) => capability === "input" || capability === "output") ? ["state"] : [],
|
|
58458
58549
|
...channel?.capabilities.includes("measurement") ? ["measurement"] : []
|
|
58459
58550
|
];
|
|
58460
58551
|
}
|
|
58461
58552
|
matchesEvent(config, nodeId, state, previous) {
|
|
58462
58553
|
if (config.controllerId !== state.controllerId || config.channelId !== state.channelId || config.category !== state.category)
|
|
58463
58554
|
return false;
|
|
58464
|
-
if (typeof config.minimumChange === "number" && typeof state.value === "number" && typeof previous?.value === "number" && Math.abs(state.value - previous.value) < config.minimumChange)
|
|
58555
|
+
if (typeof config.minimumChange === "number" && typeof state.value === "number" && typeof previous?.value === "number" && previous.streamId === state.streamId && previous.unit === state.unit && previous.kind === state.kind && Math.abs(state.value - previous.value) < config.minimumChange)
|
|
58465
58556
|
return false;
|
|
58466
58557
|
if (typeof config.minimumIntervalMs === "number") {
|
|
58467
58558
|
const lastDispatchAt = this.lastDispatchAtByNode.get(nodeId);
|
|
@@ -58471,10 +58562,26 @@ var WagoFlowService = class {
|
|
|
58471
58562
|
return true;
|
|
58472
58563
|
}
|
|
58473
58564
|
matchesCondition(state, config) {
|
|
58474
|
-
return config.equals === void 0 || state.value === config.equals;
|
|
58565
|
+
return this.freshness(state).available && (config.equals === void 0 || state.value === config.equals);
|
|
58566
|
+
}
|
|
58567
|
+
freshness(state) {
|
|
58568
|
+
const age = Date.now() - Date.parse(state.timestamp);
|
|
58569
|
+
const stale = !Number.isFinite(age) || age < 0 || age > STALE_AFTER_MS2;
|
|
58570
|
+
const offline = state.offline === true || this.offlineControllers.has(state.controllerId);
|
|
58571
|
+
const stream = this.streams.get(state.controllerId);
|
|
58572
|
+
const stateTimestamp = stream?.stateTimestamp;
|
|
58573
|
+
const connectionStale = stateTimestamp === void 0 || Date.now() - stateTimestamp > STALE_AFTER_MS2;
|
|
58574
|
+
return {
|
|
58575
|
+
stale,
|
|
58576
|
+
offline,
|
|
58577
|
+
connectionStale,
|
|
58578
|
+
available: !stale && !offline && !connectionStale && stream?.active === state.streamId && !stream.exhausted && Date.parse(state.timestamp) >= stream.sampleNotBefore && !state.invalidated && !this.unavailableHardware.has(state.controllerId) && !this.unavailableConfiguration.has(state.controllerId)
|
|
58579
|
+
};
|
|
58475
58580
|
}
|
|
58476
58581
|
payload(state) {
|
|
58477
|
-
|
|
58582
|
+
const payload = { ...state };
|
|
58583
|
+
delete payload.invalidated;
|
|
58584
|
+
return { ...payload, ...this.freshness(state) };
|
|
58478
58585
|
}
|
|
58479
58586
|
dispatch() {
|
|
58480
58587
|
while (this.activeDispatches < MAX_CONCURRENT_DISPATCHES && this.dispatches.length) {
|
|
@@ -58488,6 +58595,8 @@ var WagoFlowService = class {
|
|
|
58488
58595
|
}
|
|
58489
58596
|
}
|
|
58490
58597
|
async trigger({ state, previous }) {
|
|
58598
|
+
const stream = this.streams.get(state.controllerId);
|
|
58599
|
+
if (stream?.active !== state.streamId || stream.exhausted) return;
|
|
58491
58600
|
try {
|
|
58492
58601
|
await this.context.flows.trigger(
|
|
58493
58602
|
"plugin.wago.event-received",
|