@gapit/node-red-contrib-gapit-modbus
Version:
A Node-RED node witch reads Modbus registers based on gapit code JSON schema
1,020 lines (993 loc) • 36.5 kB
JavaScript
'use strict';
var ModbusRTU = require('modbus-serial');
function convertConfig(config, node) {
if (!config) {
node.warn("Missing gapit code");
return;
}
const modbusGroups = config.objects.map((group)=>{
const registerType = group.register_type;
const startAddress = Number(group.group[0].address);
const length = group.group.length;
const endAddress = Number(group.group[length - 1].address);
const lastByteType = group.group[length - 1].byte_type;
const byteSize = group.group[length - 1].byte_size;
let lastByteTypeNumber = Math.ceil(Number.parseInt(lastByteType.replace(/^\D+/g, "")) / 16);
if (typeof lastByteType !== "number" && byteSize) {
lastByteTypeNumber = byteSize;
} else if (lastByteType.includes("MOD10")) {
const byteSize1 = lastByteType.split("-")[1];
lastByteTypeNumber = Number.parseInt(byteSize1);
}
const absoluteEnd = endAddress + lastByteTypeNumber;
const addressLength = absoluteEnd - startAddress;
return {
registerType,
startAddress,
addressLength,
absoluteEnd
};
});
return modbusGroups;
}
class GapitModbusRTU extends ModbusRTU {
readRegister(registerType, dataAddress, length) {
switch(registerType){
case "holding":
{
return this.readHoldingRegisters(dataAddress, length);
}
case "input":
{
return this.readInputRegisters(dataAddress, length);
}
default:
{
throw new Error(`Unknown register type: ${registerType}`);
}
}
}
readCoil(registerType, dataAddress, length) {
switch(registerType){
case "status":
{
return this.readDiscreteInputs(dataAddress, length);
}
case "coil":
{
return this.readCoils(dataAddress, length);
}
default:
{
throw new Error(`Unknown register type: ${registerType}`);
}
}
}
}
async function connectModbus(node, host, portOptions) {
const client = new GapitModbusRTU();
await new Promise((resolve, reject)=>{
client.connectTCP(host, portOptions).then((response)=>{
// if (response !== undefined) node.warn({ response });
resolve(response);
return;
}).catch((error)=>{
node.error(`Cannot connect to modbus device: Host:${host}, Port ${portOptions.port}`);
// if (err !== undefined) node.warn(err);
reject(error);
});
});
return client;
}
function checkNodeConfig(msg, config, node) {
if (!config.gapitCode && !msg.gapit_code) {
node.warn("Missing gapit code, add gapit code to gapit-modbus-node or msg.gapit_code");
return;
}
try {
const gapitCode = config.gapitCode ? JSON.parse(config.gapitCode) : msg.gapit_code;
const portOptions = {};
const port = config.port === undefined ? msg.port : +config.port;
if (port !== undefined) {
portOptions.port = port;
}
const nodeConfig = {
host: config.host || msg.host || "127.0.0.1",
portOptions: portOptions,
socketAddress: `${config.host || msg.host}:${port}`,
unitId: config.unitId || msg.unit_id,
scalingType: gapitCode.scaling || "general",
gapitCode: gapitCode,
deviceName: config.deviceName || msg.device_name,
deviceTag: config.deviceNameDbTag || msg.device_name_db_tag,
customTags: config.customTags ? JSON.parse(config.customTags) : msg.custom_tags
};
for (const [key, value] of Object.entries(nodeConfig)){
if (key != "unitId" && !value) {
node.warn(`Missing value in: ${key}, add value in gapit-modbus-node or msg.`);
return;
}
}
msg.db_tags = nodeConfig.customTags;
msg.tagname_device_name = nodeConfig.deviceTag;
msg.custom_tags = nodeConfig.customTags;
return nodeConfig;
} catch (error) {
node.warn(error);
return;
}
}
function initBuffer$2(byteEndian, wordEndian, start, bufferAmount, buffer) {
if (wordEndian !== "little" && byteEndian !== "little") return buffer;
return buffer.slice(start, start + bufferAmount);
}
function decodeSInt(byteEndian, wordEndian, start, byteTypeNumber, bufferAmount, result, bitNr) {
const buffer = initBuffer$2(byteEndian, wordEndian, start, bufferAmount, result.buffer);
if (wordEndian === "little") {
if (byteEndian !== "little") buffer.swap16();
if (byteTypeNumber === 64) {
return Number(buffer.readBigInt64LE(0));
} else if (byteTypeNumber === 8 && typeof bitNr === "number") {
return buffer.readIntLE(bitNr, bufferAmount);
} else {
return buffer.readIntLE(0, bufferAmount);
}
} else {
if (byteEndian === "little") {
buffer.swap16();
if (byteTypeNumber === 64) {
return Number(buffer.readBigInt64BE(0));
} else if (byteTypeNumber === 8 && typeof bitNr === "number") {
return buffer.readIntBE(bitNr, bufferAmount);
} else {
return buffer.readIntBE(0, bufferAmount);
}
} else {
if (byteTypeNumber === 64) {
return Number(result.buffer.readBigInt64BE(start));
} else if (byteTypeNumber === 8 && typeof bitNr === "number") {
return result.buffer.readIntBE(start + bitNr, bufferAmount);
} else {
return result.buffer.readIntBE(start, bufferAmount);
}
}
}
}
function initBuffer$1(byteEndian, wordEndian, start, bufferAmount, buffer) {
if (wordEndian !== "little" && byteEndian !== "little") return buffer;
return buffer.slice(start, start + bufferAmount);
}
function decodeUInt(byteEndian, wordEndian, start, byteTypeNumber, bufferAmount, result, bitNr) {
const buffer = initBuffer$1(byteEndian, wordEndian, start, bufferAmount, result.buffer);
if (wordEndian === "little") {
if (byteEndian !== "little") buffer.swap16();
if (byteTypeNumber === 64) {
return Number(buffer.readBigUInt64LE(0));
} else if (byteTypeNumber === 8 && typeof bitNr === "number") {
return buffer.readUIntLE(bitNr, bufferAmount);
} else {
return buffer.readUIntLE(0, bufferAmount);
}
} else {
if (byteEndian === "little") {
buffer.swap16();
if (byteTypeNumber === 64) {
return Number(buffer.readBigUInt64BE(0));
} else if (byteTypeNumber === 8 && typeof bitNr === "number") {
return buffer.readUIntBE(bitNr, bufferAmount);
} else {
return buffer.readUIntBE(0, bufferAmount);
}
} else {
if (byteTypeNumber === 64) {
return Number(result.buffer.readBigUInt64BE(start));
} else if (byteTypeNumber === 8 && typeof bitNr === "number") {
return result.buffer.readUIntBE(start + bitNr, bufferAmount);
} else {
return result.buffer.readUIntBE(start, bufferAmount);
}
}
}
}
function initBuffer(byteEndian, wordEndian, start, bufferAmount, buffer) {
if (wordEndian !== "little" && byteEndian !== "little") return buffer;
return buffer.slice(start, start + bufferAmount);
}
function decodeFloat(byteEndian, wordEndian, start, byteTypeNumber, bufferAmount, result) {
const buffer = initBuffer(byteEndian, wordEndian, start, bufferAmount, result.buffer);
if (wordEndian === "little") {
if (byteEndian !== "little") buffer.swap16();
return byteTypeNumber === 64 ? buffer.readDoubleLE(0) : buffer.readFloatLE(0);
} else {
if (byteEndian === "little") {
buffer.swap16();
return byteTypeNumber === 64 ? buffer.readDoubleBE(0) : buffer.readFloatBE(0);
} else {
return byteTypeNumber === 64 ? buffer.readDoubleBE(start) : buffer.readFloatBE(start);
}
}
}
function decodeBit(byteEndian, wordEndian, sw, start, byteTypeNumber, bufferAmount, result, bitNr) {
if (byteTypeNumber === 16 && typeof bitNr === "number") {
if (wordEndian === "little") {
const value = result.buffer.readUIntBE(start, bufferAmount);
const binary = value.toString(2).padStart(16, "0");
const bitValue = Number(binary.charAt(bitNr)) ?? 0;
return bitValue;
}
const value1 = result.buffer.readUIntBE(start, bufferAmount);
const binary1 = value1.toString(2).padStart(16, "0");
const currentBit = 15 - bitNr;
const bitValue1 = Number(binary1.charAt(currentBit)) ?? 0;
return bitValue1;
} else if (byteTypeNumber === 1) {
const value2 = result.buffer.readUIntBE(start, bufferAmount + 1);
const binary2 = value2.toString(2);
return Number(binary2.charAt(0)) ?? 0;
} else if (byteTypeNumber === 32 && typeof bitNr === "number") {
const value3 = result.buffer.readUIntBE(start, bufferAmount);
const binary3 = value3.toString(2).padStart(32, "0");
const currentBit1 = 31 - bitNr;
const bitValue2 = Number(binary3.charAt(currentBit1)) ?? 0;
return bitValue2;
}
}
function decodeHex(byteEndian, sw, start, byteTypeNumber, bufferAmount, result, node, byteSize) {
if (!byteSize) return;
const byteEndianLittle = sw !== undefined != (byteEndian === "little");
if (byteSize === 8) {
const value = byteEndianLittle ? result.buffer.swap16().readBigInt64BE(start) : result.buffer.readBigInt64BE(start);
return value.toString(16);
} else {
const value1 = byteEndianLittle ? result.buffer.swap16().readUIntBE(start, byteSize) : result.buffer.readUIntBE(start, byteSize);
return value1.toString(16);
}
}
function decodeAscii(byteEndian, sw, start, result, node, byteSize) {
if (!byteSize) return;
const byteEndianLittle = sw !== undefined != (byteEndian === "little");
if (byteEndianLittle) {
const buffer = result.buffer.slice(start, start + byteSize).swap16().filter((x)=>32 <= x && x < 127 || 160 <= x && x < 256);
const asciiString = String.fromCharCode.apply(undefined, buffer);
return asciiString;
}
const buffer1 = result.buffer.slice(start, start + byteSize).filter((x)=>32 <= x && x < 127 || 160 <= x && x < 256);
const asciiString1 = String.fromCharCode.apply(undefined, buffer1);
return asciiString1;
}
function decodeMod(byteEndian, wordEndian, byteType, start, result) {
const byteTypeSize = byteType.split("-")[1];
const byteTypeNumber = Number.parseInt(byteTypeSize);
const bufferAmount = Math.ceil(byteTypeNumber / 8);
const buffer = result.buffer.slice(start, start + bufferAmount);
if (byteTypeNumber === 64) {
let tmpValue;
if (byteEndian === "little" && wordEndian === "little") {
tmpValue = Number(buffer.readInt16LE(0)) * 10_000 ** 3;
tmpValue += Number(buffer.readInt16LE(2)) * 10_000 ** 2;
tmpValue += Number(buffer.readInt16LE(4)) * 10_000;
tmpValue += Number(buffer.readInt16LE(6));
} else if (byteEndian === "big" && wordEndian === "little") {
tmpValue = Number(buffer.readInt16BE(0)) * 10_000 ** 3;
tmpValue += Number(buffer.readInt16BE(2)) * 10_000 ** 2;
tmpValue += Number(buffer.readInt16BE(4)) * 10_000;
tmpValue += Number(buffer.readInt16BE(6));
} else if (byteEndian === "little" && wordEndian === "big") {
tmpValue = Number(buffer.readInt16LE(0));
tmpValue += Number(buffer.readInt16LE(2)) * 10_000;
tmpValue += Number(buffer.readInt16LE(4)) * 10_000 ** 2;
tmpValue += Number(buffer.readInt16LE(6)) * 10_000 ** 3;
} else {
tmpValue = Number(buffer.readInt16BE(0));
tmpValue += Number(buffer.readInt16BE(2)) * 10_000;
tmpValue += Number(buffer.readInt16BE(4)) * 10_000 ** 2;
tmpValue += Number(buffer.readInt16BE(6)) * 10_000 ** 3;
}
return tmpValue;
}
}
function controlValueScaling(value, unit, node, scalingConfig, scalingFactor, byte_type, scalingType) {
const bytetypePlain = {
DUMMY16: undefined,
DUMMY32: undefined,
DUMMY64: undefined,
BIT1: "bit1",
BIT16: "bit16",
"BIT16-SW": "bit16",
BIT32: "bit32",
"BIT32-SW": "bit32",
HEX: "ascii",
"HEX-SW": "ascii",
ASCII: "ascii",
"ASCII-SW": "ascii",
UINT8: "uInt8",
UINT16: "uInt16",
SINT16: "sInt16",
UINT32: "uInt32",
SMINT32: "sInt32",
SINT32: "sInt32",
UINT64: "uInt64",
SINT64: "sInt64",
FLOAT32: "Float32",
FLOAT64: "Float64",
"MOD10-64": "sInt64"
};
if (!byte_type) return;
const plainType = bytetypePlain[byte_type];
if (scalingType && scalingConfig.hasOwnProperty(scalingType)) {
const configType = scalingType;
if (plainType && scalingConfig[configType].hasOwnProperty(plainType)) {
for (const [key, plainTypeValue] of Object.entries(scalingConfig[configType])){
if (key === plainType) {
const sizeControlValues = plainTypeValue;
if (sizeControlValues.hasOwnProperty(value)) {
const errorMessage = Object.values(sizeControlValues).map((controlValue)=>controlValue);
node.warn(`${scalingType}:Control value ${value} for unit ${unit}: ${errorMessage[0]}`);
return;
}
}
}
}
}
let scaledValue = value;
if (scalingFactor) {
scaledValue = value * scalingFactor;
}
return scaledValue;
}
function generalScaling(scalingFactor, value) {
const scaledValue = value * scalingFactor;
return scaledValue;
}
function generex(value, unit, node, scalingConfig, scalingFactor, scalingFunction) {
let scaledValue = value;
if (scalingFactor && ![
-1,
-9999
].includes(value)) {
scaledValue = value * scalingFactor;
}
if (scalingFunction === "temperature") {
scaledValue = (value - 78) / 2;
}
return scaledValue;
}
function scaling2(tempValue) {
const value = Number(tempValue.toFixed(2));
const newUnit = "0: no alarm, 1: active alarm";
return [
value,
newUnit
];
}
function scaling3(tempValue, unit, ktaktvRatio) {
if (ktaktvRatio < 5000) {
const value = Number((tempValue * 0.01).toFixed(2));
return [
value,
unit
];
} else {
const value1 = Number((tempValue * 1).toFixed(2));
return [
value1,
unit
];
}
}
function scaling3_2(tempValue, unit, ktaktvRatio) {
if (ktaktvRatio < 5000) {
const value = Number((tempValue * 0.01).toFixed(2));
return [
value,
unit
];
} else {
const value1 = Number((tempValue * 10).toFixed(2));
return [
value1,
unit
];
}
}
function scaling3_CE4D(tempValue, unit, ktaktvRatio) {
if (ktaktvRatio < 6000) {
const value = Number((tempValue * 0.01).toFixed(2));
return [
value,
unit
];
} else {
const value1 = Number((tempValue * 1).toFixed(2));
return [
value1,
unit
];
}
}
function scaling3_6(tempValue, unit, ktaktvRatio, result, node, start, offset = 0) {
const signPosition = Number(result.data[start / 2 + offset]);
const sign = signPosition === 1 ? -1 : 1;
const multi = ktaktvRatio < 5000 ? 0.01 : 1;
const value = Number((tempValue * multi * sign).toFixed(2));
return [
value,
unit
];
}
function scaling4(tempValue, unit, ktaktvRatio, node) {
if (ktaktvRatio >= 1 && ktaktvRatio < 10) {
const value = Number((tempValue * 10).toFixed(2));
return [
value,
unit
];
} else if (ktaktvRatio >= 10 && ktaktvRatio < 100) {
const value1 = Number((tempValue * 100).toFixed(2));
return [
value1,
unit
];
} else if (ktaktvRatio >= 100 && ktaktvRatio < 1000) {
const value2 = Number((tempValue * 1000).toFixed(2));
return [
value2,
unit
];
} else if (ktaktvRatio >= 1000 && ktaktvRatio < 10_000) {
const value3 = Number((tempValue * 10_000).toFixed(2));
return [
value3,
unit
];
} else if (ktaktvRatio >= 10_000 && ktaktvRatio < 100_000) {
const value4 = Number((tempValue * 100_000).toFixed(2));
return [
value4,
unit
];
} else if (ktaktvRatio >= 100_000) {
const value5 = Number((tempValue * 100_000).toFixed(2));
return [
value5,
unit
];
}
node.warn("No matching ktaktvRatio");
return [
tempValue,
unit
];
}
function scaling4_2(tempValue, unit, ktaktvRatio, node) {
if (ktaktvRatio >= 1 && ktaktvRatio < 10) {
const value = Number((tempValue * 10).toFixed(2));
return [
value,
unit
];
} else if (ktaktvRatio >= 10 && ktaktvRatio < 100) {
const value1 = Number((tempValue * 100).toFixed(2));
return [
value1,
unit
];
} else if (ktaktvRatio >= 100 && ktaktvRatio < 1000) {
const value2 = Number((tempValue * 1000).toFixed(2));
return [
value2,
unit
];
} else if (ktaktvRatio >= 1000 && ktaktvRatio < 10_000) {
const value3 = Number((tempValue * 10_000).toFixed(2));
return [
value3,
unit
];
} else if (ktaktvRatio >= 10_000 && ktaktvRatio < 100_000) {
const value4 = Number((tempValue * 100_000).toFixed(2));
return [
value4,
unit
];
} else if (ktaktvRatio >= 100_000) {
const value5 = Number((tempValue * 1_000_000).toFixed(2));
return [
value5,
unit
];
}
node.warn("No matching ktaktvRatio");
return [
tempValue,
unit
];
}
function scaling6(tempValue, unit) {
const value = Number(tempValue.toFixed(2));
return [
value,
unit
];
}
function scaling7(tempValue) {
const value = Number(tempValue.toFixed(2));
const newUnit = "High part energy, MWH/MVArh";
return [
value,
newUnit
];
}
function imeScaling(tmpValue, unit, node, result, start, scalingFactor, scalingFunction, offsetScaling) {
if (!node.ktaktvRatio) {
node.warn("Missing KTV KTA");
return [
tmpValue,
unit
];
}
if (scalingFactor) {
const scaledValue = tmpValue * scalingFactor;
return [
scaledValue,
unit
];
} else {
switch(scalingFunction){
case "scaling2":
{
return scaling2(tmpValue);
}
case "scaling3":
{
return scaling3(tmpValue, unit, node.ktaktvRatio);
}
case "scaling3_2":
{
return scaling3_2(tmpValue, unit, node.ktaktvRatio);
}
case "scaling3_CE4D":
{
return scaling3_CE4D(tmpValue, unit, node.ktaktvRatio);
}
case "scaling3_6":
{
return scaling3_6(tmpValue, unit, node.ktaktvRatio, result, node, start, offsetScaling);
}
case "scaling4":
{
return scaling4(tmpValue, unit, node.ktaktvRatio, node);
}
case "scaling4_2":
{
return scaling4_2(tmpValue, unit, node.ktaktvRatio, node);
}
case "scaling6":
{
return scaling6(tmpValue, unit);
}
case "scaling7":
{
return scaling7(tmpValue);
}
}
}
return [
tmpValue,
unit
];
}
function ion7500(value, unit, node, scalingConfig, scalingFactor) {
let scaledValue = value;
if (scalingFactor) {
scaledValue = value * scalingFactor;
}
if (unit === "register1") {
scalingConfig.ionConfig.register1 = scaledValue;
} else if (unit === "register2") {
scalingConfig.ionConfig.register2 = scaledValue;
scalingConfig.ionConfig.register_tot = scalingConfig.ionConfig.register1 + scalingConfig.ionConfig.register2;
scaledValue = scalingConfig.ionConfig.register_tot;
}
return scaledValue;
}
function janitza(value, unit, node, scalingConfig, scalingFactor, scalingFunction) {
let scaledValue = value;
if (scalingFactor) {
scaledValue = value * scalingFactor;
}
if (scalingFunction === "energy_multiplier") {
scalingConfig.janitza.energy_multiplier = scaledValue;
} else if (scalingFunction === "energy") {
scaledValue = value * 1000 * 1000 ** scalingConfig.janitza.energy_multiplier;
}
return scaledValue;
}
function schneiderPm800(value, unit, node, scalingConfig, scalingFactor) {
let scaledValue = value;
if (scalingFactor) {
scaledValue = value * scalingFactor;
}
switch(unit){
case "register1":
{
scalingConfig.schneider.register1 = scaledValue;
break;
}
case "register2":
{
scalingConfig.schneider.register2 = scaledValue;
break;
}
case "register3":
{
scalingConfig.schneider.register3 = scaledValue;
break;
}
case "register4":
{
scalingConfig.schneider.register4 = scaledValue;
scalingConfig.schneider.register_tot = scalingConfig.schneider.register1 + scalingConfig.schneider.register2 + scalingConfig.schneider.register3 + scalingConfig.schneider.register4;
scaledValue = scalingConfig.schneider.register_tot;
break;
}
}
return scaledValue;
}
function siemens(value, unit, node, scalingConfig, scalingFactor, scalingFunction) {
let scaledValue = value;
if (scalingFactor) {
scaledValue = value * scalingFactor;
}
if (scalingFunction === "energy_multiplier") {
scalingConfig.siemens.energy_multiplier = scaledValue;
} else if (scalingFunction === "energy") {
scaledValue = value * scalingConfig.siemens.energy_multiplier;
}
return scaledValue;
}
function checkScaling(scalingType, value, unit, node, result, start, scalingConfig, scalingFactor, scalingFunction, offsetScaling, byteType) {
const scalingTypeLower = scalingType?.toLowerCase();
if (scalingTypeLower === "general" && scalingFactor) {
return generalScaling(scalingFactor, value);
}
if (scalingTypeLower === "ime") {
return imeScaling(value, unit, node, result, start, scalingFactor, scalingFunction, offsetScaling)[0];
}
if (scalingTypeLower === "schneider_pm800") {
return schneiderPm800(value, unit, node, scalingConfig, scalingFactor);
}
if (scalingTypeLower === "ion_7500") {
return ion7500(value, unit, node, scalingConfig, scalingFactor);
}
if (scalingTypeLower === "siemens") {
return siemens(value, unit, node, scalingConfig, scalingFactor, scalingFunction);
}
if (scalingTypeLower === "janitza") {
return janitza(value, unit, node, scalingConfig, scalingFactor, scalingFunction);
}
if (scalingTypeLower === "generex") {
return generex(value, unit, node, scalingConfig, scalingFactor, scalingFunction);
}
if (scalingType && [
"abb_energy",
"cg_em",
"weidmuller",
"abb_cms",
"deepsea"
].includes(scalingTypeLower || "")) {
return controlValueScaling(value, unit, node, scalingConfig, scalingFactor, byteType, scalingType);
}
return value;
}
const scalingConfig = {
schneider: {
register1: 0,
register2: 0,
register3: 0,
register4: 0,
register_tot: 0
},
ionConfig: {
register1: 0,
register2: 0,
register_tot: 0
},
siemens: {
energy_multiplier: 0
},
janitza: {
energy_multiplier: 0
},
abb_energy: {
sInt16: {
0x7f_ff: "Error"
},
uInt16: {
0xff_ff: "Error"
},
sInt32: {
0x7f_ff_ff_ff: "Error"
},
uInt32: {
0xff_ff_ff_ff: "Error"
},
sInt64: {
"9223372036854775807": "Error"
},
uInt64: {
"18446744073709551615": "Error"
}
},
cg_em: {
sInt32: {
0x7f_fd_ff_ff: "Error"
}
},
weidmuller: {
sInt16: {
0x7f_ff: "Error",
0x80_00: "Error"
},
uInt16: {
0x7f_ff: "Error",
0x80_00: "Error"
}
},
abb_cms: {
uInt16: {
0x7f_f0: "Data pending, acquisition in progress",
0x7f_fc: "The sensor is known but not accessible at the moment",
0x7f_fd: "Data type TrueRMS / AC / DC is disabled",
0x7f_fe: "Overload (beyond full range)",
0x7f_ff: "Forbidden (no sensor with ID xx)",
0x7f_f1: "reserved",
0x7f_f2: "reserved",
0x7f_f3: "reserved",
0x7f_f4: "reserved",
0x7f_f5: "reserved",
0x7f_f6: "reserved",
0x7f_f7: "reserved",
0x7f_f8: "reserved",
0x7f_f9: "reserved",
0x7f_fa: "reserved",
0x7f_fb: "reserved"
},
sInt16: {
0x7f_f0: "Data pending, acquisition in progress",
0x7f_fc: "The sensor is known but not accessible at the moment",
0x7f_fd: "Data type TrueRMS / AC / DC is disabled",
0x7f_fe: "Overload (beyond full range)",
0x7f_ff: "Forbidden (no sensor with ID xx)",
0x7f_f1: "reserved",
0x7f_f2: "reserved",
0x7f_f3: "reserved",
0x7f_f4: "reserved",
0x7f_f5: "reserved",
0x7f_f6: "reserved",
0x7f_f7: "reserved",
0x7f_f8: "reserved",
0x7f_f9: "reserved",
0x7f_fa: "reserved",
0x7f_fb: "reserved"
},
uInt32: {
0xff_ff_7f_f0: "Data pending, acquisition in progress",
0xff_ff_7f_fc: "The sensor is known but not accessible at the moment",
0xff_ff_7f_fd: "Data type TrueRMS / AC / DC is disabled",
0xff_ff_7f_fe: "Overload (beyond full range)",
0xff_ff_7f_ff: "Forbidden (no sensor with ID xx)",
0xff_ff_7f_f1: "reserved",
0xff_ff_7f_f2: "reserved",
0xff_ff_7f_f3: "reserved",
0xff_ff_7f_f4: "reserved",
0xff_ff_7f_f5: "reserved",
0xff_ff_7f_f6: "reserved",
0xff_ff_7f_f7: "reserved",
0xff_ff_7f_f8: "reserved",
0xff_ff_7f_f9: "reserved",
0xff_ff_7f_fa: "reserved",
0xff_ff_7f_fb: "reserved"
}
},
deepsea: {
sInt16: {
0x7f_ff: "Error"
},
uInt16: {
0xff_ff: "Error"
},
sInt32: {
0x7f_ff_ff_ff: "Error"
},
uInt32: {
0xff_ff_ff_ff: "Error"
}
}
};
function decode_register_group(node, gapitCode, object, result, scalingType) {
const first = object.group[0] ? object.group[0]["address"] : Number.POSITIVE_INFINITY;
const group = object.group.map((variable)=>{
const [byteType, sw] = variable.byte_type.split("-");
const byteEndian = variable.byte_endian || gapitCode.byte_endian || "big";
const wordEndian = variable.word_endian || gapitCode.word_endian || "big";
const byteTypeNumber = Number.parseInt(byteType.replace(/^\D+/g, ""));
const bufferAmount = Math.ceil(byteTypeNumber / 8);
const byteTypeString = byteType.replace(/\d/g, "");
const start = (variable.address - first) * 2;
let value = 0;
if (byteTypeString.includes("DUMMY")) {
value = 0;
} else if (byteTypeString.includes("SINT")) {
value = decodeSInt(byteEndian, wordEndian, start, byteTypeNumber, bufferAmount, result, variable.bit_nr);
} else if (byteTypeString.includes("UINT")) {
value = decodeUInt(byteEndian, wordEndian, start, byteTypeNumber, bufferAmount, result, variable.bit_nr);
} else if (byteTypeString.includes("FLOAT")) {
value = decodeFloat(byteEndian, wordEndian, start, byteTypeNumber, bufferAmount, result);
} else if (byteTypeString.includes("BIT")) {
value = decodeBit(byteEndian, wordEndian, sw, start, byteTypeNumber, bufferAmount, result, variable.bit_nr);
} else if (byteTypeString.includes("HEX")) {
value = decodeHex(byteEndian, sw, start, byteTypeNumber, bufferAmount, result, node, variable.byte_size);
// eslint-disable-next-line unicorn/text-encoding-identifier-case
} else if (byteTypeString.includes("ASCII")) {
value = decodeAscii(byteEndian, sw, start, result, node, variable.byte_size);
} else if (byteTypeString.includes("MOD")) {
value = decodeMod(byteEndian, wordEndian, variable.byte_type, start, result);
} else {
return {
address: variable.address,
description: variable.description,
scaling_factor: variable.scaling_factor,
unit: variable.unit,
byte_type: variable.byte_type,
value: undefined
};
}
if (typeof value === "number" && variable.scaling_factor) {
value = checkScaling(scalingType, value, variable.unit, node, result, start, scalingConfig, variable.scaling_factor, variable.scaling_function, variable.offset_scaling, variable.byte_type);
}
return {
address: variable.address,
description: variable.description,
scaling_factor: variable.scaling_factor,
unit: variable.unit,
byte_type: variable.byte_type,
value
};
});
return {
group_name: object.group_name,
read_priority: object.read_priority,
address_type: object.address_type,
register_type: object.register_type,
group: group
};
}
function decode_coil_group(node, gapitCode, object, result) {
let bit_nr = -1;
const first = object.group[0] ? object.group[0]["address"] : Number.POSITIVE_INFINITY;
const group = object.group.map((variable)=>{
let value = 0;
bit_nr = variable.address === undefined ? ++bit_nr : variable.address - first;
if (variable.byte_type.startsWith("DUMMY")) {
value = 0;
} else if (variable.byte_type === "BIT1") {
value = result.data[bit_nr] ? 1 : 0;
} else {
return {
address: variable.address,
description: variable.description,
scaling_factor: variable.scaling_factor,
unit: variable.unit,
byte_type: variable.byte_type,
value: undefined
};
}
return {
address: variable.address,
description: variable.description,
scaling_factor: variable.scaling_factor,
unit: variable.unit,
byte_type: variable.byte_type,
value
};
});
return {
group_name: object.group_name,
read_priority: object.read_priority,
address_type: object.address_type,
register_type: object.register_type,
group: group
};
}
class RegisterDecodeReader {
async readDecode(node, client, unitId, gapitCode, object, scalingType) {
if (unitId) {
client.setID(unitId);
}
const read = await client.readRegister(this.group.registerType, this.group.startAddress, this.group.addressLength);
const data = decode_register_group(node, gapitCode, object, read, scalingType);
return {
read: read,
data: data
};
}
constructor(group){
this.group = group;
}
}
class StatusDecoderReader {
async readDecode(node, client, unitId, gapitCode, object, // eslint-disable-next-line @typescript-eslint/no-unused-vars
scalingType) {
if (unitId) {
client.setID(unitId);
}
const read = await client.readCoil(this.group.registerType, this.group.startAddress, this.group.addressLength);
const data = decode_coil_group(node, gapitCode, object, read);
return {
read: read,
data: data
};
}
constructor(group){
this.group = group;
}
}
const readDecoders = {
input: RegisterDecodeReader,
holding: RegisterDecodeReader,
status: StatusDecoderReader,
coil: StatusDecoderReader
};
function decodeReaderFactory(group) {
return new readDecoders[group.registerType](group);
}
const nodeInit = (RED)=>{
function gapitModbus(config) {
RED.nodes.createNode(this, config);
this.clients = {};
this.on("input", async (msg, send, done)=>{
try {
const activeConfig = checkNodeConfig(msg, config, this);
if (!activeConfig) return;
const groups = convertConfig(activeConfig.gapitCode, this);
if (!(activeConfig.socketAddress in this.clients) || !this.clients[activeConfig.socketAddress].isOpen) {
this.clients[activeConfig.socketAddress] = await connectModbus(this, activeConfig.host, activeConfig.portOptions);
}
if (msg.ktv && msg.kta) {
this.ktaktvRatio = msg.ktv * msg.kta;
}
if (groups instanceof Object) {
msg.gapit_results = {
[activeConfig.deviceName]: []
};
const result = [];
for (const [index, group] of groups.entries()){
const decodeReader = decodeReaderFactory(group);
const data = await decodeReader.readDecode(this, this.clients[activeConfig.socketAddress], activeConfig.unitId, activeConfig.gapitCode, activeConfig.gapitCode.objects[index], activeConfig.scalingType);
msg.gapit_results[activeConfig.deviceName].push(data.data);
result.push(data.read);
}
if (!result) return;
msg.modbus_buffer = result;
}
send(msg);
done();
} catch (error) {
this.error(error);
}
});
this.on("close", ()=>{
try {
for (const client of Object.values(this.clients)){
client.close(()=>{
/* do nothing */ });
}
} catch (error) {
this.error(error);
}
});
}
RED.nodes.registerType("gapit-modbus", gapitModbus);
};
module.exports = nodeInit;