'use strict' /** * Copyright (c) 2017~2022, OBCon Inc. * All rights reserved. */ /** * @file * @copyright 2017~2022, OBCon Inc. * @author gye hyun james kim [pnuskgh@gmail.com] */ //--- Buffer //--- LE (Little-Endian, 리틀 엔디안) : 인텔에서 동작시 (Default) //--- LSB (Least Significant Bit) : 낮은 단위를 앞에 저장 //--- 0A0B0C0D 데이터 -> 0D, 0C, 0B, 0A 순서로 저장 //--- BE (Big-Endian, 빅 엔디안) : Network에서 처리시, Modbus에서 기본 (Modbus CRC는 LE) //--- MSP (Most Significant Bit) : 큰 단위를 앞에 저장 //--- 0A0B0C0D 데이터 -> 0A, 0B, 0C, 0D 순서로 저장 //--- //--- 생성 //--- let buf = Buffer.alloc(length, char); //--- 초기화 (char의 default 값은 0) //--- let buf = Buffer.allocUnsafe(length); //--- 초기화 하지 않음 //--- let buf = Buffer.from([1, 2, 3]); //--- 비교 //--- let idx = buf.indexOf(char); //--- 결합 //--- let buf = Buffer.concat([buf001, buf002]); //--- 자르기 //--- let buf = buf1.slice(idx, length); //--- //--- 읽기 //--- //--- let str = buf.toString('UTF-8', start, end + 1); //--- let uint = buf.readUInt8(idx); //--- readUIntLE(offset, length), readUIntBE(offset, length), readIntLE(offset, length), readIntBE(offset, length) //--- readUInt8 (0 ~ 255), readUInt16LE, readUInt16BE (0 ~ 65,535), readUInt32LE, readUInt32BE (0 ~ 4,294,967,295), readBigUInt64BE, readBigUInt64LE //--- readInt8 (-128 ~ 127), readInt16LE, readInt16BE (–32,768 ~ 32,767), readInt32LE, readInt32BE (–2,147,483,648 ~ 2,147,483,647), readBigInt64BE, readBigInt64LE //--- readFloatLE, readFloatBE (3.4E+/-38(7개의 자릿수), 4 bytes), readDoubleLE, readDoubleBE (1.7E+/-308(15개의 자릿수), 8 bytes) //--- 쓰기 //--- buf.fill(char); //--- writeUIntLE(value, offset, length), writeUIntBE(value, offset, length), writeIntLE(value, offset, length), writeIntBE(value, offset, length) //--- writeUInt8(value, offset), writeUInt16LE(value, offset), writeUInt16BE(value, offset), writeUInt32LE(value, offset), writeUInt32BE(value, offset), writeBigUInt64BE(value, offset), writeBigUInt64LE(value, offset) //--- writeInt8(value, offset), writeInt16LE(value, offset), writeInt16BE(value, offset), writeInt32LE(value, offset), writeInt32BE(value, offset), writeBigInt64BE(value, offset), writeBigInt64LE(value, offset) //--- writeFloatLE(value, offset), writeFloatBE(value, offset), writeDoubleLE(value, offset), writeDoubleBE(value, offset) //--- Modbus //--- readUInt16BE, readInt16BE //--- writeUInt16BE, writeInt16BE //--- in CRC //--- readUInt16LE, readInt16LE //--- writeUInt16LE, writeInt16LE let net = require('net'); let jsmodbus = require("jsmodbus"); let { v1: uuidv1 } = require('uuid'); let moment = require('moment'); let ModbusServerClientClass = utils.getClass('modules', 'ModbusServerClient.js'); class ModbusServer { constructor() { this._host = null; this._port = null; this._netServer = null; //--- net.Server this._server = null; //--- ModbusTCPServer this._clients = []; //--- Client와 연결된 ModbusServerClient //--- Modbus Object Type SCADA에서 용도 설정 //--- Coil : Read/Write, 1 bit, address - 0x0000 ~ 0xFFFF : 디지털 출력 : 장비 데이터를 변경 한다. //--- Discrete input : Read, 1 bit, address - 0x0000 ~ 0xFFFF : 디지털 입력 : 장비 데이터를 가져 온다. //--- Input register : Read, 16 bit, address - 0x0000 ~ 0xFFFF : 아날로그 입력 또는 내부 메모리용 : 장비 설정을 가져 온다. //--- Holding register : Read/Write, 16 bit, address - 0x0000 ~ 0xFFFF : 래치메모리용 : 장비 설정을 변경 한다. this._coils = { device_100 : Buffer.from([ 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xab, 0xcd, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xab, 0xcd, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xab, 0xcd, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xab, 0xcd, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xab, 0xcd, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xab, 0xcd, ]) }; this._discrete = { device_100 : Buffer.from([ 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xab, 0xcd, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xab, 0xcd, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xab, 0xcd, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xab, 0xcd, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xab, 0xcd, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xab, 0xcd, ]) }; this._input = { device_100 : Buffer.from([ 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xab, 0xcd, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xab, 0xcd, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xab, 0xcd, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xab, 0xcd, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xab, 0xcd, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xab, 0xcd, ]) }; this._holding = { device_100 : Buffer.from([ 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xab, 0xcd, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xab, 0xcd, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xab, 0xcd, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xab, 0xcd, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xab, 0xcd, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xab, 0xcd, ]) }; this._maxPos = 32; // this._devices = {}; //--- Device 저장. 'device_{id} } //--- Server : listen() 함수 호출시 //--- Server : onNetListening > listen //--- //--- Client : connect() 함수 호출시 //--- Server : createServer(socket) > onNetConnection(socket) > onConnection(client) //--- socket : client._socket //--- client : ModbusServerClient //--- _server : ModbusTCPServer //--- _socket : Socket. 위의 socket 전달값과 동일 //--- _requestHandler : //--- _responseHandler : //--- Client : onConnect() > connect() > onReady() //--- //--- Client : connect() 함수 호출시 서버가 없을 경우 //--- Client : onError(err) > onClose(hadError = true) //--- //--- Client에서 비정상 접속 종료시 //--- ServerClient : onError(err) > onClose(hadError = true) //--- //--- Server에서 비정상 접속 종료시 //--- Client : onError(err) > onClose(hadError = true) //--- //--- //--- *** modbus-server-response-handler.js 참조 //--- Client에서 readDiscreteInputs() 함수 호출시 //--- Client : readDiscreteInputs(start, count) > onData(data) > Client(res, err) //--- Server : onPreReadDiscreteInputs(request, cb) > onPostReadDiscreteInputs(request, cb) > ServerClient : onData(data) //--- //--- Client에서 readCoils() 함수 호출시 : new jsmodbus.server.TCP(this._netServer, options)에서 지정한 options.coils의 값을 반환 //--- Client : readCoils(start, count) > onData(data) > Client(res, err) //--- Server : onPreReadCoils(request, cb) > onPostReadCoils(request, cb) > ServerClient : onData(data) //--- request : {"_id":1,"_protocol":0,"_length":6,"_unitId":245,"_body":{"_fc":1,"_start":0,"_count":13}} //--- res //--- request : ModbusTCPRequest - {"_id":1,"_protocol":0,"_length":6,"_unitId":245,"_body":{"_fc":1,"_start":0,"_count":13}} //--- response : ModbusTCPResponse - {"_id":1,"_protocol":0,"_bodyLength":5,"_unitId":245,"_body":{"_fc":1,"_coils:~,_numberOfBytes:2,_valueAsBuffer:~,_valueAsArray:~"}} //--- _body //--- _coils : Buffer //--- _numberOfBytes : _coils의 bytes 수 //--- _valueAsBuffer : _coils에 해당하는 Buffer //--- _valueAsArray : _coils에 해당하는 bit의 배열 //--- err //--- //--- Client에서 writeMultipleCoils() 함수 호출시 : //--- Client : writeMultipleCoils(start, values, quantity) > onData(data) > Client(res, err) //--- Server : onPreWriteMultipleCoils(request, cb) > onWriteMultipleCoils(request, cb) > onPostWriteMultipleCoils(request, cb) > onPostWriteMultipleCoils(request, cb) > ServerClient : onData(data) createServer() { this._netServer = new net.Server(function(socket) { logger.info(`modules/ModbusServer.js :: createServer(socket)`); }); this._netServer.on('close', this.onNetClose.bind(this)); this._netServer.on('connection', this.onNetConnection.bind(this)); this._netServer.on('error', this.onNetError.bind(this)); this._netServer.on('listening', this.onNetListening.bind(this)); let options = { coils: false, discrete: false, input: false, holding: false, // coils: Buffer.from([0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef]), // discrete: Buffer.from([0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef]), // input: Buffer.from([0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef]), // holding: Buffer.from([0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef]) }; this._server = new jsmodbus.server.TCP(this._netServer, options); this._server.on('connection', this.onConnection.bind(this)); //--- Data Access > Bit access > Physical Discrete Inputs > Read Discrete Inputs this._server.on('readDiscreteInputs', this.onReadDiscreteInputs.bind(this)); //--- fc : 2 this._server.on('preReadDiscreteInputs', this.onPreReadDiscreteInputs.bind(this)); this._server.on('postReadDiscreteInputs', this.onPostReadDiscreteInputs.bind(this)); //--- Data Access > Bit access > Internal Bits or Physical Coils > Read Coils this._server.on('readCoils', this.onReadCoils.bind(this)); //--- fc : 1 this._server.on('preReadCoils', this.onPreReadCoils.bind(this)); this._server.on('postReadCoils', this.onPostReadCoils.bind(this)); //--- Data Access > Bit access > Internal Bits or Physical Coils > Write Single Coil this._server.on('writeSingleCoil', this.onWriteSingleCoil.bind(this)); //--- fc: 5 this._server.on('preWriteSingleCoil', this.onPreWriteSingleCoil.bind(this)); this._server.on('postWriteSingleCoil', this.onPostWriteSingleCoil.bind(this)); //--- Data Access > Bit access > Internal Bits or Physical Coils > Write Multiple Coils this._server.on('writeMultipleCoils', this.onWriteMultipleCoils.bind(this)); //--- fc : 15 this._server.on('preWriteMultipleCoils', this.onPreWriteMultipleCoils.bind(this)); this._server.on('postWriteMultipleCoils', this.onPostWriteMultipleCoils.bind(this)); //--- Data Access > 16-bit access > Physical Input Registers > Read Input Register this._server.on('readInputRegisters', this.onReadInputRegisters.bind(this)); //--- fc : 4 this._server.on('preReadInputRegisters', this.onPreReadInputRegisters.bind(this)); this._server.on('postReadInputRegisters', this.onPostReadInputRegisters.bind(this)); //--- Data Access > 16-bit access > Internal Registers or Physical Output Registers > Read Multiple Holding Registers, fc : 3 this._server.on('readHoldingRegisters', this.onReadHoldingRegisters.bind(this)); this._server.on('preReadHoldingRegisters', this.onPreReadHoldingRegisters.bind(this)); this._server.on('postReadHoldingRegisters', this.onPostReadHoldingRegisters.bind(this)); //--- Data Access > 16-bit access > Internal Registers or Physical Output Registers > Write Single Holding Register, fc : 6 this._server.on('writeSingleRegister', this.onWriteSingleRegister.bind(this)); this._server.on('preWriteSingleRegister', this.onPreWriteSingleRegister.bind(this)); this._server.on('postWriteSingleRegister', this.onPostWriteSingleRegister.bind(this)); //--- Data Access > 16-bit access > Internal Registers or Physical Output Registers > Write Multiple Holding Registers, fc : 16 this._server.on('writeMultipleRegisters', this.onWriteMultipleRegisters.bind(this)); this._server.on('preWriteMultipleRegisters', this.onPreWriteMultipleRegisters.bind(this)); this._server.on('postWriteMultipleRegisters', this.onPostWriteMultipleRegisters.bind(this)); //--- Data Access > 16-bit access > Internal Registers or Physical Output Registers > Read/Write Multiple Registers, fc : 23 //--- Data Access > 16-bit access > Internal Registers or Physical Output Registers > Mask Write Register, fc : 22 //--- Data Access > 16-bit access > Internal Registers or Physical Output Registers > Read FIFO Queue, fc : 24 //--- Data Access > File Record Access > Read File Record , fc : 20 //--- Data Access > File Record Access > Write File Record , fc : 21 //--- Diagnostics > Read Exception Status, fc : 7, serial only //--- Diagnostics > Diagnostic, fc : 8, serial only //--- Diagnostics > Get Com Event Counter, fc : 11, serial only //--- Diagnostics > Get Com Event Log, fc : 12, serial only //--- Diagnostics > Report Server ID, fc : 17, serial only //--- Diagnostics > Read Device Identification, fc : 43, serial only //--- Other > Encapsulated Interface Transport, fc : 43 } listen(host, port) { this._host = host; this._port = port; this._netServer.listen(this._port, this._host, function() { logger.info(`modules/ModbusServer.js :: listen()`); logger.info(' '); }.bind(this)); } //---------------------------------------------------------------------------------------------- //--- Modbus Server 처리 함수 //---------------------------------------------------------------------------------------------- //--- on(event: 'connection', listener: (client: ModbusServerClient) => void): this; //--- on(event: 'connection', listener: (socket: Socket) => void): this; onConnection(client) { client._guid = uuidv1(); this._clients.push(client); logger.info(`modules/ModbusServer.js :: onConnection(client), client count : ${this._clients.length}`); this._displayClients(); logger.info(' '); let serverClient = new ModbusServerClientClass(this, client); } //--- 장비로부터 데이터를 읽어서 값을 반환 한다. _getCoils(request) { if (typeof(this._coils[`device_${request.unitId}`]) != 'undefined') { return this._coils[`device_${request.unitId}`]; } return null; } //--- Client로부터 받은 값으로 장비를 설정 한다. _setCoils(unitId, newValue) { this._coils[`device_${unitId}`] = newValue; } _getDiscrete(request) { if (typeof(this._discrete[`device_${request.unitId}`]) != 'undefined') { return this._discrete[`device_${request.unitId}`]; } return null; } _SetDiscrete(unitId, newValue) { this._discrete[`device_${unitId}`] = newValue; } _getInput(request) { if (typeof(this._input[`device_${request.unitId}`]) != 'undefined') { return this._input[`device_${request.unitId}`]; } return null; } _SetInput(unitId, newValue) { this._input[`device_${unitId}`] = newValue; } _getHolding(request) { if (typeof(this._holding[`device_${request.unitId}`]) != 'undefined') { return this._holding[`device_${request.unitId}`]; } return null; } _setHolding(unitId, newValue) { this._holding[`device_${unitId}`] = newValue; } //--- readDiscreteInputs(start, count) : Client에서 bit 단위의 discrete(디지털 입력)을 가져 온다. //--- start : 시작 주소 (0, 1, 2, ...) //--- count : bits 수 //--- onReadDiscreteInputs(request, cb) //--- request : ModbusTCPRequest //--- cb : 인수로 받은 Buffer를 Client로 전송 한다. //--- this._server.discrete가 false인 경우, onReadDiscreteInputs()가 호출된다. //--- this._server.discrete가 Buffer인 경우, modbus-server-response-handler.js에 정의된대로 실행 된다. //--- //--- Data Access > Bit access > Physical Discrete Inputs > Read Discrete Inputs //--- on(event: 'readDiscreteInputs', listener: (request: AbstractRequest, cb: BufferCB) => void): this; async onReadDiscreteInputs(request, cb) { logger.info(`modules/ModbusServer.js :: onReadDiscreteInputs, ${JSON.stringify(request)}`); this._errorResponse(request, cb, 0x01); // let discrete = this._getDiscrete(request); // if (discrete != null) { // const responseBody = jsmodbus.responses.ReadDiscreteInputsResponseBody.fromRequest(request.body, discrete); // const response = jsmodbus.ModbusTCPResponse.fromRequest(request, responseBody); // const payload = response.createPayload(); // cb(payload); // } } //--- on(event: 'preReadDiscreteInputs', listener: (request: AbstractRequest, cb: BufferCB) => void): this; async onPreReadDiscreteInputs(request, cb) { logger.info(`modules/ModbusServer.js :: onPreReadDiscreteInputs, ${JSON.stringify(request)}`); } //--- on(event: 'postReadDiscreteInputs', listener: (request: AbstractRequest, cb: BufferCB) => void): this; async onPostReadDiscreteInputs(request, cb) { logger.info(`modules/ModbusServer.js :: onPostReadDiscreteInputs, ${JSON.stringify(request)}`); } //--- readCoils(start, count) : Client에서 bit 단위의 coils(디지털 출력)을 가져 온다. //--- start : 시작 주소 (0, 1, 2, ...) //--- count : bits 수 //--- Data Access > Bit access > Internal Bits or Physical Coils > Read Coils //--- on(event: 'readCoils', listener: (request: AbstractRequest, cb: BufferCB) => void): this; async onReadCoils(request, cb) { logger.info(`modules/ModbusServer.js :: onReadCoils, ${JSON.stringify(request)}`); this._errorResponse(request, cb, 0x01); // let coils = this._getCoils(request); // if (coils != null) { // const responseBody = jsmodbus.responses.ReadCoilsResponseBody.fromRequest(request.body, coils); // const response = jsmodbus.ModbusTCPResponse.fromRequest(request, responseBody); // const payload = response.createPayload(); // cb(payload); // } } //--- on(event: 'preReadCoils', listener: (request: AbstractRequest, cb: BufferCB) => void): this; async onPreReadCoils(request, cb) { logger.info(''); logger.info(`modules/ModbusServer.js :: onPreReadCoils(request, cb), ${JSON.stringify(request)}`); //--- request : {"_id":1,"_protocol":0,"_length":6,"_unitId":245,"_body":{"_fc":1,"_start":0,"_count":13}} } //--- on(event: 'postReadCoils', listener: (request: AbstractRequest, cb: BufferCB) => void): this; async onPostReadCoils(request, cb) { logger.info(`modules/ModbusServer.js :: onPostReadCoils(request, cb), ${JSON.stringify(request)}`); //--- request : {"_id":1,"_protocol":0,"_length":6,"_unitId":245,"_body":{"_fc":1,"_start":0,"_count":13}} } //--- writeSingleCoil(address, value) : Client에서 하나의 bit를 coils(디지털 출력)에 기록 한다. //--- address : 주소 //--- value : 값 (true, false) 또는 new Buffer(0x01) //--- Data Access > Bit access > Internal Bits or Physical Coils > Write Single Coil // on(event: 'writeSingleCoil', listener: (request: AbstractRequest, cb: BufferCB) => void): this; async onWriteSingleCoil(request, cb) { logger.info(`modules/ModbusServer.js :: onWriteSingleCoil, ${JSON.stringify(request)}`); this._errorResponse(request, cb, 0x01); // let coils = this._getCoils(request); // if (coils != null) { // const responseBody = jsmodbus.responses.WriteSingleCoilResponseBody.fromRequest(request.body); // const address = request.body.address; // const oldValue = coils.readUInt8(Math.floor(address / 8)); // let newValue; // if ((request.body.value !== 0xFF00) && (request.body.value !== 0x0000)) { // const exceptionBody = new jsmodbus.responses.ExceptionResponseBody(request.body.fc, 0x03); // const exceptionResponse = jsmodbus.ModbusTCPResponse.fromRequest(request, exceptionBody); // cb(exceptionResponse.createPayload()); // return; // } // if (request.body.value === 0xFF00) { // newValue = oldValue | Math.pow(2, address % 8); // } // else { // newValue = oldValue & ~Math.pow(2, address % 8); // } // // logger.info(` ${address}, ${request.body.value} -> ${oldValue}, ${newValue}`); // if (responseBody.address / 8 > coils.length) { // const exceptionBody = new jsmodbus.responses.ExceptionResponseBody(request.body.fc, 0x02); // const exceptionResponse = jsmodbus.ModbusTCPResponse.fromRequest(request, exceptionBody); // cb(exceptionResponse.createPayload()); // return; // } // else { // coils.writeUInt8(newValue, Math.floor(address / 8)); // this._setCoils(request.unitId, coils); // } // const response = jsmodbus.ModbusTCPResponse.fromRequest(request, responseBody); // const payload = response.createPayload(); // cb(payload); // } } // on(event: 'preWriteSingleCoil', listener: (request: AbstractRequest, cb: BufferCB) => void): this; async onPreWriteSingleCoil(request, cb) { logger.info(`modules/ModbusServer.js :: onPreWriteSingleCoil, ${JSON.stringify(request)}`); } // on(event: 'postWriteSingleCoil', listener: (request: AbstractRequest, cb: BufferCB) => void): this; async onPostWriteSingleCoil(request, cb) { logger.info(`modules/ModbusServer.js :: onPostWriteSingleCoil, ${JSON.stringify(request)}`); } //--- Data Access > Bit access > Internal Bits or Physical Coils > Write Multiple Coils //--- on(event: 'writeMultipleCoils', listener: (request: AbstractRequest, cb: BufferCB) => void): this; //--- on(event: 'writeMultipleCoils', listener: (coils: Buffer, oldStatus: BooleanArray) => void): this; //--- xx request : this._server의 coils의 Buffer //--- xx cb : this._server의 coils의 bits 배열 async onWriteMultipleCoils(request, cb) { logger.info(`modules/ModbusServer.js :: onWriteMultipleCoils(request, cb)`); this._errorResponse(request, cb, 0x01); // let coils = this._getCoils(request); // if (coils != null) { // const responseBody = jsmodbus.responses.WriteMultipleCoilsResponseBody.fromRequest(request.body); // const oldStatus = this.bufferToArrayStatus(coils); // const requestCoilValues = this.bufferToArrayStatus(request.body.valuesAsBuffer); // const start = request.body.address; // const end = start + request.body.quantity; // const newStatus = oldStatus.map((byte, i) => { // let value = byte; // const inRange = (i >= start && i < end); // if (inRange) { // const newValue = requestCoilValues.shift(); // value = (newValue !== undefined) ? newValue : byte; // } // return value; // }); // // this._server.emit('writeMultipleCoils', coils, oldStatus); // coils.fill(this.arrayStatusToBuffer(newStatus)); // this._setCoils(request.unitId, coils); // // this._server.emit('postWriteMultipleCoils', coils, newStatus); // const response = jsmodbus.ModbusTCPResponse.fromRequest(request, responseBody); // const payload = response.createPayload(); // cb(payload); // } } //--- on(event: 'preWriteMultipleCoils', listener: (request: AbstractRequest, cb: BufferCB) => void): this; //--- request : ModbusTCPRequest async onPreWriteMultipleCoils(request, cb) { logger.info(`modules/ModbusServer.js :: onPreWriteMultipleCoils(request, cb)`); // logger.info(` request : ${JSON.stringify(request)}`); // logger.info(` cb : ${JSON.stringify(cb)}`); // logger.info(request); } //--- on(event: 'postWriteMultipleCoils', listener: (coils: Buffer, newStatus: BooleanArray) => void): this; //--- on(event: 'postWriteMultipleCoils', listener: (request: AbstractRequest, cb: BufferCB) => void): this; async onPostWriteMultipleCoils(request, cb) { logger.info(`modules/ModbusServer.js :: onPostWriteMultipleCoils(request, cb)`); // if (request instanceof Buffer) { // logger.info(`modules/ModbusServer.js :: onPostWriteMultipleCoils(request, cb)`); // logger.info(` request : ${this._bufferToHex(request)}`); //--- request : Buffer // logger.info(` cb : ${JSON.stringify(cb)}`); // } else { // logger.info(`modules/ModbusServer.js :: onPostWriteMultipleCoils(request, cb)`); // logger.info(` request : ${JSON.stringify(request)}`); //--- request : ModbusTCPRequest // logger.info(` cb : ${JSON.stringify(cb)}`); // } } //--- Data Access > 16-bit access > Physical Input Registers > Read Input Register // on(event: 'readInputRegisters', listener: (request: AbstractRequest, cb: BufferCB) => void): this; async onReadInputRegisters(request, cb) { logger.info(`modules/ModbusServer.js :: onReadInputRegisters, ${JSON.stringify(request)}`); logger.info(`modules/ModbusServer.js :: onReadInputRegisters, unitId : ${request.unitId}, start : ${request.body.start}, count : ${request.body.count}`); let modbusId = Math.floor(request.body.start / request.unitId); let buf = await this._getDeviceData(modbusId, request.unitId); if (buf == null) { this._errorResponse(request, cb, 0x03); } else { const responseBody = jsmodbus.responses.ReadInputRegistersResponseBody.fromRequest(request.body, buf); const response = jsmodbus.ModbusTCPResponse.fromRequest(request, responseBody); const payload = response.createPayload(); cb(payload); } // let input = this._getInput(request); // if (input != null) { // const responseBody = jsmodbus.responses.ReadHoldingRegistersResponseBody.fromRequest(request.body, input); // const response = jsmodbus.ModbusTCPResponse.fromRequest(request, responseBody); // const payload = response.createPayload(); // cb(payload); // } } async _getDeviceData(modbusId, unitId) { try { logger.info(`modules/ModbusServer.js :: _getDeviceData, modbusId : ${modbusId}`); let query = { where: { modbusId: modbusId, site: config.modbus.kwater.site, deleted: false } }; let device = await modules.devices.model.table.findOne(query); if (device == null) { logger.error(`modules/ModbusServer.js :: _getDeviceData, modbusId : ${modbusId}, device not found.`); return null; } query = { where: { deviceKey: device.deviceKey }, order: [ ['id', 'DESC'] ], limit: 1 }; let devicedata2s = await modules.devicedata2s.model.table.findAll(query); if (devicedata2s.length != 1) { logger.error(`modules/ModbusServer.js :: _getDeviceData, modbusId : ${modbusId}, devicedata2s not found.`); return null; } let devicedata2 = devicedata2s[0]; let address = modbusId * unitId; let buf = Buffer.alloc((modbusId + 1) * (2 * unitId), 0x00); buf.write(devicedata2.siteKey, 2 * address, 'UTF-8'); address = address + 1; buf.write(' ' + devicedata2.type, 2 * address, 'UTF-8'); address = address + 1; buf.write(devicedata2.deviceKey, 2 * address, 'UTF-8'); address = address + 2; buf.write(devicedata2.statusDatetime.substring(2), 2 * address, 'UTF-8'); address = address + 6; buf.writeFloatBE(devicedata2.ac, 2 * address); address = address + 2; buf.writeFloatBE(devicedata2.preventPotential, 2 * address); address = address + 2; buf.writeFloatBE(devicedata2.batteryVoltage, 2 * address); address = address + 2; buf.writeFloatBE(devicedata2.sensitivity, 2 * address); address = address + 2; buf.write(' ' + devicedata2.dataMode, 2 * address, 'UTF-8'); address = address + 1; buf.writeInt16BE(devicedata2.dataCycle, 2 * address); address = address + 1; buf.writeInt16BE(devicedata2.networkCycle, 2 * address); address = address + 1; switch (device.interfaceVersion) { case '1.00': buf.write(0x0000, 2 * address, 'UTF-8'); address = address + 1; buf.write(0x0000, 2 * address, 'UTF-8'); address = address + 1; buf.write(0x0000, 2 * address, 'UTF-8'); address = address + 1; break; default: buf.writeFloatBE(devicedata2.temperature, 2 * address); address = address + 2; buf.writeInt16BE(devicedata2.humidity, 2 * address); address = address + 1; break; } address = address + (17 - 1); buf.write('' + devicedata2.dataMode, 2 * address, 'UTF-8'); address = address + 1; buf.writeInt16BE(devicedata2.dataCycle, 2 * address); address = address + 1; buf.writeInt16BE(devicedata2.networkCycle, 2 * address); address = address + 1; return buf; } catch (err) { return null; } } // on(event: 'preReadInputRegisters', listener: (request: AbstractRequest, cb: BufferCB) => void): this; async onPreReadInputRegisters(request, cb) { logger.info(`modules/ModbusServer.js :: onPreReadInputRegisters, ${JSON.stringify(request)}`); } // on(event: 'postReadInputRegisters', listener: (request: AbstractRequest, cb: BufferCB) => void): this; async onPostReadInputRegisters(request, cb) { logger.info(`modules/ModbusServer.js :: onPostReadInputRegisters, ${JSON.stringify(request)}`); } //--- Data Access > 16-bit access > Internal Registers or Physical Output Registers > Read Multiple Holding Registers, fc : 3 // on(event: 'readHoldingRegisters', listener: (request: AbstractRequest, cb: BufferCB) => void): this; async onReadHoldingRegisters(request, cb) { logger.info(`modules/ModbusServer.js :: onReadHoldingRegisters, ${JSON.stringify(request)}`); let modbusId = Math.floor(request.body.start / request.unitId); let buf = await this._getDeviceData(modbusId, request.unitId); if (buf == null) { this._errorResponse(request, cb, 0x03); } else { const responseBody = jsmodbus.responses.ReadHoldingRegistersResponseBody.fromRequest(request.body, buf); const response = jsmodbus.ModbusTCPResponse.fromRequest(request, responseBody); const payload = response.createPayload(); cb(payload); } // let holding = this._getHolding(request); // if (holding != null) { // const responseBody = jsmodbus.responses.ReadHoldingRegistersResponseBody.fromRequest(request.body, holding); // const response = jsmodbus.ModbusTCPResponse.fromRequest(request, responseBody); // const payload = response.createPayload(); // cb(payload); // } } // on(event: 'preReadHoldingRegisters', listener: (request: AbstractRequest, cb: BufferCB) => void): this; async onPreReadHoldingRegisters(request, cb) { logger.info(`modules/ModbusServer.js :: onPreReadHoldingRegisters, ${JSON.stringify(request)}`); } // on(event: 'postReadHoldingRegisters', listener: (request: AbstractRequest, cb: BufferCB) => void): this; async onPostReadHoldingRegisters(request, cb) { logger.info(`modules/ModbusServer.js :: onPostReadHoldingRegisters, ${JSON.stringify(request)}`); } //--- Data Access > 16-bit access > Internal Registers or Physical Output Registers > Write Single Holding Register, fc : 6 // on(event: 'writeSingleRegister', listener: (request: AbstractRequest, cb: BufferCB) => void): this; async onWriteSingleRegister(request, cb) { logger.info(`modules/ModbusServer.js :: onWriteSingleRegister, ${JSON.stringify(request)}`); this._errorResponse(request, cb, 0x01); // let holding = this._getHolding(request); // if (holding != null) { // const responseBody = jsmodbus.responses.WriteSingleRegisterResponseBody.fromRequest(request.body); // if (responseBody.address * 2 > holding.length) { // const exceptionBody = new jsmodbus.responses.ExceptionResponseBody(request.body.fc, 0x02); // const exceptionResponse = jsmodbus.ModbusTCPResponse.fromRequest(request, exceptionBody); // cb(exceptionResponse.createPayload()); // return; // } // else { // holding.writeUInt16BE(responseBody.value, responseBody.address * 2); // this._setHolding(request.unitId, holding); // } // const response = jsmodbus.ModbusTCPResponse.fromRequest(request, responseBody); // const payload = response.createPayload(); // cb(payload); // } } // on(event: 'preWriteSingleRegister', listener: (request: AbstractRequest, cb: BufferCB) => void): this; async onPreWriteSingleRegister(request, cb) { logger.info(`modules/ModbusServer.js :: onPreWriteSingleRegister, ${JSON.stringify(request)}`); } // on(event: 'postWriteSingleRegister', listener: (request: AbstractRequest, cb: BufferCB) => void): this; async onPostWriteSingleRegister(request, cb) { logger.info(`modules/ModbusServer.js :: onPostWriteSingleRegister, ${JSON.stringify(request)}`); } //--- Data Access > 16-bit access > Internal Registers or Physical Output Registers > Write Multiple Holding Registers, fc : 16 // on(event: 'writeMultipleRegisters', listener: (request: AbstractRequest, cb: BufferCB) => void): this; // on(event: 'writeMultipleRegisters', listener: (holdingRegisters: Buffer) => void): this; async onWriteMultipleRegisters(request, cb) { logger.info(`modules/ModbusServer.js :: onWriteMultipleRegisters, ${JSON.stringify(request)}`); try { let modbusId = Math.floor(request.body.address / request.unitId); let address = request.body.address - modbusId * request.unitId; logger.info(`modules/ModbusServer.js :: onWriteMultipleRegisters, modbusId : ${modbusId}, ${address}`); //--- To-Do : 시작 주소가 18이 아니라도 18 ~ 20 address가 포함된 경우 write 처리할 것 if (address != 18) { this._errorResponse(request, cb, 0x02); return; } if (request.body.quantity != 3) { this._errorResponse(request, cb, 0x03); return; } let buf = request.body.valuesAsBuffer; let dataMode = buf.toString('UTF-8', 0, 2).trim(); let dataCycle = buf.readUInt16BE(2); let networkCycle = buf.readUInt16BE(4); logger.info(`Data Mode : [${dataMode}]`); logger.info(`수집주기 : [${dataCycle}]`); logger.info(`통신주기 : [${networkCycle}]`); let query = { where: { modbusId: modbusId, site: config.modbus.kwater.site, deleted: false } }; let device = await modules.devices.model.table.findOne(query); if (device == null) { logger.error(`modules/ModbusServer.js :: onWriteMultipleRegisters, modbusId : ${modbusId}, device not found.`); this._errorResponse(request, cb, 0x02); return; } // let isApply = this._getFlagString(device.apply, 2); // if (isApply == '0') { // //--- 수정 사항을 장비에 적용하기 전에 원본 데이터 백업 // this._devices[`device_${device.id}`] = device; // } let networkCycleUnit = '분'; let dataCycleUnit = '분'; switch (dataMode) { case '0': case '4': networkCycleUnit = '분'; dataCycleUnit = '분'; break; case '1': networkCycleUnit = '시'; dataCycleUnit = '분'; break; case '2': networkCycleUnit = '시'; dataCycleUnit = '시'; break; case '9': break; default: this._errorResponse(request, cb, 0x03); return; } let item = { dataMode: dataMode, networkCycle: networkCycle, networkCycleUnit: networkCycleUnit, dataCycle: dataCycle, dataCycleUnit: dataCycleUnit, setDatetimeReq: moment().format('YYYY-MM-DDTHH:mm:ss'), apply: this._setFlagString(device.apply, 2, '1') }; let results = await modules.devices.model.table.update(item, { where: { id: device.id, deleted: false } }); if (results[0] != 1) { this._errorResponse(request, cb, 0x03); return; } const responseBody = jsmodbus.responses.WriteMultipleRegistersResponseBody.fromRequest(request.body); const response = jsmodbus.ModbusTCPResponse.fromRequest(request, responseBody); const payload = response.createPayload(); cb(payload); } catch (err) { this._errorResponse(request, cb, 0x02); } // let holding = this._getHolding(request); // if (holding != null) { // const responseBody = jsmodbus.responses.WriteMultipleRegistersResponseBody.fromRequest(request.body); // if (((request.body.address * 2) + request.body.values.length) > holding.length) { // this._errorResponse(request, cb, 0x02); // return; // } // else { // // this._server.emit('writeMultipleRegisters', holding); // holding.fill(new Uint8Array(request.body.values), request.body.address * 2, request.body.address * 2 + request.body.values.length); // this._setHolding(request.unitId, holding); // // this._server.emit('postWriteMultipleRegisters', holding); // } // const response = jsmodbus.ModbusTCPResponse.fromRequest(request, responseBody); // const payload = response.createPayload(); // cb(payload); // } } _initFlagString(source) { let tmpStr = source + '0000000000000000000000000000000000000000'; return tmpStr.substring(0, this._maxPos); } _getFlagString(source, pos) { let tmpStr = this._initFlagString(source); tmpStr = tmpStr.substring(pos - 1, pos); return tmpStr; } _setFlagString(source, pos, set) { let tmpStr = this._initFlagString(source); tmpStr = tmpStr.substring(0, pos - 1) + set + tmpStr.substring(pos, this._maxPos); return tmpStr; } // export declare const ErrorMessages: { // readonly 0x01: "ILLEGAL FUNCTION"; // readonly 0x02: "ILLEGAL DATA ADDRESS"; // readonly 0x03: "ILLEGAL DATA VALUE"; // readonly 0x04: "SLAVE DEVICE FAILURE"; // readonly 0x05: "ACKNOWLEDGE"; // readonly 0x06: "SLAVE DEVICE BUSY"; // readonly 0x08: "MEMORY PARITY ERROR"; // readonly 0x0A: "GATEWAY PATH UNAVAILABLE"; // readonly 0x0B: "GATEWAY TARGET DEVICE FAILED TO RESPOND"; // }; _errorResponse(request, cb, errorCode) { const exceptionBody = new jsmodbus.responses.ExceptionResponseBody(request.body.fc, errorCode); const exceptionResponse = jsmodbus.ModbusTCPResponse.fromRequest(request, exceptionBody); cb(exceptionResponse.createPayload()); } // on(event: 'preWriteMultipleRegisters', listener: (request: AbstractRequest, cb: BufferCB) => void): this; async onPreWriteMultipleRegisters(request, cb) { logger.info(`modules/ModbusServer.js :: onPreWriteMultipleRegisters, ${JSON.stringify(request)}`); } // on(event: 'postWriteMultipleRegisters', listener: (holdingRegisters: Buffer) => void): this; // on(event: 'postWriteMultipleRegisters', listener: (request: AbstractRequest, cb: BufferCB) => void): this; async onPostWriteMultipleRegisters(request, cb) { logger.info(`modules/ModbusServer.js :: onPostWriteMultipleRegisters, ${JSON.stringify(request)}`); } //---------------------------------------------------------------------------------------------- //--- Server Socket 처리 함수 //---------------------------------------------------------------------------------------------- // this._netServer.on(event: 'close', listener: () => void): this; onNetClose() { logger.info(`modules/ModbusServer.js :: onNetClose`); } //--- this._netServer.on(event: 'connection', listener: (socket: Socket) => void): this; onNetConnection(socket) { logger.info(`modules/ModbusServer.js :: onNetConnection(socket)`); } // this._netServer.on(event: 'error', listener: (err: Error) => void): this; onNetError(err) { logger.info(`modules/ModbusServer.js :: onNetError, ${JSON.stringify(err)}`); } // this._netServer.on(event: 'listening', listener: () => void): this; onNetListening() { logger.info(`modules/ModbusServer.js :: onNetListening`); } //---------------------------------------------------------------------------------------------- //--- 일반 함수 //---------------------------------------------------------------------------------------------- _displayClients() { for (let idx = 0; idx < this._clients.length; idx++) { logger.info(` client ${idx + 1} : guid - ${this._clients[idx]._guid}`); } } _deleteClient(guid) { logger.info(`modules/ModbusServer.js :: _deleteClient, guid : ${guid}`); this._clients = this._clients.filter((client) => client._guid != guid); this._displayClients(); logger.info(' '); } _bufferToHex(buf) { let hex = buf.toString('hex'); let data = []; for (let idx = 0; idx < hex.length; idx = idx + 2) { data.push(hex.substring(idx, idx + 2)); } return data.join(' '); } bufferToArrayStatus(buffer) { const statusArray = []; let pos; let curByteIdx; let curByte; if (!(buffer instanceof Buffer)) { return statusArray; } for (let i = 0; i < buffer.length * 8; i += 1) { pos = i % 8; curByteIdx = Math.floor(i / 8); curByte = buffer.readUInt8(curByteIdx); const value = ((curByte & Math.pow(2, pos)) > 0); statusArray.push(value ? 1 : 0); } return statusArray; } arrayStatusToBuffer(array) { const byteCount = array instanceof Array ? Math.ceil(array.length / 8) : 0; const buffer = Buffer.alloc(byteCount); if (!(array instanceof Array)) { return buffer; } let byteOffset; let bitOffset; let byte; for (let i = 0; i < array.length; i += 1) { byteOffset = Math.floor(i / 8); bitOffset = i % 8; byte = buffer.readUInt8(byteOffset); byte += array[i] ? Math.pow(2, bitOffset) : 0; buffer.writeUInt8(byte, byteOffset); } return buffer; } } module.exports = ModbusServer;