diff --git a/README.md b/README.md
index 861d945..156c3ee 100644
--- a/README.md
+++ b/README.md
@@ -12,6 +12,57 @@
|:-|---|---|
| System.Net | [](https://dev.azure.com/nanoframework/System.Net/_build/latest?definitionId=20&repoName=nanoframework%2FSystem.Net&branchName=main) | [](https://www.nuget.org/packages/nanoFramework.System.Net/) |
+## NetworkHelper usage
+
+`NetworkHelper` provides two patterns for establishing a network connection: a blocking token-based approach for simple use-cases, and an event-based approach for background connection management.
+
+### Token-based (retryable)
+
+Call `SetupAndConnectNetwork` with a `CancellationToken` timeout. This method can be called repeatedly — if the first attempt times out, call it again:
+
+```csharp
+bool connected = false;
+while (!connected)
+{
+ CancellationTokenSource cs = new(30000);
+ connected = NetworkHelper.SetupAndConnectNetwork(requiresDateTime: true, token: cs.Token);
+ if (!connected)
+ {
+ Debug.WriteLine($"Network not ready, status: {NetworkHelper.Status}");
+ // wait before retrying
+ Thread.Sleep(5000);
+ }
+}
+```
+
+### Event-based
+
+Call `SetupNetworkHelper` once at startup. The helper connects in the background. Wait on `NetworkReady`:
+
+```csharp
+NetworkHelper.SetupNetworkHelper(requiresDateTime: true);
+
+if (!NetworkHelper.NetworkReady.WaitOne(30000, true))
+{
+ Debug.WriteLine($"Failed to connect: {NetworkHelper.Status}");
+}
+```
+
+> **Note:** `NetworkReady` is reset when the connection is lost and re-signaled when it is restored, accurately reflecting live network state. Code that previously assumed `NetworkReady` would remain set after first connect should be updated to handle transient disconnects.
+
+### Reset and reconfigure
+
+Call `Reset()` to fully reset the helper so it can be called again with different settings, or to restart after an error:
+
+```csharp
+NetworkHelper.Reset();
+
+// Now call SetupNetworkHelper or SetupAndConnectNetwork again
+NetworkHelper.SetupNetworkHelper(requiresDateTime: true);
+```
+
+`SetupNetworkHelper` throws `InvalidOperationException` if called a second time without a prior `Reset()`. Token-based methods (`SetupAndConnectNetwork`) do not have this restriction and are always retryable.
+
## Feedback and documentation
For documentation, providing feedback, issues and finding out how to contribute please refer to the [Home repo](https://github.com/nanoframework/Home).
diff --git a/Tests/NetworkHelperTests/ConnectToEthernetTests.cs b/Tests/NetworkHelperTests/ConnectToEthernetTests.cs
index ecdb406..7a55754 100644
--- a/Tests/NetworkHelperTests/ConnectToEthernetTests.cs
+++ b/Tests/NetworkHelperTests/ConnectToEthernetTests.cs
@@ -31,8 +31,7 @@ public void TestFixedIPAddress_01()
Assert.IsTrue(success);
- // need to reset this internal flag to allow calling the NetworkHelper again
- NetworkHelper.ResetInstance();
+ NetworkHelper.Reset();
}
[TestMethod]
@@ -47,8 +46,7 @@ public void TestFixedIPAddress_02()
// wait 10 seconds to connect to the network
Assert.IsTrue(NetworkHelper.NetworkReady.WaitOne(10000, true));
- // need to reset this internal flag to allow calling the NetworkHelper again
- NetworkHelper.ResetInstance();
+ NetworkHelper.Reset();
}
[TestMethod]
@@ -64,8 +62,7 @@ public void TestDhcp_01()
Assert.IsTrue(success);
- // need to reset this internal flag to allow calling the NetworkHelper again
- NetworkHelper.ResetInstance();
+ NetworkHelper.Reset();
}
[TestMethod]
@@ -76,8 +73,7 @@ public void TestDhcp_02()
// wait 10 seconds to connect to the network and get an IP address
Assert.IsTrue(NetworkHelper.NetworkReady.WaitOne(10000, true));
- // need to reset this internal flag to allow calling the NetworkHelper again
- NetworkHelper.ResetInstance();
+ NetworkHelper.Reset();
}
[TestMethod]
@@ -88,9 +84,51 @@ public void TestSingleUsage()
// call once, it's OK
NetworkHelper.SetupNetworkHelper();
- // call twice, it's a NO NO and should throw an exception
+ // call twice without Reset — must throw
NetworkHelper.SetupNetworkHelper();
});
+
+ NetworkHelper.Reset();
+ }
+
+ [TestMethod]
+ public void TestRetryAfterTimeout()
+ {
+ // First attempt: very short timeout so it expires
+ CancellationTokenSource cs1 = new(1000);
+ var firstResult = NetworkHelper.SetupAndConnectNetwork(token: cs1.Token);
+
+ Assert.IsFalse(firstResult, "First call should have timed out");
+ Assert.IsTrue(NetworkHelper.Status == NetworkHelperStatus.TokenExpiredWaitingIPAddress);
+
+ // Second attempt: longer timeout — must not throw InvalidOperationException
+ CancellationTokenSource cs2 = new(10000);
+ var secondResult = NetworkHelper.SetupAndConnectNetwork(token: cs2.Token);
+
+ // If there is a network, second attempt should succeed;
+ // if not, it will time out again — either way, it must NOT throw
+ Assert.IsTrue(
+ NetworkHelper.Status == NetworkHelperStatus.NetworkIsReady ||
+ NetworkHelper.Status == NetworkHelperStatus.TokenExpiredWaitingIPAddress ||
+ NetworkHelper.Status == NetworkHelperStatus.TokenExpiredWaitingDateTime,
+ "Expected a terminal status after the second attempt");
+
+ NetworkHelper.Reset();
+ }
+
+ [TestMethod]
+ public void TestResetAllowsSetupNetworkHelperRestart()
+ {
+ NetworkHelper.SetupNetworkHelper();
+
+ // Reset and call again — must not throw
+ NetworkHelper.Reset();
+ NetworkHelper.SetupNetworkHelper();
+
+ // wait briefly
+ NetworkHelper.NetworkReady.WaitOne(5000, true);
+
+ NetworkHelper.Reset();
}
public void DisplayLastError(bool success)
diff --git a/nanoFramework.System.Net/NetworkHelper/NetworkHelper.cs b/nanoFramework.System.Net/NetworkHelper/NetworkHelper.cs
index 13a5f29..04696d3 100644
--- a/nanoFramework.System.Net/NetworkHelper/NetworkHelper.cs
+++ b/nanoFramework.System.Net/NetworkHelper/NetworkHelper.cs
@@ -27,8 +27,11 @@ public static class NetworkHelper
private static IPConfiguration _ipConfiguration;
+ private static Thread _workerThread;
+ private static bool _stopRequested;
+
///
- /// This flag will make sure there is only one and only call to any of the helper methods.
+ /// This flag will make sure there is only one and only one call to the event-based helper methods.
///
private static bool _helperInstanciated = false;
@@ -37,7 +40,12 @@ public static class NetworkHelper
///
///
/// The conditions for this are setup in the call to .
- /// It will be a composition of network connected, IpAddress available and valid system .
+ /// It will be a composition of network connected, IpAddress available and valid system .
+ ///
+ /// When using , this event is reset when the connection is lost
+ /// and re-signaled when it is restored, accurately reflecting live network state.
+ ///
+ ///
public static ManualResetEvent NetworkReady => _networkReady;
///
@@ -55,7 +63,7 @@ public static class NetworkHelper
/// That will be the network connection to be up, having a valid IpAddress and optionally for a valid date and time to become available.
///
/// Set to if valid date and time are required.
- /// If any of the methods is called more than once.
+ /// If called more than once without an intervening call to .
/// There is no network interface configured. Open the 'Edit Network Configuration' in Device Explorer and configure one.
public static void SetupNetworkHelper(bool requiresDateTime = false)
{
@@ -64,7 +72,8 @@ public static void SetupNetworkHelper(bool requiresDateTime = false)
SetupHelper(true);
// fire working thread
- new Thread(WorkingThread).Start();
+ _workerThread = new Thread(WorkingThread);
+ _workerThread.Start();
}
///
@@ -73,6 +82,7 @@ public static void SetupNetworkHelper(bool requiresDateTime = false)
///
/// The static IP configuration you want to apply.
/// Set to if valid date and time are required.
+ /// If called more than once without an intervening call to .
/// There is no network interface configured. Open the 'Edit Network Configuration' in Device Explorer and configure one.
public static void SetupNetworkHelper(
IPConfiguration ipConfiguration,
@@ -84,11 +94,13 @@ public static void SetupNetworkHelper(
SetupHelper(true);
// fire working thread
- new Thread(WorkingThread).Start();
+ _workerThread = new Thread(WorkingThread);
+ _workerThread.Start();
}
///
/// This will wait for the network connection to be up and optionally for a valid date and time to become available.
+ /// This method is retryable and can be called multiple times after a previous call times out or fails.
///
/// A used for timing out the operation.
/// Set to if valid date and time are required.
@@ -102,6 +114,7 @@ public static bool SetupAndConnectNetwork(
///
/// This will wait for the network connection to be up and optionally for a valid date and time to become available.
+ /// This method is retryable and can be called multiple times after a previous call times out or fails.
///
/// The static IPv4 configuration to apply to the Ethernet network interface.
/// A used for timing out the operation.
@@ -122,6 +135,44 @@ public static bool SetupAndConnectNetwork(
requiresDateTime);
}
+ ///
+ /// Resets the to its initial state, allowing to be called again
+ /// or the network configuration to be changed.
+ ///
+ ///
+ /// Call this before switching network configuration or restarting the event-based helper.
+ /// This method does not disconnect the network interface or alter IP settings.
+ ///
+ public static void Reset()
+ {
+ // deregister event handler to prevent a handler leak
+ NetworkChange.NetworkAddressChanged -= AddressChangedCallback;
+
+ // signal the worker thread to stop and unblock it if it is waiting for an IP address
+ _stopRequested = true;
+
+ if (_ipAddressAvailable != null)
+ {
+ _ipAddressAvailable.Set();
+ }
+
+ if (_workerThread != null)
+ {
+ // give the thread a moment to exit cleanly before clearing shared state
+ _workerThread.Join(1000);
+ _workerThread = null;
+ }
+
+ _stopRequested = false;
+ _helperInstanciated = false;
+ _ipAddressAvailable = null;
+ _networkReady = new(false);
+ _requiresDateTime = false;
+ _networkHelperStatus = NetworkHelperStatus.None;
+ _helperException = null;
+ _ipConfiguration = null;
+ }
+
internal static bool InternalWaitNetworkAvailable(
NetworkInterfaceType networkInterface,
ref NetworkHelperStatus helperStatus,
@@ -194,20 +245,32 @@ internal static bool InternalWaitNetworkAvailable(
private static void WorkingThread()
{
// check if we have an IP
- if(!NetworkHelperInternal.CheckIP(
+ if (!NetworkHelperInternal.CheckIP(
_workingNetworkInterface,
_ipConfiguration))
{
- // wait here until we have an IP address
+ // wait here until we have an IP address or until Reset() unblocks us
_ipAddressAvailable.WaitOne();
}
+ // bail out if Reset() was called while we were waiting
+ if (_stopRequested)
+ {
+ return;
+ }
+
if (_requiresDateTime)
{
// wait until there is a valid DateTime
NetworkHelperInternal.WaitForValidDateTime();
}
+ // bail out if Reset() was called during the DateTime wait
+ if (_stopRequested)
+ {
+ return;
+ }
+
// all conditions met
_networkReady.Set();
@@ -217,68 +280,73 @@ private static void WorkingThread()
private static void AddressChangedCallback(object sender, EventArgs e)
{
- if(NetworkHelperInternal.CheckIP(
+ if (_stopRequested)
+ {
+ return;
+ }
+
+ if (NetworkHelperInternal.CheckIP(
_workingNetworkInterface,
_ipConfiguration))
{
_ipAddressAvailable.Set();
+
+ // re-signal ready; check DateTime condition in case it was required
+ if (!_requiresDateTime || DateTime.UtcNow.Year >= 2021)
+ {
+ _networkReady.Set();
+ _networkHelperStatus = NetworkHelperStatus.NetworkIsReady;
+ }
+ }
+ else
+ {
+ // IP was lost - reset signals so callers block until the connection is restored
+ _networkReady.Reset();
+ _ipAddressAvailable.Reset();
+ _networkHelperStatus = NetworkHelperStatus.Reconnecting;
}
}
///
/// Perform setup of the various fields and events, along with any of the required event handlers.
///
- /// Set true to setup the events. Required for the thread approach. Not required for the CancelationToken implementation.
+ /// Set to setup the events and background thread. Required for the event-based approach. Not required for the CancellationToken approach.
private static void SetupHelper(bool setupEvents)
{
- if (_helperInstanciated)
- {
- throw new InvalidOperationException();
- }
- else
+ if (setupEvents)
{
+ if (_helperInstanciated)
+ {
+ throw new InvalidOperationException();
+ }
+
// set flag
_helperInstanciated = true;
// setup event
_ipAddressAvailable = new(false);
+ }
- NetworkInterface[] nis = NetworkInterface.GetAllNetworkInterfaces();
+ NetworkInterface[] nis = NetworkInterface.GetAllNetworkInterfaces();
- if (setupEvents)
+ if (setupEvents)
+ {
+ // check if there are any network interfaces setup
+ if (nis.Length == 0)
{
- // check if there are any network interface setup
- if (nis.Length == 0)
- {
- _networkHelperStatus = NetworkHelperStatus.FailedNoNetworkInterface;
-
- throw new NotSupportedException();
- }
+ _networkHelperStatus = NetworkHelperStatus.FailedNoNetworkInterface;
- // setup handler
- NetworkChange.NetworkAddressChanged += new NetworkAddressChangedEventHandler(AddressChangedCallback);
+ throw new NotSupportedException();
}
- NetworkHelperInternal.InternalSetupHelper(nis, _workingNetworkInterface, _ipConfiguration);
-
- // update status
- _networkHelperStatus = NetworkHelperStatus.Started;
+ // setup handler
+ NetworkChange.NetworkAddressChanged += new NetworkAddressChangedEventHandler(AddressChangedCallback);
}
- }
-
- ///
- /// Method to reset internal fields to it's defaults
- /// ONLY TO BE USED BY UNIT TESTS
- ///
- internal static void ResetInstance()
- {
- _ipAddressAvailable = null;
- _networkReady = new(false);
- _requiresDateTime = false;
- _networkHelperStatus = NetworkHelperStatus.None;
- _helperException = null;
- _ipConfiguration = null;
- _helperInstanciated = false;
+
+ NetworkHelperInternal.InternalSetupHelper(nis, _workingNetworkInterface, _ipConfiguration);
+
+ // update status
+ _networkHelperStatus = NetworkHelperStatus.Started;
}
}
-}
+}
\ No newline at end of file
diff --git a/nanoFramework.System.Net/NetworkHelper/NetworkHelperStatus.cs b/nanoFramework.System.Net/NetworkHelper/NetworkHelperStatus.cs
index 6a4f077..57f7895 100644
--- a/nanoFramework.System.Net/NetworkHelper/NetworkHelperStatus.cs
+++ b/nanoFramework.System.Net/NetworkHelper/NetworkHelperStatus.cs
@@ -45,6 +45,11 @@ public enum NetworkHelperStatus
///
/// An exception occurred with waiting for the network to become ready. Check HelperException property to find the that was thrown.
///
- ExceptionOccurred
+ ExceptionOccurred,
+
+ ///
+ /// The network was previously ready but the IP address was lost. Waiting for the connection to be restored.
+ ///
+ Reconnecting
}
}