@yarkivaev/scada 1.4.0 → 2.3.45
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/README.md +196 -33
- package/db/migrations/C0001__central_drop_metrics.sql +1 -0
- package/db/migrations/C0002__central_revoke_delete.sql +12 -0
- package/db/migrations/C0003__central_operators.sql +7 -0
- package/db/migrations/C0004__central_operations_grants.sql +2 -0
- package/db/migrations/C0005__central_operators_grants.sql +1 -0
- package/db/migrations/C0006__central_operators_registration.sql +16 -0
- package/db/migrations/E0001__edge_metrics.sql +8 -0
- package/db/migrations/E0002__edge_retention_delete.sql +8 -0
- package/db/migrations/E0003__edge_operations_grants.sql +2 -0
- package/db/migrations/V0001__baseline.sql +25 -0
- package/db/migrations/V0002__sink_extension_tables.sql +6 -0
- package/db/migrations/V0003__operations.sql +10 -0
- package/db/migrations/V0004__ingest_checkpoints.sql +6 -0
- package/db/migrations/V0005__user_decisions_operator.sql +2 -0
- package/index.js +75 -45
- package/package.json +28 -8
- package/src/application/bindSilentStreams.js +20 -0
- package/src/application/edgeOperatorCatalog.js +45 -0
- package/src/application/export/exportJob.js +118 -0
- package/src/application/export/exportQuery.js +32 -0
- package/src/application/export/exportSink.js +25 -0
- package/src/application/export/exportStream.js +33 -0
- package/src/application/machineInPlant.js +20 -0
- package/src/application/metricsPlant.js +35 -0
- package/src/application/plantApi.js +49 -0
- package/src/application/plantOperations.js +19 -0
- package/src/application/plantServer.js +107 -0
- package/src/application/shopWithTimeline.js +112 -0
- package/src/application/siteOperatorCatalog.js +78 -0
- package/src/application/siteServer.js +191 -0
- package/src/application/supervisorSink.js +97 -0
- package/src/application/timelineOperatorFromEnv.js +19 -0
- package/src/bin/supervisor-sink.js +28 -0
- package/src/{alert.js → domain/alerting/alert.js} +2 -2
- package/src/{alerts.js → domain/alerting/alerts.js} +3 -3
- package/src/domain/alerting/ingest.js +18 -0
- package/src/domain/ingest/ingestCursor.js +26 -0
- package/src/domain/operation/operation.js +23 -0
- package/src/domain/operation/operations.js +81 -0
- package/src/domain/operator/operator.js +23 -0
- package/src/domain/plant/machine.js +32 -0
- package/src/domain/plant/plant.js +20 -0
- package/src/domain/plant/shop.js +24 -0
- package/src/domain/segment/dispatch.js +31 -0
- package/src/domain/segment/normalize.js +31 -0
- package/src/domain/segment/silenceBudget.js +16 -0
- package/src/{initialized.js → domain/shared/initialized.js} +3 -4
- package/src/{pubsub.js → domain/shared/pubsub.js} +2 -3
- package/src/domain/timeline/timeline.js +25 -0
- package/src/infrastructure/catalog/tag-catalog.json +27 -0
- package/src/infrastructure/catalog/tagCatalog.js +65 -0
- package/src/infrastructure/client/index.js +20 -0
- package/src/infrastructure/client/machineClient.js +159 -0
- package/src/infrastructure/client/machineOperationsClient.js +159 -0
- package/src/infrastructure/client/scadaClient.js +67 -0
- package/src/infrastructure/client/sseConnection.js +42 -0
- package/src/infrastructure/http/edge/edgeApi.js +54 -0
- package/src/infrastructure/http/edge/httpMetricsRead.js +34 -0
- package/src/infrastructure/http/edge/metricsSensor.js +15 -0
- package/src/infrastructure/http/edge/parseStateHttpTimeoutMs.js +20 -0
- package/src/infrastructure/http/edge/routes/checkpoint/checkpointRoutes.js +79 -0
- package/src/infrastructure/http/edge/routes/metrics/metricsBatch.js +59 -0
- package/src/infrastructure/http/edge/routes/metrics/metricsCurrent.js +29 -0
- package/src/infrastructure/http/edge/routes/metrics/metricsPoll.js +25 -0
- package/src/infrastructure/http/edge/routes/metrics/metricsRange.js +26 -0
- package/src/infrastructure/http/edge/routes/metrics/metricsRoutes.js +21 -0
- package/src/infrastructure/http/edge/routes/retention/retentionRoutes.js +41 -0
- package/src/infrastructure/http/edge/startTestEdgeApi.js +39 -0
- package/src/infrastructure/http/edge/stateAccess.js +13 -0
- package/src/infrastructure/http/edge/stateHttpClient.js +106 -0
- package/src/infrastructure/http/edge/stateHttpTimeoutError.js +13 -0
- package/src/infrastructure/http/plant/json/decisionJson.js +44 -0
- package/src/infrastructure/http/plant/json/operationJson.js +28 -0
- package/src/infrastructure/http/plant/json/operatorJson.js +22 -0
- package/src/infrastructure/http/plant/json/segmentJson.js +27 -0
- package/src/infrastructure/http/plant/operationAudit.js +56 -0
- package/src/infrastructure/http/plant/routes/alertRoute.js +93 -0
- package/src/infrastructure/http/plant/routes/catalogRoute.js +21 -0
- package/src/infrastructure/http/plant/routes/decisionRoute.js +60 -0
- package/src/infrastructure/http/plant/routes/machineRoute.js +35 -0
- package/src/infrastructure/http/plant/routes/measurementRoute.js +60 -0
- package/src/infrastructure/http/plant/routes/operationDrafts.js +79 -0
- package/src/infrastructure/http/plant/routes/operationRoute.js +80 -0
- package/src/infrastructure/http/plant/routes/operationWrites.js +186 -0
- package/src/infrastructure/http/plant/routes/operatorRoute.js +143 -0
- package/src/infrastructure/http/plant/routes/simulationRoute.js +61 -0
- package/src/infrastructure/http/plant/routes/timelineRoute.js +112 -0
- package/src/infrastructure/http/plant/streams/alertStream.js +68 -0
- package/src/infrastructure/http/plant/streams/heartbeatStream.js +34 -0
- package/src/infrastructure/http/plant/streams/measurementStream.js +90 -0
- package/src/infrastructure/http/plant/streams/operationStream.js +42 -0
- package/src/infrastructure/http/plant/streams/timelineStream.js +97 -0
- package/src/infrastructure/http/plant/timelineOperator.js +87 -0
- package/src/infrastructure/ingest/activity/activityTracking.js +74 -0
- package/src/infrastructure/ingest/activity/activityTransformer.js +45 -0
- package/src/infrastructure/ingest/codecs/alertCodec.js +56 -0
- package/src/infrastructure/ingest/codecs/segmentCodec.js +30 -0
- package/src/infrastructure/ingest/codecs/userDecisionCodec.js +78 -0
- package/src/infrastructure/ingest/cooldown.js +24 -0
- package/src/infrastructure/ingest/db/migrate.js +78 -0
- package/src/infrastructure/ingest/db/migrationProfile.js +35 -0
- package/src/infrastructure/ingest/db/runRetention.js +58 -0
- package/src/infrastructure/ingest/ingestCheckpoint.js +71 -0
- package/src/infrastructure/ingest/modbus/mx210Tcp.js +81 -0
- package/src/infrastructure/ingest/modbus/silentStreams.js +152 -0
- package/src/infrastructure/ingest/mqtt/metricsTransformer.js +54 -0
- package/src/infrastructure/ingest/mqtt/modbusDeviceSpec.js +112 -0
- package/src/infrastructure/ingest/mqtt/modbusMqtt.js +204 -0
- package/src/infrastructure/ingest/mqtt/mqttMetrics.js +78 -0
- package/src/infrastructure/ingest/parseRequestTimeoutMs.js +20 -0
- package/src/infrastructure/ingest/pipelines/alertPipeline.js +48 -0
- package/src/infrastructure/ingest/pipelines/decisionPipeline.js +45 -0
- package/src/infrastructure/ingest/pipelines/segmentPipeline.js +60 -0
- package/src/infrastructure/ingest/processingErrorLog.js +25 -0
- package/src/infrastructure/ingest/silentOpenWatch.js +50 -0
- package/src/infrastructure/ingest/sinks/alertSink.js +43 -0
- package/src/infrastructure/ingest/sinks/closeOrphanOpen.js +32 -0
- package/src/infrastructure/ingest/sinks/closeSilentOpen.js +39 -0
- package/src/infrastructure/ingest/sinks/retagSink.js +43 -0
- package/src/infrastructure/ingest/sinks/userDecisionSink.js +41 -0
- package/src/infrastructure/ingest/telemetry/amqpMetricsIngest.js +94 -0
- package/src/infrastructure/ingest/telemetry/amqpMqttRelay.js +71 -0
- package/src/infrastructure/ingest/telemetry/deliverToMqttRecord.js +19 -0
- package/src/infrastructure/ingest/telemetry/streamNameFromTopic.js +21 -0
- package/src/infrastructure/messaging/ownership/httpOperations.js +96 -0
- package/src/infrastructure/messaging/ownership/httpTimeline.js +99 -0
- package/src/infrastructure/messaging/ownership/machineOwners.js +39 -0
- package/src/infrastructure/messaging/ownership/ownerTimeline.js +28 -0
- package/src/infrastructure/messaging/stomp/alerts/stompAlerts.js +21 -0
- package/src/infrastructure/messaging/stomp/alerts/stompAlertsCollection.js +51 -0
- package/src/infrastructure/messaging/stomp/alerts/stompAlertsInit.js +24 -0
- package/src/infrastructure/messaging/stomp/alerts/stompAlertsLogic.js +51 -0
- package/src/infrastructure/messaging/stomp/stompTimelineSegments.js +80 -0
- package/src/infrastructure/messaging/stomp/timeline.js +21 -0
- package/src/infrastructure/messaging/stomp/userDecisionBody.js +25 -0
- package/src/infrastructure/messaging/stomp/userDecisions.js +42 -0
- package/src/infrastructure/operators/centralOperators.js +64 -0
- package/src/infrastructure/operators/edgeOperators.js +39 -0
- package/src/infrastructure/operators/operatorById.js +33 -0
- package/src/infrastructure/operators/operatorExtras.js +41 -0
- package/src/infrastructure/operators/operators.js +32 -0
- package/src/infrastructure/operators/operatorsFromSeed.js +18 -0
- package/src/infrastructure/operators/operatorsSync.js +57 -0
- package/src/infrastructure/persistence/clickhouse/connection.js +58 -0
- package/src/infrastructure/persistence/clickhouse/pollTopicCursors.js +48 -0
- package/src/{clickhouseSensor.js → infrastructure/persistence/clickhouse/sensor.js} +9 -27
- package/src/infrastructure/persistence/clickhouse/streamHub.js +165 -0
- package/src/infrastructure/persistence/memory/alerts.js +21 -0
- package/src/infrastructure/persistence/memory/checkpoints.js +98 -0
- package/src/infrastructure/persistence/memory/metrics.js +69 -0
- package/src/infrastructure/persistence/memory/metricsDisabled.js +19 -0
- package/src/infrastructure/persistence/memory/operations.js +87 -0
- package/src/infrastructure/persistence/memory/segments.js +83 -0
- package/src/infrastructure/persistence/memory/timeline.js +56 -0
- package/src/infrastructure/persistence/metricsSensor.js +112 -0
- package/src/infrastructure/persistence/pg/alerts.js +25 -0
- package/src/infrastructure/persistence/pg/checkpoints.js +115 -0
- package/src/infrastructure/persistence/pg/metrics.js +73 -0
- package/src/infrastructure/persistence/pg/operations.js +95 -0
- package/src/infrastructure/persistence/pg/operators.js +118 -0
- package/src/infrastructure/persistence/pg/segments.js +48 -0
- package/src/infrastructure/persistence/pg/timeline.js +53 -0
- package/src/infrastructure/persistence/pg/userDecisions.js +72 -0
- package/src/infrastructure/persistence/postgresPool.js +24 -0
- package/src/infrastructure/persistence/stateDataFromMemory.js +28 -0
- package/src/infrastructure/persistence/stateDataFromPool.js +18 -0
- package/src/infrastructure/sync/operationCodec.js +75 -0
- package/src/infrastructure/sync/operationSyncIngest.js +82 -0
- package/src/infrastructure/sync/operationSyncSink.js +40 -0
- package/src/activeMelting.js +0 -54
- package/src/completedMelting.js +0 -42
- package/src/event.js +0 -24
- package/src/events.js +0 -51
- package/src/interval.js +0 -18
- package/src/machineChronology.js +0 -79
- package/src/meltingChronology.js +0 -37
- package/src/meltingMachine.js +0 -53
- package/src/meltingRuleEngine.js +0 -37
- package/src/meltingShop.js +0 -32
- package/src/meltings.js +0 -97
- package/src/monitoredMeltingMachine.js +0 -33
- package/src/plant.js +0 -23
- package/src/requests.js +0 -44
- package/src/rule.js +0 -33
- package/src/rules.js +0 -23
- package/src/scyllaSensor.js +0 -54
- package/src/segments.js +0 -64
- package/src/sqliteSensor.js +0 -106
package/src/meltingMachine.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import machineChronology from './machineChronology.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Melting machine that tracks metal weight history and sensor measurements.
|
|
5
|
-
* Supports loading and dispensing metal during melting operations.
|
|
6
|
-
* Weight history is tracked for historical queries.
|
|
7
|
-
*
|
|
8
|
-
* @param {string} name - unique machine identifier
|
|
9
|
-
* @param {object} sensors - object containing sensor instances
|
|
10
|
-
* @param {object} alerts - centralized alerts collection
|
|
11
|
-
* @param {number} initial - initial weight (defaults to 0)
|
|
12
|
-
* @returns {object} machine with name, sensors, alerts, chronology, load, dispense
|
|
13
|
-
*
|
|
14
|
-
* @example
|
|
15
|
-
* const machine = meltingMachine('icht1', { voltage: voltageSensor() }, alerts());
|
|
16
|
-
* machine.load(500);
|
|
17
|
-
* machine.chronology().get().weight; // 500
|
|
18
|
-
* machine.chronology().get(pastDate).weight; // weight at pastDate
|
|
19
|
-
*/
|
|
20
|
-
export default function meltingMachine(name, sensors, alerts, initial) {
|
|
21
|
-
const start = initial === undefined ? 0 : initial;
|
|
22
|
-
const history = [{ timestamp: new Date(), weight: start }];
|
|
23
|
-
let current = start;
|
|
24
|
-
return {
|
|
25
|
-
name() {
|
|
26
|
-
return name;
|
|
27
|
-
},
|
|
28
|
-
sensors,
|
|
29
|
-
alerts() {
|
|
30
|
-
return alerts.all((item) => {
|
|
31
|
-
return item.object === name;
|
|
32
|
-
});
|
|
33
|
-
},
|
|
34
|
-
chronology() {
|
|
35
|
-
return machineChronology(start, history, sensors);
|
|
36
|
-
},
|
|
37
|
-
load(w) {
|
|
38
|
-
current += w;
|
|
39
|
-
history.push({ timestamp: new Date(), weight: current });
|
|
40
|
-
},
|
|
41
|
-
dispense(w) {
|
|
42
|
-
current -= w;
|
|
43
|
-
history.push({ timestamp: new Date(), weight: current });
|
|
44
|
-
},
|
|
45
|
-
reset(w) {
|
|
46
|
-
current = w;
|
|
47
|
-
history.push({ timestamp: new Date(), weight: current });
|
|
48
|
-
},
|
|
49
|
-
init() {
|
|
50
|
-
return this;
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
}
|
package/src/meltingRuleEngine.js
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Expert system that evaluates measurements and issues alerts.
|
|
3
|
-
* Checks voltage and power factor thresholds.
|
|
4
|
-
*
|
|
5
|
-
* @param {function} issue - callback to issue alerts with message and timestamp
|
|
6
|
-
* @returns {object} rule engine with evaluate method
|
|
7
|
-
*
|
|
8
|
-
* @example
|
|
9
|
-
* const engine = meltingRuleEngine(function(msg, ts) { console.log(msg); });
|
|
10
|
-
* engine.evaluate({ voltage: { value: 340 }, cosphi: { value: 0.9 } });
|
|
11
|
-
*/
|
|
12
|
-
export default function meltingRuleEngine(issue) {
|
|
13
|
-
return {
|
|
14
|
-
evaluate(snapshot) {
|
|
15
|
-
const timestamp = new Date();
|
|
16
|
-
const {voltage} = snapshot;
|
|
17
|
-
const {cosphi} = snapshot;
|
|
18
|
-
if (voltage && voltage.value < 350) {
|
|
19
|
-
issue(`Critical low voltage: ${ voltage.value.toFixed(1) }V`, timestamp);
|
|
20
|
-
} else if (voltage && voltage.value < 360) {
|
|
21
|
-
issue(`Low voltage: ${ voltage.value.toFixed(1) }V`, timestamp);
|
|
22
|
-
} else if (voltage && voltage.value > 410) {
|
|
23
|
-
issue(`Critical high voltage: ${ voltage.value.toFixed(1) }V`, timestamp);
|
|
24
|
-
} else if (voltage && voltage.value > 400) {
|
|
25
|
-
issue(`High voltage: ${ voltage.value.toFixed(1) }V`, timestamp);
|
|
26
|
-
}
|
|
27
|
-
if (cosphi && cosphi.value < 0.7) {
|
|
28
|
-
issue(`Critical low power factor: ${ cosphi.value.toFixed(2)}`, timestamp);
|
|
29
|
-
} else if (cosphi && cosphi.value < 0.8) {
|
|
30
|
-
issue(`Low power factor: ${ cosphi.value.toFixed(2)}`, timestamp);
|
|
31
|
-
}
|
|
32
|
-
if (voltage && cosphi && voltage.value < 370 && cosphi.value < 0.8) {
|
|
33
|
-
issue('Power quality issue detected', timestamp);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
|
-
}
|
package/src/meltingShop.js
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Melting shop containing melting machines and their meltings.
|
|
3
|
-
* Provides initialization for all contained machines.
|
|
4
|
-
*
|
|
5
|
-
* @param {string} name - unique identifier for the shop
|
|
6
|
-
* @param {object} meltingMachines - initialized wrapper of melting machines
|
|
7
|
-
* @param {object} meltings - collection managing melting sessions
|
|
8
|
-
* @param {object} alerts - alerts collection for the shop
|
|
9
|
-
* @param {object} events - events collection shared from plant
|
|
10
|
-
* @returns {object} shop with name, machines, meltings, alerts, events properties and init method
|
|
11
|
-
*
|
|
12
|
-
* @example
|
|
13
|
-
* const shop = meltingShop('shop1', initialized({ m1: machine }, Object.values), meltings(), alerts(), events());
|
|
14
|
-
* shop.name(); // 'shop1'
|
|
15
|
-
* shop.machines.init().m1; // access machine by key
|
|
16
|
-
* shop.events.create(new Date(), {}, ['label']);
|
|
17
|
-
* shop.init();
|
|
18
|
-
*/
|
|
19
|
-
export default function meltingShop(name, meltingMachines, meltings, alerts, events) {
|
|
20
|
-
return {
|
|
21
|
-
name() {
|
|
22
|
-
return name;
|
|
23
|
-
},
|
|
24
|
-
machines: meltingMachines,
|
|
25
|
-
meltings,
|
|
26
|
-
alerts,
|
|
27
|
-
events,
|
|
28
|
-
init() {
|
|
29
|
-
meltingMachines.init();
|
|
30
|
-
},
|
|
31
|
-
};
|
|
32
|
-
}
|
package/src/meltings.js
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
/* eslint-disable max-lines-per-function, max-statements */
|
|
2
|
-
import pubsub from './pubsub.js';
|
|
3
|
-
import meltingChronology from './meltingChronology.js';
|
|
4
|
-
import activeMelting from './activeMelting.js';
|
|
5
|
-
import completedMelting from './completedMelting.js';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Collection of melting sessions associated with machines.
|
|
9
|
-
* Uses add() for creating both active and completed meltings.
|
|
10
|
-
* Uses query() for filtering and streaming meltings.
|
|
11
|
-
*
|
|
12
|
-
* @returns {object} collection with add, query methods
|
|
13
|
-
*
|
|
14
|
-
* @example
|
|
15
|
-
* const list = meltings();
|
|
16
|
-
* const active = list.add(machine, {}); // creates active melting
|
|
17
|
-
* const completed = list.add(machine, { start, end }); // creates completed melting
|
|
18
|
-
* list.query(); // returns all completed meltings
|
|
19
|
-
* list.query({ machine }); // returns meltings for machine
|
|
20
|
-
* list.query({ id: 'm1' }); // returns melting by id
|
|
21
|
-
* list.query({ stream: callback }); // subscribe to events
|
|
22
|
-
*/
|
|
23
|
-
export default function meltings() {
|
|
24
|
-
const items = [];
|
|
25
|
-
const bus = pubsub();
|
|
26
|
-
let counter = 0;
|
|
27
|
-
function onUpdate(id, updated) {
|
|
28
|
-
const item = items.find((i) => {
|
|
29
|
-
return i.melting.id() === id;
|
|
30
|
-
});
|
|
31
|
-
if (item) {
|
|
32
|
-
item.melting = updated;
|
|
33
|
-
bus.emit({ type: 'updated', melting: updated });
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
return {
|
|
37
|
-
add(machine, data) {
|
|
38
|
-
const opts = data === undefined ? {} : data;
|
|
39
|
-
if (opts.end === undefined) {
|
|
40
|
-
const existing = items.find((i) => {
|
|
41
|
-
const chron = i.melting.chronology().get();
|
|
42
|
-
return i.machine === machine && chron.end === undefined;
|
|
43
|
-
});
|
|
44
|
-
if (existing) {
|
|
45
|
-
return existing.melting;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
counter += 1;
|
|
49
|
-
const id = `m${counter}`;
|
|
50
|
-
if (opts.end !== undefined) {
|
|
51
|
-
const chron = meltingChronology(machine, new Date(opts.start), new Date(opts.end));
|
|
52
|
-
const completed = completedMelting(id, machine, chron, (updated) => {
|
|
53
|
-
onUpdate(id, updated);
|
|
54
|
-
});
|
|
55
|
-
items.push({ machine, melting: completed });
|
|
56
|
-
bus.emit({ type: 'completed', melting: completed });
|
|
57
|
-
return completed;
|
|
58
|
-
}
|
|
59
|
-
const start = opts.start === undefined ? new Date() : new Date(opts.start);
|
|
60
|
-
const item = { machine, melting: undefined };
|
|
61
|
-
items.push(item);
|
|
62
|
-
const active = activeMelting(id, machine, start, (completed) => {
|
|
63
|
-
item.melting = completed;
|
|
64
|
-
bus.emit({ type: 'completed', melting: completed });
|
|
65
|
-
}, (updated) => {
|
|
66
|
-
onUpdate(id, updated);
|
|
67
|
-
});
|
|
68
|
-
item.melting = active;
|
|
69
|
-
bus.emit({ type: 'started', melting: active });
|
|
70
|
-
return active;
|
|
71
|
-
},
|
|
72
|
-
query(options) {
|
|
73
|
-
const opts = options === undefined ? {} : options;
|
|
74
|
-
if (opts.stream !== undefined) {
|
|
75
|
-
return bus.stream(opts.stream);
|
|
76
|
-
}
|
|
77
|
-
if (opts.id !== undefined) {
|
|
78
|
-
const item = items.find((i) => {
|
|
79
|
-
return i.melting.id() === opts.id;
|
|
80
|
-
});
|
|
81
|
-
return item === undefined ? undefined : item.melting;
|
|
82
|
-
}
|
|
83
|
-
if (opts.machine !== undefined) {
|
|
84
|
-
return items.filter((i) => {
|
|
85
|
-
return i.machine === opts.machine;
|
|
86
|
-
}).map((i) => {
|
|
87
|
-
return i.melting;
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
return items.filter((i) => {
|
|
91
|
-
return i.melting.chronology().get().end !== undefined;
|
|
92
|
-
}).map((i) => {
|
|
93
|
-
return i.melting;
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Melting machine wrapper that monitors measurements and generates alerts.
|
|
3
|
-
* Periodically evaluates sensor readings using a rule engine via chronology.
|
|
4
|
-
*
|
|
5
|
-
* @param {object} machine - the melting machine to monitor
|
|
6
|
-
* @param {object} ruleEngine - engine that evaluates measurements and triggers alerts
|
|
7
|
-
* @param {function} interval - factory to create periodic intervals
|
|
8
|
-
* @returns {object} monitored machine with name, sensors, alerts, chronology, init methods
|
|
9
|
-
*
|
|
10
|
-
* @example
|
|
11
|
-
* const monitored = monitoredMeltingMachine(machine, ruleEngine(), interval);
|
|
12
|
-
* monitored.init(); // starts periodic monitoring
|
|
13
|
-
*/
|
|
14
|
-
export default function monitoredMeltingMachine(machine, ruleEngine, interval) {
|
|
15
|
-
return {
|
|
16
|
-
name() {
|
|
17
|
-
return machine.name();
|
|
18
|
-
},
|
|
19
|
-
sensors: machine.sensors,
|
|
20
|
-
alerts() {
|
|
21
|
-
return machine.alerts();
|
|
22
|
-
},
|
|
23
|
-
chronology() {
|
|
24
|
-
return machine.chronology();
|
|
25
|
-
},
|
|
26
|
-
init() {
|
|
27
|
-
interval(1000, async () => {
|
|
28
|
-
const snapshot = await machine.chronology().get({ type: 'current' });
|
|
29
|
-
ruleEngine.evaluate(snapshot);
|
|
30
|
-
}).start();
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
}
|
package/src/plant.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Top-level plant structure containing melting shops.
|
|
3
|
-
* Provides initialization for all contained shops.
|
|
4
|
-
*
|
|
5
|
-
* @param {object} shops - initialized list of melting shops
|
|
6
|
-
* @param {object} events - events collection shared by all shops
|
|
7
|
-
* @returns {object} plant with shops, events properties and init method
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* const p = plant(initializedList(shop1, shop2), events(event, rules));
|
|
11
|
-
* p.shops.list(); // [shop1, shop2]
|
|
12
|
-
* p.events.create(new Date(), {}, ['label']);
|
|
13
|
-
* p.init();
|
|
14
|
-
*/
|
|
15
|
-
export default function plant(shops, events) {
|
|
16
|
-
return {
|
|
17
|
-
shops,
|
|
18
|
-
events,
|
|
19
|
-
init() {
|
|
20
|
-
shops.init();
|
|
21
|
-
},
|
|
22
|
-
};
|
|
23
|
-
}
|
package/src/requests.js
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import pubsub from './pubsub.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Per-machine in-memory label request collection.
|
|
5
|
-
* Stores requests for segment labeling with options for the user to choose from.
|
|
6
|
-
* Supports responding to requests and streaming events via pubsub.
|
|
7
|
-
*
|
|
8
|
-
* @returns {object} collection with add, respond, query, stream methods
|
|
9
|
-
*
|
|
10
|
-
* @example
|
|
11
|
-
* const reqs = requests();
|
|
12
|
-
* reqs.add({ id: 'req-0', name: 'unknown', startTime: new Date(), endTime: new Date(), duration: 60, options: ['on', 'off'] });
|
|
13
|
-
* reqs.respond('req-0', { label: 'on' }); // marks resolved
|
|
14
|
-
* reqs.query(); // returns unresolved requests
|
|
15
|
-
* const sub = reqs.stream((e) => console.log(e));
|
|
16
|
-
* sub.cancel();
|
|
17
|
-
*/
|
|
18
|
-
export default function requests() {
|
|
19
|
-
const items = [];
|
|
20
|
-
const bus = pubsub();
|
|
21
|
-
return {
|
|
22
|
-
add(request) {
|
|
23
|
-
items.push({ ...request, resolved: false });
|
|
24
|
-
bus.emit({ type: 'created', request });
|
|
25
|
-
},
|
|
26
|
-
respond(id, body) {
|
|
27
|
-
const found = items.find((r) => {
|
|
28
|
-
return r.id === id && !r.resolved;
|
|
29
|
-
});
|
|
30
|
-
if (!found) {
|
|
31
|
-
return undefined;
|
|
32
|
-
}
|
|
33
|
-
found.resolved = true;
|
|
34
|
-
bus.emit({ type: 'resolved', request: found });
|
|
35
|
-
return { id, ...body };
|
|
36
|
-
},
|
|
37
|
-
query() {
|
|
38
|
-
return items.filter((r) => {
|
|
39
|
-
return !r.resolved;
|
|
40
|
-
});
|
|
41
|
-
},
|
|
42
|
-
stream: bus.stream
|
|
43
|
-
};
|
|
44
|
-
}
|
package/src/rule.js
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unified trigger-action mapping for event processing.
|
|
3
|
-
* Evaluates context and executes action when trigger matches.
|
|
4
|
-
*
|
|
5
|
-
* @param {function} trigger - predicate that receives context and returns boolean
|
|
6
|
-
* @param {function} action - callback executed when trigger returns true
|
|
7
|
-
* @returns {object} rule with evaluate method
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* // Sensor rule - executes on voltage threshold
|
|
11
|
-
* const r1 = rule(
|
|
12
|
-
* (ctx) => ctx.sensor && ctx.sensor.voltage < 350,
|
|
13
|
-
* (ctx) => alerts.trigger('Low voltage')
|
|
14
|
-
* );
|
|
15
|
-
*
|
|
16
|
-
* // Event rule - handles labeled events
|
|
17
|
-
* const r2 = rule(
|
|
18
|
-
* (ctx) => ctx.event && ctx.event.labels().includes('melting-start'),
|
|
19
|
-
* (ctx) => meltings.add(ctx.event.properties().machine)
|
|
20
|
-
* );
|
|
21
|
-
*
|
|
22
|
-
* r1.evaluate({sensor: {voltage: 340}}); // triggers action
|
|
23
|
-
* r2.evaluate({event: e}); // triggers action if labels match
|
|
24
|
-
*/
|
|
25
|
-
export default function rule(trigger, action) {
|
|
26
|
-
return {
|
|
27
|
-
evaluate(context) {
|
|
28
|
-
if (trigger(context)) {
|
|
29
|
-
action(context);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
}
|
package/src/rules.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Collection of rules with unified evaluation.
|
|
3
|
-
* Evaluates all rules against provided context.
|
|
4
|
-
*
|
|
5
|
-
* @param {object[]} list - array of rule objects with evaluate method
|
|
6
|
-
* @returns {object} rules collection with evaluate and all methods
|
|
7
|
-
*
|
|
8
|
-
* @example
|
|
9
|
-
* const rs = rules([rule1, rule2, rule3]);
|
|
10
|
-
* rs.evaluate({sensor: {voltage: 340}}); // evaluates all rules
|
|
11
|
-
* rs.evaluate({event: e}); // evaluates all rules against event
|
|
12
|
-
* rs.all(); // returns array of all rules
|
|
13
|
-
*/
|
|
14
|
-
export default function rules(list) {
|
|
15
|
-
return {
|
|
16
|
-
evaluate(context) {
|
|
17
|
-
list.forEach((item) => {
|
|
18
|
-
item.evaluate(context);
|
|
19
|
-
});
|
|
20
|
-
},
|
|
21
|
-
all: () => {return [...list]}
|
|
22
|
-
};
|
|
23
|
-
}
|
package/src/scyllaSensor.js
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sensor backed by ScyllaDB metrics table.
|
|
3
|
-
*
|
|
4
|
-
* Reads sensor measurements from scada.metrics table
|
|
5
|
-
* and provides real-time streaming via polling.
|
|
6
|
-
*
|
|
7
|
-
* @param {object} connection - ScyllaDB connection with query method
|
|
8
|
-
* @param {string} topic - Metric topic in format '{machine}/{sensor}'
|
|
9
|
-
* @param {string} displayName - Human-readable sensor name
|
|
10
|
-
* @param {string} unit - Measurement unit (e.g., 'V', 'cos(φ)')
|
|
11
|
-
* @returns {object} sensor with name, measurements and stream methods
|
|
12
|
-
*
|
|
13
|
-
* @example
|
|
14
|
-
* const sensor = scyllaSensor(conn, 'icht1/voltage', 'Voltage', 'V');
|
|
15
|
-
* sensor.name(); // 'Voltage'
|
|
16
|
-
* await sensor.measurements({ start, end }); // array of readings
|
|
17
|
-
* sensor.stream(since, 1000, callback); // live stream
|
|
18
|
-
*/
|
|
19
|
-
export default function scyllaSensor(connection, topic, displayName, unit) {
|
|
20
|
-
return {
|
|
21
|
-
name() {
|
|
22
|
-
return displayName;
|
|
23
|
-
},
|
|
24
|
-
async measurements(range, step) {
|
|
25
|
-
void step;
|
|
26
|
-
const rows = await connection.query(
|
|
27
|
-
'SELECT ts, value FROM scada.metrics WHERE topic = ? AND ts >= ? AND ts <= ?',
|
|
28
|
-
[topic, range.start, range.end]
|
|
29
|
-
);
|
|
30
|
-
return rows.map((row) => {
|
|
31
|
-
return { timestamp: row.ts, value: row.value, unit };
|
|
32
|
-
});
|
|
33
|
-
},
|
|
34
|
-
stream(since, step, callback, clock) {
|
|
35
|
-
const time = clock || (() => { return new Date(); });
|
|
36
|
-
let lastTs = since;
|
|
37
|
-
const timer = setInterval(async () => {
|
|
38
|
-
const rows = await connection.query(
|
|
39
|
-
'SELECT ts, value FROM scada.metrics WHERE topic = ? AND ts > ? AND ts <= ? LIMIT 100',
|
|
40
|
-
[topic, lastTs, time()]
|
|
41
|
-
);
|
|
42
|
-
rows.forEach((row) => {
|
|
43
|
-
callback({ timestamp: row.ts, value: row.value, unit });
|
|
44
|
-
lastTs = row.ts;
|
|
45
|
-
});
|
|
46
|
-
}, step);
|
|
47
|
-
return {
|
|
48
|
-
cancel() {
|
|
49
|
-
clearInterval(timer);
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
}
|
package/src/segments.js
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import pubsub from './pubsub.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Per-machine in-memory timeline segment collection.
|
|
5
|
-
* Stores segments with name, startTime, endTime, duration.
|
|
6
|
-
* Supports resolving by startTime and streaming events via pubsub.
|
|
7
|
-
*
|
|
8
|
-
* @returns {object} collection with add, resolve, query, stream methods
|
|
9
|
-
*
|
|
10
|
-
* @example
|
|
11
|
-
* const segs = segments();
|
|
12
|
-
* segs.add({ name: 'on', startTime: new Date(), endTime: new Date(), duration: 60 });
|
|
13
|
-
* segs.resolve(startTime, ['heating'], {});
|
|
14
|
-
* segs.query(); // all segments
|
|
15
|
-
* segs.query({ from: '2024-01-01', to: '2024-01-02' }); // filtered
|
|
16
|
-
* const sub = segs.stream((e) => console.log(e));
|
|
17
|
-
* sub.cancel();
|
|
18
|
-
*/
|
|
19
|
-
export default function segments() {
|
|
20
|
-
const items = [];
|
|
21
|
-
const bus = pubsub();
|
|
22
|
-
return {
|
|
23
|
-
add(segment) {
|
|
24
|
-
items.push(segment);
|
|
25
|
-
bus.emit({ type: 'created', segment });
|
|
26
|
-
},
|
|
27
|
-
resolve(start, tags, properties) {
|
|
28
|
-
const found = items.find((item) => {
|
|
29
|
-
return item.startTime.getTime() === start.getTime();
|
|
30
|
-
});
|
|
31
|
-
if (found) {
|
|
32
|
-
found.tags = tags;
|
|
33
|
-
found.properties = properties;
|
|
34
|
-
bus.emit({ type: 'resolved', segment: found });
|
|
35
|
-
}
|
|
36
|
-
},
|
|
37
|
-
retag(start, tags, properties) {
|
|
38
|
-
const found = items.find((item) => {
|
|
39
|
-
return item.startTime.getTime() === start.getTime();
|
|
40
|
-
});
|
|
41
|
-
if (found) {
|
|
42
|
-
found.tags = tags;
|
|
43
|
-
found.properties = properties;
|
|
44
|
-
delete found.options;
|
|
45
|
-
bus.emit({ type: 'resolved', segment: found });
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
query(options) {
|
|
49
|
-
if (!options) {
|
|
50
|
-
return items.slice();
|
|
51
|
-
}
|
|
52
|
-
return items.filter((item) => {
|
|
53
|
-
if (options.from && item.endTime < new Date(options.from)) {
|
|
54
|
-
return false;
|
|
55
|
-
}
|
|
56
|
-
if (options.to && item.startTime > new Date(options.to)) {
|
|
57
|
-
return false;
|
|
58
|
-
}
|
|
59
|
-
return true;
|
|
60
|
-
});
|
|
61
|
-
},
|
|
62
|
-
stream: bus.stream
|
|
63
|
-
};
|
|
64
|
-
}
|
package/src/sqliteSensor.js
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sensor backed by SQLite metrics table with downsampling.
|
|
3
|
-
*
|
|
4
|
-
* Reads sensor measurements from a SQLite metrics table
|
|
5
|
-
* and provides real-time streaming via polling.
|
|
6
|
-
* Supports time-based downsampling using window functions.
|
|
7
|
-
* Timestamps are stored as epoch milliseconds (REAL).
|
|
8
|
-
*
|
|
9
|
-
* @param {object} connection - SQLite connection with query(sql, params) method
|
|
10
|
-
* @param {string} topic - Metric topic in format '{machine}/{sensor}'
|
|
11
|
-
* @param {string} displayName - Human-readable sensor name
|
|
12
|
-
* @param {string} unit - Measurement unit (e.g., 'V', 'cos(φ)')
|
|
13
|
-
* @returns {object} sensor with name, current, measurements and stream methods
|
|
14
|
-
*
|
|
15
|
-
* @example
|
|
16
|
-
* const sensor = sqliteSensor(conn, 'icht1/voltage', 'Voltage', 'V');
|
|
17
|
-
* sensor.name(); // 'Voltage'
|
|
18
|
-
* await sensor.current(); // { found: true, timestamp, value, unit } or { found: false }
|
|
19
|
-
* await sensor.measurements({ start, end }, 60000); // downsampled to 1-minute intervals
|
|
20
|
-
* sensor.stream(since, 1000, callback); // live stream
|
|
21
|
-
*/
|
|
22
|
-
// eslint-disable-next-line max-lines-per-function
|
|
23
|
-
export default function sqliteSensor(connection, topic, displayName, unit) {
|
|
24
|
-
return {
|
|
25
|
-
/**
|
|
26
|
-
* Returns the human-readable sensor name.
|
|
27
|
-
*
|
|
28
|
-
* @returns {string} Display name
|
|
29
|
-
*/
|
|
30
|
-
name() {
|
|
31
|
-
return displayName;
|
|
32
|
-
},
|
|
33
|
-
/**
|
|
34
|
-
* Returns the most recent measurement.
|
|
35
|
-
*
|
|
36
|
-
* @returns {Promise<object>} Object with found flag and optional timestamp, value, unit
|
|
37
|
-
*/
|
|
38
|
-
async current() {
|
|
39
|
-
const rows = await connection.query(
|
|
40
|
-
'SELECT ts, value FROM metrics WHERE topic = ? ORDER BY ts DESC LIMIT 1',
|
|
41
|
-
[topic]
|
|
42
|
-
);
|
|
43
|
-
if (rows.length === 0) {
|
|
44
|
-
return { found: false };
|
|
45
|
-
}
|
|
46
|
-
return { found: true, timestamp: new Date(rows[0].ts), value: rows[0].value, unit };
|
|
47
|
-
},
|
|
48
|
-
/**
|
|
49
|
-
* Returns downsampled measurements within a time range.
|
|
50
|
-
*
|
|
51
|
-
* @param {object} range - Object with start and end Date properties
|
|
52
|
-
* @param {number} step - Downsampling bucket size in milliseconds
|
|
53
|
-
* @returns {Promise<Array>} Array of {timestamp, value, unit} objects
|
|
54
|
-
*/
|
|
55
|
-
async measurements(range, step) {
|
|
56
|
-
const millis = Math.max(1000, step);
|
|
57
|
-
const rows = await connection.query(
|
|
58
|
-
`SELECT bucket * ? as ts, value FROM (
|
|
59
|
-
SELECT CAST(ts / ? AS INTEGER) as bucket, value,
|
|
60
|
-
ROW_NUMBER() OVER (PARTITION BY CAST(ts / ? AS INTEGER) ORDER BY ts DESC) as rn
|
|
61
|
-
FROM metrics WHERE topic = ? AND ts >= ? AND ts <= ?
|
|
62
|
-
) WHERE rn = 1 ORDER BY ts`,
|
|
63
|
-
[millis, millis, millis, topic, range.start.getTime(), range.end.getTime()]
|
|
64
|
-
);
|
|
65
|
-
return rows.map((row) => {
|
|
66
|
-
return { timestamp: new Date(row.ts), value: row.value, unit };
|
|
67
|
-
});
|
|
68
|
-
},
|
|
69
|
-
/**
|
|
70
|
-
* Polls for new measurements and delivers them via callback.
|
|
71
|
-
*
|
|
72
|
-
* @param {Date} since - Start timestamp for polling
|
|
73
|
-
* @param {number} step - Polling interval in milliseconds
|
|
74
|
-
* @param {Function} callback - Called with each {timestamp, value, unit}
|
|
75
|
-
* @param {Function} [clock] - Optional time provider returning current Date
|
|
76
|
-
* @returns {object} Object with cancel() method to stop polling
|
|
77
|
-
*/
|
|
78
|
-
stream(since, step, callback, clock) {
|
|
79
|
-
const time = clock || (() => { return new Date(); });
|
|
80
|
-
let lastTs = since;
|
|
81
|
-
const timer = setInterval(async () => {
|
|
82
|
-
try {
|
|
83
|
-
const rows = await connection.query(
|
|
84
|
-
'SELECT ts, value FROM metrics WHERE topic = ? AND ts > ? AND ts <= ? ORDER BY ts LIMIT 100',
|
|
85
|
-
[topic, lastTs.getTime(), time().getTime()]
|
|
86
|
-
);
|
|
87
|
-
rows.forEach((row) => {
|
|
88
|
-
const timestamp = new Date(row.ts);
|
|
89
|
-
callback({ timestamp, value: row.value, unit });
|
|
90
|
-
lastTs = timestamp;
|
|
91
|
-
});
|
|
92
|
-
} catch {
|
|
93
|
-
// Connection errors are non-fatal; next poll retries
|
|
94
|
-
}
|
|
95
|
-
}, step);
|
|
96
|
-
return {
|
|
97
|
-
/**
|
|
98
|
-
* Stops the polling timer.
|
|
99
|
-
*/
|
|
100
|
-
cancel() {
|
|
101
|
-
clearInterval(timer);
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
};
|
|
106
|
-
}
|