Skip to content

Commit 5d52466

Browse files
feat(nm): Added modem reset timer when connection is failed (#5644)
* First implementation of failed modem reset Signed-off-by: pierantoniomerlino <[email protected]> * Improved failed modem reset Signed-off-by: pierantoniomerlino <[email protected]> * Minimal improvemet Signed-off-by: pierantoniomerlino <[email protected]> * Added test for failed modem reset handler Signed-off-by: pierantoniomerlino <[email protected]> * Moved reset handlers to ModemManagerDbusWrapper; added tests Signed-off-by: pierantoniomerlino <[email protected]> * Fixed copyright Signed-off-by: pierantoniomerlino <[email protected]> * Several improvements on failedModemResetTimer Signed-off-by: pierantoniomerlino <[email protected]> * Changed visibility of to logger protected Signed-off-by: pierantoniomerlino <[email protected]> * Updated methods name to make them more clear Signed-off-by: pierantoniomerlino <[email protected]> * Changed variable name Signed-off-by: pierantoniomerlino <[email protected]> * Changed variable name again Signed-off-by: pierantoniomerlino <[email protected]> --------- Signed-off-by: pierantoniomerlino <[email protected]>
1 parent 0ea4057 commit 5d52466

File tree

5 files changed

+174
-13
lines changed

5 files changed

+174
-13
lines changed

kura/org.eclipse.kura.nm/src/main/java/org/eclipse/kura/nm/ModemManagerDbusWrapper.java

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2023, 2024 Eurotech and/or its affiliates and others
2+
* Copyright (c) 2023, 2025 Eurotech and/or its affiliates and others
33
*
44
* This program and the accompanying materials are made
55
* available under the terms of the Eclipse Public License 2.0
@@ -20,10 +20,12 @@
2020
import java.util.Objects;
2121
import java.util.Optional;
2222
import java.util.Set;
23+
import java.util.Timer;
2324

2425
import org.eclipse.kura.nm.enums.MMModemLocationSource;
2526
import org.eclipse.kura.nm.enums.MMModemState;
2627
import org.eclipse.kura.nm.signal.handlers.NMModemResetHandler;
28+
import org.eclipse.kura.nm.signal.handlers.NMModemResetTimerTask;
2729
import org.eclipse.kura.nm.status.SimProperties;
2830
import org.freedesktop.dbus.DBusPath;
2931
import org.freedesktop.dbus.connections.impl.DBusConnection;
@@ -49,6 +51,7 @@ public class ModemManagerDbusWrapper {
4951
private final DBusConnection dbusConnection;
5052

5153
private final Map<String, NMModemResetHandler> modemHandlers = new HashMap<>();
54+
private final Map<String, MMFailedModemResetTimer> failedModemResetTimers = new HashMap<>();
5255

5356
public ModemManagerDbusWrapper(DBusConnection dbusConnection) {
5457
this.dbusConnection = dbusConnection;
@@ -142,6 +145,15 @@ public MMModemState getMMModemState(Properties modemProperties) {
142145
return MMModemState.toMMModemState(modemProperties.Get(MM_MODEM_NAME, MM_MODEM_PROPERTY_STATE));
143146
}
144147

148+
public MMModemState getMMModemState(String modemPath) throws DBusException {
149+
Optional<Properties> properties = getModemProperties(modemPath);
150+
if (properties.isPresent()) {
151+
return getMMModemState(properties.get());
152+
} else {
153+
return MMModemState.MM_MODEM_STATE_UNKNOWN;
154+
}
155+
}
156+
145157
public Optional<Properties> getModemProperties(String modemPath) throws DBusException {
146158
Optional<Properties> modemProperties = Optional.empty();
147159
Properties properties = this.dbusConnection.getRemoteObject(MM_BUS_NAME, modemPath, Properties.class);
@@ -275,4 +287,82 @@ protected void resetHandlersDisable(String deviceId) {
275287
this.modemHandlers.remove(deviceId);
276288
}
277289
}
290+
291+
protected void failedModemResetTimerSchedule(String deviceId, Optional<String> modemManagerDbusPath, int delayMinutes)
292+
throws DBusException {
293+
if (!modemManagerDbusPath.isPresent()) {
294+
logger.warn("Cannot retrieve modem device for {}. Skipping modem reset monitor setup.", deviceId);
295+
return;
296+
}
297+
298+
Modem mmModemDevice = this.dbusConnection.getRemoteObject(MM_BUS_NAME, modemManagerDbusPath.get(), Modem.class);
299+
300+
MMFailedModemResetTimer resetTimer = new MMFailedModemResetTimer(mmModemDevice, delayMinutes);
301+
resetTimer.schedule();
302+
303+
this.failedModemResetTimers.put(deviceId, resetTimer);
304+
}
305+
306+
protected void failedModemResetTimerCancel() {
307+
for (String deviceId : this.failedModemResetTimers.keySet()) {
308+
failedModemResetTimerCancel(deviceId);
309+
}
310+
this.modemHandlers.clear();
311+
}
312+
313+
protected void failedModemResetTimerCancel(String deviceId) {
314+
if (this.failedModemResetTimers.containsKey(deviceId)) {
315+
MMFailedModemResetTimer timer = this.failedModemResetTimers.get(deviceId);
316+
timer.cancel();
317+
}
318+
}
319+
320+
protected boolean isMMFailedModemResetTimerArmed(String deviceId) {
321+
return this.failedModemResetTimers.containsKey(deviceId);
322+
}
323+
324+
private class MMFailedModemResetTimerTask extends NMModemResetTimerTask {
325+
326+
public MMFailedModemResetTimerTask(Modem modem) {
327+
super(modem);
328+
}
329+
330+
@Override
331+
public void run() {
332+
try {
333+
MMModemState modemState = getMMModemState(this.getModemDbusPath());
334+
if (MMModemState.MM_MODEM_STATE_FAILED.equals(modemState)) {
335+
super.run();
336+
} else {
337+
NMModemResetTimerTask.logger.info("Modem state changed. Reset skipped.");
338+
}
339+
} catch (DBusException e) {
340+
NMModemResetTimerTask.logger.warn("Couldn't get state of modem interface, caused by:", e);
341+
}
342+
}
343+
344+
}
345+
346+
private class MMFailedModemResetTimer {
347+
348+
private final Timer timer = new Timer("FailedModemResetTimer");
349+
private final MMFailedModemResetTimerTask task;
350+
private final long delay;
351+
352+
public MMFailedModemResetTimer(Modem modem, long delayMinutes) {
353+
this.delay = delayMinutes * 60L * 1000L;
354+
this.task = new MMFailedModemResetTimerTask(modem);
355+
}
356+
357+
public void schedule() {
358+
this.timer.schedule(this.task, this.delay);
359+
}
360+
361+
public void cancel() {
362+
if (this.task != null) {
363+
this.task.cancel();
364+
}
365+
this.timer.cancel();
366+
}
367+
}
278368
}

kura/org.eclipse.kura.nm/src/main/java/org/eclipse/kura/nm/NMDbusConnector.java

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2023, 2024 Eurotech and/or its affiliates and others
2+
* Copyright (c) 2023, 2025 Eurotech and/or its affiliates and others
33
*
44
* This program and the accompanying materials are made
55
* available under the terms of the Eclipse Public License 2.0
@@ -35,6 +35,7 @@
3535
import org.eclipse.kura.net.wifi.WifiSecurity;
3636
import org.eclipse.kura.nm.configuration.NMSettingsConverter;
3737
import org.eclipse.kura.nm.enums.MMModemLocationSource;
38+
import org.eclipse.kura.nm.enums.MMModemState;
3839
import org.eclipse.kura.nm.enums.NMDeviceState;
3940
import org.eclipse.kura.nm.enums.NMDeviceType;
4041
import org.eclipse.kura.nm.signal.handlers.DeviceCreationLock;
@@ -133,11 +134,15 @@ public void setSystemService(SystemService systemService) {
133134
this.optionalSystemService = Optional.of(systemService);
134135
}
135136

136-
public boolean configurationEnforcementIsActive() {
137+
protected boolean configurationEnforcementIsActive() {
137138
return Objects.nonNull(this.configurationEnforcementHandler) && Objects.nonNull(this.deviceAddedHandler)
138139
&& this.configurationEnforcementHandlerIsArmed;
139140
}
140141

142+
protected boolean failedModemResetTimerIsActive(String modemId) {
143+
return this.modemManager.isMMFailedModemResetTimerArmed(modemId);
144+
}
145+
141146
public void checkPermissions() {
142147
Map<String, String> getPermissions = this.networkManager.getPermissions();
143148
if (logger.isDebugEnabled()) {
@@ -396,7 +401,7 @@ private synchronized void doApply(Map<String, Object> networkConfiguration) thro
396401
logger.info("Applying configuration using NetworkManager Dbus connector");
397402
NetworkProperties properties = new NetworkProperties(networkConfiguration);
398403
List<String> availableDeviceIds = getInterfaceIds();
399-
Set<String> availableDevices = new LinkedHashSet<String>(availableDeviceIds);
404+
Set<String> availableDevices = new LinkedHashSet<>(availableDeviceIds);
400405
Optional<List<String>> configuredInterfaceIds = properties.getOptStringList("net.interfaces");
401406
if (configuredInterfaceIds.isPresent()) {
402407
availableDevices.addAll(configuredInterfaceIds.get());
@@ -556,11 +561,14 @@ private void enableInterface(String deviceId, NetworkProperties properties, Devi
556561
connection = Optional.of(createdConnection);
557562
}
558563

564+
boolean activationSucceeded = true;
559565
try {
566+
this.modemManager.failedModemResetTimerCancel(deviceId);
560567
this.networkManager.activateConnection(connection.get(), device);
561568
dsLock.waitForSignal();
562569
} catch (DBusExecutionException e) {
563570
logger.warn("Couldn't complete activation of {} interface, caused by:", deviceId, e);
571+
activationSucceeded = false;
564572
}
565573

566574
// Housekeeping
@@ -573,15 +581,32 @@ private void enableInterface(String deviceId, NetworkProperties properties, Devi
573581

574582
if (deviceType == NMDeviceType.NM_DEVICE_TYPE_MODEM) {
575583
int delayMinutes = properties.get(Integer.class, "net.interface.%s.config.resetTimeout", deviceId);
584+
Optional<String> mmDbusPath = this.networkManager.getModemManagerDbusPath(device.getObjectPath());
585+
586+
if (delayMinutes == 0 || !mmDbusPath.isPresent()) {
587+
return;
588+
}
576589

577-
if (delayMinutes != 0) {
578-
Optional<String> mmDbusPath = this.networkManager.getModemManagerDbusPath(device.getObjectPath());
579-
this.modemManager.resetHandlerEnable(deviceId, mmDbusPath, delayMinutes, device.getObjectPath());
590+
this.modemManager.resetHandlerEnable(deviceId, mmDbusPath, delayMinutes, device.getObjectPath());
591+
592+
// If a modem connection fails at the first try, it stays in the failed state, thus not triggering the usual
593+
// modem reset procedure. So, start a reset timer here.
594+
if (!activationSucceeded && isModemFailed(mmDbusPath.get())) {
595+
logger.info("Modem {} in failed state or unavailable. Scheduling modem reset in {} minutes ...",
596+
device.getObjectPath(), delayMinutes);
597+
598+
this.modemManager.failedModemResetTimerSchedule(deviceId, mmDbusPath, delayMinutes);
580599
}
600+
581601
}
582602

583603
}
584604

605+
private boolean isModemFailed(String mmDbusPath) throws DBusException {
606+
MMModemState modemState = this.modemManager.getMMModemState(mmDbusPath);
607+
return MMModemState.MM_MODEM_STATE_FAILED.equals(modemState);
608+
}
609+
585610
private void createVirtualInterface(String deviceId, NetworkProperties properties, NMDeviceType deviceType)
586611
throws DBusException {
587612
Map<String, Map<String, Variant<?>>> newConnectionSettings = NMSettingsConverter.buildSettings(properties,
@@ -643,6 +668,7 @@ private void disable(Optional<Device> optDevice, String deviceId) throws DBusExc
643668
logger.warn("Can't disable missing device {}", deviceId);
644669
return;
645670
}
671+
this.modemManager.failedModemResetTimerCancel(deviceId);
646672
Device device = optDevice.get();
647673
Optional<Connection> appliedConnection = this.networkManager.getAppliedConnection(device);
648674

@@ -730,4 +756,5 @@ private List<String> getModemsPaths() {
730756

731757
return modemsPath;
732758
}
759+
733760
}

kura/org.eclipse.kura.nm/src/main/java/org/eclipse/kura/nm/signal/handlers/NMModemResetHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2023 Eurotech and/or its affiliates and others
2+
* Copyright (c) 2023, 2025 Eurotech and/or its affiliates and others
33
*
44
* This program and the accompanying materials are made
55
* available under the terms of the Eclipse Public License 2.0

kura/org.eclipse.kura.nm/src/main/java/org/eclipse/kura/nm/signal/handlers/NMModemResetTimerTask.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2023 Eurotech and/or its affiliates and others
2+
* Copyright (c) 2023, 2025 Eurotech and/or its affiliates and others
33
*
44
* This program and the accompanying materials are made
55
* available under the terms of the Eclipse Public License 2.0
@@ -22,7 +22,7 @@
2222

2323
public class NMModemResetTimerTask extends TimerTask {
2424

25-
private static final Logger logger = LoggerFactory.getLogger(NMModemResetTimerTask.class);
25+
protected static final Logger logger = LoggerFactory.getLogger(NMModemResetTimerTask.class);
2626

2727
private final Modem modem;
2828
private boolean expired = false;

kura/test/org.eclipse.kura.nm.test/src/test/java/org/eclipse/kura/nm/NMDbusConnectorTest.java

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2023, 2024 Eurotech and/or its affiliates and others
2+
* Copyright (c) 2023, 2025 Eurotech and/or its affiliates and others
33
*
44
* This program and the accompanying materials are made
55
* available under the terms of the Eclipse Public License 2.0
@@ -1127,6 +1127,41 @@ public void shouldNotApplyWPA2WPA3WiFiConfigurationIfWPA3IsNotSupported() throws
11271127
thenActivateConnectionIsNotCalledFor("wlan0");
11281128
}
11291129

1130+
@Test
1131+
public void shouldNotStartFailedModemResetTimerIfConnectionSucceeds() throws DBusException, IOException {
1132+
givenBasicMockedDbusConnector();
1133+
givenMockedDevice("1-6", "wwan0", NMDeviceType.NM_DEVICE_TYPE_MODEM, NMDeviceState.NM_DEVICE_STATE_ACTIVATED,
1134+
true, false, false);
1135+
givenMockedDeviceList();
1136+
givenNetworkConfigMapWith("net.interfaces", "1-6");
1137+
givenNetworkConfigMapWith("net.interface.1-6.config.resetTimeout", 2);
1138+
givenNetworkConfigMapWith("net.interface.1-6.config.dhcpClient4.enabled", true);
1139+
givenNetworkConfigMapWith("net.interface.1-6.config.ip4.status", "netIPv4StatusEnabledWAN");
1140+
1141+
whenApplyIsCalledWith(this.netConfig);
1142+
1143+
thenNoExceptionIsThrown();
1144+
thenFailedModemResetTimerIsActive(false, "1-6");
1145+
}
1146+
1147+
@Test
1148+
public void shouldStartFailedModemResetTimerIfConnectionFails() throws DBusException, IOException {
1149+
givenBasicMockedDbusConnector();
1150+
givenNMActivationFailed();
1151+
givenMockedDevice("1-6", "wwan0", NMDeviceType.NM_DEVICE_TYPE_MODEM, NMDeviceState.NM_DEVICE_STATE_FAILED, true,
1152+
false, false);
1153+
givenMockedDeviceList();
1154+
givenNetworkConfigMapWith("net.interfaces", "1-6");
1155+
givenNetworkConfigMapWith("net.interface.1-6.config.resetTimeout", 2);
1156+
givenNetworkConfigMapWith("net.interface.1-6.config.dhcpClient4.enabled", true);
1157+
givenNetworkConfigMapWith("net.interface.1-6.config.ip4.status", "netIPv4StatusEnabledWAN");
1158+
1159+
whenApplyIsCalledWith(this.netConfig);
1160+
1161+
thenNoExceptionIsThrown();
1162+
thenFailedModemResetTimerIsActive(true, "1-6");
1163+
}
1164+
11301165
/*
11311166
* Given
11321167
*/
@@ -1154,6 +1189,11 @@ private void givenBasicMockedDbusConnector() throws DBusException, IOException {
11541189

11551190
}
11561191

1192+
private void givenNMActivationFailed() {
1193+
when(this.mockedNetworkManager.ActivateConnection(any(), any(), any()))
1194+
.thenThrow(new DBusExecutionException("Activation Failed!"));
1195+
}
1196+
11571197
private void givenMockedPermissions() {
11581198

11591199
Map<String, String> tempPerms = new HashMap<>();
@@ -1426,7 +1466,7 @@ private void givenModemMocksFor(String deviceId, String interfaceName, Propertie
14261466
.thenReturn(Arrays.asList(new UInt32[] { new UInt32(40), new UInt32(69) }));
14271467
when(modemProperties.Get(MM_MODEM_BUS_NAME, "PrimarySimSlot")).thenReturn(new UInt32(0));
14281468
when(modemProperties.Get(MM_MODEM_BUS_NAME, "UnlockRequired")).thenReturn(new UInt32(1));
1429-
when(modemProperties.Get(MM_MODEM_BUS_NAME, "State")).thenReturn(8);
1469+
when(modemProperties.Get(MM_MODEM_BUS_NAME, "State")).thenReturn(-1);
14301470
when(modemProperties.Get(MM_MODEM_BUS_NAME, "AccessTechnologies")).thenReturn(new UInt32(0));
14311471
when(modemProperties.Get(MM_MODEM_BUS_NAME, "SignalQuality"))
14321472
.thenReturn(new UInt32[] { new UInt32(97), new UInt32(2) });
@@ -1813,7 +1853,7 @@ private void thenModemStatusHasCorrectValues(boolean hasBearers, boolean hasSims
18131853
assertTrue(modemStatus.isGpsSupported());
18141854
assertEquals(EnumSet.of(ModemGpsMode.UNMANAGED, ModemGpsMode.MANAGED_GPS), modemStatus.getSupporteGpsModes());
18151855
assertFalse(modemStatus.isSimLocked());
1816-
assertEquals(ModemConnectionStatus.REGISTERED, modemStatus.getConnectionStatus());
1856+
assertEquals(ModemConnectionStatus.FAILED, modemStatus.getConnectionStatus());
18171857
assertEquals(1, modemStatus.getAccessTechnologies().size());
18181858
assertTrue(modemStatus.getAccessTechnologies().contains(AccessTechnology.UNKNOWN));
18191859
assertEquals(97, modemStatus.getSignalQuality());
@@ -1851,6 +1891,10 @@ private void thenDeviceExists(String interfaceName) {
18511891
assertTrue(this.mockDevices.containsKey(interfaceName));
18521892
}
18531893

1894+
public void thenFailedModemResetTimerIsActive(boolean expectedValue, String modemId) {
1895+
assertEquals(expectedValue, this.instanceNMDbusConnector.failedModemResetTimerIsActive(modemId));
1896+
}
1897+
18541898
private void simulateIwCommandOutputs(String interfaceName, Properties preMockedProperties)
18551899
throws IOException, DBusException {
18561900
Wireless wirelessDevice = mock(Wireless.class);

0 commit comments

Comments
 (0)