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.
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.
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
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:
- IP address is on the same subnet as the Windows host running the UWA.
- Subnet mask and default gateway are set (gateway optional for direct connection).
- 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.
- TCP port is 502 unless intentionally remapped.
- No more than the documented maximum number of Modbus clients are connected simultaneously.
- 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:
- Use a singleton
ModbusClientwrapped by a reconnection helper. - On any
IOException, callDisconnect()and rebuild the client. - Set
ConnectionTimeoutto 1000–3000 ms. Values below 500 ms cause flaky behavior on switched networks. - Do not share a single
ModbusClientinstance across UWP pages. UWP may suspend the page and tear down the underlying socket; recreate the client on eachOnNavigatedTo. - 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
-
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. -
Modbus TCP check. From the host's command line, run
Test-NetConnection -ComputerName 192.168.0.10 -Port 502in PowerShell. Expected output:TcpTestSucceeded : True. If false, the port is closed or filtered. - 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.
-
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. - 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.appxmanifestwith only theinternetClientcapability. AddingprivateNetworkClientServeralone is enough for Modbus TCP to the LOGO!;internetClientServeris 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.