@frangoteam/fuxa-min 1.2.11 → 1.3.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/api/auth/index.js +141 -3
- package/api/command/index.js +1 -1
- package/api/index.js +21 -2
- package/api/jwt-helper.js +3 -1
- package/api/resources/index.js +19 -2
- package/api/scripts/index.js +7 -3
- package/dist/3rdpartylicenses.txt +139 -7
- package/dist/assets/i18n/de.json +10 -0
- package/dist/assets/i18n/en.json +21 -3
- package/dist/assets/i18n/es.json +12 -0
- package/dist/assets/i18n/fr.json +11 -0
- package/dist/assets/i18n/ja.json +16 -6
- package/dist/assets/i18n/ko.json +12 -0
- package/dist/assets/i18n/pt.json +9 -2
- package/dist/assets/i18n/ru.json +11 -0
- package/dist/assets/i18n/sv.json +11 -1
- package/dist/assets/i18n/tr.json +8 -1
- package/dist/assets/i18n/ua.json +9 -2
- package/dist/assets/i18n/zh-cn.json +11 -0
- package/dist/assets/i18n/zh-tw.json +12 -1
- package/dist/assets/lib/svgeditor/fuxa-editor.min.js +17 -23
- package/dist/index.html +2 -2
- package/dist/main.72bdfed42c527918.js +329 -0
- package/dist/polyfills.d7de05f9af2fb559.js +1 -0
- package/dist/{runtime.8ef63094e52a66ba.js → runtime.9136a61a9b98f987.js} +1 -1
- package/dist/{scripts.40b60f02658462e4.js → scripts.d9e6ee984bf6f3b7.js} +1 -1
- package/dist/styles.545e37beb3e671ba.css +1 -0
- package/docs/openapi.yaml +3 -0
- package/integrations/node-red/index.js +4 -5
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-daq.html +56 -5
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-daq.js +8 -2
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-tag-change.html +56 -5
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-tag-change.js +12 -12
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-tag-daq-settings.html +56 -5
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-tag-daq-settings.js +14 -10
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-tag.html +69 -14
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-get-tag.js +44 -12
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-set-tag-daq-settings.html +56 -5
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-set-tag-daq-settings.js +24 -20
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-set-tag.html +72 -13
- package/integrations/node-red/node-red-contrib-fuxa/nodes/fuxa-set-tag.js +33 -7
- package/main.js +38 -17
- package/package.json +10 -5
- package/runtime/alarms/alarmstorage.js +45 -30
- package/runtime/apikeys/apiKeysStorage.js +34 -22
- package/runtime/devices/adsclient/index.js +1 -1
- package/runtime/devices/ethernetip/index.js +1 -1
- package/runtime/devices/gpio/index.js +1 -1
- package/runtime/devices/odbc/index.js +14 -9
- package/runtime/devices/s7/index.js +2 -2
- package/runtime/devices/template/index.js +14 -14
- package/runtime/notificator/notifystorage.js +34 -23
- package/runtime/project/index.js +16 -0
- package/runtime/project/prjstorage.js +78 -40
- package/runtime/scripts/index.js +6 -2
- package/runtime/storage/calculator.js +17 -13
- package/runtime/storage/daqstorage.js +28 -2
- package/runtime/storage/influxdb/index.js +8 -3
- package/runtime/storage/questdb/index.js +224 -0
- package/runtime/storage/sqlite/index.js +11 -8
- package/runtime/storage/tdengine/index.js +24 -9
- package/runtime/users/usrstorage.js +65 -61
- package/runtime/utils.js +5 -0
- package/settings.default.js +8 -2
- package/dist/main.92522279642ef880.js +0 -329
- package/dist/polyfills.c8e7db9850a3ad8b.js +0 -1
- package/dist/styles.03cc550382689976.css +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Calculator for DAQ value array (integrale, max, min, average...)
|
|
2
|
+
* Calculator for DAQ value array (integrale, max, min, average...)
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
'use strict';
|
|
@@ -19,11 +19,11 @@ function getFunctionValues(values, fromts, tots, fnc, interval, format) {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
*
|
|
23
|
-
* @param {*} values
|
|
24
|
-
* @param {*} fnc
|
|
25
|
-
* @param {*} intervalType hour / day
|
|
26
|
-
* @returns
|
|
22
|
+
*
|
|
23
|
+
* @param {*} values
|
|
24
|
+
* @param {*} fnc
|
|
25
|
+
* @param {*} intervalType hour / day
|
|
26
|
+
* @returns
|
|
27
27
|
*/
|
|
28
28
|
function getMin(timeserie, fromts, tots, intervalType) {
|
|
29
29
|
let result = getInterval(fromts, tots, intervalType, Number.MAX_VALUE);
|
|
@@ -39,7 +39,7 @@ function getMin(timeserie, fromts, tots, intervalType) {
|
|
|
39
39
|
intervals[intervalIndex] = value;
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
-
|
|
42
|
+
|
|
43
43
|
for (let i = 0; i < sorted.length; i++) {
|
|
44
44
|
let intervalIndex = getIntervalTime(sorted[i].dt, intervalType, false).getTime();
|
|
45
45
|
addToInterval(result, intervalIndex, utils.parseFloat(sorted[i].value, 5));
|
|
@@ -61,7 +61,7 @@ function getMax(timeserie, fromts, tots, intervalType) {
|
|
|
61
61
|
intervals[intervalIndex] = value;
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
|
-
|
|
64
|
+
|
|
65
65
|
for (let i = 0; i < sorted.length; i++) {
|
|
66
66
|
let intervalIndex = getIntervalTime(sorted[i].dt, intervalType, false).getTime();
|
|
67
67
|
addToInterval(result, intervalIndex, utils.parseFloat(sorted[i].value, 5));
|
|
@@ -85,7 +85,7 @@ function getAverage(timeserie, fromts, tots, intervalType, format) {
|
|
|
85
85
|
}
|
|
86
86
|
counters[intervalIndex]++;
|
|
87
87
|
}
|
|
88
|
-
|
|
88
|
+
|
|
89
89
|
for (let i = 0; i < sorted.length; i++) {
|
|
90
90
|
let intervalIndex = getIntervalTime(sorted[i].dt, intervalType, false).getTime();
|
|
91
91
|
addToInterval(result, counts, intervalIndex, parseFloat(sorted[i].value));
|
|
@@ -96,7 +96,7 @@ function getAverage(timeserie, fromts, tots, intervalType, format) {
|
|
|
96
96
|
result[k] = utils.parseFloat(result[k] / counts[k], format || 5);
|
|
97
97
|
}
|
|
98
98
|
});
|
|
99
|
-
return result;
|
|
99
|
+
return result;
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
function getSum(timeserie, fromts, tots, intervalType) {
|
|
@@ -114,12 +114,12 @@ function getSum(timeserie, fromts, tots, intervalType) {
|
|
|
114
114
|
intervals[intervalIndex] = utils.parseFloat(intervals[intervalIndex], 5)
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
|
-
|
|
117
|
+
|
|
118
118
|
for (let i = 0; i < sorted.length; i++) {
|
|
119
119
|
let intervalIndex = getIntervalTime(sorted[i].dt, intervalType, false).getTime();
|
|
120
120
|
addToInterval(result, intervalIndex, parseFloat(sorted[i].value));
|
|
121
121
|
}
|
|
122
|
-
return result;
|
|
122
|
+
return result;
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
|
|
@@ -137,7 +137,7 @@ function getIntegral(timeserie, fromts, tots, intervalType) {
|
|
|
137
137
|
intervals[intervalIndex] += value;
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
|
-
|
|
140
|
+
|
|
141
141
|
let lastRecord = null;// : TimeValue { dt: number, value: number };
|
|
142
142
|
let lastIntervalIndex = null;
|
|
143
143
|
for (let i = 0; i < sorted.length; i++) {
|
|
@@ -191,6 +191,10 @@ function getStepDate(ts, type, toadd) {
|
|
|
191
191
|
dtRange = new Date(dt.getFullYear(), dt.getMonth(), dt.getDate(), dt.getHours(), 0, 0);
|
|
192
192
|
var minutesRange = parseInt(dt.getMinutes() / 30);
|
|
193
193
|
dtRange.setMinutes((minutesRange + toadd) * 30);
|
|
194
|
+
} else if (type === ReportIntervalType.min15) {
|
|
195
|
+
dtRange = new Date(dt.getFullYear(), dt.getMonth(), dt.getDate(), dt.getHours(), 0, 0);
|
|
196
|
+
var minutesRange = parseInt(dt.getMinutes() / 15);
|
|
197
|
+
dtRange.setMinutes((minutesRange + toadd) * 15);
|
|
194
198
|
} else if (type === ReportIntervalType.min10) {
|
|
195
199
|
dtRange = new Date(dt.getFullYear(), dt.getMonth(), dt.getDate(), dt.getHours(), 0, 0);
|
|
196
200
|
var minutesRange = parseInt(dt.getMinutes() / 10);
|
|
@@ -21,6 +21,7 @@ var logger;
|
|
|
21
21
|
var daqDB = {}; // list of daqDB node: SQlite one pro device, influxDB only one
|
|
22
22
|
var currentStorateDB;
|
|
23
23
|
var runtime;
|
|
24
|
+
var questDbModule = null;
|
|
24
25
|
|
|
25
26
|
function init(_settings, _log, _runtime) {
|
|
26
27
|
settings = _settings;
|
|
@@ -42,7 +43,7 @@ function reset() {
|
|
|
42
43
|
function addDaqNode(_id, fncgetprop) {
|
|
43
44
|
var id = _id;
|
|
44
45
|
const dbType = _getDbType();
|
|
45
|
-
if (dbType === DaqStoreTypeEnum.influxDB || dbType === DaqStoreTypeEnum.influxDB18 || dbType === DaqStoreTypeEnum.TDengine) {
|
|
46
|
+
if (dbType === DaqStoreTypeEnum.influxDB || dbType === DaqStoreTypeEnum.influxDB18 || dbType === DaqStoreTypeEnum.TDengine || dbType === DaqStoreTypeEnum.questDB) {
|
|
46
47
|
id = dbType;
|
|
47
48
|
}
|
|
48
49
|
if (!daqDB[id]) {
|
|
@@ -50,6 +51,14 @@ function addDaqNode(_id, fncgetprop) {
|
|
|
50
51
|
daqDB[id] = InfluxDB.create(settings, logger, currentStorateDB);
|
|
51
52
|
} else if(id === DaqStoreTypeEnum.TDengine){
|
|
52
53
|
daqDB[id] = TDengine.create(settings, logger, currentStorateDB);
|
|
54
|
+
} else if(id === DaqStoreTypeEnum.questDB){
|
|
55
|
+
const QuestDB = _getQuestDbModule();
|
|
56
|
+
if (QuestDB) {
|
|
57
|
+
daqDB[id] = QuestDB.create(settings, logger, currentStorateDB);
|
|
58
|
+
} else {
|
|
59
|
+
logger.warn('daqstorage: QuestDB storage selected but package dependencies are not installed. Falling back to SQLite.');
|
|
60
|
+
daqDB[id] = SqliteDB.create(settings, logger, id, currentStorateDB);
|
|
61
|
+
}
|
|
53
62
|
} else {
|
|
54
63
|
daqDB[id] = SqliteDB.create(settings, logger, id, currentStorateDB);
|
|
55
64
|
}
|
|
@@ -160,16 +169,33 @@ function _getDaqNode(tagid) {
|
|
|
160
169
|
|
|
161
170
|
function _getDbType() {
|
|
162
171
|
if (settings.daqstore && settings.daqstore.type) {
|
|
172
|
+
if (settings.daqstore.type === 'QuestDB') {
|
|
173
|
+
return DaqStoreTypeEnum.questDB;
|
|
174
|
+
}
|
|
163
175
|
return settings.daqstore.type;
|
|
164
176
|
}
|
|
165
177
|
return DaqStoreTypeEnum.SQlite;
|
|
166
178
|
}
|
|
167
179
|
|
|
180
|
+
function _getQuestDbModule() {
|
|
181
|
+
if (questDbModule) {
|
|
182
|
+
return questDbModule;
|
|
183
|
+
}
|
|
184
|
+
try {
|
|
185
|
+
questDbModule = require("./questdb");
|
|
186
|
+
} catch (err) {
|
|
187
|
+
questDbModule = null;
|
|
188
|
+
logger.warn(`daqstorage: QuestDB module unavailable (${err.message})`);
|
|
189
|
+
}
|
|
190
|
+
return questDbModule;
|
|
191
|
+
}
|
|
192
|
+
|
|
168
193
|
var DaqStoreTypeEnum = {
|
|
169
194
|
SQlite: 'SQlite',
|
|
170
195
|
influxDB: 'influxDB',
|
|
171
196
|
influxDB18: 'influxDB18',
|
|
172
197
|
TDengine: 'TDengine',
|
|
198
|
+
questDB: 'questDB',
|
|
173
199
|
}
|
|
174
200
|
|
|
175
201
|
function _getValue(value) {
|
|
@@ -187,4 +213,4 @@ module.exports = {
|
|
|
187
213
|
getNodesValues: getNodesValues,
|
|
188
214
|
checkRetention: checkRetention,
|
|
189
215
|
getCurrentStorageFnc: getCurrentStorageFnc,
|
|
190
|
-
};
|
|
216
|
+
};
|
|
@@ -12,6 +12,10 @@ var { InfluxDB, Point, flux } = require('@influxdata/influxdb-client');
|
|
|
12
12
|
const VERSION_18_FLUX = '1.8-flux';
|
|
13
13
|
const VERSION_20 = '2.0';
|
|
14
14
|
|
|
15
|
+
function escapeInfluxIdentifier(value) {
|
|
16
|
+
return String(value).replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
17
|
+
}
|
|
18
|
+
|
|
15
19
|
function Influx(_settings, _log, _currentStorate) {
|
|
16
20
|
|
|
17
21
|
var settings = _settings; // Application settings
|
|
@@ -157,7 +161,8 @@ function Influx(_settings, _log, _currentStorate) {
|
|
|
157
161
|
if (influxdbVersion === VERSION_18_FLUX) {
|
|
158
162
|
fromts *= 1000000;
|
|
159
163
|
tots *= 1000000;
|
|
160
|
-
const
|
|
164
|
+
const measurement = escapeInfluxIdentifier(tagid);
|
|
165
|
+
const query = `SELECT * FROM "${measurement}" WHERE time >= ${Number(fromts)} AND time <= ${Number(tots)}`;
|
|
161
166
|
client.query(query).then((result) => {
|
|
162
167
|
resolve(result.map(row => {
|
|
163
168
|
return {
|
|
@@ -171,7 +176,7 @@ function Influx(_settings, _log, _currentStorate) {
|
|
|
171
176
|
reject(error);
|
|
172
177
|
});
|
|
173
178
|
} else {
|
|
174
|
-
const query = flux`from(bucket:
|
|
179
|
+
const query = flux`from(bucket: ${settings.daqstore.bucket}) |> range(start: ${new Date(fromts)}, stop: ${new Date(tots)}) |> filter(fn: (r) => r.id == ${String(tagid)})`;
|
|
175
180
|
var result = [];
|
|
176
181
|
queryApi.queryRows(query, {
|
|
177
182
|
next(row, tableMeta) {
|
|
@@ -234,4 +239,4 @@ module.exports = {
|
|
|
234
239
|
var InfluxDBStatusEnum = {
|
|
235
240
|
OPEN: 'open',
|
|
236
241
|
CLOSE: 'close',
|
|
237
|
-
}
|
|
242
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { Pool } = require('pg');
|
|
4
|
+
const { Sender } = require('@questdb/nodejs-client');
|
|
5
|
+
let utils = require('../../utils');
|
|
6
|
+
|
|
7
|
+
function QuestDB(_settings, _log, _currentStorage) {
|
|
8
|
+
let settings = _settings; // Application settings
|
|
9
|
+
const logger = _log; // Application logger
|
|
10
|
+
const currentStorage = _currentStorage; // Database to set the last value (current)
|
|
11
|
+
let pool = null;
|
|
12
|
+
let sender = null;
|
|
13
|
+
const tableName = getTableName();
|
|
14
|
+
let writeQueue = Promise.resolve();
|
|
15
|
+
let initPromise = Promise.resolve();
|
|
16
|
+
|
|
17
|
+
this.setCall = function (_fncGetProp) {
|
|
18
|
+
fncGetTagProp = _fncGetProp;
|
|
19
|
+
return this.addDaqValues;
|
|
20
|
+
}
|
|
21
|
+
var fncGetTagProp = null;
|
|
22
|
+
|
|
23
|
+
this.init = async function () {
|
|
24
|
+
try {
|
|
25
|
+
pool = new Pool(getQueryClientConfig());
|
|
26
|
+
sender = await Sender.fromConfig(getIngestConfigString());
|
|
27
|
+
await ensureSchema();
|
|
28
|
+
logger.info('QuestDB connected');
|
|
29
|
+
} catch (error) {
|
|
30
|
+
logger.error(`questdb-init failed! ${error}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
this.addDaqValues = function (tagsValues, deviceName, deviceId) {
|
|
35
|
+
var dataToRestore = [];
|
|
36
|
+
var rowsToWrite = [];
|
|
37
|
+
|
|
38
|
+
for (const tagid in tagsValues) {
|
|
39
|
+
let tag = tagsValues[tagid];
|
|
40
|
+
if (!tag.daq || utils.isNullOrUndefined(tag.value) || Number.isNaN(tag.value)) {
|
|
41
|
+
if (tag.daq && tag.daq.restored) {
|
|
42
|
+
dataToRestore.push({ id: tag.id, deviceId: deviceId, value: tag.value });
|
|
43
|
+
}
|
|
44
|
+
if (tag.daq && !tag.daq.enabled) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
rowsToWrite.push({
|
|
50
|
+
tagid,
|
|
51
|
+
deviceId,
|
|
52
|
+
deviceName: deviceName || '',
|
|
53
|
+
unsPath: normalizeUnsPath(tag.unsPath),
|
|
54
|
+
value: tag.value,
|
|
55
|
+
timestamp: tag.timestamp || Date.now(),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (rowsToWrite.length) {
|
|
60
|
+
writeQueue = writeQueue.then(async () => {
|
|
61
|
+
await initPromise;
|
|
62
|
+
if (!sender) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
for (const row of rowsToWrite) {
|
|
67
|
+
const parsedValue = normalizeValue(row.value);
|
|
68
|
+
let line = sender
|
|
69
|
+
.table(tableName)
|
|
70
|
+
.symbol('tag_id', row.tagid)
|
|
71
|
+
.symbol('device_id', row.deviceId)
|
|
72
|
+
.stringColumn('device_name', row.deviceName);
|
|
73
|
+
if (!utils.isNullOrUndefined(row.unsPath)) {
|
|
74
|
+
line = line.symbol('uns_path', row.unsPath);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!utils.isNullOrUndefined(parsedValue.numberValue)) {
|
|
78
|
+
line = line.floatColumn('number_value', parsedValue.numberValue);
|
|
79
|
+
}
|
|
80
|
+
if (!utils.isNullOrUndefined(parsedValue.stringValue)) {
|
|
81
|
+
line = line.stringColumn('string_value', parsedValue.stringValue);
|
|
82
|
+
}
|
|
83
|
+
await line.at(Number(row.timestamp), 'ms');
|
|
84
|
+
}
|
|
85
|
+
await sender.flush();
|
|
86
|
+
}).catch((error) => {
|
|
87
|
+
logger.error(`questdb-addDaqValues failed! ${error}`);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (dataToRestore.length && currentStorage) {
|
|
92
|
+
currentStorage.setValues(dataToRestore);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
this.getDaqValue = function (tagid, fromts, tots) {
|
|
97
|
+
return new Promise(function (resolve, reject) {
|
|
98
|
+
initPromise.then(() => {
|
|
99
|
+
if (!pool) {
|
|
100
|
+
resolve([]);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const query = `SELECT timestamp, number_value, string_value
|
|
105
|
+
FROM ${tableName}
|
|
106
|
+
WHERE tag_id = $1
|
|
107
|
+
AND timestamp >= $2
|
|
108
|
+
AND timestamp < $3
|
|
109
|
+
ORDER BY timestamp`;
|
|
110
|
+
const params = [tagid, new Date(fromts), new Date(tots)];
|
|
111
|
+
|
|
112
|
+
pool.query(query, params)
|
|
113
|
+
.then((result) => {
|
|
114
|
+
let data = [];
|
|
115
|
+
result.rows.forEach((row) => {
|
|
116
|
+
const value = !utils.isNullOrUndefined(row.number_value) ? Number(row.number_value) : row.string_value;
|
|
117
|
+
data.push({ dt: new Date(row.timestamp).getTime(), value });
|
|
118
|
+
});
|
|
119
|
+
resolve(data)
|
|
120
|
+
})
|
|
121
|
+
.catch((error) => {
|
|
122
|
+
logger.error(`questdb-getDaqValue failed! ${error}`)
|
|
123
|
+
reject(error)
|
|
124
|
+
})
|
|
125
|
+
}).catch((error) => {
|
|
126
|
+
logger.error(`questdb-getDaqValue failed! ${error}`)
|
|
127
|
+
reject(error)
|
|
128
|
+
});
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
this.close = function () {
|
|
133
|
+
if (sender) {
|
|
134
|
+
sender.close().catch((error) => {
|
|
135
|
+
logger.error(`questdb-close sender failed! ${error}`);
|
|
136
|
+
});
|
|
137
|
+
sender = null;
|
|
138
|
+
}
|
|
139
|
+
if (pool) {
|
|
140
|
+
pool.end().catch((error) => {
|
|
141
|
+
logger.error(`questdb-close pool failed! ${error}`);
|
|
142
|
+
});
|
|
143
|
+
pool = null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
this.getDaqMap = function (tagid) {
|
|
148
|
+
var dummy = {};
|
|
149
|
+
dummy[tagid] = true;
|
|
150
|
+
return dummy;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function ensureSchema() {
|
|
154
|
+
if (!pool) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
await pool.query(`CREATE TABLE IF NOT EXISTS ${tableName} (
|
|
158
|
+
timestamp TIMESTAMP,
|
|
159
|
+
tag_id SYMBOL,
|
|
160
|
+
number_value FLOAT,
|
|
161
|
+
string_value STRING,
|
|
162
|
+
device_id SYMBOL,
|
|
163
|
+
device_name STRING,
|
|
164
|
+
uns_path SYMBOL
|
|
165
|
+
) TIMESTAMP(timestamp) PARTITION BY DAY`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function getIngestConfigString() {
|
|
169
|
+
return settings.daqstore.configurationString || 'http::addr=localhost:9000;';
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function getQueryClientConfig() {
|
|
173
|
+
return {
|
|
174
|
+
host: settings.daqstore.host || '127.0.0.1',
|
|
175
|
+
port: 8812, // Standard port
|
|
176
|
+
database: 'qdb', // Standard database
|
|
177
|
+
user: settings.daqstore.credentials?.username || 'admin',
|
|
178
|
+
password: settings.daqstore.credentials?.password || 'quest',
|
|
179
|
+
max: 10, // Pool with 10 connections
|
|
180
|
+
idleTimeoutMillis: 30000, // 30s
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function getTableName() {
|
|
185
|
+
const name = (settings.daqstore.tableName || 'meters').trim();
|
|
186
|
+
if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
|
|
187
|
+
return name.toLowerCase();
|
|
188
|
+
}
|
|
189
|
+
logger.warn(`questdb invalid tableName "${name}", fallback to meters`);
|
|
190
|
+
return 'meters';
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function normalizeValue(value) {
|
|
194
|
+
if (utils.isNullOrUndefined(value)) {
|
|
195
|
+
return { numberValue: null, stringValue: null };
|
|
196
|
+
}
|
|
197
|
+
if (utils.isBoolean(value)) {
|
|
198
|
+
return {
|
|
199
|
+
numberValue: value ? 1 : 0,
|
|
200
|
+
stringValue: null
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
204
|
+
return { numberValue: value, stringValue: null };
|
|
205
|
+
}
|
|
206
|
+
return { numberValue: null, stringValue: String(value) };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function normalizeUnsPath(value) {
|
|
210
|
+
if (utils.isNullOrUndefined(value)) {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
const normalized = String(value).trim();
|
|
214
|
+
return normalized.length ? normalized : null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
initPromise = this.init();
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
module.exports = {
|
|
221
|
+
create: function (data, logger, currentStorage) {
|
|
222
|
+
return new QuestDB(data, logger, currentStorage);
|
|
223
|
+
}
|
|
224
|
+
};
|
|
@@ -227,14 +227,14 @@ function DaqNode(_settings, _log, _id, _currentStorate) {
|
|
|
227
227
|
_checkDataWorking(false);
|
|
228
228
|
});
|
|
229
229
|
}
|
|
230
|
-
// check function to add daq data
|
|
230
|
+
// check function to add daq data
|
|
231
231
|
if (addDaqfnc.length > 0) {
|
|
232
232
|
if (_checkDataWorking(true)) {
|
|
233
233
|
Promise.all(addDaqfnc).then(result => {
|
|
234
234
|
// check db tokenizer after inserted
|
|
235
235
|
var lastts = 0 || result[0];
|
|
236
236
|
if (daqNextToken && daqNextToken < lastts) {
|
|
237
|
-
// close data DB, open and bind the new db, rename and move the closed db
|
|
237
|
+
// close data DB, open and bind the new db, rename and move the closed db
|
|
238
238
|
db_daqdata.close(function () {
|
|
239
239
|
var suffix = _getDateTimeSuffix(new Date());
|
|
240
240
|
var oldfile = db_daqdata_file;
|
|
@@ -262,6 +262,9 @@ function DaqNode(_settings, _log, _id, _currentStorate) {
|
|
|
262
262
|
}
|
|
263
263
|
_checkDataWorking(false);
|
|
264
264
|
});
|
|
265
|
+
} else {
|
|
266
|
+
// overload: attach no-op catch handlers to prevent UnhandledPromiseRejection
|
|
267
|
+
addDaqfnc.forEach(function (p) { p.catch(function () {}); });
|
|
265
268
|
}
|
|
266
269
|
}
|
|
267
270
|
if (dataToRestore.length && currentStorage) {
|
|
@@ -554,8 +557,8 @@ function DaqNode(_settings, _log, _id, _currentStorate) {
|
|
|
554
557
|
|
|
555
558
|
function _insertTagToMap(tagid, name, type) {
|
|
556
559
|
return new Promise(function (resolve, reject) {
|
|
557
|
-
var sqlRequest = "INSERT INTO data (id, name, type) ";
|
|
558
|
-
db_daqmap.run(sqlRequest
|
|
560
|
+
var sqlRequest = "INSERT INTO data (id, name, type) VALUES (?, ?, ?)";
|
|
561
|
+
db_daqmap.run(sqlRequest, [tagid, name, type], function (err) {
|
|
559
562
|
if (err !== null) {
|
|
560
563
|
reject(err);
|
|
561
564
|
} else {
|
|
@@ -615,10 +618,10 @@ function DaqNode(_settings, _log, _id, _currentStorate) {
|
|
|
615
618
|
function _insertTagValue(mapid, value) {
|
|
616
619
|
return new Promise(function (resolve, reject) {
|
|
617
620
|
var ts = new Date().getTime();
|
|
618
|
-
var sqlRequest = "INSERT INTO data (dt, id, value) ";
|
|
619
|
-
db_daqdata.run(sqlRequest
|
|
621
|
+
var sqlRequest = "INSERT INTO data (dt, id, value) VALUES (?, ?, ?)";
|
|
622
|
+
db_daqdata.run(sqlRequest, [ts, mapid, value], function (err) {
|
|
620
623
|
if (err !== null) {
|
|
621
|
-
reject();
|
|
624
|
+
reject(err);
|
|
622
625
|
}
|
|
623
626
|
else {
|
|
624
627
|
resolve(ts);
|
|
@@ -692,4 +695,4 @@ module.exports = {
|
|
|
692
695
|
return new DaqNode(data, logger, id, currentStorate);
|
|
693
696
|
},
|
|
694
697
|
checkRetention: checkRetention
|
|
695
|
-
};
|
|
698
|
+
};
|
|
@@ -3,11 +3,20 @@
|
|
|
3
3
|
let { options, connect } = require("@tdengine/rest");
|
|
4
4
|
let utils = require('../../utils');
|
|
5
5
|
|
|
6
|
+
function quoteTdIdentifier(value) {
|
|
7
|
+
return '`' + String(value).replace(/`/g, '``') + '`';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function escapeTdString(value) {
|
|
11
|
+
return String(value).replace(/'/g, "''");
|
|
12
|
+
}
|
|
13
|
+
|
|
6
14
|
function TDengine(_settings, _log, _currentStorage) {
|
|
7
15
|
let settings = _settings; // Application settings
|
|
8
16
|
const logger = _log; // Application logger
|
|
9
17
|
const currentStorage = _currentStorage; // Database to set the last value (current)
|
|
10
18
|
const database = settings.daqstore.database || 'fuxa';
|
|
19
|
+
const databaseRef = quoteTdIdentifier(database);
|
|
11
20
|
let conn;
|
|
12
21
|
|
|
13
22
|
this.setCall = function (_fncGetProp) {
|
|
@@ -25,8 +34,8 @@ function TDengine(_settings, _log, _currentStorage) {
|
|
|
25
34
|
const cursor = conn.cursor();
|
|
26
35
|
try {
|
|
27
36
|
//TODO add retention
|
|
28
|
-
let res = await cursor.query(`CREATE DATABASE IF NOT EXISTS ${
|
|
29
|
-
res = await cursor.query(`CREATE STABLE IF NOT EXISTS ${
|
|
37
|
+
let res = await cursor.query(`CREATE DATABASE IF NOT EXISTS ${databaseRef} `);
|
|
38
|
+
res = await cursor.query(`CREATE STABLE IF NOT EXISTS ${databaseRef}.meters (dt TIMESTAMP,tag_id VARCHAR(200), tag_value BINARY(20)) TAGS (device_id VARCHAR(200),device_name BINARY(256) )`);
|
|
30
39
|
|
|
31
40
|
} catch (error) {
|
|
32
41
|
console.error(error);
|
|
@@ -47,8 +56,13 @@ function TDengine(_settings, _log, _currentStorage) {
|
|
|
47
56
|
continue;
|
|
48
57
|
}
|
|
49
58
|
}
|
|
50
|
-
|
|
51
|
-
|
|
59
|
+
const safeDeviceId = quoteTdIdentifier(deviceId);
|
|
60
|
+
const safeDeviceTag = escapeTdString(deviceId);
|
|
61
|
+
const safeDeviceName = escapeTdString(deviceName);
|
|
62
|
+
const safeTagId = escapeTdString(tagid);
|
|
63
|
+
const safeTagValue = escapeTdString(tag.value);
|
|
64
|
+
let insertSql = `INSERT INTO ${databaseRef}.${safeDeviceId} USING ${databaseRef}.meters TAGS('${safeDeviceTag}','${safeDeviceName}')
|
|
65
|
+
VALUES (NOW, '${safeTagId}', '${safeTagValue}')`;
|
|
52
66
|
//async
|
|
53
67
|
|
|
54
68
|
cursor.query(insertSql).then((rst) => {
|
|
@@ -71,11 +85,12 @@ function TDengine(_settings, _log, _currentStorage) {
|
|
|
71
85
|
const cursor = conn.cursor()
|
|
72
86
|
let data = []
|
|
73
87
|
//add by J, the tagid is missed in the sql, should be one of the filter condition
|
|
88
|
+
const safeTagId = escapeTdString(tagid);
|
|
74
89
|
cursor.query(`SELECT CAST(dt as BIGINT) as dt, tag_value
|
|
75
|
-
FROM ${
|
|
76
|
-
WHERE tag_id = '${
|
|
77
|
-
and dt >= ${fromts}
|
|
78
|
-
and dt < ${tots} `).then((result) => {
|
|
90
|
+
FROM ${databaseRef}.meters
|
|
91
|
+
WHERE tag_id = '${safeTagId}'
|
|
92
|
+
and dt >= ${Number(fromts)}
|
|
93
|
+
and dt < ${Number(tots)} `).then((result) => {
|
|
79
94
|
// logger.debug(result)
|
|
80
95
|
result.getData().forEach((row) => {
|
|
81
96
|
data.push({ dt: row[0], value: row[1] })
|
|
@@ -105,4 +120,4 @@ module.exports = {
|
|
|
105
120
|
create: function (data, logger, currentStorage) {
|
|
106
121
|
return new TDengine(data, logger, currentStorage);
|
|
107
122
|
}
|
|
108
|
-
};
|
|
123
|
+
};
|