@frangoteam/fuxa
Version:
Web-based Process Visualization (SCADA/HMI/Dashboard) software
330 lines (301 loc) • 10.5 kB
JavaScript
/**
* 'FuxaServer': FUXA as device to use with the scripts
*/
;
const utils = require('../../utils');
const deviceUtils = require('../device-utils');
function FuxaServer(_data, _logger, _events) {
var data = JSON.parse(JSON.stringify(_data)); // Current Device data { id, name, tags, enabled, ... }
var logger = _logger;
var working = false; // Working flag to manage overloading polling and connection
var events = _events; // Events to commit change to runtime
var varsValue = {}; // Tags to send to frontend { id, type, value }
var lastTimestampValue; // Last Timestamp of values
var tagsMap = {}; // Map of tag id
var overloading = 0; // Overloading counter to mange the break connection
var tocheck = false; // Flag that define if there are tags to check by polling
var connectionTags = []; // Tags of connection status of devices
var type;
/**
* initialize the server device type
*/
this.init = function (_type) {
type = _type;
}
/**
* Connected with itself
*/
this.connect = function () {
return new Promise(async function (resolve, reject) {
resolve();
});
}
/**
* Disconnect with itself
* Clear all Tags values
*/
this.disconnect = function () {
return new Promise(function (resolve, reject) {
_clearVarsValue();
resolve(true);
});
}
/**
* Read values in polling mode
* Update the tags values list, save in DAQ if value changed or in interval and emit values to clients
*/
this.polling = async function () {
if (_checkWorking(true)) {
try {
if (tocheck) {
var varsValueChanged = await _checkVarsChanged();
lastTimestampValue = new Date().getTime();
_emitValues(varsValue);
if (this.addDaq && !utils.isEmptyObject(varsValueChanged)) {
this.addDaq(varsValueChanged, data.name, data.id);
}
}
_checkConnectionStatus();
} catch (err) {
logger.error(`'${data.name}' polling error: ${err}`);
}
_checkWorking(false);
}
}
/**
* Load Tags attribute to read with polling
*/
this.load = function (_data) {
varsValue = [];
data = JSON.parse(JSON.stringify(_data));
tagsMap = {};
var count = Object.keys(data.tags).length;
connectionTags = [];
for (var id in data.tags) {
tagsMap[id] = data.tags[id];
const dataTag = data.tags[id];
const shouldRestore = dataTag.daq && dataTag.daq.restored;
if (shouldRestore) {
data.tags[id].value = null;
} else if (dataTag.init !== undefined && dataTag.init !== null && dataTag.init !== '') {
data.tags[id].value = deviceUtils.parseValue(dataTag.init, dataTag.type);
} else {
if (dataTag.type === 'boolean') {
data.tags[id].value = false;
} else if (dataTag.type === 'number') {
data.tags[id].value = 0;
} else if (dataTag.type === 'string') {
data.tags[id].value = '';
} else {
data.tags[id].value = null;
}
}
if (dataTag.sysType === TagSystemTypeEnum.deviceConnectionStatus) {
data.tags[id].timestamp = Date.now();
connectionTags.push(data.tags[id]);
}
varsValue[id] = data.tags[id];
}
tocheck = !utils.isEmptyObject(data.tags);
logger.info(`'${data.name}' data loaded (${count})`, true);
}
/**
* Return Tags values array { id: <tagId>, value: <value> }
*/
this.getValues = function () {
return data.tags;
}
/**
* Return Tag value { id: <tagId>, value: <value>, ts: <lastTimestampValue> }
*/
this.getValue = function (id) {
if (varsValue[id]) {
return { id: id, value: varsValue[id].value, ts: lastTimestampValue };
}
return null;
}
/**
* Return connection status FUXA server is always connected, 'connect-ok'
*/
this.getStatus = function () {
return 'connect-ok';
}
/**
* Return Tag property to show in frontend
*/
this.getTagProperty = function (id) {
if (data.tags[id]) {
return { id: id, name: data.tags[id].name, type: data.tags[id].type, format: data.tags[id].format };
} else {
return null;
}
}
/**
* Set the Tag value to device
*/
this.setValue = function (id, value) {
if (varsValue[id]) {
var val = _parseValue(value, varsValue[id].type);
varsValue[id].value = val;
varsValue[id].changed = true;
logger.info(`'${data.name}' setValue(${id}, ${value})`, true, true);
return true;
}
return false;
}
/**
* Set the connection status to tag of device sttus
* @param {*} deviceId
* @param {*} status
*/
this.setConnectionStatus = function(deviceId, status) {
var tag = connectionTags.find(tag => tag.memaddress === deviceId);
if (tag) {
tag.value = status;
tag.timestamp = Date.now();
}
}
/**
* Return connected with itself
*/
this.isConnected = function () {
return true;
}
/**
* Bind the DAQ store function
*/
this.bindAddDaq = function (fnc) {
this.addDaq = fnc; // Add the DAQ value to db history
}
this.addDaq = null;
/**
* Return the timestamp of last read tag operation on polling
* @returns
*/
this.lastReadTimestamp = () => {
return lastTimestampValue;
}
/**
* Return the Daq settings of Tag
* @returns
*/
this.getTagDaqSettings = (tagId) => {
return data.tags[tagId] ? data.tags[tagId].daq : null;
}
/**
* Set Daq settings of Tag
* @returns
*/
this.setTagDaqSettings = (tagId, settings) => {
if (data.tags[tagId]) {
utils.mergeObjectsValues(data.tags[tagId].daq, settings);
}
}
/**
* Cheack and parse the value return converted value
* @param {*} value as string
*/
var _parseValue = function (value, type) {
if (type === 'number') {
return parseFloat(value);
} else if (type === 'boolean') {
// Handle null, undefined, empty string cases
if (value === null || value === undefined || value === '') {
return false; // Default to false for safety
}
if (typeof value === 'string') {
// Properly handle boolean string values
const lowerValue = value.toLowerCase().trim();
return lowerValue === 'true' || lowerValue === '1';
}
return Boolean(value);
} else if (type === 'string') {
return value;
} else {
let val = parseFloat(value);
if (Number.isNaN(val)) {
// maybe boolean
val = Number(value);
// maybe string
if (Number.isNaN(val)) {
val = value;
}
} else {
val = parseFloat(val.toFixed(5));
}
return val;
}
}
/**
* Clear Tags value
*/
var _clearVarsValue = function () {
for (var id in varsValue) {
varsValue[id].value = null;
}
_emitValues(varsValue);
}
/**
* Return the Tags that have value changed and clear value changed flag of all Tags
*/
var _checkVarsChanged = async () => {
const timestamp = new Date().getTime();
var result = {};
for (var id in data.tags) {
if (!utils.isNullOrUndefined(data.tags[id].value)) {
data.tags[id].value = await deviceUtils.tagValueCompose(data.tags[id].value, varsValue[id] ? varsValue[id].value : null, data.tags[id]);
data.tags[id].timestamp = timestamp;
if (this.addDaq && deviceUtils.tagDaqToSave(data.tags[id], timestamp)) {
result[id] = data.tags[id];
}
}
data.tags[id].changed = false;
varsValue[id] = data.tags[id];
}
return result;
}
/**
* Emit the Tags values array { id: <name>, value: <value>, type: <type> }
* @param {*} values
*/
var _emitValues = function (values) {
events.emit('device-value:changed', { id: data.name, values: values });
}
/**
* Used to manage the async connection and polling automation (that not overloading)
* @param {*} check
*/
var _checkWorking = function (check) {
if (check && working) {
overloading++;
logger.warn(`'${data.name}' working (connection || polling) overload! ${overloading}`);
// !The driver don't give the break connection
if (overloading >= 3) {
disconnect();
} else {
return false;
}
}
working = check;
overloading = 0;
return true;
}
var _checkConnectionStatus = function () {
var dt = Date.now() - 60000;
connectionTags.forEach(tag => {
if (tag.value && tag.timestamp < dt) {
tag.value = 0;
}
});
}
}
module.exports = {
init: function (settings) {
},
create: function (data, logger, events) {
return new FuxaServer(data, logger, events);
}
}
var TagSystemTypeEnum = {
deviceConnectionStatus: 1,
}