@yarkivaev/scada 1.1.5 → 1.2.0
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/index.js +4 -0
- package/package.json +1 -1
- package/src/clickhouseSensor.js +17 -13
- package/src/requests.js +44 -0
- package/src/segments.js +52 -0
package/index.js
CHANGED
|
@@ -35,6 +35,10 @@ export { default as meltingRuleEngine } from './src/meltingRuleEngine.js';
|
|
|
35
35
|
export { alert, acknowledgedAlert } from './src/alert.js';
|
|
36
36
|
export { default as alerts } from './src/alerts.js';
|
|
37
37
|
|
|
38
|
+
// Timeline
|
|
39
|
+
export { default as segments } from './src/segments.js';
|
|
40
|
+
export { default as requests } from './src/requests.js';
|
|
41
|
+
|
|
38
42
|
// Utilities
|
|
39
43
|
export { default as interval } from './src/interval.js';
|
|
40
44
|
export { default as initialized } from './src/initialized.js';
|
package/package.json
CHANGED
package/src/clickhouseSensor.js
CHANGED
|
@@ -39,7 +39,7 @@ export default function clickhouseSensor(connection, topic, displayName, unit) {
|
|
|
39
39
|
if (rows.length === 0) {
|
|
40
40
|
return { timestamp: new Date(), value: 0, unit };
|
|
41
41
|
}
|
|
42
|
-
return { timestamp: new Date(rows[0].ts), value: rows[0].value, unit };
|
|
42
|
+
return { timestamp: new Date(`${rows[0].ts}Z`), value: rows[0].value, unit };
|
|
43
43
|
},
|
|
44
44
|
async measurements(range, step) {
|
|
45
45
|
const seconds = Math.max(1, Math.floor(step / 1000));
|
|
@@ -60,23 +60,27 @@ export default function clickhouseSensor(connection, topic, displayName, unit) {
|
|
|
60
60
|
}
|
|
61
61
|
);
|
|
62
62
|
return rows.map((row) => {
|
|
63
|
-
return { timestamp: new Date(row.ts), value: row.value, unit };
|
|
63
|
+
return { timestamp: new Date(`${row.ts}Z`), value: row.value, unit };
|
|
64
64
|
});
|
|
65
65
|
},
|
|
66
66
|
stream(since, step, callback) {
|
|
67
67
|
let lastTs = since;
|
|
68
68
|
const timer = setInterval(async () => {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
69
|
+
try {
|
|
70
|
+
const rows = await connection.query(
|
|
71
|
+
`SELECT ts, value FROM scada.metrics
|
|
72
|
+
WHERE topic = {topic:String} AND ts > {since:DateTime64(3)}
|
|
73
|
+
ORDER BY ts LIMIT 100`,
|
|
74
|
+
{ topic, since: formatDateTime(lastTs) }
|
|
75
|
+
);
|
|
76
|
+
rows.forEach((row) => {
|
|
77
|
+
const timestamp = new Date(`${row.ts}Z`);
|
|
78
|
+
callback({ timestamp, value: row.value, unit });
|
|
79
|
+
lastTs = timestamp;
|
|
80
|
+
});
|
|
81
|
+
} catch (err) {
|
|
82
|
+
console.error('Stream query failed:', err.message); // eslint-disable-line no-console
|
|
83
|
+
}
|
|
80
84
|
}, step);
|
|
81
85
|
return {
|
|
82
86
|
cancel() {
|
package/src/requests.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
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/segments.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
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 relabeling by startTime and streaming events via pubsub.
|
|
7
|
+
*
|
|
8
|
+
* @returns {object} collection with add, relabel, 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.relabel(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
|
+
relabel(start, name) {
|
|
28
|
+
const found = items.find((item) => {
|
|
29
|
+
return item.startTime.getTime() === start.getTime();
|
|
30
|
+
});
|
|
31
|
+
if (found) {
|
|
32
|
+
found.name = name;
|
|
33
|
+
bus.emit({ type: 'relabeled', segment: found });
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
query(options) {
|
|
37
|
+
if (!options) {
|
|
38
|
+
return items.slice();
|
|
39
|
+
}
|
|
40
|
+
return items.filter((item) => {
|
|
41
|
+
if (options.from && item.endTime < new Date(options.from)) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
if (options.to && item.startTime > new Date(options.to)) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
return true;
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
stream: bus.stream
|
|
51
|
+
};
|
|
52
|
+
}
|