Spaces:
Running
Running
| /** | |
| * Copyright (c) 2017~2024, OBCon Inc. | |
| * All rights reserved. | |
| */ | |
| /** | |
| * @file | |
| * @copyright 2017~2024, OBCon Inc. | |
| * @author gye hyun james kim [pnuskgh@gmail.com] | |
| */ | |
| const fs = require('fs'); | |
| const path = require('path'); | |
| const arg = require('arg'); | |
| const moment = require('moment'); | |
| const cluster = require('cluster'); | |
| const appl = (global.appl = global.appl || {}); | |
| global.type = (appl && appl.type) ? appl.type : (global.type || ''); | |
| class Application { | |
| constructor(options) { | |
| // logger가 아직 초기화 전이라도 안전하게 콘솔로 시작 | |
| global.logger = console; | |
| Object.assign(appl, { | |
| isECMAScriptModules: typeof (require.main) == 'undefined', | |
| datetime: moment().format('YYYY-MM-DD HH:mm:ss'), // Application 시작 일시 | |
| ...options | |
| }); | |
| // ✅ PATCH: name/type 기본값 보정 (PM2에서 "*** undefined Application ..." 방지) | |
| if (!appl.name) appl.name = (process.env.APP_NAME || 'OBCon SCADA'); | |
| if (!appl.type && global.type) appl.type = global.type; | |
| if (process.platform == 'darwin') { | |
| // 오류 발생 :: MaxListenersExceededWarning: Possible EventEmitter memory leak detected. | |
| process.setMaxListeners(50); | |
| process.on('unhandledRejection', error => { | |
| console.error('Unhandled promise rejection:', error); | |
| }); | |
| } | |
| this._programName = 'bin/scada.js'; | |
| global.funcShutdowns = []; // 종료시 실행할 함수 { name: string, func: async function } | |
| this._intervalDeviceIndex = null; | |
| } | |
| _using() { | |
| console.log(`Using : node ${this._programName} --conf CONFFILE`); | |
| console.log(' CONFFILE : 설정 파일'); | |
| console.log(''); | |
| } | |
| _copyFile(source, target) { | |
| const content = fs.readFileSync(path.join(appl.root, source)); | |
| fs.writeFileSync(path.join(appl.root, target), content); | |
| } | |
| /** | |
| * logger.js가 여러 형태로 export될 수 있어서 안전하게 초기화: | |
| * 1) { createLogger() } 형태 | |
| * 2) module.exports = class Logger 형태 | |
| * 3) module.exports = function(...) 형태 | |
| * 실패 시 console로 fallback | |
| */ | |
| _initGlobalLogger() { | |
| try { | |
| const loggerMod = utils.getModule('include', 'logger.js'); | |
| // case 1) createLogger 제공 | |
| if (loggerMod && typeof loggerMod.createLogger === 'function') { | |
| global.logger = loggerMod.createLogger(); | |
| return; | |
| } | |
| // case 2) 클래스/함수 자체 export (module.exports = class Logger) | |
| if (typeof loggerMod === 'function') { | |
| try { | |
| // class로 new 가능한 경우 | |
| global.logger = new loggerMod(); | |
| return; | |
| } catch (e) { | |
| // new가 안되면 함수 호출 시도 | |
| try { | |
| global.logger = loggerMod(); | |
| if (!global.logger) global.logger = console; | |
| return; | |
| } catch (e2) { | |
| global.logger = console; | |
| return; | |
| } | |
| } | |
| } | |
| // case 3) 객체인데 createLogger가 없으면 console fallback | |
| global.logger = console; | |
| } catch (e) { | |
| global.logger = console; | |
| } | |
| } | |
| async initApplication() { | |
| try { | |
| //--- 1. 환경 변수 등 설정 | |
| process.env.NODE_ENV = 'production'; | |
| // ✅ PATCH: PM2 감지 강화 | |
| appl.isPM2 = !!( | |
| process.env.PM2_HOME || | |
| process.env.pm_id || | |
| process.env.PM2 || | |
| process.env.PM2_UUID || | |
| process.env.NODE_APP_INSTANCE || | |
| process.env.INSTANCE_ID | |
| ); | |
| appl.platform = process.platform; // win32, linux, darwin | |
| process.chdir(path.join(__dirname, '..')); | |
| appl.root = process.cwd().replace(/\\/g, '/'); | |
| if (['scada_batch'].includes(appl.type)) { | |
| appl.worker = { | |
| id: 1, | |
| type: 'master', | |
| seq: 1 | |
| }; | |
| } else { | |
| // https://pm2.keymetrics.io/docs/usage/quick-start/ | |
| if (appl.isPM2) { | |
| if (!appl.worker) appl.worker = { id: ((parseInt(process.env.INSTANCE_ID || process.env.NODE_APP_INSTANCE || process.env.pm_id || "0", 10) || 0) + 1) }; | |
| // INSTANCE_ID / NODE_APP_INSTANCE / pm_id -> 0,1,2... 형태이므로 +1 해서 1-based로 사용 | |
| const rawInstance = (process.env.INSTANCE_ID ?? process.env.NODE_APP_INSTANCE ?? process.env.pm_id ?? '0'); | |
| const parsed = parseInt(rawInstance, 10); | |
| const instanceId = (Number.isFinite(parsed) ? parsed : 0) + 1; | |
| appl.worker = { | |
| id: instanceId, | |
| type: 'pm2', | |
| seq: instanceId | |
| }; | |
| } else { | |
| appl.worker = { | |
| id: (cluster.isMaster) ? 1 : -1, | |
| type: 'master', | |
| seq: 1, | |
| }; | |
| } | |
| } | |
| this._programName = process.argv[1].replace(/\\/g, '/').replace(`${appl.root}/`, ''); | |
| //--- 2. 설정 정보 로딩 | |
| if (process.argv.length <= 2) { | |
| this._using(); | |
| process.exit(1); | |
| } | |
| const args = arg( | |
| { | |
| '--conf': String, | |
| '-c': '--conf' | |
| }, | |
| { argv: process.argv.slice(2) } | |
| ); | |
| const configFile = args['--conf']; | |
| if (!configFile) { | |
| console.error('Error: 설정 파일을 지정 하세요.'); | |
| this._using(); | |
| process.exit(2); | |
| } | |
| if (!fs.existsSync(path.join(appl.root, configFile))) { | |
| console.error(`Error: 설정 파일이 존재하지 않습니다 - ${configFile}`); | |
| this._using(); | |
| process.exit(3); | |
| } | |
| global.config = require(path.join(appl.root, configFile)); | |
| global.conf = global.config; | |
| // ✅ PATCH: config에 서비스 이름/타입이 있으면 appl 기본값 보정 | |
| try { | |
| if (!appl.name) appl.name = (config?.service?.name || config?.service?.title || 'OBCon SCADA'); | |
| if (!appl.type) appl.type = (config?.service?.type || appl.type); | |
| } catch (e) { } | |
| if (config && typeof config.funcAfter === 'function') { | |
| config.funcAfter(); | |
| } | |
| this._copyFile('version.json', 'version_local.json'); | |
| //--- 3. Global Utility 함수 로드 | |
| const UtilityPrev = require(path.join(appl.root, 'include', 'UtilityPrev.js')); | |
| const utilityPrev = new UtilityPrev(); | |
| global.utils = utilityPrev.getModule('include', 'Utility.js'); | |
| // [hotfix] utilityPrev.getModule() 결과가 wrapper일 수 있어 Utility 인스턴스로 보정 | |
| try { | |
| const utilMod = require(path.join(__dirname, 'Utility.js')); | |
| if (!global.utils || | |
| (typeof global.utils.existField !== 'function') || | |
| (typeof global.utils.isNetworkError !== 'function') || | |
| (typeof global.utils.getModule !== 'function')) { | |
| global.utils = (typeof utilMod === 'function') ? new utilMod() : utilMod; | |
| } | |
| global.utils.obj = global.utils.obj || {}; | |
| global.utils.obj.loggerError = global.utils.obj.loggerError || function (e) { | |
| try { logger.error(e && e.stack ? e.stack : e); } catch (_) { } | |
| }; | |
| } catch (e) { | |
| global.utils = global.utils || {}; | |
| global.utils.obj = global.utils.obj || {}; | |
| global.utils.obj.loggerError = global.utils.obj.loggerError || function (err) { | |
| try { logger.error(err && err.stack ? err.stack : err); } catch (_) { } | |
| }; | |
| } | |
| //--- 4. Cluster 설정 | |
| if (appl.isPM2 == false) { | |
| switch (appl.type) { | |
| case 'obcon_scada': | |
| case 'obcon_proxy': | |
| case 'obcon_cms': | |
| case 'obcon_iot': | |
| global.clusters = utils.getModule('include', 'cluster.js'); | |
| if (global.clusters && typeof global.clusters.forks === 'function') { | |
| global.clusters.forks(); | |
| } else { | |
| try { console.error('[WARN] cluster.js has no forks(), continue without clustering'); } catch (e) { } | |
| } | |
| if (cluster.isMaster == false) { | |
| // cluster에서 worker에게 메시지를 보내는 시간보다 5초간 대기 한다. | |
| if (utils && typeof utils.wait !== "function") { | |
| utils.wait = async (condFn, timeoutMs = 5000, intervalMs = 50) => { | |
| const start = Date.now(); | |
| while (true) { | |
| let ok = false; | |
| try { ok = !!condFn(); } catch (e) { ok = false; } | |
| if (ok) return true; | |
| if (Date.now() - start > timeoutMs) throw new Error(`utils.wait timeout after ${timeoutMs}ms`); | |
| await new Promise(r => setTimeout(r, intervalMs)); | |
| } | |
| }; | |
| } | |
| await utils.wait(() => ((appl.worker && (appl.worker && appl.worker.id != -1))), 5 * 1000); | |
| } | |
| break; | |
| } | |
| } | |
| //--- 5. Global Logger 설정 : logger.info(), logger.warn(), logger.error() | |
| this._initGlobalLogger(); | |
| const dbg = (msg) => { | |
| try { | |
| if (global.logger && typeof global.logger.debug === 'function') global.logger.debug(msg); | |
| else console.log(msg); | |
| } catch (e) { } | |
| }; | |
| dbg(`1. 환경 변수 등 설정`); | |
| dbg(`2. 설정 정보 로딩`); | |
| dbg(`3. Global Utility 함수 로드`); | |
| dbg(`4. Cluster 설정`); | |
| dbg(`5. Global Logger 설정`); | |
| //--- 6. Global Security 설정 | |
| global.security = utils.getModule('include', 'Security.js'); | |
| dbg(`6. Global Security 설정`); | |
| //--- 7. Global Cache 설정 | |
| global.caches = utils.getModule('include', 'caches.js'); // Cluster에서 사용 | |
| dbg(`7. Global Cache 설정`); | |
| //--- Global WatchDogTimer 설정 | |
| global.watchDogTimer = utils.getModule('include', 'watch_dog_timer.js'); | |
| dbg(`Global WatchDogTimer 설정`); | |
| //--- 8. Global Emails 설정 | |
| global.emails = utils.getModule('include', 'Emails.js'); | |
| dbg(`8. Global Emails 설정`); | |
| //--- 9. Global Telegram 설정 | |
| global.telegram = (config.telegram && config.telegram.use) ? await utils.getModule('include', 'Telegram.js') : null; | |
| dbg(`9. Global Telegram 설정`); | |
| //--- 10. Global SMS 설정 | |
| global.sms = await utils.getModule('include', 'sms.js'); | |
| dbg(`10. Global SMS 설정- ${this._programName}`); | |
| //--- 11. Global Database 설정 | |
| global.databases = utils.getModule('include', 'databases.js'); | |
| global.db = { | |
| dataTypes: databases.dataTypes, | |
| db_scada: databases.scada, | |
| db_data: databases.data | |
| }; | |
| dbg(`11. Global Database 설정`); | |
| //--- 보안: 비밀번호는 마스킹해서 로그에 남김. HF Logs 가 공개될 수 있음. | |
| try { | |
| const _sc = global.conf?.databases?.scada || {}; | |
| const _safe = { ...(_sc), password: _sc.password ? `<len=${String(_sc.password).length}>` : null }; | |
| console.log("[DB-CONF scada]", _safe); | |
| } catch (e) { console.log("[DB-CONF scada] <print failed:", e.message, ">"); } | |
| console.log("[CONF keys]", global.conf ? Object.keys(global.conf) : null); | |
| console.log("[CONF.databases keys]", global.conf?.databases ? Object.keys(global.conf.databases) : null); | |
| console.log("[CONF file hint]", global.conf?.__filename || global.conf?.configFile || null); | |
| //--- 12. Fields 정의 | |
| global.fields = utils.getModule('include', 'fields.js'); | |
| dbg(`12. Global Fields 설정`); | |
| //--- 13. Module별로 Model과 View 정의 | |
| require(path.join(appl.root, 'modules', 'modules.js')); | |
| dbg(`13. Module별로 Model과 View 정의`); | |
| //--- 14. Settings 정의 | |
| global.settingSync = utils.getModule('include', 'settings.js'); | |
| dbg(`14. Global Settings 정의`); | |
| //--- 15. Global Interfaces 설정 | |
| switch (appl.type) { | |
| case 'obcon_scada': | |
| global.internalProxyClient = utils.getModule('modules', 'proxies', 'proxy_client.js'); | |
| dbg(`15. Global Proxy Client 설정`); | |
| // intentional fallthrough | |
| case 'obcon_scada_device': | |
| global.interfaces = utils.getModule('interfaces', 'interfaces.js'); | |
| dbg(`15. Global Interfaces 설정`); | |
| break; | |
| } | |
| //--- MQTT 설정 | |
| require(path.join(appl.root, 'modules', 'mqtt_app.js')); | |
| dbg(`Global MQTT Client (mqtt_client) 설정`); | |
| // ---- HOTFIX: utils.wait polyfill | |
| if (typeof utils.wait !== 'function') { | |
| utils.wait = async (condFn, timeoutMs = 5000, intervalMs = 50) => { | |
| const start = Date.now(); | |
| while (true) { | |
| try { | |
| if (typeof condFn === 'function' ? !!condFn() : !!condFn) return true; | |
| } catch (e) { } | |
| if ((Date.now() - start) >= timeoutMs) { | |
| throw new Error(`utils.wait timeout after ${timeoutMs}ms`); | |
| } | |
| await new Promise(r => setTimeout(r, intervalMs)); | |
| } | |
| }; | |
| } | |
| //--- 16. Redis 설정 | |
| if (config.redis && config.redis.isUse) { | |
| global.redis = utils.getModule('include', 'Redis.js'); | |
| await utils.wait(() => global.redis.ready, 5 * 60 * 1000); | |
| setTimeout(async function () { | |
| try { | |
| await modules.devices.view.getDevicesInfo(true); | |
| } catch (e) { } | |
| }, 1000); | |
| } | |
| dbg(`16. Redis 설정`); | |
| //--- IECP 설정 | |
| global.iecp = utils.getModule('interfaces', 'IECP.js'); | |
| iecp.startGarbageCollection(); | |
| dbg(`IECP 설정`); | |
| } catch (e) { | |
| if (typeof (logger) == 'undefined') { | |
| console.error(e.message); | |
| console.error(e.stack); | |
| this._using(); | |
| } else { | |
| try { logger.error(e.message); } catch (ex) { } | |
| console.error(e.stack); | |
| } | |
| process.exit(3); | |
| } | |
| } | |
| async run() { | |
| global.app_http = null; | |
| global.app_io = null; | |
| global.app_tcp = null; | |
| global.app_udp = null; | |
| global.app_modbus = null; | |
| global.app_proxy = null; | |
| const workType = () => { | |
| if (appl.isPM2) { | |
| if (!appl.worker) appl.worker = { id: ((parseInt(process.env.INSTANCE_ID || process.env.NODE_APP_INSTANCE || process.env.pm_id || "0", 10) || 0) + 1) }; | |
| const wid = appl.worker?.id ?? (parseInt(process.env.NODE_APP_INSTANCE ?? process.env.INSTANCE_ID ?? process.env.pm_id ?? '0', 10) + 1); | |
| return `PM2 worker ${wid}`; | |
| } else { | |
| return (cluster.isMaster) ? 'Master' : `Worker ${(appl.worker && appl.worker.id)}`; | |
| } | |
| } | |
| if (appl.isPM2) { | |
| if (!appl.worker) appl.worker = { id: ((parseInt(process.env.INSTANCE_ID || process.env.NODE_APP_INSTANCE || process.env.pm_id || "0", 10) || 0) + 1) }; | |
| process.on('SIGINT', function () { | |
| watchDogTimer.stop(); // WatchDogTimer를 종료 한다. | |
| if (global.app_io != null) { | |
| try { global.app_io.close(function () { }.bind(this)); } catch (e) { utils.obj.loggerError(e); } | |
| } | |
| if ((global.app_tcp != null) && (global.app_tcp.server != null)) { | |
| try { global.app_tcp.server.close(function () { }.bind(this)); } catch (e) { utils.obj.loggerError(e); } | |
| } | |
| if ((global.app_udp != null) && (global.app_udp._serverSocket != null)) { | |
| try { global.app_udp._serverSocket.close(function () { }.bind(this)); } catch (e) { utils.obj.loggerError(e); } | |
| } | |
| if ((global.app_modbus != null) && (global.app_modbus.server != null)) { | |
| try { global.app_modbus.server.close(function () { }.bind(this)); } catch (e) { utils.obj.loggerError(e); } | |
| } | |
| if ((typeof (global.app_proxy) != 'undefined') && (global.app_proxy != null)) { | |
| try { global.app_proxy.close(); } catch (e) { utils.obj.loggerError(e); } | |
| } | |
| try { logger.debug('Bye bye'); } catch (e) { } | |
| try { | |
| logger.info('------------------------------------------------------------'); | |
| logger.info('SIGINT ------------------------------------------------------------'); | |
| logger.info('------------------------------------------------------------'); | |
| } catch (e) { } | |
| process.exit(0); | |
| }.bind(this)); | |
| } | |
| try { | |
| logger.info(`*** ${appl.name || 'OBCon SCADA'} Application - ${workType()} ***`); | |
| switch (appl.type) { | |
| case 'obcon_scada': this._scada_app(); break; | |
| case 'scada_schedule': this._schedule_app(); break; | |
| case 'obcon_proxy': this._proxy_app(); break; | |
| case 'obcon_scada_device': await this._device_app(); break; | |
| case 'obcon_iot': this._http_app(); break; | |
| case 'scada_batch': this._batch_app(); break; | |
| case 'obcon_cms': this._http_app(); break; // Deprecated | |
| case 'cms_batch': this._batch_app(); break; // Deprecated | |
| default: | |
| logger.error('정의되지 않은 application type 입니다.'); | |
| break; | |
| } | |
| } catch (e) { | |
| console.error('error', e); | |
| utils.obj.loggerError(e); | |
| watchDogTimer.stop(); | |
| if ((appl.isPM2 == false) && (cluster.isMaster)) { | |
| for (let idx in cluster.workers) { | |
| cluster.workers[idx].kill(); | |
| } | |
| } | |
| process.exit(4); | |
| } | |
| } | |
| // PM2에게 ready 신호를 보내 application이 실행 준비가 되었음을 알린다. | |
| _pm2_ready() { | |
| if (appl.isPM2) { | |
| if (!appl.worker) appl.worker = { id: ((parseInt(process.env.INSTANCE_ID || process.env.NODE_APP_INSTANCE || process.env.pm_id || "0", 10) || 0) + 1) }; | |
| setTimeout(function () { | |
| process.send('ready'); | |
| }, 500); | |
| } | |
| } | |
| async _scada_app() { | |
| try { | |
| global.idxDevices = {}; // Device별 Interface Version 관리 | |
| if (appl.isPM2) { | |
| if (!appl.worker) appl.worker = { id: ((parseInt(process.env.INSTANCE_ID || process.env.NODE_APP_INSTANCE || process.env.pm_id || "0", 10) || 0) + 1) }; | |
| // ✅ PM2에서는 cluster.worker가 없을 수 있으니 appl.worker.id 사용 | |
| const pm2WorkerId = ((parseInt(process.env.INSTANCE_ID || process.env.NODE_APP_INSTANCE || process.env.pm_id || "0", 10) || 0) + 1); | |
| this._mqtt_client_init('pm2', pm2WorkerId); | |
| setTimeout(async function () { | |
| await this._mqtt_client_makeIndex('pm2'); | |
| let appTypes = []; | |
| if (0 < config.cluster.worker_count_http) appTypes.push('http'); | |
| if (0 < config.cluster.worker_count_tcp) appTypes.push('tcp'); | |
| if (0 < config.cluster.worker_count_udp) appTypes.push('udp'); | |
| if (0 < config.cluster.worker_count_modbus) appTypes.push('modbus'); | |
| if (0 < config.cluster.worker_count_proxy) appTypes.push('proxy'); | |
| for (let type of appTypes) { | |
| if (type == 'modbus') { | |
| require(path.resolve(utils.getCustomPath('modules', 'ModbusApp.js'))); | |
| } else if (type == 'proxy') { | |
| global.app_proxy = utils.getModule('modules', 'proxies', 'proxy_app.js'); | |
| global.app_proxy.run(); | |
| } else { | |
| require(path.resolve(utils.getCustomPath('modules', `${type}_app.js`))); | |
| } | |
| logger.info(`start ${type} service`); | |
| } | |
| mqtt_client.publish( | |
| 'publish_command', | |
| '{ "cmd": "stop checkAlive", "clientId": "' + mqtt_client.clientId + '" }' | |
| ); | |
| setTimeout(function () { | |
| if (process.env.OBCON_DISABLE_CHECKALIVE === '1') { | |
| console.log('[HF] checkAlive disabled (OBCON_DISABLE_CHECKALIVE=1) - snapshot demo mode'); | |
| } else { | |
| watchDogTimer.set_wdt('checkAlive', 10, modules.devices.view.checkAlive.bind(modules.devices.view), moment()); | |
| } | |
| this._pm2_ready(); | |
| }.bind(this), 1000); | |
| }.bind(this), 1000); | |
| } else { | |
| // ✅ PATCH: cluster.worker 안전 접근 | |
| if (cluster.isWorker && cluster.worker && cluster.worker.id != null) { | |
| let type = 'proxy'; | |
| if (cluster.worker.id <= config.cluster.worker_count_http) type = 'http'; | |
| else if (cluster.worker.id <= config.cluster.worker_count_http + config.cluster.worker_count_tcp) type = 'tcp'; | |
| else if (cluster.worker.id <= config.cluster.worker_count_http + config.cluster.worker_count_tcp + config.cluster.worker_count_udp) type = 'udp'; | |
| else if (cluster.worker.id <= config.cluster.worker_count_http + config.cluster.worker_count_tcp + config.cluster.worker_count_udp + config.cluster.worker_count_modbus) type = 'modbus'; | |
| this._mqtt_client_init(type, cluster.worker.id); | |
| setTimeout(async function () { | |
| await this._mqtt_client_makeIndex(type); | |
| if (type == 'modbus') { | |
| require(path.resolve(utils.getCustomPath('modules', 'ModbusApp.js'))); | |
| } else if (type == 'proxy') { | |
| global.app_proxy = utils.getModule('modules', 'proxies', 'proxy_app.js'); | |
| global.app_proxy.run(); | |
| } else { | |
| require(path.resolve(utils.getCustomPath('modules', `${type}_app.js`))); | |
| } | |
| logger.info(`start ${type} service`); | |
| }.bind(this), 1000); | |
| } else { | |
| if (process.env.OBCON_DISABLE_CHECKALIVE === '1') { | |
| console.log('[HF] checkAlive disabled (OBCON_DISABLE_CHECKALIVE=1) - snapshot demo mode'); | |
| } else { | |
| watchDogTimer.set_wdt('checkAlive', 10, modules.devices.view.checkAlive.bind(modules.devices.view), moment()); | |
| } | |
| logger.debug(`master start checkAlive`); | |
| this._mqtt_client_init('master', 'master'); | |
| setTimeout(async function () { | |
| await this._mqtt_client_makeIndex('master'); | |
| }.bind(this), 1000); | |
| } | |
| } | |
| } catch (ex) { | |
| console.error(ex); | |
| } | |
| } | |
| async _proxy_app() { | |
| if (appl.isPM2) { | |
| if (!appl.worker) appl.worker = { id: ((parseInt(process.env.INSTANCE_ID || process.env.NODE_APP_INSTANCE || process.env.pm_id || "0", 10) || 0) + 1) }; | |
| setTimeout(async function () { | |
| global.app_proxy = utils.getModule('modules', 'proxies', 'proxy_app.js'); | |
| await global.app_proxy.run(appl.type); | |
| logger.info(`start ${appl.type} service`); | |
| }.bind(this), 1000); | |
| } else { | |
| if (cluster.isWorker) { | |
| setTimeout(async function () { | |
| global.app_proxy = utils.getModule('modules', 'proxies', 'proxy_app.js'); | |
| await global.app_proxy.run(appl.type); | |
| logger.info(`start proxy service`); | |
| }.bind(this), 1000); | |
| } | |
| } | |
| } | |
| _getRandomInt(min, max) { | |
| min = Math.ceil(min); | |
| max = Math.floor(max); | |
| return Math.floor(Math.random() * (max - min)) + min; // 최댓값은 제외, 최솟값은 포함 | |
| } | |
| /** | |
| * http_app.js 로딩 실패 시에도 로그 남기고, | |
| * PM2 ready는 finally에서 항상 실행 | |
| */ | |
| _http_app() { | |
| if ((appl.isPM2) || (cluster.isWorker)) { | |
| setTimeout(function () { | |
| try { | |
| const p = path.resolve(utils.getCustomPath('modules', 'http_app.js')); | |
| try { logger.info(`[HTTP] require: ${p}`); } catch (e) { console.log(`[HTTP] require: ${p}`); } | |
| require(p); | |
| try { logger.info(`woker start http service : ${config.http.host}:${config.http.port}`); } catch (e) { } | |
| } catch (e) { | |
| try { logger.error(`[HTTP] http_app.js load failed: ${e && (e.stack || e)}`); } | |
| catch (ex) { console.error(`[HTTP] http_app.js load failed: ${e && (e.stack || e)}`); } | |
| } finally { | |
| this._pm2_ready(); | |
| } | |
| }.bind(this), 1000); | |
| } | |
| } | |
| _batch_app() { | |
| setTimeout(function () { | |
| let jobBatch = null; | |
| switch (config.batch.path.length) { | |
| case 1: jobBatch = utils.getModule(config.batch.path[0]); break; | |
| case 2: jobBatch = utils.getModule(config.batch.path[0], config.batch.path[1]); break; | |
| case 3: jobBatch = utils.getModule(config.batch.path[0], config.batch.path[1], config.batch.path[2]); break; | |
| case 4: jobBatch = utils.getModule(config.batch.path[0], config.batch.path[1], config.batch.path[2], config.batch.path[3]); break; | |
| case 5: jobBatch = utils.getModule(config.batch.path[0], config.batch.path[1], config.batch.path[2], config.batch.path[3], config.batch.path[4]); break; | |
| default: | |
| logger.error(`실행할 batch 파일을 올바르게 지정 하세요 - ${config.batch.path.join('/')}`); | |
| process.exit(5); | |
| } | |
| logger.info(`Start batch - ${config.batch.path.join('/')}`); | |
| this._pm2_ready(); | |
| jobBatch.batchJob(config.batch.options, function (code, message) { | |
| logger.info(`Batch job complete : code - ${code}, message - [${message}]`); | |
| setTimeout(function () { | |
| process.exit(0); | |
| }.bind(this), config.batch.waitTime * 1000); | |
| }); | |
| }.bind(this), 1000); | |
| } | |
| async _device_app() { | |
| if (config.emulator.isUse == false) { | |
| console.error('장비 애뮬레이터를 지원하지 않습니다.'); | |
| return; | |
| } | |
| console.log('30초 sleep . . . . . .'); | |
| await utils.sleep(30 * 1000); | |
| global.idxDevices = {}; | |
| await modules.devices.view.makeDeviceIndexForInterface(); | |
| console.log('30초 sleep . . . . . . again'); | |
| await utils.sleep(30 * 1000); | |
| await modules.devices.view.makeDeviceIndexForInterface(); | |
| setTimeout(function () { | |
| let query = { where: { deleted: false } }; | |
| modules.sites.model.table.findAll(query) | |
| .then(function (sites) { | |
| let idxSites = modules.sites.view.makeIdx(sites, 'id'); | |
| query = { where: { statusUse: 'Active', deleted: false } }; | |
| modules.devices.model.table.findAll(query) | |
| .then(function (devices) { | |
| let idxDevices = modules.devices.view.makeIdx(devices, 'deviceKey'); | |
| for (let idx = 0; idx < config.emulator.devices.length; idx++) { | |
| let client = config.emulator.devices[idx]; | |
| let site = idxSites[`idx_${client.site}`]; | |
| let device = idxDevices[`idx_${client.deviceKey}`]; | |
| logger.info(`${client.clientType} Client : ${site.siteKey}_${device.type}_${device.deviceKey}`); | |
| let emulator = null; | |
| if (client.clientType == '') { | |
| emulator = utils.getModule('modules', 'clients', `ModbusClient_${device.type}.js`); | |
| } else { | |
| emulator = utils.getModule('modules', 'clients', `${client.clientType}_client_${device.type}.js`); | |
| } | |
| emulator.init(device, site); | |
| setTimeout(function () { | |
| if (emulator._site != null) { | |
| switch (client.clientType) { | |
| case 'modbus': | |
| emulator.startConnect(config.modbus.server.host, config.modbus.server.port); | |
| break; | |
| case 'udp': | |
| emulator.startConnect(config.udp.server.host, config.udp.server.port); | |
| break; | |
| default: | |
| emulator.startConnect(config.tcp.server.host, config.tcp.server.port); | |
| break; | |
| } | |
| setTimeout(function () { | |
| emulator.startAction(); | |
| }, 500); | |
| } | |
| }.bind(this), 500); | |
| } | |
| this._pm2_ready(); | |
| }.bind(this)).catch(utils.obj.loggerError); | |
| }.bind(this)).catch(utils.obj.loggerError); | |
| }.bind(this), 1000); | |
| } | |
| _schedule_app() { | |
| global.jobs = { jobFiles: [] }; | |
| logger.info(`registerAll()`); | |
| modules.jobManagers.view.registerAll(); | |
| setTimeout(function () { | |
| logger.info(`startAll()`); | |
| modules.jobManagers.view.startAll(); | |
| }.bind(this), 2000); | |
| this._pm2_ready(); | |
| } | |
| _mqtt_client_init(type, clientId) { | |
| mqtt_client.type = type; | |
| mqtt_client.clientId = config.mqtt.clientIdPrefix + clientId; | |
| mqtt_client.init(); | |
| } | |
| async _mqtt_client_makeIndex(type) { | |
| mqtt_client.subscribe('publish_command'); | |
| mqtt_client.subscribe('iecp'); // IECP 통신을 위한 topic 등록 | |
| if (this._intervalDeviceIndex == null) { | |
| this._intervalDeviceIndex = setInterval(async function () { | |
| try { | |
| await modules.devices.view.makeDeviceIndexForInterface(); | |
| } catch (ex) { } | |
| }.bind(this), 60 * 60 * 1000); | |
| } | |
| } | |
| } | |
| module.exports = Application; | |