Spaces:
Running
Running
| /** | |
| * 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; | |