UNPKG

homebridge-logo-platform

Version:
608 lines (508 loc) 20.7 kB
let ModbusRTU = require('modbus-serial'); import { ReadCoilResult, ReadRegisterResult, WriteCoilResult, WriteRegisterResult } from "./ModbusRTU"; import { ErrorNumber } from "./error"; export enum AddressType { MBATDiscreteInput = 0, MBATCoil = 1, MBATInputRegister = 2, MBATHoldingRegister = 3 } export enum WordLen { MBWLBit = 0, MBWLByte = 1, MBWLWord = 2, MBWLDWord = 3 } export class LogoAddress { constructor( public addr: number, public type: AddressType, public wLen: WordLen, public readOnly: Boolean ) {} } export class ModBusLogo { public ip: string; public port: number; public debugMsgLog: number; public log: Function; public retryCnt: number; timeout: number = 500; // original 100 sleeptime: number = 100; constructor( ip: string, port: number, debug: number, logFunction: any, retrys: number ) { this.ip = ip; this.port = port; this.debugMsgLog = debug; this.log = logFunction; this.retryCnt = retrys; } ReadLogo(item: string, callBack: (value: number) => any) { if (!item) { if (this.debugMsgLog == 1) { this.log('ReadLogo() ModBus - No LOGO! Address!'); } callBack(ErrorNumber.noData); return ErrorNumber.noData; } var addr = this.getLogoAddress(item); switch (addr.type) { case AddressType.MBATDiscreteInput: this.readDiscreteInput(addr, callBack, this.debugMsgLog, this.log, this.retryCnt); break; case AddressType.MBATCoil: this.readCoil(addr, callBack, this.debugMsgLog, this.log, this.retryCnt); break; case AddressType.MBATInputRegister: this.readInputRegister(addr, callBack, this.debugMsgLog, this.log, this.retryCnt); break; case AddressType.MBATHoldingRegister: this.readHoldingRegister(addr, callBack, this.debugMsgLog, this.log, this.retryCnt); break; } } WriteLogo(item: string, value: number) { if (!item) { if (this.debugMsgLog == 1) { this.log('WriteLogo() ModBus - No LOGO! Address!'); } return ErrorNumber.noData; } var addr = this.getLogoAddress(item); if ((addr.readOnly == false) && (value >= 0)) { if (addr.type == AddressType.MBATCoil) { this.writeCoil(addr.addr, (value == 1 ? true : false), this.debugMsgLog, this.log, this.retryCnt); } if (addr.type == AddressType.MBATHoldingRegister) { switch (addr.wLen) { case WordLen.MBWLByte: this.writeRegister(addr.addr, ((value & 0b11111111) << 8), this.debugMsgLog, this.log, this.retryCnt); break; case WordLen.MBWLWord: this.writeRegister(addr.addr, value, this.debugMsgLog, this.log, this.retryCnt); break; case WordLen.MBWLDWord: this.writeRegisters(addr.addr, [((value & 0b11111111111111110000000000000000) >> 16), (value & 0b00000000000000001111111111111111)], this.debugMsgLog, this.log, this.retryCnt); break; } } } } DisconnectS7() { if (this.debugMsgLog == 1) { this.log('DisconnectS7() - ModBus LOGO! has no disconnect.'); } } private withConnection( client: any, debugLog: number, log: any, onReady: () => void, onConnectFail: () => void ): void { let failed = false; const fail = (reason: string) => { if (failed) return; failed = true; if (debugLog == 1) { log('ModBus connect failed: ' + reason); } try { client.close(() => { /* ignore */ }); } catch (e) { /* ignore */ } sleep(this.sleeptime).then(onConnectFail); }; try { client.connectTcpRTUBuffered(this.ip, { port: this.port }, (connectErr: any) => { if (connectErr) { fail(connectErr.message || String(connectErr)); return; } const port = client._port; if (port) { port.on('error', (err: Error) => { if (debugLog == 1) log('ModBus port error: ' + err.message); }); if (port._client && typeof port._client.on === 'function') { port._client.on('error', (err: Error) => { if (debugLog == 1) log('ModBus raw socket error: ' + err.message); }); } } client.setTimeout(this.timeout); client.setID(1); onReady(); }); } catch (e: any) { fail('exception: ' + (e && e.message ? e.message : String(e))); } } readDiscreteInput(addr: LogoAddress, callBack: (value: number) => any, debugLog: number, log: any, retryCount: number) { if (retryCount == 0) { if (debugLog == 1) { log('readDiscreteInput() - Retry counter reached max value'); } callBack(ErrorNumber.noData); return ErrorNumber.noData; } let len = 1; let client = new ModbusRTU(); (client as any).on('error', (err: Error) => { if (debugLog == 1) { log('ModBus socket error: ' + err.message); } }); retryCount = retryCount - 1; this.withConnection(client, debugLog, log, () => { client.readDiscreteInputs(addr.addr, len, (err: Error, data: ReadCoilResult) => { if (err) { this.logError(log, err, debugLog, retryCount); sleep(this.sleeptime).then(() => { this.readDiscreteInput(addr, callBack, debugLog, log, retryCount); }); } else { callBack((data.data[0] == true ? 1 : 0)); } client.close(); }); }, () => this.readDiscreteInput(addr, callBack, debugLog, log, retryCount) ); } readCoil(addr: LogoAddress, callBack: (value: number) => any, debugLog: number, log: any, retryCount: number) { if (retryCount == 0) { if (debugLog == 1) { log('readCoil() - Retry counter reached max value'); } callBack(ErrorNumber.noData); return ErrorNumber.noData; } let len = 1; let client = new ModbusRTU(); (client as any).on('error', (err: Error) => { if (debugLog == 1) { log('ModBus socket error: ' + err.message); } }); retryCount = retryCount - 1; this.withConnection(client, debugLog, log, () => { client.readCoils(addr.addr, len, (err: Error, data: ReadCoilResult) => { if (err) { this.logError(log, err, debugLog, retryCount); sleep(this.sleeptime).then(() => { this.readCoil(addr, callBack, debugLog, log, retryCount); }); } else { callBack((data.data[0] == true ? 1 : 0)); } client.close(); }); }, () => this.readCoil(addr, callBack, debugLog, log, retryCount) ); } readInputRegister(addr: LogoAddress, callBack: (value: number) => any, debugLog: number, log: any, retryCount: number) { if (retryCount == 0) { if (debugLog == 1) { log('readInputRegister() - Retry counter reached max value'); } callBack(ErrorNumber.noData); return ErrorNumber.noData; } let len = 1; let client = new ModbusRTU(); (client as any).on('error', (err: Error) => { if (debugLog == 1) { log('ModBus socket error: ' + err.message); } }); retryCount = retryCount - 1; this.withConnection(client, debugLog, log, () => { client.readInputRegisters(addr.addr, len, (err: Error, data: ReadRegisterResult) => { if (err) { this.logError(log, err, debugLog, retryCount); sleep(this.sleeptime).then(() => { this.readInputRegister(addr, callBack, debugLog, log, retryCount); }); } else { let num = data.data[0]; if (num > ErrorNumber.maxPositivNumber) { num = num - ErrorNumber.max16BitNumber; } callBack(num); } client.close(); }); }, () => this.readInputRegister(addr, callBack, debugLog, log, retryCount) ); } readHoldingRegister(addr: LogoAddress, callBack: (value: number) => any, debugLog: number, log: any, retryCount: number) { if (retryCount == 0) { if (debugLog == 1) { log('readHoldingRegister() - Retry counter reached max value'); } callBack(ErrorNumber.noData); return ErrorNumber.noData; } let len = (addr.wLen == WordLen.MBWLDWord ? 2 : 1); let client = new ModbusRTU(); (client as any).on('error', (err: Error) => { if (debugLog == 1) { log('ModBus socket error: ' + err.message); } }); retryCount = retryCount - 1; this.withConnection(client, debugLog, log, () => { client.readHoldingRegisters(addr.addr, len, (err: Error, data: ReadRegisterResult) => { if (err) { this.logError(log, err, debugLog, retryCount); sleep(this.sleeptime).then(() => { this.readHoldingRegister(addr, callBack, debugLog, log, retryCount); }); } else { let num = 0; switch (addr.wLen) { case WordLen.MBWLByte: num = (data.data[0] & 0b1111111100000000) >> 8; if (num > ErrorNumber.maxPositivNumber) { num = num - ErrorNumber.max16BitNumber; } callBack(num); break; case WordLen.MBWLWord: num = data.data[0]; if (num > ErrorNumber.maxPositivNumber) { num = num - ErrorNumber.max16BitNumber; } callBack(num); break; case WordLen.MBWLDWord: num = (data.data[0] << 16) | data.data[1]; if (num > ErrorNumber.maxPositivNumber) { num = num - ErrorNumber.max16BitNumber; } callBack(num); break; } } client.close(); }); }, () => this.readHoldingRegister(addr, callBack, debugLog, log, retryCount) ); } writeCoil(addr: number, state: Boolean, debugLog: number, log: any, retryCount: number) { if (retryCount == 0) { if (debugLog == 1) { log('writeCoil() - Retry counter reached max value'); } return ErrorNumber.noData; } let client = new ModbusRTU(); (client as any).on('error', (err: Error) => { if (debugLog == 1) { log('ModBus socket error: ' + err.message); } }); retryCount = retryCount - 1; this.withConnection(client, debugLog, log, () => { client.writeCoil(addr, state, (err: Error, data: WriteCoilResult) => { if (err) { this.logError(log, err, debugLog, retryCount); sleep(this.sleeptime).then(() => { this.writeCoil(addr, state, debugLog, log, retryCount); }); } client.close(); }); }, () => this.writeCoil(addr, state, debugLog, log, retryCount) ); } writeRegister(addr: number, value: number, debugLog: number, log: any, retryCount: number) { if (retryCount == 0) { if (debugLog == 1) { log('writeRegister() - Retry counter reached max value'); } return ErrorNumber.noData; } let client = new ModbusRTU(); (client as any).on('error', (err: Error) => { if (debugLog == 1) { log('ModBus socket error: ' + err.message); } }); retryCount = retryCount - 1; this.withConnection(client, debugLog, log, () => { client.writeRegister(addr, value, (err: Error, data: WriteRegisterResult) => { if (err) { this.logError(log, err, debugLog, retryCount); sleep(this.sleeptime).then(() => { this.writeRegister(addr, value, debugLog, log, retryCount); }); } client.close(); }); }, () => this.writeRegister(addr, value, debugLog, log, retryCount) ); } writeRegisters(addr: number, value: number[], debugLog: number, log: any, retryCount: number) { if (retryCount == 0) { if (debugLog == 1) { log(' writeRegisters() - Retry counter reached max value'); } return ErrorNumber.noData; } let client = new ModbusRTU(); (client as any).on('error', (err: Error) => { if (debugLog == 1) { log('ModBus socket error: ' + err.message); } }); retryCount = retryCount - 1; this.withConnection(client, debugLog, log, () => { client.writeRegisters(addr, value, (err: Error, data: WriteRegisterResult) => { if (err) { this.logError(log, err, debugLog, retryCount); sleep(this.sleeptime).then(() => { this.writeRegisters(addr, value, debugLog, log, retryCount); }); } client.close(); }); }, () => this.writeRegisters(addr, value, debugLog, log, retryCount) ); } logError(log: any, err: Error, debugLog: number, retryCount: number) { if ((debugLog == 1) && (retryCount == 1)) { log(err); } } getLogoAddress(name: string): LogoAddress { if (name.match("AI[0-9]{1,2}")) { var num = parseInt(name.replace("AI", ""), 10) return new LogoAddress(num - 1, AddressType.MBATInputRegister, WordLen.MBWLWord, true); // Start: 0, Lenght: 8 } if (name.match("AQ[0-9]{1,2}")) { var num = parseInt(name.replace("AQ", ""), 10) return new LogoAddress((511 + num), AddressType.MBATHoldingRegister, WordLen.MBWLWord, false); // Start: 512, Lenght: 8 } if (name.match("AM[0-9]{1,2}")) { var num = parseInt(name.replace("AM", ""), 10) return new LogoAddress((527 + num), AddressType.MBATHoldingRegister, WordLen.MBWLWord, false); // Start: 528, Lenght: 8 } if (name.match("I[0-9]{1,2}")) { var num = parseInt(name.replace("I", ""), 10) return new LogoAddress(num - 1, AddressType.MBATDiscreteInput, WordLen.MBWLBit, true); // Start: 0, Lenght: 24 } if (name.match("Q[0-9]{1,2}")) { var num = parseInt(name.replace("Q", ""), 10) return new LogoAddress((8191 + num), AddressType.MBATCoil, WordLen.MBWLBit, false); // Start: 8192, Lenght: 20 } if (name.match("M[0-9]{1,2}")) { var num = parseInt(name.replace("M", ""), 10) return new LogoAddress((8255 + num), AddressType.MBATCoil, WordLen.MBWLBit, false); // Start: 8256, Lenght: 64 } if (name.match("V[0-9]{1,4}\.[0-7]{1}")) { var str = name.replace("V", ""); var a = parseInt(str.split(".", 2)[0], 10); var b = parseInt(str.split(".", 2)[1], 10); return new LogoAddress(((a * 8) + b), AddressType.MBATCoil, WordLen.MBWLBit, false); // Start: 0, Lenght: 6808 (850*8+8) } if (name.match("VB[0-9]{1,4}")) { var num = parseInt(name.replace("VB", ""), 10) return new LogoAddress(Math.floor(num / 2), AddressType.MBATHoldingRegister, WordLen.MBWLByte, false); // Start: 0, Lenght: 425 (850/2) } if (name.match("VW[0-9]{1,4}")) { var num = parseInt(name.replace("VW", ""), 10) return new LogoAddress(Math.floor(num / 2), AddressType.MBATHoldingRegister, WordLen.MBWLWord, false); // Start: 0, Lenght: 425 (850/2) } if (name.match("VD[0-9]{1,4}")) { var num = parseInt(name.replace("VD", ""), 10) return new LogoAddress(Math.floor(num / 2), AddressType.MBATHoldingRegister, WordLen.MBWLDWord, false); // Start: 0, Lenght: 425 (850/2) } return new LogoAddress(0, AddressType.MBATCoil, WordLen.MBWLBit, false); } isValidLogoAddress(name: string): boolean { if (name.match("AI[0-9]{1,2}")) { return true; } if (name.match("AQ[0-9]{1,2}")) { return true; } if (name.match("AM[0-9]{1,2}")) { return true; } if (name.match("I[0-9]{1,2}")) { return true; } if (name.match("Q[0-9]{1,2}")) { return true; } if (name.match("M[0-9]{1,2}")) { return true; } if (name.match("V[0-9]{1,4}\.[0-7]{1}")) { return true; } if (name.match("VB[0-9]{1,4}")) { return true; } if (name.match("VW[0-9]{1,4}")) { return true; } if (name.match("VD[0-9]{1,4}")) { return true; } return false; } isAnalogLogoAddress(name: string): boolean { if (name.match("AI[0-9]{1,2}")) { return true; } if (name.match("AQ[0-9]{1,2}")) { return true; } if (name.match("AM[0-9]{1,2}")) { return true; } if (name.match("I[0-9]{1,2}")) { return false; } if (name.match("Q[0-9]{1,2}")) { return false; } if (name.match("M[0-9]{1,2}")) { return false; } if (name.match("V[0-9]{1,4}\.[0-7]{1}")) { return false; } if (name.match("VB[0-9]{1,4}")) { return true; } if (name.match("VW[0-9]{1,4}")) { return true; } if (name.match("VD[0-9]{1,4}")) { return true; } return false; } } const sleep = (milliseconds: number) => { return new Promise(resolve => setTimeout(resolve, milliseconds)) }