@frangoteam/fuxa
Version:
Web-based Process Visualization (SCADA/HMI/Dashboard) software
632 lines (595 loc) • 26.6 kB
JavaScript
/**
* Module to manage the DAQ node with sqlite (a DaqNode per Device)
* There are 2 database:
* 1 to map all Tags/Nodes with map-id, Tag/Node-id, name and type.
* 2 to save the Tags/Nodes values with timestamp, map-id and value.
* The values have a Timestamp resolution of Seconds
*/
;
const fs = require('fs');
const path = require('path');
var sqlite3 = require('sqlite3').verbose();
const db_daqdata_prefix = 'daq-data_';
const db_daqmap_prefix = 'daq-map_';
const db_daqtoken = 3600000; // 1 hour
function DaqNode(_settings, _log, _id) {
var settings = _settings; // Application settings
var logger = _log; // Application logger
var id = _id; // Device id
var initready = false; // Initilized flag
var mapworking = false; // Data mapping working flag
var dataworking = false; // Save data working flag
var db_daqmap; // Database of mapped data
var db_daqdata; // Database of data
var daqTagsMap = {}; // Mapped db Tags/Nodes
var daqNextToken = new Date().getTime();// Interval to split Database data
daqNextToken += (settings.daqTokenizer) ? db_daqtoken * settings.daqTokenizer : db_daqtoken * 24;
if (settings.daqTokenizer === 0) {
daqNextToken = 0;
} else {
// check database pending
var pendsfile = path.join(settings.dbDir, db_daqdata_prefix + id + '_');
var pendsfiles = fs.readdirSync(settings.dbDir);
for (var i in pendsfiles) {
var filePath = path.join(settings.dbDir, pendsfiles[i]);
if (filePath.indexOf(pendsfile) === 0) {
_checkToArchiveDBfile(filePath);
}
}
}
// define file database
var suffix = (daqNextToken) ? _getDateTimeSuffix(new Date()) : '';
var db_daqdata_file = path.join(settings.dbDir, db_daqdata_prefix + id + '_' + suffix + '.db');
var db_daqmap_file = path.join(settings.dbDir, db_daqmap_prefix + id + '.db');
var db_daqmap_exists = require('fs').existsSync(db_daqmap_file);
var db_daqdata_exists = require('fs').existsSync(db_daqdata_file);
if (!db_daqmap_exists && db_daqdata_exists) {
// reset daq db
_resetDB(db_daqdata_file);
}
// bind database daqdata
_bindDaqData(db_daqdata_file).then(result => {
logger.info(`daqstorage.connected-to-data '${db_daqdata_file}' database`, true);
db_daqdata = result;
}).catch(function (err) {
if (err) {
logger.error(`daqstorage.connected-to-data failed! '${id}' ${err}`);
}
});
// bind database daqmap
_bindDaqMap(db_daqmap_file).then(result => {
logger.info(`daqstorage.connected-to-map '${db_daqmap_file}' database`, true);
db_daqmap = result;
_loadMap().then(result => {
logger.info(`daqstorage.load-map successful! '${id}'`, true);
initready = true;
}).catch(function (err) {
if (err) {
logger.error(`daqstorage.load-map failed! '${id}' ${err}`);
}
});
}).catch(function (err) {
if (err) {
logger.error(`daqstorage.bind-map failed! '${id}' ${err}`);
}
});
/**
* Close data and map database
*/
this.close = function () {
if (db_daqmap) {
db_daqmap.close();
}
if (db_daqdata) {
db_daqdata.close();
}
logger.info(`daqstorage.closed successful! '${id}'`, true);
}
/**
* Set function callback to get Tag/Node property and return function reference to set value
*/
this.setCall = function (fncgetprop) {
fncGetTagProp = fncgetprop;
return this.addDaqValues;
}
var fncGetTagProp = null;
/**
* Add Daq value of Tag/Node
* Check if the Tag/Node is mapped otherwise first add the value to data db then check if the db is to split and to archive
*/
this.addDaqValue = function (tagid, tagvalue) {
if (initready) {
if (!daqTagsMap[tagid]) {
// tags is not mapped
var prop = fncGetTagProp(tagid);
if (prop && _checkMapWorking(true)) {
_insertTagToMap(prop.id, prop.name, prop.type).then(function (result) {
_getTagMap(prop.id, prop.name, prop.type).then(function (result) {
_addTagMap(result.mapid, prop.id, prop.name);
}).catch(function (err) {
logger.error(`daqstorage.add-daq-value _getTagMap failed! '${id}' ${err}`);
});
_checkMapWorking(false);
}).catch(function (err) {
_checkMapWorking(false);
logger.error(`daqstorage.add-daq-value _insertTagToMap failed! '${id}' ${err}`);
});
}
} else {
if (_checkDataWorking(true)) {
// check if db_daqdata are bindet
if (db_daqdata) {
_insertTagValue(daqTagsMap[tagid].mapid, tagvalue).then(function (lastts) {
// check db tokenizer after inserted
if (daqNextToken && daqNextToken < lastts) {
// close data DB, open and bind the new db, rename and move the closed db
db_daqdata.close(function () {
var suffix = _getDateTimeSuffix(new Date());
var oldfile = db_daqdata_file;
db_daqdata_file = path.join(settings.dbDir, db_daqdata_prefix + id + '_' + suffix + '.db');
db_daqdata = null;
_bindDaqData(db_daqdata_file).then(result => {
logger.info(`daqstorage.add-daq-value _bindDaqData '${id}' database`, true);
daqNextToken += (settings.daqTimeToken) ? db_daqtoken * settings.daqTimeToken : db_daqtoken;
db_daqdata = result;
_archiveDBfile(oldfile, lastts);
_checkDataWorking(false);
}).catch(function (err) {
_checkDataWorking(false);
logger.error(`daqstorage.add-daq-value _bindDaqData failed! '${id}' ${err}`);
});
});
} else {
_checkDataWorking(false);
}
}).catch(function (err) {
_checkDataWorking(false);
});
} else {
// some things was wrong by tokenize...try to bind the db_daqdata
_bindDaqData(db_daqdata_file).then(result => {
logger.info(`daqstorage.add-daq-value _bindDaqData '${db_daqmap_file}' database`, true);
db_daqdata = result;
_checkDataWorking(false);
}).catch(function (err) {
_checkDataWorking(false);
logger.error(`daqstorage.add-daq-value _bindDaqData failed! '${id}' ${err}`);
});
}
}
}
}
}
/**
* Add Daq value of Tag/Node
* Check if the Tag/Node is mapped otherwise first add the value to data db then check if the db is to split and to archive
*/
this.addDaqValues = function (tags, tagid, tagvalue) {
if (initready) {
if (db_daqdata) {
var addMapfnc = [];
var addDaqfnc = [];
// prepare functions
for (var tagid in tags) {
if (!tags[tagid].daq || !tags[tagid].daq.enabled) {
continue;
}
if (!daqTagsMap[tagid]) {
var prop = fncGetTagProp(tagid);
if (prop) {
addMapfnc.push(_insertTagToMap(prop.id, prop.name, prop.type));
}
} else {
addDaqfnc.push(_insertTagValue(daqTagsMap[tagid].mapid, tags[tagid].value));
}
}
// check function to insert in map
if (addMapfnc.length > 0) {
Promise.all(addMapfnc).then(result => {
logger.info(`daqstorage.add-daq-values _insertTagToMap '${id}' ${result}`, true);
for (var idx = 0; idx < result.length; idx++) {
_getTagMap(result[idx]).then(function (result) {
_addTagMap(result.mapid, result.id, result.name);
}).catch(function (err) {
logger.error(`daqstorage.add-daq-value _getTagMap failed! '${id}' ${err}`);
});
}
}, reason => {
if (reason && reason.stack) {
logger.error(`daqstorage.add-daq-value _insertTagToMap failed! '${id}' ${reason.stack}`);
} else {
logger.error(`daqstorage.add-daq-value _insertTagToMap failed! '${id}' ${reason}`);
}
_checkDataWorking(false);
});
}
// check function to add daq data
if (addDaqfnc.length > 0) {
if (_checkDataWorking(true)) {
Promise.all(addDaqfnc).then(result => {
// check db tokenizer after inserted
var lastts = 0 || result[0];
if (daqNextToken && daqNextToken < lastts) {
// close data DB, open and bind the new db, rename and move the closed db
db_daqdata.close(function () {
var suffix = _getDateTimeSuffix(new Date());
var oldfile = db_daqdata_file;
db_daqdata_file = path.join(settings.dbDir, db_daqdata_prefix + id + '_' + suffix + '.db');
db_daqdata = null;
_bindDaqData(db_daqdata_file).then(result => {
logger.info(`daqstorage.add-daq-values '${db_daqmap_file}' database`, true);
daqNextToken += (settings.daqTimeToken) ? db_daqtoken * settings.daqTimeToken : db_daqtoken;
db_daqdata = result;
_archiveDBfile(oldfile, lastts);
_checkDataWorking(false);
}).catch(function (err) {
_checkDataWorking(false);
logger.error(`daqstorage.add-daq-values _bindDaqData failed! '${id}' ${err}`);
});
});
} else {
_checkDataWorking(false);
}
}, reason => {
if (reason && reason.stack) {
logger.error(`daqstorage.add-daq-values addDaqfnc failed! '${id}' ${reason.stack}`);
} else {
logger.error(`daqstorage.add-daq-values addDaqfnc failed! '${id}' ${reason}`);
}
_checkDataWorking(false);
});
}
}
} else {
// some things was wrong by tokenize...try to bind the db_daqdata
_bindDaqData(db_daqdata_file).then(result => {
logger.info(`daqstorage.add-daq-values '${db_daqmap_file}' database`, true);
db_daqdata = result;
_checkDataWorking(false);
}).catch(function (err) {
_checkDataWorking(false);
logger.error(`daqstorage.add-daq-values _bindDaqData failed! '${id}' ${err}`);
});
}
}
}
/**
* Return Tags/Nodes map list
*/
this.getDaqMap = function () {
return daqTagsMap;
}
/**
* Return array of timeserie <timestamp, value>
*/
this.getDaqValue = function (tagid, fromts, tots) {
return new Promise(function (resolve, reject) {
if (daqTagsMap[tagid]) {
var result = [];
// search in current db
_getTagValues(db_daqdata, daqTagsMap[tagid].mapid, fromts, tots).then(function (rows) {
// search in archive
var archivefiles = _getArchiveFiles(id, fromts, tots);
var dbfncs = [];
archivefiles.forEach(file => {
dbfncs.push(_bindAndGetTagValues(file, daqTagsMap[tagid].mapid, fromts, tots));
});
Promise.all(dbfncs).then(values => {
for (var i = 0; i < values.length; i++) {
result = result.concat(values[i]);
}
result = result.concat(rows);
resolve(result);
}, reason => {
if (reason.stack) {
logger.error(`daqstorage.get-daq-value failed! '${id}' ${reason.stack}`);
} else {
logger.error(`daqstorage.get-daq-value failed! '${id}' ${reason}`);
}
reject(reason);
});
}).catch(function (err) {
logger.error(`daqstorage.get-daq-value _getTagValues failed! '${id}' ${err}`);
reject(err);
});
} else {
reject('tag id ' + tagid + ' not found!');
}
});
}
/**
* Used to manage the async add Tag/Node in map db (that not overloading)
* @param {*} check
*/
var _checkMapWorking = function (check) {
if (check && mapworking) {
logger.warn(`daqstorage.mapping-overload! '${id}'`);
return false;
}
mapworking = check;
return true;
}
/**
* Used to manage the async add Tag/Node in data db (that not overloading)
* @param {*} check
*/
var _checkDataWorking = function (check) {
if (check && dataworking) {
logger.warn(`daqstorage.data-overload! '${id}'`);
return false;
}
dataworking = check;
return true;
}
/**
* Bind the map database by create the table if not exist
* @param {*} dbfile
*/
function _bindDaqMap(dbfile) {
return new Promise(function (resolve, reject) {
try {
var db = new sqlite3.Database(dbfile, function (err) {
if (err) {
logger.error(`daqstorage.bind-daq-map error! '${id}' ${err}`);
reject();
}
logger.info(`daqstorage.bind-daq-map '${dbfile}' database`, true);
});
db.serialize(function () {
db.run("CREATE TABLE if not exists data (mapid INTEGER PRIMARY KEY AUTOINCREMENT, id TEXT, name TEXT, type TEXT)", function (err) {
if (err) {
logger.error(`daqstorage.bind-daq-map-to-create-table error! '${id}' ${err}`);
reject();
} else {
resolve(db);
}
});
});
} catch (err) {
reject(err);
}
});
}
/**
* Bind the data database by create the table if not exist
* @param {*} dbfile
*/
function _bindDaqData(dbfile) {
return new Promise(function (resolve, reject) {
try {
var db = new sqlite3.Database(dbfile, function (err) {
if (err) {
logger.error(`daqstorage.bind-daq-data error! '${id}' ${err}`);
reject();
}
logger.info(`daqstorage.bind-daq-data '${dbfile}' database`, true);
});
db.serialize(function () {
db.run("CREATE TABLE if not exists data (dt INTEGER, id INTEGER, value TEXT)", function (err) {
if (err) {
logger.error(`daqstorage.bind-daq-data-to-create-table error! '${id}' ${err}`);
reject();
} else {
resolve(db);
}
});
});
} catch (err) {
reject(err);
}
});
}
/**
* Move the db file to archive folder.
* Open the db take the last data and rename the file with the start and last data time
* @param {*} dbfile
*/
function _checkToArchiveDBfile(dbfile) {
if (dbfile.indexOf('-journal') !== -1) {
// delete pending db journal
fs.unlinkSync(dbfile);
} else {
_bindDaqData(dbfile).then(result => {
logger.info(`daqstorage.check-to-archive-db-file '${dbfile}'`, true);
_getLastTagValue(result).then(lastrow => {
result.close(function () {
// rename and move to archive
if (lastrow) {
var filearchived = _archiveDBfile(dbfile, lastrow.dt);
logger.info(`daqstorage.check-to-archive-db-file '${filearchived}'`, true);
} else {
// delete void db
try {
fs.unlinkSync(dbfile);
logger.info(`daqstorage.check-to-archive-db-file '${dbfile}' database deleted!`, true);
} catch (e) { }
}
});
}).catch(function (err) {
logger.error(`daqstorage.check-to-archive-db-file error! '${id}' ${err}`);
});
}).catch(function (err) {
logger.error(`daqstorage.check-to-archive-db-file error! '${id}' ${err}`);
});
}
}
function _getArchiveFiles(id, fromts, tots) {
var archive = path.resolve(settings.dbDir, 'archive');
var result = [];
if (fs.existsSync(archive)) {
fs.readdirSync(archive).forEach(file => {
var ranges = file.split('_');
if (ranges.length >= 3) {
var fr = ranges[ranges.length - 2];
var to = ranges[ranges.length - 1];
var f = _suffixToTimestamp(fr);
var t = _suffixToTimestamp(to);
if (file.indexOf(id) > 0 && f <= tots && t >= fromts) {
result.push(path.join(archive, file));
}
}
});
result.sort();
}
// result.reverse();
return result;
}
function _archiveDBfile(dbfile, ts) {
var suffix = _getDateTimeSuffix(new Date(ts));
var archive = path.resolve(settings.dbDir, 'archive');
if (!fs.existsSync(archive)) {
fs.mkdirSync(archive);
}
var dbfilenew = path.join(archive, path.basename(dbfile, path.extname(dbfile))) + '_' + suffix + path.extname(dbfile);
fs.renameSync(dbfile, dbfilenew);
return dbfilenew;
}
function _getDateTimeSuffix(dt) {
var yyyy = dt.getFullYear();
var mm = dt.getMonth() + 1;
var dd = dt.getDate();
if (dd < 10) {
dd = '0' + dd;
}
if (mm < 10) {
mm = '0' + mm;
}
var HH = dt.getHours();
var MM = dt.getMinutes();
var SS = dt.getSeconds();
if (HH < 10) {
HH = '0' + HH;
}
if (MM < 10) {
MM = '0' + MM;
}
if (SS < 10) {
SS = '0' + SS;
}
return '' + yyyy + mm + dd + HH + MM + SS;
}
function _suffixToTimestamp(dt) {
if (dt.length >= 14) {
var yyyy = parseInt(dt.substring(0, 4));
var mm = parseInt(dt.substring(4, 6)) - 1;
var dd = parseInt(dt.substring(6, 8));
var HH = parseInt(dt.substring(8, 10));
var MM = parseInt(dt.substring(10, 12));
var SS = parseInt(dt.substring(12, 14));
return new Date(yyyy, mm, dd, HH, MM, SS).getTime();
}
return null;
}
function _resetDB(file) {
fs.unlinkSync(file);
}
function _loadMap() {
daqTagsMap = {};
return new Promise(function (resolve, reject) {
db_daqmap.all("SELECT mapid, id, name, type FROM data", function (err, rows) {
if (err) {
reject(err);
}
else if (rows) {
for (var idx = 0; idx < rows.length; idx++) {
var row = rows[idx];
_addTagMap(row.mapid, row.id, row.name);
// cfg.push({ id: row.mapid, name: row.name, interval_range: row.interval_range, interval: row.interval, sample_range: row.sample_range, samples: row.samples, enabled: row.enabled, divisor: row.divisor, value: null });
}
}
resolve(true);
});
});
}
function _getTagMap(id) {
return new Promise(function (resolve, reject) {
var sql = "SELECT mapid, id, name, type FROM data WHERE id = ?";
db_daqmap.get(sql, [id], function (err, row) {
if (err) {
reject(err);
} else {
resolve(row);
}
});
});
}
function _insertTagToMap(tagid, name, type) {
return new Promise(function (resolve, reject) {
var sqlRequest = "INSERT INTO data (id, name, type) ";
db_daqmap.run(sqlRequest + "VALUES('" + tagid + "','" + name + "','" + type + "'); SELECT last_insert_rowid() FROM data", function (err) {
if (err !== null) {
reject(err);
} else {
resolve(tagid);
}
});
});
}
function _getLastTagValue(db) {
return new Promise(function (resolve, reject) {
var sql = "SELECT dt FROM data ORDER BY dt DESC LIMIT 1";
db.get(sql, [], function (err, row) {
if (err) {
console.error(err);
reject(err);
} else {
resolve(row);
}
});
});
}
function _bindAndGetTagValues(dbfile, id, fromts, tots) {
return new Promise(function (resolve, reject) {
_bindDaqData(dbfile).then(result => {
_getTagValues(result, id, fromts, tots).then(rows => {
result.close();
resolve(rows);
}).catch(function (err) {
reject(err);
});
}).catch(function (err) {
reject(err);
});
});
}
function _getTagValues(db, id, fromts, tots) {
return new Promise(function (resolve, reject) {
var sql = "SELECT dt, value FROM data WHERE id = ? AND dt BETWEEN ? and ? ORDER BY dt ASC";
db.all(sql, [id, fromts, tots], function (err, rows) {
if (err) {
console.error(err);
reject(err);
} else {
resolve(rows);
}
});
});
}
function _insertTagValue(mapid, value) {
return new Promise(function (resolve, reject) {
var ts = new Date().getTime();
var sqlRequest = "INSERT INTO data (dt, id, value) ";
db_daqdata.run(sqlRequest + "VALUES('" + ts + "','" + mapid + "','" + value + "')", function (err) {
if (err !== null) {
reject();
}
else {
resolve(ts);
}
});
});
}
function _addTagMap(mapid, id, name) {
if (daqTagsMap[id]) {
return false;
}
daqTagsMap[id] = { mapid: mapid, name: name };
return true;
}
return true;
}
module.exports = {
create: function (data, logger, events) {
return new DaqNode(data, logger, events);
}
};