UNPKG

node-modbustcp

Version:

A ModbusTcp driver based on modbus-serial

721 lines (684 loc) 30.5 kB
/** * 引用 modbus-serial 模块 * @link https://github.com/yaacov/node-modbus-serial */ import ModbusSerial from 'modbus-serial'; import { ModbusTCPClientInterface, Register, RegisterAll, Package, PackageItem, ResultInterface } from './interface'; import { EnumRegisterType, EnumDataType, RegisterName } from './enum'; import { ReadCoilResult, ReadRegisterResult } from 'modbus-serial/ModbusRTU'; export default class ModbusTCPClient { /** 创建一个新的 client */ private client: ModbusSerial = new ModbusSerial(); /** 主机地址 */ host: string; /** 端口号 */ port: number; /** 设备ID */ deviceID: number; /** 连接超时时间,单位:毫秒 */ timeout: number = 3000; /** length of one package */ packageLength: number = 100; /** 最大重连时长,单位:小时,0为无限时长 */ maxReconnectTime: number = 0; /** 尝试重连时间,单位:秒 */ tryReconnectTime: number = 3; /** 所有寄存器 */ registerMap: Map<string, RegisterAll> = new Map(); /** 寄存器包 */ private registerPackage: Package = { Coil: [], DisInput: [], Input: [], Holding: [] }; /** 轮询间隔时间,单位:毫秒 */ pollingInterval: number = 1000; /** 初次连接失败时间戳 */ private firstConnFailTime: number = 0; constructor(option: ModbusTCPClientInterface) { this.host = option.host; this.port = option.port; this.deviceID = option.deviceID; this.timeout = option.timeout === undefined ? this.timeout : option.timeout; this.packageLength = option.packageLength === undefined ? this.packageLength : option.packageLength; this.maxReconnectTime = option.maxReconnectTime === undefined ? this.maxReconnectTime : option.maxReconnectTime; this.tryReconnectTime = option.tryReconnectTime === undefined ? this.tryReconnectTime : option.tryReconnectTime; } /** * 连接设备 */ private async connect() { return await this.client.connectTCP(this.host, { port: this.port }) .then(() => { this.client.setID(this.deviceID); this.client.setTimeout(this.timeout); console.log(`connect ${this.host}:${this.port} ${this.deviceID} success ${new Date()}`); return this.client; }) .catch(e => { return e.message }); } /** * 断开连接 */ disconnect() { this.client.close((closeRes: any) => { return closeRes }); } /** * 将设置的寄存器数组进行解析整合并排序,然后存入 Map 中 * @param registerArr 寄存器数组 */ private sortRegisters(registerArr: Register[]) { let sortedRegisterArr = []; for (let i = 0; i < registerArr.length; i++) { let tempRegister: RegisterAll = { register: registerArr[i].register, registerType: EnumRegisterType.Coil, startAddr: 0, endAddr: 0, dataType: EnumDataType.Bit, packageIndex: 0, bufferAddr: [], value: null, timestamp: new Date(), quality: '' }; // 寄存器类型 let registerType = registerArr[i].register.substr(0, 1); tempRegister.registerType = RegisterName[registerType]; // 起始地址 tempRegister.startAddr = +registerArr[i].register.substr(1); // 结束地址 switch (registerArr[i].dataType) { case EnumDataType.Bit: case EnumDataType.Binary: case EnumDataType.Decimal: case EnumDataType.Int: case EnumDataType.Hex: tempRegister.endAddr = tempRegister.startAddr; break; case EnumDataType.Float: case EnumDataType.SwappedFloat: case EnumDataType.Long: case EnumDataType.SwappedLong: tempRegister.endAddr = tempRegister.startAddr + 1; break; case EnumDataType.Double: case EnumDataType.SwappedDouble: tempRegister.endAddr = tempRegister.startAddr + 3; break; default: tempRegister.endAddr = tempRegister.startAddr; } // 数据类型 tempRegister.dataType = registerArr[i].dataType; sortedRegisterArr.push(tempRegister); } // 按照寄存器的其实地址从小到大排序 sortedRegisterArr = sortedRegisterArr.sort(sortRegister); for (let i = 0; i < sortedRegisterArr.length; i++) { this.registerMap.set(sortedRegisterArr[i].register, sortedRegisterArr[i]); } function sortRegister(r1: RegisterAll, r2: RegisterAll) { return r1.startAddr - r2.startAddr; } } /** * 根据排序后的寄存器数组,创建打包信息 this.registerPackage 和一部分寄存器信息,准备读取 */ private createPack() { for (let register of this.registerMap.values()) { let registerType = register.registerType; let packageItemArr: PackageItem[]; let packageLength: number; switch (registerType) { case EnumRegisterType.Coil: packageItemArr = this.registerPackage.Coil; packageLength = this.packageLength * 16; break; case EnumRegisterType.DiscreteInput: packageItemArr = this.registerPackage.DisInput; packageLength = this.packageLength * 16; break; case EnumRegisterType.Input: packageItemArr = this.registerPackage.Input; packageLength = this.packageLength; break; case EnumRegisterType.Holding: packageItemArr = this.registerPackage.Holding; packageLength = this.packageLength; break; default: packageItemArr = this.registerPackage.Coil; packageLength = this.packageLength * 16; } // 当前寄存器开始地址 let startAddr = register.startAddr; // 当前寄存器结束地址 let endAddr = register.endAddr; if (packageItemArr.length === 0) { // 包中还没项 let packageItem: PackageItem = { startAddr: startAddr, endAddr: endAddr, length: endAddr - startAddr + 1, datas: null, timestamp: new Date(), quality: '' } packageItemArr.push(packageItem); } else { // 已经有项了 // 获取最后一个包的索引号 let packageIndex = packageItemArr.length - 1; if (endAddr - packageItemArr[packageIndex].startAddr >= packageLength) { // 如果当前寄存器的结束地址 - 包的开始地址 >= 包的长度 // 直接新开一个包 let packageItem: PackageItem = { startAddr: startAddr, endAddr: endAddr, length: endAddr - startAddr + 1, datas: null, timestamp: new Date(), quality: '' } packageItemArr.push(packageItem); } else { // 否则 if (endAddr > packageItemArr[packageIndex].endAddr) { // 如果当前寄存器的结束地址 > 当前包的结束地址 // 把当前寄存器的结束地址作为当前包的结束地址 packageItemArr[packageIndex].endAddr = endAddr; packageItemArr[packageIndex].length = endAddr - packageItemArr[packageIndex].startAddr + 1; } } } // 包索引号生成 register.packageIndex = packageItemArr.length - 1; // 数据长度,单位:字 Word let dataLength = 1; switch (register.dataType) { case EnumDataType.Bit: case EnumDataType.Binary: case EnumDataType.Decimal: case EnumDataType.Hex: dataLength = 0; break; case EnumDataType.Int: dataLength = 1; break; case EnumDataType.Float: case EnumDataType.SwappedFloat: case EnumDataType.Long: case EnumDataType.SwappedLong: dataLength = 2; break; case EnumDataType.Double: case EnumDataType.SwappedDouble: dataLength = 4; break; default: dataLength = 0; } // buffer 地址是按字节 Byte 算的 // buffer 起始地址、结束地址生成 let bufferStartAddr: number, bufferEndAddr: number; if (dataLength !== 0) { bufferStartAddr = (startAddr - packageItemArr[register.packageIndex].startAddr) * 2; bufferEndAddr = bufferStartAddr + dataLength * 2 - 1; } else { bufferStartAddr = startAddr - packageItemArr[register.packageIndex].startAddr; bufferEndAddr = bufferStartAddr; } // bufferAddr 生成 register.bufferAddr.push(bufferStartAddr, bufferEndAddr); } } /** * 设置准备要进行数据读取的寄存器 * @param registerArr 准备读取的寄存器数据 */ setRegisters(registerArr: Register[]) { this.sortRegisters(registerArr); this.createPack(); return this; } /** * 对数据包进行读取 */ private async readData() { let promiseArr: Promise<ReadCoilResult | ReadRegisterResult | void>[] = []; let coilPakArr = this.registerPackage.Coil; let disInputPakArr = this.registerPackage.DisInput; let inputPakArr = this.registerPackage.Input; let holdingPakArr = this.registerPackage.Holding; // 读取线圈包 for (let i = 0; i < coilPakArr.length; i++) { // 数据包起始地址 let startAddr = coilPakArr[i].startAddr - 1; // 数据包长度 let length = coilPakArr[i].length; let readPromise: Promise<ReadCoilResult | void> = this.client.readCoils(startAddr, length) .then(datas => { this.registerPackage.Coil[i].datas = datas; coilPakArr[i].timestamp = new Date(); coilPakArr[i].quality = 'GOOD'; return datas; }) .catch(e => { coilPakArr[i].timestamp = new Date(); coilPakArr[i].quality = e.message; }); promiseArr.push(readPromise); } // 读取离散输入包 for (let i = 0; i < disInputPakArr.length; i++) { // 数据包起始地址 let startAddr = disInputPakArr[i].startAddr - 1; // 数据包长度 let length = disInputPakArr[i].length; let readPromise: Promise<ReadCoilResult | void> = this.client.readDiscreteInputs(startAddr, length) .then(datas => { this.registerPackage.DisInput[i].datas = datas; disInputPakArr[i].timestamp = new Date(); coilPakArr[i].quality = 'GOOD'; }) .catch(e => { disInputPakArr[i].timestamp = new Date(); disInputPakArr[i].quality = e.message; }); promiseArr.push(readPromise); } // 读取输入包 for (let i = 0; i < inputPakArr.length; i++) { // 数据包起始地址 let startAddr = inputPakArr[i].startAddr - 1; // 数据包长度 let length = inputPakArr[i].length; let readPromise: Promise<ReadRegisterResult | void> = this.client.readInputRegisters(startAddr, length) .then(datas => { this.registerPackage.Input[i].datas = datas; inputPakArr[i].timestamp = new Date(); inputPakArr[i].quality = 'GOOD'; }) .catch(e => { inputPakArr[i].timestamp = new Date(); inputPakArr[i].quality = e.message; }); promiseArr.push(readPromise); } // 读取保持包 for (let i = 0; i < holdingPakArr.length; i++) { // 数据包起始地址 let startAddr = holdingPakArr[i].startAddr - 1; // 数据包长度 let length = holdingPakArr[i].length; let readPromise: Promise<ReadRegisterResult | void> = this.client.readHoldingRegisters(startAddr, length) .then(datas => { this.registerPackage.Holding[i].datas = datas; holdingPakArr[i].timestamp = new Date(); holdingPakArr[i].quality = 'GOOD'; }) .catch(e => { holdingPakArr[i].timestamp = new Date(); holdingPakArr[i].quality = e.message; }); promiseArr.push(readPromise); } // 当所有数据读取完毕之后对数据进行派发,派发到结果集中 await Promise.all(promiseArr).then(() => { this.distribute() }); } /** * 分派数据值到 registerMap 的 value、timestamp、quality 中,并返回数据 */ private distribute() { for (let register of this.registerMap.values()) { // 数据类型 let dataType = register.dataType; // buffer 地址数组 let bufferAddr = register.bufferAddr; // 包索引号 let packageIndex = register.packageIndex; // 寄存器类型 let registerType = ''; switch (register.registerType) { case EnumRegisterType.Coil: registerType = "Coil"; break; case EnumRegisterType.DiscreteInput: registerType = "DisInput"; break; case EnumRegisterType.Input: registerType = "Input"; break; case EnumRegisterType.Holding: registerType = "Holding"; break; default: registerType = "Coil"; } register.timestamp = this.registerPackage[registerType][packageIndex].timestamp; register.quality = this.registerPackage[registerType][packageIndex].quality; // 包读取完成之后生成的 buffer 数据 let bufferDatas = this.registerPackage[registerType][packageIndex].datas; // 一些中间变量 let bufferArr: number[], sortedBufferArr: number[], newBuffer: Buffer; // 根据数据类型生成最终要返回的数据 if (bufferDatas) { switch (dataType) { case EnumDataType.Bit: register.value = bufferDatas.data[bufferAddr[0]]; break; case EnumDataType.Binary: register.value = bufferDatas.data[bufferAddr[0]].toString(2); break; case EnumDataType.Decimal: register.value = bufferDatas.data[bufferAddr[0]]; break; case EnumDataType.Int: register.value = bufferDatas.buffer.readInt16BE(bufferAddr[0]); break; case EnumDataType.Hex: register.value = bufferDatas.data[bufferAddr[0]].toString(16); break; case EnumDataType.Float: bufferArr = bufferDatas.buffer.slice(bufferAddr[0], bufferAddr[1] + 1); // 改变顺序 0123 => 2301 sortedBufferArr = [bufferArr[2], bufferArr[3], bufferArr[0], bufferArr[1]]; newBuffer = Buffer.from(sortedBufferArr); register.value = newBuffer.readFloatBE(0); break; case EnumDataType.SwappedFloat: bufferArr = bufferDatas.buffer.slice(bufferAddr[0], bufferAddr[1] + 1); // 顺序 0123 即可 newBuffer = Buffer.from(bufferArr); register.value = newBuffer.readFloatBE(0); break; case EnumDataType.Long: bufferArr = bufferDatas.buffer.slice(bufferAddr[0], bufferAddr[1] + 1); // 改变顺序 0123 => 2301 sortedBufferArr = [bufferArr[2], bufferArr[3], bufferArr[0], bufferArr[1]]; newBuffer = Buffer.from(sortedBufferArr); register.value = newBuffer.readInt32BE(0); break; case EnumDataType.SwappedLong: bufferArr = bufferDatas.buffer.slice(bufferAddr[0], bufferAddr[1] + 1); // 顺序 0123 即可 newBuffer = Buffer.from(bufferArr); register.value = newBuffer.readInt32BE(0); break; case EnumDataType.Double: bufferArr = bufferDatas.buffer.slice(bufferAddr[0], bufferAddr[1] + 1); // 改变顺序 0123 4567 => 6745 2301 sortedBufferArr = [bufferArr[6], bufferArr[7], bufferArr[4], bufferArr[5], bufferArr[2], bufferArr[3], bufferArr[0], bufferArr[1]]; newBuffer = Buffer.from(sortedBufferArr); register.value = newBuffer.readDoubleBE(0); break; case EnumDataType.SwappedDouble: bufferArr = bufferDatas.buffer.slice(bufferAddr[0], bufferAddr[1] + 1); // 顺序 0123 4567 即可 newBuffer = Buffer.from(bufferArr); register.value = newBuffer.readDoubleBE(0); break; default: register.value = bufferDatas.data[bufferAddr[0]]; } } } } /** * 读取数据一次,然后断开 */ async readOnce() { let connRes = await this.connect(); if (this.client.isOpen === true) { await this.readData(); this.disconnect(); return this.registerMap; } else { return connRes; } } /** * 轮询,外部调用 * @param {Number} interval 轮询间隔,单位:毫秒 */ async polling(interval?: number) { let res: ResultInterface = { result: false, info: null } if (interval !== undefined) { // 如果未传入数字 Interval,那么使用默认值 this.pollingInterval = interval; } if (this.client.isOpen === true) { await this.readData(); setTimeout(() => { this.polling() }, this.pollingInterval); res.result = true; res.info = this.registerMap; return res; } else { let connRes = await this.connect(); if (typeof connRes !== 'string') { await this.readData(); setTimeout(() => { this.polling() }, this.pollingInterval); res.result = true; res.info = this.registerMap; return res; } else { await this.reconnect(); res.info = 'reconnecting'; return res; } } } /** * 重连 */ private async reconnect() { console.log(`try to reconnect ${this.host}:${this.port} ${this.deviceID} ${new Date()}`); const connRes = await this.connect(); if (typeof connRes !== 'string') { // 重连成功 this.firstConnFailTime = 0; this.polling(); } else { // 重连失败 if (this.firstConnFailTime === 0) { this.firstConnFailTime = new Date().getTime(); } if (this.maxReconnectTime !== 0) { // 最大尝试重连时长不为无限 if (new Date().getTime() - this.firstConnFailTime <= this.maxReconnectTime * 60 * 60 * 1000) { // 未超过最大尝试重连时间,继续重连 setTimeout(async () => { await this.reconnect(); }, this.tryReconnectTime * 1000); } else { console.log(`${this.host}:${this.port} ${this.deviceID} ${new Date()} max reconnect time has come, connetion closed`); } } else { // 最大尝试重连时长为无限 setTimeout(async () => { await this.reconnect(); }, this.tryReconnectTime * 1000); } } } /** * 写数据 * @param register 寄存器 * @param value 要写的值 */ async writeData(register: string, value: boolean | number | string | null) { let reg = this.registerMap.get(register); let result: ResultInterface = { result: true, info: '', }; if (reg === undefined) { // 从设置的寄存器表中也没有找到此寄存器信息 result.result = false; result.info = 'register is not exist'; } else { // 找到了寄存器 let regAddr = reg.startAddr - 1; let regType = reg.registerType; let val: any; if (regType === EnumRegisterType.Coil) { if (typeof value !== 'boolean') { result.result = false; result.info = 'wrong value type'; } else { val = value; } } else if (reg.registerType === EnumRegisterType.Holding) { switch (reg.dataType) { case EnumDataType.Decimal: case EnumDataType.Int: if (typeof value !== 'number' || value % 1 !== 0) { // 不是整数 result.result = false; result.info = 'value is not an integer'; } else { val = [value % 65536]; } break; case EnumDataType.Binary: if (/^[0-1]{1,16}$/.test(value + '')) { val = parseInt(value + '', 2); } else { result.result = false; result.info = 'value is illegal'; } break; case EnumDataType.Hex: if (/^[0-9A-F]{1,4}$/.test(value + '')) { val = parseInt(value + '', 16); } else { result.result = false; result.info = 'value is illegal'; } break; case EnumDataType.Float: try { if (typeof value === 'number') { let buffer = Buffer.alloc(4); buffer.writeFloatBE(value, 0); val = [buffer.readUInt16BE(2), buffer.readUInt16BE(0)]; } else { result.result = false; result.info = 'value is not a number'; } } catch (e) { result.result = false; result.info = e.message; } break; case EnumDataType.SwappedFloat: try { if (typeof value === 'number') { let buffer = Buffer.alloc(4); buffer.writeFloatBE(value, 0); val = [buffer.readUInt16BE(0), buffer.readUInt16BE(2)]; } else { result.result = false; result.info = 'value is not a number'; } } catch (e) { result.result = false; result.info = e.message; } break; case EnumDataType.Long: try { if (typeof value === 'number') { let buffer = Buffer.alloc(4); buffer.writeInt32BE(value, 0); val = [buffer.readUInt16BE(2), buffer.readUInt16BE(0)]; } else { result.result = false; result.info = 'value is not a number'; } } catch (e) { result.result = false; result.info = e.message; } break; case EnumDataType.SwappedLong: try { if (typeof value === 'number') { let buffer = Buffer.alloc(4); buffer.writeInt32BE(value, 0); val = [buffer.readUInt16BE(0), buffer.readUInt16BE(2)]; } else { result.result = false; result.info = 'value is not a number'; } } catch (e) { result.result = false; result.info = e.message; } break; case EnumDataType.Double: try { if (typeof value === 'number') { let buffer = Buffer.alloc(8); buffer.writeDoubleBE(value, 0); val = [buffer.readUInt16BE(6), buffer.readUInt16BE(4), buffer.readUInt16BE(2), buffer.readUInt16BE(0)]; } else { result.result = false; result.info = 'value is not a number'; } } catch (e) { result.result = false; result.info = e.message; } break; case EnumDataType.SwappedDouble: try { if (typeof value === 'number') { let buffer = Buffer.alloc(8); buffer.writeDoubleBE(value, 0); val = [buffer.readUInt16BE(0), buffer.readUInt16BE(2), buffer.readUInt16BE(4), buffer.readUInt16BE(6)]; } else { result.result = false; result.info = 'value is not a number'; } } catch (e) { result.result = false; result.info = e.message; } break; } } else { result.result = false; result.info = 'wrong register type'; } if (result.result) { if (this.client.isOpen) { // 已连接 if (regType === EnumRegisterType.Coil) { result.info = await this.client.writeCoil(regAddr, val); } else if (regType === EnumRegisterType.Holding) { result.info = await this.client.writeRegisters(regAddr, val); } } else { // 未连接 const connRes = await this.connect(); if (typeof connRes !== 'string') { // 连接成功 if (regType === EnumRegisterType.Coil) { result.info = await this.client.writeCoil(regAddr, val); } else if (regType === EnumRegisterType.Holding) { result.info = await this.client.writeRegisters(regAddr, val); } } else { // 连接失败 result.result = false; result.info = 'connect failed'; } } } return result; } } }