obcon-scada / app /include /global_utils.js
chanmin0723's picture
Initial obcon SCADA deploy
e4bf523
Raw
History Blame Contribute Delete
25 kB
'use strict'
/**
* Copyright (c) 2017~2020, OBCon Inc.
* All rights reserved.
*/
/**
* @file
* @copyright 2017~2020, OBCon Inc.
* @author gye hyun james kim [pnuskgh@gmail.com]
*/
let fs = require('fs');
let path = require('path');
let crypto = require('crypto');
let moment = require('moment'); //--- format : YYYYMMDDHHmmss.SSS ZZ - 20191220172919.083 +0900
//--- Full Path를 반환 한다.
function getPath() {
return path.resolve(config.folder_root, path.join.apply(this, arguments));
}
//--- Custom Path를 반환 한다.
function getCustomPath() {
let filePath = path.join.apply(this, arguments);
let files = [
getPath('custom', config.service.name, filePath),
getPath('custom', filePath),
getPath(filePath)
];
let rtFile = getPath(filePath);
for (let idx = 0; idx < files.length; idx++) {
if (fs.existsSync(files[idx])) {
rtFile = files[idx];
break;
}
}
return path.resolve(rtFile);
}
function existFile() {
let filePath = path.join.apply(this, arguments);
let files = [
getPath('custom', config.service.name, filePath),
getPath('custom', filePath),
getPath(filePath)
];
let rtFlag = false;
for (let idx = 0; idx < files.length; idx++) {
if (fs.existsSync(files[idx])) {
rtFlag = true;
break;
}
}
return rtFlag;
}
function getClass() {
let filePath = getCustomPath.apply(this, arguments);
return (fs.existsSync(filePath)) ? require(filePath):null;
}
function getModule() {
let moduleClass = getClass.apply(this, arguments);
return (moduleClass == null) ? null:new moduleClass();
}
// function getSettingModule() {
// let filePath = getCustomPath.apply(this, arguments);
// let moduleClass = require(filePath);
// return new moduleClass();
// }
//--- 전체 URL을 반환 한다.
function getFullUrl(url='') {
// let rturl = config.http.baseUrl + config.http.path.substring(1);
return (url == '') ? config.http.baseUrl:config.http.baseUrl + url.substring(1);
}
function getTheme(req) {
let theme = JSON.parse(JSON.stringify(config.theme));
//--- Nginx에서 호출되는 경우 사용
// if (req.headers.hasOwnProperty('x-forwarded-for')) {
// theme.templateBase = '/themes/' + config.theme.name + '/public';
// } else {
// theme.templateBase = '';
// }
theme['http'] = config.http;
return theme;
}
//--- To-Do : 향후 site, user별 설정을 가져 간다.
//--- Theme Customize를 위한 함수 : getTemplatePath()
//--- modbus_ws_app.js의 app.set('views', getPath('themes'));와 app.use(express.static(getPath('themes'))); 참조
//--- app.use(express.static(getPath('themes'))); 설정에 의해 themes/ 폴더가 기본 폴더가 됨
function _getTemplateFile() {
let filePath = path.join.apply(this, arguments);
let files = [
getPath('themes', config.service.name, config.theme.name, filePath),
getPath('themes', config.service.name, config.theme.defaultName, filePath),
getPath('themes', config.theme.name, filePath),
getPath('themes', config.theme.defaultName, filePath)
];
let urls = [
path.join(config.service.name, config.theme.name, filePath),
path.join(config.service.name, config.theme.defaultName, filePath),
path.join(config.theme.name, filePath),
path.join(config.theme.defaultName, filePath)
];
// if (-1 < filePath.indexOf('spaAll')) {
// console.log('filePath', filePath);
// console.log('getTemplateFile', files);
// }
for (let idx = 0; idx < files.length; idx++) {
if (fs.existsSync(files[idx])) {
// if (-1 < filePath.indexOf('spaAll')) {
// console.log(urls[idx]);
// }
return urls[idx].replace(/\\/g, '/');
}
}
return path.join('themes', config.theme.defaultName, filePath).replace(/\\/g, '/');
}
function getTemplateFile() {
return '/' + _getTemplateFile.apply(this, arguments);
}
//--- ejs에서 파일을 링크하기 위해서 사용 한다.
function getTemplateFile2() {
if ((arguments.length == 1) && (arguments[0].startsWith('http'))) {
return arguments[0];
}
return config.http.path + '/' + _getTemplateFile.apply(this, arguments);
}
//--- app.set('views', getPath('themes')); 설정에 의해 theme/ 폴더가 기본 폴더가 됨
function getTemplateEjs() {
let file = _getTemplateFile.apply(this, arguments);
return file;
// return file.substr(0, file.length - 4);
}
function stringFormat(valStr, len, pad=' ') {
let rtStr = valStr;
while (rtStr.length < len) {
rtStr = pad + rtStr;
}
return rtStr;
}
function stringLength(str) {
let len = 0;
for (let idx = 0; idx < str.length; idx++) {
if (escape(str.charAt(idx)).length == 6) {
len++;
}
len++;
}
return len;
}
function integerFormat(valInt, len, pad=' ', abs=true) {
let rtStr = valInt.toString();
if (-1 < rtStr.indexOf('.')) {
rtStr = rtStr.split('.')[0];
}
while (rtStr.length < len) {
rtStr = pad + rtStr;
}
if ((abs) && (len < rtStr.length)) {
rtStr = rtStr.slice(rtStr.length - len, rtStr.length);
}
return rtStr;
}
//--- To-Do : 향후 개선 한다.
//--- abs : true. 소수점 아래와 소수점 위의 길이를 지킨다.
function floatFormat(valFloat, lenFirst=5, lenSecond=2, padFirst=' ', padSecond='0', abs=true) {
let rtStr = valFloat.toString();
let tmpFirst = null;
let tmpSecond = null;
if (-1 < rtStr.indexOf('.')) {
let items = rtStr.split('.');
tmpFirst = items[0];
tmpSecond = items[1];
if (abs) {
if (lenSecond < tmpSecond.length) {
tmpSecond = tmpSecond.slice(0, lenSecond);
}
}
} else {
tmpFirst = rtStr;
tmpSecond = '';
}
while (tmpSecond.length < lenSecond) {
tmpSecond = tmpSecond + padSecond;
}
rtStr = tmpFirst + '.' + tmpSecond;
while (rtStr.length < (lenFirst + 1 + lenSecond)) {
rtStr = padFirst + rtStr;
}
if ((abs) && ((lenFirst + 1 + lenSecond) < rtStr.length)) {
rtStr = rtStr.slice(rtStr.length - (lenFirst + 1 + lenSecond), rtStr.length);
}
return rtStr;
}
//--- 소수점 아래 자리만 확정 한다.
function floatFormat2(valFloat, lenSecond=2, padSecond='0') {
let rtStr = valFloat.toString();
let tmpFirst = null;
let tmpSecond = null;
if (-1 < rtStr.indexOf('.')) {
let items = rtStr.split('.');
tmpFirst = items[0];
tmpSecond = items[1];
if (lenSecond < tmpSecond.length) {
tmpSecond = tmpSecond.slice(0, lenSecond);
}
} else {
tmpFirst = rtStr;
tmpSecond = '';
}
while (tmpSecond.length < lenSecond) {
tmpSecond = tmpSecond + padSecond;
}
rtStr = tmpFirst + '.' + tmpSecond;
return rtStr;
}
function getHash(plainText) {
let hash = crypto.createHash('md5');
hash.update(plainText);
return hash.digest('hex').toLowerCase();
}
//--- string, number, bigint, boolean, function, undefined, symbol
//--- object : null, object, array
function obconTypeof(param) {
return Object.prototype.toString.call(param).replace(/\[object /g, '').replace(/\]/g, '').toLowerCase();
}
function Null(value) {
if (isNone(value)) {
return '';
} else if (value == null) {
return '';
}
return value;
}
//--- typeof : string (String), bigint (BigInt), number (Number), boolean (Boolean), object (Null), symbol (Symbol), function, undefined (Undefined)
function isNone(param1) {
return typeof(param1) === 'undefined';
}
function isNull(param1) {
return param1 == null;
}
function isString(param1) {
return typeof(param1) == 'string';
}
function isNumber(param1) {
return typeof(param1) == 'number';
}
function isArray(param1) {
return obconTypeof(param1) == 'array';
}
function existField(paramObject, paramField) {
if ((paramObject == null) || (typeof(paramObject) == 'undefined')) {
return false;
}
return typeof(paramObject[paramField]) != 'undefined';
// return Object.getOwnPropertyNames(paramObject).includes(paramField);
// return -1 < Object.getOwnPropertyNames(paramObject).indexOf(paramField);
}
function existIdxField(paramObject, paramField) {
if ((paramObject == null) || (typeof(paramObject) == 'undefined')) {
return false;
}
return typeof(paramObject[`idx_${paramField}`]) != 'undefined';
// return Object.getOwnPropertyNames(paramObject).includes(paramField);
// return -1 < Object.getOwnPropertyNames(paramObject).indexOf(paramField);
}
function getObjectValue(paramObject, paramField, paramDefault) {
return existField(paramObject, paramField) ? paramObject[paramField]:paramDefault;
}
function deepCopy(obj) {
return JSON.parse(JSON.stringify(obj));
}
function randomInteger(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function randomFloat(min, max) {
return Math.random() * (max - min) + min;
}
//--- pos : 1(첫번째 문자), 2(두번째 문자), ..., 32
let maxPos = 32;
function initFlagString(source) {
let tmpStr = source + '0000000000000000000000000000000000000000';
return tmpStr.substring(0, maxPos);
}
function getFlagString(source, pos) {
let tmpStr = initFlagString(source);
tmpStr = tmpStr.substring(pos - 1, pos);
return tmpStr;
}
function setFlagString(source, pos, set) {
let tmpStr = initFlagString(source);
tmpStr = tmpStr.substring(0, pos - 1) + set + tmpStr.substring(pos, maxPos);
return tmpStr;
}
//--- pos : 1(일자리), 2(십자리), ..., 32
function getFlagNumber(source, pos) {
let tmpStr = '0000000000000000' + Math.abs(source);
tmpStr = tmpStr.substring(tmpStr.length - 16);
tmpStr = tmpStr.substring(16 - pos, 16 - pos + 1);
return parseInt(tmpStr);
}
function setFlagNumber(source, pos, set) {
let tmpStr = '0000000000000000' + Math.abs(source);
tmpStr = tmpStr.substring(tmpStr.length - 16);
tmpStr = tmpStr.substring(0, 16 - pos) + set + tmpStr.substring(16 - pos + 1);
return parseInt(tmpStr);
}
function checkHacking(param) {
if (!param) {
return false;
}
if ((-1 < param.indexOf(';')) || (-1 < param.indexOf('\''))) {
return true;
}
return false;
}
//--- 박광서의 요청에 따라 현재 시간이 아니라, 전달되는 데이터의 시간으로 체크 한다. 2019.03.31
//--- 통신 이상 검사 : true. 통신 이상, false. 통신 정상
//---
//--- 한번도 데이터를 받은 적이 없으면 통신 이상
//--- 1. 정류기: 설정한 시간(Default 60초) 이상 데이터가 입력되지 않으면 통신 이상
//--- 2. 원격TB: 통신 주기 * 3 보다 통신이 되지 않은 시간이 클 경우 통신 이상
//--- 3. 배관링크: 통신 주기 * 3 보다 통신이 되지 않은 시간이 클 경우 통신 이상
//--- 4. 수위센서: 통신 주기 * 3 보다 통신이 되지 않은 시간이 클 경우 통신 이상
//--- 5. 관말압력
//--- 피크타임 모드인 경우
//--- 피크타임인 경우 - 통신 주기 * 3 보다 통신이 되지 않은 시간이 클 경우 통신 이상
//--- 피크타임이 아닌 경우 - 항상 통신 정상
//--- 피크타임 모드가 아닌 경우 - 통신 주기 * 3 보다 통신이 되지 않은 시간이 클 경우 통신 이상
//--- 6. 특정정압기: 통신 주기 * 3 보다 통신이 되지 않은 시간이 클 경우 통신 이상
//--- 7. 수신감도: 통신 주기 * 3 보다 통신이 되지 않은 시간이 클 경우 통신 이상
// function isNetworkError(deviceIndex, type, networkCycle, networkCycleUnit, statusDatetime) {
function isNetworkError(siteKey, device, statusDatetime) {
let deviceIndex = siteKey + '_' + device.type + '_' + device.deviceKey;
let type = device.type;
let networkCycle = device.networkCycle;
let networkCycleUnit = device.networkCycleUnit;
if (type == '5') { //--- 5. 관말압력
if (device.networkMode == '4') { //--- 4. 피크타임 모드
networkCycle = device.peakNetworkCycle;
networkCycleUnit = '분';
//--- 설정된 피크타임 시간이 아니면, statusDatetime을 현재 시간으로 설정하여 항상 통신 이상이 발생하지 않도록 한다.
let isPeak = false;
let hour = parseInt(moment().format('HH')); //--- 현재 시간 (0 ~ 24)
if ((device.peakTimeStart1 <= hour) && (hour < device.peakTimeEnd1)) {
isPeak = true;
}
if ((device.peakTimeStart2 <= hour) && (hour < device.peakTimeEnd2)) {
isPeak = true;
}
if (isPeak == false) {
statusDatetime = moment();
}
}
}
//--- 통신 이상 여부를 검사 한다.
let rtcd = true;
if (statusDatetime == null) { //--- 한번도 데이터를 받은 적이 없으면 통신 이상 처리
return true;
}
let datetimeFr = statusDatetime; //--- 최종 도착한 데이터의 시간
let datetimeTo = moment(); //--- 현재 시간
let duration = 0;
let unit = '분';
switch (type) {
case '1' :
unit = '초';
duration = moment.duration(datetimeTo.diff(datetimeFr)).asSeconds();
//--- 정류기는 60초 이상 데이터가 입력되지 않으면 통신 이상 처리 -> 설정한 시간으로 비교
// rtcd = (60 < duration);
rtcd = (device.networkCheckTime < duration);
logger.info(`include/global_utils.js :: ${deviceIndex} : Duration - ${floatFormat(duration, 7, 2)} ${unit}, networkCycle : ${device.networkCheckTime} :: ${rtcd}`);
break;
case '2' :
case '3' :
case '4' :
case '5' :
case '6' :
case '7' :
unit = '분';
duration = moment.duration(datetimeTo.diff(datetimeFr)).asMinutes();
if (networkCycleUnit == '시') {
networkCycle = networkCycle * 60;
}
//--- 박광서의 요청으로 임시로 통신 주기 * 3 보다 통신이 되지 않은 시간이 클 경우 오류 처리 (2019-03-11)
rtcd = (3 * networkCycle < duration);
logger.info(`include/global_utils.js :: ${deviceIndex} : Duration - ${floatFormat(duration, 7, 2)} ${unit}, 3 * networkCycle : ${3 * networkCycle} :: ${rtcd}`);
break;
case '8':
case 'A':
default:
switch (networkCycleUnit) {
case '초':
unit = '초';
duration = moment.duration(datetimeTo.diff(datetimeFr)).asSeconds();
break;
case '분':
unit = '분';
duration = moment.duration(datetimeTo.diff(datetimeFr)).asMinutes();
break;
case '시':
unit = '분';
duration = moment.duration(datetimeTo.diff(datetimeFr)).asMinutes();
networkCycle = networkCycle * 60;
break;
}
rtcd = (3 * networkCycle < duration);
logger.info(`include/global_utils.js :: ${deviceIndex} : Duration - ${floatFormat(duration, 7, 2)} ${unit}, 3 * networkCycle : ${3 * networkCycle} :: ${rtcd}`);
break;
}
return rtcd;
}
function saveNotificationNormal(device, datetimeTo) {
let noti = {};
noti.name = '통신 정상';
noti.site = device.site;
noti.device = device.id;
noti.deviceKey = device.deviceKey;
noti.deviceName = device.name;
noti.branch = -1;
noti.branchName = '';
noti.type = device.type;
noti.eventDatetime = datetimeTo.format('YYYY-MM-DD HH:mm:ss');
noti.level = 'Info';
noti.description = '통신 정상';
//--- branch로 branch 이름을 가져 온다.
let query = { where: { id: device.branch, deleted: false } };
modules.branches.model.table.findOne(query)
.then(function(branch) {
if (branch != null) {
noti.branch = branch.id;
noti.branchName = branch.name;
}
modules.notifications.model.table.create(noti)
.then(function(results) {
// logger.info(JSON.stringify(results));
}).catch(utils.obj.loggerError);
}).catch(function(err) {
logger.error(JSON.stringify(err));
modules.notifications.model.table.create(noti)
.then(function(results) {
// logger.info(JSON.stringify(results));
}).catch(utils.obj.loggerError);
});
}
function saveNotificationError(device, datetimeTo) {
let noti = {};
noti.name = '통신 이상';
noti.site = device.site;
noti.device = device.id;
noti.deviceKey = device.deviceKey;
noti.deviceName = device.name;
noti.branch = -1;
noti.branchName = '';
noti.type = device.type;
noti.eventDatetime = datetimeTo.format('YYYY-MM-DD HH:mm:ss');
noti.level = 'Error';
noti.description = '통신 이상';
//--- branch로 branch 이름을 가져 온다.
let query = { where: { id: device.branch, deleted: false } };
modules.branches.model.table.findOne(query)
.then(function(branch) {
if (branch != null) {
noti.branch = branch.id;
noti.branchName = branch.name;
}
modules.notifications.model.table.create(noti)
.then(function(results) {
// logger.info(JSON.stringify(results));
}).catch(utils.obj.loggerError);
}).catch(function(err) {
logger.error(JSON.stringify(err));
modules.notifications.model.table.create(noti)
.then(function(results) {
// logger.info(JSON.stringify(results));
}).catch(utils.obj.loggerError);
});
}
function stringToHex(str) {
const buf = Buffer.from(str, 'utf8');
return buf.toString('hex');
}
function hexToString(str) {
const buf = new Buffer(str, 'hex');
return buf.toString('utf8');
}
function isWindows() {
return (process.env.HOMEDRIVE) ? true:false;
}
function isLinux() {
return (process.env.HOME) ? true:false;
}
function stockUnitPrice(price) {
if (price < 1000) {
return 1;
} else if (price < 5000) {
return 5;
} else if (price < 10000) {
return 10;
} else if (price < 50000) {
return 50;
} else if (price < 100000) {
return 100;
} else if (price < 500000) {
return 500;
} else {
return 1000;
}
}
//--- 주문가격으로 판매 가격 계산 : ROUNDUP(R350 * (1 + 1%) / H350, 0) * H350
function stockCalcSalePrice(stockCode, orderPrice, saleRate = 1.0) { //--- saleRate : 1% 상승시 판매
// let saleRate = self.getPolicy(stockCode, 'saleRate')
let salePrice = orderPrice * (1 + saleRate / 100.0);
let unit = stockUnitPrice(salePrice);
return Math.ceil(salePrice / unit) * unit;
}
//--- 키움증권 매수 수수료 : ROUNDDOWN(매수 금액 * 0.015%, -1)
function stockCalcPurchaseFee(purchaseTotal) {
let fee = purchaseTotal * 0.015 / 100
return Math.floor(fee / 10) * 10;
}
//--- 키움증권 매도 수수료 : ROUNDDOWN(매도 금액 * 0.015%, -1)
function stockCalcSaleFee(saleTotal) {
let fee = saleTotal * 0.015 / 100;
return Math.floor(fee / 10) * 10;
}
//--- 키움증권 매도 거래세 : ROUND((W1257 - J1257 - X1257) * (0.1% + 0.15%), -1)
function stockCalcSaleTax(purchaseTotal, saleTotal) {
let fee = (saleTotal - stockCalcPurchaseFee(purchaseTotal) - stockCalcSaleFee(saleTotal)) * (0.1 + 0.15) / 100;
return Math.ceil(fee / 10) * 10;
}
//--- To-Do : LocalStorage, Cookie 등을 사용하여 설정 관리
function makeSetting(id='iconSetting') {
// if ((!config.func) || (!config.func.page) || (!config.func.page.setting)) {
// return '';
// }
// if (config.func.page.setting == false) {
// return '';
// }
//--- https://icons.getbootstrap.com/
// <i class="bi-alarm" style="${config.theme.font.help} color: cornflowerblue; cursor: pointer;"
// onclick="window.alert('Reserved');"></i>&nbsp;
return `<i id="${id}" class="bi-gear" style="${config.theme.font.help} color: cornflowerblue; cursor: pointer; display: none;"
onclick="nsOBCon.pageSettings.display();"></i>`;
}
//--- options.helpPage = 'url';
//--- options.helpPage = [ { name: '', url: '' } ]; //--- To-Do : 선택 목록을 보여준 후 선택시 도움말 화면 표시
function makeHelp(id='iconHelp') {
// if ((!config.func) || (!config.func.page) || (!config.func.page.setting)) {
// return '';
// }
// if (config.func.page.setting == false) {
// return '';
// }
//--- https://icons.getbootstrap.com/
// return `<i class="bi-question-square" style="${config.theme.font.help} color: cornflowerblue; cursor: pointer;"
// onclick="window.open('https://www.obcon.biz/cms/${options.helpPage}', '_blank');"></i>`;
return `<i id="${id}" class="bi-question-square" style="${config.theme.font.help} color: cornflowerblue; cursor: pointer; display: none;"
onclick="nsOBCon.pageHelps.display();"></i>`;
}
function getClientIp(req) {
var clientIp = req.body.clientIp ||
req.headers['x-forwarded-for'] ||
req.connection.remoteAddress ||
req.socket.remoteAddress ||
req.connection.socket.remoteAddress || '';
clientIp = clientIp.split(',')[0];
clientIp = clientIp.split(':').slice(-1)[0];
return clientIp;
}
function datetimeFormatter(str, formatFr = 'YYYYMMDDHHmmss', formatTo = 'YYYY-MM-DD HH:mm:ss') {
return (str == '') ? '': moment(str, formatFr).format(formatTo);
}
// async function sleep(miliseconds) {
// let promise = new Promise(function(resolve, reject) {
// setTimeout(function() {
// resolve();
// }, miliseconds);
// });
// await promise;
// }
module.exports = {
getPath: getPath,
getCustomPath: getCustomPath,
existFile: existFile,
getClass: getClass,
getModule: getModule,
// getSettingModule: getSettingModule,
getFullUrl: getFullUrl,
getTheme: getTheme,
getTemplateFile: getTemplateFile,
getTemplateFile2: getTemplateFile2,
getTemplateEjs: getTemplateEjs,
getHash: getHash,
stringFormat: stringFormat,
stringLength: stringLength,
integerFormat: integerFormat,
floatFormat: floatFormat,
floatFormat2: floatFormat2,
Null: Null,
isNone: isNone,
isNull: isNull,
isString: isString,
isNumber: isNumber,
isArray: isArray,
existField: existField,
existIdxField: existIdxField,
getObjectValue: getObjectValue,
deepCopy: deepCopy,
datetimeFormatter: datetimeFormatter,
initFlagString: initFlagString,
getFlagString: getFlagString,
setFlagString: setFlagString,
getFlagNumber: getFlagNumber,
setFlagNumber: setFlagNumber,
checkHacking: checkHacking,
randomInteger: randomInteger,
randomFloat: randomFloat,
isNetworkError: isNetworkError,
saveNotificationNormal: saveNotificationNormal,
saveNotificationError: saveNotificationError,
stringToHex: stringToHex,
hexToString: hexToString,
isWindows: isWindows,
isLinux: isLinux,
makeHelp: makeHelp,
makeSetting: makeSetting,
getClientIp: getClientIp,
// sleep: sleep,
stock: {
unitPrice: stockUnitPrice,
calcSalePrice : stockCalcSalePrice,
calcPurchaseFee: stockCalcPurchaseFee,
calcSaleFee: stockCalcSaleFee,
calcSaleTax: stockCalcSaleTax
},
obj: getModule('include', 'obconObject.js')
};