Search code examples
c#ethernetindustrial

Ethernet/IP Only one usage of each socket address (protocol/network address/port) is normally permitted


I am using Ethernet/IP (Ethernet Industrial Protocol) library from https://github.com/rossmann-engineering/EEIP.NET

Overall, the library does what I need, but I ran into a socket issue. It works fine when I talk to a single VFD (in my case, Weg VFD with Anybus Ethernet/IP card).

In this project I have 3 VFDs using Ethernet/IP to communicate. I can connect to each drive separately and set the speed, read speed, read inputs, write outputs, etc. The problem is when I try to connect to more than one drive at the same time, I get: "Only one usage of each socket address (protocol/network address/port) is normally permitted"

I have 3 separate client instances, one for each drive.

So far I have looked at the following along with other places:

WCF: System.Net.SocketException - Only one usage of each socket address (protocol/network address/port) is normally permitted

HttpClient: Only one usage of each socket address (protocol/network address/port) is normally permitted

TCP Socket Error: Only one usage of each socket address (protocol/network address/port) is normally permitted

Only one usage of each socket address (protocol/network address/port) is normally permitted

I understand that the issue is not exactly from the EEIP.NET library itself, but rather Windows telling me that the same combination of IP Address and Port cannot be used (and by "same combination" I mean my computer's IP address and port 2222).

The message is very clear, but I know if this was a PLC, it would have no problem talking to multiple devices at the same time.

To test my theory I ran 3 instances of my application and tried to connect to different drives. The first one connects, the second one abruptly exits the app.

The relevant part of code is below:

    private async void BtnOpenVFD_Click(object sender, RoutedEventArgs e)
    {
        if (_vfd_running) return;

        var random = new Random();

        if (!(LstBoxDevices.SelectedItem is CIPIdentityItemViewModel item)) return;
        _client_vfd = new EEIPClient() { IPAddress = item.IPAddress.ToString() };
        var ping = new Ping();
        if (ping.Send(_client_vfd.IPAddress).Status == IPStatus.Success)
            _vfd_session = _client_vfd.RegisterSession();

        if (_vfd_session == 0) return;

        _client_vfd.ConfigurationAssemblyInstanceID = 0x66;

        //Parameters from Originator -> Target
        _client_vfd.O_T_InstanceID = 150; //Instance ID of the Output Assembly
        _client_vfd.O_T_Length = 16; //The Method "Detect_O_T_Length" detect the Length using an UCMM Message
        _client_vfd.O_T_RealTimeFormat = RealTimeFormat.Header32Bit;   //Header Format
        _client_vfd.O_T_OwnerRedundant = false;
        _client_vfd.O_T_Priority = Priority.Scheduled;
        _client_vfd.O_T_VariableLength = false;
        _client_vfd.O_T_ConnectionType = ConnectionType.Point_to_Point;
        _client_vfd.RequestedPacketRate_O_T = 250000; //500ms is the Standard value

        //Parameters from Target -> Originator
        _client_vfd.T_O_InstanceID = 100;
        _client_vfd.T_O_Length = 16;
        _client_vfd.T_O_RealTimeFormat = RealTimeFormat.Modeless;
        _client_vfd.T_O_OwnerRedundant = false;
        _client_vfd.T_O_Priority = Priority.Scheduled;
        _client_vfd.T_O_VariableLength = false;
        _client_vfd.T_O_ConnectionType = ConnectionType.Point_to_Point;
        _client_vfd.RequestedPacketRate_T_O = 250000;    //RPI in  500ms is the Standard value

        try
        {
            //Forward open initiates the Implicit Messaging
            _client_vfd.ForwardOpen();

            await Task.Run(async () =>
            {
                _vfd_running = true;
                Dispatcher.Invoke(() => { LblStatus.Content = $"Opened connection to VFD successfully"; });

                bool stop=true, fwd=false, rev=false;
                int rpm_sp = 0;

                ushort ow3 = 0;
                short ow4 = 0, ow5 = 0, ow6 = 0, ow7 = 0;

                while (!_cts_vfd.Token.IsCancellationRequested)
                {

                    Dispatcher.Invoke(() =>
                    {
                        stop = (RadBtnStop.IsChecked == true);
                        fwd = (RadBtnFwd.IsChecked == true);
                        rev = (RadBtnRev.IsChecked == true);
                        
                        if (!int.TryParse(TBoxSpeedSP.Text, out rpm_sp)) rpm_sp = 300;

                        var status = (CFW11_EEIP_Status)BitConverter.ToInt16(_client_vfd.T_O_IOData, 0); // word 1
                        var spd_fb = BitConverter.ToInt16(_client_vfd.T_O_IOData, 2); // word 2

                        _vfd_di.UInt16Value = BitConverter.ToUInt16(_client_vfd.T_O_IOData, 4); // word 3
                        _vfd_analogio.AI1 = BitConverter.ToInt16(_client_vfd.T_O_IOData, 6); // word 4
                        _vfd_analogio.AI2 = BitConverter.ToInt16(_client_vfd.T_O_IOData, 8); // word 5
                        _vfd_analogio.AI3 = BitConverter.ToInt16(_client_vfd.T_O_IOData, 10); // word 6
                        _vfd_analogio.AI4 = BitConverter.ToInt16(_client_vfd.T_O_IOData, 12); // word 7
                        var current = BitConverter.ToInt16(_client_vfd.T_O_IOData, 14); // word 8

                        ow3 = _vfd_do.UInt16Value;
                        ow4 = _vfd_analogio.AO1;
                        ow5 = _vfd_analogio.AO2;
                        ow6 = _vfd_analogio.AO3;
                        ow7 = _vfd_analogio.AO4;

                        var rpm_fb = (0.0 + spd_fb) / 8192.0 * 1800.0;
                        var motor_amps = (0.0 + current) / 10.0;

                        TBlkSpeedFB.Text = $"{rpm_fb:F1} RPM";
                        TBlkAmpFB.Text = $"{motor_amps:F1} A";
                    });

                    CFW11_EEIP_Control ctrl = CFW11_EEIP_Control.None;
                    if (!stop)
                    {
                        ctrl = CFW11_EEIP_Control.LOC_REM | CFW11_EEIP_Control.GeneralEnable | CFW11_EEIP_Control.Run;
                        if (fwd)
                        {
                            ctrl |= CFW11_EEIP_Control.Forward;
                        }
                        else if (rev)
                        {
                            ctrl &= ~CFW11_EEIP_Control.Forward;
                        }
                    }

                    var speed_counts = (short)((0.0 + rpm_sp) * 8192.0 / 1800.0);

                    BitConverter.GetBytes((short)ctrl).CopyTo(_client_vfd.O_T_IOData, 0); // word 1
                    BitConverter.GetBytes(speed_counts).CopyTo(_client_vfd.O_T_IOData, 2); // word 2
                    BitConverter.GetBytes(ow3).CopyTo(_client_vfd.O_T_IOData, 4); // word 3
                    BitConverter.GetBytes(ow4).CopyTo(_client_vfd.O_T_IOData, 6); // word 4
                    BitConverter.GetBytes(ow5).CopyTo(_client_vfd.O_T_IOData, 8); // word 5
                    BitConverter.GetBytes(ow6).CopyTo(_client_vfd.O_T_IOData, 10); // word 6
                    BitConverter.GetBytes(ow7).CopyTo(_client_vfd.O_T_IOData, 12); // word 7

                    await Task.Delay(250);
                }
            });

            _client_vfd.ForwardClose();
            Dispatcher.Invoke(() => { LblStatus.Content = $"Closed connection to VFD successfully"; });
            _vfd_running = false;
            _cts_vfd = new CancellationTokenSource();

        }
        catch (CIPException exc)
        {
            LblStatus.Content = $"{exc.Message}";
        }
        catch (Exception exc)
        {
            var dummy = exc.Message;
        }
    }

P.S.:

In the line: _client_vfd = new EEIPClient() { IPAddress = item.IPAddress.ToString() }; I tried to add OriginatorUDPPort as follows: _client_vfd = new EEIPClient() { IPAddress = item.IPAddress.ToString(), OriginatorUDPPort = (ushort)random.Next(2000, 5000) };

This got rid of the error, but also made no actual connection to the VFD (or at least it seemed to ignore the run command, it also did not send any status information).

My understanding is the port (2222) must be hardcoded somewhere in the protocol.

Can this be circumvented somehow. Is there a solution to my issue?

The next thing I would consider is add a USB Ethernet card, bridge connections, program a PLC to connect to 3 drives, use my computer as a middle-man with WireShark running, but that seems like an overkill.

What other options do I have?

Closing one connection to open another may not be an option because I have to read the inputs from each drive and display realtime data on the screen. All this happens about 50 times per second (which is pretty slow).

P.P.S.

I don't have enough rep to add a new tag, but #ethernet-ip would be appropriate.


Solution

  • 2222 is a UDP port, so you would have to use different port numbers for different devices. Some Googling suggests that the OriginatorUDPPort is the correct way to do this and also suggests that some devices are buggy and always use the default port. It sounds like that matches what you're seeing.

    If eeip.net doesn't support hooking into the protocol and forwarding packets to client instances based on source IP, then you could open your own UDP socket on 2222 and do the forwarding to the other ports.