yaacov/node-modbus-serial

After some time of working, the PLC suddenly stopped responding to requests

haydave opened this issue · 0 comments

It is usually stopped after some failed requests either
2023-06-29T06:41:29.365Z startGettingData Timed out ETIMEDOUT
or
2023-06-29T06:33:26.760Z startGettingData Port Not Open ECONNREFUSED
or
2023-07-06T05:27:19.498Z connect ETIMEDOUT {IP}:{PORT} -110

HMI continues working correctly. Our system gets the responses after restarting the PLC or plugging out/in the LAN cable.

It issues accrues when code is deployed in AWS ECS. During the local deployment, we didn't face it yet.

I attempted to read both individual registers and multiple registers simultaneously. The issue persists. Interestingly, even though the requests count to PLC is lowered drastically, the issue arises more frequently in the case of reading several registers at once.

PLC Model:
photo_2023-06-29_10-54-04

The following is my setup:
main.js

const modbus = require('./src/plc/plc');
const getters = require('./src/redis');
async function main() {
    try {
        ...
        await modbus.connect();
        getters.startGettingData();
        ...
    } catch (error) {
        console.log(new Date().toISOString(), 'Something goes wrong', error.message, error.errno);
        modbus.client.close();
        process.exit(1);
    }
};
main();

plc.js

const ModbusRTU = require('modbus-serial');
const { setTimeout } = require('timers/promises');
let client = new ModbusRTU();

// check error, and reconnect if needed
async function checkError() {
    console.log(new Date().toISOString(), "we have to reconnect");

    // close port
    await client.close();
    await setTimeout(5000);
    // re open client
    client = new ModbusRTU();
    await setTimeout(2000);
    await connect();
}

// open connection to a serial port
async function connect() {
    // if client closed, open a new connection
    console.log(new Date().toISOString(), `Trying to connect: ${process.env.PLC_IP}:${process.env.PLC_PORT}`);
    try {
        await client.connectTCP(process.env.PLC_IP, { port: Number(process.env.PLC_PORT) });
        await setClient();
        console.log(new Date().toISOString(), "Connected");
    } catch (error) {
        console.log(new Date().toISOString(), error.message, error.errno);
        await checkError();
    }
}

async function setClient() {
    // set the client's unit id
    // set a timout for requests default is null (no timeout)
    await client.setID(1);
    await client.setTimeout(10000);
}

module.exports = {
    client,
    connect,
    checkError,
};

redis/index.js

const plc = require('../plc/handlers/handlers');
const redisClient = require('./redisClient');
....
const getDeviceState = async () => {
    const devicesStates = await plc.getDevicesStates();
    await redisClient.hSet('devicesStates', devicesStates);
};
...
const timeout = ms => new Promise(resolve => setTimeout(resolve, ms));

const startGettingData = async () => {
    try {
        await getDeviceMode();
        await getDevicesRemoteMode();
        await getDeviceState();
        await getWindowsCurrentValues();
        await paramStates();
        await paramsConfigs();
        await timeout(constants.getParamsStatesInterval);
        console.log(new Date().toISOString(), 'startGettingData');
        return await startGettingData();
    } catch (error) {
        console.log(new Date().toISOString(), 'startGettingData', error.message, error.errno);
        await reconnect(error.errno);
        return await startGettingData();
    }
};

const reconnect = async (errorType) => {
    if (errorType !== 'ETIMEDOUT') {
        console.log('restart server')
        process.exit(1);
    }
};

module.exports = {
    startGettingData,
};

handlers.js – getting several register values at once

....
getDevicesStates: async () => {
    const deviceStateRegs = regTypeM_RW.deviceState;
    let devicesStates = {};
    // NOTE: Group windows related: holding registers
    ․․․
    let modbusRes = await modbus.client.readHoldingRegisters(min.num, (max.num - min.num + 1));
    for (let i = 0; i < modbusRes.data.length; i++) {
        const curRegNum = min.num + i;
        const foundRegs = windowsDeviceStates.filter(reg => reg.num === curRegNum);
        for (let j = 0; j < foundRegs.length; j++) {
            const deviceName = foundRegs[j].name;
            devicesStates[deviceName] = modbusRes.data[i];
        }
    }
    await timeout(1000);
    // NOTE: Group not windows related: coils
    ...
    await timeout(1000);
    return devicesStates;
}
...

handlers.js – getting one by one example

....
getDevicesStates: async () => {
    const deviceStateRegs = regTypeM_RW.deviceState;
    let devicesStates = {};
    for (let i = 0; i < deviceStateRegs.length; i++) {
        const deviceName = deviceStateRegs[i].name;
        const deviceNum = deviceStateRegs[i].num;
        let deviceState = {
            data: []
        };
        if (deviceName.includes('window')) {
            deviceState = await modbus.client.readHoldingRegisters(deviceNum, constants.registersReadLength);
        } else {
            deviceState = await modbus.client.readCoils(deviceNum, constants.registersReadLength);
        }
        devicesStates[deviceName] = deviceState.data[0];
        await timeout(1000);
    }
    return devicesStates;
}
...

Could you kindly provide me with guidance on the appropriate direction to dig? Is it something related to latency or PLC capabilities? Or maybe there are some subtleties which are not considered in the code.