Resolving IOException Polling Siemens LOGO! 8 Modbus TCP

David Krause13 min read
ModbusSiemensTroubleshooting
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

1. Problem Overview: System.IO.IOException on LOGO! Modbus Read

When a C# Universal Windows Application (UWA/UWP) uses the EasyModbusTCP library to poll a Siemens LOGO! 6ED1052-1HB08-0BA0 (LOGO! 24RCEo, LOGO! 8 family) over Ethernet, the call

bool[] i1Value = modbusClient.ReadDiscreteInputs(0, 1)[0];

raises System.IO.IOException even though the LOGO! is reachable from a browser. The web interface responds, the IP is in range, and the firewall has been disabled, yet the socket call still fails. This symptom is extremely common when integrating LOGO! 8 with managed .NET code on Windows because it is usually caused by three interacting factors: a missing UWP network capability, an incorrect TCP port, or an out-of-range Modbus address for the LOGO! 8 server.

This article walks through the official Siemens mapping for the LOGO! 8 Modbus TCP server, the network configuration that must exist on the device, the EasyModbus call sequence that is known to work, and the UWP manifest change that is required to permit private-network socket I/O.

Critical: The default LOGO! 8 Modbus TCP server uses TCP port 502 (standard Modbus). Some LOGO! Soft Comfort projects remap this to 503 or a custom value through the "Server Connection" parameters in the Ethernet dialog. Always verify the actual port from LOGO! Soft Comfort or the device's web UI before debugging socket code.

2. Root Cause Analysis: Why IOException Occurs

EasyModbusTCP wraps a System.Net.Sockets.TcpClient and surfaces low-level failures as System.IO.IOException. The same exception class is also raised when the underlying socket cannot be created at all. With a UWP/UWA host process, there are three probable root causes ordered by frequency.

Rank Root Cause Symptom Fix Location
1 UWP app missing privateNetworkClientServer capability All socket calls fail; Ping works in browser because it is a different process Package.appxmanifest
2 Modbus TCP server not enabled on LOGO! 8, or wrong TCP port Socket connects and is immediately reset; or connect itself throws LOGO! Soft Comfort, web UI
3 Modbus address outside the LOGO! 8 supported range Connect succeeds, but the first PDU raises IOException on response Address mapping table

Open the LOGO! web UI in a browser first to confirm IP reachability and Modbus TCP status. The page http://<logo-ip>/ exposes the "Modbus TCP" toggle and current port. According to the LOGO! 8 system manual, the device ships with the Modbus TCP server enabled and bound to port 502. If the page indicates "disabled," you must enable it from LOGO! Soft Comfort before any client can complete a transaction.

3. UWA/UWP Networking Capability Constraint

Universal Windows Apps run inside an AppContainer sandbox. By default, the AppContainer has no permission to open TCP sockets to hosts on the local subnet. This is the single most common reason a .NET Modbus client works from a Win32 console app but throws System.IO.IOException from a UWP/UWA binary.

You must declare the capability in Package.appxmanifest:

<?xml version="1.0" encoding="utf-8"?>
<Package
  xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
  xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
  xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
  IgnorableNamespaces="uap mp">
  <Capabilities>
    <Capability Name="internetClient" />
    <Capability Name="privateNetworkClientServer" />
  </Capabilities>
</Package>

The privateNetworkClientServer capability permits both inbound and outbound socket I/O on the local network and is required for any Modbus TCP client or server running inside a UWA. The internetClient capability alone is insufficient because LOGO! 8 sits on the LAN, not on the public internet. Without privateNetworkClientServer, TcpClient.Connect() raises System.IO.IOException ("A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond") even when ping succeeds from a different application.

Verification: After editing Package.appxmanifest, rebuild the UWA package and re-deploy. Modifying the manifest after a previous deployment is not picked up by an incremental rebuild; you must perform a clean deployment for the new capability to take effect.

4. LOGO! 8 Hardware Reference: 6ED1052-1HB08-0BA0

The part number 6ED1052-1HB08-0BA0 decodes as follows:

Field Value Meaning
Series 6ED1052 LOGO! 8 base module
Variant 1HB 24RCEo: 24 V DC supply, relay outputs, integrated Ethernet
HW/FW 08 LOGO! 8.3 generation; Modbus TCP server on port 502
Order suffix 0BA0 Standard catalog order form

The integrated Ethernet interface exposes the LOGO! 8 as a Modbus TCP server. The relevant specifications are documented in the LOGO! 8 System Manual, which is published under Siemens article ID 109741041 on the Siemens Industry Online Support portal. The Modbus TCP application example is published as 109769926. Both documents confirm the following server-side capabilities:

  • Supported function codes: 01 (Read Coils), 02 (Read Discrete Inputs), 03 (Read Holding Registers), 04 (Read Input Registers), 05 (Write Single Coil), 06 (Write Single Register), 15 (Write Multiple Coils), 16 (Write Multiple Registers).
  • Default TCP port: 502.
  • Maximum simultaneous client connections: 8 (firmware-dependent; verify against the manual for your FS level).
  • Modbus unit identifier (slave address): 255 by default; LOGO! ignores the unit identifier field in TCP mode per the Modbus organization specification, but some clients still emit it.

5. LOGO! 8 Modbus TCP Address Mapping

The address map below is the canonical mapping from the LOGO! 8 System Manual. The base offset is zero-based; some LOGO! 8 firmware versions present 1-based offsets in the documentation, but the wire format is always 0-based. Always verify against your specific firmware version through the manual.

LOGO! Object Modbus Object Function Code Address Range (0-based) Access
Digital inputs I1 to I24 Discrete Inputs 02 0 to 23 Read
Digital outputs Q1 to Q20 Coils 01, 05, 15 0 to 19 Read / Write
Markers / flags M1 to M64 Coils 01, 05, 15 20 to 83 Read / Write
Variable memory VW (analog) area Holding Registers 03, 06, 16 0 to 849 Read / Write
Analog inputs / process values Input Registers 04 0 to 31 Read

For the user's use case — reading I1, Q1, and Q2 status — the correct calls are:

bool[] inputs  = modbusClient.ReadDiscreteInputs(0, 1);   // I1
bool[] q1      = modbusClient.ReadCoils(0, 1);            // Q1
bool[] q2      = modbusClient.ReadCoils(1, 1);            // Q2
Address ambiguity: The official Siemens manual documents the LOGO! 8 address space as 0-based at the protocol level, but LOGO! Soft Comfort's user interface labels inputs and outputs with 1-based numbering (I1, I2, ...). If a request returns IOException only on certain reads, swap 0 for 1 as the base and confirm the next three reads (I1, I2, I3 or Q1, Q2, Q3) return the expected pattern.

6. Network Prerequisites Before Coding

Confirm the following from the LOGO! 8 web UI (http://<logo-ip>) before running the C# client:

  1. IP address is on the same subnet as the Windows host running the UWA.
  2. Subnet mask and default gateway are set (gateway optional for direct connection).
  3. Modbus TCP server toggle is set to "On" or "Enabled." This is the default on most LOGO! 8 firmware versions but is sometimes disabled in user projects.
  4. TCP port is 502 unless intentionally remapped.
  5. No more than the documented maximum number of Modbus clients are connected simultaneously.
  6. If a Windows firewall rule is in place, it permits inbound/outbound TCP to the LOGO! IP on port 502.

7. Working C# Implementation with EasyModbus

The NuGet package EasyModbusTCP (versions 5.x through 7.x) ships the EasyModbus.ModbusClient class. The class exposes Connect(), Connected, ConnectionTimeout, and the discrete-input / coil read methods used in the user's code. The constructor accepts host and port; the default constructor uses localhost:502 and is not appropriate for the LOGO!.

using EasyModbus;
using System;
using System.IO;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;

public sealed class Logo8ModbusClient : IDisposable
{
    private readonly string _host;
    private readonly int _port;
    private readonly int _connectTimeoutMs;
    private readonly int _readTimeoutMs;
    private ModbusClient _client;
    private readonly object _gate = new object();

    public Logo8ModbusClient(string host, int port = 502,
                             int connectTimeoutMs = 2000,
                             int readTimeoutMs = 2000)
    {
        _host = host;
        _port = port;
        _connectTimeoutMs = connectTimeoutMs;
        _readTimeoutMs = readTimeoutMs;
    }

    public bool IsConnected => _client != null && _client.Connected;

    public void Connect()
    {
        lock (_gate)
        {
            if (IsConnected) return;

            _client = new ModbusClient(_host, _port)
            {
                ConnectionTimeout = _connectTimeoutMs
            };

            try
            {
                _client.Connect();
            }
            catch (IOException ex)
            {
                throw new IOException(
                    $"Modbus TCP connect to {_host}:{_port} failed: {ex.Message}", ex);
            }
            catch (SocketException ex)
            {
                throw new IOException(
                    $"Socket error connecting to {_host}:{_port} (code {ex.SocketErrorCode}).", ex);
            }
        }
    }

    public bool[] ReadInputs(int address, int count)
    {
        return ExecuteWithReconnect(
            () => _client.ReadDiscreteInputs(address, count));
    }

    public bool[] ReadOutputs(int address, int count)
    {
        return ExecuteWithReconnect(
            () => _client.ReadCoils(address, count));
    }

    private bool[] ExecuteWithReconnect(Func<bool[]> op)
    {
        lock (_gate)
        {
            for (int attempt = 0; attempt < 2; attempt++)
            {
                if (!IsConnected) Connect();
                try
                {
                    return op();
                }
                catch (IOException)
                {
                    SafeDisconnect();
                    if (attempt == 1) throw;
                }
                catch (SocketException)
                {
                    SafeDisconnect();
                    if (attempt == 1) throw;
                }
            }
            return Array.Empty<bool>();
        }
    }

    private void SafeDisconnect()
    {
        try { _client?.Disconnect(); } catch { /* swallow on teardown */ }
    }

    public void Dispose()
    {
        lock (_gate)
        {
            SafeDisconnect();
            _client = null;
        }
    }
}

For a UWA host, wrap the polling loop in a background task and marshal the result back to the UI thread through the dispatcher. EasyModbus's blocking I/O will deadlock the UI thread if called directly from a button click handler.

private async void PollButton_Click(object sender, RoutedEventArgs e)
{
    using var logo = new Logo8ModbusClient("192.168.0.10", 502);

    bool[] input;
    bool[] q1;
    bool[] q2;
    try
    {
        input = await Task.Run(() => logo.ReadInputs(0, 1));
        q1    = await Task.Run(() => logo.ReadOutputs(0, 1));
        q2    = await Task.Run(() => logo.ReadOutputs(1, 1));
    }
    catch (IOException ex)
    {
        StatusTextBlock.Text = $"Modbus error: {ex.Message}";
        return;
    }

    Input1TextBlock.Text  = input[0] ? "ON" : "OFF";
    Output1TextBlock.Text = q1[0]    ? "ON" : "OFF";
    Output2TextBlock.Text = q2[0]    ? "ON" : "OFF";
}

8. Connection Lifecycle and Socket Management

EasyModbusTCP holds the TCP socket open for the lifetime of the ModbusClient instance. The LOGO! 8 server has a server-side idle timer that closes idle sockets after a few minutes (firmware-dependent). When the server closes the socket, the next client read raises System.IO.IOException with the message "Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host." This is normal — it is not a configuration error.

Recommended pattern for a long-running UWA:

  1. Use a singleton ModbusClient wrapped by a reconnection helper.
  2. On any IOException, call Disconnect() and rebuild the client.
  3. Set ConnectionTimeout to 1000–3000 ms. Values below 500 ms cause flaky behavior on switched networks.
  4. Do not share a single ModbusClient instance across UWP pages. UWP may suspend the page and tear down the underlying socket; recreate the client on each OnNavigatedTo.
  5. Disable Nagle's algorithm on the socket for sub-50 ms Modbus polling. EasyModbus exposes this via modbusClient.TcpClient.NoDelay = true; after construction.

9. Alternative Libraries

If EasyModbusTCP continues to throw IOException after the manifest capability is added and the address map is corrected, the recommended replacement is the open-source NModbus library (NModbus NuGet package). It exposes a more granular IModbusMaster interface and a Transport abstraction that makes the IOException→reconnect boundary explicit.

using (var tcp = new TcpClient())
{
    tcp.NoDelay = true;
    tcp.Connect("192.168.0.10", 502);
    var factory = new ModbusFactory();
    IModbusMaster master = factory.CreateMaster(tcp);
    master.Transport.ReadTimeout = 2000;
    master.Transport.WriteTimeout = 2000;

    bool[] inputs = master.ReadInputs(1, 0, 1);   // unit id 1, addr 0, count 1
    bool[] q1     = master.ReadCoils(1, 0, 1);
    bool[] q2     = master.ReadCoils(1, 1, 1);
}

For industrial PCs running a Win32 console or Windows service rather than UWA, NModbus is the more stable choice because no AppContainer sandbox is involved and the IOException is always network- or device-side.

10. Verification and Diagnostics

  1. Browser check. Open http://<logo-ip> in Edge or Chrome and confirm the LOGO! 8 home page loads. This proves IP reachability but does not prove Modbus TCP is enabled.
  2. Modbus TCP check. From the host's command line, run Test-NetConnection -ComputerName 192.168.0.10 -Port 502 in PowerShell. Expected output: TcpTestSucceeded : True. If false, the port is closed or filtered.
  3. Third-party client. Install QModMaster or Modbus Poll on the host (not inside UWA), point it at the LOGO! IP, FC 02, address 0, length 1. If the third-party tool reads correctly, the issue is the UWA manifest or EasyModbus configuration.
  4. Wireshark capture. Filter on tcp.port == 502. You should see a Modbus TCP request (MBAP header + function code 02) followed by a valid response. If the server resets the connection (TCP RST), the LOGO! 8 is rejecting the request — most often because the address is out of range.
  5. LOGO! diagnostic page. The LOGO! 8 web UI includes a "Status" or "Diagnostics" section that shows the current number of active Modbus connections.

11. Troubleshooting Matrix

Symptom Likely Cause Confirm With Resolution
IOException on every call, including Connect() UWP manifest missing privateNetworkClientServer Inspect Package.appxmanifest Add capability, clean and redeploy
IOException with "connection refused" Modbus TCP server not enabled or wrong port LOGO! web UI Modbus page; Test-NetConnection Enable server in LOGO! Soft Comfort; verify port 502
Connect succeeds, first ReadDiscreteInputs fails Address out of LOGO! 8 range, or using FC 02 where FC 01 is needed (or vice versa) Address mapping table in this article; cross-check with QModMaster Switch function code; correct address offset
Works for 1–5 minutes, then IOException Server-side idle timeout; socket closed by LOGO! Wireshark shows TCP FIN/RST from LOGO! Implement auto-reconnect; lower poll interval
IOException with WSAETIMEDOUT ReadTimeout too aggressive for LAN latency Time read duration with Stopwatch Raise ConnectionTimeout to 2000–3000 ms
IOException with "No such host is known" DNS or hostname instead of IP Use IP literal Pass IP literal to ModbusClient
Reads work from console app but not UWA UWP capability + AppContainer networking restriction Compare manifest against sample above Add privateNetworkClientServer capability
Reads return all false / all true Wrong byte order or wrong FC for the address class QModMaster cross-check Use FC 02 for inputs, FC 01 for outputs

12. Field-Proven Caveats

  • EasyModbusTCP version 5.x uses synchronous socket reads. Long-running polls on a UWA UI thread will freeze the UI. Always wrap in Task.Run.
  • Some UWA project templates generate Package.appxmanifest with only the internetClient capability. Adding privateNetworkClientServer alone is enough for Modbus TCP to the LOGO!; internetClientServer is not required.
  • When debugging UWP networking, run the app under the Local Machine or Remote Machine debug target. The Simulator target does not have a real network stack and will mask the IOException.
  • The LOGO! 8 firmware resets the Modbus server's active connection list on every program download. Force the C# client to reconnect after any download from LOGO! Soft Comfort.
  • If multiple Modbus masters connect to the same LOGO!, the device allocates a small fixed connection pool. Exhausting this pool causes the LOGO! to silently drop new TCP connection attempts; the client then sees IOException on Connect() after a SYN-ACK timeout.

Frequently Asked Questions

What TCP port does the Siemens LOGO! 8 use for Modbus TCP?

The default port is 502, which is the IANA-registered Modbus TCP port. The port can be remapped in LOGO! Soft Comfort under the Ethernet > Server Connection settings. Verify the active port through the LOGO! 8 web UI or a Test-NetConnection -Port probe before debugging client code.

Why does System.IO.IOException occur specifically in UWP/UWA apps but not in console apps?

UWP apps run inside an AppContainer sandbox and must explicitly declare the privateNetworkClientServer capability in Package.appxmanifest. Without this capability, the AppContainer blocks TCP socket creation on the LAN, and TcpClient.Connect() throws System.IO.IOException. Console apps run under the user's full token and have no such restriction.

What Modbus address should I use to read LOGO! digital input I1?

Per the LOGO! 8 System Manual, I1 maps to discrete input address 0 using Modbus function code 02 (Read Discrete Inputs). I2 is address 1, I3 is address 2, and so on through I24 at address 23. Some older documentation lists 1-based addresses; if you read all false values, try address 1 and confirm against a known input state.

How do I read LOGO! 8 outputs Q1 and Q2 over Modbus?

Use Modbus function code 01 (Read Coils) at address 0 for Q1 and address 1 for Q2. The same function code with addresses 0–19 covers all 20 digital outputs. Use function code 05 or 15 to write outputs back to the LOGO!.

Should I use EasyModbus or NModbus for LOGO! 8 communication?

EasyModbusTCP is fine for simple read-only polling on a UWA once the manifest capability is added. NModbus is preferred when you need finer control over transport timeouts, explicit reconnect logic, or non-UWP host processes. Both implement the same Modbus TCP wire format and address map, so swapping libraries does not require changes to the LOGO! configuration.

Back to blog