obcon-scada / app /modules /ModbusServer.js
chanmin0723's picture
Initial obcon SCADA deploy
e4bf523
Raw
History Blame Contribute Delete
48.4 kB
'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<Buffer>, cb) > onPostWriteMultipleCoils(request<ModbusTCPRequest>, 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<any, any, any>) => 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<Buffer>, 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<ModbusTCPRequest>, cb)`);
// logger.info(` request<ModbusTCPRequest> : ${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<ModbusTCPRequest>, cb)`);
// if (request instanceof Buffer) {
// logger.info(`modules/ModbusServer.js :: onPostWriteMultipleCoils(request<Buffer>, cb)`);
// logger.info(` request<Buffer> : ${this._bufferToHex(request)}`); //--- request : Buffer
// logger.info(` cb : ${JSON.stringify(cb)}`);
// } else {
// logger.info(`modules/ModbusServer.js :: onPostWriteMultipleCoils(request<ModbusTCPRequest>, cb)`);
// logger.info(` request<ModbusTCPRequest> : ${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;