yaacov / node-modbus-serial

A pure JavaScript implemetation of MODBUS-RTU (and TCP) for NodeJS
ISC License
636 stars 239 forks source link

Strange interaction with PLC when writing strings #546

Open jbockmon opened 8 months ago

jbockmon commented 8 months ago

PLC: Schneider m172 String length: 32 (31 + null term)

Looking through some of the other issues here, I was able to use your help to pull the string successfully from the PLC. This is the code:

console.log("initial register read:") let data = await modbusConnection.readHoldingRegisters(regAddress, 16); console.log(data.buffer) console.log((data.buffer.swap16().toString('ascii')).replace(/\0/g, '')); console.log(Array.from(data.buffer))

console.log('Register write:') const charCodes = Array.from(Buffer.from('abcdefghijklmnop', 'ascii')); await modbusConnection.writeRegisters(regAddress, charCodes); console.log(charCodes)

console.log('Second read back') data = await modbusConnection.readHoldingRegisters(regAddress, 16); console.log(data.buffer) console.log((data.buffer.swap16().toString('ascii')).replace(/\0/g, '')); console.log(Array.from(data.buffer))

The PLC contains the initial string of: "abcdefghijklmnopqrstuvwxyz" to show that it is reading properly.

Output from the code:

[2024-02-15T19:45:00.566Z] initial register read: [2024-02-15T19:45:00.643Z] <Buffer 62 61 64 63 66 65 68 67 6a 69 6c 6b 6e 6d 70 6f 72 71 74 73 76 75 78 77 7a 00 00 00 00 00 00 00> [2024-02-15T19:45:00.644Z] abcdefghijklmnopqrstuvwxyz [2024-02-15T19:45:00.644Z] [ 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 0, 0, 0, 0, 0, 0 ] [2024-02-15T19:45:00.645Z] Register write: [2024-02-15T19:45:00.739Z] [ 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112 ] [2024-02-15T19:45:00.740Z] Second read back [2024-02-15T19:45:00.812Z] <Buffer 00 61 00 62 00 63 00 64 00 65 00 66 00 67 00 68 00 69 00 6a 00 6b 00 6c 00 6d 00 6e 00 6f 00 70> [2024-02-15T19:45:00.812Z] abcdefghijklmnop [2024-02-15T19:45:00.813Z] [ 97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102, 0, 103, 0, 104, 0, 105, 0, 106, 0, 107, 0, 108, 0, 109, 0, 110, 0, 111, 0, 112, 0 ]

It appears that the writeRegisters method with the PLC is being interpreted as 16 characters with terms between each character. I'm not sure if this is due to the library or the PLC. Or possibly something to do with the endianness for each character? I can't write to the individual registers because the PLC blocks all registers after the string starts for the duration of the string length.

Any help you can provide would be appreciated.

jbockmon commented 8 months ago

After lots of shower thinking, I realized that the issue is due to the PLC packing 2 x 8 bit chars into a single 16 bit register for each register assigned to the string. Because the PLC blocks writing along the length of registers reserved for a string, the only way to get around this is bit packing each value being sent in the array with 2 chars.

Here is the code that is working for me:

async function writeStringToRegisters(regAddress, str, maxLength) { const charCodes = Array.from(Buffer.from(str, 'ascii'));

if (charCodes.length > maxLength) {
    console.warn(`String length exceeds maximum allowed length. Truncating to ${maxLength} characters.`);
    charCodes.length = maxLength;
}

while (charCodes.length < maxLength) {
    charCodes.push(0);
}

const registerValues = [];

for (let i = 0; i < charCodes.length; i += 2) {
    const char1 = charCodes[i + 1] || 0; 
    const char2 = charCodes[i] || 0; 
    const value = (char1 << 8) | char2; 
    registerValues.push(value);
}

try {
    await modbusConnection.writeRegisters(regAddress, registerValues);
    console.log('Write successful');
} catch (error) {
    console.error('Write failed:', error);
}

}

In my packing, I had to switch endianness for my PLC, but if you want it without it swapping the packed chars, it's this:

for (let i = 0; i < charCodes.length; i += 2) { const char1 = charCodes[i] || 0; const char2 = charCodes[i + 1] || 0; const value = (char1 << 8) | char2; registerValues.push(value); }

My issue is resolved, but I'll comment and keep this here for you folks to close should you want the code. My suggestion would be to implement a string writing function but don't want to overstep if that would be considered bloat.

yaacov commented 8 months ago

Hello,

Thank you for the issue, and for the work around :green_heart:

It looks like a good example code that may help other users, can you consider adding an example for reading and writing strings to "Schneider m172" to the examples directory ?

Other examples in the example directory: https://github.com/yaacov/node-modbus-serial/tree/master/examples