Apollo3zehn / FluentModbus

Lightweight and fast client and server implementation of the Modbus protocol (TCP/RTU).
MIT License
189 stars 71 forks source link

FluentModbus.ModbusException: 'The protocol identifier is invalid.' #71

Open kirillivan0ff opened 2 years ago

kirillivan0ff commented 2 years ago

Seems like a var unitIdentifier = 0xFF; giving this error when I using your examples.

The device manual saying: "INPUT DATA AREA The following table lists the registers of the input area (produced from the instrument and read by the master), common to all PROFINET, ETHERCAT, ETHERNET/IP fieldbuses. The registers are 16 bit in size. The input area is updated at a maximum frequency of 125 Hz (80 Hz in case of FIELBUS). The size of the output area configured in the master fieldbus must match the size configured in the instrument" Which seems to be OK. 16bit."

EasyModbus library reading registers but I have other issues with it.

Any suggestions?

Apollo3zehn commented 2 years ago

Is it a Modbus TCP or RTU problem? Can I reproduce it somehow?

From the error message it seems that the device is sending an incorrect response header but that is difficult to say.

kirillivan0ff commented 2 years ago

Is it a Modbus TCP or RTU problem? Can I reproduce it somehow?

From the error message it seems that the device is sending an incorrect response header but that is difficult to say.

It's TCP. Trying to speak with this guy and not really successful. Not sure if you can reproduce it but if I can check the header somehow I'll be glad to try it.

Apollo3zehn commented 2 years ago

You could add a breakpoint to that line:

https://github.com/Apollo3zehn/FluentModbus/blob/ae88394c51cbe109c9ea5cae1c08bc57860fb6cc/src/FluentModbus/Client/ModbusTcpClient.cs#L251

And then see what data the routine received and compare it to the Modbus spec. Alternatively you could try Wireshark to capture the Modbus traffic.

kirillivan0ff commented 2 years ago

It’s 8224

Make any sense for you?

I can try to catch packets with Wireshark and this would probably be my next step… During next few days.

Apollo3zehn commented 2 years ago

8224 is not correct but I do not know why. I am sure Wireshark will help.

kirillivan0ff commented 2 years ago

8224 is not correct but I do not know why. I am sure Wireshark will help.

OK, I think you can close this one for now. I will ping you up if I will find something interesting.

kirillivan0ff commented 2 years ago

Hi, It’s me again.

The device reply with wrong Protocol ID. Is this number make some sense to you?

From: Apollo3zehn @.> Sent: Monday, May 30, 2022 2:53 PM To: Apollo3zehn/FluentModbus @.> Cc: Kirill @.>; Author @.> Subject: Re: [Apollo3zehn/FluentModbus] FluentModbus.ModbusException: 'The protocol identifier is invalid.' (Issue #71)

You could add a breakpoint to that line:

https://github.com/Apollo3zehn/FluentModbus/blob/ae88394c51cbe109c9ea5cae1c08bc57860fb6cc/src/FluentModbus/Client/ModbusTcpClient.cs#L251

And then see what data the routine received and compare it to the Modbus spec. Alternatively you could try Wireshark to capture the Modbus traffic.

— Reply to this email directly, view it on GitHub https://github.com/Apollo3zehn/FluentModbus/issues/71#issuecomment-1141062871 , or unsubscribe https://github.com/notifications/unsubscribe-auth/AA6XP32E2ZIVT5EUWORUNLTVMSTYFANCNFSM5XJTIR3A . You are receiving this because you authored the thread. https://github.com/notifications/beacon/AA6XP34GR4GPV3MADHJJYTDVMSTYHA5CNFSM5XJTIR3KYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOIQBTZVY.gif Message ID: @. @.> >

Apollo3zehn commented 2 years ago

You mean the value 8224? No it does not really make sense to me. Do you have a Wireshark screenshot or log of the full response of your device?

fred777 commented 11 months ago

Hi! I just run into the same issue and I have absolutely no clue why, see attached sample code which is being compiled with VS2022 for .NET Framework 4.8 x64 on Win10 and FluentModbus 5.0.2. The first block of code will run without any issues, but the second one - which does the very same thing but encapsulated within a class - will always fail after some delay with FluentModbus.ModbusException: 'The protocol identifier is invalid.'

(and it will also fail if I just remove the first code block....)

namespace ModbusTest
{
    using System;
    using System.Net;
    using System.Threading.Tasks;
    using FluentModbus;   

    internal class Program
    {
        static async Task Main(string[] args)
        {
            // this blocks runs without a flaw
            {
                var client = new ModbusTcpClient();
                client.Connect(IPAddress.Parse("192.168.4.25"), ModbusEndianness.BigEndian);

                if (!client.IsConnected) throw new Exception("not connected!");

                var x = (await client.ReadInputRegistersAsync<float>(0, 1000, 1)).Span[0];
                if (x != 123.456f) Console.WriteLine($"Endianness does not seem to be correct! {x}");

                var version = await client.ReadInputRegistersAsync<short>(0, 1002, 2);
                var serial = await client.ReadInputRegistersAsync<int>(0, 1006, 1);

                Console.WriteLine(
                    $"Found device: serial {serial.Span[0]}, firmware {version.Span[1]}, hardware {version.Span[0]}");
                client.Disconnect();
            }

            // this blocks always fails after some delay with FluentModbus.ModbusException: 'The protocol identifier is invalid.'
            {
                var client = new ModbusInterface();
                client.Connect(IPAddress.Parse("192.168.4.25"));

                if (!client.IsConnected) throw new Exception("not connected!");
                var info = await client.GetInfo();
                Console.WriteLine($"Found device: {info}");
            }
        }
    }

    internal class ModbusInterface
    {
        public async void Connect(IPAddress ipAddress)
        {
            Disconnect();
            _client = new ModbusTcpClient();
            _client.Connect(ipAddress, ModbusEndianness.BigEndian);
            Console.WriteLine("Connected.");
            {
                const float expected = 123.456f;
                var actual = (await _client.ReadInputRegistersAsync<float>(UnitIdentifier, 1000, 1)).Span[0];
                if (actual != expected)
                {
                    throw new Exception($"Endianness does not seem to be correct! (got {actual} but expected {expected}");
                }
            }
            Console.WriteLine("Verified.");
        }

        public void Disconnect()
        {
            if (_client == null) return;
            Console.WriteLine("Disconnecting...");
            _client.Disconnect();
            _client = null;
        }

        public bool IsConnected
        {
            get
            {
                if (_client == null) return false;
                return _client.IsConnected;
            }
        }

        public async Task<string> GetInfo()
        {
            if (!IsConnected) return "not connected!";

            // this always fails after some delay with FluentModbus.ModbusException: 'The protocol identifier is invalid.'
            var version = await _client.ReadInputRegistersAsync<short>(UnitIdentifier, 1002, 2);
            var serial = await _client.ReadInputRegistersAsync<int>(UnitIdentifier, 1006, 1);

            return $"serial {serial.Span[0]}, firmware {version.Span[1]}, hardware {version.Span[0]}";
        }

        private ModbusTcpClient _client;
        private const int UnitIdentifier = 0;
    }
}
karateboy commented 5 months ago

I ran into the same situation. I also take the aysnc/await usage. I use it for device calibration. In this case, dely during between reading/writing is very common practice.

karateboy commented 5 months ago

@Apollo3zehn Please mark this issue as open. I may take sometime study how to fix it.

karateboy commented 5 months ago

It is common to use a modbus gateway to map Modbus RTU devices into Modbus TCP registers. Since they are in fact different devices, my application are using different tasks with individual modbus clients to read/write those Modbus registers/coils.

I found that without locking the read/write may fail randomly. (after enforcing SemaphoreSlim locking, this kind of error disappear) I don't think it a bug. However, if the library can maintain a semaphore for the modbus device (ID is IP + slave ID), it can save a lot of time for people like me to figure out what went wrong.

As for the 'The protocol identifier is invalid.' error, I suspect it may result from my "configureAwit(false)". I just chain all my assync modbus actions into "configureAwit(true)" to see if the error disappear. It may take some time to test.

Apollo3zehn commented 5 months ago

It would be great if you create a pull request or show me some code to illustrate the required changes :-) Thank you

karateboy commented 4 months ago

I wish I can provide you a pull request but I don't have time to prepare it and fully tested. So instead, I provide you my workaround for youre reference.

I hava a wrapper class called ModbusClient which encapsulate your ModbusTcpClient. ModbusClient has a static ConcurrentDictionary storing pairs of semaphore and IP/Port and a internal semaphore reference.

public class ModbusClient : IDisposable { private record ModbusTarget(IPAddress Address, int Port); private static readonly ConcurrentDictionary<ModbusTarget, SemaphoreSlim> TargetLockMap = new(); private readonly SemaphoreSlim _targetLock; ... }

In the constructor, It try to get/add the semaphore from/to the concurrent map. Thus, for modbus clients of the same IP, they will share the same semaphore.

public ModbusClient(Device device...) { ... var modbusTarget = GetModbusTarget(device); // Generate record according to device IP and port TargetLockMap.TryGetValue(modbusTarget, out var targetLock); _targetLock = targetLock ?? TargetLockMap.GetOrAdd(modbusTarget, new SemaphoreSlim(1, 1)); }

For each modbus read/write operation, e.g. read coil. ModbusClient has to wait for the semaphore before the operation.

public async Task<List<(DeviceSignalIo.IDeviceSignal, bool)>> ReadSignalAsync(CancellationToken cancellationToken){ try { await _targetLock.WaitAsync(cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested();

                var coilMemory =
                    await client.ReadCoilsAsync((byte)device.SlaveId, (ushort)signal.Address + signal.Offset, 1,
                        cancellationToken);
    }
    catch (Exception ex)
    {

    }
    finally
    {
        _targetLock.Release();
    }
    ....

}

For async task, it is possible different tasks executing on the same thread may read/write modbus master at the same time. Without a semaphore, multiple read/write may try to access the underlying network stream at the same time. A Semaphore(1, 1) ensure only one operaton can be performed until the previous result come back.

In contrast, for disk or other high throughput devices, they support outstanding I/O, which means the master can issue multiple requests before the previous result return. However, for modbus devices, this kind of operation is not supported.

karateboy commented 4 months ago

Here is my scenario, I hava multiple timely tasks to reading modbus devies data at the same time. Also I have a task to perform the calibration of the modbus devices. Each task has its onw ModbusClient and may operate at the same time. By the way, after I add those logics, the protocol identifier invalid disappear.