homebridge-logo-platform
Version:
This is a Siemens LOGO! Platform Plugin.
608 lines (508 loc) • 20.7 kB
text/typescript
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))
}