@attraccess/plugin-wago 0.1.0-nightly.966.1 → 0.1.0-nightly.970.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
CHANGED
|
@@ -52485,6 +52485,23 @@ async function auditCommissioning(context, principal, id, action, operation, suc
|
|
|
52485
52485
|
var import_node_crypto3 = require("node:crypto");
|
|
52486
52486
|
var import_common2 = require("@nestjs/common");
|
|
52487
52487
|
|
|
52488
|
+
// channel-behavior.ts
|
|
52489
|
+
function outputBehavior(channel) {
|
|
52490
|
+
if (!channel.capabilities.includes("output")) return null;
|
|
52491
|
+
return channel.capabilities.includes("pulse") || channel.pulse !== void 0 ? "pulsed" : "switched";
|
|
52492
|
+
}
|
|
52493
|
+
function supportsOutputAction(channel, action) {
|
|
52494
|
+
const behavior = outputBehavior(channel);
|
|
52495
|
+
return behavior === "switched" ? action === "set" : behavior === "pulsed" && action === "pulse" && !pulseBehaviorError(channel.capabilities, channel.pulse);
|
|
52496
|
+
}
|
|
52497
|
+
function pulseBehaviorError(capabilities, pulse) {
|
|
52498
|
+
if (!capabilities.includes("pulse") && pulse === void 0) return void 0;
|
|
52499
|
+
const duration = pulse && typeof pulse === "object" && !Array.isArray(pulse) ? pulse.durationMs : void 0;
|
|
52500
|
+
if (!capabilities.includes("output") || !capabilities.includes("pulse") || typeof duration !== "number" || !Number.isSafeInteger(duration) || duration <= 0)
|
|
52501
|
+
return "Pulsed behavior requires output and pulse capabilities and a positive integer durationMs.";
|
|
52502
|
+
return void 0;
|
|
52503
|
+
}
|
|
52504
|
+
|
|
52488
52505
|
// backend/configuration.ts
|
|
52489
52506
|
var import_node_crypto2 = require("node:crypto");
|
|
52490
52507
|
|
|
@@ -52579,6 +52596,9 @@ function commandTopic(prefix, hardwareId) {
|
|
|
52579
52596
|
function configurationReportedWildcardTopic(prefix) {
|
|
52580
52597
|
return configurationReportedTopic(prefix, "+");
|
|
52581
52598
|
}
|
|
52599
|
+
function operationalWildcardTopic(prefix) {
|
|
52600
|
+
return `${normalizeOperationalPrefix(prefix)}/v${CONFIGURATION_PROTOCOL_VERSION}/controllers/+/#`;
|
|
52601
|
+
}
|
|
52582
52602
|
function parseOperationalMessage(prefix, topic, payload) {
|
|
52583
52603
|
const root = `${normalizeOperationalPrefix(prefix)}/v${CONFIGURATION_PROTOCOL_VERSION}/controllers/`;
|
|
52584
52604
|
if (!topic.startsWith(root)) return null;
|
|
@@ -53117,7 +53137,6 @@ function validateSnapshot(snapshot) {
|
|
|
53117
53137
|
validateGuard(channel.guard, `${path}.guard`, capabilities, channelIds, errors);
|
|
53118
53138
|
validateFeedback(channel.feedback, `${path}.feedback`, capabilities, channel.id, channelsById, errors);
|
|
53119
53139
|
validateMeasurement(channel.measurement, `${path}.measurement`, capabilities, errors);
|
|
53120
|
-
validateProfile(channel.profile, capabilities, path, errors);
|
|
53121
53140
|
});
|
|
53122
53141
|
return errors;
|
|
53123
53142
|
}
|
|
@@ -53239,17 +53258,11 @@ function validateRange(value, path, capabilities, errors) {
|
|
|
53239
53258
|
errors.push({ path, code: "unsupported_field", message: "range requires input or measurement capability" });
|
|
53240
53259
|
}
|
|
53241
53260
|
function validatePulse(value, path, capabilities, errors) {
|
|
53261
|
+
const message = pulseBehaviorError([...capabilities], value);
|
|
53262
|
+
if (message) errors.push({ path, code: "invalid_pulse", message });
|
|
53242
53263
|
if (value === void 0) return;
|
|
53243
53264
|
if (!record(value, path, errors)) return;
|
|
53244
53265
|
exactKeys(value, path, ["durationMs"], errors);
|
|
53245
|
-
if (!Number.isSafeInteger(value.durationMs) || value.durationMs <= 0)
|
|
53246
|
-
errors.push({
|
|
53247
|
-
path: `${path}.durationMs`,
|
|
53248
|
-
code: "invalid_duration",
|
|
53249
|
-
message: "durationMs must be a positive integer"
|
|
53250
|
-
});
|
|
53251
|
-
if (!capabilities.has("pulse"))
|
|
53252
|
-
errors.push({ path, code: "unsupported_field", message: "pulse requires pulse capability" });
|
|
53253
53266
|
}
|
|
53254
53267
|
function validateGuard(value, path, capabilities, channelIds, errors) {
|
|
53255
53268
|
if (value === void 0) return;
|
|
@@ -53297,22 +53310,6 @@ function validateFeedback(value, path, capabilities, currentChannelId, channelsB
|
|
|
53297
53310
|
if (!capabilities.has("feedback"))
|
|
53298
53311
|
errors.push({ path, code: "unsupported_field", message: "feedback requires feedback capability" });
|
|
53299
53312
|
}
|
|
53300
|
-
function validateProfile(value, capabilities, path, errors) {
|
|
53301
|
-
const required = {
|
|
53302
|
-
"metered-switched-load": ["output", "measurement"],
|
|
53303
|
-
"pulsed-lock-bank": ["output", "pulse"],
|
|
53304
|
-
"guarded-enable-request": ["output", "guard"],
|
|
53305
|
-
"generic-digital-output": ["output"],
|
|
53306
|
-
"generic-monitored-input": ["input"]
|
|
53307
|
-
};
|
|
53308
|
-
required[value]?.filter((capability) => !capabilities.has(capability)).forEach(
|
|
53309
|
-
(capability) => errors.push({
|
|
53310
|
-
path: `${path}.capabilities`,
|
|
53311
|
-
code: "missing_capability",
|
|
53312
|
-
message: `${value} requires ${capability} capability`
|
|
53313
|
-
})
|
|
53314
|
-
);
|
|
53315
|
-
}
|
|
53316
53313
|
function presetChannel(application) {
|
|
53317
53314
|
const base2 = {
|
|
53318
53315
|
id: application.channelId,
|
|
@@ -53944,12 +53941,18 @@ var WagoCommandHandler = class {
|
|
|
53944
53941
|
};
|
|
53945
53942
|
}
|
|
53946
53943
|
if (channel) {
|
|
53947
|
-
const
|
|
53948
|
-
|
|
53949
|
-
|
|
53950
|
-
|
|
53951
|
-
|
|
53952
|
-
|
|
53944
|
+
const pulsed = outputBehavior(channel) === "pulsed";
|
|
53945
|
+
const action = pulsed ? "pulse" : "set";
|
|
53946
|
+
properties.action = {
|
|
53947
|
+
type: "string",
|
|
53948
|
+
title: "Operation",
|
|
53949
|
+
oneOf: [{ const: action, title: pulsed ? "Trigger pulse" : "Turn on / turn off" }],
|
|
53950
|
+
// Never silently reinterpret an existing incompatible flow action.
|
|
53951
|
+
...config.action === void 0 ? { default: action } : {},
|
|
53952
|
+
refreshesSchema: true,
|
|
53953
|
+
description: pulsed ? channel.pulse ? `Turns on for ${channel.pulse.durationMs} ms, then off automatically. Change the duration in the controller configuration.` : "This pulsed channel is missing its duration. Correct and publish its controller configuration." : "Stays on or off until another command or the configured disconnect policy changes it."
|
|
53954
|
+
};
|
|
53955
|
+
if (!pulsed) properties.value = { type: "boolean", title: "Output on", default: false };
|
|
53953
53956
|
properties.expectedConfigurationRevision = {
|
|
53954
53957
|
type: "number",
|
|
53955
53958
|
title: "Configuration revision",
|
|
@@ -54030,8 +54033,13 @@ var WagoCommandHandler = class {
|
|
|
54030
54033
|
if (!channel) return [{ field: "channelId", message: "The selected Logical Channel no longer exists." }];
|
|
54031
54034
|
if (!channel.capabilities.includes("output"))
|
|
54032
54035
|
return [{ field: "channelId", message: "The selected Logical Channel no longer supports output commands." }];
|
|
54033
|
-
if (
|
|
54034
|
-
return [
|
|
54036
|
+
if (!supportsOutputAction(channel, action))
|
|
54037
|
+
return [
|
|
54038
|
+
{
|
|
54039
|
+
field: "action",
|
|
54040
|
+
message: outputBehavior(channel) === "pulsed" ? action === "set" ? "This channel is configured as pulsed. Reopen this node and explicitly select Trigger pulse, or change the channel to switched behavior and publish it." : "This pulsed channel has invalid pulse settings. Correct and publish its controller configuration." : "This channel is configured as switched. Reopen this node and explicitly select Turn on / turn off, or change the channel to pulsed behavior and publish it."
|
|
54041
|
+
}
|
|
54042
|
+
];
|
|
54035
54043
|
return [];
|
|
54036
54044
|
}
|
|
54037
54045
|
async execute(config, commandId = (0, import_node_crypto4.randomUUID)()) {
|
|
@@ -54744,9 +54752,21 @@ var WagoService = class {
|
|
|
54744
54752
|
await this.claimedController(controllerId);
|
|
54745
54753
|
return this.drafts.findOneBy({ controllerId });
|
|
54746
54754
|
}
|
|
54747
|
-
async
|
|
54755
|
+
async getConfigurationBaseline(controllerId) {
|
|
54756
|
+
await this.claimedController(controllerId);
|
|
54757
|
+
const [revision] = await this.revisions.find({
|
|
54758
|
+
where: { controllerId, state: "applied" },
|
|
54759
|
+
order: { revision: "DESC" },
|
|
54760
|
+
take: 1
|
|
54761
|
+
});
|
|
54762
|
+
return revision ?? null;
|
|
54763
|
+
}
|
|
54764
|
+
async saveDraft(controllerId, snapshot, metadata2, principal, expectedDraft) {
|
|
54748
54765
|
return this.withConfigurationLock(controllerId, async () => {
|
|
54749
54766
|
const previous = await this.getDraft(controllerId);
|
|
54767
|
+
if (expectedDraft !== void 0 && (expectedDraft === null ? previous !== null : !previous || expectedDraft.snapshot !== previous.snapshot || expectedDraft.updatedAt !== previous.updatedAt || expectedDraft.presetProvenance !== previous.presetProvenance)) {
|
|
54768
|
+
throw new import_common3.ConflictException("Saved draft changed. Reload it before saving your edits.");
|
|
54769
|
+
}
|
|
54750
54770
|
const validatedMetadata = metadata2 === void 0 ? void 0 : editorMetadata(metadata2);
|
|
54751
54771
|
const previousMetadata = this.metadataFromProvenance(previous?.presetProvenance);
|
|
54752
54772
|
const before = previous ? JSON.parse(previous.snapshot) : null;
|
|
@@ -61908,6 +61928,9 @@ var WagoControllerApi = class {
|
|
|
61908
61928
|
draft(id) {
|
|
61909
61929
|
return this.wago.getDraft(id);
|
|
61910
61930
|
}
|
|
61931
|
+
baseline(id) {
|
|
61932
|
+
return this.wago.getConfigurationBaseline(id);
|
|
61933
|
+
}
|
|
61911
61934
|
presets() {
|
|
61912
61935
|
return this.wago.presets();
|
|
61913
61936
|
}
|
|
@@ -61927,7 +61950,7 @@ var WagoControllerApi = class {
|
|
|
61927
61950
|
);
|
|
61928
61951
|
}
|
|
61929
61952
|
saveDraft(id, body, request) {
|
|
61930
|
-
return this.wago.saveDraft(id, body?.snapshot, body?.metadata, wagoAuditPrincipal(request));
|
|
61953
|
+
return this.wago.saveDraft(id, body?.snapshot, body?.metadata, wagoAuditPrincipal(request), body?.expectedDraft);
|
|
61931
61954
|
}
|
|
61932
61955
|
validateDraft(id, body) {
|
|
61933
61956
|
return this.wago.validateDraft(id, body?.snapshot);
|
|
@@ -62096,6 +62119,10 @@ __decorateClass([
|
|
|
62096
62119
|
(0, import_common9.Get)("controllers/:id/configuration/draft"),
|
|
62097
62120
|
__decorateParam(0, (0, import_common9.Param)("id", import_common9.ParseIntPipe))
|
|
62098
62121
|
], WagoControllerApi.prototype, "draft", 1);
|
|
62122
|
+
__decorateClass([
|
|
62123
|
+
(0, import_common9.Get)("controllers/:id/configuration/baseline"),
|
|
62124
|
+
__decorateParam(0, (0, import_common9.Param)("id", import_common9.ParseIntPipe))
|
|
62125
|
+
], WagoControllerApi.prototype, "baseline", 1);
|
|
62099
62126
|
__decorateClass([
|
|
62100
62127
|
(0, import_common9.Get)("configuration/presets")
|
|
62101
62128
|
], WagoControllerApi.prototype, "presets", 1);
|
|
@@ -62641,8 +62668,7 @@ WagoDiagnosticsController = __decorateClass([
|
|
|
62641
62668
|
|
|
62642
62669
|
// backend/wago-command-node.ts
|
|
62643
62670
|
var WAGO_COMMAND_NODE_TYPE = "plugin.wago.command";
|
|
62644
|
-
function createWagoCommandNode(
|
|
62645
|
-
const service = () => context.get(WagoService);
|
|
62671
|
+
function createWagoCommandNode(service) {
|
|
62646
62672
|
return {
|
|
62647
62673
|
type: WAGO_COMMAND_NODE_TYPE,
|
|
62648
62674
|
label: "WAGO command",
|
|
@@ -62661,10 +62687,559 @@ function createWagoCommandNode(context) {
|
|
|
62661
62687
|
};
|
|
62662
62688
|
}
|
|
62663
62689
|
|
|
62664
|
-
// backend/
|
|
62690
|
+
// backend/wago-flow.service.ts
|
|
62691
|
+
var import_common13 = require("@nestjs/common");
|
|
62692
|
+
|
|
62693
|
+
// backend/wago-state-nodes.ts
|
|
62694
|
+
var WAGO_EVENT_NODE_TYPE = "plugin.wago.event-received";
|
|
62695
|
+
function createWagoStateNodes(service) {
|
|
62696
|
+
return [
|
|
62697
|
+
{
|
|
62698
|
+
type: WAGO_EVENT_NODE_TYPE,
|
|
62699
|
+
label: "WAGO event received",
|
|
62700
|
+
description: "Start a flow when a channel reports state, a measurement, or a fault. Data is available in wago.",
|
|
62701
|
+
isInput: true,
|
|
62702
|
+
inputs: [],
|
|
62703
|
+
outputs: ["output"],
|
|
62704
|
+
resolveConfigSchema: (config) => service().resolveConfigSchema(config, "event"),
|
|
62705
|
+
validateConfig: (config, context) => service().validateConfig(config, "event", context)
|
|
62706
|
+
},
|
|
62707
|
+
{
|
|
62708
|
+
type: "plugin.wago.read-state",
|
|
62709
|
+
label: "WAGO read state",
|
|
62710
|
+
description: "Read the latest channel state or measurement into wago. Unavailable data uses the unavailable output.",
|
|
62711
|
+
inputs: ["input"],
|
|
62712
|
+
outputs: ["output", "unavailable"],
|
|
62713
|
+
resolveConfigSchema: (config) => service().resolveConfigSchema(config, "read"),
|
|
62714
|
+
validateConfig: (config, context) => service().validateConfig(config, "read", context),
|
|
62715
|
+
execute: async (node, input) => {
|
|
62716
|
+
const state = service().read(node.data);
|
|
62717
|
+
const wago = state ? service().payload(state) : { available: false };
|
|
62718
|
+
return { payload: { ...input, wago }, outputHandle: wago.available ? "output" : "unavailable" };
|
|
62719
|
+
}
|
|
62720
|
+
},
|
|
62721
|
+
{
|
|
62722
|
+
type: "plugin.wago.wait-for-state",
|
|
62723
|
+
label: "WAGO wait for state",
|
|
62724
|
+
description: "Wait for an available channel value to match, then continue with the value in wago.",
|
|
62725
|
+
inputs: ["input"],
|
|
62726
|
+
outputs: ["output", "timeout"],
|
|
62727
|
+
resolveConfigSchema: (config) => service().resolveConfigSchema(config, "wait"),
|
|
62728
|
+
validateConfig: (config, context) => service().validateConfig(config, "wait", context),
|
|
62729
|
+
execute: async (node, input) => {
|
|
62730
|
+
const state = await service().wait(node.data);
|
|
62731
|
+
return {
|
|
62732
|
+
payload: { ...input, wago: state ? service().payload(state) : { available: false } },
|
|
62733
|
+
outputHandle: state ? "output" : "timeout"
|
|
62734
|
+
};
|
|
62735
|
+
}
|
|
62736
|
+
}
|
|
62737
|
+
];
|
|
62738
|
+
}
|
|
62739
|
+
|
|
62740
|
+
// backend/wago-flow.service.ts
|
|
62665
62741
|
var PLUGIN_CONTEXT5 = /* @__PURE__ */ Symbol.for("attraccess.plugin.context");
|
|
62742
|
+
var STALE_AFTER_MS2 = 9e4;
|
|
62743
|
+
var MAX_CACHE_ENTRIES = 2e3;
|
|
62744
|
+
var MAX_PENDING_DISPATCHES = 100;
|
|
62745
|
+
var MAX_TIMEOUT_MS = 2147483647;
|
|
62746
|
+
var MAX_RETIRED_STREAMS = 128;
|
|
62747
|
+
var WagoFlowService = class {
|
|
62748
|
+
constructor(context) {
|
|
62749
|
+
__publicField(this, "context", context);
|
|
62750
|
+
__publicField(this, "cache", /* @__PURE__ */ new Map());
|
|
62751
|
+
__publicField(this, "controllerByHardwareId", /* @__PURE__ */ new Map());
|
|
62752
|
+
__publicField(this, "streams", /* @__PURE__ */ new Map());
|
|
62753
|
+
__publicField(this, "offlineControllers", /* @__PURE__ */ new Set());
|
|
62754
|
+
__publicField(this, "unavailableHardware", /* @__PURE__ */ new Set());
|
|
62755
|
+
__publicField(this, "unavailableConfiguration", /* @__PURE__ */ new Set());
|
|
62756
|
+
__publicField(this, "appliedConfigurations", /* @__PURE__ */ new Map());
|
|
62757
|
+
__publicField(this, "channelCache", /* @__PURE__ */ new Map());
|
|
62758
|
+
__publicField(this, "waiters", /* @__PURE__ */ new Set());
|
|
62759
|
+
__publicField(this, "waitersByKey", /* @__PURE__ */ new Map());
|
|
62760
|
+
__publicField(this, "subscriptions", []);
|
|
62761
|
+
__publicField(this, "dispatches", []);
|
|
62762
|
+
__publicField(this, "lastDispatchAtByNode", /* @__PURE__ */ new Map());
|
|
62763
|
+
__publicField(this, "dispatching", false);
|
|
62764
|
+
__publicField(this, "messageQueue", Promise.resolve());
|
|
62765
|
+
__publicField(this, "refreshTimer", null);
|
|
62766
|
+
}
|
|
62767
|
+
// Plugin registration precedes the host datasource; resolve repositories only when used.
|
|
62768
|
+
get controllers() {
|
|
62769
|
+
return this.context.getRepository(WagoController);
|
|
62770
|
+
}
|
|
62771
|
+
get revisions() {
|
|
62772
|
+
return this.context.getRepository(WagoConfigurationRevision);
|
|
62773
|
+
}
|
|
62774
|
+
get settings() {
|
|
62775
|
+
return this.context.getRepository(WagoSettings);
|
|
62776
|
+
}
|
|
62777
|
+
async onModuleInit() {
|
|
62778
|
+
await this.refresh();
|
|
62779
|
+
this.refreshTimer = setInterval(
|
|
62780
|
+
() => void this.refresh().catch(
|
|
62781
|
+
(error) => this.context.logger.warn(`Could not refresh WAGO flow subscriptions: ${String(error)}`)
|
|
62782
|
+
),
|
|
62783
|
+
6e4
|
|
62784
|
+
);
|
|
62785
|
+
}
|
|
62786
|
+
onModuleDestroy() {
|
|
62787
|
+
if (this.refreshTimer) clearInterval(this.refreshTimer);
|
|
62788
|
+
this.subscriptions.splice(0).forEach((subscription) => subscription.unsubscribe());
|
|
62789
|
+
this.waiters.forEach((wake) => wake(void 0, true));
|
|
62790
|
+
this.waiters.clear();
|
|
62791
|
+
this.waitersByKey.clear();
|
|
62792
|
+
}
|
|
62793
|
+
async refresh() {
|
|
62794
|
+
const settings = await this.settings.findOneBy({ id: 1 });
|
|
62795
|
+
if (!settings) return;
|
|
62796
|
+
const controllers = await this.controllers.find({ where: { trustState: "claimed" } });
|
|
62797
|
+
const controllerIds = new Set(controllers.map((controller) => controller.id));
|
|
62798
|
+
for (const id of this.offlineControllers) if (!controllerIds.has(id)) this.offlineControllers.delete(id);
|
|
62799
|
+
for (const id of this.unavailableHardware) if (!controllerIds.has(id)) this.unavailableHardware.delete(id);
|
|
62800
|
+
for (const id of this.unavailableConfiguration)
|
|
62801
|
+
if (!controllerIds.has(id)) this.unavailableConfiguration.delete(id);
|
|
62802
|
+
for (const id of this.appliedConfigurations.keys())
|
|
62803
|
+
if (!controllerIds.has(id)) this.appliedConfigurations.delete(id);
|
|
62804
|
+
for (const id of this.streams.keys()) if (!controllerIds.has(id)) this.streams.delete(id);
|
|
62805
|
+
this.channelCache.clear();
|
|
62806
|
+
const revisions = await this.loadLatestAppliedRevisions(controllers.map((controller) => controller.id));
|
|
62807
|
+
for (const revision of revisions) this.cacheChannels(revision);
|
|
62808
|
+
for (const controller of controllers)
|
|
62809
|
+
if (!this.channelCache.has(controller.id)) this.channelCache.set(controller.id, []);
|
|
62810
|
+
const validChannels = new Map(
|
|
62811
|
+
controllers.map((controller) => [
|
|
62812
|
+
controller.id,
|
|
62813
|
+
new Set((this.channelCache.get(controller.id) ?? []).map((channel) => channel.id))
|
|
62814
|
+
])
|
|
62815
|
+
);
|
|
62816
|
+
for (const [key, state] of this.cache)
|
|
62817
|
+
if (!validChannels.get(state.controllerId)?.has(state.channelId)) this.cache.delete(key);
|
|
62818
|
+
const serverIds = new Set(
|
|
62819
|
+
controllers.map((controller) => controller.mqttServerId ?? settings.defaultMqttServerId).filter(Boolean)
|
|
62820
|
+
);
|
|
62821
|
+
const controllerByHardwareId = new Map(
|
|
62822
|
+
controllers.map(
|
|
62823
|
+
(controller) => [
|
|
62824
|
+
controller.hardwareId,
|
|
62825
|
+
{ controller, serverId: controller.mqttServerId ?? settings.defaultMqttServerId }
|
|
62826
|
+
]
|
|
62827
|
+
).filter(([, entry]) => Boolean(entry.serverId))
|
|
62828
|
+
);
|
|
62829
|
+
const replacements = [];
|
|
62830
|
+
try {
|
|
62831
|
+
for (const serverId of serverIds)
|
|
62832
|
+
replacements.push(
|
|
62833
|
+
await this.context.mqtt.subscribe(
|
|
62834
|
+
serverId,
|
|
62835
|
+
operationalWildcardTopic(settings.operationalPrefix),
|
|
62836
|
+
(message) => this.onMessage(serverId, settings.operationalPrefix, message.topic, message.payload)
|
|
62837
|
+
)
|
|
62838
|
+
);
|
|
62839
|
+
} catch (error) {
|
|
62840
|
+
replacements.forEach((subscription) => subscription.unsubscribe());
|
|
62841
|
+
throw error;
|
|
62842
|
+
}
|
|
62843
|
+
this.subscriptions.splice(0).forEach((subscription) => subscription.unsubscribe());
|
|
62844
|
+
this.subscriptions.push(...replacements);
|
|
62845
|
+
this.controllerByHardwareId = controllerByHardwareId;
|
|
62846
|
+
}
|
|
62847
|
+
async resolveConfigSchema(config, kind, validationContext = /* @__PURE__ */ new Map()) {
|
|
62848
|
+
const controllers = await this.cached(
|
|
62849
|
+
validationContext,
|
|
62850
|
+
"wago-flow-controllers",
|
|
62851
|
+
() => this.controllers.find({ where: { trustState: "claimed" }, order: { name: "ASC" } })
|
|
62852
|
+
);
|
|
62853
|
+
const selected = typeof config.controllerId === "number" ? controllers.find((controller) => controller.id === config.controllerId) : void 0;
|
|
62854
|
+
const revision = selected ? await this.cached(validationContext, `wago-applied-revision:${selected.id}`, async () => {
|
|
62855
|
+
const [latest] = await this.revisions.find({
|
|
62856
|
+
where: { controllerId: selected.id, state: "applied" },
|
|
62857
|
+
order: { revision: "DESC" },
|
|
62858
|
+
take: 1
|
|
62859
|
+
});
|
|
62860
|
+
return latest ?? null;
|
|
62861
|
+
}) : null;
|
|
62862
|
+
const channels = revision ? JSON.parse(revision.snapshot).logicalChannels : [];
|
|
62863
|
+
let names = {};
|
|
62864
|
+
try {
|
|
62865
|
+
names = JSON.parse(revision?.presetProvenance ?? "null")?.editor?.names ?? {};
|
|
62866
|
+
} catch {
|
|
62867
|
+
}
|
|
62868
|
+
const channel = channels.find((item) => item.id === config.channelId);
|
|
62869
|
+
const properties = {
|
|
62870
|
+
controllerId: {
|
|
62871
|
+
type: "number",
|
|
62872
|
+
title: "Controller",
|
|
62873
|
+
refreshesSchema: true,
|
|
62874
|
+
oneOf: controllers.map((controller) => ({
|
|
62875
|
+
const: controller.id,
|
|
62876
|
+
title: controller.name ?? controller.hardwareId
|
|
62877
|
+
})),
|
|
62878
|
+
description: selected && !revision ? "Publish a configuration and wait for the controller to apply it first." : void 0
|
|
62879
|
+
},
|
|
62880
|
+
channelId: {
|
|
62881
|
+
type: "string",
|
|
62882
|
+
title: "Logical Channel",
|
|
62883
|
+
refreshesSchema: true,
|
|
62884
|
+
oneOf: channels.map((channel2) => ({
|
|
62885
|
+
const: channel2.id,
|
|
62886
|
+
title: typeof names[channel2.id] === "string" ? names[channel2.id] : channel2.id
|
|
62887
|
+
}))
|
|
62888
|
+
}
|
|
62889
|
+
};
|
|
62890
|
+
if (kind === "event") {
|
|
62891
|
+
properties.category = {
|
|
62892
|
+
type: "string",
|
|
62893
|
+
title: "Event category",
|
|
62894
|
+
oneOf: this.categories(channel).map((category) => ({ const: category, title: category }))
|
|
62895
|
+
};
|
|
62896
|
+
properties.minimumIntervalMs = { type: "number", title: "Minimum interval (ms)", minimum: 0, default: 0 };
|
|
62897
|
+
if (channel?.capabilities.includes("measurement"))
|
|
62898
|
+
properties.minimumChange = { type: "number", title: "Minimum change (wire units)", minimum: 0 };
|
|
62899
|
+
}
|
|
62900
|
+
if (kind !== "event")
|
|
62901
|
+
properties.category = {
|
|
62902
|
+
type: "string",
|
|
62903
|
+
title: "State category",
|
|
62904
|
+
refreshesSchema: true,
|
|
62905
|
+
oneOf: this.readCategories(channel).map((category) => ({ const: category, title: category }))
|
|
62906
|
+
};
|
|
62907
|
+
if (kind === "wait") {
|
|
62908
|
+
properties.equals = {
|
|
62909
|
+
type: config.category === "measurement" ? "number" : "boolean",
|
|
62910
|
+
title: config.category === "measurement" ? "Equals (wire value)" : "Equals"
|
|
62911
|
+
};
|
|
62912
|
+
properties.timeoutMs = {
|
|
62913
|
+
type: "number",
|
|
62914
|
+
title: "Timeout (ms)",
|
|
62915
|
+
minimum: 1,
|
|
62916
|
+
maximum: MAX_TIMEOUT_MS,
|
|
62917
|
+
default: 3e4
|
|
62918
|
+
};
|
|
62919
|
+
}
|
|
62920
|
+
return {
|
|
62921
|
+
dynamic: true,
|
|
62922
|
+
type: "object",
|
|
62923
|
+
properties,
|
|
62924
|
+
required: ["controllerId", "channelId", "category", ...kind === "wait" ? ["equals", "timeoutMs"] : []]
|
|
62925
|
+
};
|
|
62926
|
+
}
|
|
62927
|
+
cached(context, key, load) {
|
|
62928
|
+
if (!context.has(key)) context.set(key, load());
|
|
62929
|
+
return context.get(key);
|
|
62930
|
+
}
|
|
62931
|
+
async validateConfig(config, kind, context = /* @__PURE__ */ new Map()) {
|
|
62932
|
+
const schema = await this.resolveConfigSchema(config, kind, context);
|
|
62933
|
+
const properties = schema.properties;
|
|
62934
|
+
const errors = [];
|
|
62935
|
+
for (const [field2, message] of [
|
|
62936
|
+
["controllerId", "Select a claimed WAGO controller."],
|
|
62937
|
+
["channelId", "Select a channel from the applied controller configuration."],
|
|
62938
|
+
["category", "Select a category supported by this channel."]
|
|
62939
|
+
]) {
|
|
62940
|
+
if (!properties[field2]?.oneOf?.some((choice) => choice.const === config[field2])) errors.push({ field: field2, message });
|
|
62941
|
+
}
|
|
62942
|
+
const numericFields = kind === "event" ? ["minimumIntervalMs", "minimumChange"] : kind === "wait" ? ["timeoutMs"] : [];
|
|
62943
|
+
for (const field2 of numericFields) {
|
|
62944
|
+
const value = config[field2];
|
|
62945
|
+
if (value !== void 0 && (typeof value !== "number" || !Number.isFinite(value) || value < (field2 === "timeoutMs" ? 1 : 0) || field2 === "timeoutMs" && value > MAX_TIMEOUT_MS))
|
|
62946
|
+
errors.push({
|
|
62947
|
+
field: field2,
|
|
62948
|
+
message: field2 === "timeoutMs" ? "Enter a valid positive timeout." : "Enter a non-negative number."
|
|
62949
|
+
});
|
|
62950
|
+
}
|
|
62951
|
+
if (kind === "wait" && (config.category === "measurement" ? typeof config.equals !== "number" || !Number.isFinite(config.equals) : typeof config.equals !== "boolean"))
|
|
62952
|
+
errors.push({ field: "equals", message: "Enter a matching value for the selected state category." });
|
|
62953
|
+
return errors;
|
|
62954
|
+
}
|
|
62955
|
+
read(config) {
|
|
62956
|
+
if (typeof config.controllerId !== "number" || typeof config.channelId !== "string") return null;
|
|
62957
|
+
if (typeof config.category === "string")
|
|
62958
|
+
return this.cache.get(this.cacheKey(config.controllerId, config.channelId, config.category)) ?? null;
|
|
62959
|
+
const entries = [...this.cache.values()].filter(
|
|
62960
|
+
(state) => state.controllerId === config.controllerId && state.channelId === config.channelId && (!config.category || state.category === config.category)
|
|
62961
|
+
);
|
|
62962
|
+
return entries.sort((left, right) => right.receivedAt - left.receivedAt)[0] ?? null;
|
|
62963
|
+
}
|
|
62964
|
+
async wait(config) {
|
|
62965
|
+
if (typeof config.controllerId !== "number" || typeof config.channelId !== "string" || typeof config.category !== "string")
|
|
62966
|
+
return null;
|
|
62967
|
+
const current = this.read(config);
|
|
62968
|
+
if (current && this.matchesCondition(current, config)) return current;
|
|
62969
|
+
const timeoutMs = typeof config.timeoutMs === "number" && Number.isFinite(config.timeoutMs) && config.timeoutMs > 0 ? Math.min(config.timeoutMs, MAX_TIMEOUT_MS) : 3e4;
|
|
62970
|
+
const waiterKey = this.cacheKey(config.controllerId, config.channelId, config.category);
|
|
62971
|
+
return new Promise((resolve2) => {
|
|
62972
|
+
const cleanup = () => {
|
|
62973
|
+
clearTimeout(timer);
|
|
62974
|
+
this.waiters.delete(wake);
|
|
62975
|
+
const waiters2 = this.waitersByKey.get(waiterKey);
|
|
62976
|
+
waiters2?.delete(wake);
|
|
62977
|
+
if (!waiters2?.size) this.waitersByKey.delete(waiterKey);
|
|
62978
|
+
};
|
|
62979
|
+
const wake = (state, cancel = false) => {
|
|
62980
|
+
if (!cancel && (!state || !this.matchesCondition(state, config))) return;
|
|
62981
|
+
cleanup();
|
|
62982
|
+
resolve2(cancel ? null : state);
|
|
62983
|
+
};
|
|
62984
|
+
const timer = setTimeout(() => {
|
|
62985
|
+
cleanup();
|
|
62986
|
+
resolve2(null);
|
|
62987
|
+
}, timeoutMs);
|
|
62988
|
+
this.waiters.add(wake);
|
|
62989
|
+
const waiters = this.waitersByKey.get(waiterKey) ?? /* @__PURE__ */ new Set();
|
|
62990
|
+
waiters.add(wake);
|
|
62991
|
+
this.waitersByKey.set(waiterKey, waiters);
|
|
62992
|
+
});
|
|
62993
|
+
}
|
|
62994
|
+
onMessage(serverId, prefix, topic, payload) {
|
|
62995
|
+
const queued = this.messageQueue.catch(() => void 0).then(() => this.processMessage(serverId, prefix, topic, payload));
|
|
62996
|
+
this.messageQueue = queued;
|
|
62997
|
+
return queued;
|
|
62998
|
+
}
|
|
62999
|
+
async processMessage(serverId, prefix, topic, payload) {
|
|
63000
|
+
let parsed;
|
|
63001
|
+
try {
|
|
63002
|
+
parsed = parseOperationalMessage(prefix, topic, payload);
|
|
63003
|
+
} catch (error) {
|
|
63004
|
+
this.context.logger.warn(`Ignoring invalid WAGO event: ${String(error)}`);
|
|
63005
|
+
return;
|
|
63006
|
+
}
|
|
63007
|
+
if (!parsed) return;
|
|
63008
|
+
const { hardwareId, message: event } = parsed;
|
|
63009
|
+
const entry = this.controllerByHardwareId.get(hardwareId);
|
|
63010
|
+
if (!entry || entry.serverId !== serverId) return;
|
|
63011
|
+
const { controller } = entry;
|
|
63012
|
+
const eventTime = Date.parse(event.timestamp);
|
|
63013
|
+
if (eventTime > Date.now()) {
|
|
63014
|
+
this.context.logger.warn(`Ignoring future-dated WAGO event for ${controller.hardwareId}`);
|
|
63015
|
+
return;
|
|
63016
|
+
}
|
|
63017
|
+
const channels = await this.channels(controller.id);
|
|
63018
|
+
let stream = this.streams.get(controller.id);
|
|
63019
|
+
if (stream?.exhausted) return;
|
|
63020
|
+
if (!stream || stream.active !== event.streamId) {
|
|
63021
|
+
if (event.category !== "state" || stream?.retired.has(event.streamId) || stream && (!event.connected || Date.now() - eventTime > STALE_AFTER_MS2 || eventTime <= stream.latestSourceTime)) {
|
|
63022
|
+
this.context.logger.warn(`Ignoring unestablished or retired WAGO stream for ${controller.hardwareId}`);
|
|
63023
|
+
return;
|
|
63024
|
+
}
|
|
63025
|
+
if (stream && stream.retired.size >= MAX_RETIRED_STREAMS) {
|
|
63026
|
+
stream.exhausted = true;
|
|
63027
|
+
for (const state of this.cache.values()) if (state.controllerId === controller.id) state.invalidated = true;
|
|
63028
|
+
this.context.logger.warn(
|
|
63029
|
+
`WAGO stream history exhausted for ${controller.hardwareId}; refusing further samples`
|
|
63030
|
+
);
|
|
63031
|
+
return;
|
|
63032
|
+
}
|
|
63033
|
+
const retired = stream?.retired ?? /* @__PURE__ */ new Set();
|
|
63034
|
+
if (stream) retired.add(stream.active);
|
|
63035
|
+
stream = {
|
|
63036
|
+
active: event.streamId,
|
|
63037
|
+
latestSourceTime: eventTime,
|
|
63038
|
+
sampleNotBefore: eventTime,
|
|
63039
|
+
retired,
|
|
63040
|
+
sequences: /* @__PURE__ */ new Map()
|
|
63041
|
+
};
|
|
63042
|
+
this.streams.set(controller.id, stream);
|
|
63043
|
+
for (const state of this.cache.values()) if (state.controllerId === controller.id) state.invalidated = true;
|
|
63044
|
+
}
|
|
63045
|
+
const previous = stream.sequences.get(event.category);
|
|
63046
|
+
if (previous !== void 0 && event.sequence <= previous) {
|
|
63047
|
+
this.context.logger.warn(`Ignoring duplicate or out-of-order WAGO event for ${controller.hardwareId}`);
|
|
63048
|
+
return;
|
|
63049
|
+
}
|
|
63050
|
+
if (previous !== void 0 && event.sequence > previous + 1)
|
|
63051
|
+
this.context.logger.warn(
|
|
63052
|
+
`WAGO event sequence gap for ${controller.hardwareId}: ${previous} to ${event.sequence}`
|
|
63053
|
+
);
|
|
63054
|
+
stream.sequences.set(event.category, event.sequence);
|
|
63055
|
+
stream.latestSourceTime = Math.max(stream.latestSourceTime, eventTime);
|
|
63056
|
+
if (event.category === "state") {
|
|
63057
|
+
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;
|
|
63058
|
+
stream.stateTimestamp = eventTime;
|
|
63059
|
+
if (event.connected) this.offlineControllers.delete(controller.id);
|
|
63060
|
+
else this.offlineControllers.add(controller.id);
|
|
63061
|
+
if (event.readiness?.hardwareAvailable === false) this.unavailableHardware.add(controller.id);
|
|
63062
|
+
else if (event.readiness?.hardwareAvailable === true) this.unavailableHardware.delete(controller.id);
|
|
63063
|
+
const applied = this.appliedConfigurations.get(controller.id);
|
|
63064
|
+
if (applied && event.revision === applied.revision && event.contentHash === applied.contentHash)
|
|
63065
|
+
this.unavailableConfiguration.delete(controller.id);
|
|
63066
|
+
else this.unavailableConfiguration.add(controller.id);
|
|
63067
|
+
if (wasUnavailable || !event.connected || this.unavailableHardware.has(controller.id) || this.unavailableConfiguration.has(controller.id))
|
|
63068
|
+
stream.sampleNotBefore = Math.max(stream.sampleNotBefore, eventTime);
|
|
63069
|
+
for (const state of this.cache.values()) {
|
|
63070
|
+
if (state.controllerId !== controller.id) continue;
|
|
63071
|
+
if (wasUnavailable || !event.connected || this.unavailableHardware.has(controller.id) || this.unavailableConfiguration.has(controller.id) || state.category === "state")
|
|
63072
|
+
state.invalidated = true;
|
|
63073
|
+
}
|
|
63074
|
+
for (const channel of channels) {
|
|
63075
|
+
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;
|
|
63076
|
+
if (typeof value === "boolean") this.store(controller, channel.id, event, value);
|
|
63077
|
+
}
|
|
63078
|
+
} else if ("channelId" in event) {
|
|
63079
|
+
const channel = channels.find((channel2) => channel2.id === event.channelId);
|
|
63080
|
+
if (!channel || event.category === "measurement" && !channel.capabilities.includes("measurement")) return;
|
|
63081
|
+
this.store(controller, event.channelId, event, event.category === "measurement" ? event.value : event);
|
|
63082
|
+
}
|
|
63083
|
+
}
|
|
63084
|
+
store(controller, channelId, event, value) {
|
|
63085
|
+
const state = {
|
|
63086
|
+
controllerId: controller.id,
|
|
63087
|
+
hardwareId: controller.hardwareId,
|
|
63088
|
+
channelId,
|
|
63089
|
+
category: event.category,
|
|
63090
|
+
value,
|
|
63091
|
+
timestamp: event.timestamp,
|
|
63092
|
+
sequence: event.sequence,
|
|
63093
|
+
streamId: event.streamId,
|
|
63094
|
+
...event.category === "measurement" ? { unit: event.unit, kind: event.kind } : {},
|
|
63095
|
+
...event.category === "state" ? { revision: event.revision, contentHash: event.contentHash } : {},
|
|
63096
|
+
receivedAt: Date.now(),
|
|
63097
|
+
offline: this.offlineControllers.has(controller.id),
|
|
63098
|
+
invalidated: this.unavailableHardware.has(controller.id) || this.unavailableConfiguration.has(controller.id) || Date.parse(event.timestamp) < (this.streams.get(controller.id)?.sampleNotBefore ?? 0)
|
|
63099
|
+
};
|
|
63100
|
+
const cacheKey = this.cacheKey(controller.id, channelId, event.category);
|
|
63101
|
+
const previous = this.cache.get(cacheKey);
|
|
63102
|
+
this.cache.set(cacheKey, state);
|
|
63103
|
+
if (this.cache.size > MAX_CACHE_ENTRIES) {
|
|
63104
|
+
const oldest = this.cache.keys().next().value;
|
|
63105
|
+
if (oldest) this.cache.delete(oldest);
|
|
63106
|
+
}
|
|
63107
|
+
this.waitersByKey.get(cacheKey)?.forEach((wake) => wake(state));
|
|
63108
|
+
if (this.dispatches.length >= MAX_PENDING_DISPATCHES) {
|
|
63109
|
+
this.context.logger.warn(`Dropping excess WAGO flow dispatch for ${controller.hardwareId}`);
|
|
63110
|
+
return;
|
|
63111
|
+
}
|
|
63112
|
+
this.dispatches.push({ state, previous });
|
|
63113
|
+
if (!this.dispatching) void this.dispatch();
|
|
63114
|
+
}
|
|
63115
|
+
async channels(controllerId) {
|
|
63116
|
+
const cached = this.channelCache.get(controllerId);
|
|
63117
|
+
if (cached) return cached;
|
|
63118
|
+
const [revision] = await this.revisions.find({
|
|
63119
|
+
where: { controllerId, state: "applied" },
|
|
63120
|
+
order: { revision: "DESC" },
|
|
63121
|
+
take: 1
|
|
63122
|
+
});
|
|
63123
|
+
if (!revision) {
|
|
63124
|
+
this.channelCache.set(controllerId, []);
|
|
63125
|
+
return [];
|
|
63126
|
+
}
|
|
63127
|
+
return this.cacheChannels(revision);
|
|
63128
|
+
}
|
|
63129
|
+
async loadLatestAppliedRevisions(controllerIds) {
|
|
63130
|
+
if (!controllerIds.length) return [];
|
|
63131
|
+
return this.revisions.createQueryBuilder("revision").innerJoin(
|
|
63132
|
+
(query) => query.subQuery().select("latest.controllerId", "controllerId").addSelect("MAX(latest.revision)", "revision").from(WagoConfigurationRevision, "latest").where("latest.controllerId IN (:...controllerIds)", { controllerIds }).andWhere("latest.state = :state", { state: "applied" }).groupBy("latest.controllerId"),
|
|
63133
|
+
"latest",
|
|
63134
|
+
"latest.controllerId = revision.controllerId AND latest.revision = revision.revision"
|
|
63135
|
+
).where("revision.state = :state", { state: "applied" }).getMany();
|
|
63136
|
+
}
|
|
63137
|
+
cacheChannels(revision) {
|
|
63138
|
+
try {
|
|
63139
|
+
const channels = JSON.parse(revision.snapshot).logicalChannels;
|
|
63140
|
+
const previous = this.appliedConfigurations.get(revision.controllerId);
|
|
63141
|
+
if (previous?.revision !== revision.revision || previous?.contentHash !== revision.contentHash) {
|
|
63142
|
+
this.unavailableConfiguration.add(revision.controllerId);
|
|
63143
|
+
for (const state of this.cache.values())
|
|
63144
|
+
if (state.controllerId === revision.controllerId) state.invalidated = true;
|
|
63145
|
+
}
|
|
63146
|
+
this.appliedConfigurations.set(revision.controllerId, {
|
|
63147
|
+
revision: revision.revision,
|
|
63148
|
+
contentHash: revision.contentHash
|
|
63149
|
+
});
|
|
63150
|
+
this.channelCache.set(revision.controllerId, channels);
|
|
63151
|
+
return channels;
|
|
63152
|
+
} catch {
|
|
63153
|
+
this.channelCache.set(revision.controllerId, []);
|
|
63154
|
+
return [];
|
|
63155
|
+
}
|
|
63156
|
+
}
|
|
63157
|
+
cacheKey(controllerId, channelId, category) {
|
|
63158
|
+
return `${controllerId}:${channelId}:${category}`;
|
|
63159
|
+
}
|
|
63160
|
+
categories(channel) {
|
|
63161
|
+
return [
|
|
63162
|
+
...channel?.capabilities.some((capability) => capability === "input" || capability === "output") ? ["state"] : [],
|
|
63163
|
+
...channel?.capabilities.includes("measurement") ? ["measurement"] : [],
|
|
63164
|
+
"fault"
|
|
63165
|
+
];
|
|
63166
|
+
}
|
|
63167
|
+
readCategories(channel) {
|
|
63168
|
+
return [
|
|
63169
|
+
...channel?.capabilities.some((capability) => capability === "input" || capability === "output") ? ["state"] : [],
|
|
63170
|
+
...channel?.capabilities.includes("measurement") ? ["measurement"] : []
|
|
63171
|
+
];
|
|
63172
|
+
}
|
|
63173
|
+
matchesEvent(config, nodeId, state, previous) {
|
|
63174
|
+
if (config.controllerId !== state.controllerId || config.channelId !== state.channelId || config.category !== state.category)
|
|
63175
|
+
return false;
|
|
63176
|
+
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)
|
|
63177
|
+
return false;
|
|
63178
|
+
if (typeof config.minimumIntervalMs === "number") {
|
|
63179
|
+
const lastDispatchAt = this.lastDispatchAtByNode.get(nodeId);
|
|
63180
|
+
if (lastDispatchAt !== void 0 && state.receivedAt - lastDispatchAt < config.minimumIntervalMs) return false;
|
|
63181
|
+
this.lastDispatchAtByNode.set(nodeId, state.receivedAt);
|
|
63182
|
+
}
|
|
63183
|
+
return true;
|
|
63184
|
+
}
|
|
63185
|
+
matchesCondition(state, config) {
|
|
63186
|
+
return this.freshness(state).available && (config.equals === void 0 || state.value === config.equals);
|
|
63187
|
+
}
|
|
63188
|
+
freshness(state) {
|
|
63189
|
+
const age = Date.now() - Date.parse(state.timestamp);
|
|
63190
|
+
const stale = !Number.isFinite(age) || age < 0 || age > STALE_AFTER_MS2;
|
|
63191
|
+
const offline = state.offline === true || this.offlineControllers.has(state.controllerId);
|
|
63192
|
+
const stream = this.streams.get(state.controllerId);
|
|
63193
|
+
const stateTimestamp = stream?.stateTimestamp;
|
|
63194
|
+
const connectionStale = stateTimestamp === void 0 || Date.now() - stateTimestamp > STALE_AFTER_MS2;
|
|
63195
|
+
return {
|
|
63196
|
+
stale,
|
|
63197
|
+
offline,
|
|
63198
|
+
connectionStale,
|
|
63199
|
+
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)
|
|
63200
|
+
};
|
|
63201
|
+
}
|
|
63202
|
+
payload(state) {
|
|
63203
|
+
const payload = { ...state };
|
|
63204
|
+
delete payload.invalidated;
|
|
63205
|
+
return { ...payload, ...this.freshness(state) };
|
|
63206
|
+
}
|
|
63207
|
+
async dispatch() {
|
|
63208
|
+
this.dispatching = true;
|
|
63209
|
+
while (this.dispatches.length) {
|
|
63210
|
+
const dispatch = this.dispatches.shift();
|
|
63211
|
+
if (!dispatch) continue;
|
|
63212
|
+
const { state, previous } = dispatch;
|
|
63213
|
+
const stream = this.streams.get(state.controllerId);
|
|
63214
|
+
if (stream?.active !== state.streamId || stream.exhausted) continue;
|
|
63215
|
+
try {
|
|
63216
|
+
await this.context.flows.trigger(
|
|
63217
|
+
WAGO_EVENT_NODE_TYPE,
|
|
63218
|
+
(config, nodeId) => this.matchesEvent(config, nodeId, state, previous),
|
|
63219
|
+
{ wago: this.payload(state) }
|
|
63220
|
+
);
|
|
63221
|
+
} catch (error) {
|
|
63222
|
+
this.context.logger.warn(`Could not trigger WAGO flows: ${String(error)}`);
|
|
63223
|
+
}
|
|
63224
|
+
}
|
|
63225
|
+
this.dispatching = false;
|
|
63226
|
+
}
|
|
63227
|
+
};
|
|
63228
|
+
WagoFlowService = __decorateClass([
|
|
63229
|
+
(0, import_common13.Injectable)(),
|
|
63230
|
+
__decorateParam(0, (0, import_common13.Inject)(PLUGIN_CONTEXT5))
|
|
63231
|
+
], WagoFlowService);
|
|
63232
|
+
|
|
63233
|
+
// backend/plugin.ts
|
|
63234
|
+
var PLUGIN_CONTEXT6 = /* @__PURE__ */ Symbol.for("attraccess.plugin.context");
|
|
62666
63235
|
var WagoPluginModule = class {
|
|
62667
63236
|
};
|
|
63237
|
+
var flowServices = /* @__PURE__ */ new WeakMap();
|
|
63238
|
+
function services(context) {
|
|
63239
|
+
const result = flowServices.get(context);
|
|
63240
|
+
if (!result) throw new Error("WAGO flow services are not initialized.");
|
|
63241
|
+
return result;
|
|
63242
|
+
}
|
|
62668
63243
|
var plugin = {
|
|
62669
63244
|
entities: [
|
|
62670
63245
|
WagoController,
|
|
@@ -62677,14 +63252,31 @@ var plugin = {
|
|
|
62677
63252
|
WagoManagementEntity,
|
|
62678
63253
|
WagoCommissioningLeaseEntity
|
|
62679
63254
|
],
|
|
62680
|
-
flowNodes: (context) => [
|
|
63255
|
+
flowNodes: (context) => [
|
|
63256
|
+
createWagoCommandNode(() => services(context).command),
|
|
63257
|
+
...createWagoStateNodes(() => services(context).state)
|
|
63258
|
+
],
|
|
62681
63259
|
register(context) {
|
|
62682
63260
|
return {
|
|
62683
63261
|
module: WagoPluginModule,
|
|
62684
63262
|
controllers: [WagoControllerApi, WagoArtifactsController, WagoDiagnosticsController],
|
|
62685
63263
|
providers: [
|
|
62686
|
-
{ provide:
|
|
63264
|
+
{ provide: PLUGIN_CONTEXT6, useValue: context },
|
|
62687
63265
|
WagoService,
|
|
63266
|
+
WagoFlowService,
|
|
63267
|
+
{
|
|
63268
|
+
provide: "wago-flow-services",
|
|
63269
|
+
inject: [WagoService, WagoFlowService],
|
|
63270
|
+
useFactory: (command, state) => {
|
|
63271
|
+
const bound = { command, state };
|
|
63272
|
+
flowServices.set(context, bound);
|
|
63273
|
+
return {
|
|
63274
|
+
onModuleDestroy: () => {
|
|
63275
|
+
if (flowServices.get(context) === bound) flowServices.delete(context);
|
|
63276
|
+
}
|
|
63277
|
+
};
|
|
63278
|
+
}
|
|
63279
|
+
},
|
|
62688
63280
|
WagoRuntimeArtifactsService,
|
|
62689
63281
|
WagoArtifactUploadInterceptor,
|
|
62690
63282
|
WagoCommissioningReadiness,
|